|  | // Copyright (c) 2013, 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 'package:js_runtime/synced/array_flags.dart' show ArrayFlags; | 
|  | import '../common/elements.dart' show JCommonElements; | 
|  | import '../constants/constant_system.dart' as constant_system; | 
|  | import '../constants/values.dart'; | 
|  | import '../elements/entities.dart'; | 
|  | import '../elements/names.dart'; | 
|  | import '../inferrer/abstract_value_domain.dart'; | 
|  | import '../inferrer/types.dart'; | 
|  | import '../js_model/js_world.dart' show JClosedWorld; | 
|  | import '../universe/selector.dart'; | 
|  | import 'logging.dart'; | 
|  | import 'nodes.dart'; | 
|  | import 'types.dart'; | 
|  |  | 
|  | /// [InvokeDynamicSpecializer] and its subclasses are helpers to | 
|  | /// optimize intercepted dynamic calls. It knows what input types | 
|  | /// would be beneficial for performance, and how to change a invoke | 
|  | /// dynamic to a builtin instruction (e.g. HIndex, HBitNot). | 
|  | class InvokeDynamicSpecializer { | 
|  | const InvokeDynamicSpecializer(); | 
|  |  | 
|  | AbstractValue computeTypeFromInputTypes( | 
|  | HInvokeDynamic instruction, | 
|  | GlobalTypeInferenceResults results, | 
|  | JClosedWorld closedWorld, | 
|  | ) { | 
|  | return AbstractValueFactory.inferredResultTypeForSelector( | 
|  | instruction.selector, | 
|  | instruction.receiverType, | 
|  | results, | 
|  | ); | 
|  | } | 
|  |  | 
|  | HInstruction? tryConvertToBuiltin( | 
|  | HInvokeDynamic instruction, | 
|  | HGraph graph, | 
|  | GlobalTypeInferenceResults results, | 
|  | JCommonElements commonElements, | 
|  | JClosedWorld closedWorld, | 
|  | OptimizationTestLog? log, | 
|  | ) { | 
|  | return null; | 
|  | } | 
|  |  | 
|  | void clearAllSideEffects(HInstruction instruction) { | 
|  | instruction.sideEffects.clearAllSideEffects(); | 
|  | instruction.sideEffects.clearAllDependencies(); | 
|  | instruction.setUseGvn(); | 
|  | } | 
|  |  | 
|  | void redirectSelector( | 
|  | HInvokeDynamic instruction, | 
|  | String name, | 
|  | JCommonElements commonElements, | 
|  | ) { | 
|  | Selector selector = instruction.selector; | 
|  | if (selector.name == name) return; | 
|  | instruction.selector = Selector.call( | 
|  | Name(name, commonElements.interceptorsLibrary!.canonicalUri), | 
|  | selector.callStructure, | 
|  | ); | 
|  | } | 
|  |  | 
|  | constant_system.Operation? operation() => null; | 
|  |  | 
|  | static InvokeDynamicSpecializer lookupSpecializer(Selector selector) { | 
|  | if (selector.isIndex) return const IndexSpecializer(); | 
|  | if (selector.isIndexSet) return const IndexAssignSpecializer(); | 
|  | String name = selector.name; | 
|  | if (selector.isOperator) { | 
|  | if (name == 'unary-') return const UnaryNegateSpecializer(); | 
|  | if (name == '~') return const BitNotSpecializer(); | 
|  | if (name == '+') return const AddSpecializer(); | 
|  | if (name == '-') return const SubtractSpecializer(); | 
|  | if (name == '*') return const MultiplySpecializer(); | 
|  | if (name == '/') return const DivideSpecializer(); | 
|  | if (name == '~/') return const TruncatingDivideSpecializer(); | 
|  | if (name == '%') return const ModuloSpecializer(); | 
|  | if (name == '<<') return const ShiftLeftSpecializer(); | 
|  | if (name == '>>') return const ShiftRightSpecializer(); | 
|  | if (name == '>>>') return const ShiftRightUnsignedSpecializer(); | 
|  | if (name == '&') return const BitAndSpecializer(); | 
|  | if (name == '|') return const BitOrSpecializer(); | 
|  | if (name == '^') return const BitXorSpecializer(); | 
|  | if (name == '==') return const EqualsSpecializer(); | 
|  | if (name == '<') return const LessSpecializer(); | 
|  | if (name == '<=') return const LessEqualSpecializer(); | 
|  | if (name == '>') return const GreaterSpecializer(); | 
|  | if (name == '>=') return const GreaterEqualSpecializer(); | 
|  | return const InvokeDynamicSpecializer(); | 
|  | } | 
|  | if (selector.isCall) { | 
|  | if (selector.namedArguments.isEmpty) { | 
|  | int argumentCount = selector.argumentCount; | 
|  | if (argumentCount == 0) { | 
|  | if (name == 'abs') return const AbsSpecializer(); | 
|  | if (name == 'removeLast') return const RemoveLastSpecializer(); | 
|  | if (name == 'round') return const RoundSpecializer(); | 
|  | if (name == 'toInt') return const ToIntSpecializer(); | 
|  | if (name == 'trim') return const TrimSpecializer(); | 
|  | } else if (argumentCount == 1) { | 
|  | if (name == 'codeUnitAt') return const CodeUnitAtSpecializer(); | 
|  | if (name == 'compareTo') return const CompareToSpecializer(); | 
|  | if (name == 'remainder') return const RemainderSpecializer(); | 
|  | if (name == 'substring') return const SubstringSpecializer(); | 
|  | if (name == 'contains') return const PatternMatchSpecializer(); | 
|  | if (name == 'indexOf') return const PatternMatchSpecializer(); | 
|  | if (name == 'startsWith') return const PatternMatchSpecializer(); | 
|  | if (name == 'endsWith') return const PatternMatchSpecializer(); | 
|  | } else if (argumentCount == 2) { | 
|  | if (name == 'substring') return const SubstringSpecializer(); | 
|  | if (name == 'contains') return const PatternMatchSpecializer(); | 
|  | if (name == 'indexOf') return const PatternMatchSpecializer(); | 
|  | if (name == 'startsWith') return const PatternMatchSpecializer(); | 
|  | if (name == 'endsWith') return const PatternMatchSpecializer(); | 
|  | } | 
|  | } | 
|  | } | 
|  | return const InvokeDynamicSpecializer(); | 
|  | } | 
|  |  | 
|  | bool requiresBoundsCheck(HInvokeDynamic node, JClosedWorld closedWorld) { | 
|  | if (node.isBoundsSafe) return false; | 
|  | return closedWorld.annotationsData | 
|  | .getIndexBoundsCheckPolicy(node.instructionContext) | 
|  | .isEmitted; | 
|  | } | 
|  |  | 
|  | HBoundsCheck insertBoundsCheck( | 
|  | HInvokeDynamic indexerNode, | 
|  | HInstruction array, | 
|  | HInstruction indexArgument, | 
|  | JClosedWorld closedWorld, | 
|  | OptimizationTestLog? log, | 
|  | ) { | 
|  | final abstractValueDomain = closedWorld.abstractValueDomain; | 
|  | final block = indexerNode.block!; | 
|  |  | 
|  | if (abstractValueDomain.isNull(array.instructionType).isPotentiallyTrue) { | 
|  | HNullCheck check = | 
|  | HNullCheck( | 
|  | array, | 
|  | abstractValueDomain.excludeNull(array.instructionType), | 
|  | ) | 
|  | ..selector = indexerNode.selector | 
|  | ..sourceInformation = indexerNode.sourceInformation; | 
|  | log?.registerNullCheck(indexerNode, check); | 
|  | block.addBefore(indexerNode, check); | 
|  | array = check; | 
|  | } | 
|  |  | 
|  | HGetLength length = HGetLength( | 
|  | array, | 
|  | abstractValueDomain.positiveIntType, | 
|  | isAssignable: abstractValueDomain | 
|  | .isFixedLengthJsIndexable(array.instructionType) | 
|  | .isPotentiallyFalse, | 
|  | ); | 
|  | block.addBefore(indexerNode, length); | 
|  |  | 
|  | AbstractValue type = | 
|  | indexArgument.isPositiveInteger(abstractValueDomain).isDefinitelyTrue | 
|  | ? indexArgument.instructionType | 
|  | : abstractValueDomain.positiveIntType; | 
|  | HBoundsCheck check = HBoundsCheck(indexArgument, length, array, type) | 
|  | ..sourceInformation = indexerNode.sourceInformation; | 
|  | block.addBefore(indexerNode, check); | 
|  | // TODO(sra): This should be useful but causes some crashes. Figure out why: | 
|  | //     indexArgument.replaceAllUsersDominatedBy(indexerNode, check); | 
|  | return check; | 
|  | } | 
|  | } | 
|  |  | 
|  | bool canBeNegativeZero(HInstruction input) { | 
|  | bool canBePositiveZero(HInstruction input) { | 
|  | if (input is HConstant) { | 
|  | ConstantValue value = input.constant; | 
|  | if (value is DoubleConstantValue && value.isZero) return true; | 
|  | if (value is IntConstantValue && value.isZero) return true; | 
|  | return false; | 
|  | } | 
|  | return true; | 
|  | } | 
|  |  | 
|  | if (input is HConstant) { | 
|  | ConstantValue value = input.constant; | 
|  | if (value is DoubleConstantValue && value.isMinusZero) return true; | 
|  | return false; | 
|  | } | 
|  | if (input is HAdd) { | 
|  | // '+' can only generate -0.0 when both inputs are -0.0. | 
|  | return canBeNegativeZero(input.left) && canBeNegativeZero(input.right); | 
|  | } | 
|  | if (input is HSubtract) { | 
|  | // '-' can only generate -0.0 for inputs `-0.0` and `0`. | 
|  | return canBeNegativeZero(input.left) && canBePositiveZero(input.right); | 
|  | } | 
|  | if (input is HPhi) { | 
|  | final inputBlockId = input.block!.id; | 
|  | if (input.inputs.any((phiInput) => phiInput.block!.id >= inputBlockId)) { | 
|  | return true; // Assume back-edge may be negative zero. | 
|  | } | 
|  | return input.inputs.any(canBeNegativeZero); | 
|  | } | 
|  | return true; | 
|  | } | 
|  |  | 
|  | class IndexAssignSpecializer extends InvokeDynamicSpecializer { | 
|  | const IndexAssignSpecializer(); | 
|  |  | 
|  | @override | 
|  | HInstruction? tryConvertToBuiltin( | 
|  | HInvokeDynamic instruction, | 
|  | HGraph graph, | 
|  | GlobalTypeInferenceResults results, | 
|  | JCommonElements commonElements, | 
|  | JClosedWorld closedWorld, | 
|  | OptimizationTestLog? log, | 
|  | ) { | 
|  | HInstruction receiver = instruction.inputs[1]; | 
|  | HInstruction index = instruction.inputs[2]; | 
|  | final abstractValueDomain = closedWorld.abstractValueDomain; | 
|  |  | 
|  | bool needsMutableCheck = false; | 
|  | if (abstractValueDomain | 
|  | .isTypedArray(receiver.instructionType) | 
|  | .isDefinitelyTrue) { | 
|  | needsMutableCheck = true; | 
|  | } else if (receiver.isArray(abstractValueDomain).isDefinitelyTrue) { | 
|  | needsMutableCheck = receiver | 
|  | .isModifiableArray(abstractValueDomain) | 
|  | .isPotentiallyFalse; | 
|  | } else { | 
|  | if (receiver.isMutableIndexable(abstractValueDomain).isPotentiallyFalse) { | 
|  | return null; | 
|  | } | 
|  | } | 
|  | // TODO(johnniwinther): Merge this and the following if statement. | 
|  | if (index.isInteger(abstractValueDomain).isPotentiallyFalse && | 
|  | // TODO(johnniwinther): Support annotations on the possible targets | 
|  | // and used their parameter check policy here. | 
|  | closedWorld.annotationsData.getParameterCheckPolicy(null).isEmitted) { | 
|  | // We want the right checked mode error. | 
|  | return null; | 
|  | } | 
|  |  | 
|  | HInstruction value = instruction.inputs[3]; | 
|  | // TODO(johnniwinther): Support annotations on the possible targets | 
|  | // and used their parameter check policy here. | 
|  | if (closedWorld.annotationsData.getParameterCheckPolicy(null).isEmitted) { | 
|  | if (!_valueParameterCheckAlwaysSucceeds( | 
|  | instruction, | 
|  | receiver, | 
|  | value, | 
|  | commonElements, | 
|  | closedWorld, | 
|  | )) { | 
|  | return null; | 
|  | } | 
|  | } | 
|  |  | 
|  | if (needsMutableCheck) { | 
|  | HInstruction getFlags = HArrayFlagsGet( | 
|  | receiver, | 
|  | abstractValueDomain.uint31Type, | 
|  | )..sourceInformation = instruction.sourceInformation; | 
|  | instruction.block!.addBefore(instruction, getFlags); | 
|  | HInstruction mask = graph.addConstantInt( | 
|  | ArrayFlags.unmodifiableCheck, | 
|  | closedWorld, | 
|  | ); | 
|  | HInstruction name = graph.addConstantString('[]=', closedWorld); | 
|  | HInstruction verb = graph.addConstantString('modify', closedWorld); | 
|  | final instructionType = receiver.instructionType; | 
|  | final checkFlags = HArrayFlagsCheck( | 
|  | receiver, | 
|  | getFlags, | 
|  | mask, | 
|  | name, | 
|  | verb, | 
|  | instructionType, | 
|  | )..sourceInformation = instruction.sourceInformation; | 
|  | instruction.block!.addBefore(instruction, checkFlags); | 
|  | checkFlags.instructionType = checkFlags.computeInstructionType( | 
|  | instructionType, | 
|  | abstractValueDomain, | 
|  | ); | 
|  | receiver = checkFlags; | 
|  | } | 
|  |  | 
|  | HInstruction checkedIndex = index; | 
|  | if (requiresBoundsCheck(instruction, closedWorld)) { | 
|  | checkedIndex = insertBoundsCheck( | 
|  | instruction, | 
|  | receiver, | 
|  | index, | 
|  | closedWorld, | 
|  | log, | 
|  | ); | 
|  | } | 
|  | HIndexAssign converted = HIndexAssign(receiver, checkedIndex, value); | 
|  | log?.registerIndexAssign(instruction, converted); | 
|  | return converted; | 
|  | } | 
|  |  | 
|  | /// Returns [true] if [value] meets the requirements for being stored into | 
|  | /// indexable [receiver]. | 
|  | bool _valueParameterCheckAlwaysSucceeds( | 
|  | HInvokeDynamic instruction, | 
|  | HInstruction receiver, | 
|  | HInstruction value, | 
|  | JCommonElements commonElements, | 
|  | JClosedWorld closedWorld, | 
|  | ) { | 
|  | if (instruction.isInvariant) { | 
|  | return true; | 
|  | } | 
|  | // Handle typed arrays by recognizing the exact implementation of `[]=` and | 
|  | // checking if [value] has the appropriate type. | 
|  | if (instruction.element != null) { | 
|  | ClassEntity? cls = instruction.element!.enclosingClass; | 
|  | if (cls == commonElements.typedArrayOfIntClass) { | 
|  | return value | 
|  | .isInteger(closedWorld.abstractValueDomain) | 
|  | .isDefinitelyTrue; | 
|  | } else if (cls == commonElements.typedArrayOfDoubleClass) { | 
|  | return value.isNumber(closedWorld.abstractValueDomain).isDefinitelyTrue; | 
|  | } | 
|  | } | 
|  |  | 
|  | // The type check will pass if it passed before. We know it passed before if | 
|  | // the value was loaded from the same indexable. | 
|  | if (value is HIndex) { | 
|  | if (value.receiver.nonCheck() == receiver.nonCheck()) { | 
|  | return true; | 
|  | } | 
|  | } | 
|  |  | 
|  | return false; | 
|  | } | 
|  | } | 
|  |  | 
|  | class IndexSpecializer extends InvokeDynamicSpecializer { | 
|  | const IndexSpecializer(); | 
|  |  | 
|  | @override | 
|  | constant_system.BinaryOperation operation() { | 
|  | return constant_system.index; | 
|  | } | 
|  |  | 
|  | @override | 
|  | HInstruction? tryConvertToBuiltin( | 
|  | HInvokeDynamic instruction, | 
|  | HGraph graph, | 
|  | GlobalTypeInferenceResults results, | 
|  | JCommonElements commonElements, | 
|  | JClosedWorld closedWorld, | 
|  | OptimizationTestLog? log, | 
|  | ) { | 
|  | HInstruction receiver = instruction.getDartReceiver(); | 
|  | var abstractValueDomain = closedWorld.abstractValueDomain; | 
|  | if (receiver.isIndexablePrimitive(abstractValueDomain).isPotentiallyFalse) { | 
|  | return null; | 
|  | } | 
|  | HInstruction index = instruction.inputs.last; | 
|  | if (index.isInteger(abstractValueDomain).isPotentiallyFalse && | 
|  | // TODO(johnniwinther): Support annotations on the possible targets | 
|  | // and used their parameter check policy here. | 
|  | closedWorld.annotationsData.getParameterCheckPolicy(null).isEmitted) { | 
|  | // We want the right checked mode error. | 
|  | return null; | 
|  | } | 
|  | AbstractValue receiverType = receiver.instructionType; | 
|  | AbstractValue elementType = | 
|  | AbstractValueFactory.inferredResultTypeForSelector( | 
|  | instruction.selector, | 
|  | receiverType, | 
|  | results, | 
|  | ); | 
|  | if (abstractValueDomain.isTypedArray(receiverType).isDefinitelyTrue) { | 
|  | elementType = abstractValueDomain.excludeNull(elementType); | 
|  | } | 
|  |  | 
|  | HInstruction checkedIndex = index; | 
|  | if (requiresBoundsCheck(instruction, closedWorld)) { | 
|  | checkedIndex = insertBoundsCheck( | 
|  | instruction, | 
|  | receiver, | 
|  | index, | 
|  | closedWorld, | 
|  | log, | 
|  | ); | 
|  | } | 
|  | HIndex converted = HIndex(receiver, checkedIndex, elementType); | 
|  | log?.registerIndex(instruction, converted); | 
|  | return converted; | 
|  | } | 
|  | } | 
|  |  | 
|  | class CodeUnitAtSpecializer extends InvokeDynamicSpecializer { | 
|  | const CodeUnitAtSpecializer(); | 
|  |  | 
|  | @override | 
|  | constant_system.BinaryOperation operation() { | 
|  | return constant_system.codeUnitAt; | 
|  | } | 
|  |  | 
|  | @override | 
|  | HInstruction? tryConvertToBuiltin( | 
|  | HInvokeDynamic instruction, | 
|  | HGraph graph, | 
|  | GlobalTypeInferenceResults results, | 
|  | JCommonElements commonElements, | 
|  | JClosedWorld closedWorld, | 
|  | OptimizationTestLog? log, | 
|  | ) { | 
|  | final abstractValueDomain = closedWorld.abstractValueDomain; | 
|  | HInstruction receiver = instruction.getDartReceiver(); | 
|  | if (receiver.isStringOrNull(abstractValueDomain).isPotentiallyFalse) { | 
|  | return null; | 
|  | } | 
|  | HInstruction index = instruction.inputs.last; | 
|  | if (index.isInteger(abstractValueDomain).isPotentiallyFalse) { | 
|  | return null; | 
|  | } | 
|  |  | 
|  | HInstruction checkedIndex = index; | 
|  | if (requiresBoundsCheck(instruction, closedWorld)) { | 
|  | checkedIndex = insertBoundsCheck( | 
|  | instruction, | 
|  | receiver, | 
|  | index, | 
|  | closedWorld, | 
|  | log, | 
|  | ); | 
|  | } | 
|  | final converted = HCharCodeAt( | 
|  | receiver, | 
|  | checkedIndex, | 
|  | abstractValueDomain.uint31Type, | 
|  | ); | 
|  | log?.registerCodeUnitAt(instruction); | 
|  | return converted; | 
|  | } | 
|  | } | 
|  |  | 
|  | class RemoveLastSpecializer extends InvokeDynamicSpecializer { | 
|  | const RemoveLastSpecializer(); | 
|  |  | 
|  | @override | 
|  | HInstruction? tryConvertToBuiltin( | 
|  | HInvokeDynamic instruction, | 
|  | HGraph graph, | 
|  | GlobalTypeInferenceResults results, | 
|  | JCommonElements commonElements, | 
|  | JClosedWorld closedWorld, | 
|  | OptimizationTestLog? log, | 
|  | ) { | 
|  | HInstruction receiver = instruction.getDartReceiver(); | 
|  | final abstractValueDomain = closedWorld.abstractValueDomain; | 
|  | if (receiver.isGrowableArray(abstractValueDomain).isPotentiallyFalse) { | 
|  | return null; | 
|  | } | 
|  |  | 
|  | // We are essentially inlining `result = a[a.length - 1]`. `0` is the only | 
|  | // index that can fail so we check zero directly, but we want to report the | 
|  | // error index as `-1`, so we add `-1` as an extra input that to the check. | 
|  | if (requiresBoundsCheck(instruction, closedWorld)) { | 
|  | HConstant zeroIndex = graph.addConstantInt(0, closedWorld); | 
|  | HBoundsCheck check = insertBoundsCheck( | 
|  | instruction, | 
|  | receiver, | 
|  | zeroIndex, | 
|  | closedWorld, | 
|  | log, | 
|  | ); | 
|  | HInstruction minusOne = graph.addConstantInt(-1, closedWorld); | 
|  | check.inputs.add(minusOne); | 
|  | minusOne.usedBy.add(check); | 
|  | } | 
|  | // `Array.pop` is encoded as a non-intercepted call to `JSArray.removeLast`. | 
|  | // TODO(sra): Add a better encoding for `Array.pop`, perhaps a HInstruction. | 
|  | HInvokeDynamic converted = HInvokeDynamicMethod( | 
|  | instruction.selector, | 
|  | instruction.receiverType, | 
|  | [receiver], // Drop interceptor. | 
|  | instruction.instructionType, | 
|  | instruction.typeArguments, | 
|  | instruction.sourceInformation, | 
|  | isIntercepted: false, | 
|  | )..element = commonElements.jsArrayRemoveLast; | 
|  | log?.registerRemoveLast(instruction, converted); | 
|  | return converted; | 
|  | } | 
|  | } | 
|  |  | 
|  | class BitNotSpecializer extends InvokeDynamicSpecializer { | 
|  | const BitNotSpecializer(); | 
|  |  | 
|  | @override | 
|  | constant_system.UnaryOperation operation() { | 
|  | return constant_system.bitNot; | 
|  | } | 
|  |  | 
|  | @override | 
|  | AbstractValue computeTypeFromInputTypes( | 
|  | HInvokeDynamic instruction, | 
|  | GlobalTypeInferenceResults results, | 
|  | JClosedWorld closedWorld, | 
|  | ) { | 
|  | // All bitwise operations on primitive types either produce an | 
|  | // integer or throw an error. | 
|  | if (instruction.inputs[1] | 
|  | .isPrimitiveOrNull(closedWorld.abstractValueDomain) | 
|  | .isDefinitelyTrue) { | 
|  | return closedWorld.abstractValueDomain.uint32Type; | 
|  | } | 
|  | return super.computeTypeFromInputTypes(instruction, results, closedWorld); | 
|  | } | 
|  |  | 
|  | @override | 
|  | HInstruction? tryConvertToBuiltin( | 
|  | HInvokeDynamic instruction, | 
|  | HGraph graph, | 
|  | GlobalTypeInferenceResults results, | 
|  | JCommonElements commonElements, | 
|  | JClosedWorld closedWorld, | 
|  | OptimizationTestLog? log, | 
|  | ) { | 
|  | HInstruction input = instruction.inputs[1]; | 
|  | if (input.isNumber(closedWorld.abstractValueDomain).isDefinitelyTrue) { | 
|  | HBitNot converted = HBitNot( | 
|  | input, | 
|  | computeTypeFromInputTypes(instruction, results, closedWorld), | 
|  | ); | 
|  | log?.registerBitNot(instruction, converted); | 
|  | return converted; | 
|  | } | 
|  | return null; | 
|  | } | 
|  | } | 
|  |  | 
|  | class UnaryNegateSpecializer extends InvokeDynamicSpecializer { | 
|  | const UnaryNegateSpecializer(); | 
|  |  | 
|  | @override | 
|  | constant_system.UnaryOperation operation() { | 
|  | return constant_system.negate; | 
|  | } | 
|  |  | 
|  | @override | 
|  | AbstractValue computeTypeFromInputTypes( | 
|  | HInvokeDynamic instruction, | 
|  | GlobalTypeInferenceResults results, | 
|  | JClosedWorld closedWorld, | 
|  | ) { | 
|  | HInstruction operand = instruction.inputs[1]; | 
|  | if (operand | 
|  | .isNumberOrNull(closedWorld.abstractValueDomain) | 
|  | .isDefinitelyTrue) { | 
|  | // We have integer subclasses that represent ranges, so widen any int | 
|  | // subclass to full integer. | 
|  | if (operand | 
|  | .isIntegerOrNull(closedWorld.abstractValueDomain) | 
|  | .isDefinitelyTrue) { | 
|  | return closedWorld.abstractValueDomain.intType; | 
|  | } | 
|  | return closedWorld.abstractValueDomain.numType; | 
|  | } | 
|  | return super.computeTypeFromInputTypes(instruction, results, closedWorld); | 
|  | } | 
|  |  | 
|  | @override | 
|  | HInstruction? tryConvertToBuiltin( | 
|  | HInvokeDynamic instruction, | 
|  | HGraph graph, | 
|  | GlobalTypeInferenceResults results, | 
|  | JCommonElements commonElements, | 
|  | JClosedWorld closedWorld, | 
|  | OptimizationTestLog? log, | 
|  | ) { | 
|  | HInstruction input = instruction.inputs[1]; | 
|  | if (input.isNumber(closedWorld.abstractValueDomain).isDefinitelyTrue) { | 
|  | HNegate converted = HNegate( | 
|  | input, | 
|  | computeTypeFromInputTypes(instruction, results, closedWorld), | 
|  | ); | 
|  | log?.registerUnaryNegate(instruction, converted); | 
|  | return converted; | 
|  | } | 
|  | return null; | 
|  | } | 
|  | } | 
|  |  | 
|  | class AbsSpecializer extends InvokeDynamicSpecializer { | 
|  | const AbsSpecializer(); | 
|  |  | 
|  | @override | 
|  | constant_system.UnaryOperation operation() { | 
|  | return constant_system.abs; | 
|  | } | 
|  |  | 
|  | @override | 
|  | AbstractValue computeTypeFromInputTypes( | 
|  | HInvokeDynamic instruction, | 
|  | GlobalTypeInferenceResults results, | 
|  | JClosedWorld closedWorld, | 
|  | ) { | 
|  | HInstruction input = instruction.inputs[1]; | 
|  | if (input | 
|  | .isNumberOrNull(closedWorld.abstractValueDomain) | 
|  | .isDefinitelyTrue) { | 
|  | return closedWorld.abstractValueDomain.excludeNull(input.instructionType); | 
|  | } | 
|  | return super.computeTypeFromInputTypes(instruction, results, closedWorld); | 
|  | } | 
|  |  | 
|  | @override | 
|  | HInstruction? tryConvertToBuiltin( | 
|  | HInvokeDynamic instruction, | 
|  | HGraph graph, | 
|  | GlobalTypeInferenceResults results, | 
|  | JCommonElements commonElements, | 
|  | JClosedWorld closedWorld, | 
|  | OptimizationTestLog? log, | 
|  | ) { | 
|  | HInstruction input = instruction.inputs[1]; | 
|  | if (input.isNumber(closedWorld.abstractValueDomain).isDefinitelyTrue) { | 
|  | HAbs converted = HAbs( | 
|  | input, | 
|  | computeTypeFromInputTypes(instruction, results, closedWorld), | 
|  | ); | 
|  | log?.registerAbs(instruction, converted); | 
|  | return converted; | 
|  | } | 
|  | return null; | 
|  | } | 
|  |  | 
|  | void registerOptimization( | 
|  | OptimizationTestLog log, | 
|  | HInvokeDynamic original, | 
|  | HInstruction converted, | 
|  | ) {} | 
|  | } | 
|  |  | 
|  | abstract class BinaryArithmeticSpecializer extends InvokeDynamicSpecializer { | 
|  | const BinaryArithmeticSpecializer(); | 
|  |  | 
|  | @override | 
|  | AbstractValue computeTypeFromInputTypes( | 
|  | HInvokeDynamic instruction, | 
|  | GlobalTypeInferenceResults results, | 
|  | JClosedWorld closedWorld, | 
|  | ) { | 
|  | HInstruction left = instruction.inputs[1]; | 
|  | HInstruction right = instruction.inputs[2]; | 
|  | if (left | 
|  | .isIntegerOrNull(closedWorld.abstractValueDomain) | 
|  | .isDefinitelyTrue && | 
|  | right | 
|  | .isIntegerOrNull(closedWorld.abstractValueDomain) | 
|  | .isDefinitelyTrue) { | 
|  | return closedWorld.abstractValueDomain.intType; | 
|  | } | 
|  | if (left.isNumberOrNull(closedWorld.abstractValueDomain).isDefinitelyTrue) { | 
|  | return closedWorld.abstractValueDomain.numType; | 
|  | } | 
|  | return super.computeTypeFromInputTypes(instruction, results, closedWorld); | 
|  | } | 
|  |  | 
|  | bool isBuiltin(HInvokeDynamic instruction, JClosedWorld closedWorld) { | 
|  | return instruction.inputs[1] | 
|  | .isNumber(closedWorld.abstractValueDomain) | 
|  | .isDefinitelyTrue && | 
|  | instruction.inputs[2] | 
|  | .isNumber(closedWorld.abstractValueDomain) | 
|  | .isDefinitelyTrue; | 
|  | } | 
|  |  | 
|  | @override | 
|  | HInstruction? tryConvertToBuiltin( | 
|  | HInvokeDynamic instruction, | 
|  | HGraph graph, | 
|  | GlobalTypeInferenceResults results, | 
|  | JCommonElements commonElements, | 
|  | JClosedWorld closedWorld, | 
|  | OptimizationTestLog? log, | 
|  | ) { | 
|  | if (isBuiltin(instruction, closedWorld)) { | 
|  | HInstruction? builtin = newBuiltinVariant( | 
|  | instruction, | 
|  | results, | 
|  | closedWorld, | 
|  | ); | 
|  | if (log != null) { | 
|  | registerOptimization(log, instruction, builtin); | 
|  | } | 
|  | if (builtin != null) { | 
|  | return builtin; | 
|  | } | 
|  | // Even if there is no builtin equivalent instruction, we know | 
|  | // the instruction does not have any side effect, and that it | 
|  | // can be GVN'ed. | 
|  | clearAllSideEffects(instruction); | 
|  | } | 
|  | return null; | 
|  | } | 
|  |  | 
|  | bool inputsAreNum(HInstruction instruction, JClosedWorld closedWorld) { | 
|  | HInstruction left = instruction.inputs[1]; | 
|  | HInstruction right = instruction.inputs[2]; | 
|  | return left | 
|  | .isNumberOrNull(closedWorld.abstractValueDomain) | 
|  | .isDefinitelyTrue && | 
|  | right.isNumberOrNull(closedWorld.abstractValueDomain).isDefinitelyTrue; | 
|  | } | 
|  |  | 
|  | bool inputsArePositiveIntegers( | 
|  | HInstruction instruction, | 
|  | JClosedWorld closedWorld, | 
|  | ) { | 
|  | HInstruction left = instruction.inputs[1]; | 
|  | HInstruction right = instruction.inputs[2]; | 
|  | return left | 
|  | .isPositiveIntegerOrNull(closedWorld.abstractValueDomain) | 
|  | .isDefinitelyTrue && | 
|  | right | 
|  | .isPositiveIntegerOrNull(closedWorld.abstractValueDomain) | 
|  | .isDefinitelyTrue; | 
|  | } | 
|  |  | 
|  | bool inputsAreUInt31(HInstruction instruction, JClosedWorld closedWorld) { | 
|  | HInstruction left = instruction.inputs[1]; | 
|  | HInstruction right = instruction.inputs[2]; | 
|  | return left.isUInt31(closedWorld.abstractValueDomain).isDefinitelyTrue && | 
|  | right.isUInt31(closedWorld.abstractValueDomain).isDefinitelyTrue; | 
|  | } | 
|  |  | 
|  | HInstruction? newBuiltinVariant( | 
|  | HInvokeDynamic instruction, | 
|  | GlobalTypeInferenceResults results, | 
|  | JClosedWorld closedWorld, | 
|  | ); | 
|  |  | 
|  | void registerOptimization( | 
|  | OptimizationTestLog log, | 
|  | HInvokeDynamic original, | 
|  | HInstruction? converted, | 
|  | ); | 
|  | } | 
|  |  | 
|  | class AddSpecializer extends BinaryArithmeticSpecializer { | 
|  | const AddSpecializer(); | 
|  |  | 
|  | @override | 
|  | AbstractValue computeTypeFromInputTypes( | 
|  | HInvokeDynamic instruction, | 
|  | GlobalTypeInferenceResults results, | 
|  | JClosedWorld closedWorld, | 
|  | ) { | 
|  | if (inputsAreUInt31(instruction, closedWorld)) { | 
|  | return closedWorld.abstractValueDomain.uint32Type; | 
|  | } | 
|  | if (inputsArePositiveIntegers(instruction, closedWorld)) { | 
|  | return closedWorld.abstractValueDomain.positiveIntType; | 
|  | } | 
|  | return super.computeTypeFromInputTypes(instruction, results, closedWorld); | 
|  | } | 
|  |  | 
|  | @override | 
|  | constant_system.BinaryOperation operation() { | 
|  | return constant_system.add; | 
|  | } | 
|  |  | 
|  | @override | 
|  | HInstruction newBuiltinVariant( | 
|  | HInvokeDynamic instruction, | 
|  | GlobalTypeInferenceResults results, | 
|  | JClosedWorld closedWorld, | 
|  | ) { | 
|  | return HAdd( | 
|  | instruction.inputs[1], | 
|  | instruction.inputs[2], | 
|  | computeTypeFromInputTypes(instruction, results, closedWorld), | 
|  | ); | 
|  | } | 
|  |  | 
|  | @override | 
|  | void registerOptimization( | 
|  | OptimizationTestLog log, | 
|  | HInvokeDynamic original, | 
|  | HInstruction? converted, | 
|  | ) { | 
|  | log.registerAdd(original, converted); | 
|  | } | 
|  | } | 
|  |  | 
|  | class DivideSpecializer extends BinaryArithmeticSpecializer { | 
|  | const DivideSpecializer(); | 
|  |  | 
|  | @override | 
|  | constant_system.BinaryOperation operation() { | 
|  | return constant_system.divide; | 
|  | } | 
|  |  | 
|  | @override | 
|  | AbstractValue computeTypeFromInputTypes( | 
|  | HInvokeDynamic instruction, | 
|  | GlobalTypeInferenceResults results, | 
|  | JClosedWorld closedWorld, | 
|  | ) { | 
|  | HInstruction left = instruction.inputs[1]; | 
|  | if (left.isNumberOrNull(closedWorld.abstractValueDomain).isDefinitelyTrue) { | 
|  | return closedWorld.abstractValueDomain.numType; | 
|  | } | 
|  | return super.computeTypeFromInputTypes(instruction, results, closedWorld); | 
|  | } | 
|  |  | 
|  | @override | 
|  | HInstruction newBuiltinVariant( | 
|  | HInvokeDynamic instruction, | 
|  | GlobalTypeInferenceResults results, | 
|  | JClosedWorld closedWorld, | 
|  | ) { | 
|  | return HDivide( | 
|  | instruction.inputs[1], | 
|  | instruction.inputs[2], | 
|  | closedWorld.abstractValueDomain.numType, | 
|  | ); | 
|  | } | 
|  |  | 
|  | @override | 
|  | void registerOptimization( | 
|  | OptimizationTestLog log, | 
|  | HInvokeDynamic original, | 
|  | HInstruction? converted, | 
|  | ) { | 
|  | log.registerDivide(original, converted); | 
|  | } | 
|  | } | 
|  |  | 
|  | class ModuloSpecializer extends BinaryArithmeticSpecializer { | 
|  | const ModuloSpecializer(); | 
|  |  | 
|  | @override | 
|  | AbstractValue computeTypeFromInputTypes( | 
|  | HInvokeDynamic instruction, | 
|  | GlobalTypeInferenceResults results, | 
|  | JClosedWorld closedWorld, | 
|  | ) { | 
|  | if (inputsArePositiveIntegers(instruction, closedWorld)) { | 
|  | return closedWorld.abstractValueDomain.positiveIntType; | 
|  | } | 
|  | return super.computeTypeFromInputTypes(instruction, results, closedWorld); | 
|  | } | 
|  |  | 
|  | @override | 
|  | constant_system.BinaryOperation operation() { | 
|  | return constant_system.modulo; | 
|  | } | 
|  |  | 
|  | @override | 
|  | HInstruction? newBuiltinVariant( | 
|  | HInvokeDynamic instruction, | 
|  | GlobalTypeInferenceResults results, | 
|  | JClosedWorld closedWorld, | 
|  | ) { | 
|  | // Modulo cannot be mapped to the native operator (different semantics). | 
|  |  | 
|  | // We can use HRemainder if both inputs are non-negative and the receiver | 
|  | // cannot be -0.0.  Note that -0.0 is considered to be an int, so until we | 
|  | // track -0.0 precisely, we have to syntactically filter inputs that cannot | 
|  | // generate -0.0. | 
|  |  | 
|  | HInstruction receiver = instruction.getDartReceiver(); | 
|  | if (inputsArePositiveIntegers(instruction, closedWorld) && | 
|  | !canBeNegativeZero(receiver)) { | 
|  | return HRemainder( | 
|  | instruction.inputs[1], | 
|  | instruction.inputs[2], | 
|  | computeTypeFromInputTypes(instruction, results, closedWorld), | 
|  | ); | 
|  | } | 
|  | // TODO(sra): | 
|  | //   a % N -->  a & (N-1), N=2^k, where a>=0, does not have -0.0 problem. | 
|  |  | 
|  | // TODO(sra): We could avoid problems with -0.0 if we generate x % y as (x + | 
|  | // 0) % y, but we would have to fix HAdd optimizations. | 
|  |  | 
|  | // TODO(sra): We could replace $mod with HRemainder when we don't care about | 
|  | // a -0.0 result (e.g. a % 10 == 0, a[i % 3]). This is tricky, since we | 
|  | // don't want to ruin GVN opportunities. | 
|  | return null; | 
|  | } | 
|  |  | 
|  | @override | 
|  | void registerOptimization( | 
|  | OptimizationTestLog log, | 
|  | HInvokeDynamic original, | 
|  | HInstruction? converted, | 
|  | ) { | 
|  | log.registerModulo(original, converted); | 
|  | } | 
|  | } | 
|  |  | 
|  | class RemainderSpecializer extends BinaryArithmeticSpecializer { | 
|  | const RemainderSpecializer(); | 
|  |  | 
|  | @override | 
|  | AbstractValue computeTypeFromInputTypes( | 
|  | HInvokeDynamic instruction, | 
|  | GlobalTypeInferenceResults results, | 
|  | JClosedWorld closedWorld, | 
|  | ) { | 
|  | if (inputsArePositiveIntegers(instruction, closedWorld)) { | 
|  | return closedWorld.abstractValueDomain.positiveIntType; | 
|  | } | 
|  | return super.computeTypeFromInputTypes(instruction, results, closedWorld); | 
|  | } | 
|  |  | 
|  | @override | 
|  | constant_system.BinaryOperation operation() { | 
|  | return constant_system.remainder; | 
|  | } | 
|  |  | 
|  | @override | 
|  | HInstruction newBuiltinVariant( | 
|  | HInvokeDynamic instruction, | 
|  | GlobalTypeInferenceResults results, | 
|  | JClosedWorld closedWorld, | 
|  | ) { | 
|  | return HRemainder( | 
|  | instruction.inputs[1], | 
|  | instruction.inputs[2], | 
|  | computeTypeFromInputTypes(instruction, results, closedWorld), | 
|  | ); | 
|  | } | 
|  |  | 
|  | @override | 
|  | void registerOptimization( | 
|  | OptimizationTestLog log, | 
|  | HInvokeDynamic original, | 
|  | HInstruction? converted, | 
|  | ) { | 
|  | log.registerRemainder(original, converted); | 
|  | } | 
|  | } | 
|  |  | 
|  | class MultiplySpecializer extends BinaryArithmeticSpecializer { | 
|  | const MultiplySpecializer(); | 
|  |  | 
|  | @override | 
|  | constant_system.BinaryOperation operation() { | 
|  | return constant_system.multiply; | 
|  | } | 
|  |  | 
|  | @override | 
|  | AbstractValue computeTypeFromInputTypes( | 
|  | HInvokeDynamic instruction, | 
|  | GlobalTypeInferenceResults results, | 
|  | JClosedWorld closedWorld, | 
|  | ) { | 
|  | if (inputsArePositiveIntegers(instruction, closedWorld)) { | 
|  | return closedWorld.abstractValueDomain.positiveIntType; | 
|  | } | 
|  | return super.computeTypeFromInputTypes(instruction, results, closedWorld); | 
|  | } | 
|  |  | 
|  | @override | 
|  | HInstruction newBuiltinVariant( | 
|  | HInvokeDynamic instruction, | 
|  | GlobalTypeInferenceResults results, | 
|  | JClosedWorld closedWorld, | 
|  | ) { | 
|  | return HMultiply( | 
|  | instruction.inputs[1], | 
|  | instruction.inputs[2], | 
|  | computeTypeFromInputTypes(instruction, results, closedWorld), | 
|  | ); | 
|  | } | 
|  |  | 
|  | @override | 
|  | void registerOptimization( | 
|  | OptimizationTestLog log, | 
|  | HInvokeDynamic original, | 
|  | HInstruction? converted, | 
|  | ) { | 
|  | log.registerMultiply(original, converted); | 
|  | } | 
|  | } | 
|  |  | 
|  | class SubtractSpecializer extends BinaryArithmeticSpecializer { | 
|  | const SubtractSpecializer(); | 
|  |  | 
|  | @override | 
|  | constant_system.BinaryOperation operation() { | 
|  | return constant_system.subtract; | 
|  | } | 
|  |  | 
|  | @override | 
|  | HInstruction newBuiltinVariant( | 
|  | HInvokeDynamic instruction, | 
|  | GlobalTypeInferenceResults results, | 
|  | JClosedWorld closedWorld, | 
|  | ) { | 
|  | return HSubtract( | 
|  | instruction.inputs[1], | 
|  | instruction.inputs[2], | 
|  | computeTypeFromInputTypes(instruction, results, closedWorld), | 
|  | ); | 
|  | } | 
|  |  | 
|  | @override | 
|  | void registerOptimization( | 
|  | OptimizationTestLog log, | 
|  | HInvokeDynamic original, | 
|  | HInstruction? converted, | 
|  | ) { | 
|  | log.registerSubtract(original, converted); | 
|  | } | 
|  | } | 
|  |  | 
|  | class TruncatingDivideSpecializer extends BinaryArithmeticSpecializer { | 
|  | const TruncatingDivideSpecializer(); | 
|  |  | 
|  | @override | 
|  | constant_system.BinaryOperation operation() { | 
|  | return constant_system.truncatingDivide; | 
|  | } | 
|  |  | 
|  | @override | 
|  | AbstractValue computeTypeFromInputTypes( | 
|  | HInvokeDynamic instruction, | 
|  | GlobalTypeInferenceResults results, | 
|  | JClosedWorld closedWorld, | 
|  | ) { | 
|  | if (hasUint31Result(instruction, closedWorld)) { | 
|  | return closedWorld.abstractValueDomain.uint31Type; | 
|  | } | 
|  | if (inputsArePositiveIntegers(instruction, closedWorld)) { | 
|  | return closedWorld.abstractValueDomain.positiveIntType; | 
|  | } | 
|  | if (inputsAreNum(instruction, closedWorld)) { | 
|  | return closedWorld.abstractValueDomain.intType; | 
|  | } | 
|  | return super.computeTypeFromInputTypes(instruction, results, closedWorld); | 
|  | } | 
|  |  | 
|  | bool isNotZero(HInstruction instruction) { | 
|  | if (instruction is HConstant) { | 
|  | ConstantValue constant = instruction.constant; | 
|  | if (constant is IntConstantValue) { | 
|  | return constant.intValue != BigInt.zero; | 
|  | } | 
|  | } | 
|  | return false; | 
|  | } | 
|  |  | 
|  | bool isTwoOrGreater(HInstruction instruction) { | 
|  | if (instruction is HConstant) { | 
|  | ConstantValue constant = instruction.constant; | 
|  | if (constant is IntConstantValue) { | 
|  | return constant.intValue >= BigInt.two; | 
|  | } | 
|  | } | 
|  | return false; | 
|  | } | 
|  |  | 
|  | bool hasUint31Result(HInstruction instruction, JClosedWorld closedWorld) { | 
|  | HInstruction left = instruction.inputs[1]; | 
|  | HInstruction right = instruction.inputs[2]; | 
|  | if (right | 
|  | .isPositiveInteger(closedWorld.abstractValueDomain) | 
|  | .isDefinitelyTrue) { | 
|  | if (left.isUInt31(closedWorld.abstractValueDomain).isDefinitelyTrue && | 
|  | isNotZero(right)) { | 
|  | return true; | 
|  | } | 
|  | if (left.isUInt32(closedWorld.abstractValueDomain).isDefinitelyTrue && | 
|  | isTwoOrGreater(right)) { | 
|  | return true; | 
|  | } | 
|  | } | 
|  | return false; | 
|  | } | 
|  |  | 
|  | @override | 
|  | HInstruction? tryConvertToBuiltin( | 
|  | HInvokeDynamic instruction, | 
|  | HGraph graph, | 
|  | GlobalTypeInferenceResults results, | 
|  | JCommonElements commonElements, | 
|  | JClosedWorld closedWorld, | 
|  | OptimizationTestLog? log, | 
|  | ) { | 
|  | HInstruction right = instruction.inputs[2]; | 
|  | if (isBuiltin(instruction, closedWorld)) { | 
|  | if (right | 
|  | .isPositiveInteger(closedWorld.abstractValueDomain) | 
|  | .isDefinitelyTrue && | 
|  | isNotZero(right)) { | 
|  | if (hasUint31Result(instruction, closedWorld)) { | 
|  | HInstruction converted = newBuiltinVariant( | 
|  | instruction, | 
|  | results, | 
|  | closedWorld, | 
|  | ); | 
|  | if (log != null) { | 
|  | registerOptimization(log, instruction, converted); | 
|  | } | 
|  | return converted; | 
|  | } | 
|  | // We can call _tdivFast because the rhs is a 32bit integer | 
|  | // and not 0, nor -1. | 
|  | redirectSelector(instruction, '_tdivFast', commonElements); | 
|  | if (log != null) { | 
|  | registerOptimization(log, instruction, null); | 
|  | } | 
|  | } | 
|  | clearAllSideEffects(instruction); | 
|  | } | 
|  | return null; | 
|  | } | 
|  |  | 
|  | @override | 
|  | HInstruction newBuiltinVariant( | 
|  | HInvokeDynamic instruction, | 
|  | GlobalTypeInferenceResults results, | 
|  | JClosedWorld closedWorld, | 
|  | ) { | 
|  | return HTruncatingDivide( | 
|  | instruction.inputs[1], | 
|  | instruction.inputs[2], | 
|  | computeTypeFromInputTypes(instruction, results, closedWorld), | 
|  | ); | 
|  | } | 
|  |  | 
|  | @override | 
|  | void registerOptimization( | 
|  | OptimizationTestLog log, | 
|  | HInvokeDynamic original, | 
|  | HInstruction? converted, | 
|  | ) { | 
|  | log.registerTruncatingDivide(original, converted); | 
|  | } | 
|  | } | 
|  |  | 
|  | abstract class BinaryBitOpSpecializer extends BinaryArithmeticSpecializer { | 
|  | const BinaryBitOpSpecializer(); | 
|  |  | 
|  | @override | 
|  | AbstractValue computeTypeFromInputTypes( | 
|  | HInvokeDynamic instruction, | 
|  | GlobalTypeInferenceResults results, | 
|  | JClosedWorld closedWorld, | 
|  | ) { | 
|  | // All bitwise operations on number types either produce an unsigned 32-bit | 
|  | // integer or throw an error. | 
|  | HInstruction left = instruction.inputs[1]; | 
|  | if (left.isNumberOrNull(closedWorld.abstractValueDomain).isDefinitelyTrue) { | 
|  | return closedWorld.abstractValueDomain.uint32Type; | 
|  | } | 
|  | return super.computeTypeFromInputTypes(instruction, results, closedWorld); | 
|  | } | 
|  |  | 
|  | bool argumentLessThan32(HInstruction instruction) { | 
|  | return argumentInRange(instruction, 0, 31); | 
|  | } | 
|  |  | 
|  | bool argumentInRange(HInstruction instruction, int low, int high) { | 
|  | if (instruction is HConstant) { | 
|  | ConstantValue constant = instruction.constant; | 
|  | if (constant is IntConstantValue) { | 
|  | int value = constant.intValue.toInt(); | 
|  | assert(constant.intValue == BigInt.from(constant.intValue.toInt())); | 
|  | return value >= low && value <= high; | 
|  | } | 
|  | } | 
|  | // TODO(sra): Integrate with the bit-width analysis in codegen.dart. | 
|  | if (instruction is HBitAnd) { | 
|  | return low == 0 && | 
|  | (argumentInRange(instruction.inputs[0], low, high) || | 
|  | argumentInRange(instruction.inputs[1], low, high)); | 
|  | } | 
|  | return false; | 
|  | } | 
|  |  | 
|  | bool isPositive(HInstruction instruction, JClosedWorld closedWorld) { | 
|  | // TODO: We should use the value range analysis. Currently, ranges | 
|  | // are discarded just after the analysis. | 
|  | return instruction | 
|  | .isPositiveInteger(closedWorld.abstractValueDomain) | 
|  | .isDefinitelyTrue; | 
|  | } | 
|  | } | 
|  |  | 
|  | class ShiftLeftSpecializer extends BinaryBitOpSpecializer { | 
|  | const ShiftLeftSpecializer(); | 
|  |  | 
|  | @override | 
|  | constant_system.BinaryOperation operation() { | 
|  | return constant_system.shiftLeft; | 
|  | } | 
|  |  | 
|  | @override | 
|  | HInstruction? tryConvertToBuiltin( | 
|  | HInvokeDynamic instruction, | 
|  | HGraph graph, | 
|  | GlobalTypeInferenceResults results, | 
|  | JCommonElements commonElements, | 
|  | JClosedWorld closedWorld, | 
|  | OptimizationTestLog? log, | 
|  | ) { | 
|  | HInstruction left = instruction.inputs[1]; | 
|  | HInstruction right = instruction.inputs[2]; | 
|  | if (left.isNumber(closedWorld.abstractValueDomain).isDefinitelyTrue) { | 
|  | if (argumentLessThan32(right)) { | 
|  | HInstruction converted = newBuiltinVariant( | 
|  | instruction, | 
|  | results, | 
|  | closedWorld, | 
|  | ); | 
|  | if (log != null) { | 
|  | registerOptimization(log, instruction, converted); | 
|  | } | 
|  | return converted; | 
|  | } | 
|  | // Even if there is no builtin equivalent instruction, we know | 
|  | // the instruction does not have any side effect, and that it | 
|  | // can be GVN'ed. | 
|  | clearAllSideEffects(instruction); | 
|  | if (isPositive(right, closedWorld)) { | 
|  | redirectSelector(instruction, '_shlPositive', commonElements); | 
|  | } | 
|  | if (log != null) { | 
|  | registerOptimization(log, instruction, null); | 
|  | } | 
|  | } | 
|  | return null; | 
|  | } | 
|  |  | 
|  | @override | 
|  | HInstruction newBuiltinVariant( | 
|  | HInvokeDynamic instruction, | 
|  | GlobalTypeInferenceResults results, | 
|  | JClosedWorld closedWorld, | 
|  | ) { | 
|  | return HShiftLeft( | 
|  | instruction.inputs[1], | 
|  | instruction.inputs[2], | 
|  | computeTypeFromInputTypes(instruction, results, closedWorld), | 
|  | ); | 
|  | } | 
|  |  | 
|  | @override | 
|  | void registerOptimization( | 
|  | OptimizationTestLog log, | 
|  | HInvokeDynamic original, | 
|  | HInstruction? converted, | 
|  | ) { | 
|  | log.registerShiftLeft(original, converted); | 
|  | } | 
|  | } | 
|  |  | 
|  | class ShiftRightSpecializer extends BinaryBitOpSpecializer { | 
|  | const ShiftRightSpecializer(); | 
|  |  | 
|  | @override | 
|  | AbstractValue computeTypeFromInputTypes( | 
|  | HInvokeDynamic instruction, | 
|  | GlobalTypeInferenceResults results, | 
|  | JClosedWorld closedWorld, | 
|  | ) { | 
|  | HInstruction left = instruction.inputs[1]; | 
|  | if (left.isUInt32(closedWorld.abstractValueDomain).isDefinitelyTrue) { | 
|  | return left.instructionType; | 
|  | } | 
|  | return super.computeTypeFromInputTypes(instruction, results, closedWorld); | 
|  | } | 
|  |  | 
|  | @override | 
|  | HInstruction? tryConvertToBuiltin( | 
|  | HInvokeDynamic instruction, | 
|  | HGraph graph, | 
|  | GlobalTypeInferenceResults results, | 
|  | JCommonElements commonElements, | 
|  | JClosedWorld closedWorld, | 
|  | OptimizationTestLog? log, | 
|  | ) { | 
|  | HInstruction left = instruction.inputs[1]; | 
|  | HInstruction right = instruction.inputs[2]; | 
|  | if (left.isNumber(closedWorld.abstractValueDomain).isDefinitelyTrue) { | 
|  | if (argumentLessThan32(right) && isPositive(left, closedWorld)) { | 
|  | HInstruction converted = newBuiltinVariant( | 
|  | instruction, | 
|  | results, | 
|  | closedWorld, | 
|  | ); | 
|  | if (log != null) { | 
|  | registerOptimization(log, instruction, converted); | 
|  | } | 
|  | return converted; | 
|  | } | 
|  | // Even if there is no builtin equivalent instruction, we know | 
|  | // the instruction does not have any side effect, and that it | 
|  | // can be GVN'ed. | 
|  | clearAllSideEffects(instruction); | 
|  | if (isPositive(right, closedWorld) && isPositive(left, closedWorld)) { | 
|  | redirectSelector(instruction, '_shrBothPositive', commonElements); | 
|  | if (log != null) { | 
|  | registerOptimization(log, instruction, null); | 
|  | } | 
|  | } else if (isPositive(left, closedWorld) && | 
|  | right.isNumber(closedWorld.abstractValueDomain).isDefinitelyTrue) { | 
|  | redirectSelector(instruction, '_shrReceiverPositive', commonElements); | 
|  | if (log != null) { | 
|  | registerOptimization(log, instruction, null); | 
|  | } | 
|  | } else if (isPositive(right, closedWorld)) { | 
|  | redirectSelector(instruction, '_shrOtherPositive', commonElements); | 
|  | if (log != null) { | 
|  | registerOptimization(log, instruction, null); | 
|  | } | 
|  | } | 
|  | } | 
|  | return null; | 
|  | } | 
|  |  | 
|  | @override | 
|  | HInstruction newBuiltinVariant( | 
|  | HInvokeDynamic instruction, | 
|  | GlobalTypeInferenceResults results, | 
|  | JClosedWorld closedWorld, | 
|  | ) { | 
|  | return HShiftRight( | 
|  | instruction.inputs[1], | 
|  | instruction.inputs[2], | 
|  | computeTypeFromInputTypes(instruction, results, closedWorld), | 
|  | ); | 
|  | } | 
|  |  | 
|  | @override | 
|  | constant_system.BinaryOperation operation() { | 
|  | return constant_system.shiftRight; | 
|  | } | 
|  |  | 
|  | @override | 
|  | void registerOptimization( | 
|  | OptimizationTestLog log, | 
|  | HInvokeDynamic original, | 
|  | HInstruction? converted, | 
|  | ) { | 
|  | log.registerShiftRight(original, converted); | 
|  | } | 
|  | } | 
|  |  | 
|  | class ShiftRightUnsignedSpecializer extends BinaryBitOpSpecializer { | 
|  | const ShiftRightUnsignedSpecializer(); | 
|  |  | 
|  | @override | 
|  | AbstractValue computeTypeFromInputTypes( | 
|  | HInvokeDynamic instruction, | 
|  | GlobalTypeInferenceResults results, | 
|  | JClosedWorld closedWorld, | 
|  | ) { | 
|  | HInstruction left = instruction.inputs[1]; | 
|  | if (left.isUInt32(closedWorld.abstractValueDomain).isDefinitelyTrue) { | 
|  | return left.instructionType; | 
|  | } | 
|  | return super.computeTypeFromInputTypes(instruction, results, closedWorld); | 
|  | } | 
|  |  | 
|  | @override | 
|  | HInstruction? tryConvertToBuiltin( | 
|  | HInvokeDynamic instruction, | 
|  | HGraph graph, | 
|  | GlobalTypeInferenceResults results, | 
|  | JCommonElements commonElements, | 
|  | JClosedWorld closedWorld, | 
|  | OptimizationTestLog? log, | 
|  | ) { | 
|  | HInstruction left = instruction.inputs[1]; | 
|  | HInstruction right = instruction.inputs[2]; | 
|  | if (left.isInteger(closedWorld.abstractValueDomain).isDefinitelyTrue) { | 
|  | if (argumentLessThan32(right)) { | 
|  | HInstruction converted = newBuiltinVariant( | 
|  | instruction, | 
|  | results, | 
|  | closedWorld, | 
|  | ); | 
|  | if (log != null) { | 
|  | registerOptimization(log, instruction, converted); | 
|  | } | 
|  | return converted; | 
|  | } | 
|  | // Even if there is no builtin equivalent instruction, we know | 
|  | // the instruction does not have any side effect, and that it | 
|  | // can be GVN'ed. | 
|  | clearAllSideEffects(instruction); | 
|  | if (isPositive(right, closedWorld)) { | 
|  | redirectSelector(instruction, '_shruOtherPositive', commonElements); | 
|  | if (log != null) { | 
|  | registerOptimization(log, instruction, null); | 
|  | } | 
|  | } | 
|  | } | 
|  | return null; | 
|  | } | 
|  |  | 
|  | @override | 
|  | HInstruction newBuiltinVariant( | 
|  | HInvokeDynamic instruction, | 
|  | GlobalTypeInferenceResults results, | 
|  | JClosedWorld closedWorld, | 
|  | ) { | 
|  | return HShiftRight( | 
|  | instruction.inputs[1], | 
|  | instruction.inputs[2], | 
|  | computeTypeFromInputTypes(instruction, results, closedWorld), | 
|  | ); | 
|  | } | 
|  |  | 
|  | @override | 
|  | constant_system.BinaryOperation operation() { | 
|  | return constant_system.shiftRightUnsigned; | 
|  | } | 
|  |  | 
|  | @override | 
|  | void registerOptimization( | 
|  | OptimizationTestLog log, | 
|  | HInvokeDynamic original, | 
|  | HInstruction? converted, | 
|  | ) { | 
|  | log.registerShiftRightUnsigned(original, converted); | 
|  | } | 
|  | } | 
|  |  | 
|  | class BitOrSpecializer extends BinaryBitOpSpecializer { | 
|  | const BitOrSpecializer(); | 
|  |  | 
|  | @override | 
|  | constant_system.BinaryOperation operation() { | 
|  | return constant_system.bitOr; | 
|  | } | 
|  |  | 
|  | @override | 
|  | AbstractValue computeTypeFromInputTypes( | 
|  | HInvokeDynamic instruction, | 
|  | GlobalTypeInferenceResults results, | 
|  | JClosedWorld closedWorld, | 
|  | ) { | 
|  | HInstruction left = instruction.inputs[1]; | 
|  | HInstruction right = instruction.inputs[2]; | 
|  | if (left.isUInt31(closedWorld.abstractValueDomain).isDefinitelyTrue && | 
|  | right.isUInt31(closedWorld.abstractValueDomain).isDefinitelyTrue) { | 
|  | return closedWorld.abstractValueDomain.uint31Type; | 
|  | } | 
|  | return super.computeTypeFromInputTypes(instruction, results, closedWorld); | 
|  | } | 
|  |  | 
|  | @override | 
|  | HInstruction newBuiltinVariant( | 
|  | HInvokeDynamic instruction, | 
|  | GlobalTypeInferenceResults results, | 
|  | JClosedWorld closedWorld, | 
|  | ) { | 
|  | return HBitOr( | 
|  | instruction.inputs[1], | 
|  | instruction.inputs[2], | 
|  | computeTypeFromInputTypes(instruction, results, closedWorld), | 
|  | ); | 
|  | } | 
|  |  | 
|  | @override | 
|  | void registerOptimization( | 
|  | OptimizationTestLog log, | 
|  | HInvokeDynamic original, | 
|  | HInstruction? converted, | 
|  | ) { | 
|  | log.registerBitOr(original, converted); | 
|  | } | 
|  | } | 
|  |  | 
|  | class BitAndSpecializer extends BinaryBitOpSpecializer { | 
|  | const BitAndSpecializer(); | 
|  |  | 
|  | @override | 
|  | constant_system.BinaryOperation operation() { | 
|  | return constant_system.bitAnd; | 
|  | } | 
|  |  | 
|  | @override | 
|  | AbstractValue computeTypeFromInputTypes( | 
|  | HInvokeDynamic instruction, | 
|  | GlobalTypeInferenceResults results, | 
|  | JClosedWorld closedWorld, | 
|  | ) { | 
|  | HInstruction left = instruction.inputs[1]; | 
|  | HInstruction right = instruction.inputs[2]; | 
|  | if (left | 
|  | .isPrimitiveOrNull(closedWorld.abstractValueDomain) | 
|  | .isDefinitelyTrue && | 
|  | (left.isUInt31(closedWorld.abstractValueDomain).isDefinitelyTrue || | 
|  | right.isUInt31(closedWorld.abstractValueDomain).isDefinitelyTrue)) { | 
|  | return closedWorld.abstractValueDomain.uint31Type; | 
|  | } | 
|  | return super.computeTypeFromInputTypes(instruction, results, closedWorld); | 
|  | } | 
|  |  | 
|  | @override | 
|  | HInstruction newBuiltinVariant( | 
|  | HInvokeDynamic instruction, | 
|  | GlobalTypeInferenceResults results, | 
|  | JClosedWorld closedWorld, | 
|  | ) { | 
|  | return HBitAnd( | 
|  | instruction.inputs[1], | 
|  | instruction.inputs[2], | 
|  | computeTypeFromInputTypes(instruction, results, closedWorld), | 
|  | ); | 
|  | } | 
|  |  | 
|  | @override | 
|  | void registerOptimization( | 
|  | OptimizationTestLog log, | 
|  | HInvokeDynamic original, | 
|  | HInstruction? converted, | 
|  | ) { | 
|  | log.registerBitAnd(original, converted); | 
|  | } | 
|  | } | 
|  |  | 
|  | class BitXorSpecializer extends BinaryBitOpSpecializer { | 
|  | const BitXorSpecializer(); | 
|  |  | 
|  | @override | 
|  | constant_system.BinaryOperation operation() { | 
|  | return constant_system.bitXor; | 
|  | } | 
|  |  | 
|  | @override | 
|  | AbstractValue computeTypeFromInputTypes( | 
|  | HInvokeDynamic instruction, | 
|  | GlobalTypeInferenceResults results, | 
|  | JClosedWorld closedWorld, | 
|  | ) { | 
|  | HInstruction left = instruction.inputs[1]; | 
|  | HInstruction right = instruction.inputs[2]; | 
|  | if (left.isUInt31(closedWorld.abstractValueDomain).isDefinitelyTrue && | 
|  | right.isUInt31(closedWorld.abstractValueDomain).isDefinitelyTrue) { | 
|  | return closedWorld.abstractValueDomain.uint31Type; | 
|  | } | 
|  | return super.computeTypeFromInputTypes(instruction, results, closedWorld); | 
|  | } | 
|  |  | 
|  | @override | 
|  | HInstruction newBuiltinVariant( | 
|  | HInvokeDynamic instruction, | 
|  | GlobalTypeInferenceResults results, | 
|  | JClosedWorld closedWorld, | 
|  | ) { | 
|  | return HBitXor( | 
|  | instruction.inputs[1], | 
|  | instruction.inputs[2], | 
|  | computeTypeFromInputTypes(instruction, results, closedWorld), | 
|  | ); | 
|  | } | 
|  |  | 
|  | @override | 
|  | void registerOptimization( | 
|  | OptimizationTestLog log, | 
|  | HInvokeDynamic original, | 
|  | HInstruction? converted, | 
|  | ) { | 
|  | log.registerBitXor(original, converted); | 
|  | } | 
|  | } | 
|  |  | 
|  | abstract class RelationalSpecializer extends InvokeDynamicSpecializer { | 
|  | const RelationalSpecializer(); | 
|  |  | 
|  | @override | 
|  | AbstractValue computeTypeFromInputTypes( | 
|  | HInvokeDynamic instruction, | 
|  | GlobalTypeInferenceResults results, | 
|  | JClosedWorld closedWorld, | 
|  | ) { | 
|  | if (instruction.inputs[1] | 
|  | .isPrimitiveOrNull(closedWorld.abstractValueDomain) | 
|  | .isDefinitelyTrue) { | 
|  | return closedWorld.abstractValueDomain.boolType; | 
|  | } | 
|  | return super.computeTypeFromInputTypes(instruction, results, closedWorld); | 
|  | } | 
|  |  | 
|  | @override | 
|  | HInstruction? tryConvertToBuiltin( | 
|  | HInvokeDynamic instruction, | 
|  | HGraph graph, | 
|  | GlobalTypeInferenceResults results, | 
|  | JCommonElements commonElements, | 
|  | JClosedWorld closedWorld, | 
|  | OptimizationTestLog? log, | 
|  | ) { | 
|  | HInstruction left = instruction.inputs[1]; | 
|  | HInstruction right = instruction.inputs[2]; | 
|  | if (left.isNumber(closedWorld.abstractValueDomain).isDefinitelyTrue && | 
|  | right.isNumber(closedWorld.abstractValueDomain).isDefinitelyTrue) { | 
|  | HInstruction converted = newBuiltinVariant(instruction, closedWorld); | 
|  | if (log != null) { | 
|  | registerOptimization(log, instruction, converted); | 
|  | } | 
|  | return converted; | 
|  | } | 
|  | return null; | 
|  | } | 
|  |  | 
|  | HInstruction newBuiltinVariant( | 
|  | HInvokeDynamic instruction, | 
|  | JClosedWorld closedWorld, | 
|  | ); | 
|  |  | 
|  | void registerOptimization( | 
|  | OptimizationTestLog log, | 
|  | HInvokeDynamic original, | 
|  | HInstruction converted, | 
|  | ); | 
|  | } | 
|  |  | 
|  | class EqualsSpecializer extends RelationalSpecializer { | 
|  | const EqualsSpecializer(); | 
|  |  | 
|  | @override | 
|  | HInstruction? tryConvertToBuiltin( | 
|  | HInvokeDynamic instruction, | 
|  | HGraph graph, | 
|  | GlobalTypeInferenceResults results, | 
|  | JCommonElements commonElements, | 
|  | JClosedWorld closedWorld, | 
|  | OptimizationTestLog? log, | 
|  | ) { | 
|  | HInstruction left = instruction.inputs[1]; | 
|  | HInstruction right = instruction.inputs[2]; | 
|  | AbstractValue instructionType = left.instructionType; | 
|  | if (right.isConstantNull() || | 
|  | left | 
|  | .isPrimitiveOrNull(closedWorld.abstractValueDomain) | 
|  | .isDefinitelyTrue) { | 
|  | HInstruction converted = newBuiltinVariant(instruction, closedWorld); | 
|  | if (log != null) { | 
|  | registerOptimization(log, instruction, converted); | 
|  | } | 
|  | return converted; | 
|  | } | 
|  | if (closedWorld.includesClosureCall( | 
|  | instruction.selector, | 
|  | instructionType, | 
|  | )) { | 
|  | return null; | 
|  | } | 
|  | Iterable<MemberEntity> matches = closedWorld.locateMembers( | 
|  | instruction.selector, | 
|  | instructionType, | 
|  | ); | 
|  | // This test relies on `Object.==` and `Interceptor.==` always being | 
|  | // implemented because if the selector matches by subtype, it still will be | 
|  | // a regular object or an interceptor. | 
|  | if (matches.every( | 
|  | closedWorld.commonElements.isDefaultEqualityImplementation, | 
|  | )) { | 
|  | HInstruction converted = newBuiltinVariant(instruction, closedWorld); | 
|  | if (log != null) { | 
|  | registerOptimization(log, instruction, converted); | 
|  | } | 
|  | return converted; | 
|  | } | 
|  | return null; | 
|  | } | 
|  |  | 
|  | @override | 
|  | constant_system.BinaryOperation operation() { | 
|  | return constant_system.equal; | 
|  | } | 
|  |  | 
|  | @override | 
|  | HInstruction newBuiltinVariant( | 
|  | HInvokeDynamic instruction, | 
|  | JClosedWorld closedWorld, | 
|  | ) { | 
|  | return HIdentity( | 
|  | instruction.inputs[1], | 
|  | instruction.inputs[2], | 
|  | closedWorld.abstractValueDomain.boolType, | 
|  | ); | 
|  | } | 
|  |  | 
|  | @override | 
|  | void registerOptimization( | 
|  | OptimizationTestLog log, | 
|  | HInvokeDynamic original, | 
|  | HInstruction converted, | 
|  | ) { | 
|  | log.registerEquals(original, converted); | 
|  | } | 
|  | } | 
|  |  | 
|  | class LessSpecializer extends RelationalSpecializer { | 
|  | const LessSpecializer(); | 
|  |  | 
|  | @override | 
|  | constant_system.BinaryOperation operation() { | 
|  | return constant_system.less; | 
|  | } | 
|  |  | 
|  | @override | 
|  | HInstruction newBuiltinVariant( | 
|  | HInvokeDynamic instruction, | 
|  | JClosedWorld closedWorld, | 
|  | ) { | 
|  | return HLess( | 
|  | instruction.inputs[1], | 
|  | instruction.inputs[2], | 
|  | closedWorld.abstractValueDomain.boolType, | 
|  | ); | 
|  | } | 
|  |  | 
|  | @override | 
|  | void registerOptimization( | 
|  | OptimizationTestLog log, | 
|  | HInvokeDynamic original, | 
|  | HInstruction converted, | 
|  | ) { | 
|  | log.registerLess(original, converted); | 
|  | } | 
|  | } | 
|  |  | 
|  | class GreaterSpecializer extends RelationalSpecializer { | 
|  | const GreaterSpecializer(); | 
|  |  | 
|  | @override | 
|  | constant_system.BinaryOperation operation() { | 
|  | return constant_system.greater; | 
|  | } | 
|  |  | 
|  | @override | 
|  | HInstruction newBuiltinVariant( | 
|  | HInvokeDynamic instruction, | 
|  | JClosedWorld closedWorld, | 
|  | ) { | 
|  | return HGreater( | 
|  | instruction.inputs[1], | 
|  | instruction.inputs[2], | 
|  | closedWorld.abstractValueDomain.boolType, | 
|  | ); | 
|  | } | 
|  |  | 
|  | @override | 
|  | void registerOptimization( | 
|  | OptimizationTestLog log, | 
|  | HInvokeDynamic original, | 
|  | HInstruction converted, | 
|  | ) { | 
|  | log.registerGreater(original, converted); | 
|  | } | 
|  | } | 
|  |  | 
|  | class GreaterEqualSpecializer extends RelationalSpecializer { | 
|  | const GreaterEqualSpecializer(); | 
|  |  | 
|  | @override | 
|  | constant_system.BinaryOperation operation() { | 
|  | return constant_system.greaterEqual; | 
|  | } | 
|  |  | 
|  | @override | 
|  | HInstruction newBuiltinVariant( | 
|  | HInvokeDynamic instruction, | 
|  | JClosedWorld closedWorld, | 
|  | ) { | 
|  | return HGreaterEqual( | 
|  | instruction.inputs[1], | 
|  | instruction.inputs[2], | 
|  | closedWorld.abstractValueDomain.boolType, | 
|  | ); | 
|  | } | 
|  |  | 
|  | @override | 
|  | void registerOptimization( | 
|  | OptimizationTestLog log, | 
|  | HInvokeDynamic original, | 
|  | HInstruction converted, | 
|  | ) { | 
|  | log.registerGreaterEqual(original, converted); | 
|  | } | 
|  | } | 
|  |  | 
|  | class LessEqualSpecializer extends RelationalSpecializer { | 
|  | const LessEqualSpecializer(); | 
|  |  | 
|  | @override | 
|  | constant_system.BinaryOperation operation() { | 
|  | return constant_system.lessEqual; | 
|  | } | 
|  |  | 
|  | @override | 
|  | HInstruction newBuiltinVariant( | 
|  | HInvokeDynamic instruction, | 
|  | JClosedWorld closedWorld, | 
|  | ) { | 
|  | return HLessEqual( | 
|  | instruction.inputs[1], | 
|  | instruction.inputs[2], | 
|  | closedWorld.abstractValueDomain.boolType, | 
|  | ); | 
|  | } | 
|  |  | 
|  | @override | 
|  | void registerOptimization( | 
|  | OptimizationTestLog log, | 
|  | HInvokeDynamic original, | 
|  | HInstruction converted, | 
|  | ) { | 
|  | log.registerLessEqual(original, converted); | 
|  | } | 
|  | } | 
|  |  | 
|  | class CompareToSpecializer extends InvokeDynamicSpecializer { | 
|  | const CompareToSpecializer(); | 
|  |  | 
|  | @override | 
|  | HInstruction? tryConvertToBuiltin( | 
|  | HInvokeDynamic instruction, | 
|  | HGraph graph, | 
|  | GlobalTypeInferenceResults results, | 
|  | JCommonElements commonElements, | 
|  | JClosedWorld closedWorld, | 
|  | OptimizationTestLog? log, | 
|  | ) { | 
|  | HInstruction receiver = instruction.getDartReceiver(); | 
|  | // `compareTo` has no side-effect (other than throwing) and can be GVN'ed | 
|  | // for some known types. | 
|  | if (receiver | 
|  | .isStringOrNull(closedWorld.abstractValueDomain) | 
|  | .isDefinitelyTrue || | 
|  | receiver | 
|  | .isNumberOrNull(closedWorld.abstractValueDomain) | 
|  | .isDefinitelyTrue) { | 
|  | // Replace `a.compareTo(a)` with `0`, but only if receiver and argument | 
|  | // are such that no exceptions can be thrown. | 
|  | HInstruction argument = instruction.inputs.last; | 
|  | if ((receiver | 
|  | .isNumber(closedWorld.abstractValueDomain) | 
|  | .isDefinitelyTrue && | 
|  | argument | 
|  | .isNumber(closedWorld.abstractValueDomain) | 
|  | .isDefinitelyTrue) || | 
|  | (receiver | 
|  | .isString(closedWorld.abstractValueDomain) | 
|  | .isDefinitelyTrue && | 
|  | argument | 
|  | .isString(closedWorld.abstractValueDomain) | 
|  | .isDefinitelyTrue)) { | 
|  | if (identical(receiver.nonCheck(), argument.nonCheck())) { | 
|  | final converted = graph.addConstantInt(0, closedWorld); | 
|  | log?.registerCompareTo(instruction, converted); | 
|  | return converted; | 
|  | } | 
|  | } | 
|  | clearAllSideEffects(instruction); | 
|  | log?.registerCompareTo(instruction); | 
|  | } | 
|  | return null; | 
|  | } | 
|  | } | 
|  |  | 
|  | abstract class IdempotentStringOperationSpecializer | 
|  | extends InvokeDynamicSpecializer { | 
|  | const IdempotentStringOperationSpecializer(); | 
|  |  | 
|  | @override | 
|  | HInstruction? tryConvertToBuiltin( | 
|  | HInvokeDynamic instruction, | 
|  | HGraph graph, | 
|  | GlobalTypeInferenceResults results, | 
|  | JCommonElements commonElements, | 
|  | JClosedWorld closedWorld, | 
|  | OptimizationTestLog? log, | 
|  | ) { | 
|  | HInstruction receiver = instruction.getDartReceiver(); | 
|  | if (receiver | 
|  | .isStringOrNull(closedWorld.abstractValueDomain) | 
|  | .isDefinitelyTrue) { | 
|  | // String.xxx does not have any side effect (other than throwing), and it | 
|  | // can be GVN'ed. | 
|  | clearAllSideEffects(instruction); | 
|  | if (log != null) { | 
|  | registerOptimization(log, instruction); | 
|  | } | 
|  | } | 
|  | return null; | 
|  | } | 
|  |  | 
|  | void registerOptimization(OptimizationTestLog log, HInvokeDynamic original); | 
|  | } | 
|  |  | 
|  | class SubstringSpecializer extends IdempotentStringOperationSpecializer { | 
|  | const SubstringSpecializer(); | 
|  |  | 
|  | @override | 
|  | void registerOptimization(OptimizationTestLog log, HInvokeDynamic original) { | 
|  | log.registerSubstring(original); | 
|  | } | 
|  | } | 
|  |  | 
|  | class TrimSpecializer extends IdempotentStringOperationSpecializer { | 
|  | const TrimSpecializer(); | 
|  |  | 
|  | @override | 
|  | void registerOptimization(OptimizationTestLog log, HInvokeDynamic original) { | 
|  | log.registerTrim(original); | 
|  | } | 
|  | } | 
|  |  | 
|  | class PatternMatchSpecializer extends InvokeDynamicSpecializer { | 
|  | const PatternMatchSpecializer(); | 
|  |  | 
|  | @override | 
|  | HInstruction? tryConvertToBuiltin( | 
|  | HInvokeDynamic instruction, | 
|  | HGraph graph, | 
|  | GlobalTypeInferenceResults results, | 
|  | JCommonElements commonElements, | 
|  | JClosedWorld closedWorld, | 
|  | OptimizationTestLog? log, | 
|  | ) { | 
|  | HInstruction receiver = instruction.getDartReceiver(); | 
|  | HInstruction pattern = instruction.inputs[2]; | 
|  | if (receiver | 
|  | .isStringOrNull(closedWorld.abstractValueDomain) | 
|  | .isDefinitelyTrue && | 
|  | pattern | 
|  | .isStringOrNull(closedWorld.abstractValueDomain) | 
|  | .isDefinitelyTrue) { | 
|  | // String.contains(String s) does not have any side effect (other than | 
|  | // throwing), and it can be GVN'ed. | 
|  | clearAllSideEffects(instruction); | 
|  | log?.registerPatternMatch(instruction); | 
|  | } | 
|  | return null; | 
|  | } | 
|  | } | 
|  |  | 
|  | class RoundSpecializer extends InvokeDynamicSpecializer { | 
|  | const RoundSpecializer(); | 
|  |  | 
|  | @override | 
|  | constant_system.UnaryOperation operation() => constant_system.round; | 
|  |  | 
|  | @override | 
|  | HInstruction? tryConvertToBuiltin( | 
|  | HInvokeDynamic instruction, | 
|  | HGraph graph, | 
|  | GlobalTypeInferenceResults results, | 
|  | JCommonElements commonElements, | 
|  | JClosedWorld closedWorld, | 
|  | OptimizationTestLog? log, | 
|  | ) { | 
|  | HInstruction receiver = instruction.getDartReceiver(); | 
|  | if (receiver | 
|  | .isNumberOrNull(closedWorld.abstractValueDomain) | 
|  | .isDefinitelyTrue) { | 
|  | // Even if there is no builtin equivalent instruction, we know the | 
|  | // instruction does not have any side effect, and that it can be GVN'ed. | 
|  | clearAllSideEffects(instruction); | 
|  | log?.registerRound(instruction); | 
|  | } | 
|  | return null; | 
|  | } | 
|  | } | 
|  |  | 
|  | class ToIntSpecializer extends InvokeDynamicSpecializer { | 
|  | const ToIntSpecializer(); | 
|  |  | 
|  | @override | 
|  | constant_system.UnaryOperation operation() => constant_system.toInt; | 
|  |  | 
|  | @override | 
|  | HInstruction? tryConvertToBuiltin( | 
|  | HInvokeDynamic instruction, | 
|  | HGraph graph, | 
|  | GlobalTypeInferenceResults results, | 
|  | JCommonElements commonElements, | 
|  | JClosedWorld closedWorld, | 
|  | OptimizationTestLog? log, | 
|  | ) { | 
|  | HInstruction receiver = instruction.getDartReceiver(); | 
|  |  | 
|  | // We would like to reduce `x.toInt()` to `x`. The web platform considers | 
|  | // infinities to be `int` values, but it is too hard to tell if an input is | 
|  | // a finite integral value. Further `(-0.0).toInt()` returns `0`, so | 
|  | // `toInt()` is not strictly an identity on finite integral values. | 
|  |  | 
|  | if (receiver | 
|  | .isNumberOrNull(closedWorld.abstractValueDomain) | 
|  | .isDefinitelyTrue) { | 
|  | // Even if there is no builtin equivalent instruction, we know the | 
|  | // instruction does not have any side effect, and that it can be GVN'ed. | 
|  | clearAllSideEffects(instruction); | 
|  | log?.registerToInt(instruction); | 
|  | } | 
|  | return null; | 
|  | } | 
|  | } |