blob: fbbd0ca0971bbbbc8de8b45c3b9245530b938e6c [file] [log] [blame]
// Copyright (c) 2019, the Dart project authors. Please see the AUTHORS file
// for details. All rights reserved. Use of this source code is governed by a
// BSD-style license that can be found in the LICENSE file.
// @dart = 2.9
import 'package:kernel/ast.dart';
import 'package:kernel/target/targets.dart';
import 'constant_evaluator.dart';
import '../fasta_codes.dart'
show
templateConstEvalNegativeShift,
templateConstEvalTruncateError,
templateConstEvalZeroDivisor;
abstract class ConstantIntFolder {
final ConstantEvaluator evaluator;
ConstantIntFolder(this.evaluator);
factory ConstantIntFolder.forSemantics(
ConstantEvaluator evaluator, NumberSemantics semantics) {
if (semantics == NumberSemantics.js) {
return new JsConstantIntFolder(evaluator);
} else {
return new VmConstantIntFolder(evaluator);
}
}
bool isInt(Constant constant);
Constant makeIntConstant(int value, {bool unsigned: false});
Constant foldUnaryOperator(
Expression node, String op, covariant Constant operand);
Constant foldBinaryOperator(Expression node, String op,
covariant Constant left, covariant Constant right);
Constant truncatingDivide(Expression node, num left, num right);
/// Returns [null] on success and an error-"constant" on failure, as such the
/// return value should be checked.
AbortConstant _checkOperands(
Expression node, String op, num left, num right) {
if ((op == '<<' || op == '>>' || op == '>>>') && right < 0) {
return evaluator.createErrorConstant(node,
templateConstEvalNegativeShift.withArguments(op, '$left', '$right'));
}
if ((op == '%' || op == '~/') && right == 0) {
return evaluator.createErrorConstant(
node, templateConstEvalZeroDivisor.withArguments(op, '$left'));
}
return null;
}
}
class VmConstantIntFolder extends ConstantIntFolder {
VmConstantIntFolder(ConstantEvaluator evaluator) : super(evaluator);
@override
bool isInt(Constant constant) => constant is IntConstant;
@override
IntConstant makeIntConstant(int value, {bool unsigned: false}) {
return new IntConstant(value);
}
@override
Constant foldUnaryOperator(Expression node, String op, IntConstant operand) {
switch (op) {
case 'unary-':
return new IntConstant(-operand.value);
case '~':
return new IntConstant(~operand.value);
default:
// Probably unreachable.
return evaluator.createInvalidExpressionConstant(
node, "Invalid unary operator $op");
}
}
@override
Constant foldBinaryOperator(
Expression node, String op, IntConstant left, IntConstant right) {
int a = left.value;
int b = right.value;
AbortConstant error = _checkOperands(node, op, a, b);
if (error != null) return error;
switch (op) {
case '+':
return new IntConstant(a + b);
case '-':
return new IntConstant(a - b);
case '*':
return new IntConstant(a * b);
case '/':
return new DoubleConstant(a / b);
case '~/':
return new IntConstant(a ~/ b);
case '%':
return new IntConstant(a % b);
case '|':
return new IntConstant(a | b);
case '&':
return new IntConstant(a & b);
case '^':
return new IntConstant(a ^ b);
case '<<':
return new IntConstant(a << b);
case '>>':
return new IntConstant(a >> b);
case '>>>':
// Currently unreachable as int hasn't defined '>>>'.
int result = b >= 64 ? 0 : (a >> b) & ((1 << (64 - b)) - 1);
return new IntConstant(result);
case '<':
return evaluator.makeBoolConstant(a < b);
case '<=':
return evaluator.makeBoolConstant(a <= b);
case '>=':
return evaluator.makeBoolConstant(a >= b);
case '>':
return evaluator.makeBoolConstant(a > b);
default:
// Probably unreachable.
return evaluator.createInvalidExpressionConstant(
node, "Invalid binary operator $op");
}
}
@override
Constant truncatingDivide(Expression node, num left, num right) {
try {
return new IntConstant(left ~/ right);
} catch (e) {
return evaluator.createErrorConstant(node,
templateConstEvalTruncateError.withArguments('$left', '$right'));
}
}
}
class JsConstantIntFolder extends ConstantIntFolder {
JsConstantIntFolder(ConstantEvaluator evaluator) : super(evaluator);
static bool _valueIsInteger(double value) {
return value.isFinite && value.truncateToDouble() == value;
}
static int _truncate32(int value) => value & 0xFFFFFFFF;
static int _toUint32(double value) {
return new BigInt.from(value).toUnsigned(32).toInt();
}
@override
bool isInt(Constant constant) {
return constant is DoubleConstant && _valueIsInteger(constant.value);
}
@override
DoubleConstant makeIntConstant(int value, {bool unsigned: false}) {
double doubleValue = value.toDouble();
// Invalid assert: assert(doubleValue.toInt() == value);
if (unsigned) {
const double twoTo64 = 18446744073709551616.0;
if (value < 0) doubleValue += twoTo64;
}
return new DoubleConstant(doubleValue);
}
@override
Constant foldUnaryOperator(
Expression node, String op, DoubleConstant operand) {
switch (op) {
case 'unary-':
return new DoubleConstant(-operand.value);
case '~':
int intValue = _toUint32(operand.value);
return new DoubleConstant(_truncate32(~intValue).toDouble());
default:
// Probably unreachable.
return evaluator.createInvalidExpressionConstant(
node, "Invalid unary operator $op");
}
}
@override
Constant foldBinaryOperator(
Expression node, String op, DoubleConstant left, DoubleConstant right) {
double a = left.value;
double b = right.value;
AbortConstant error = _checkOperands(node, op, a, b);
if (error != null) return error;
switch (op) {
case '+':
return new DoubleConstant(a + b);
case '-':
return new DoubleConstant(a - b);
case '*':
return new DoubleConstant(a * b);
case '/':
return new DoubleConstant(a / b);
case '~/':
return truncatingDivide(node, a, b);
case '%':
return new DoubleConstant(a % b);
case '|':
return new DoubleConstant((_toUint32(a) | _toUint32(b)).toDouble());
case '&':
return new DoubleConstant((_toUint32(a) & _toUint32(b)).toDouble());
case '^':
return new DoubleConstant((_toUint32(a) ^ _toUint32(b)).toDouble());
case '<<':
int ai = _toUint32(a);
return new DoubleConstant(_truncate32(ai << b.toInt()).toDouble());
case '>>':
int ai = _toUint32(a);
if (a < 0) {
const int signBit = 0x80000000;
ai -= (ai & signBit) << 1;
}
return new DoubleConstant(_truncate32(ai >> b.toInt()).toDouble());
case '>>>':
// Currently unreachable as int hasn't defined '>>>'.
int ai = _toUint32(a);
return new DoubleConstant(_truncate32(ai >> b.toInt()).toDouble());
case '<':
return evaluator.makeBoolConstant(a < b);
case '<=':
return evaluator.makeBoolConstant(a <= b);
case '>=':
return evaluator.makeBoolConstant(a >= b);
case '>':
return evaluator.makeBoolConstant(a > b);
default:
// Probably unreachable.
return evaluator.createInvalidExpressionConstant(
node, "Invalid binary operator $op");
}
}
@override
Constant truncatingDivide(Expression node, num left, num right) {
double division = (left / right);
if (division.isNaN || division.isInfinite) {
return evaluator.createErrorConstant(node,
templateConstEvalTruncateError.withArguments('$left', '${right}'));
}
double result = division.truncateToDouble();
return new DoubleConstant(result == 0.0 ? 0.0 : result);
}
}