| // 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. | |
| import 'dart:math' as math; | |
| import 'dart:collection' show Queue; | |
| import 'package:front_end/src/fasta/util/link.dart' show Link; | |
| import '../common.dart'; | |
| import '../common/names.dart'; | |
| import '../common/codegen.dart' show CodegenRegistry, CodegenWorkItem; | |
| import '../common/tasks.dart' show CompilerTask; | |
| import '../constants/constant_system.dart'; | |
| import '../constants/values.dart'; | |
| import '../common_elements.dart' show JCommonElements; | |
| import '../elements/entities.dart'; | |
| import '../elements/jumps.dart'; | |
| import '../elements/types.dart'; | |
| import '../io/source_information.dart'; | |
| import '../js/js.dart' as js; | |
| import '../js_backend/interceptor_data.dart'; | |
| import '../js_backend/backend.dart'; | |
| import '../js_backend/checked_mode_helpers.dart'; | |
| import '../js_backend/native_data.dart'; | |
| import '../js_backend/namer.dart'; | |
| import '../js_backend/runtime_types.dart'; | |
| import '../js_emitter/code_emitter_task.dart'; | |
| import '../js_model/elements.dart' show JGeneratorBody; | |
| import '../native/native.dart' as native; | |
| import '../options.dart'; | |
| import '../types/abstract_value_domain.dart'; | |
| import '../universe/call_structure.dart' show CallStructure; | |
| import '../universe/selector.dart' show Selector; | |
| import '../universe/use.dart' | |
| show ConstantUse, ConstrainedDynamicUse, StaticUse, TypeUse; | |
| import '../world.dart' show JClosedWorld; | |
| import 'codegen_helpers.dart'; | |
| import 'nodes.dart'; | |
| import 'variable_allocator.dart'; | |
| class SsaCodeGeneratorTask extends CompilerTask { | |
| final JavaScriptBackend backend; | |
| final SourceInformationStrategy sourceInformationFactory; | |
| SsaCodeGeneratorTask(JavaScriptBackend backend, this.sourceInformationFactory) | |
| : this.backend = backend, | |
| super(backend.compiler.measurer); | |
| String get name => 'SSA code generator'; | |
| js.Fun buildJavaScriptFunction(bool needsAsyncRewrite, FunctionEntity element, | |
| List<js.Parameter> parameters, js.Block body) { | |
| js.Fun finish(js.AsyncModifier asyncModifier) { | |
| return new js.Fun(parameters, body, asyncModifier: asyncModifier) | |
| .withSourceInformation(sourceInformationFactory | |
| .createBuilderForContext(element) | |
| .buildDeclaration(element)); | |
| } | |
| if (needsAsyncRewrite) { | |
| return finish(element.asyncMarker.isAsync | |
| ? (element.asyncMarker.isYielding | |
| ? const js.AsyncModifier.asyncStar() | |
| : const js.AsyncModifier.async()) | |
| : (element.asyncMarker.isYielding | |
| ? const js.AsyncModifier.syncStar() | |
| : const js.AsyncModifier.sync())); | |
| } else { | |
| return finish(const js.AsyncModifier.sync()); | |
| } | |
| } | |
| js.Expression generateCode( | |
| CodegenWorkItem work, HGraph graph, JClosedWorld closedWorld) { | |
| if (work.element.isField) { | |
| return generateLazyInitializer(work, graph, closedWorld); | |
| } else { | |
| return generateMethod(work, graph, closedWorld); | |
| } | |
| } | |
| js.Expression generateLazyInitializer( | |
| CodegenWorkItem work, HGraph graph, JClosedWorld closedWorld) { | |
| return measure(() { | |
| backend.tracer.traceGraph("codegen", graph); | |
| SourceInformation sourceInformation = sourceInformationFactory | |
| .createBuilderForContext(work.element) | |
| .buildDeclaration(work.element); | |
| SsaCodeGenerator codegen = new SsaCodeGenerator( | |
| backend.compiler.options, | |
| backend.emitter, | |
| backend.nativeCodegenEnqueuer, | |
| backend.checkedModeHelpers, | |
| backend.oneShotInterceptorData, | |
| backend.rtiSubstitutions, | |
| backend.rtiEncoder, | |
| backend.namer, | |
| backend.superMemberData, | |
| closedWorld, | |
| work); | |
| codegen.visitGraph(graph); | |
| return new js.Fun(codegen.parameters, codegen.body) | |
| .withSourceInformation(sourceInformation); | |
| }); | |
| } | |
| js.Expression generateMethod( | |
| CodegenWorkItem work, HGraph graph, JClosedWorld closedWorld) { | |
| return measure(() { | |
| FunctionEntity element = work.element; | |
| if (element.asyncMarker != AsyncMarker.SYNC) { | |
| work.registry.registerAsyncMarker(element.asyncMarker); | |
| } | |
| SsaCodeGenerator codegen = new SsaCodeGenerator( | |
| backend.compiler.options, | |
| backend.emitter, | |
| backend.nativeCodegenEnqueuer, | |
| backend.checkedModeHelpers, | |
| backend.oneShotInterceptorData, | |
| backend.rtiSubstitutions, | |
| backend.rtiEncoder, | |
| backend.namer, | |
| backend.superMemberData, | |
| closedWorld, | |
| work); | |
| codegen.visitGraph(graph); | |
| backend.tracer.traceGraph("codegen", graph); | |
| return buildJavaScriptFunction(graph.needsAsyncRewrite, work.element, | |
| codegen.parameters, codegen.body); | |
| }); | |
| } | |
| } | |
| class SsaCodeGenerator implements HVisitor, HBlockInformationVisitor { | |
| /** | |
| * Returned by [expressionType] to tell how code can be generated for | |
| * a subgraph. | |
| * - [TYPE_STATEMENT] means that the graph must be generated as a statement, | |
| * which is always possible. | |
| * - [TYPE_EXPRESSION] means that the graph can be generated as an expression, | |
| * or possibly several comma-separated expressions. | |
| * - [TYPE_DECLARATION] means that the graph can be generated as an | |
| * expression, and that it only generates expressions of the form | |
| * variable = expression | |
| * which are also valid as parts of a "var" declaration. | |
| */ | |
| static const int TYPE_STATEMENT = 0; | |
| static const int TYPE_EXPRESSION = 1; | |
| static const int TYPE_DECLARATION = 2; | |
| /** | |
| * Whether we are currently generating expressions instead of statements. | |
| * This includes declarations, which are generated as expressions. | |
| */ | |
| bool isGeneratingExpression = false; | |
| final CompilerOptions _options; | |
| final CodeEmitterTask _emitter; | |
| final native.NativeCodegenEnqueuer _nativeEnqueuer; | |
| final CheckedModeHelpers _checkedModeHelpers; | |
| final OneShotInterceptorData _oneShotInterceptorData; | |
| final RuntimeTypesSubstitutions _rtiSubstitutions; | |
| final RuntimeTypesEncoder _rtiEncoder; | |
| final Namer _namer; | |
| final SuperMemberData _superMemberData; | |
| final JClosedWorld _closedWorld; | |
| final CodegenWorkItem _work; | |
| final Set<HInstruction> generateAtUseSite; | |
| final Set<HInstruction> controlFlowOperators; | |
| final Set<JumpTarget> breakAction; | |
| final Set<LabelDefinition> continueAction; | |
| final Set<JumpTarget> implicitContinueAction; | |
| final List<js.Parameter> parameters; | |
| js.Block currentContainer; | |
| js.Block get body => currentContainer; | |
| List<js.Expression> expressionStack; | |
| List<js.Block> oldContainerStack; | |
| /** | |
| * Contains the names of the instructions, as well as the parallel | |
| * copies to perform on block transitioning. | |
| */ | |
| VariableNames variableNames; | |
| bool shouldGroupVarDeclarations = false; | |
| /** | |
| * While generating expressions, we can't insert variable declarations. | |
| * Instead we declare them at the start of the function. When minifying | |
| * we do this most of the time, because it reduces the size unless there | |
| * is only one variable. | |
| */ | |
| final Set<String> collectedVariableDeclarations; | |
| /** | |
| * Set of variables and parameters that have already been declared. | |
| */ | |
| final Set<String> declaredLocals; | |
| HGraph currentGraph; | |
| // Records a block-information that is being handled specially. | |
| // Used to break bad recursion. | |
| HBlockInformation currentBlockInformation; | |
| // The subgraph is used to delimit traversal for some constructions, e.g., | |
| // if branches. | |
| SubGraph subGraph; | |
| // Pending blocks than need to be visited as part of current subgraph. | |
| Queue<HBasicBlock> blockQueue; | |
| SsaCodeGenerator( | |
| this._options, | |
| this._emitter, | |
| this._nativeEnqueuer, | |
| this._checkedModeHelpers, | |
| this._oneShotInterceptorData, | |
| this._rtiSubstitutions, | |
| this._rtiEncoder, | |
| this._namer, | |
| this._superMemberData, | |
| this._closedWorld, | |
| this._work, | |
| {SourceInformation sourceInformation}) | |
| : declaredLocals = new Set<String>(), | |
| collectedVariableDeclarations = new Set<String>(), | |
| currentContainer = new js.Block.empty(), | |
| parameters = <js.Parameter>[], | |
| expressionStack = <js.Expression>[], | |
| oldContainerStack = <js.Block>[], | |
| generateAtUseSite = new Set<HInstruction>(), | |
| controlFlowOperators = new Set<HInstruction>(), | |
| breakAction = new Set<JumpTarget>(), | |
| continueAction = new Set<LabelDefinition>(), | |
| implicitContinueAction = new Set<JumpTarget>(); | |
| CodegenRegistry get _registry => _work.registry; | |
| JCommonElements get _commonElements => _closedWorld.commonElements; | |
| ConstantSystem get _constantSystem => _closedWorld.constantSystem; | |
| NativeData get _nativeData => _closedWorld.nativeData; | |
| InterceptorData get _interceptorData => _closedWorld.interceptorData; | |
| AbstractValueDomain get _abstractValueDomain => | |
| _closedWorld.abstractValueDomain; | |
| bool isGenerateAtUseSite(HInstruction instruction) { | |
| return generateAtUseSite.contains(instruction); | |
| } | |
| bool hasNonBitOpUser(HInstruction instruction, Set<HPhi> phiSet) { | |
| for (HInstruction user in instruction.usedBy) { | |
| if (user is HPhi) { | |
| if (!phiSet.contains(user)) { | |
| phiSet.add(user); | |
| if (hasNonBitOpUser(user, phiSet)) return true; | |
| } | |
| } else if (user is! HBitNot && user is! HBinaryBitOp) { | |
| return true; | |
| } | |
| } | |
| return false; | |
| } | |
| // Returns the number of bits occupied by the value computed by [instruction]. | |
| // Returns `32` if the value is negative or does not fit in a smaller number | |
| // of bits. | |
| int bitWidth(HInstruction instruction) { | |
| const int MAX = 32; | |
| int constant(HInstruction instruction) { | |
| if (instruction is HConstant && instruction.isConstantInteger()) { | |
| IntConstantValue constant = instruction.constant; | |
| return constant.intValue.toInt(); | |
| } | |
| return null; | |
| } | |
| if (instruction.isConstantInteger()) { | |
| int value = constant(instruction); | |
| if (value < 0) return MAX; | |
| if (value > ((1 << 31) - 1)) return MAX; | |
| return value.bitLength; | |
| } | |
| if (instruction is HBitAnd) { | |
| return math.min(bitWidth(instruction.left), bitWidth(instruction.right)); | |
| } | |
| if (instruction is HBitOr || instruction is HBitXor) { | |
| HBinaryBitOp bitOp = instruction; | |
| int leftWidth = bitWidth(bitOp.left); | |
| if (leftWidth == MAX) return MAX; | |
| return math.max(leftWidth, bitWidth(bitOp.right)); | |
| } | |
| if (instruction is HShiftLeft) { | |
| int shiftCount = constant(instruction.right); | |
| if (shiftCount == null || shiftCount < 0 || shiftCount > 31) return MAX; | |
| int leftWidth = bitWidth(instruction.left); | |
| int width = leftWidth + shiftCount; | |
| return math.min(width, MAX); | |
| } | |
| if (instruction is HShiftRight) { | |
| int shiftCount = constant(instruction.right); | |
| if (shiftCount == null || shiftCount < 0 || shiftCount > 31) return MAX; | |
| int leftWidth = bitWidth(instruction.left); | |
| if (leftWidth >= MAX) return MAX; | |
| return math.max(leftWidth - shiftCount, 0); | |
| } | |
| if (instruction is HAdd) { | |
| return math.min( | |
| 1 + math.max(bitWidth(instruction.left), bitWidth(instruction.right)), | |
| MAX); | |
| } | |
| return MAX; | |
| } | |
| bool requiresUintConversion(HInstruction instruction) { | |
| if (instruction.isUInt31(_abstractValueDomain)) return false; | |
| if (bitWidth(instruction) <= 31) return false; | |
| // If the result of a bit-operation is only used by other bit | |
| // operations, we do not have to convert to an unsigned integer. | |
| return hasNonBitOpUser(instruction, new Set<HPhi>()); | |
| } | |
| /** | |
| * If the [instruction] is not `null` it will be used to attach the position | |
| * to the [statement]. | |
| */ | |
| void pushStatement(js.Statement statement) { | |
| assert(expressionStack.isEmpty); | |
| currentContainer.statements.add(statement); | |
| } | |
| void insertStatementAtStart(js.Statement statement) { | |
| currentContainer.statements.insert(0, statement); | |
| } | |
| /** | |
| * If the [instruction] is not `null` it will be used to attach the position | |
| * to the [expression]. | |
| */ | |
| pushExpressionAsStatement( | |
| js.Expression expression, SourceInformation sourceInformation) { | |
| pushStatement(new js.ExpressionStatement(expression) | |
| .withSourceInformation(sourceInformation)); | |
| } | |
| /** | |
| * If the [instruction] is not `null` it will be used to attach the position | |
| * to the [expression]. | |
| */ | |
| push(js.Expression expression) { | |
| expressionStack.add(expression); | |
| } | |
| js.Expression pop() { | |
| return expressionStack.removeLast(); | |
| } | |
| void preGenerateMethod(HGraph graph) { | |
| new SsaInstructionSelection(_closedWorld, _interceptorData) | |
| .visitGraph(graph); | |
| new SsaTypeKnownRemover().visitGraph(graph); | |
| new SsaTrustedCheckRemover(_options).visitGraph(graph); | |
| new SsaInstructionMerger( | |
| _abstractValueDomain, generateAtUseSite, _superMemberData) | |
| .visitGraph(graph); | |
| new SsaConditionMerger(generateAtUseSite, controlFlowOperators) | |
| .visitGraph(graph); | |
| SsaLiveIntervalBuilder intervalBuilder = | |
| new SsaLiveIntervalBuilder(generateAtUseSite, controlFlowOperators); | |
| intervalBuilder.visitGraph(graph); | |
| SsaVariableAllocator allocator = new SsaVariableAllocator( | |
| _namer, | |
| intervalBuilder.liveInstructions, | |
| intervalBuilder.liveIntervals, | |
| generateAtUseSite); | |
| allocator.visitGraph(graph); | |
| variableNames = allocator.names; | |
| shouldGroupVarDeclarations = allocator.names.numberOfVariables > 1; | |
| } | |
| void handleDelayedVariableDeclarations(SourceInformation sourceInformation) { | |
| // If we have only one variable declaration and the first statement is an | |
| // assignment to that variable then we can merge the two. We count the | |
| // number of variables in the variable allocator to try to avoid this issue, | |
| // but it sometimes happens that the variable allocator introduces a | |
| // temporary variable that it later eliminates. | |
| if (!collectedVariableDeclarations.isEmpty) { | |
| if (collectedVariableDeclarations.length == 1 && | |
| currentContainer.statements.length >= 1 && | |
| currentContainer.statements[0] is js.ExpressionStatement) { | |
| String name = collectedVariableDeclarations.first; | |
| js.ExpressionStatement statement = currentContainer.statements[0]; | |
| if (statement.expression is js.Assignment) { | |
| js.Assignment assignment = statement.expression; | |
| if (!assignment.isCompound && | |
| assignment.leftHandSide is js.VariableReference) { | |
| js.VariableReference variableReference = assignment.leftHandSide; | |
| if (variableReference.name == name) { | |
| js.VariableDeclaration decl = new js.VariableDeclaration(name); | |
| js.VariableInitialization initialization = | |
| new js.VariableInitialization(decl, assignment.value); | |
| currentContainer.statements[0] = new js.ExpressionStatement( | |
| new js.VariableDeclarationList([initialization])) | |
| .withSourceInformation(sourceInformation); | |
| return; | |
| } | |
| } | |
| } | |
| } | |
| // If we can't merge the declaration with the first assignment then we | |
| // just do it with a new var z,y,x; statement. | |
| List<js.VariableInitialization> declarations = | |
| <js.VariableInitialization>[]; | |
| collectedVariableDeclarations.forEach((String name) { | |
| declarations.add(new js.VariableInitialization( | |
| new js.VariableDeclaration(name), null)); | |
| }); | |
| var declarationList = new js.VariableDeclarationList(declarations) | |
| .withSourceInformation(sourceInformation); | |
| ; | |
| insertStatementAtStart(new js.ExpressionStatement(declarationList)); | |
| } | |
| } | |
| visitGraph(HGraph graph) { | |
| preGenerateMethod(graph); | |
| currentGraph = graph; | |
| visitSubGraph(new SubGraph(graph.entry, graph.exit)); | |
| handleDelayedVariableDeclarations(graph.sourceInformation); | |
| } | |
| void visitSubGraph(SubGraph newSubGraph) { | |
| SubGraph oldSubGraph = subGraph; | |
| Queue<HBasicBlock> oldBlockQueue = blockQueue; | |
| subGraph = newSubGraph; | |
| blockQueue = new Queue<HBasicBlock>(); | |
| enterSubGraph(subGraph.start); | |
| blockQueue = oldBlockQueue; | |
| subGraph = oldSubGraph; | |
| } | |
| /** | |
| * Check whether a sub-graph can be generated as an expression, or even | |
| * as a declaration, or if it has to fall back to being generated as | |
| * a statement. | |
| * Expressions are anything that doesn't generate control flow constructs. | |
| * Declarations must only generate assignments on the form "id = expression", | |
| * and not, e.g., expressions where the value isn't assigned, or where it's | |
| * assigned to something that's not a simple variable. | |
| */ | |
| int expressionType(HExpressionInformation info) { | |
| // The only HExpressionInformation used as part of a HBlockInformation is | |
| // current HSubExpressionBlockInformation, so it's the only one reaching | |
| // here. If we start using the other HExpressionInformation types too, | |
| // this code should be generalized. | |
| assert(info is HSubExpressionBlockInformation); | |
| HSubExpressionBlockInformation expressionInfo = info; | |
| SubGraph limits = expressionInfo.subExpression; | |
| // Start assuming that we can generate declarations. If we find a | |
| // counter-example, we degrade our assumption to either expression or | |
| // statement, and in the latter case, we can return immediately since | |
| // it can't get any worse. E.g., a function call where the return value | |
| // isn't used can't be in a declaration. | |
| int result = TYPE_DECLARATION; | |
| HBasicBlock basicBlock = limits.start; | |
| do { | |
| HInstruction current = basicBlock.first; | |
| while (current != basicBlock.last) { | |
| // E.g, bounds check. | |
| if (current.isControlFlow()) { | |
| return TYPE_STATEMENT; | |
| } | |
| // HFieldSet generates code on the form x.y = ..., which isn't | |
| // valid in a declaration, but it also always have no uses, so | |
| // it's caught by that test too. | |
| assert(current is! HFieldSet || current.usedBy.isEmpty); | |
| if (current.usedBy.isEmpty) { | |
| result = TYPE_EXPRESSION; | |
| } | |
| current = current.next; | |
| } | |
| if (current is HGoto) { | |
| basicBlock = basicBlock.successors[0]; | |
| } else if (current is HConditionalBranch) { | |
| if (generateAtUseSite.contains(current)) { | |
| // Short-circuit control flow operator trickery. | |
| // Check the second half, which will continue into the join. | |
| // (The first half is [inputs[0]], the second half is [successors[0]], | |
| // and [successors[1]] is the join-block). | |
| basicBlock = basicBlock.successors[0]; | |
| } else { | |
| // We allow an expression to end on an HIf (a condition expression). | |
| return identical(basicBlock, limits.end) ? result : TYPE_STATEMENT; | |
| } | |
| } else { | |
| // Expression-incompatible control flow. | |
| return TYPE_STATEMENT; | |
| } | |
| } while (limits.contains(basicBlock)); | |
| return result; | |
| } | |
| bool isJSExpression(HExpressionInformation info) { | |
| return !identical(expressionType(info), TYPE_STATEMENT); | |
| } | |
| bool isJSCondition(HExpressionInformation info) { | |
| HSubExpressionBlockInformation graph = info; | |
| SubExpression limits = graph.subExpression; | |
| return !identical(expressionType(info), TYPE_STATEMENT) && | |
| (limits.end.last is HConditionalBranch); | |
| } | |
| /** | |
| * Generate statements from block information. | |
| * If the block information contains expressions, generate only | |
| * assignments, and if it ends in a conditional branch, don't generate | |
| * the condition. | |
| */ | |
| void generateStatements(HBlockInformation block) { | |
| if (block is HStatementInformation) { | |
| block.accept(this); | |
| } else { | |
| HSubExpressionBlockInformation expression = block; | |
| visitSubGraph(expression.subExpression); | |
| } | |
| } | |
| js.Block generateStatementsInNewBlock(HBlockInformation block) { | |
| js.Block result = new js.Block.empty(); | |
| js.Block oldContainer = currentContainer; | |
| currentContainer = result; | |
| generateStatements(block); | |
| currentContainer = oldContainer; | |
| return result; | |
| } | |
| /** | |
| * If the [block] only contains one statement returns that statement. If the | |
| * that statement itself is a block, recursively calls this method. | |
| * | |
| * If the block is empty, returns a new instance of [js.NOP]. | |
| */ | |
| js.Statement unwrapStatement(js.Block block) { | |
| int len = block.statements.length; | |
| if (len == 0) return new js.EmptyStatement(); | |
| if (len == 1) { | |
| js.Statement result = block.statements[0]; | |
| if (result is js.Block) return unwrapStatement(result); | |
| return result; | |
| } | |
| return block; | |
| } | |
| /** | |
| * Generate expressions from block information. | |
| */ | |
| js.Expression generateExpression(HExpressionInformation expression) { | |
| // Currently we only handle sub-expression graphs. | |
| assert(expression is HSubExpressionBlockInformation); | |
| bool oldIsGeneratingExpression = isGeneratingExpression; | |
| isGeneratingExpression = true; | |
| List<js.Expression> oldExpressionStack = expressionStack; | |
| List<js.Expression> sequenceElements = <js.Expression>[]; | |
| expressionStack = sequenceElements; | |
| HSubExpressionBlockInformation expressionSubGraph = expression; | |
| visitSubGraph(expressionSubGraph.subExpression); | |
| expressionStack = oldExpressionStack; | |
| isGeneratingExpression = oldIsGeneratingExpression; | |
| if (sequenceElements.isEmpty) { | |
| // Happens when the initializer, condition or update of a loop is empty. | |
| return null; | |
| } else if (sequenceElements.length == 1) { | |
| return sequenceElements[0]; | |
| } else { | |
| js.Expression result = sequenceElements.removeLast(); | |
| while (sequenceElements.isNotEmpty) { | |
| result = new js.Binary(',', sequenceElements.removeLast(), result); | |
| } | |
| return result; | |
| } | |
| } | |
| /** | |
| * Only visits the arguments starting at inputs[HInvoke.ARGUMENTS_OFFSET]. | |
| */ | |
| List<js.Expression> visitArguments(List<HInstruction> inputs, | |
| {int start: HInvoke.ARGUMENTS_OFFSET}) { | |
| assert(inputs.length >= start); | |
| List<js.Expression> result = new List<js.Expression>(inputs.length - start); | |
| for (int i = start; i < inputs.length; i++) { | |
| use(inputs[i]); | |
| result[i - start] = pop(); | |
| } | |
| return result; | |
| } | |
| bool isVariableDeclared(String variableName) { | |
| return declaredLocals.contains(variableName) || | |
| collectedVariableDeclarations.contains(variableName); | |
| } | |
| js.Expression generateExpressionAssignment(String variableName, | |
| js.Expression value, SourceInformation sourceInformation) { | |
| if (value is js.Binary) { | |
| js.Binary binary = value; | |
| String op = binary.op; | |
| if (op == '+' || | |
| op == '-' || | |
| op == '/' || | |
| op == '*' || | |
| op == '%' || | |
| op == '^' || | |
| op == '&' || | |
| op == '|') { | |
| if (binary.left is js.VariableUse && | |
| (binary.left as js.VariableUse).name == variableName) { | |
| // We know now, that we can shorten x = x + y into x += y. | |
| // Also check for the shortcut where y equals 1: x++ and x--. | |
| if ((op == '+' || op == '-') && | |
| binary.right is js.LiteralNumber && | |
| (binary.right as js.LiteralNumber).value == "1") { | |
| return new js.Prefix(op == '+' ? '++' : '--', binary.left); | |
| } | |
| return new js.Assignment.compound(binary.left, op, binary.right); | |
| } | |
| } | |
| } | |
| return new js.Assignment(new js.VariableUse(variableName), value) | |
| .withSourceInformation(value.sourceInformation ?? sourceInformation); | |
| } | |
| void assignVariable(String variableName, js.Expression value, | |
| SourceInformation sourceInformation) { | |
| if (isGeneratingExpression) { | |
| // If we are in an expression then we can't declare the variable here. | |
| // We have no choice, but to use it and then declare it separately. | |
| if (!isVariableDeclared(variableName)) { | |
| collectedVariableDeclarations.add(variableName); | |
| } | |
| push( | |
| generateExpressionAssignment(variableName, value, sourceInformation)); | |
| // Otherwise if we are trying to declare inline and we are in a statement | |
| // then we declare (unless it was already declared). | |
| } else if (!shouldGroupVarDeclarations && | |
| !declaredLocals.contains(variableName)) { | |
| // It may be necessary to remove it from the ones to be declared later. | |
| collectedVariableDeclarations.remove(variableName); | |
| declaredLocals.add(variableName); | |
| js.VariableDeclaration decl = new js.VariableDeclaration(variableName); | |
| js.VariableInitialization initialization = | |
| new js.VariableInitialization(decl, value); | |
| pushExpressionAsStatement( | |
| new js.VariableDeclarationList( | |
| <js.VariableInitialization>[initialization]), | |
| sourceInformation); | |
| } else { | |
| // Otherwise we are just going to use it. If we have not already declared | |
| // it then we make sure we will declare it later. | |
| if (!declaredLocals.contains(variableName)) { | |
| collectedVariableDeclarations.add(variableName); | |
| } | |
| pushExpressionAsStatement( | |
| generateExpressionAssignment(variableName, value, sourceInformation), | |
| sourceInformation); | |
| } | |
| } | |
| void define(HInstruction instruction) { | |
| // For simple type checks like i = intTypeCheck(i), we don't have to | |
| // emit an assignment, because the intTypeCheck just returns its | |
| // argument. | |
| bool needsAssignment = true; | |
| if (instruction is HTypeConversion) { | |
| HTypeConversion typeConversion = instruction; | |
| String inputName = variableNames.getName(typeConversion.checkedInput); | |
| if (variableNames.getName(instruction) == inputName) { | |
| needsAssignment = false; | |
| } | |
| } | |
| if (instruction is HLocalValue) { | |
| needsAssignment = false; | |
| } | |
| if (needsAssignment && | |
| !instruction.isControlFlow() && | |
| variableNames.hasName(instruction)) { | |
| visitExpression(instruction); | |
| assignVariable(variableNames.getName(instruction), pop(), | |
| instruction.sourceInformation); | |
| return; | |
| } | |
| if (isGeneratingExpression) { | |
| visitExpression(instruction); | |
| } else { | |
| visitStatement(instruction); | |
| } | |
| } | |
| void use(HInstruction argument) { | |
| if (isGenerateAtUseSite(argument)) { | |
| visitExpression(argument); | |
| } else if (argument is HCheck && !variableNames.hasName(argument)) { | |
| HCheck check = argument; | |
| // This can only happen if the checked node does not have a name. | |
| assert(!variableNames.hasName(check.checkedInput)); | |
| use(check.checkedInput); | |
| } else { | |
| assert(variableNames.hasName(argument)); | |
| push(new js.VariableUse(variableNames.getName(argument))); | |
| } | |
| } | |
| visit(HInstruction node) { | |
| node.accept(this); | |
| } | |
| visitExpression(HInstruction node) { | |
| bool oldIsGeneratingExpression = isGeneratingExpression; | |
| isGeneratingExpression = true; | |
| visit(node); | |
| isGeneratingExpression = oldIsGeneratingExpression; | |
| } | |
| visitStatement(HInstruction node) { | |
| assert(!isGeneratingExpression); | |
| visit(node); | |
| if (!expressionStack.isEmpty) { | |
| assert(expressionStack.length == 1); | |
| js.Expression expression = pop(); | |
| pushExpressionAsStatement(expression, node.sourceInformation); | |
| } | |
| } | |
| void continueAsBreak(LabelDefinition target) { | |
| pushStatement(new js.Break(_namer.continueLabelName(target))); | |
| } | |
| void implicitContinueAsBreak(JumpTarget target) { | |
| pushStatement(new js.Break(_namer.implicitContinueLabelName(target))); | |
| } | |
| void implicitBreakWithLabel(JumpTarget target) { | |
| pushStatement(new js.Break(_namer.implicitBreakLabelName(target))); | |
| } | |
| js.Statement wrapIntoLabels( | |
| js.Statement result, List<LabelDefinition> labels) { | |
| for (LabelDefinition label in labels) { | |
| if (label.isTarget) { | |
| String breakLabelString = _namer.breakLabelName(label); | |
| result = new js.LabeledStatement(breakLabelString, result); | |
| } | |
| } | |
| return result; | |
| } | |
| // The regular [visitIf] method implements the needed logic. | |
| bool visitIfInfo(HIfBlockInformation info) => false; | |
| bool visitSwitchInfo(HSwitchBlockInformation info) { | |
| bool isExpression = isJSExpression(info.expression); | |
| if (!isExpression) { | |
| generateStatements(info.expression); | |
| } | |
| if (isExpression) { | |
| push(generateExpression(info.expression)); | |
| } else { | |
| use(info.expression.conditionExpression); | |
| } | |
| js.Expression key = pop(); | |
| bool handledDefault = false; | |
| List<js.SwitchClause> cases = <js.SwitchClause>[]; | |
| HSwitch switchInstruction = info.expression.end.last; | |
| List<HInstruction> inputs = switchInstruction.inputs; | |
| List<HBasicBlock> successors = switchInstruction.block.successors; | |
| js.Block oldContainer = currentContainer; | |
| for (int inputIndex = 1, statementIndex = 0; | |
| inputIndex < inputs.length; | |
| statementIndex++) { | |
| HBasicBlock successor = successors[inputIndex - 1]; | |
| // If liveness analysis has figured out that this case is dead, | |
| // omit the code for it. | |
| if (successor.isLive) { | |
| do { | |
| visit(inputs[inputIndex]); | |
| currentContainer = new js.Block.empty(); | |
| cases.add(new js.Case(pop(), currentContainer)); | |
| inputIndex++; | |
| } while ((successors[inputIndex - 1] == successor) && | |
| (inputIndex < inputs.length)); | |
| // If this is the last statement, then these cases also belong to the | |
| // default block. | |
| if (statementIndex == info.statements.length - 1) { | |
| currentContainer = new js.Block.empty(); | |
| cases.add(new js.Default(currentContainer)); | |
| handledDefault = true; | |
| } | |
| generateStatements(info.statements[statementIndex]); | |
| } else { | |
| // Skip all the case statements that belong to this | |
| // block. | |
| while ((successors[inputIndex - 1] == successor) && | |
| (inputIndex < inputs.length)) { | |
| ++inputIndex; | |
| } | |
| } | |
| } | |
| // If the default case is dead, we omit it. Likewise, if it is an | |
| // empty block, we omit it, too. | |
| if (info.statements.last.start.isLive && !handledDefault) { | |
| currentContainer = new js.Block.empty(); | |
| generateStatements(info.statements.last); | |
| if (currentContainer.statements.isNotEmpty) { | |
| cases.add(new js.Default(currentContainer)); | |
| } | |
| } | |
| currentContainer = oldContainer; | |
| js.Statement result = | |
| new js.Switch(key, cases).withSourceInformation(info.sourceInformation); | |
| pushStatement(wrapIntoLabels(result, info.labels)); | |
| return true; | |
| } | |
| bool visitSequenceInfo(HStatementSequenceInformation info) { | |
| return false; | |
| } | |
| bool visitSubGraphInfo(HSubGraphBlockInformation info) { | |
| visitSubGraph(info.subGraph); | |
| return true; | |
| } | |
| bool visitSubExpressionInfo(HSubExpressionBlockInformation info) { | |
| return false; | |
| } | |
| bool visitAndOrInfo(HAndOrBlockInformation info) { | |
| return false; | |
| } | |
| bool visitTryInfo(HTryBlockInformation info) { | |
| js.Block body = generateStatementsInNewBlock(info.body); | |
| js.Catch catchPart = null; | |
| js.Block finallyPart = null; | |
| if (info.catchBlock != null) { | |
| void register(ClassEntity classElement) { | |
| if (classElement != null) { | |
| _registry.registerInstantiatedClass(classElement); | |
| } | |
| } | |
| register(_commonElements.jsPlainJavaScriptObjectClass); | |
| register(_commonElements.jsUnknownJavaScriptObjectClass); | |
| HLocalValue exception = info.catchVariable; | |
| String name = variableNames.getName(exception); | |
| js.VariableDeclaration decl = new js.VariableDeclaration(name); | |
| js.Block catchBlock = generateStatementsInNewBlock(info.catchBlock); | |
| catchPart = new js.Catch(decl, catchBlock); | |
| } | |
| if (info.finallyBlock != null) { | |
| finallyPart = generateStatementsInNewBlock(info.finallyBlock); | |
| } | |
| pushStatement(new js.Try(body, catchPart, finallyPart)); | |
| return true; | |
| } | |
| void visitBodyIgnoreLabels(HLoopBlockInformation info) { | |
| if (info.body.start.isLabeledBlock()) { | |
| HBlockInformation oldInfo = currentBlockInformation; | |
| currentBlockInformation = info.body.start.blockFlow.body; | |
| generateStatements(info.body); | |
| currentBlockInformation = oldInfo; | |
| } else { | |
| generateStatements(info.body); | |
| } | |
| } | |
| bool visitLoopInfo(HLoopBlockInformation info) { | |
| HExpressionInformation condition = info.condition; | |
| bool isConditionExpression = isJSCondition(condition); | |
| js.Loop loop; | |
| switch (info.kind) { | |
| // Treat all three "test-first" loops the same way. | |
| case HLoopBlockInformation.FOR_LOOP: | |
| case HLoopBlockInformation.WHILE_LOOP: | |
| case HLoopBlockInformation.FOR_IN_LOOP: | |
| case HLoopBlockInformation.SWITCH_CONTINUE_LOOP: | |
| HBlockInformation initialization = info.initializer; | |
| int initializationType = TYPE_STATEMENT; | |
| if (initialization != null) { | |
| initializationType = expressionType(initialization); | |
| if (initializationType == TYPE_STATEMENT) { | |
| generateStatements(initialization); | |
| initialization = null; | |
| } | |
| } | |
| // We inserted a basic block to avoid critical edges. This block is | |
| // part of the LoopBlockInformation and must therefore be handled here. | |
| js.Block oldContainer = currentContainer; | |
| js.Block avoidContainer = new js.Block.empty(); | |
| currentContainer = avoidContainer; | |
| assignPhisOfSuccessors(condition.end.successors.last); | |
| bool hasPhiUpdates = !avoidContainer.statements.isEmpty; | |
| currentContainer = oldContainer; | |
| if (isConditionExpression && | |
| !hasPhiUpdates && | |
| info.updates != null && | |
| isJSExpression(info.updates)) { | |
| // If we have an updates graph, and it's expressible as an | |
| // expression, generate a for-loop. | |
| js.Expression jsInitialization = null; | |
| if (initialization != null) { | |
| int delayedVariablesCount = collectedVariableDeclarations.length; | |
| jsInitialization = generateExpression(initialization); | |
| if (!shouldGroupVarDeclarations && | |
| delayedVariablesCount < collectedVariableDeclarations.length) { | |
| // We just added a new delayed variable-declaration. See if we can | |
| // put in a 'var' in front of the initialization to make it go | |
| // away. We walk the 'tree' of comma-operators to find the | |
| // expressions and see if they are all assignments that can be | |
| // converted into declarations. | |
| List<js.Assignment> assignments; | |
| bool allSimpleAssignments(js.Expression expression) { | |
| if (expression is js.Assignment) { | |
| js.Assignment assignment = expression; | |
| if (assignment.leftHandSide is js.VariableUse && | |
| !assignment.isCompound) { | |
| if (assignments == null) assignments = <js.Assignment>[]; | |
| assignments.add(expression); | |
| return true; | |
| } | |
| } else if (expression.isCommaOperator) { | |
| js.Binary binary = expression; | |
| return allSimpleAssignments(binary.left) && | |
| allSimpleAssignments(binary.right); | |
| } | |
| return false; | |
| } | |
| if (allSimpleAssignments(jsInitialization)) { | |
| List<js.VariableInitialization> inits = | |
| <js.VariableInitialization>[]; | |
| for (js.Assignment assignment in assignments) { | |
| String id = (assignment.leftHandSide as js.VariableUse).name; | |
| js.Node declaration = new js.VariableDeclaration(id); | |
| inits.add(new js.VariableInitialization( | |
| declaration, assignment.value)); | |
| collectedVariableDeclarations.remove(id); | |
| declaredLocals.add(id); | |
| } | |
| jsInitialization = new js.VariableDeclarationList(inits); | |
| } | |
| } | |
| } | |
| js.Expression jsCondition = generateExpression(condition); | |
| js.Expression jsUpdates = generateExpression(info.updates); | |
| // The body might be labeled. Ignore this when recursing on the | |
| // subgraph. | |
| // TODO(lrn): Remove this extra labeling when handling all loops | |
| // using subgraphs. | |
| oldContainer = currentContainer; | |
| js.Statement body = new js.Block.empty(); | |
| currentContainer = body; | |
| visitBodyIgnoreLabels(info); | |
| currentContainer = oldContainer; | |
| body = unwrapStatement(body); | |
| loop = new js.For(jsInitialization, jsCondition, jsUpdates, body) | |
| .withSourceInformation(info.sourceInformation); | |
| } else { | |
| // We have either no update graph, or it's too complex to | |
| // put in an expression. | |
| if (initialization != null) { | |
| generateStatements(initialization); | |
| } | |
| js.Expression jsCondition; | |
| js.Block oldContainer = currentContainer; | |
| js.Statement body = new js.Block.empty(); | |
| if (isConditionExpression && !hasPhiUpdates) { | |
| jsCondition = generateExpression(condition); | |
| currentContainer = body; | |
| } else { | |
| jsCondition = newLiteralBool(true, info.sourceInformation); | |
| currentContainer = body; | |
| generateStatements(condition); | |
| use(condition.conditionExpression); | |
| js.Expression ifTest = new js.Prefix("!", pop()); | |
| js.Statement jsBreak = new js.Break(null); | |
| js.Statement exitLoop; | |
| if (avoidContainer.statements.isEmpty) { | |
| exitLoop = jsBreak; | |
| } else { | |
| avoidContainer.statements.add(jsBreak); | |
| exitLoop = avoidContainer; | |
| } | |
| pushStatement(new js.If.noElse(ifTest, exitLoop)); | |
| } | |
| if (info.updates != null) { | |
| wrapLoopBodyForContinue(info); | |
| generateStatements(info.updates); | |
| } else { | |
| visitBodyIgnoreLabels(info); | |
| } | |
| currentContainer = oldContainer; | |
| body = unwrapStatement(body); | |
| loop = new js.While(jsCondition, body) | |
| .withSourceInformation(info.sourceInformation); | |
| } | |
| break; | |
| case HLoopBlockInformation.DO_WHILE_LOOP: | |
| if (info.initializer != null) { | |
| generateStatements(info.initializer); | |
| } | |
| // We inserted a basic block to avoid critical edges. This block is | |
| // part of the LoopBlockInformation and must therefore be handled here. | |
| js.Block oldContainer = currentContainer; | |
| js.Block exitAvoidContainer = new js.Block.empty(); | |
| currentContainer = exitAvoidContainer; | |
| assignPhisOfSuccessors(condition.end.successors.last); | |
| bool hasExitPhiUpdates = !exitAvoidContainer.statements.isEmpty; | |
| currentContainer = oldContainer; | |
| oldContainer = currentContainer; | |
| js.Block body = new js.Block.empty(); | |
| // If there are phi copies in the block that jumps to the | |
| // loop entry, we must emit the condition like this: | |
| // do { | |
| // body; | |
| // if (condition) { | |
| // phi updates; | |
| // continue; | |
| // } else { | |
| // break; | |
| // } | |
| // } while (true); | |
| HBasicBlock avoidEdge = info.end.successors[0]; | |
| js.Block updateBody = new js.Block.empty(); | |
| currentContainer = updateBody; | |
| assignPhisOfSuccessors(avoidEdge); | |
| bool hasPhiUpdates = !updateBody.statements.isEmpty; | |
| currentContainer = body; | |
| visitBodyIgnoreLabels(info); | |
| if (info.updates != null) { | |
| generateStatements(info.updates); | |
| } | |
| if (isConditionExpression) { | |
| push(generateExpression(condition)); | |
| } else { | |
| generateStatements(condition); | |
| use(condition.conditionExpression); | |
| } | |
| js.Expression jsCondition = pop(); | |
| if (jsCondition == null) { | |
| // If the condition is dead code, we turn the do-while into | |
| // a simpler while because we will never reach the condition | |
| // at the end of the loop anyway. | |
| loop = new js.While(newLiteralBool(true, info.sourceInformation), | |
| unwrapStatement(body)) | |
| .withSourceInformation(info.sourceInformation); | |
| } else { | |
| if (hasPhiUpdates || hasExitPhiUpdates) { | |
| updateBody.statements.add(new js.Continue(null)); | |
| js.Statement jsBreak = new js.Break(null); | |
| js.Statement exitLoop; | |
| if (exitAvoidContainer.statements.isEmpty) { | |
| exitLoop = jsBreak; | |
| } else { | |
| exitAvoidContainer.statements.add(jsBreak); | |
| exitLoop = exitAvoidContainer; | |
| } | |
| body.statements.add(new js.If(jsCondition, updateBody, exitLoop)); | |
| jsCondition = newLiteralBool(true, info.sourceInformation); | |
| } | |
| loop = new js.Do(unwrapStatement(body), jsCondition) | |
| .withSourceInformation(info.sourceInformation); | |
| } | |
| currentContainer = oldContainer; | |
| break; | |
| default: | |
| failedAt(condition.conditionExpression, | |
| 'Unexpected loop kind: ${info.kind}.'); | |
| } | |
| js.Statement result = loop; | |
| if (info.kind == HLoopBlockInformation.SWITCH_CONTINUE_LOOP) { | |
| String continueLabelString = | |
| _namer.implicitContinueLabelName(info.target); | |
| result = new js.LabeledStatement(continueLabelString, result); | |
| } | |
| pushStatement(wrapIntoLabels(result, info.labels)); | |
| return true; | |
| } | |
| bool visitLabeledBlockInfo(HLabeledBlockInformation labeledBlockInfo) { | |
| Link<Entity> continueOverrides = const Link<Entity>(); | |
| js.Block oldContainer = currentContainer; | |
| js.Block body = new js.Block.empty(); | |
| js.Statement result = body; | |
| currentContainer = body; | |
| // If [labeledBlockInfo.isContinue], the block is an artificial | |
| // block around the body of a loop with an update block, so that | |
| // continues of the loop can be written as breaks of the body | |
| // block. | |
| if (labeledBlockInfo.isContinue) { | |
| for (LabelDefinition label in labeledBlockInfo.labels) { | |
| if (label.isContinueTarget) { | |
| String labelName = _namer.continueLabelName(label); | |
| result = new js.LabeledStatement(labelName, result); | |
| continueAction.add(label); | |
| continueOverrides = continueOverrides.prepend(label); | |
| } | |
| } | |
| // For handling unlabeled continues from the body of a loop. | |
| // TODO(lrn): Consider recording whether the target is in fact | |
| // a target of an unlabeled continue, and not generate this if it isn't. | |
| JumpTarget target = labeledBlockInfo.target; | |
| String labelName = _namer.implicitContinueLabelName(target); | |
| result = new js.LabeledStatement(labelName, result); | |
| implicitContinueAction.add(target); | |
| continueOverrides = continueOverrides.prepend(target); | |
| } else { | |
| for (LabelDefinition label in labeledBlockInfo.labels) { | |
| if (label.isBreakTarget) { | |
| String labelName = _namer.breakLabelName(label); | |
| result = new js.LabeledStatement(labelName, result); | |
| } | |
| } | |
| } | |
| JumpTarget target = labeledBlockInfo.target; | |
| if (target.isSwitch) { | |
| // This is an extra block around a switch that is generated | |
| // as a nested if/else chain. We add an extra break target | |
| // so that case code can break. | |
| String labelName = _namer.implicitBreakLabelName(target); | |
| result = new js.LabeledStatement(labelName, result); | |
| breakAction.add(target); | |
| } | |
| currentContainer = body; | |
| generateStatements(labeledBlockInfo.body); | |
| if (labeledBlockInfo.isContinue) { | |
| while (!continueOverrides.isEmpty) { | |
| continueAction.remove(continueOverrides.head); | |
| implicitContinueAction.remove(continueOverrides.head); | |
| continueOverrides = continueOverrides.tail; | |
| } | |
| } else { | |
| breakAction.remove(labeledBlockInfo.target); | |
| } | |
| currentContainer = oldContainer; | |
| pushStatement(result); | |
| return true; | |
| } | |
| // Wraps a loop body in a block to make continues have a target to break | |
| // to (if necessary). | |
| void wrapLoopBodyForContinue(HLoopBlockInformation info) { | |
| JumpTarget target = info.target; | |
| if (target != null && target.isContinueTarget) { | |
| js.Block oldContainer = currentContainer; | |
| js.Block body = new js.Block.empty(); | |
| currentContainer = body; | |
| js.Statement result = body; | |
| for (LabelDefinition label in info.labels) { | |
| if (label.isContinueTarget) { | |
| String labelName = _namer.continueLabelName(label); | |
| result = new js.LabeledStatement(labelName, result); | |
| continueAction.add(label); | |
| } | |
| } | |
| String labelName = _namer.implicitContinueLabelName(target); | |
| result = new js.LabeledStatement(labelName, result); | |
| implicitContinueAction.add(info.target); | |
| visitBodyIgnoreLabels(info); | |
| implicitContinueAction.remove(info.target); | |
| for (LabelDefinition label in info.labels) { | |
| if (label.isContinueTarget) { | |
| continueAction.remove(label); | |
| } | |
| } | |
| currentContainer = oldContainer; | |
| pushStatement(result); | |
| } else { | |
| // Loop body contains no continues, so we don't need a break target. | |
| generateStatements(info.body); | |
| } | |
| } | |
| bool handleBlockFlow(HBlockFlow block) { | |
| HBlockInformation info = block.body; | |
| // If we reach here again while handling the attached information, | |
| // e.g., because we call visitSubGraph on a subgraph starting on | |
| // the same block, don't handle it again. | |
| // When the structure graph is complete, we will be able to have | |
| // different structures starting on the same basic block (e.g., an | |
| // "if" and its condition). | |
| if (identical(info, currentBlockInformation)) return false; | |
| HBlockInformation oldBlockInformation = currentBlockInformation; | |
| currentBlockInformation = info; | |
| bool success = info.accept(this); | |
| currentBlockInformation = oldBlockInformation; | |
| if (success) { | |
| HBasicBlock continuation = block.continuation; | |
| if (continuation != null) { | |
| continueSubGraph(continuation); | |
| } | |
| } | |
| return success; | |
| } | |
| void enterSubGraph(HBasicBlock node) { | |
| assert(blockQueue.isEmpty); | |
| assert(node != null); | |
| continueSubGraph(node); | |
| while (blockQueue.isNotEmpty) { | |
| node = blockQueue.removeFirst(); | |
| assert(node.isLive); | |
| assert(subGraph.contains(node)); | |
| // If this node has block-structure based information attached, | |
| // try using that to traverse from here. | |
| if (node.blockFlow != null && handleBlockFlow(node.blockFlow)) { | |
| continue; | |
| } | |
| iterateBasicBlock(node); | |
| } | |
| } | |
| void continueSubGraph(HBasicBlock node) { | |
| if (!node.isLive) return; | |
| // Don't follow edges out of the current sub-graph. | |
| if (!subGraph.contains(node)) return; | |
| blockQueue.add(node); | |
| } | |
| void emitAssignment( | |
| String destination, String source, SourceInformation sourceInformation) { | |
| assignVariable(destination, new js.VariableUse(source), sourceInformation); | |
| } | |
| /** | |
| * Sequentialize a list of conceptually parallel copies. Parallel | |
| * copies may contain cycles, that this method breaks. | |
| */ | |
| void sequentializeCopies( | |
| Iterable<Copy<HInstruction>> instructionCopies, | |
| String tempName, | |
| void doAssignment( | |
| String target, String source, SourceInformation sourceInformation)) { | |
| Map<String, SourceInformation> sourceInformationMap = | |
| <String, SourceInformation>{}; | |
| // Map the instructions to strings. | |
| Iterable<Copy<String>> copies = | |
| instructionCopies.map((Copy<HInstruction> copy) { | |
| String sourceName = variableNames.getName(copy.source); | |
| sourceInformationMap[sourceName] = copy.source.sourceInformation; | |
| String destinationName = variableNames.getName(copy.destination); | |
| sourceInformationMap[sourceName] = copy.destination.sourceInformation; | |
| return new Copy<String>(sourceName, destinationName); | |
| }); | |
| // Map to keep track of the current location (ie the variable that | |
| // holds the initial value) of a variable. | |
| Map<String, String> currentLocation = new Map<String, String>(); | |
| // Map to keep track of the initial value of a variable. | |
| Map<String, String> initialValue = new Map<String, String>(); | |
| // List of variables to assign a value. | |
| List<String> worklist = <String>[]; | |
| // List of variables that we can assign a value to (ie are not | |
| // being used anymore). | |
| List<String> ready = <String>[]; | |
| // Prune [copies] by removing self-copies. | |
| List<Copy<String>> prunedCopies = <Copy<String>>[]; | |
| for (Copy<String> copy in copies) { | |
| if (copy.source != copy.destination) { | |
| prunedCopies.add(copy); | |
| } | |
| } | |
| copies = prunedCopies; | |
| // For each copy, set the current location of the source to | |
| // itself, and the initial value of the destination to the source. | |
| // Add the destination to the list of copies to make. | |
| for (Copy<String> copy in copies) { | |
| currentLocation[copy.source] = copy.source; | |
| initialValue[copy.destination] = copy.source; | |
| worklist.add(copy.destination); | |
| } | |
| // For each copy, if the destination does not have a current | |
| // location, then we can safely assign to it. | |
| for (Copy<String> copy in copies) { | |
| if (currentLocation[copy.destination] == null) { | |
| ready.add(copy.destination); | |
| } | |
| } | |
| while (!worklist.isEmpty) { | |
| while (!ready.isEmpty) { | |
| String destination = ready.removeLast(); | |
| String source = initialValue[destination]; | |
| // Since [source] might have been updated, use the current | |
| // location of [source] | |
| String copy = currentLocation[source]; | |
| doAssignment(destination, copy, | |
| sourceInformationMap[copy] ?? sourceInformationMap[destination]); | |
| // Now [destination] is the current location of [source]. | |
| currentLocation[source] = destination; | |
| // If [source] hasn't been updated and needs to have a value, | |
| // add it to the list of variables that can be updated. Copies | |
| // of [source] will now use [destination]. | |
| if (source == copy && initialValue[source] != null) { | |
| ready.add(source); | |
| } | |
| } | |
| // Check if we have a cycle. | |
| String current = worklist.removeLast(); | |
| // If [current] is used as a source, and the assignment has been | |
| // done, we are done with this variable. Otherwise there is a | |
| // cycle that we break by using a temporary name. | |
| if (currentLocation[current] != null && | |
| current != currentLocation[initialValue[current]]) { | |
| doAssignment(tempName, current, sourceInformationMap[current]); | |
| currentLocation[current] = tempName; | |
| // [current] can now be safely updated. Copies of [current] | |
| // will now use [tempName]. | |
| ready.add(current); | |
| } | |
| } | |
| } | |
| void assignPhisOfSuccessors(HBasicBlock node) { | |
| CopyHandler handler = variableNames.getCopyHandler(node); | |
| if (handler == null) return; | |
| sequentializeCopies( | |
| handler.copies, variableNames.getSwapTemp(), emitAssignment); | |
| for (Copy<HInstruction> copy in handler.assignments) { | |
| String name = variableNames.getName(copy.destination); | |
| use(copy.source); | |
| assignVariable(name, pop(), | |
| copy.source.sourceInformation ?? copy.destination.sourceInformation); | |
| } | |
| } | |
| void iterateBasicBlock(HBasicBlock node) { | |
| HInstruction instruction = node.first; | |
| while (!identical(instruction, node.last)) { | |
| if (!isGenerateAtUseSite(instruction)) { | |
| define(instruction); | |
| } | |
| instruction = instruction.next; | |
| } | |
| assignPhisOfSuccessors(node); | |
| visit(instruction); | |
| } | |
| void handleInvokeBinary( | |
| HInvokeBinary node, String op, SourceInformation sourceInformation) { | |
| use(node.left); | |
| js.Expression jsLeft = pop(); | |
| use(node.right); | |
| push(new js.Binary(op, jsLeft, pop()) | |
| .withSourceInformation(sourceInformation)); | |
| } | |
| visitInvokeBinary(HInvokeBinary node, String op) { | |
| handleInvokeBinary(node, op, node.sourceInformation); | |
| } | |
| visitRelational(HRelational node, String op) { | |
| handleInvokeBinary(node, op, node.sourceInformation); | |
| } | |
| // We want the outcome of bit-operations to be positive. We use the unsigned | |
| // shift operator to achieve this. | |
| visitBitInvokeBinary(HBinaryBitOp node, String op) { | |
| visitInvokeBinary(node, op); | |
| if (op != '>>>' && requiresUintConversion(node)) { | |
| push(new js.Binary(">>>", pop(), new js.LiteralNumber("0")) | |
| .withSourceInformation(node.sourceInformation)); | |
| } | |
| } | |
| visitInvokeUnary(HInvokeUnary node, String op) { | |
| use(node.operand); | |
| push( | |
| new js.Prefix(op, pop()).withSourceInformation(node.sourceInformation)); | |
| } | |
| // We want the outcome of bit-operations to be positive. We use the unsigned | |
| // shift operator to achieve this. | |
| visitBitInvokeUnary(HInvokeUnary node, String op) { | |
| visitInvokeUnary(node, op); | |
| if (requiresUintConversion(node)) { | |
| push(new js.Binary(">>>", pop(), new js.LiteralNumber("0")) | |
| .withSourceInformation(node.sourceInformation)); | |
| } | |
| } | |
| void emitIdentityComparison( | |
| HIdentity instruction, SourceInformation sourceInformation, | |
| {bool inverse: false}) { | |
| String op = instruction.singleComparisonOp; | |
| HInstruction left = instruction.left; | |
| HInstruction right = instruction.right; | |
| if (op != null) { | |
| use(left); | |
| js.Expression jsLeft = pop(); | |
| use(right); | |
| push(new js.Binary(mapRelationalOperator(op, inverse), jsLeft, pop()) | |
| .withSourceInformation(sourceInformation)); | |
| } else { | |
| assert(NullConstantValue.JsNull == 'null'); | |
| use(left); | |
| js.Binary leftEqualsNull = | |
| new js.Binary("==", pop(), new js.LiteralNull()); | |
| use(right); | |
| js.Binary rightEqualsNull = new js.Binary( | |
| mapRelationalOperator("==", inverse), pop(), new js.LiteralNull()); | |
| use(right); | |
| use(left); | |
| js.Binary tripleEq = | |
| new js.Binary(mapRelationalOperator("===", inverse), pop(), pop()); | |
| push(new js.Conditional(leftEqualsNull, rightEqualsNull, tripleEq) | |
| .withSourceInformation(sourceInformation)); | |
| } | |
| } | |
| visitIdentity(HIdentity node) { | |
| emitIdentityComparison(node, node.sourceInformation, inverse: false); | |
| } | |
| visitAdd(HAdd node) => visitInvokeBinary(node, '+'); | |
| visitDivide(HDivide node) => visitInvokeBinary(node, '/'); | |
| visitMultiply(HMultiply node) => visitInvokeBinary(node, '*'); | |
| visitSubtract(HSubtract node) => visitInvokeBinary(node, '-'); | |
| visitBitAnd(HBitAnd node) => visitBitInvokeBinary(node, '&'); | |
| visitBitNot(HBitNot node) => visitBitInvokeUnary(node, '~'); | |
| visitBitOr(HBitOr node) => visitBitInvokeBinary(node, '|'); | |
| visitBitXor(HBitXor node) => visitBitInvokeBinary(node, '^'); | |
| visitShiftLeft(HShiftLeft node) => visitBitInvokeBinary(node, '<<'); | |
| visitShiftRight(HShiftRight node) => visitBitInvokeBinary(node, '>>>'); | |
| visitTruncatingDivide(HTruncatingDivide node) { | |
| assert(node.isUInt31(_abstractValueDomain)); | |
| // TODO(karlklose): Enable this assertion again when type propagation is | |
| // fixed. Issue 23555. | |
| // assert(node.left.isUInt32(compiler)); | |
| assert(node.right.isPositiveInteger(_abstractValueDomain)); | |
| use(node.left); | |
| js.Expression jsLeft = pop(); | |
| use(node.right); | |
| push(new js.Binary('/', jsLeft, pop()) | |
| .withSourceInformation(node.sourceInformation)); | |
| push(new js.Binary('|', pop(), new js.LiteralNumber("0")) | |
| .withSourceInformation(node.sourceInformation)); | |
| } | |
| visitRemainder(HRemainder node) { | |
| return visitInvokeBinary(node, '%'); | |
| } | |
| visitNegate(HNegate node) => visitInvokeUnary(node, '-'); | |
| visitAbs(HAbs node) { | |
| use(node.operand); | |
| push(js | |
| .js('Math.abs(#)', pop()) | |
| .withSourceInformation(node.sourceInformation)); | |
| } | |
| visitLess(HLess node) => visitRelational(node, '<'); | |
| visitLessEqual(HLessEqual node) => visitRelational(node, '<='); | |
| visitGreater(HGreater node) => visitRelational(node, '>'); | |
| visitGreaterEqual(HGreaterEqual node) => visitRelational(node, '>='); | |
| visitBoolify(HBoolify node) { | |
| assert(node.inputs.length == 1); | |
| use(node.inputs[0]); | |
| push(new js.Binary( | |
| '===', pop(), newLiteralBool(true, node.sourceInformation)) | |
| .withSourceInformation(node.sourceInformation)); | |
| } | |
| visitExit(HExit node) { | |
| // Don't do anything. | |
| } | |
| visitGoto(HGoto node) { | |
| HBasicBlock block = node.block; | |
| assert(block.successors.length == 1); | |
| List<HBasicBlock> dominated = block.dominatedBlocks; | |
| // With the exception of the entry-node which dominates its successor | |
| // and the exit node, no block finishing with a 'goto' can have more than | |
| // one dominated block (since it has only one successor). | |
| // If the successor is dominated by another block, then the other block | |
| // is responsible for visiting the successor. | |
| if (dominated.isEmpty) return; | |
| if (dominated.length > 2) { | |
| failedAt(node, 'dominated.length = ${dominated.length}'); | |
| } | |
| if (dominated.length == 2 && block != currentGraph.entry) { | |
| failedAt(node, 'node.block != currentGraph.entry'); | |
| } | |
| assert(dominated[0] == block.successors[0]); | |
| continueSubGraph(dominated.first); | |
| } | |
| visitLoopBranch(HLoopBranch node) { | |
| assert(node.block == subGraph.end); | |
| // We are generating code for a loop condition. | |
| // If we are generating the subgraph as an expression, the | |
| // condition will be generated as the expression. | |
| // Otherwise, we don't generate the expression, and leave that | |
| // to the code that called [visitSubGraph]. | |
| if (isGeneratingExpression) { | |
| use(node.inputs[0]); | |
| } | |
| } | |
| visitBreak(HBreak node) { | |
| assert(node.block.successors.length == 1); | |
| if (node.label != null) { | |
| LabelDefinition label = node.label; | |
| if (breakAction.contains(label.target)) { | |
| implicitBreakWithLabel(label.target); | |
| } else { | |
| pushStatement(new js.Break(_namer.breakLabelName(label)) | |
| .withSourceInformation(node.sourceInformation)); | |
| } | |
| } else { | |
| JumpTarget target = node.target; | |
| if (breakAction.contains(target)) { | |
| implicitBreakWithLabel(target); | |
| } else { | |
| if (node.breakSwitchContinueLoop) { | |
| pushStatement(new js.Break(_namer.implicitContinueLabelName(target)) | |
| .withSourceInformation(node.sourceInformation)); | |
| } else { | |
| pushStatement( | |
| new js.Break(null).withSourceInformation(node.sourceInformation)); | |
| } | |
| } | |
| } | |
| } | |
| visitContinue(HContinue node) { | |
| assert(node.block.successors.length == 1); | |
| if (node.label != null) { | |
| LabelDefinition label = node.label; | |
| if (continueAction.contains(label)) { | |
| continueAsBreak(label); | |
| } else { | |
| // TODO(floitsch): should this really be the breakLabelName? | |
| pushStatement(new js.Continue(_namer.breakLabelName(label)) | |
| .withSourceInformation(node.sourceInformation)); | |
| } | |
| } else { | |
| JumpTarget target = node.target; | |
| if (implicitContinueAction.contains(target)) { | |
| implicitContinueAsBreak(target); | |
| } else { | |
| if (target.isSwitch) { | |
| pushStatement( | |
| new js.Continue(_namer.implicitContinueLabelName(target)) | |
| .withSourceInformation(node.sourceInformation)); | |
| } else { | |
| pushStatement(new js.Continue(null) | |
| .withSourceInformation(node.sourceInformation)); | |
| } | |
| } | |
| } | |
| } | |
| visitExitTry(HExitTry node) { | |
| // An [HExitTry] is used to represent the control flow graph of a | |
| // try/catch block, ie the try body is always a predecessor | |
| // of the catch and finally. Here, we continue visiting the try | |
| // body by visiting the block that contains the user-level control | |
| // flow instruction. | |
| continueSubGraph(node.bodyTrySuccessor); | |
| } | |
| visitTry(HTry node) { | |
| // We should never get here. Try/catch/finally is always handled using block | |
| // information in [visitTryInfo]. | |
| failedAt(node, 'visitTry should not be called.'); | |
| } | |
| bool tryControlFlowOperation(HIf node) { | |
| if (!controlFlowOperators.contains(node)) return false; | |
| HPhi phi = node.joinBlock.phis.first; | |
| bool atUseSite = isGenerateAtUseSite(phi); | |
| // Don't generate a conditional operator in this situation: | |
| // i = condition ? bar() : i; | |
| // But generate this instead: | |
| // if (condition) i = bar(); | |
| // Usually, the variable name is longer than 'if' and it takes up | |
| // more space to duplicate the name. | |
| if (!atUseSite && | |
| variableNames.getName(phi) == variableNames.getName(phi.inputs[1])) { | |
| return false; | |
| } | |
| if (!atUseSite) define(phi); | |
| continueSubGraph(node.joinBlock); | |
| return true; | |
| } | |
| void generateIf(HIf node, HIfBlockInformation info) { | |
| use(node.inputs[0]); | |
| js.Expression test = pop(); | |
| HStatementInformation thenGraph = info.thenGraph; | |
| HStatementInformation elseGraph = info.elseGraph; | |
| js.Statement thenPart = | |
| unwrapStatement(generateStatementsInNewBlock(thenGraph)); | |
| js.Statement elsePart = | |
| unwrapStatement(generateStatementsInNewBlock(elseGraph)); | |
| js.Statement code; | |
| // Peephole rewrites: | |
| // | |
| // if (e); else S; --> if(!e) S; | |
| // | |
| // if (e); --> e; | |
| // | |
| // TODO(sra): This peephole optimization would be better done as an SSA | |
| // optimization. | |
| if (thenPart is js.EmptyStatement) { | |
| if (elsePart is js.EmptyStatement) { | |
| code = new js.ExpressionStatement(test); | |
| } else { | |
| code = new js.If.noElse(new js.Prefix('!', test), elsePart); | |
| } | |
| } else { | |
| code = new js.If(test, thenPart, elsePart); | |
| } | |
| pushStatement(code.withSourceInformation(node.sourceInformation)); | |
| } | |
| visitIf(HIf node) { | |
| if (tryControlFlowOperation(node)) return; | |
| HInstruction condition = node.inputs[0]; | |
| HIfBlockInformation info = node.blockInformation.body; | |
| if (condition.isConstant()) { | |
| HConstant constant = condition; | |
| if (constant.constant.isTrue) { | |
| generateStatements(info.thenGraph); | |
| } else { | |
| generateStatements(info.elseGraph); | |
| } | |
| } else { | |
| generateIf(node, info); | |
| } | |
| HBasicBlock joinBlock = node.joinBlock; | |
| if (joinBlock != null && !identical(joinBlock.dominator, node.block)) { | |
| // The join block is dominated by a block in one of the branches. | |
| // The subgraph traversal never reached it, so we visit it here | |
| // instead. | |
| continueSubGraph(joinBlock); | |
| } | |
| // Visit all the dominated blocks that are not part of the then or else | |
| // branches, and is not the join block. | |
| // Depending on how the then/else branches terminate | |
| // (e.g., return/throw/break) there can be any number of these. | |
| List<HBasicBlock> dominated = node.block.dominatedBlocks; | |
| for (int i = 2; i < dominated.length; i++) { | |
| continueSubGraph(dominated[i]); | |
| } | |
| } | |
| void visitInterceptor(HInterceptor node) { | |
| if (node.isConditionalConstantInterceptor) { | |
| assert(node.inputs.length == 2); | |
| use(node.receiver); | |
| js.Expression receiverExpression = pop(); | |
| use(node.conditionalConstantInterceptor); | |
| js.Expression constant = pop(); | |
| push(js.js('# && #', [receiverExpression, constant])); | |
| } else { | |
| assert(node.inputs.length == 1); | |
| _registry.registerSpecializedGetInterceptor(node.interceptedClasses); | |
| js.Name name = _namer.nameForGetInterceptor(node.interceptedClasses); | |
| var isolate = new js.VariableUse( | |
| _namer.globalObjectForLibrary(_commonElements.interceptorsLibrary)); | |
| use(node.receiver); | |
| List<js.Expression> arguments = <js.Expression>[pop()]; | |
| push(js | |
| .propertyCall(isolate, name, arguments) | |
| .withSourceInformation(node.sourceInformation)); | |
| _registry.registerUseInterceptor(); | |
| } | |
| } | |
| visitInvokeDynamicMethod(HInvokeDynamicMethod node) { | |
| use(node.receiver); | |
| js.Expression object = pop(); | |
| String methodName; | |
| List<js.Expression> arguments = visitArguments(node.inputs); | |
| MemberEntity target = node.element; | |
| // TODO(herhut): The namer should return the appropriate backendname here. | |
| if (target != null && !node.isInterceptedCall) { | |
| if (target == _commonElements.jsArrayAdd) { | |
| methodName = 'push'; | |
| } else if (target == _commonElements.jsArrayRemoveLast) { | |
| methodName = 'pop'; | |
| } else if (_commonElements.isJsStringSplit(target)) { | |
| methodName = 'split'; | |
| // Split returns a List, so we make sure the backend knows the | |
| // list class is instantiated. | |
| _registry.registerInstantiatedClass(_commonElements.listClass); | |
| } else if (_nativeData.isNativeMember(target) && | |
| target.isFunction && | |
| !node.isInterceptedCall) { | |
| // A direct (i.e. non-interceptor) native call is the result of | |
| // optimization. The optimization ensures any type checks or | |
| // conversions have been satisified. | |
| methodName = _nativeData.getFixedBackendName(target); | |
| } | |
| } | |
| js.Name methodLiteral; | |
| if (methodName == null) { | |
| methodLiteral = _namer.invocationName(node.selector); | |
| registerMethodInvoke(node); | |
| } else { | |
| methodLiteral = _namer.asName(methodName); | |
| } | |
| push(js | |
| .propertyCall(object, methodLiteral, arguments) | |
| .withSourceInformation(node.sourceInformation)); | |
| } | |
| void visitInvokeConstructorBody(HInvokeConstructorBody node) { | |
| use(node.inputs[0]); | |
| js.Expression object = pop(); | |
| js.Name methodName = _namer.instanceMethodName(node.element); | |
| List<js.Expression> arguments = visitArguments(node.inputs); | |
| push(js | |
| .propertyCall(object, methodName, arguments) | |
| .withSourceInformation(node.sourceInformation)); | |
| _registry.registerStaticUse(new StaticUse.constructorBodyInvoke( | |
| node.element, new CallStructure.unnamed(arguments.length))); | |
| } | |
| void visitInvokeGeneratorBody(HInvokeGeneratorBody node) { | |
| JGeneratorBody element = node.element; | |
| if (element.isInstanceMember) { | |
| use(node.inputs[0]); | |
| js.Expression object = pop(); | |
| List<js.Expression> arguments = visitArguments(node.inputs); | |
| js.Name methodName = _namer.instanceMethodName(element); | |
| push(js | |
| .propertyCall(object, methodName, arguments) | |
| .withSourceInformation(node.sourceInformation)); | |
| } else { | |
| push(_emitter.staticFunctionAccess(element)); | |
| List<js.Expression> arguments = visitArguments(node.inputs, start: 0); | |
| push(new js.Call(pop(), arguments, | |
| sourceInformation: node.sourceInformation)); | |
| } | |
| _registry | |
| .registerStaticUse(new StaticUse.generatorBodyInvoke(node.element)); | |
| } | |
| void visitOneShotInterceptor(HOneShotInterceptor node) { | |
| List<js.Expression> arguments = visitArguments(node.inputs); | |
| var isolate = new js.VariableUse( | |
| _namer.globalObjectForLibrary(_commonElements.interceptorsLibrary)); | |
| Selector selector = node.selector; | |
| js.Name methodName = _oneShotInterceptorData.registerOneShotInterceptor( | |
| selector, _namer, _closedWorld); | |
| push(js | |
| .propertyCall(isolate, methodName, arguments) | |
| .withSourceInformation(node.sourceInformation)); | |
| if (selector.isGetter) { | |
| registerGetter(node); | |
| } else if (selector.isSetter) { | |
| registerSetter(node); | |
| } else { | |
| registerMethodInvoke(node); | |
| } | |
| _registry.registerUseInterceptor(); | |
| } | |
| AbstractValue getOptimizedSelectorFor( | |
| HInvokeDynamic node, Selector selector, AbstractValue mask) { | |
| if (node.element != null) { | |
| // Create an artificial type mask to make sure only | |
| // [node.element] will be enqueued. We're not using the receiver | |
| // type because our optimizations might end up in a state where the | |
| // invoke dynamic knows more than the receiver. | |
| ClassEntity enclosing = node.element.enclosingClass; | |
| if (_closedWorld.classHierarchy.isInstantiated(enclosing)) { | |
| return _abstractValueDomain.createNonNullExact(enclosing); | |
| } else { | |
| // The element is mixed in so a non-null subtype mask is the most | |
| // precise we have. | |
| assert( | |
| _closedWorld.isUsedAsMixin(enclosing), | |
| failedAt( | |
| node, | |
| "Element ${node.element} from $enclosing expected " | |
| "to be mixed in.")); | |
| return _abstractValueDomain.createNonNullSubtype(enclosing); | |
| } | |
| } | |
| return mask ?? _abstractValueDomain.dynamicType; | |
| } | |
| void registerMethodInvoke(HInvokeDynamic node) { | |
| Selector selector = node.selector; | |
| // If we don't know what we're calling or if we are calling a getter, | |
| // we need to register that fact that we may be calling a closure | |
| // with the same arguments. | |
| MemberEntity target = node.element; | |
| if ((target == null || target.isGetter) && | |
| // TODO(johnniwinther): Remove this when kernel adds an `isFunctionCall` | |
| // flag to [ir.MethodInvocation]. Currently we can't tell the difference | |
| // between a dynamic call and a function call, but we at least know that | |
| // toString is not a getter (a potential function call should otherwise | |
| // have been register for string concatenation). | |
| selector != Selectors.toString_) { | |
| // TODO(kasperl): If we have a typed selector for the call, we | |
| // may know something about the types of closures that need | |
| // the specific closure call method. | |
| Selector call = new Selector.callClosureFrom(selector); | |
| _registry.registerDynamicUse( | |
| new ConstrainedDynamicUse(call, null, node.typeArguments)); | |
| } | |
| if (target != null) { | |
| // This is a dynamic invocation which we have found to have a single | |
| // target but for some reason haven't inlined. We are _still_ accessing | |
| // the target dynamically but we don't need to enqueue more than target | |
| // for this to work. | |
| assert(selector.applies(target), | |
| failedAt(node, '$selector does not apply to $target')); | |
| _registry.registerStaticUse(new StaticUse.directInvoke( | |
| target, selector.callStructure, node.typeArguments)); | |
| } else { | |
| AbstractValue mask = getOptimizedSelectorFor(node, selector, node.mask); | |
| _registry.registerDynamicUse( | |
| new ConstrainedDynamicUse(selector, mask, node.typeArguments)); | |
| } | |
| } | |
| void registerSetter(HInvokeDynamic node) { | |
| if (node.element != null) { | |
| // This is a dynamic update which we have found to have a single | |
| // target but for some reason haven't inlined. We are _still_ accessing | |
| // the target dynamically but we don't need to enqueue more than target | |
| // for this to work. | |
| _registry.registerStaticUse(new StaticUse.directSet(node.element)); | |
| } else { | |
| Selector selector = node.selector; | |
| AbstractValue mask = getOptimizedSelectorFor(node, selector, node.mask); | |
| _registry.registerDynamicUse( | |
| new ConstrainedDynamicUse(selector, mask, node.typeArguments)); | |
| } | |
| } | |
| void registerGetter(HInvokeDynamic node) { | |
| if (node.element != null && | |
| (node.element.isGetter || node.element.isField)) { | |
| // This is a dynamic read which we have found to have a single target but | |
| // for some reason haven't inlined. We are _still_ accessing the target | |
| // dynamically but we don't need to enqueue more than target for this to | |
| // work. The test above excludes non-getter functions since the element | |
| // represents two targets - a tearoff getter and the torn-off method. | |
| _registry.registerStaticUse(new StaticUse.directGet(node.element)); | |
| } else { | |
| Selector selector = node.selector; | |
| AbstractValue mask = getOptimizedSelectorFor(node, selector, node.mask); | |
| _registry.registerDynamicUse( | |
| new ConstrainedDynamicUse(selector, mask, node.typeArguments)); | |
| } | |
| } | |
| visitInvokeDynamicSetter(HInvokeDynamicSetter node) { | |
| use(node.receiver); | |
| js.Name name = _namer.invocationName(node.selector); | |
| push(js | |
| .propertyCall(pop(), name, visitArguments(node.inputs)) | |
| .withSourceInformation(node.sourceInformation)); | |
| registerSetter(node); | |
| } | |
| visitInvokeDynamicGetter(HInvokeDynamicGetter node) { | |
| use(node.receiver); | |
| js.Name name = _namer.invocationName(node.selector); | |
| push(js | |
| .propertyCall(pop(), name, visitArguments(node.inputs)) | |
| .withSourceInformation(node.sourceInformation)); | |
| registerGetter(node); | |
| } | |
| visitInvokeClosure(HInvokeClosure node) { | |
| Selector call = new Selector.callClosureFrom(node.selector); | |
| use(node.receiver); | |
| push(js | |
| .propertyCall( | |
| pop(), _namer.invocationName(call), visitArguments(node.inputs)) | |
| .withSourceInformation(node.sourceInformation)); | |
| // TODO(kasperl): If we have a typed selector for the call, we | |
| // may know something about the types of closures that need | |
| // the specific closure call method. | |
| _registry.registerDynamicUse( | |
| new ConstrainedDynamicUse(call, null, node.typeArguments)); | |
| } | |
| visitInvokeStatic(HInvokeStatic node) { | |
| MemberEntity element = node.element; | |
| node.instantiatedTypes?.forEach(_registry.registerInstantiation); | |
| List<js.Expression> arguments = visitArguments(node.inputs, start: 0); | |
| if (element == _commonElements.checkConcurrentModificationError) { | |
| // Manually inline the [checkConcurrentModificationError] function. This | |
| // function is only called from a for-loop update. Ideally we would just | |
| // generate the conditionalcontrol flow in the builder but it adds basic | |
| // blocks in the loop update that interfere with other optimizations and | |
| // confuses loop recognition. | |
| assert(arguments.length == 2); | |
| FunctionEntity throwFunction = | |
| _commonElements.throwConcurrentModificationError; | |
| _registry.registerStaticUse( | |
| new StaticUse.staticInvoke(throwFunction, CallStructure.ONE_ARG)); | |
| // Calling using `(0, #)(#)` instead of `#(#)` separates the property load | |
| // of the static function access from the call. For some reason this | |
| // helps V8 see that the call never happens so V8 makes the call a | |
| // deoptimization. This removes the call from the optimized loop, making | |
| // more optimizations available to the loop. This form is 50% faster on | |
| // some small loop, almost as fast as loops with no concurrent | |
| // modification check. | |
| // Create [right] as a separate JS node to give the call a source | |
| // location. | |
| js.Expression right = js.js('(0, #)(#)', [ | |
| _emitter.staticFunctionAccess(throwFunction), | |
| arguments[1] | |
| ]).withSourceInformation(node.sourceInformation); | |
| push(js.js('# || #', [arguments[0], right]).withSourceInformation( | |
| node.sourceInformation)); | |
| } else { | |
| CallStructure callStructure = new CallStructure.unnamed( | |
| arguments.length, node.typeArguments.length); | |
| _registry.registerStaticUse(element.isConstructor | |
| ? new StaticUse.constructorInvoke(element, callStructure) | |
| : new StaticUse.staticInvoke( | |
| element, callStructure, node.typeArguments)); | |
| push(_emitter.staticFunctionAccess(element)); | |
| push(new js.Call(pop(), arguments, | |
| sourceInformation: node.sourceInformation)); | |
| } | |
| } | |
| visitInvokeSuper(HInvokeSuper node) { | |
| MemberEntity superElement = node.element; | |
| ClassEntity superClass = superElement.enclosingClass; | |
| if (superElement.isField) { | |
| js.Name fieldName = _namer.instanceFieldPropertyName(superElement); | |
| use(node.inputs[0]); | |
| js.PropertyAccess access = new js.PropertyAccess(pop(), fieldName) | |
| .withSourceInformation(node.sourceInformation); | |
| if (node.isSetter) { | |
| _registry.registerStaticUse(superElement.isSetter | |
| ? new StaticUse.superSetterSet(superElement) | |
| : new StaticUse.superFieldSet(superElement)); | |
| use(node.value); | |
| push(new js.Assignment(access, pop()) | |
| .withSourceInformation(node.sourceInformation)); | |
| } else { | |
| _registry.registerStaticUse(new StaticUse.superGet(superElement)); | |
| push(access); | |
| } | |
| } else { | |
| Selector selector = node.selector; | |
| if (!_superMemberData.maybeRegisterAliasedSuperMember( | |
| superElement, selector)) { | |
| js.Name methodName; | |
| if (selector.isGetter && !superElement.isGetter) { | |
| // If this is a tear-off, register the fact that a tear-off closure | |
| // will be created, and that this tear-off must bypass ordinary | |
| // dispatch to ensure the super method is invoked. | |
| FunctionEntity helper = _commonElements.closureFromTearOff; | |
| _registry.registerStaticUse(new StaticUse.staticInvoke( | |
| helper, | |
| new CallStructure.unnamed( | |
| node.inputs.length, node.typeArguments.length), | |
| node.typeArguments)); | |
| _registry.registerStaticUse(new StaticUse.superTearOff(node.element)); | |
| methodName = _namer.invocationName(selector); | |
| } else { | |
| methodName = _namer.instanceMethodName(superElement); | |
| } | |
| _registry.registerStaticUse(new StaticUse.superInvoke( | |
| superElement, new CallStructure.unnamed(node.inputs.length))); | |
| push(js.js('#.#.call(#)', [ | |
| _emitter.prototypeAccess(superClass, hasBeenInstantiated: true), | |
| methodName, | |
| visitArguments(node.inputs, start: 0) | |
| ]).withSourceInformation(node.sourceInformation)); | |
| } else { | |
| use(node.receiver); | |
| _registry.registerStaticUse(new StaticUse.superInvoke( | |
| superElement, new CallStructure.unnamed(node.inputs.length - 1))); | |
| push(js.js('#.#(#)', [ | |
| pop(), | |
| _namer.aliasedSuperMemberPropertyName(superElement), | |
| visitArguments(node.inputs, start: 1) | |
| ]) // Skip receiver argument. | |
| .withSourceInformation(node.sourceInformation)); | |
| } | |
| } | |
| } | |
| visitFieldGet(HFieldGet node) { | |
| use(node.receiver); | |
| if (node.isNullCheck) { | |
| // We access a JavaScript member we know all objects besides | |
| // null and undefined have: V8 does not like accessing a member | |
| // that does not exist. | |
| push(new js.PropertyAccess.field(pop(), 'toString') | |
| .withSourceInformation(node.sourceInformation)); | |
| } else { | |
| FieldEntity field = node.element; | |
| js.Name name = _namer.instanceFieldPropertyName(field); | |
| push(new js.PropertyAccess(pop(), name) | |
| .withSourceInformation(node.sourceInformation)); | |
| _registry.registerStaticUse(new StaticUse.fieldGet(field)); | |
| } | |
| } | |
| visitFieldSet(HFieldSet node) { | |
| FieldEntity element = node.element; | |
| _registry.registerStaticUse(new StaticUse.fieldSet(element)); | |
| js.Name name = _namer.instanceFieldPropertyName(element); | |
| use(node.receiver); | |
| js.Expression receiver = pop(); | |
| use(node.value); | |
| push(new js.Assignment(new js.PropertyAccess(receiver, name), pop()) | |
| .withSourceInformation(node.sourceInformation)); | |
| } | |
| visitGetLength(HGetLength node) { | |
| use(node.receiver); | |
| push(new js.PropertyAccess.field(pop(), 'length') | |
| .withSourceInformation(node.sourceInformation)); | |
| } | |
| visitReadModifyWrite(HReadModifyWrite node) { | |
| FieldEntity element = node.element; | |
| _registry.registerStaticUse(new StaticUse.fieldGet(element)); | |
| _registry.registerStaticUse(new StaticUse.fieldSet(element)); | |
| js.Name name = _namer.instanceFieldPropertyName(element); | |
| use(node.receiver); | |
| js.Expression fieldReference = new js.PropertyAccess(pop(), name); | |
| if (node.isPreOp) { | |
| push(new js.Prefix(node.jsOp, fieldReference) | |
| .withSourceInformation(node.sourceInformation)); | |
| } else if (node.isPostOp) { | |
| push(new js.Postfix(node.jsOp, fieldReference) | |
| .withSourceInformation(node.sourceInformation)); | |
| } else { | |
| use(node.value); | |
| push(new js.Assignment.compound(fieldReference, node.jsOp, pop()) | |
| .withSourceInformation(node.sourceInformation)); | |
| } | |
| } | |
| visitLocalGet(HLocalGet node) { | |
| use(node.receiver); | |
| } | |
| visitLocalSet(HLocalSet node) { | |
| use(node.value); | |
| assignVariable( | |
| variableNames.getName(node.receiver), pop(), node.sourceInformation); | |
| } | |
| void registerForeignTypes(HForeign node) { | |
| native.NativeBehavior nativeBehavior = node.nativeBehavior; | |
| if (nativeBehavior == null) return; | |
| _nativeEnqueuer.registerNativeBehavior( | |
| _registry.worldImpact, nativeBehavior, node); | |
| } | |
| visitForeignCode(HForeignCode node) { | |
| List<HInstruction> inputs = node.inputs; | |
| if (node.isJsStatement()) { | |
| List<js.Expression> interpolatedExpressions = <js.Expression>[]; | |
| for (int i = 0; i < inputs.length; i++) { | |
| use(inputs[i]); | |
| interpolatedExpressions.add(pop()); | |
| } | |
| pushStatement(node.codeTemplate | |
| .instantiate(interpolatedExpressions) | |
| .withSourceInformation(node.sourceInformation)); | |
| } else { | |
| List<js.Expression> interpolatedExpressions = <js.Expression>[]; | |
| for (int i = 0; i < inputs.length; i++) { | |
| use(inputs[i]); | |
| interpolatedExpressions.add(pop()); | |
| } | |
| push(node.codeTemplate | |
| .instantiate(interpolatedExpressions) | |
| .withSourceInformation(node.sourceInformation)); | |
| } | |
| // TODO(sra): Tell world.nativeEnqueuer about the types created here. | |
| registerForeignTypes(node); | |
| if (node.foreignFunction != null) { | |
| _registry?.registerStaticUse( | |
| new StaticUse.implicitInvoke(node.foreignFunction)); | |
| } | |
| } | |
| visitCreate(HCreate node) { | |
| js.Expression jsClassReference = _emitter.constructorAccess(node.element); | |
| List<js.Expression> arguments = visitArguments(node.inputs, start: 0); | |
| push(new js.New(jsClassReference, arguments) | |
| .withSourceInformation(node.sourceInformation)); | |
| // We also use HCreate to instantiate closure classes that belong to | |
| // function expressions. We have to register their use here, as otherwise | |
| // code for them might not be emitted. | |
| if (node.element.isClosure) { | |
| _registry.registerInstantiatedClass(node.element); | |
| } | |
| node.instantiatedTypes?.forEach(_registry.registerInstantiation); | |
| if (node.callMethod != null) { | |
| _registry | |
| ?.registerStaticUse(new StaticUse.implicitInvoke(node.callMethod)); | |
| _registry?.registerInstantiatedClosure(node.callMethod); | |
| } | |
| } | |
| visitCreateBox(HCreateBox node) { | |
| push(new js.ObjectInitializer(<js.Property>[])); | |
| } | |
| js.Expression newLiteralBool( | |
| bool value, SourceInformation sourceInformation) { | |
| if (_options.enableMinification) { | |
| // Use !0 for true, !1 for false. | |
| return new js.Prefix("!", new js.LiteralNumber(value ? "0" : "1")) | |
| .withSourceInformation(sourceInformation); | |
| } else { | |
| return new js.LiteralBool(value).withSourceInformation(sourceInformation); | |
| } | |
| } | |
| void generateConstant( | |
| ConstantValue constant, SourceInformation sourceInformation) { | |
| js.Expression expression = _emitter.constantReference(constant); | |
| if (!constant.isDummy) { | |
| // TODO(johnniwinther): Support source information on synthetic constants. | |
| expression = expression.withSourceInformation(sourceInformation); | |
| } | |
| push(expression); | |
| } | |
| visitConstant(HConstant node) { | |
| assert(isGenerateAtUseSite(node)); | |
| generateConstant(node.constant, node.sourceInformation); | |
| _registry.registerConstantUse(new ConstantUse.literal(node.constant)); | |
| if (node.constant.isType) { | |
| TypeConstantValue typeConstant = node.constant; | |
| _registry.registerTypeUse( | |
| new TypeUse.constTypeLiteral(typeConstant.representedType)); | |
| } | |
| } | |
| visitNot(HNot node) { | |
| assert(node.inputs.length == 1); | |
| generateNot(node.inputs[0], node.sourceInformation); | |
| } | |
| static String mapRelationalOperator(String op, bool inverse) { | |
| Map<String, String> inverseOperator = const <String, String>{ | |
| "==": "!=", | |
| "!=": "==", | |
| "===": "!==", | |
| "!==": "===", | |
| "<": ">=", | |
| "<=": ">", | |
| ">": "<=", | |
| ">=": "<" | |
| }; | |
| return inverse ? inverseOperator[op] : op; | |
| } | |
| void generateNot(HInstruction input, SourceInformation sourceInformation) { | |
| bool canGenerateOptimizedComparison(HInstruction instruction) { | |
| if (instruction is! HRelational) return false; | |
| HRelational relational = instruction; | |
| HInstruction left = relational.left; | |
| HInstruction right = relational.right; | |
| if (left.isStringOrNull(_abstractValueDomain) && | |
| right.isStringOrNull(_abstractValueDomain)) { | |
| return true; | |
| } | |
| // This optimization doesn't work for NaN, so we only do it if the | |
| // type is known to be an integer. | |
| return left.isInteger(_abstractValueDomain) && | |
| right.isInteger(_abstractValueDomain); | |
| } | |
| bool handledBySpecialCase = false; | |
| if (isGenerateAtUseSite(input)) { | |
| handledBySpecialCase = true; | |
| if (input is HIs) { | |
| emitIs(input, '!==', sourceInformation); | |
| } else if (input is HIsViaInterceptor) { | |
| emitIsViaInterceptor(input, sourceInformation, negative: true); | |
| } else if (input is HNot) { | |
| use(input.inputs[0]); | |
| } else if (input is HIdentity) { | |
| emitIdentityComparison(input, sourceInformation, inverse: true); | |
| } else if (input is HBoolify) { | |
| use(input.inputs[0]); | |
| push(new js.Binary( | |
| "!==", pop(), newLiteralBool(true, input.sourceInformation)) | |
| .withSourceInformation(sourceInformation)); | |
| } else if (canGenerateOptimizedComparison(input)) { | |
| HRelational relational = input; | |
| BinaryOperation operation = relational.operation(_constantSystem); | |
| String op = mapRelationalOperator(operation.name, true); | |
| handleInvokeBinary(input, op, sourceInformation); | |
| } else { | |
| handledBySpecialCase = false; | |
| } | |
| } | |
| if (!handledBySpecialCase) { | |
| use(input); | |
| push(new js.Prefix("!", pop()).withSourceInformation(sourceInformation)); | |
| } | |
| } | |
| visitParameterValue(HParameterValue node) { | |
| assert(!isGenerateAtUseSite(node)); | |
| String name = variableNames.getName(node); | |
| parameters.add(new js.Parameter(name)); | |
| declaredLocals.add(name); | |
| } | |
| visitLocalValue(HLocalValue node) { | |
| assert(!isGenerateAtUseSite(node)); | |
| String name = variableNames.getName(node); | |
| collectedVariableDeclarations.add(name); | |
| } | |
| visitPhi(HPhi node) { | |
| // This method is only called for phis that are generated at use | |
| // site. A phi can be generated at use site only if it is the | |
| // result of a control flow operation. | |
| HBasicBlock ifBlock = node.block.dominator; | |
| assert(controlFlowOperators.contains(ifBlock.last)); | |
| HInstruction input = ifBlock.last.inputs[0]; | |
| if (input.isConstantFalse()) { | |
| use(node.inputs[1]); | |
| } else if (input.isConstantTrue()) { | |
| use(node.inputs[0]); | |
| } else if (node.inputs[1].isConstantBoolean()) { | |
| String operation = node.inputs[1].isConstantFalse() ? '&&' : '||'; | |
| if (operation == '||') { | |
| generateNot(input, input.sourceInformation); | |
| } else { | |
| use(input); | |
| } | |
| js.Expression left = pop(); | |
| use(node.inputs[0]); | |
| push(new js.Binary(operation, left, pop())); | |
| } else { | |
| use(input); | |
| js.Expression test = pop(); | |
| use(node.inputs[0]); | |
| js.Expression then = pop(); | |
| use(node.inputs[1]); | |
| push(new js.Conditional(test, then, pop())); | |
| } | |
| } | |
| visitReturn(HReturn node) { | |
| assert(node.inputs.length == 1); | |
| HInstruction input = node.inputs[0]; | |
| if (input.isConstantNull()) { | |
| pushStatement( | |
| new js.Return().withSourceInformation(node.sourceInformation)); | |
| } else { | |
| use(node.inputs[0]); | |
| pushStatement( | |
| new js.Return(pop()).withSourceInformation(node.sourceInformation)); | |
| } | |
| } | |
| visitThis(HThis node) { | |
| push(new js.This()); | |
| } | |
| visitThrow(HThrow node) { | |
| if (node.isRethrow) { | |
| use(node.inputs[0]); | |
| pushStatement( | |
| new js.Throw(pop()).withSourceInformation(node.sourceInformation)); | |
| } else { | |
| generateThrowWithHelper( | |
| _commonElements.wrapExceptionHelper, node.inputs[0], | |
| sourceInformation: node.sourceInformation); | |
| } | |
| } | |
| visitAwait(HAwait node) { | |
| use(node.inputs[0]); | |
| push(new js.Await(pop()).withSourceInformation(node.sourceInformation)); | |
| } | |
| visitYield(HYield node) { | |
| use(node.inputs[0]); | |
| pushStatement(new js.DartYield(pop(), node.hasStar) | |
| .withSourceInformation(node.sourceInformation)); | |
| } | |
| visitRangeConversion(HRangeConversion node) { | |
| // Range conversion instructions are removed by the value range | |
| // analyzer. | |
| assert(false); | |
| } | |
| visitBoundsCheck(HBoundsCheck node) { | |
| // TODO(ngeoffray): Separate the two checks of the bounds check, so, | |
| // e.g., the zero checks can be shared if possible. | |
| // If the checks always succeeds, we would have removed the bounds check | |
| // completely. | |
| assert(node.staticChecks != HBoundsCheck.ALWAYS_TRUE); | |
| if (node.staticChecks != HBoundsCheck.ALWAYS_FALSE) { | |
| js.Expression under; | |
| js.Expression over; | |
| if (node.staticChecks != HBoundsCheck.ALWAYS_ABOVE_ZERO) { | |
| use(node.index); | |
| if (node.index.isInteger(_abstractValueDomain)) { | |
| under = js.js("# < 0", pop()); | |
| } else { | |
| js.Expression jsIndex = pop(); | |
| under = js.js("# >>> 0 !== #", [jsIndex, jsIndex]); | |
| } | |
| } else if (!node.index.isInteger(_abstractValueDomain)) { | |
| checkInt(node.index, '!=='); | |
| under = pop(); | |
| } | |
| if (node.staticChecks != HBoundsCheck.ALWAYS_BELOW_LENGTH) { | |
| var index = node.index; | |
| use(index); | |
| js.Expression jsIndex = pop(); | |
| use(node.length); | |
| over = new js.Binary(">=", jsIndex, pop()); | |
| } | |
| assert(over != null || under != null); | |
| js.Expression underOver = under == null | |
| ? over | |
| : over == null ? under : new js.Binary("||", under, over); | |
| js.Statement thenBody = new js.Block.empty(); | |
| js.Block oldContainer = currentContainer; | |
| currentContainer = thenBody; | |
| generateThrowWithHelper(_commonElements.throwIndexOutOfRangeException, | |
| [node.array, node.reportedIndex], | |
| sourceInformation: node.sourceInformation); | |
| currentContainer = oldContainer; | |
| thenBody = unwrapStatement(thenBody); | |
| pushStatement(new js.If.noElse(underOver, thenBody) | |
| .withSourceInformation(node.sourceInformation)); | |
| } else { | |
| generateThrowWithHelper(_commonElements.throwIndexOutOfRangeException, | |
| [node.array, node.index]); | |
| } | |
| } | |
| void generateThrowWithHelper(FunctionEntity helper, argument, | |
| {SourceInformation sourceInformation}) { | |
| js.Expression jsHelper = _emitter.staticFunctionAccess(helper); | |
| List arguments = <js.Expression>[]; | |
| if (argument is List) { | |
| argument.forEach((instruction) { | |
| use(instruction); | |
| arguments.add(pop()); | |
| }); | |
| } else { | |
| use(argument); | |
| arguments.add(pop()); | |
| } | |
| _registry.registerStaticUse(new StaticUse.staticInvoke( | |
| helper, new CallStructure.unnamed(arguments.length))); | |
| js.Call value = new js.Call(jsHelper, arguments.toList(growable: false), | |
| sourceInformation: sourceInformation); | |
| // BUG(4906): Using throw/return here adds to the size of the generated code | |
| // but it has the advantage of explicitly telling the JS engine that | |
| // this code path will terminate abruptly. Needs more work. | |
| if (helper == _commonElements.wrapExceptionHelper) { | |
| pushStatement( | |
| new js.Throw(value).withSourceInformation(sourceInformation)); | |
| } else { | |
| pushStatement( | |
| new js.Return(value).withSourceInformation(sourceInformation)); | |
| } | |
| } | |
| visitThrowExpression(HThrowExpression node) { | |
| HInstruction argument = node.inputs[0]; | |
| use(argument); | |
| FunctionEntity helper = _commonElements.throwExpressionHelper; | |
| _registry.registerStaticUse( | |
| new StaticUse.staticInvoke(helper, CallStructure.ONE_ARG)); | |
| js.Expression jsHelper = _emitter.staticFunctionAccess(helper); | |
| js.Call value = new js.Call(jsHelper, [pop()]) | |
| .withSourceInformation(node.sourceInformation); | |
| push(value); | |
| } | |
| void visitSwitch(HSwitch node) { | |
| // Switches are handled using [visitSwitchInfo]. | |
| } | |
| void visitStatic(HStatic node) { | |
| MemberEntity element = node.element; | |
| assert(element.isFunction || element.isField); | |
| if (element.isFunction) { | |
| push(_emitter | |
| .isolateStaticClosureAccess(element) | |
| .withSourceInformation(node.sourceInformation)); | |
| _registry.registerStaticUse(new StaticUse.staticTearOff(element)); | |
| } else { | |
| push(_emitter | |
| .staticFieldAccess(element) | |
| .withSourceInformation(node.sourceInformation)); | |
| _registry.registerStaticUse(new StaticUse.staticGet(element)); | |
| } | |
| } | |
| void visitLazyStatic(HLazyStatic node) { | |
| FieldEntity element = node.element; | |
| _registry.registerStaticUse(new StaticUse.staticInit(element)); | |
| js.Expression lazyGetter = _emitter.isolateLazyInitializerAccess(element); | |
| js.Call call = new js.Call(lazyGetter, <js.Expression>[], | |
| sourceInformation: node.sourceInformation); | |
| push(call); | |
| } | |
| void visitStaticStore(HStaticStore node) { | |
| _registry.registerStaticUse(new StaticUse.staticSet(node.element)); | |
| js.Node variable = _emitter.staticFieldAccess(node.element); | |
| use(node.inputs[0]); | |
| push(new js.Assignment(variable, pop()) | |
| .withSourceInformation(node.sourceInformation)); | |
| } | |
| void visitStringConcat(HStringConcat node) { | |
| use(node.left); | |
| js.Expression jsLeft = pop(); | |
| use(node.right); | |
| push(new js.Binary('+', jsLeft, pop()) | |
| .withSourceInformation(node.sourceInformation)); | |
| } | |
| void visitStringify(HStringify node) { | |
| HInstruction input = node.inputs.first; | |
| if (input.isString(_abstractValueDomain)) { | |
| use(input); | |
| } else if (input.isInteger(_abstractValueDomain) || | |
| input.isBoolean(_abstractValueDomain)) { | |
| // JavaScript's + operator with a string for the left operand will convert | |
| // the right operand to a string, and the conversion result is correct. | |
| use(input); | |
| if (node.usedBy.length == 1 && | |
| node.usedBy[0] is HStringConcat && | |
| node.usedBy[0].inputs[1] == node) { | |
| // The context is already <string> + value. | |
| } else { | |
| // Force an empty string for the first operand. | |
| push(new js.Binary('+', js.string(""), pop()) | |
| .withSourceInformation(node.sourceInformation)); | |
| } | |
| } else { | |
| FunctionEntity convertToString = | |
| _commonElements.stringInterpolationHelper; | |
| _registry.registerStaticUse( | |
| new StaticUse.staticInvoke(convertToString, CallStructure.ONE_ARG)); | |
| js.Expression jsHelper = _emitter.staticFunctionAccess(convertToString); | |
| use(input); | |
| push(new js.Call(jsHelper, <js.Expression>[pop()], | |
| sourceInformation: node.sourceInformation)); | |
| } | |
| } | |
| void visitLiteralList(HLiteralList node) { | |
| _registry.registerInstantiatedClass(_commonElements.listClass); | |
| generateArrayLiteral(node); | |
| } | |
| void generateArrayLiteral(HLiteralList node) { | |
| List<js.Expression> elements = node.inputs.map((HInstruction input) { | |
| use(input); | |
| return pop(); | |
| }).toList(); | |
| push(new js.ArrayInitializer(elements) | |
| .withSourceInformation(node.sourceInformation)); | |
| } | |
| void visitIndex(HIndex node) { | |
| use(node.receiver); | |
| js.Expression receiver = pop(); | |
| use(node.index); | |
| push(new js.PropertyAccess(receiver, pop()) | |
| .withSourceInformation(node.sourceInformation)); | |
| } | |
| void visitIndexAssign(HIndexAssign node) { | |
| use(node.receiver); | |
| js.Expression receiver = pop(); | |
| use(node.index); | |
| js.Expression index = pop(); | |
| use(node.value); | |
| push(new js.Assignment(new js.PropertyAccess(receiver, index), pop()) | |
| .withSourceInformation(node.sourceInformation)); | |
| } | |
| void checkInt(HInstruction input, String cmp) { | |
| use(input); | |
| js.Expression left = pop(); | |
| use(input); | |
| js.Expression or0 = new js.Binary("|", pop(), new js.LiteralNumber("0")); | |
| push(new js.Binary(cmp, left, or0)); | |
| } | |
| void checkBigInt( | |
| HInstruction input, String cmp, SourceInformation sourceInformation) { | |
| use(input); | |
| js.Expression left = pop(); | |
| use(input); | |
| js.Expression right = pop(); | |
| // TODO(4984): Deal with infinity and -0.0. | |
| js.Expression mathFloorCall = js.js('Math.floor(#)', | |
| <js.Expression>[left]).withSourceInformation(sourceInformation); | |
| push(js.js('# $cmp #', <js.Expression>[ | |
| mathFloorCall, | |
| right | |
| ]).withSourceInformation(sourceInformation)); | |
| } | |
| void checkTypeOf(HInstruction input, String cmp, String typeName, | |
| SourceInformation sourceInformation) { | |
| use(input); | |
| js.Expression typeOf = new js.Prefix("typeof", pop()); | |
| push(new js.Binary(cmp, typeOf, js.string(typeName)) | |
| .withSourceInformation(sourceInformation)); | |
| } | |
| void checkNum( | |
| HInstruction input, String cmp, SourceInformation sourceInformation) { | |
| return checkTypeOf(input, cmp, 'number', sourceInformation); | |
| } | |
| void checkDouble( | |
| HInstruction input, String cmp, SourceInformation sourceInformation) { | |
| return checkNum(input, cmp, sourceInformation); | |
| } | |
| void checkString( | |
| HInstruction input, String cmp, SourceInformation sourceInformation) { | |
| return checkTypeOf(input, cmp, 'string', sourceInformation); | |
| } | |
| void checkBool( | |
| HInstruction input, String cmp, SourceInformation sourceInformation) { | |
| return checkTypeOf(input, cmp, 'boolean', sourceInformation); | |
| } | |
| void checkObject( | |
| HInstruction input, String cmp, SourceInformation sourceInformation) { | |
| assert(NullConstantValue.JsNull == 'null'); | |
| if (cmp == "===") { | |
| checkTypeOf(input, '===', 'object', sourceInformation); | |
| js.Expression left = pop(); | |
| use(input); | |
| js.Expression notNull = new js.Binary("!==", pop(), new js.LiteralNull()); | |
| push(new js.Binary("&&", left, notNull) | |
| .withSourceInformation(sourceInformation)); | |
| } else { | |
| assert(cmp == "!=="); | |
| checkTypeOf(input, '!==', 'object', sourceInformation); | |
| js.Expression left = pop(); | |
| use(input); | |
| js.Expression eqNull = new js.Binary("===", pop(), new js.LiteralNull()); | |
| push(new js.Binary("||", left, eqNull) | |
| .withSourceInformation(sourceInformation)); | |
| } | |
| } | |
| void checkArray(HInstruction input, String cmp) { | |
| use(input); | |
| js.PropertyAccess constructor = | |
| new js.PropertyAccess.field(pop(), 'constructor'); | |
| push(new js.Binary(cmp, constructor, new js.VariableUse('Array'))); | |
| } | |
| void checkFieldExists(HInstruction input, String fieldName) { | |
| use(input); | |
| js.PropertyAccess field = new js.PropertyAccess.field(pop(), fieldName); | |
| // Double negate to boolify the result. | |
| push(new js.Prefix('!', new js.Prefix('!', field))); | |
| } | |
| void checkFieldDoesNotExist(HInstruction input, String fieldName) { | |
| use(input); | |
| js.PropertyAccess field = new js.PropertyAccess.field(pop(), fieldName); | |
| push(new js.Prefix('!', field)); | |
| } | |
| void checkImmutableArray(HInstruction input) { | |
| checkFieldExists(input, 'immutable\$list'); | |
| } | |
| void checkMutableArray(HInstruction input) { | |
| checkFieldDoesNotExist(input, 'immutable\$list'); | |
| } | |
| void checkExtendableArray(HInstruction input) { | |
| checkFieldDoesNotExist(input, 'fixed\$length'); | |
| } | |
| void checkFixedArray(HInstruction input) { | |
| checkFieldExists(input, 'fixed\$length'); | |
| } | |
| void checkNull(HInstruction input) { | |
| use(input); | |
| push(new js.Binary('==', pop(), new js.LiteralNull())); | |
| } | |
| void checkNonNull(HInstruction input) { | |
| use(input); | |
| push(new js.Binary('!=', pop(), new js.LiteralNull())); | |
| } | |
| void checkType(HInstruction input, HInstruction interceptor, DartType type, | |
| SourceInformation sourceInformation, | |
| {bool negative: false}) { | |
| if (type.isInterfaceType) { | |
| InterfaceType interfaceType = type; | |
| ClassEntity element = interfaceType.element; | |
| if (element == _commonElements.jsArrayClass) { | |
| checkArray(input, negative ? '!==' : '==='); | |
| return; | |
| } else if (element == _commonElements.jsMutableArrayClass) { | |
| if (negative) { | |
| checkImmutableArray(input); | |
| } else { | |
| checkMutableArray(input); | |
| } | |
| return; | |
| } else if (element == _commonElements.jsExtendableArrayClass) { | |
| if (negative) { | |
| checkFixedArray(input); | |
| } else { | |
| checkExtendableArray(input); | |
| } | |
| return; | |
| } else if (element == _commonElements.jsFixedArrayClass) { | |
| if (negative) { | |
| checkExtendableArray(input); | |
| } else { | |
| checkFixedArray(input); | |
| } | |
| return; | |
| } else if (element == _commonElements.jsUnmodifiableArrayClass) { | |
| if (negative) { | |
| checkMutableArray(input); | |
| } else { | |
| checkImmutableArray(input); | |
| } | |
| return; | |
| } | |
| } | |
| if (interceptor != null) { | |
| checkTypeViaProperty(interceptor, type, sourceInformation, | |
| negative: negative); | |
| } else { | |
| checkTypeViaProperty(input, type, sourceInformation, negative: negative); | |
| } | |
| } | |
| void checkTypeViaProperty( | |
| HInstruction input, DartType type, SourceInformation sourceInformation, | |
| {bool negative: false}) { | |
| _registry.registerTypeUse(new TypeUse.isCheck(type)); | |
| use(input); | |
| js.PropertyAccess field = | |
| new js.PropertyAccess(pop(), _namer.operatorIsType(type)) | |
| .withSourceInformation(sourceInformation); | |
| // We always negate at least once so that the result is boolified. | |
| push(new js.Prefix('!', field).withSourceInformation(sourceInformation)); | |
| // If the result is not negated, put another '!' in front. | |
| if (!negative) { | |
| push(new js.Prefix('!', pop()).withSourceInformation(sourceInformation)); | |
| } | |
| } | |
| void checkTypeViaInstanceof(HInstruction input, InterfaceType type, | |
| SourceInformation sourceInformation, | |
| {bool negative: false}) { | |
| _registry.registerTypeUse(new TypeUse.instanceConstructor(type)); | |
| use(input); | |
| js.Expression jsClassReference = _emitter.constructorAccess(type.element); | |
| push(js.js('# instanceof #', | |
| [pop(), jsClassReference]).withSourceInformation(sourceInformation)); | |
| if (negative) { | |
| push(new js.Prefix('!', pop()).withSourceInformation(sourceInformation)); | |
| } | |
| } | |
| void handleNumberOrStringSupertypeCheck( | |
| HInstruction input, | |
| HInstruction interceptor, | |
| InterfaceType type, | |
| SourceInformation sourceInformation, | |
| {bool negative: false}) { | |
| assert(!identical(type.element, _commonElements.listClass) && | |
| !_commonElements.isListSupertype(type.element) && | |
| !_commonElements.isStringOnlySupertype(type.element)); | |
| String relation = negative ? '!==' : '==='; | |
| checkNum(input, relation, sourceInformation); | |
| js.Expression numberTest = pop(); | |
| checkString(input, relation, sourceInformation); | |
| js.Expression stringTest = pop(); | |
| checkObject(input, relation, sourceInformation); | |
| js.Expression objectTest = pop(); | |
| checkType(input, interceptor, type, sourceInformation, negative: negative); | |
| String combiner = negative ? '&&' : '||'; | |
| String combiner2 = negative ? '||' : '&&'; | |
| push(new js.Binary( | |
| combiner, | |
| new js.Binary(combiner, numberTest, stringTest) | |
| .withSourceInformation(sourceInformation), | |
| new js.Binary(combiner2, objectTest, pop()) | |
| .withSourceInformation(sourceInformation)) | |
| .withSourceInformation(sourceInformation)); | |
| } | |
| void handleStringSupertypeCheck(HInstruction input, HInstruction interceptor, | |
| InterfaceType type, SourceInformation sourceInformation, | |
| {bool negative: false}) { | |
| assert(!identical(type.element, _commonElements.listClass) && | |
| !_commonElements.isListSupertype(type.element) && | |
| !_commonElements.isNumberOrStringSupertype(type.element)); | |
| String relation = negative ? '!==' : '==='; | |
| checkString(input, relation, sourceInformation); | |
| js.Expression stringTest = pop(); | |
| checkObject(input, relation, sourceInformation); | |
| js.Expression objectTest = pop(); | |
| checkType(input, interceptor, type, sourceInformation, negative: negative); | |
| String combiner = negative ? '||' : '&&'; | |
| push(new js.Binary(negative ? '&&' : '||', stringTest, | |
| new js.Binary(combiner, objectTest, pop()))); | |
| } | |
| void handleListOrSupertypeCheck(HInstruction input, HInstruction interceptor, | |
| InterfaceType type, SourceInformation sourceInformation, | |
| {bool negative: false}) { | |
| assert(!identical(type.element, _commonElements.stringClass) && | |
| !_commonElements.isStringOnlySupertype(type.element) && | |
| !_commonElements.isNumberOrStringSupertype(type.element)); | |
| String relation = negative ? '!==' : '==='; | |
| checkObject(input, relation, sourceInformation); | |
| js.Expression objectTest = pop(); | |
| checkArray(input, relation); | |
| js.Expression arrayTest = pop(); | |
| checkType(input, interceptor, type, sourceInformation, negative: negative); | |
| String combiner = negative ? '&&' : '||'; | |
| push(new js.Binary(negative ? '||' : '&&', objectTest, | |
| new js.Binary(combiner, arrayTest, pop())) | |
| .withSourceInformation(sourceInformation)); | |
| } | |
| void visitIs(HIs node) { | |
| emitIs(node, "===", node.sourceInformation); | |
| } | |
| void visitIsViaInterceptor(HIsViaInterceptor node) { | |
| emitIsViaInterceptor(node, node.sourceInformation, negative: false); | |
| } | |
| void emitIs(HIs node, String relation, SourceInformation sourceInformation) { | |
| DartType type = node.typeExpression; | |
| _registry.registerTypeUse(new TypeUse.isCheck(type)); | |
| HInstruction input = node.expression; | |
| // If this is changed to single == there are several places below that must | |
| // be changed to match. | |
| assert(relation == '===' || relation == '!=='); | |
| bool negative = relation == '!=='; | |
| if (node.isVariableCheck || node.isCompoundCheck) { | |
| use(node.checkCall); | |
| if (negative) push(new js.Prefix('!', pop())); | |
| } else { | |
| assert(node.isRawCheck); | |
| HInstruction interceptor = node.interceptor; | |
| InterfaceType interfaceType = type; | |
| ClassEntity element = interfaceType.element; | |
| if (element == _commonElements.nullClass) { | |
| if (negative) { | |
| checkNonNull(input); | |
| } else { | |
| checkNull(input); | |
| } | |
| } else if (element == | |
| _commonElements.objectClass /* || type.treatAsDynamic*/) { | |
| // The constant folder also does this optimization, but we make | |
| // it safe by assuming it may have not run. | |
| push(newLiteralBool(!negative, sourceInformation)); | |
| } else if (element == _commonElements.stringClass) { | |
| checkString(input, relation, sourceInformation); | |
| } else if (element == _commonElements.doubleClass) { | |
| checkDouble(input, relation, sourceInformation); | |
| } else if (element == _commonElements.numClass) { | |
| checkNum(input, relation, sourceInformation); | |
| } else if (element == _commonElements.boolClass) { | |
| checkBool(input, relation, sourceInformation); | |
| } else if (element == _commonElements.intClass) { | |
| // The is check in the code tells us that it might not be an | |
| // int. So we do a typeof first to avoid possible | |
| // deoptimizations on the JS engine due to the Math.floor check. | |
| checkNum(input, relation, sourceInformation); | |
| js.Expression numTest = pop(); | |
| checkBigInt(input, relation, sourceInformation); | |
| push(new js.Binary(negative ? '||' : '&&', numTest, pop()) | |
| .withSourceInformation(sourceInformation)); | |
| } else if (node.useInstanceOf) { | |
| assert(interceptor == null); | |
| checkTypeViaInstanceof(input, type, sourceInformation, | |
| negative: negative); | |
| } else if (_commonElements.isNumberOrStringSupertype(element)) { | |
| handleNumberOrStringSupertypeCheck( | |
| input, interceptor, type, sourceInformation, | |
| negative: negative); | |
| } else if (_commonElements.isStringOnlySupertype(element)) { | |
| handleStringSupertypeCheck(input, interceptor, type, sourceInformation, | |
| negative: negative); | |
| } else if (element == _commonElements.listClass || | |
| _commonElements.isListSupertype(element)) { | |
| handleListOrSupertypeCheck(input, interceptor, type, sourceInformation, | |
| negative: negative); | |
| } else if (type.isFunctionType) { | |
| checkType(input, interceptor, type, sourceInformation, | |
| negative: negative); | |
| } else if ((input.canBePrimitive(_abstractValueDomain) && | |
| !input.canBePrimitiveArray(_abstractValueDomain)) || | |
| input.canBeNull(_abstractValueDomain)) { | |
| checkObject(input, relation, node.sourceInformation); | |
| js.Expression objectTest = pop(); | |
| checkType(input, interceptor, type, sourceInformation, | |
| negative: negative); | |
| push(new js.Binary(negative ? '||' : '&&', objectTest, pop()) | |
| .withSourceInformation(sourceInformation)); | |
| } else { | |
| checkType(input, interceptor, type, sourceInformation, | |
| negative: negative); | |
| } | |
| } | |
| } | |
| void emitIsViaInterceptor( | |
| HIsViaInterceptor node, SourceInformation sourceInformation, | |
| {bool negative: false}) { | |
| checkTypeViaProperty( | |
| node.interceptor, node.typeExpression, sourceInformation, | |
| negative: negative); | |
| } | |
| js.Expression generateReceiverOrArgumentTypeTest(HTypeConversion node) { | |
| HInstruction input = node.checkedInput; | |
| AbstractValue inputType = node.inputType ?? input.instructionType; | |
| AbstractValue checkedType = node.checkedType; | |
| // This path is no longer used for indexable primitive types. | |
| assert(!_abstractValueDomain.isJsIndexable(checkedType)); | |
| // Figure out if it is beneficial to use a null check. V8 generally prefers | |
| // 'typeof' checks, but for integers we cannot compile this test into a | |
| // single typeof check so the null check is cheaper. | |
| bool isIntCheck = _abstractValueDomain.isIntegerOrNull(checkedType); | |
| bool turnIntoNumCheck = | |
| isIntCheck && _abstractValueDomain.isIntegerOrNull(inputType); | |
| bool turnIntoNullCheck = !turnIntoNumCheck && | |
| (_abstractValueDomain.includeNull(checkedType) == inputType) && | |
| isIntCheck; | |
| if (turnIntoNullCheck) { | |
| use(input); | |
| return new js.Binary("==", pop(), new js.LiteralNull()) | |
| .withSourceInformation(input.sourceInformation); | |
| } else if (isIntCheck && !turnIntoNumCheck) { | |
| // input is !int | |
| checkBigInt(input, '!==', input.sourceInformation); | |
| return pop(); | |
| } else if (turnIntoNumCheck || | |
| _abstractValueDomain.isNumberOrNull(checkedType)) { | |
| // input is !num | |
| checkNum(input, '!==', input.sourceInformation); | |
| return pop(); | |
| } else if (_abstractValueDomain.isBooleanOrNull(checkedType)) { | |
| // input is !bool | |
| checkBool(input, '!==', input.sourceInformation); | |
| return pop(); | |
| } else if (_abstractValueDomain.isStringOrNull(checkedType)) { | |
| // input is !string | |
| checkString(input, '!==', input.sourceInformation); | |
| return pop(); | |
| } | |
| throw failedAt(input, 'Unexpected check: $checkedType.'); | |
| } | |
| void visitTypeConversion(HTypeConversion node) { | |
| if (node.isArgumentTypeCheck || node.isReceiverTypeCheck) { | |
| js.Expression test = generateReceiverOrArgumentTypeTest(node); | |
| js.Block oldContainer = currentContainer; | |
| js.Statement body = new js.Block.empty(); | |
| currentContainer = body; | |
| if (node.isArgumentTypeCheck) { | |
| generateThrowWithHelper( | |
| _commonElements.throwIllegalArgumentException, node.checkedInput, | |
| sourceInformation: node.sourceInformation); | |
| } else if (node.isReceiverTypeCheck) { | |
| use(node.checkedInput); | |
| js.Name methodName = | |
| _namer.invocationName(node.receiverTypeCheckSelector); | |
| js.Expression call = js.propertyCall(pop(), methodName, | |
| []).withSourceInformation(node.sourceInformation); | |
| pushStatement( | |
| new js.Return(call).withSourceInformation(node.sourceInformation)); | |
| } | |
| currentContainer = oldContainer; | |
| body = unwrapStatement(body); | |
| pushStatement(new js.If.noElse(test, body) | |
| .withSourceInformation(node.sourceInformation)); | |
| return; | |
| } | |
| assert(node.isCheckedModeCheck || node.isCastTypeCheck); | |
| DartType type = node.typeExpression; | |
| assert(!type.isTypedef); | |
| assert(!type.isDynamic); | |
| assert(!type.isVoid); | |
| if (type.isFunctionType) { | |
| // TODO(5022): We currently generate $isFunction checks for | |
| // function types. | |
| _registry | |
| .registerTypeUse(new TypeUse.isCheck(_commonElements.functionType)); | |
| } | |
| _registry.registerTypeUse(new TypeUse.isCheck(type)); | |
| CheckedModeHelper helper; | |
| if (node.isBooleanConversionCheck) { | |
| helper = const CheckedModeHelper('boolConversionCheck'); | |
| } else { | |
| helper = _checkedModeHelpers.getCheckedModeHelper( | |
| type, _closedWorld.commonElements, | |
| typeCast: node.isCastTypeCheck); | |
| } | |
| StaticUse staticUse = helper.getStaticUse(_closedWorld.commonElements); | |
| _registry.registerStaticUse(staticUse); | |
| List<js.Expression> arguments = <js.Expression>[]; | |
| use(node.checkedInput); | |
| arguments.add(pop()); | |
| helper.generateAdditionalArguments(this, _namer, node, arguments); | |
| push( | |
| new js.Call(_emitter.staticFunctionAccess(staticUse.element), arguments) | |
| .withSourceInformation(node.sourceInformation)); | |
| } | |
| void visitTypeKnown(HTypeKnown node) { | |
| // [HTypeKnown] instructions are removed before generating code. | |
| assert(false); | |
| } | |
| void visitTypeInfoReadRaw(HTypeInfoReadRaw node) { | |
| use(node.inputs[0]); | |
| js.Expression receiver = pop(); | |
| push(js.js(r'#.#', [receiver, _namer.rtiFieldJsName])); | |
| } | |
| void visitTypeInfoReadVariable(HTypeInfoReadVariable node) { | |
| TypeVariableEntity element = node.variable.element; | |
| int index = element.index; | |
| js.Expression interceptor; | |
| if (node.isIntercepted) { | |
| use(node.interceptor); | |
| interceptor = pop(); | |
| } | |
| HInstruction object = node.object; | |
| use(object); | |
| js.Expression receiver = pop(); | |
| if (typeVariableAccessNeedsSubstitution(element, object.instructionType)) { | |
| js.Expression typeName = | |
| js.quoteName(_namer.runtimeTypeName(element.typeDeclaration)); | |
| if (node.isIntercepted) { | |
| FunctionEntity helperElement = | |
| _commonElements.getRuntimeTypeArgumentIntercepted; | |
| _registry.registerStaticUse( | |
| new StaticUse.staticInvoke(helperElement, CallStructure.FOUR_ARGS)); | |
| js.Expression helper = _emitter.staticFunctionAccess(helperElement); | |
| push(js.js(r'#(#, #, #, #)', [ | |
| helper, | |
| interceptor, | |
| receiver, | |
| typeName, | |
| js.js.number(index) | |
| ]).withSourceInformation(node.sourceInformation)); | |
| } else { | |
| FunctionEntity helperElement = _commonElements.getRuntimeTypeArgument; | |
| _registry.registerStaticUse(new StaticUse.staticInvoke( | |
| helperElement, CallStructure.THREE_ARGS)); | |
| js.Expression helper = _emitter.staticFunctionAccess(helperElement); | |
| push(js.js(r'#(#, #, #)', [ | |
| helper, | |
| receiver, | |
| typeName, | |
| js.js.number(index) | |
| ]).withSourceInformation(node.sourceInformation)); | |
| } | |
| } else { | |
| FunctionEntity helperElement = _commonElements.getTypeArgumentByIndex; | |
| _registry.registerStaticUse( | |
| new StaticUse.staticInvoke(helperElement, CallStructure.TWO_ARGS)); | |
| js.Expression helper = _emitter.staticFunctionAccess(helperElement); | |
| push(js.js(r'#(#, #)', [ | |
| helper, | |
| receiver, | |
| js.js.number(index) | |
| ]).withSourceInformation(node.sourceInformation)); | |
| } | |
| } | |
| void visitTypeInfoExpression(HTypeInfoExpression node) { | |
| DartType type = node.dartType; | |
| if (node.isTypeVariableReplacement) { | |
| _registry.registerTypeUse(new TypeUse.typeArgument(type)); | |
| } | |
| List<js.Expression> arguments = <js.Expression>[]; | |
| for (HInstruction input in node.inputs) { | |
| use(input); | |
| arguments.add(pop()); | |
| } | |
| switch (node.kind) { | |
| case TypeInfoExpressionKind.COMPLETE: | |
| int index = 0; | |
| js.Expression result = _rtiEncoder.getTypeRepresentation( | |
| _emitter.emitter, | |
| type, | |
| (TypeVariableType variable) => arguments[index++]); | |
| assert( | |
| index == node.inputs.length, | |
| "Not all input is read for type ${type}: " | |
| "$index of ${node.inputs}."); | |
| push(result); | |
| return; | |
| case TypeInfoExpressionKind.INSTANCE: | |
| // We expect only flat types for the INSTANCE representation. | |
| assert( | |
| (type as InterfaceType).typeArguments.length == arguments.length); | |
| _registry.registerInstantiatedClass(_commonElements.listClass); | |
| push(new js.ArrayInitializer(arguments) | |
| .withSourceInformation(node.sourceInformation)); | |
| } | |
| } | |
| bool typeVariableAccessNeedsSubstitution( | |
| TypeVariableEntity element, AbstractValue receiverMask) { | |
| ClassEntity cls = element.typeDeclaration; | |
| // See if the receiver type narrows the set of classes to ones that can be | |
| // indexed. | |
| // TODO(sra): Currently the only convenient query is [singleClass]. We | |
| // should iterate over all the concrete classes in [receiverMask]. | |
| ClassEntity receiverClass = | |
| _abstractValueDomain.getExactClass(receiverMask); | |
| if (receiverClass != null) { | |
| if (_rtiSubstitutions.isTrivialSubstitution(receiverClass, cls)) { | |
| return false; | |
| } | |
| } | |
| if (_closedWorld.isUsedAsMixin(cls)) return true; | |
| return _closedWorld.classHierarchy.anyStrictSubclassOf(cls, | |
| (ClassEntity subclass) { | |
| return !_rtiSubstitutions.isTrivialSubstitution(subclass, cls); | |
| }); | |
| } | |
| @override | |
| void visitRef(HRef node) { | |
| visit(node.value); | |
| } | |
| } |