| // Copyright (c) 2012, 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. |
| |
| /// Constant system following the semantics for Dart code that has been |
| /// compiled to JavaScript. |
| library dart2js.constant_system; |
| |
| import '../common_elements.dart' show CommonElements; |
| import '../elements/entities.dart'; |
| import '../elements/operators.dart'; |
| import '../elements/types.dart'; |
| import 'values.dart'; |
| |
| final _BITS32 = new BigInt.from(0xFFFFFFFF); |
| |
| const add = const AddOperation(); |
| const bitAnd = const BitAndOperation(); |
| const bitNot = const BitNotOperation(); |
| const bitOr = const BitOrOperation(); |
| const bitXor = const BitXorOperation(); |
| const booleanAnd = const BooleanAndOperation(); |
| const booleanOr = const BooleanOrOperation(); |
| const divide = const DivideOperation(); |
| const equal = const EqualsOperation(); |
| const greaterEqual = const GreaterEqualOperation(); |
| const greater = const GreaterOperation(); |
| const identity = const IdentityOperation(); |
| const ifNull = const IfNullOperation(); |
| const lessEqual = const LessEqualOperation(); |
| const less = const LessOperation(); |
| const modulo = const ModuloOperation(); |
| const multiply = const MultiplyOperation(); |
| const negate = const NegateOperation(); |
| const not = const NotOperation(); |
| const remainder = const RemainderOperation(); |
| const shiftLeft = const ShiftLeftOperation(); |
| const shiftRight = const ShiftRightOperation(); |
| const subtract = const SubtractOperation(); |
| const truncatingDivide = const TruncatingDivideOperation(); |
| const codeUnitAt = const CodeUnitAtOperation(); |
| const round = const RoundOperation(); |
| const abs = const UnfoldedUnaryOperation('abs'); |
| |
| /// Returns true if [value] will turn into NaN or infinity |
| /// at runtime. |
| bool _integerBecomesNanOrInfinity(BigInt value) { |
| double doubleValue = value.toDouble(); |
| return doubleValue.isNaN || doubleValue.isInfinite; |
| } |
| |
| NumConstantValue _convertToJavaScriptConstant(NumConstantValue constant) { |
| if (constant.isInt) { |
| IntConstantValue intConstant = constant; |
| BigInt intValue = intConstant.intValue; |
| if (_integerBecomesNanOrInfinity(intValue)) { |
| return new DoubleConstantValue(intValue.toDouble()); |
| } |
| // If the integer loses precision with JavaScript numbers, use |
| // the floored version JavaScript will use. |
| BigInt floorValue = new BigInt.from(intValue.toDouble()); |
| if (floorValue != intValue) { |
| return new IntConstantValue(floorValue); |
| } |
| } else if (constant.isDouble) { |
| DoubleConstantValue doubleResult = constant; |
| double doubleValue = doubleResult.doubleValue; |
| if (!doubleValue.isInfinite && |
| !doubleValue.isNaN && |
| !constant.isMinusZero) { |
| double truncated = doubleValue.truncateToDouble(); |
| if (truncated == doubleValue) { |
| return new IntConstantValue(new BigInt.from(truncated)); |
| } |
| } |
| } |
| return constant; |
| } |
| |
| NumConstantValue createInt(BigInt i) => |
| _convertToJavaScriptConstant(new IntConstantValue(i)); |
| NumConstantValue createIntFromInt(int i) => createInt(new BigInt.from(i)); |
| NumConstantValue _createInt32(BigInt i) => new IntConstantValue(i & _BITS32); |
| NumConstantValue createDouble(double d) => |
| _convertToJavaScriptConstant(new DoubleConstantValue(d)); |
| StringConstantValue createString(String string) => |
| new StringConstantValue(string); |
| BoolConstantValue createBool(bool value) => new BoolConstantValue(value); |
| NullConstantValue createNull() => new NullConstantValue(); |
| ListConstantValue createList(InterfaceType type, List<ConstantValue> values) => |
| new ListConstantValue(type, values); |
| |
| ConstantValue createType(CommonElements commonElements, DartType type) { |
| InterfaceType instanceType = commonElements.typeLiteralType; |
| return new TypeConstantValue(type, instanceType); |
| } |
| |
| /// Returns true if the [constant] is an integer at runtime. |
| /// |
| /// Integer checks report true for -0.0, INFINITY, and -INFINITY. At |
| /// runtime an 'X is int' check is implemented as: |
| /// |
| /// typeof(X) === "number" && Math.floor(X) === X |
| /// |
| /// We consistently match that runtime semantics at compile time as well. |
| bool isInt(ConstantValue constant) => |
| constant.isInt || |
| constant.isMinusZero || |
| constant.isPositiveInfinity || |
| constant.isNegativeInfinity; |
| |
| /// Returns true if the [constant] is a double at runtime. |
| bool isDouble(ConstantValue constant) => |
| constant.isDouble && !constant.isMinusZero; |
| |
| /// Returns true if the [constant] is a string at runtime. |
| bool isString(ConstantValue constant) => constant.isString; |
| |
| /// Returns true if the [constant] is a boolean at runtime. |
| bool isBool(ConstantValue constant) => constant.isBool; |
| |
| /// Returns true if the [constant] is null at runtime. |
| bool isNull(ConstantValue constant) => constant.isNull; |
| |
| bool isSubtype(DartTypes types, DartType s, DartType t) { |
| // At runtime, an integer is both an integer and a double: the |
| // integer type check is Math.floor, which will return true only |
| // for real integers, and our double type check is 'typeof number' |
| // which will return true for both integers and doubles. |
| if (s == types.commonElements.intType && |
| t == types.commonElements.doubleType) { |
| return true; |
| } |
| return types.isSubtype(s, t); |
| } |
| |
| SetConstantValue createSet(CommonElements commonElements, |
| InterfaceType sourceType, List<ConstantValue> values) { |
| InterfaceType type = commonElements.getConstantSetTypeFor(sourceType); |
| DartType elementType = type.typeArguments.first; |
| InterfaceType mapType = |
| commonElements.mapType(elementType, commonElements.nullType); |
| List<NullConstantValue> nulls = new List<NullConstantValue>.filled( |
| values.length, const NullConstantValue()); |
| MapConstantValue entries = createMap(commonElements, mapType, values, nulls); |
| return new JavaScriptSetConstant(type, entries); |
| } |
| |
| MapConstantValue createMap( |
| CommonElements commonElements, |
| InterfaceType sourceType, |
| List<ConstantValue> keys, |
| List<ConstantValue> values) { |
| bool onlyStringKeys = true; |
| ConstantValue protoValue = null; |
| for (int i = 0; i < keys.length; i++) { |
| dynamic key = keys[i]; |
| if (key.isString) { |
| if (key.stringValue == JavaScriptMapConstant.PROTO_PROPERTY) { |
| protoValue = values[i]; |
| } |
| } else { |
| onlyStringKeys = false; |
| // Don't handle __proto__ values specially in the general map case. |
| protoValue = null; |
| break; |
| } |
| } |
| |
| bool hasProtoKey = (protoValue != null); |
| InterfaceType keysType; |
| if (sourceType.treatAsRaw) { |
| keysType = commonElements.listType(); |
| } else { |
| keysType = commonElements.listType(sourceType.typeArguments.first); |
| } |
| ListConstantValue keysList = new ListConstantValue(keysType, keys); |
| InterfaceType type = commonElements.getConstantMapTypeFor(sourceType, |
| hasProtoKey: hasProtoKey, onlyStringKeys: onlyStringKeys); |
| return new JavaScriptMapConstant( |
| type, keysList, values, protoValue, onlyStringKeys); |
| } |
| |
| ConstantValue createSymbol(CommonElements commonElements, String text) { |
| InterfaceType type = commonElements.symbolImplementationType; |
| FieldEntity field = commonElements.symbolField; |
| ConstantValue argument = createString(text); |
| // TODO(johnniwinther): Use type arguments when all uses no longer expect |
| // a [FieldElement]. |
| var fields = <FieldEntity, ConstantValue>{field: argument}; |
| return new ConstructedConstantValue(type, fields); |
| } |
| |
| UnaryOperation lookupUnary(UnaryOperator operator) { |
| switch (operator.kind) { |
| case UnaryOperatorKind.COMPLEMENT: |
| return bitNot; |
| case UnaryOperatorKind.NEGATE: |
| return negate; |
| case UnaryOperatorKind.NOT: |
| return not; |
| default: |
| return null; |
| } |
| } |
| |
| BinaryOperation lookupBinary(BinaryOperator operator) { |
| switch (operator.kind) { |
| case BinaryOperatorKind.ADD: |
| return add; |
| case BinaryOperatorKind.SUB: |
| return subtract; |
| case BinaryOperatorKind.MUL: |
| return multiply; |
| case BinaryOperatorKind.DIV: |
| return divide; |
| case BinaryOperatorKind.MOD: |
| return modulo; |
| case BinaryOperatorKind.IDIV: |
| return truncatingDivide; |
| case BinaryOperatorKind.OR: |
| return bitOr; |
| case BinaryOperatorKind.AND: |
| return bitAnd; |
| case BinaryOperatorKind.XOR: |
| return bitXor; |
| case BinaryOperatorKind.LOGICAL_OR: |
| return booleanOr; |
| case BinaryOperatorKind.LOGICAL_AND: |
| return booleanAnd; |
| case BinaryOperatorKind.SHL: |
| return shiftLeft; |
| case BinaryOperatorKind.SHR: |
| return shiftRight; |
| case BinaryOperatorKind.LT: |
| return less; |
| case BinaryOperatorKind.LTEQ: |
| return lessEqual; |
| case BinaryOperatorKind.GT: |
| return greater; |
| case BinaryOperatorKind.GTEQ: |
| return greaterEqual; |
| case BinaryOperatorKind.EQ: |
| return equal; |
| case BinaryOperatorKind.IF_NULL: |
| return ifNull; |
| default: |
| return null; |
| } |
| } |
| |
| abstract class Operation { |
| String get name; |
| } |
| |
| abstract class UnaryOperation extends Operation { |
| /// Returns [:null:] if it was unable to fold the operation. |
| ConstantValue fold(ConstantValue constant); |
| } |
| |
| abstract class BinaryOperation extends Operation { |
| /// Returns [:null:] if it was unable to fold the operation. |
| ConstantValue fold(ConstantValue left, ConstantValue right); |
| apply(left, right); |
| } |
| |
| class BitNotOperation implements UnaryOperation { |
| @override |
| final String name = '~'; |
| |
| const BitNotOperation(); |
| |
| @override |
| ConstantValue fold(ConstantValue constant) { |
| if (isInt(constant)) { |
| // In JavaScript we don't check for -0 and treat it as if it was zero. |
| if (constant.isMinusZero) { |
| constant = createInt(BigInt.zero); |
| } |
| IntConstantValue intConstant = constant; |
| // We convert the result of bit-operations to 32 bit unsigned integers. |
| return _createInt32(~intConstant.intValue); |
| } |
| return null; |
| } |
| } |
| |
| class NegateOperation implements UnaryOperation { |
| @override |
| final String name = 'negate'; |
| |
| const NegateOperation(); |
| |
| @override |
| ConstantValue fold(ConstantValue constant) { |
| ConstantValue _fold(ConstantValue constant) { |
| if (constant.isInt) { |
| IntConstantValue intConstant = constant; |
| return createInt(-intConstant.intValue); |
| } |
| if (constant.isDouble) { |
| DoubleConstantValue doubleConstant = constant; |
| return createDouble(-doubleConstant.doubleValue); |
| } |
| return null; |
| } |
| |
| if (constant.isInt) { |
| IntConstantValue intConstant = constant; |
| if (intConstant.intValue == BigInt.zero) { |
| return createDouble(-0.0); |
| } |
| } |
| return _fold(constant); |
| } |
| } |
| |
| class NotOperation implements UnaryOperation { |
| @override |
| final String name = '!'; |
| |
| const NotOperation(); |
| |
| @override |
| ConstantValue fold(ConstantValue constant) { |
| if (constant.isBool) { |
| BoolConstantValue boolConstant = constant; |
| return createBool(!boolConstant.boolValue); |
| } |
| return null; |
| } |
| } |
| |
| /// Operations that only work if both arguments are integers. |
| abstract class BinaryBitOperation implements BinaryOperation { |
| const BinaryBitOperation(); |
| |
| @override |
| ConstantValue fold(ConstantValue left, ConstantValue right) { |
| ConstantValue _fold(ConstantValue left, ConstantValue right) { |
| if (left.isInt && right.isInt) { |
| IntConstantValue leftInt = left; |
| IntConstantValue rightInt = right; |
| BigInt resultValue = foldInts(leftInt.intValue, rightInt.intValue); |
| if (resultValue == null) return null; |
| return createInt(resultValue); |
| } |
| return null; |
| } |
| |
| // In JavaScript we don't check for -0 and treat it as if it was zero. |
| if (left.isMinusZero) { |
| left = createInt(BigInt.zero); |
| } |
| if (right.isMinusZero) { |
| right = createInt(BigInt.zero); |
| } |
| IntConstantValue result = _fold(left, right); |
| if (result != null) { |
| // We convert the result of bit-operations to 32 bit unsigned integers. |
| return _createInt32(result.intValue); |
| } |
| return result; |
| } |
| |
| BigInt foldInts(BigInt left, BigInt right); |
| } |
| |
| class BitAndOperation extends BinaryBitOperation { |
| @override |
| final String name = '&'; |
| |
| const BitAndOperation(); |
| |
| @override |
| BigInt foldInts(BigInt left, BigInt right) => left & right; |
| |
| @override |
| apply(left, right) => left & right; |
| } |
| |
| class BitOrOperation extends BinaryBitOperation { |
| @override |
| final String name = '|'; |
| |
| const BitOrOperation(); |
| |
| @override |
| BigInt foldInts(BigInt left, BigInt right) => left | right; |
| |
| @override |
| apply(left, right) => left | right; |
| } |
| |
| class BitXorOperation extends BinaryBitOperation { |
| @override |
| final String name = '^'; |
| |
| const BitXorOperation(); |
| |
| @override |
| BigInt foldInts(BigInt left, BigInt right) => left ^ right; |
| |
| @override |
| apply(left, right) => left ^ right; |
| } |
| |
| class ShiftLeftOperation extends BinaryBitOperation { |
| @override |
| final String name = '<<'; |
| |
| const ShiftLeftOperation(); |
| |
| @override |
| BigInt foldInts(BigInt left, BigInt right) { |
| // TODO(floitsch): find a better way to guard against excessive shifts to |
| // the left. |
| if (right > new BigInt.from(100) || right < BigInt.zero) return null; |
| return left << right.toInt(); |
| } |
| |
| @override |
| apply(left, right) => left << right; |
| } |
| |
| class ShiftRightOperation extends BinaryBitOperation { |
| @override |
| final String name = '>>'; |
| |
| const ShiftRightOperation(); |
| |
| @override |
| ConstantValue fold(ConstantValue left, ConstantValue right) { |
| // Truncate the input value to 32 bits if necessary. |
| if (left.isInt) { |
| IntConstantValue intConstant = left; |
| BigInt value = intConstant.intValue; |
| BigInt truncatedValue = value & _BITS32; |
| if (value < BigInt.zero) { |
| // Sign-extend if the input was negative. The current semantics don't |
| // make much sense, since we only look at bit 31. |
| // TODO(floitsch): we should treat the input to right shifts as |
| // unsigned. |
| |
| // A 32 bit complement-two value x can be computed by: |
| // x_u - 2^32 (where x_u is its unsigned representation). |
| // Example: 0xFFFFFFFF - 0x100000000 => -1. |
| // We simply and with the sign-bit and multiply by two. If the sign-bit |
| // was set, then the result is 0. Otherwise it will become 2^32. |
| final BigInt SIGN_BIT = new BigInt.from(0x80000000); |
| truncatedValue -= BigInt.two * (truncatedValue & SIGN_BIT); |
| } |
| if (value != truncatedValue) { |
| left = createInt(truncatedValue); |
| } |
| } |
| return super.fold(left, right); |
| } |
| |
| @override |
| BigInt foldInts(BigInt left, BigInt right) { |
| if (right < BigInt.zero) return null; |
| return left >> right.toInt(); |
| } |
| |
| @override |
| apply(left, right) => left >> right; |
| } |
| |
| abstract class BinaryBoolOperation implements BinaryOperation { |
| const BinaryBoolOperation(); |
| |
| @override |
| ConstantValue fold(ConstantValue left, ConstantValue right) { |
| if (left.isBool && right.isBool) { |
| BoolConstantValue leftBool = left; |
| BoolConstantValue rightBool = right; |
| bool resultValue = foldBools(leftBool.boolValue, rightBool.boolValue); |
| return createBool(resultValue); |
| } |
| return null; |
| } |
| |
| bool foldBools(bool left, bool right); |
| } |
| |
| class BooleanAndOperation extends BinaryBoolOperation { |
| @override |
| final String name = '&&'; |
| |
| const BooleanAndOperation(); |
| |
| @override |
| bool foldBools(bool left, bool right) => left && right; |
| |
| @override |
| apply(left, right) => left && right; |
| } |
| |
| class BooleanOrOperation extends BinaryBoolOperation { |
| @override |
| final String name = '||'; |
| |
| const BooleanOrOperation(); |
| |
| @override |
| bool foldBools(bool left, bool right) => left || right; |
| |
| @override |
| apply(left, right) => left || right; |
| } |
| |
| abstract class ArithmeticNumOperation implements BinaryOperation { |
| const ArithmeticNumOperation(); |
| |
| @override |
| ConstantValue fold(ConstantValue left, ConstantValue right) { |
| ConstantValue _fold(ConstantValue left, ConstantValue right) { |
| if (left.isNum && right.isNum) { |
| NumConstantValue leftNum = left; |
| NumConstantValue rightNum = right; |
| var foldedValue; |
| if (left.isInt && right.isInt) { |
| IntConstantValue leftInt = leftNum; |
| IntConstantValue rightInt = rightNum; |
| foldedValue = foldInts(leftInt.intValue, rightInt.intValue); |
| } else { |
| foldedValue = foldNums(leftNum.doubleValue, rightNum.doubleValue); |
| } |
| // A division by 0 means that we might not have a folded value. |
| if (foldedValue == null) return null; |
| if (left.isInt && right.isInt && !isDivide() || isTruncatingDivide()) { |
| assert(foldedValue is BigInt); |
| return createInt(foldedValue); |
| } else { |
| return createDouble(foldedValue); |
| } |
| } |
| return null; |
| } |
| |
| ConstantValue result = _fold(left, right); |
| if (result == null) return result; |
| return _convertToJavaScriptConstant(result); |
| } |
| |
| bool isDivide() => false; |
| bool isTruncatingDivide() => false; |
| foldInts(BigInt left, BigInt right); |
| foldNums(num left, num right); |
| } |
| |
| class SubtractOperation extends ArithmeticNumOperation { |
| @override |
| final String name = '-'; |
| |
| const SubtractOperation(); |
| |
| @override |
| BigInt foldInts(BigInt left, BigInt right) => left - right; |
| |
| @override |
| num foldNums(num left, num right) => left - right; |
| |
| @override |
| apply(left, right) => left - right; |
| } |
| |
| class MultiplyOperation extends ArithmeticNumOperation { |
| @override |
| final String name = '*'; |
| |
| const MultiplyOperation(); |
| |
| @override |
| BigInt foldInts(BigInt left, BigInt right) => left * right; |
| |
| @override |
| num foldNums(num left, num right) => left * right; |
| |
| @override |
| apply(left, right) => left * right; |
| } |
| |
| class ModuloOperation extends ArithmeticNumOperation { |
| @override |
| final String name = '%'; |
| |
| const ModuloOperation(); |
| |
| @override |
| BigInt foldInts(BigInt left, BigInt right) { |
| if (right == BigInt.zero) return null; |
| return left % right; |
| } |
| |
| @override |
| num foldNums(num left, num right) => left % right; |
| |
| @override |
| apply(left, right) => left % right; |
| } |
| |
| class RemainderOperation extends ArithmeticNumOperation { |
| @override |
| final String name = 'remainder'; |
| |
| const RemainderOperation(); |
| |
| @override |
| BigInt foldInts(BigInt left, BigInt right) { |
| if (right == BigInt.zero) return null; |
| return left.remainder(right); |
| } |
| |
| @override |
| num foldNums(num left, num right) => left.remainder(right); |
| |
| @override |
| apply(left, right) => left.remainder(right); |
| } |
| |
| class TruncatingDivideOperation extends ArithmeticNumOperation { |
| @override |
| final String name = '~/'; |
| |
| const TruncatingDivideOperation(); |
| |
| @override |
| BigInt foldInts(BigInt left, BigInt right) { |
| if (right == BigInt.zero) return null; |
| return left ~/ right; |
| } |
| |
| @override |
| BigInt foldNums(num left, num right) { |
| num ratio = left / right; |
| if (ratio.isNaN || ratio.isInfinite) return null; |
| return new BigInt.from(ratio.truncate().toInt()); |
| } |
| |
| @override |
| apply(left, right) => left ~/ right; |
| |
| @override |
| bool isTruncatingDivide() => true; |
| } |
| |
| class DivideOperation extends ArithmeticNumOperation { |
| @override |
| final String name = '/'; |
| |
| const DivideOperation(); |
| |
| @override |
| double foldInts(BigInt left, BigInt right) => left / right; |
| |
| @override |
| num foldNums(num left, num right) => left / right; |
| |
| @override |
| bool isDivide() => true; |
| |
| @override |
| apply(left, right) => left / right; |
| } |
| |
| class AddOperation implements BinaryOperation { |
| @override |
| final String name = '+'; |
| |
| const AddOperation(); |
| |
| @override |
| ConstantValue fold(ConstantValue left, ConstantValue right) { |
| ConstantValue _fold(ConstantValue left, ConstantValue right) { |
| if (left.isInt && right.isInt) { |
| IntConstantValue leftInt = left; |
| IntConstantValue rightInt = right; |
| BigInt result = leftInt.intValue + rightInt.intValue; |
| return createInt(result); |
| } else if (left.isNum && right.isNum) { |
| NumConstantValue leftNum = left; |
| NumConstantValue rightNum = right; |
| double result = leftNum.doubleValue + rightNum.doubleValue; |
| return createDouble(result); |
| } else if (left.isString && right.isString) { |
| StringConstantValue leftString = left; |
| StringConstantValue rightString = right; |
| String result = leftString.stringValue + rightString.stringValue; |
| return createString(result); |
| } else { |
| return null; |
| } |
| } |
| |
| ConstantValue result = _fold(left, right); |
| if (result != null && result.isNum) { |
| return _convertToJavaScriptConstant(result); |
| } |
| return result; |
| } |
| |
| @override |
| apply(left, right) => left + right; |
| } |
| |
| abstract class RelationalNumOperation implements BinaryOperation { |
| const RelationalNumOperation(); |
| |
| @override |
| ConstantValue fold(ConstantValue left, ConstantValue right) { |
| if (!left.isNum || !right.isNum) return null; |
| bool foldedValue; |
| if (left.isInt && right.isInt) { |
| IntConstantValue leftInt = left; |
| IntConstantValue rightInt = right; |
| foldedValue = foldInts(leftInt.intValue, rightInt.intValue); |
| } else { |
| NumConstantValue leftNum = left; |
| NumConstantValue rightNum = right; |
| foldedValue = foldNums(leftNum.doubleValue, rightNum.doubleValue); |
| } |
| assert(foldedValue != null); |
| return createBool(foldedValue); |
| } |
| |
| bool foldInts(BigInt left, BigInt right); |
| bool foldNums(num left, num right); |
| } |
| |
| class LessOperation extends RelationalNumOperation { |
| @override |
| final String name = '<'; |
| |
| const LessOperation(); |
| |
| @override |
| bool foldInts(BigInt left, BigInt right) => left < right; |
| |
| @override |
| bool foldNums(num left, num right) => left < right; |
| |
| @override |
| apply(left, right) => left < right; |
| } |
| |
| class LessEqualOperation extends RelationalNumOperation { |
| @override |
| final String name = '<='; |
| |
| const LessEqualOperation(); |
| |
| @override |
| bool foldInts(BigInt left, BigInt right) => left <= right; |
| |
| @override |
| bool foldNums(num left, num right) => left <= right; |
| |
| @override |
| apply(left, right) => left <= right; |
| } |
| |
| class GreaterOperation extends RelationalNumOperation { |
| @override |
| final String name = '>'; |
| |
| const GreaterOperation(); |
| |
| @override |
| bool foldInts(BigInt left, BigInt right) => left > right; |
| |
| @override |
| bool foldNums(num left, num right) => left > right; |
| |
| @override |
| apply(left, right) => left > right; |
| } |
| |
| class GreaterEqualOperation extends RelationalNumOperation { |
| @override |
| final String name = '>='; |
| |
| const GreaterEqualOperation(); |
| |
| @override |
| bool foldInts(BigInt left, BigInt right) => left >= right; |
| |
| @override |
| bool foldNums(num left, num right) => left >= right; |
| |
| @override |
| apply(left, right) => left >= right; |
| } |
| |
| class EqualsOperation implements BinaryOperation { |
| @override |
| final String name = '=='; |
| |
| const EqualsOperation(); |
| |
| @override |
| ConstantValue fold(ConstantValue left, ConstantValue right) { |
| // Numbers need to be treated specially because: NaN != NaN, -0.0 == 0.0, |
| // and 1 == 1.0. |
| if (left.isInt && right.isInt) { |
| IntConstantValue leftInt = left; |
| IntConstantValue rightInt = right; |
| bool result = leftInt.intValue == rightInt.intValue; |
| return createBool(result); |
| } |
| |
| if (left.isNum && right.isNum) { |
| NumConstantValue leftNum = left; |
| NumConstantValue rightNum = right; |
| bool result = leftNum.doubleValue == rightNum.doubleValue; |
| return createBool(result); |
| } |
| |
| if (left.isConstructedObject) { |
| if (right.isNull) { |
| return createBool(false); |
| } |
| // Unless we know that the user-defined object does not implement the |
| // equality operator we cannot fold here. |
| return null; |
| } |
| |
| return createBool(left == right); |
| } |
| |
| @override |
| apply(left, right) => left == right; |
| } |
| |
| class IdentityOperation implements BinaryOperation { |
| @override |
| final String name = '==='; |
| |
| const IdentityOperation(); |
| |
| @override |
| BoolConstantValue fold(ConstantValue left, ConstantValue right) { |
| BoolConstantValue _fold(ConstantValue left, ConstantValue right) { |
| // In order to preserve runtime semantics which says that NaN !== NaN |
| // don't constant fold NaN === NaN. Otherwise the output depends on |
| // inlined variables and other optimizations. |
| if (left.isNaN && right.isNaN) return null; |
| return createBool(left == right); |
| } |
| |
| BoolConstantValue result = _fold(left, right); |
| if (result == null || result.boolValue) return result; |
| // In JavaScript -0.0 === 0 and all doubles are equal to their integer |
| // values. Furthermore NaN !== NaN. |
| if (left.isInt && right.isInt) { |
| IntConstantValue leftInt = left; |
| IntConstantValue rightInt = right; |
| return new BoolConstantValue(leftInt.intValue == rightInt.intValue); |
| } |
| if (left.isNum && right.isNum) { |
| NumConstantValue leftNum = left; |
| NumConstantValue rightNum = right; |
| double leftDouble = leftNum.doubleValue; |
| double rightDouble = rightNum.doubleValue; |
| return new BoolConstantValue(leftDouble == rightDouble); |
| } |
| return result; |
| } |
| |
| @override |
| apply(left, right) => identical(left, right); |
| } |
| |
| class IfNullOperation implements BinaryOperation { |
| @override |
| final String name = '??'; |
| |
| const IfNullOperation(); |
| |
| @override |
| ConstantValue fold(ConstantValue left, ConstantValue right) { |
| if (left.isNull) return right; |
| return left; |
| } |
| |
| @override |
| apply(left, right) => left ?? right; |
| } |
| |
| class CodeUnitAtOperation implements BinaryOperation { |
| @override |
| final String name = 'charCodeAt'; |
| |
| const CodeUnitAtOperation(); |
| |
| @override |
| IntConstantValue fold(ConstantValue left, ConstantValue right) { |
| if (left.isString && right.isInt) { |
| StringConstantValue stringConstant = left; |
| IntConstantValue indexConstant = right; |
| String string = stringConstant.stringValue; |
| int index = indexConstant.intValue.toInt(); |
| if (index < 0 || index >= string.length) return null; |
| int value = string.codeUnitAt(index); |
| return createIntFromInt(value); |
| } |
| return null; |
| } |
| |
| @override |
| apply(left, right) => left.codeUnitAt(right); |
| } |
| |
| class RoundOperation implements UnaryOperation { |
| @override |
| final String name = 'round'; |
| |
| const RoundOperation(); |
| |
| @override |
| ConstantValue fold(ConstantValue constant) { |
| // Be careful to round() only values that do not throw on either the host or |
| // target platform. |
| ConstantValue tryToRound(double value) { |
| // Due to differences between browsers, only 'round' easy cases. Avoid |
| // cases where nudging the value up or down changes the answer. |
| // 13 digits is safely within the ~15 digit precision of doubles. |
| const severalULP = 0.0000000000001; |
| // Use 'roundToDouble()' to avoid exceptions on rounding the nudged value. |
| double rounded = value.roundToDouble(); |
| double rounded1 = (value * (1.0 + severalULP)).roundToDouble(); |
| double rounded2 = (value * (1.0 - severalULP)).roundToDouble(); |
| if (rounded != rounded1 || rounded != rounded2) return null; |
| return _convertToJavaScriptConstant( |
| new IntConstantValue(new BigInt.from(value.round()))); |
| } |
| |
| if (constant.isInt) { |
| IntConstantValue intConstant = constant; |
| double value = intConstant.intValue.toDouble(); |
| if (value >= -double.maxFinite && value <= double.maxFinite) { |
| return tryToRound(value); |
| } |
| } |
| if (constant.isDouble) { |
| DoubleConstantValue doubleConstant = constant; |
| double value = doubleConstant.doubleValue; |
| // NaN and infinities will throw. |
| if (value.isNaN) return null; |
| if (value.isInfinite) return null; |
| return tryToRound(value); |
| } |
| return null; |
| } |
| } |
| |
| class UnfoldedUnaryOperation implements UnaryOperation { |
| @override |
| final String name; |
| |
| const UnfoldedUnaryOperation(this.name); |
| |
| @override |
| ConstantValue fold(ConstantValue constant) { |
| return null; |
| } |
| } |
| |
| class JavaScriptSetConstant extends SetConstantValue { |
| final MapConstantValue entries; |
| |
| JavaScriptSetConstant(InterfaceType type, this.entries) |
| : super(type, entries.keys); |
| |
| @override |
| List<ConstantValue> getDependencies() => [entries]; |
| } |
| |
| class JavaScriptMapConstant extends MapConstantValue { |
| /// The [PROTO_PROPERTY] must not be used as normal property in any JavaScript |
| /// object. It would change the prototype chain. |
| static const String PROTO_PROPERTY = "__proto__"; |
| |
| /// The dart class implementing constant map literals. |
| static const String DART_CLASS = "ConstantMap"; |
| static const String DART_STRING_CLASS = "ConstantStringMap"; |
| static const String DART_PROTO_CLASS = "ConstantProtoMap"; |
| static const String DART_GENERAL_CLASS = "GeneralConstantMap"; |
| static const String LENGTH_NAME = "_length"; |
| static const String JS_OBJECT_NAME = "_jsObject"; |
| static const String KEYS_NAME = "_keys"; |
| static const String PROTO_VALUE = "_protoValue"; |
| static const String JS_DATA_NAME = "_jsData"; |
| |
| final ListConstantValue keyList; |
| final ConstantValue protoValue; |
| final bool onlyStringKeys; |
| |
| JavaScriptMapConstant(InterfaceType type, ListConstantValue keyList, |
| List<ConstantValue> values, this.protoValue, this.onlyStringKeys) |
| : this.keyList = keyList, |
| super(type, keyList.entries, values); |
| @override |
| bool get isMap => true; |
| |
| @override |
| List<ConstantValue> getDependencies() { |
| List<ConstantValue> result = <ConstantValue>[]; |
| if (onlyStringKeys) { |
| result.add(keyList); |
| } else { |
| // Add the keys individually to avoid generating an unused list constant |
| // for the keys. |
| result.addAll(keys); |
| } |
| result.addAll(values); |
| return result; |
| } |
| } |