| // 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. |
| |
| import 'dart:core' hide MapEntry; |
| |
| 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( |
| MethodInvocation node, String op, covariant Constant operand); |
| |
| Constant foldBinaryOperator(MethodInvocation node, String op, |
| covariant Constant left, covariant Constant right); |
| |
| Constant truncatingDivide(MethodInvocation 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( |
| MethodInvocation 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( |
| MethodInvocation 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( |
| MethodInvocation 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(MethodInvocation 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( |
| MethodInvocation 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(MethodInvocation 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(MethodInvocation 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); |
| } |
| } |