| // 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. | |
| library dart2js.constant_system.js; | |
| import '../constant_system_dart.dart'; | |
| import '../constants/constant_system.dart'; | |
| import '../constants/values.dart'; | |
| import '../common_elements.dart' show CommonElements; | |
| import '../elements/types.dart'; | |
| import '../elements/entities.dart'; | |
| class JavaScriptBitNotOperation extends BitNotOperation { | |
| const JavaScriptBitNotOperation(); | |
| ConstantValue fold(ConstantValue constant) { | |
| if (JavaScriptConstantSystem.only.isInt(constant)) { | |
| // In JavaScript we don't check for -0 and treat it as if it was zero. | |
| if (constant.isMinusZero) { | |
| constant = DART_CONSTANT_SYSTEM.createInt(BigInt.zero); | |
| } | |
| IntConstantValue intConstant = constant; | |
| // We convert the result of bit-operations to 32 bit unsigned integers. | |
| return JavaScriptConstantSystem.only.createInt32(~intConstant.intValue); | |
| } | |
| return null; | |
| } | |
| } | |
| /** | |
| * In JavaScript we truncate the result to an unsigned 32 bit integer. Also, -0 | |
| * is treated as if it was the integer 0. | |
| */ | |
| class JavaScriptBinaryBitOperation implements BinaryOperation { | |
| final BinaryBitOperation dartBitOperation; | |
| const JavaScriptBinaryBitOperation(this.dartBitOperation); | |
| String get name => dartBitOperation.name; | |
| ConstantValue fold(ConstantValue left, ConstantValue right) { | |
| // In JavaScript we don't check for -0 and treat it as if it was zero. | |
| if (left.isMinusZero) { | |
| left = DART_CONSTANT_SYSTEM.createInt(BigInt.zero); | |
| } | |
| if (right.isMinusZero) { | |
| right = DART_CONSTANT_SYSTEM.createInt(BigInt.zero); | |
| } | |
| IntConstantValue result = dartBitOperation.fold(left, right); | |
| if (result != null) { | |
| // We convert the result of bit-operations to 32 bit unsigned integers. | |
| return JavaScriptConstantSystem.only.createInt32(result.intValue); | |
| } | |
| return result; | |
| } | |
| apply(left, right) => dartBitOperation.apply(left, right); | |
| } | |
| class JavaScriptShiftRightOperation extends JavaScriptBinaryBitOperation { | |
| const JavaScriptShiftRightOperation() : super(const ShiftRightOperation()); | |
| 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 & JavaScriptConstantSystem.only.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 = DART_CONSTANT_SYSTEM.createInt(truncatedValue); | |
| } | |
| } | |
| return super.fold(left, right); | |
| } | |
| } | |
| class JavaScriptNegateOperation implements UnaryOperation { | |
| final NegateOperation dartNegateOperation = const NegateOperation(); | |
| const JavaScriptNegateOperation(); | |
| String get name => dartNegateOperation.name; | |
| ConstantValue fold(ConstantValue constant) { | |
| if (constant.isInt) { | |
| IntConstantValue intConstant = constant; | |
| if (intConstant.intValue == BigInt.zero) { | |
| return JavaScriptConstantSystem.only.createDouble(-0.0); | |
| } | |
| } | |
| return dartNegateOperation.fold(constant); | |
| } | |
| } | |
| class JavaScriptAddOperation implements BinaryOperation { | |
| final _addOperation = const AddOperation(); | |
| String get name => _addOperation.name; | |
| const JavaScriptAddOperation(); | |
| ConstantValue fold(ConstantValue left, ConstantValue right) { | |
| ConstantValue result = _addOperation.fold(left, right); | |
| if (result != null && result.isNum) { | |
| return JavaScriptConstantSystem.only.convertToJavaScriptConstant(result); | |
| } | |
| return result; | |
| } | |
| apply(left, right) => _addOperation.apply(left, right); | |
| } | |
| class JavaScriptRemainderOperation extends ArithmeticNumOperation { | |
| String get name => 'remainder'; | |
| const JavaScriptRemainderOperation(); | |
| BigInt foldInts(BigInt left, BigInt right) { | |
| if (right == BigInt.zero) return null; | |
| return left.remainder(right); | |
| } | |
| num foldNums(num left, num right) => left.remainder(right); | |
| apply(left, right) => left.remainder(right); | |
| } | |
| class JavaScriptBinaryArithmeticOperation implements BinaryOperation { | |
| final BinaryOperation dartArithmeticOperation; | |
| const JavaScriptBinaryArithmeticOperation(this.dartArithmeticOperation); | |
| String get name => dartArithmeticOperation.name; | |
| ConstantValue fold(ConstantValue left, ConstantValue right) { | |
| ConstantValue result = dartArithmeticOperation.fold(left, right); | |
| if (result == null) return result; | |
| return JavaScriptConstantSystem.only.convertToJavaScriptConstant(result); | |
| } | |
| apply(left, right) => dartArithmeticOperation.apply(left, right); | |
| } | |
| class JavaScriptIdentityOperation implements BinaryOperation { | |
| final IdentityOperation dartIdentityOperation = const IdentityOperation(); | |
| const JavaScriptIdentityOperation(); | |
| String get name => dartIdentityOperation.name; | |
| BoolConstantValue fold(ConstantValue left, ConstantValue right) { | |
| BoolConstantValue result = dartIdentityOperation.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; | |
| } | |
| apply(left, right) => identical(left, right); | |
| } | |
| class JavaScriptRoundOperation implements UnaryOperation { | |
| const JavaScriptRoundOperation(); | |
| String get name => DART_CONSTANT_SYSTEM.round.name; | |
| 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 JavaScriptConstantSystem.only.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; | |
| } | |
| } | |
| /** | |
| * Constant system following the semantics for Dart code that has been | |
| * compiled to JavaScript. | |
| */ | |
| class JavaScriptConstantSystem extends ConstantSystem { | |
| final BITS32 = new BigInt.from(0xFFFFFFFF); | |
| final add = const JavaScriptAddOperation(); | |
| final bitAnd = const JavaScriptBinaryBitOperation(const BitAndOperation()); | |
| final bitNot = const JavaScriptBitNotOperation(); | |
| final bitOr = const JavaScriptBinaryBitOperation(const BitOrOperation()); | |
| final bitXor = const JavaScriptBinaryBitOperation(const BitXorOperation()); | |
| final booleanAnd = const BooleanAndOperation(); | |
| final booleanOr = const BooleanOrOperation(); | |
| final divide = | |
| const JavaScriptBinaryArithmeticOperation(const DivideOperation()); | |
| final equal = const EqualsOperation(); | |
| final greaterEqual = const GreaterEqualOperation(); | |
| final greater = const GreaterOperation(); | |
| final identity = const JavaScriptIdentityOperation(); | |
| final ifNull = const IfNullOperation(); | |
| final lessEqual = const LessEqualOperation(); | |
| final less = const LessOperation(); | |
| final modulo = | |
| const JavaScriptBinaryArithmeticOperation(const ModuloOperation()); | |
| final multiply = | |
| const JavaScriptBinaryArithmeticOperation(const MultiplyOperation()); | |
| final negate = const JavaScriptNegateOperation(); | |
| final not = const NotOperation(); | |
| final remainder = const JavaScriptRemainderOperation(); | |
| final shiftLeft = | |
| const JavaScriptBinaryBitOperation(const ShiftLeftOperation()); | |
| final shiftRight = const JavaScriptShiftRightOperation(); | |
| final subtract = | |
| const JavaScriptBinaryArithmeticOperation(const SubtractOperation()); | |
| final truncatingDivide = const JavaScriptBinaryArithmeticOperation( | |
| const TruncatingDivideOperation()); | |
| final codeUnitAt = const CodeUnitAtRuntimeOperation(); | |
| final round = const JavaScriptRoundOperation(); | |
| final abs = const UnfoldedUnaryOperation('abs'); | |
| static final JavaScriptConstantSystem only = | |
| new JavaScriptConstantSystem._internal(); | |
| JavaScriptConstantSystem._internal(); | |
| /** | |
| * 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; | |
| } | |
| @override | |
| NumConstantValue createInt(BigInt i) { | |
| return convertToJavaScriptConstant(new IntConstantValue(i)); | |
| } | |
| NumConstantValue createInt32(BigInt i) => new IntConstantValue(i & BITS32); | |
| NumConstantValue createDouble(double d) => | |
| convertToJavaScriptConstant(new DoubleConstantValue(d)); | |
| StringConstantValue createString(String string) { | |
| return new StringConstantValue(string); | |
| } | |
| BoolConstantValue createBool(bool value) => new BoolConstantValue(value); | |
| NullConstantValue createNull() => new NullConstantValue(); | |
| @override | |
| ListConstantValue createList(InterfaceType type, List<ConstantValue> values) { | |
| return new ListConstantValue(type, values); | |
| } | |
| @override | |
| ConstantValue createType(CommonElements commonElements, DartType type) { | |
| InterfaceType instanceType = commonElements.typeLiteralType; | |
| return new TypeConstantValue(type, instanceType); | |
| } | |
| // 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) { | |
| return constant.isInt || | |
| constant.isMinusZero || | |
| constant.isPositiveInfinity || | |
| constant.isNegativeInfinity; | |
| } | |
| bool isDouble(ConstantValue constant) => | |
| constant.isDouble && !constant.isMinusZero; | |
| bool isString(ConstantValue constant) => constant.isString; | |
| bool isBool(ConstantValue constant) => constant.isBool; | |
| 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); | |
| } | |
| 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); | |
| } | |
| @override | |
| 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); | |
| } | |
| } | |
| 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); | |
| bool get isMap => true; | |
| 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; | |
| } | |
| } |