Linter Demo Errors: 5Warnings: 259File: /home/fstrocco/Dart/dart/benchmark/compiler/lib/src/ssa/builder.dart // 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. part of ssa; class SsaFunctionCompiler implements FunctionCompiler { SsaCodeGeneratorTask generator; SsaBuilderTask builder; SsaOptimizerTask optimizer; SsaFunctionCompiler(JavaScriptBackend backend, bool generateSourceMap) : generator = new SsaCodeGeneratorTask(backend), builder = new SsaBuilderTask(backend, generateSourceMap), optimizer = new SsaOptimizerTask(backend); /// Generates JavaScript code for `work.element`. /// Using the ssa builder, optimizer and codegenerator. js.Fun compile(CodegenWorkItem work) { HGraph graph = builder.build(work); optimizer.optimize(work, graph); Element element = work.element; js.Expression result = generator.generateCode(work, graph); if (element is FunctionElement) { JavaScriptBackend backend = builder.backend; AsyncRewriterBase rewriter = null; String name = backend.namer.methodPropertyName(element); if (element.asyncMarker == AsyncMarker.ASYNC) { rewriter = new AsyncRewriter( backend.compiler, backend.compiler.currentElement, asyncHelper: backend.emitter.staticFunctionAccess(backend.getAsyncHelper()), newCompleter: backend.emitter.staticFunctionAccess( backend.getCompleterConstructor()), safeVariableName: backend.namer.safeVariableName, bodyName: name); } else if (element.asyncMarker == AsyncMarker.SYNC_STAR) { rewriter = new SyncStarRewriter( backend.compiler, backend.compiler.currentElement, endOfIteration: backend.emitter.staticFunctionAccess( backend.getEndOfIteration()), newIterable: backend.emitter.staticFunctionAccess( backend.getSyncStarIterableConstructor()), yieldStarExpression: backend.emitter.staticFunctionAccess( backend.getYieldStar()), uncaughtErrorExpression: backend.emitter.staticFunctionAccess( backend.getSyncStarUncaughtError()), safeVariableName: backend.namer.safeVariableName, bodyName: name); } else if (element.asyncMarker == AsyncMarker.ASYNC_STAR) { rewriter = new AsyncStarRewriter( backend.compiler, backend.compiler.currentElement, asyncStarHelper: backend.emitter.staticFunctionAccess( backend.getAsyncStarHelper()), streamOfController: backend.emitter.staticFunctionAccess( backend.getStreamOfController()), newController: backend.emitter.staticFunctionAccess( backend.getASyncStarControllerConstructor()), safeVariableName: backend.namer.safeVariableName, yieldExpression: backend.emitter.staticFunctionAccess( backend.getYieldSingle()), yieldStarExpression: backend.emitter.staticFunctionAccess( backend.getYieldStar()), bodyName: name); } if (rewriter != null) { result = rewriter.rewrite(result); } } return result; } Iterable get tasks { return [builder, optimizer, generator]; } } /// A synthetic local variable only used with the SSA graph. /// /// For instance used for holding return value of function or the exception of a /// try-catch statement. class SyntheticLocal extends Local { final String name; final ExecutableElement executableContext; SyntheticLocal(this.name, this.executableContext); } class SsaBuilderTask extends CompilerTask { final CodeEmitterTask emitter; final JavaScriptBackend backend; final bool generateSourceMap; String get name => 'SSA builder'; SsaBuilderTask(JavaScriptBackend backend, this.generateSourceMap) : emitter = backend.emitter, backend = backend, super(backend.compiler); HGraph build(CodegenWorkItem work) { return measure(() { Element element = work.element.implementation; return compiler.withCurrentElement(element, () { HInstruction.idCounter = 0; SsaBuilder builder = new SsaBuilder( backend, work, emitter.nativeEmitter, generateSourceMap); HGraph graph; ElementKind kind = element.kind; if (kind == ElementKind.GENERATIVE_CONSTRUCTOR) { graph = compileConstructor(builder, work); } else if (kind == ElementKind.GENERATIVE_CONSTRUCTOR_BODY || kind == ElementKind.FUNCTION || kind == ElementKind.GETTER || kind == ElementKind.SETTER) { graph = builder.buildMethod(element); } else if (kind == ElementKind.FIELD) { if (element.isInstanceMember) { assert(compiler.enableTypeAssertions); graph = builder.buildCheckedSetter(element); } else { graph = builder.buildLazyInitializer(element); } } else { compiler.internalError(element, 'Unexpected element kind $kind.'); } assert(graph.isValid()); if (!identical(kind, ElementKind.FIELD)) { FunctionElement function = element; FunctionSignature signature = function.functionSignature; signature.forEachOptionalParameter((ParameterElement parameter) { // This ensures the default value will be computed. ConstantValue constant = backend.constants.getConstantForVariable(parameter).value; CodegenRegistry registry = work.registry; registry.registerCompileTimeConstant(constant); }); } if (compiler.tracer.isEnabled) { String name; if (element.isClassMember) { String className = element.enclosingClass.name; String memberName = element.name; name = "$className.$memberName"; if (element.isGenerativeConstructorBody) { name = "$name (body)"; } } else { name = "${element.name}"; } compiler.tracer.traceCompilation( name, work.compilationContext); compiler.tracer.traceGraph('builder', graph); } return graph; }); }); } HGraph compileConstructor(SsaBuilder builder, CodegenWorkItem work) { return builder.buildFactory(work.element); } } /** * Keeps track of locals (including parameters and phis) when building. The * 'this' reference is treated as parameter and hence handled by this class, * too. */ class LocalsHandler { /** * The values of locals that can be directly accessed (without redirections * to boxes or closure-fields). * * [directLocals] is iterated, so it is "insertion ordered" to make the * iteration order a function only of insertions and not a function of * e.g. Element hash codes. I'd prefer to use a SortedMap but some elements * don't have source locations for [Elements.compareByPosition]. */ Map directLocals = new Map(); Map redirectionMapping = new Map(); SsaBuilder builder; ClosureClassMap closureData; Map typeVariableLocals = new Map(); final ExecutableElement executableContext; /// The class that defines the current type environment or null if no type /// variables are in scope. ClassElement get contextClass => executableContext.contextClass; LocalsHandler(this.builder, this.executableContext); /// Substituted type variables occurring in [type] into the context of /// [contextClass]. DartType substInContext(DartType type) { if (contextClass != null) { ClassElement typeContext = Types.getClassContext(type); if (typeContext != null) { type = type.substByContext( contextClass.asInstanceOf(typeContext)); } } return type; } get typesTask => builder.compiler.typesTask; /** * Creates a new [LocalsHandler] based on [other]. We only need to * copy the [directLocals], since the other fields can be shared * throughout the AST visit. */ LocalsHandler.from(LocalsHandler other) : directLocals = new Map.from(other.directLocals), redirectionMapping = other.redirectionMapping, executableContext = other.executableContext, builder = other.builder, closureData = other.closureData; /** * Redirects accesses from element [from] to element [to]. The [to] element * must be a boxed variable or a variable that is stored in a closure-field. */ void redirectElement(Local from, CapturedVariable to) { assert(redirectionMapping[from] == null); redirectionMapping[from] = to; assert(isStoredInClosureField(from) || isBoxed(from)); } HInstruction createBox() { // TODO(floitsch): Clean up this hack. Should we create a box-object by // just creating an empty object literal? JavaScriptBackend backend = builder.backend; HInstruction box = new HForeignCode(js.js.parseForeignJS('{}'), backend.nonNullType, []); builder.add(box); return box; } /** * If the scope (function or loop) [node] has captured variables then this * method creates a box and sets up the redirections. */ void enterScope(ast.Node node, Element element) { // See if any variable in the top-scope of the function is captured. If yes // we need to create a box-object. ClosureScope scopeData = closureData.capturingScopes[node]; if (scopeData == null) return; HInstruction box; // The scope has captured variables. if (element != null && element.isGenerativeConstructorBody) { // The box is passed as a parameter to a generative // constructor body. JavaScriptBackend backend = builder.backend; box = builder.addParameter(scopeData.boxElement, backend.nonNullType); } else { box = createBox(); } // Add the box to the known locals. directLocals[scopeData.boxElement] = box; // Make sure that accesses to the boxed locals go into the box. We also // need to make sure that parameters are copied into the box if necessary. scopeData.forEachCapturedVariable( (LocalVariableElement from, BoxFieldElement to) { // The [from] can only be a parameter for function-scopes and not // loop scopes. if (from.isParameter && !element.isGenerativeConstructorBody) { // Now that the redirection is set up, the update to the local will // write the parameter value into the box. // Store the captured parameter in the box. Get the current value // before we put the redirection in place. // We don't need to update the local for a generative // constructor body, because it receives a box that already // contains the updates as the last parameter. HInstruction instruction = readLocal(from); redirectElement(from, to); updateLocal(from, instruction); } else { redirectElement(from, to); } }); } /** * Replaces the current box with a new box and copies over the given list * of elements from the old box into the new box. */ void updateCaptureBox(BoxLocal boxElement, List toBeCopiedElements) { // Create a new box and copy over the values from the old box into the // new one. HInstruction oldBox = readLocal(boxElement); HInstruction newBox = createBox(); for (LocalVariableElement boxedVariable in toBeCopiedElements) { // [readLocal] uses the [boxElement] to find its box. By replacing it // behind its back we can still get to the old values. updateLocal(boxElement, oldBox); HInstruction oldValue = readLocal(boxedVariable); updateLocal(boxElement, newBox); updateLocal(boxedVariable, oldValue); } updateLocal(boxElement, newBox); } /** * Documentation wanted -- johnniwinther * * Invariant: [function] must be an implementation element. */ void startFunction(Element element, ast.Node node) { assert(invariant(element, element.isImplementation)); Compiler compiler = builder.compiler; closureData = compiler.closureToClassMapper.computeClosureToClassMapping( element, node, builder.elements); if (element is FunctionElement) { FunctionElement functionElement = element; FunctionSignature params = functionElement.functionSignature; ClosureScope scopeData = closureData.capturingScopes[node]; params.orderedForEachParameter((ParameterElement parameterElement) { if (element.isGenerativeConstructorBody) { if (scopeData != null && scopeData.isCapturedVariable(parameterElement)) { // The parameter will be a field in the box passed as the // last parameter. So no need to have it. return; } } HInstruction parameter = builder.addParameter( parameterElement, TypeMaskFactory.inferredTypeForElement(parameterElement, compiler)); builder.parameters[parameterElement] = parameter; directLocals[parameterElement] = parameter; }); } enterScope(node, element); // If the freeVariableMapping is not empty, then this function was a // nested closure that captures variables. Redirect the captured // variables to fields in the closure. closureData.forEachFreeVariable((Local from, CapturedVariable to) { redirectElement(from, to); }); JavaScriptBackend backend = compiler.backend; if (closureData.isClosure) { // Inside closure redirect references to itself to [:this:]. HThis thisInstruction = new HThis(closureData.thisLocal, backend.nonNullType); builder.graph.thisInstruction = thisInstruction; builder.graph.entry.addAtEntry(thisInstruction); updateLocal(closureData.closureElement, thisInstruction); } else if (element.isInstanceMember) { // Once closures have been mapped to classes their instance members might // not have any thisElement if the closure was created inside a static // context. HThis thisInstruction = new HThis( closureData.thisLocal, builder.getTypeOfThis()); builder.graph.thisInstruction = thisInstruction; builder.graph.entry.addAtEntry(thisInstruction); directLocals[closureData.thisLocal] = thisInstruction; } // If this method is an intercepted method, add the extra // parameter to it, that is the actual receiver for intercepted // classes, or the same as [:this:] for non-intercepted classes. ClassElement cls = element.enclosingClass; // When the class extends a native class, the instance is pre-constructed // and passed to the generative constructor factory function as a parameter. // Instead of allocating and initializing the object, the constructor // 'upgrades' the native subclass object by initializing the Dart fields. bool isNativeUpgradeFactory = element.isGenerativeConstructor && Elements.isNativeOrExtendsNative(cls); if (backend.isInterceptedMethod(element)) { bool isInterceptorClass = backend.isInterceptorClass(cls.declaration); String name = isInterceptorClass ? 'receiver' : '_'; SyntheticLocal parameter = new SyntheticLocal(name, executableContext); HParameterValue value = new HParameterValue(parameter, builder.getTypeOfThis()); builder.graph.explicitReceiverParameter = value; builder.graph.entry.addAfter( directLocals[closureData.thisLocal], value); if (isInterceptorClass) { // Only use the extra parameter in intercepted classes. directLocals[closureData.thisLocal] = value; } } else if (isNativeUpgradeFactory) { SyntheticLocal parameter = new SyntheticLocal('receiver', executableContext); // Unlike `this`, receiver is nullable since direct calls to generative // constructor call the constructor with `null`. ClassWorld classWorld = compiler.world; HParameterValue value = new HParameterValue(parameter, new TypeMask.exact(cls, classWorld)); builder.graph.explicitReceiverParameter = value; builder.graph.entry.addAtEntry(value); } } /** * Returns true if the local can be accessed directly. Boxed variables or * captured variables that are stored in the closure-field return [:false:]. */ bool isAccessedDirectly(Local local) { assert(local != null); return !redirectionMapping.containsKey(local) && !closureData.variablesUsedInTryOrGenerator.contains(local); } bool isStoredInClosureField(Local local) { assert(local != null); if (isAccessedDirectly(local)) return false; CapturedVariable redirectTarget = redirectionMapping[local]; if (redirectTarget == null) return false; return redirectTarget is ClosureFieldElement; } bool isBoxed(Local local) { if (isAccessedDirectly(local)) return false; if (isStoredInClosureField(local)) return false; return redirectionMapping.containsKey(local); } bool isUsedInTryOrGenerator(Local local) { return closureData.variablesUsedInTryOrGenerator.contains(local); } /** * Returns an [HInstruction] for the given element. If the element is * boxed or stored in a closure then the method generates code to retrieve * the value. */ HInstruction readLocal(Local local) { if (isAccessedDirectly(local)) { if (directLocals[local] == null) { if (local is TypeVariableElement) { builder.compiler.internalError(builder.compiler.currentElement, "Runtime type information not available for $local."); } else { builder.compiler.internalError(local, "Cannot find value $local."); } } return directLocals[local]; } else if (isStoredInClosureField(local)) { ClosureFieldElement redirect = redirectionMapping[local]; HInstruction receiver = readLocal(closureData.closureElement); TypeMask type = local is BoxLocal ? builder.backend.nonNullType : builder.getTypeOfCapturedVariable(redirect); HInstruction fieldGet = new HFieldGet(redirect, receiver, type); builder.add(fieldGet); return fieldGet; } else if (isBoxed(local)) { BoxFieldElement redirect = redirectionMapping[local]; // In the function that declares the captured variable the box is // accessed as direct local. Inside the nested closure the box is // accessed through a closure-field. // Calling [readLocal] makes sure we generate the correct code to get // the box. HInstruction box = readLocal(redirect.box); HInstruction lookup = new HFieldGet( redirect, box, builder.getTypeOfCapturedVariable(redirect)); builder.add(lookup); return lookup; } else { assert(isUsedInTryOrGenerator(local)); HLocalValue localValue = getLocal(local); HInstruction instruction = new HLocalGet( local, localValue, builder.backend.dynamicType); builder.add(instruction); return instruction; } } HInstruction readThis() { HInstruction res = readLocal(closureData.thisLocal); if (res.instructionType == null) { res.instructionType = builder.getTypeOfThis(); } return res; } HLocalValue getLocal(Local local) { // If the element is a parameter, we already have a // HParameterValue for it. We cannot create another one because // it could then have another name than the real parameter. And // the other one would not know it is just a copy of the real // parameter. if (local is ParameterElement) return builder.parameters[local]; return builder.activationVariables.putIfAbsent(local, () { JavaScriptBackend backend = builder.backend; HLocalValue localValue = new HLocalValue(local, backend.nonNullType); builder.graph.entry.addAtExit(localValue); return localValue; }); } Local getTypeVariableAsLocal(TypeVariableType type) { return typeVariableLocals.putIfAbsent(type, () { return new TypeVariableLocal(type, executableContext); }); } /** * Sets the [element] to [value]. If the element is boxed or stored in a * closure then the method generates code to set the value. */ void updateLocal(Local local, HInstruction value) { assert(!isStoredInClosureField(local)); if (isAccessedDirectly(local)) { directLocals[local] = value; } else if (isBoxed(local)) { BoxFieldElement redirect = redirectionMapping[local]; // The box itself could be captured, or be local. A local variable that // is captured will be boxed, but the box itself will be a local. // Inside the closure the box is stored in a closure-field and cannot // be accessed directly. HInstruction box = readLocal(redirect.box); builder.add(new HFieldSet(redirect, box, value)); } else { assert(isUsedInTryOrGenerator(local)); HLocalValue localValue = getLocal(local); builder.add(new HLocalSet(local, localValue, value)); } } /** * This function, startLoop, must be called before visiting any children of * the loop. In particular it needs to be called before executing the * initializers. * * The [LocalsHandler] will make the boxes and updates at the right moment. * The builder just needs to call [enterLoopBody] and [enterLoopUpdates] * (for [ast.For] loops) at the correct places. For phi-handling * [beginLoopHeader] and [endLoop] must also be called. * * The correct place for the box depends on the given loop. In most cases * the box will be created when entering the loop-body: while, do-while, and * for-in (assuming the call to [:next:] is inside the body) can always be * constructed this way. * * Things are slightly more complicated for [ast.For] loops. If no declared * loop variable is boxed then the loop-body approach works here too. If a * loop-variable is boxed we need to introduce a new box for the * loop-variable before we enter the initializer so that the initializer * writes the values into the box. In any case we need to create the box * before the condition since the condition could box the variable. * Since the first box is created outside the actual loop we have a second * location where a box is created: just before the updates. This is * necessary since updates are considered to be part of the next iteration * (and can again capture variables). * * For example the following Dart code prints 1 3 -- 3 4. * * var fs = []; * for (var i = 0; i < 3; (f() { fs.add(f); print(i); i++; })()) { * i++; * } * print("--"); * for (var i = 0; i < 2; i++) fs[i](); * * We solve this by emitting the following code (only for [ast.For] loops): * <== move the first box creation outside the loop. * ; * loop-entry: * if (!) goto loop-exit; * * // create a new box and copy the captured loop-variables. * * goto loop-entry; * loop-exit: */ void startLoop(ast.Node node) { ClosureScope scopeData = closureData.capturingScopes[node]; if (scopeData == null) return; if (scopeData.hasBoxedLoopVariables()) { // If there are boxed loop variables then we set up the box and // redirections already now. This way the initializer can write its // values into the box. // For other loops the box will be created when entering the body. enterScope(node, null); } } /** * Create phis at the loop entry for local variables (ready for the values * from the back edge). Populate the phis with the current values. */ void beginLoopHeader(HBasicBlock loopEntry) { // Create a copy because we modify the map while iterating over it. Map savedDirectLocals = new Map.from(directLocals); JavaScriptBackend backend = builder.backend; // Create phis for all elements in the definitions environment. savedDirectLocals.forEach((Local local, HInstruction instruction) { if (isAccessedDirectly(local)) { // We know 'this' cannot be modified. if (local != closureData.thisLocal) { HPhi phi = new HPhi.singleInput( local, instruction, backend.dynamicType); loopEntry.addPhi(phi); directLocals[local] = phi; } else { directLocals[local] = instruction; } } }); } void enterLoopBody(ast.Node node) { ClosureScope scopeData = closureData.capturingScopes[node]; if (scopeData == null) return; // If there are no declared boxed loop variables then we did not create the // box before the initializer and we have to create the box now. if (!scopeData.hasBoxedLoopVariables()) { enterScope(node, null); } } void enterLoopUpdates(ast.Node node) { // If there are declared boxed loop variables then the updates might have // access to the box and we must switch to a new box before executing the // updates. // In all other cases a new box will be created when entering the body of // the next iteration. ClosureScope scopeData = closureData.capturingScopes[node]; if (scopeData == null) return; if (scopeData.hasBoxedLoopVariables()) { updateCaptureBox(scopeData.boxElement, scopeData.boxedLoopVariables); } } /** * Goes through the phis created in beginLoopHeader entry and adds the * input from the back edge (from the current value of directLocals) to them. */ void endLoop(HBasicBlock loopEntry) { // If the loop has an aborting body, we don't update the loop // phis. if (loopEntry.predecessors.length == 1) return; loopEntry.forEachPhi((HPhi phi) { Local element = phi.sourceElement; HInstruction postLoopDefinition = directLocals[element]; phi.addInput(postLoopDefinition); }); } /** * Merge [otherLocals] into this locals handler, creating phi-nodes when * there is a conflict. * If a phi node is necessary, it will use this handler's instruction as the * first input, and the otherLocals instruction as the second. */ void mergeWith(LocalsHandler otherLocals, HBasicBlock joinBlock) { // If an element is in one map but not the other we can safely // ignore it. It means that a variable was declared in the // block. Since variable declarations are scoped the declared // variable cannot be alive outside the block. Note: this is only // true for nodes where we do joins. Map joinedLocals = new Map(); JavaScriptBackend backend = builder.backend; otherLocals.directLocals.forEach((Local local, HInstruction instruction) { // We know 'this' cannot be modified. if (local == closureData.thisLocal) { assert(directLocals[local] == instruction); joinedLocals[local] = instruction; } else { HInstruction mine = directLocals[local]; if (mine == null) return; if (identical(instruction, mine)) { joinedLocals[local] = instruction; } else { HInstruction phi = new HPhi.manyInputs( local, [mine, instruction], backend.dynamicType); joinBlock.addPhi(phi); joinedLocals[local] = phi; } } }); directLocals = joinedLocals; } /** * When control flow merges, this method can be used to merge several * localsHandlers into a new one using phis. The new localsHandler is * returned. Unless it is also in the list, the current localsHandler is not * used for its values, only for its declared variables. This is a way to * exclude local values from the result when they are no longer in scope. */ LocalsHandler mergeMultiple(List localsHandlers, HBasicBlock joinBlock) { assert(localsHandlers.length > 0); if (localsHandlers.length == 1) return localsHandlers[0]; Map joinedLocals = new Map(); HInstruction thisValue = null; JavaScriptBackend backend = builder.backend; directLocals.forEach((Local local, HInstruction instruction) { if (local != closureData.thisLocal) { HPhi phi = new HPhi.noInputs(local, backend.dynamicType); joinedLocals[local] = phi; joinBlock.addPhi(phi); } else { // We know that "this" never changes, if it's there. // Save it for later. While merging, there is no phi for "this", // so we don't have to special case it in the merge loop. thisValue = instruction; } }); for (LocalsHandler handler in localsHandlers) { handler.directLocals.forEach((Local local, HInstruction instruction) { HPhi phi = joinedLocals[local]; if (phi != null) { phi.addInput(instruction); } }); } if (thisValue != null) { // If there was a "this" for the scope, add it to the new locals. joinedLocals[closureData.thisLocal] = thisValue; } // Remove locals that are not in all handlers. directLocals = new Map(); joinedLocals.forEach((Local local, HInstruction instruction) { if (local != closureData.thisLocal && instruction.inputs.length != localsHandlers.length) { joinBlock.removePhi(instruction); } else { directLocals[local] = instruction; } }); return this; } } // Represents a single break/continue instruction. class JumpHandlerEntry { final HJump jumpInstruction; final LocalsHandler locals; bool isBreak() => jumpInstruction is HBreak; bool isContinue() => jumpInstruction is HContinue; JumpHandlerEntry(this.jumpInstruction, this.locals); } abstract class JumpHandler { factory JumpHandler(SsaBuilder builder, JumpTarget target) { return new TargetJumpHandler(builder, target); } void generateBreak([LabelDefinition label]); void generateContinue([LabelDefinition label]); void forEachBreak(void action(HBreak instruction, LocalsHandler locals)); void forEachContinue(void action(HContinue instruction, LocalsHandler locals)); bool hasAnyContinue(); bool hasAnyBreak(); void close(); final JumpTarget target; List labels(); } // Insert break handler used to avoid null checks when a target isn't // used as the target of a break, and therefore doesn't need a break // handler associated with it. class NullJumpHandler implements JumpHandler { final Compiler compiler; NullJumpHandler(this.compiler); void generateBreak([LabelDefinition label]) { compiler.internalError(CURRENT_ELEMENT_SPANNABLE, 'NullJumpHandler.generateBreak should not be called.'); } void generateContinue([LabelDefinition label]) { compiler.internalError(CURRENT_ELEMENT_SPANNABLE, 'NullJumpHandler.generateContinue should not be called.'); } void forEachBreak(Function ignored) { } void forEachContinue(Function ignored) { } void close() { } bool hasAnyContinue() => false; bool hasAnyBreak() => false; List labels() => const []; JumpTarget get target => null; } // Records breaks until a target block is available. // Breaks are always forward jumps. // Continues in loops are implemented as breaks of the body. // Continues in switches is currently not handled. class TargetJumpHandler implements JumpHandler { final SsaBuilder builder; final JumpTarget target; final List jumps; TargetJumpHandler(SsaBuilder builder, this.target) : this.builder = builder, jumps = [] { assert(builder.jumpTargets[target] == null); builder.jumpTargets[target] = this; } void generateBreak([LabelDefinition label]) { HInstruction breakInstruction; if (label == null) { breakInstruction = new HBreak(target); } else { breakInstruction = new HBreak.toLabel(label); } LocalsHandler locals = new LocalsHandler.from(builder.localsHandler); builder.close(breakInstruction); jumps.add(new JumpHandlerEntry(breakInstruction, locals)); } void generateContinue([LabelDefinition label]) { HInstruction continueInstruction; if (label == null) { continueInstruction = new HContinue(target); } else { continueInstruction = new HContinue.toLabel(label); // Switch case continue statements must be handled by the // [SwitchCaseJumpHandler]. assert(label.target.statement is! ast.SwitchCase); } LocalsHandler locals = new LocalsHandler.from(builder.localsHandler); builder.close(continueInstruction); jumps.add(new JumpHandlerEntry(continueInstruction, locals)); } void forEachBreak(Function action) { for (JumpHandlerEntry entry in jumps) { if (entry.isBreak()) action(entry.jumpInstruction, entry.locals); } } void forEachContinue(Function action) { for (JumpHandlerEntry entry in jumps) { if (entry.isContinue()) action(entry.jumpInstruction, entry.locals); } } bool hasAnyContinue() { for (JumpHandlerEntry entry in jumps) { if (entry.isContinue()) return true; } return false; } bool hasAnyBreak() { for (JumpHandlerEntry entry in jumps) { if (entry.isBreak()) return true; } return false; } void close() { // The mapping from TargetElement to JumpHandler is no longer needed. builder.jumpTargets.remove(target); } List labels() { List result = null; for (LabelDefinition element in target.labels) { if (result == null) result = []; result.add(element); } return (result == null) ? const [] : result; } } /// Special [JumpHandler] implementation used to handle continue statements /// targeting switch cases. class SwitchCaseJumpHandler extends TargetJumpHandler { /// Map from switch case targets to indices used to encode the flow of the /// switch case loop. final Map targetIndexMap = new Map(); SwitchCaseJumpHandler(SsaBuilder builder, JumpTarget target, ast.SwitchStatement node) : super(builder, target) { // The switch case indices must match those computed in // [SsaFromAstMixin.buildSwitchCaseConstants]. // Switch indices are 1-based so we can bypass the synthetic loop when no // cases match simply by branching on the index (which defaults to null). int switchIndex = 1; for (ast.SwitchCase switchCase in node.cases) { for (ast.Node labelOrCase in switchCase.labelsAndCases) { ast.Node label = labelOrCase.asLabel(); if (label != null) { LabelDefinition labelElement = builder.elements.getLabelDefinition(label); if (labelElement != null && labelElement.isContinueTarget) { JumpTarget continueTarget = labelElement.target; targetIndexMap[continueTarget] = switchIndex; assert(builder.jumpTargets[continueTarget] == null); builder.jumpTargets[continueTarget] = this; } } } switchIndex++; } } void generateBreak([LabelDefinition label]) { if (label == null) { // Creates a special break instruction for the synthetic loop generated // for a switch statement with continue statements. See // [SsaFromAstMixin.buildComplexSwitchStatement] for detail. HInstruction breakInstruction = new HBreak(target, breakSwitchContinueLoop: true); LocalsHandler locals = new LocalsHandler.from(builder.localsHandler); builder.close(breakInstruction); jumps.add(new JumpHandlerEntry(breakInstruction, locals)); } else { super.generateBreak(label); } } bool isContinueToSwitchCase(LabelDefinition label) { return label != null && targetIndexMap.containsKey(label.target); } void generateContinue([LabelDefinition label]) { if (isContinueToSwitchCase(label)) { // Creates the special instructions 'label = i; continue l;' used in // switch statements with continue statements. See // [SsaFromAstMixin.buildComplexSwitchStatement] for detail. assert(label != null); HInstruction value = builder.graph.addConstantInt( targetIndexMap[label.target], builder.compiler); builder.localsHandler.updateLocal(target, value); assert(label.target.labels.contains(label)); HInstruction continueInstruction = new HContinue(target); LocalsHandler locals = new LocalsHandler.from(builder.localsHandler); builder.close(continueInstruction); jumps.add(new JumpHandlerEntry(continueInstruction, locals)); } else { super.generateContinue(label); } } void close() { // The mapping from TargetElement to JumpHandler is no longer needed. for (JumpTarget target in targetIndexMap.keys) { builder.jumpTargets.remove(target); } super.close(); } } /** * This class builds SSA nodes for functions represented in AST. */ class SsaBuilder extends NewResolvedVisitor { final Compiler compiler; final JavaScriptBackend backend; final ConstantSystem constantSystem; final CodegenWorkItem work; final RuntimeTypes rti; final bool generateSourceMap; bool inLazyInitializerExpression = false; /* This field is used by the native handler. */ final NativeEmitter nativeEmitter; final HGraph graph = new HGraph(); /** * The current block to add instructions to. Might be null, if we are * visiting dead code, but see [isReachable]. */ HBasicBlock _current; HBasicBlock get current => _current; void set current(c) { isReachable = c != null; _current = c; } /** * The most recently opened block. Has the same value as [current] while * the block is open, but unlike [current], it isn't cleared when the * current block is closed. */ HBasicBlock lastOpenedBlock; /** * Indicates whether the current block is dead (because it has a throw or a * return further up). If this is false, then [current] may be null. If the * block is dead then it may also be aborted, but for simplicity we only * abort on statement boundaries, not in the middle of expressions. See * isAborted. */ bool isReachable = true; /** * True if we are visiting the expression of a throw statement; we assume this * is a slow path. */ bool inExpressionOfThrow = false; /** * The loop nesting is consulted when inlining a function invocation in * [tryInlineMethod]. The inlining heuristics take this information into * account. */ int loopNesting = 0; /** * This stack contains declaration elements of the functions being built * or inlined by this builder. */ final List sourceElementStack = []; LocalsHandler localsHandler; HInstruction rethrowableException; HParameterValue lastAddedParameter; Map parameters = {}; Map jumpTargets = {}; /** * Variables stored in the current activation. These variables are * being updated in try/catch blocks, and should be * accessed indirectly through [HLocalGet] and [HLocalSet]. */ Map activationVariables = {}; // We build the Ssa graph by simulating a stack machine. List stack = []; /// Returns `true` if the current element is an `async` function. bool get isBuildingAsyncFunction { Element element = sourceElement; return (element is FunctionElement && element.asyncMarker == AsyncMarker.ASYNC); } SsaBuilder(JavaScriptBackend backend, CodegenWorkItem work, this.nativeEmitter, this.generateSourceMap) : this.compiler = backend.compiler, this.backend = backend, this.constantSystem = backend.constantSystem, this.work = work, this.rti = backend.rti, super(work.resolutionTree) { localsHandler = new LocalsHandler(this, work.element); sourceElementStack.add(work.element); } CodegenRegistry get registry => work.registry; /// Returns the current source element. /// /// The returned element is a declaration element. // TODO(johnniwinther): Check that all usages of sourceElement agree on // implementation/declaration distinction. Element get sourceElement => sourceElementStack.last; bool get _checkOrTrustTypes => compiler.enableTypeAssertions || compiler.trustTypeAnnotations; HBasicBlock addNewBlock() { HBasicBlock block = graph.addNewBlock(); // If adding a new block during building of an expression, it is due to // conditional expressions or short-circuit logical operators. return block; } void open(HBasicBlock block) { block.open(); current = block; lastOpenedBlock = block; } HBasicBlock close(HControlFlow end) { HBasicBlock result = current; current.close(end); current = null; return result; } HBasicBlock closeAndGotoExit(HControlFlow end) { HBasicBlock result = current; current.close(end); current = null; result.addSuccessor(graph.exit); return result; } void goto(HBasicBlock from, HBasicBlock to) { from.close(new HGoto()); from.addSuccessor(to); } bool isAborted() { return current == null; } /** * Creates a new block, transitions to it from any current block, and * opens the new block. */ HBasicBlock openNewBlock() { HBasicBlock newBlock = addNewBlock(); if (!isAborted()) goto(current, newBlock); open(newBlock); return newBlock; } void add(HInstruction instruction) { current.add(instruction); } void addWithPosition(HInstruction instruction, ast.Node node) { add(attachPosition(instruction, node)); } SourceFile currentSourceFile() { return sourceElement.implementation.compilationUnit.script.file; } void checkValidSourceFileLocation( SourceLocation location, SourceFile sourceFile, int offset) { if (!location.isValid) { throw MessageKind.INVALID_SOURCE_FILE_LOCATION.message( {'offset': offset, 'fileName': sourceFile.filename, 'length': sourceFile.length}); } } /** * Returns a complete argument list for a call of [function]. */ List completeSendArgumentsList( FunctionElement function, Selector selector, List providedArguments, ast.Node currentNode) { assert(invariant(function, function.isImplementation)); assert(providedArguments != null); bool isInstanceMember = function.isInstanceMember; // For static calls, [providedArguments] is complete, default arguments // have been included if necessary, see [makeStaticArgumentList]. if (!isInstanceMember || currentNode == null // In erroneous code, currentNode can be null. || providedArgumentsKnownToBeComplete(currentNode) || function.isGenerativeConstructorBody || selector.isGetter) { // For these cases, the provided argument list is known to be complete. return providedArguments; } else { return completeDynamicSendArgumentsList( selector, function, providedArguments); } } /** * Returns a complete argument list for a dynamic call of [function]. The * initial argument list [providedArguments], created by * [addDynamicSendArgumentsToList], does not include values for default * arguments used in the call. The reason is that the target function (which * defines the defaults) is not known. * * However, inlining can only be performed when the target function can be * resolved statically. The defaults can therefore be included at this point. * * The [providedArguments] list contains first all positional arguments, then * the provided named arguments (the named arguments that are defined in the * [selector]) in a specific order (see [addDynamicSendArgumentsToList]). */ List completeDynamicSendArgumentsList( Selector selector, FunctionElement function, List providedArguments) { assert(selector.applies(function, compiler.world)); FunctionSignature signature = function.functionSignature; List compiledArguments = new List( signature.parameterCount + 1); // Plus one for receiver. compiledArguments[0] = providedArguments[0]; // Receiver. int index = 1; for (; index <= signature.requiredParameterCount; index++) { compiledArguments[index] = providedArguments[index]; } if (!signature.optionalParametersAreNamed) { signature.forEachOptionalParameter((element) { if (index < providedArguments.length) { compiledArguments[index] = providedArguments[index]; } else { compiledArguments[index] = handleConstantForOptionalParameter(element); } index++; }); } else { /* Example: * void foo(a, {b, d, c}) * foo(0, d = 1, b = 2) * * providedArguments = [0, 2, 1] * selectorArgumentNames = [b, d] * signature.orderedOptionalParameters = [b, c, d] * * For each parameter name in the signature, if the argument name matches * we use the next provided argument, otherwise we get the default. */ List selectorArgumentNames = selector.callStructure.getOrderedNamedArguments(); int namedArgumentIndex = 0; int firstProvidedNamedArgument = index; signature.orderedOptionalParameters.forEach((element) { if (namedArgumentIndex < selectorArgumentNames.length && element.name == selectorArgumentNames[namedArgumentIndex]) { // The named argument was provided in the function invocation. compiledArguments[index] = providedArguments[ firstProvidedNamedArgument + namedArgumentIndex++]; } else { compiledArguments[index] = handleConstantForOptionalParameter(element); } index++; }); } return compiledArguments; } /** * Try to inline [element] within the currect context of the builder. The * insertion point is the state of the builder. */ bool tryInlineMethod(Element element, Selector selector, List providedArguments, ast.Node currentNode) { // TODO(johnniwinther): Register this on the [registry]. Currently the // [CodegenRegistry] calls the enqueuer, but [element] should _not_ be // enqueued. backend.registerStaticUse(element, compiler.enqueuer.codegen); // Ensure that [element] is an implementation element. element = element.implementation; if (compiler.elementHasCompileTimeError(element)) return false; FunctionElement function = element; bool insideLoop = loopNesting > 0 || graph.calledInLoop; // Bail out early if the inlining decision is in the cache and we can't // inline (no need to check the hard constraints). bool cachedCanBeInlined = backend.inlineCache.canInline(function, insideLoop: insideLoop); if (cachedCanBeInlined == false) return false; bool meetsHardConstraints() { // Don't inline from one output unit to another. If something is deferred // it is to save space in the loading code. if (!compiler.deferredLoadTask .inSameOutputUnit(element,compiler.currentElement)) { return false; } if (compiler.disableInlining) return false; assert(selector != null || Elements.isStaticOrTopLevel(element) || element.isGenerativeConstructorBody); if (selector != null && !selector.applies(function, compiler.world)) { return false; } // Don't inline operator== methods if the parameter can be null. if (element.name == '==') { if (element.enclosingClass != compiler.objectClass && providedArguments[1].canBeNull()) { return false; } } // Generative constructors of native classes should not be called directly // and have an extra argument that causes problems with inlining. if (element.isGenerativeConstructor && Elements.isNativeOrExtendsNative(element.enclosingClass)) { return false; } // A generative constructor body is not seen by global analysis, // so we should not query for its type. if (!element.isGenerativeConstructorBody) { // Don't inline if the return type was inferred to be non-null empty. // This means that the function always throws an exception. TypeMask returnType = compiler.typesTask.getGuaranteedReturnTypeOfElement(element); if (returnType != null && returnType.isEmpty && !returnType.isNullable) { isReachable = false; return false; } } return true; } bool reductiveHeuristic() { // The call is on a path which is executed rarely, so inline only if it // does not make the program larger. if (isCalledOnce(element)) { return InlineWeeder.canBeInlined(function, -1, false); } // TODO(sra): Measure if inlining would 'reduce' the size. One desirable // case we miss my doing nothing is inlining very simple constructors // where all fields are initialized with values from the arguments at this // call site. The code is slightly larger (`new Foo(1)` vs `Foo$(1)`) but // that usually means the factory constructor is left unused and not // emitted. return false; } bool heuristicSayGoodToGo() { // Don't inline recursively if (inliningStack.any((entry) => entry.function == function)) { return false; } if (element.isSynthesized) return true; if (inExpressionOfThrow || inLazyInitializerExpression) { return reductiveHeuristic(); } if (cachedCanBeInlined == true) { // We may have forced the inlining of some methods. Therefore check // if we can inline this method regardless of size. assert(InlineWeeder.canBeInlined(function, -1, false, allowLoops: true)); return true; } int numParameters = function.functionSignature.parameterCount; int maxInliningNodes; bool useMaxInliningNodes = true; if (insideLoop) { maxInliningNodes = InlineWeeder.INLINING_NODES_INSIDE_LOOP + InlineWeeder.INLINING_NODES_INSIDE_LOOP_ARG_FACTOR * numParameters; } else { maxInliningNodes = InlineWeeder.INLINING_NODES_OUTSIDE_LOOP + InlineWeeder.INLINING_NODES_OUTSIDE_LOOP_ARG_FACTOR * numParameters; } // If a method is called only once, and all the methods in the // inlining stack are called only once as well, we know we will // save on output size by inlining this method. if (isCalledOnce(element)) { useMaxInliningNodes = false; } bool canInline; canInline = InlineWeeder.canBeInlined( function, maxInliningNodes, useMaxInliningNodes); if (canInline) { backend.inlineCache.markAsInlinable(element, insideLoop: insideLoop); } else { backend.inlineCache.markAsNonInlinable(element, insideLoop: insideLoop); } return canInline; } void doInlining() { // Add an explicit null check on the receiver before doing the // inlining. We use [element] to get the same name in the // NoSuchMethodError message as if we had called it. if (element.isInstanceMember && !element.isGenerativeConstructorBody && (selector.mask == null || selector.mask.isNullable)) { addWithPosition( new HFieldGet(null, providedArguments[0], backend.dynamicType, isAssignable: false), currentNode); } List compiledArguments = completeSendArgumentsList( function, selector, providedArguments, currentNode); enterInlinedMethod(function, currentNode, compiledArguments); inlinedFrom(function, () { if (!isReachable) { emitReturn(graph.addConstantNull(compiler), null); } else { doInline(function); } }); leaveInlinedMethod(); } if (meetsHardConstraints() && heuristicSayGoodToGo()) { doInlining(); registry.registerInlining( element, compiler.currentElement); return true; } return false; } bool get allInlinedFunctionsCalledOnce { return inliningStack.isEmpty || inliningStack.last.allFunctionsCalledOnce; } bool isCalledOnce(Element element) { if (!allInlinedFunctionsCalledOnce) return false; TypesInferrer inferrer = compiler.typesTask.typesInferrer; return inferrer.isCalledOnce(element); } inlinedFrom(Element element, f()) { assert(element is FunctionElement || element is VariableElement); return compiler.withCurrentElement(element, () { // The [sourceElementStack] contains declaration elements. sourceElementStack.add(element.declaration); var result = f(); sourceElementStack.removeLast(); return result; }); } HInstruction handleConstantForOptionalParameter(Element parameter) { ConstantExpression constant = backend.constants.getConstantForVariable(parameter); assert(invariant(parameter, constant != null, message: 'No constant computed for $parameter')); return graph.addConstant(constant.value, compiler); } Element get currentNonClosureClass { ClassElement cls = sourceElement.enclosingClass; if (cls != null && cls.isClosure) { var closureClass = cls; return closureClass.methodElement.enclosingClass; } else { return cls; } } /** * Returns whether this builder is building code for [element]. */ bool isBuildingFor(Element element) { return work.element == element; } /// A stack of [DartType]s the have been seen during inlining of factory /// constructors. These types are preserved in [HInvokeStatic]s and /// [HForeignNew]s inside the inline code and registered during code /// generation for these nodes. // TODO(karlklose): consider removing this and keeping the (substituted) // types of the type variables in an environment (like the [LocalsHandler]). final List currentInlinedInstantiations = []; final List inliningStack = []; Local returnLocal; DartType returnType; bool inTryStatement = false; ConstantValue getConstantForNode(ast.Node node) { ConstantExpression constant = backend.constants.getConstantForNode(node, elements); assert(invariant(node, constant != null, message: 'No constant computed for $node')); return constant.value; } HInstruction addConstant(ast.Node node) { return graph.addConstant(getConstantForNode(node), compiler); } bool isLazilyInitialized(VariableElement element) { ConstantExpression initialValue = backend.constants.getConstantForVariable(element); return initialValue == null; } TypeMask cachedTypeOfThis; TypeMask getTypeOfThis() { TypeMask result = cachedTypeOfThis; if (result == null) { ThisLocal local = localsHandler.closureData.thisLocal; ClassElement cls = local.enclosingClass; ClassWorld classWorld = compiler.world; if (classWorld.isUsedAsMixin(cls)) { // If the enclosing class is used as a mixin, [:this:] can be // of the class that mixins the enclosing class. These two // classes do not have a subclass relationship, so, for // simplicity, we mark the type as an interface type. result = new TypeMask.nonNullSubtype(cls.declaration, compiler.world); } else { result = new TypeMask.nonNullSubclass(cls.declaration, compiler.world); } cachedTypeOfThis = result; } return result; } Map cachedTypesOfCapturedVariables = new Map(); TypeMask getTypeOfCapturedVariable(Element element) { assert(element.isField); return cachedTypesOfCapturedVariables.putIfAbsent(element, () { return TypeMaskFactory.inferredTypeForElement(element, compiler); }); } /** * Documentation wanted -- johnniwinther * * Invariant: [functionElement] must be an implementation element. */ HGraph buildMethod(FunctionElement functionElement) { assert(invariant(functionElement, functionElement.isImplementation)); graph.calledInLoop = compiler.world.isCalledInLoop(functionElement); ast.FunctionExpression function = functionElement.node; assert(function != null); assert(!function.modifiers.isExternal); assert(elements.getFunctionDefinition(function) != null); openFunction(functionElement, function); String name = functionElement.name; // If [functionElement] is `operator==` we explicitely add a null check at // the beginning of the method. This is to avoid having call sites do the // null check. if (name == '==') { if (!backend.operatorEqHandlesNullArgument(functionElement)) { handleIf( function, () { HParameterValue parameter = parameters.values.first; push(new HIdentity( parameter, graph.addConstantNull(compiler), null, backend.boolType)); }, () { closeAndGotoExit(new HReturn( graph.addConstantBool(false, compiler))); }, null); } } function.body.accept(this); return closeFunction(); } HGraph buildCheckedSetter(VariableElement field) { openFunction(field, field.node); HInstruction thisInstruction = localsHandler.readThis(); // Use dynamic type because the type computed by the inferrer is // narrowed to the type annotation. HInstruction parameter = new HParameterValue(field, backend.dynamicType); // Add the parameter as the last instruction of the entry block. // If the method is intercepted, we want the actual receiver // to be the first parameter. graph.entry.addBefore(graph.entry.last, parameter); HInstruction value = potentiallyCheckOrTrustType(parameter, field.type); add(new HFieldSet(field, thisInstruction, value)); return closeFunction(); } HGraph buildLazyInitializer(VariableElement variable) { inLazyInitializerExpression = true; ast.Node node = variable.node; openFunction(variable, node); assert(invariant(variable, variable.initializer != null, message: "Non-constant variable $variable has no initializer.")); visit(variable.initializer); HInstruction value = pop(); value = potentiallyCheckOrTrustType(value, variable.type); closeAndGotoExit(new HReturn(value)); return closeFunction(); } /** * Returns the constructor body associated with the given constructor or * creates a new constructor body, if none can be found. * * Returns [:null:] if the constructor does not have a body. */ ConstructorBodyElement getConstructorBody(FunctionElement constructor) { assert(constructor.isGenerativeConstructor); assert(invariant(constructor, constructor.isImplementation)); if (constructor.isSynthesized) return null; ast.FunctionExpression node = constructor.node; // If we know the body doesn't have any code, we don't generate it. if (!node.hasBody()) return null; if (node.hasEmptyBody()) return null; ClassElement classElement = constructor.enclosingClass; ConstructorBodyElement bodyElement; classElement.forEachBackendMember((Element backendMember) { if (backendMember.isGenerativeConstructorBody) { ConstructorBodyElement body = backendMember; if (body.constructor == constructor) { // TODO(kasperl): Find a way of stopping the iteration // through the backend members. bodyElement = backendMember; } } }); if (bodyElement == null) { bodyElement = new ConstructorBodyElementX(constructor); classElement.addBackendMember(bodyElement); if (constructor.isPatch) { // Create origin body element for patched constructors. ConstructorBodyElementX patch = bodyElement; ConstructorBodyElementX origin = new ConstructorBodyElementX(constructor.origin); origin.applyPatch(patch); classElement.origin.addBackendMember(bodyElement.origin); } } assert(bodyElement.isGenerativeConstructorBody); return bodyElement; } HParameterValue addParameter(Entity parameter, TypeMask type) { assert(inliningStack.isEmpty); HParameterValue result = new HParameterValue(parameter, type); if (lastAddedParameter == null) { graph.entry.addBefore(graph.entry.first, result); } else { graph.entry.addAfter(lastAddedParameter, result); } lastAddedParameter = result; return result; } /** * This method sets up the local state of the builder for inlining [function]. * The arguments of the function are inserted into the [localsHandler]. * * When inlining a function, [:return:] statements are not emitted as * [HReturn] instructions. Instead, the value of a synthetic element is * updated in the [localsHandler]. This function creates such an element and * stores it in the [returnLocal] field. */ void setupStateForInlining(FunctionElement function, List compiledArguments) { localsHandler = new LocalsHandler(this, function); localsHandler.closureData = compiler.closureToClassMapper.computeClosureToClassMapping( function, function.node, elements); returnLocal = new SyntheticLocal("result", function); localsHandler.updateLocal(returnLocal, graph.addConstantNull(compiler)); inTryStatement = false; // TODO(lry): why? Document. int argumentIndex = 0; if (function.isInstanceMember) { localsHandler.updateLocal(localsHandler.closureData.thisLocal, compiledArguments[argumentIndex++]); } FunctionSignature signature = function.functionSignature; signature.orderedForEachParameter((ParameterElement parameter) { HInstruction argument = compiledArguments[argumentIndex++]; localsHandler.updateLocal(parameter, argument); }); ClassElement enclosing = function.enclosingClass; if ((function.isConstructor || function.isGenerativeConstructorBody) && backend.classNeedsRti(enclosing)) { enclosing.typeVariables.forEach((TypeVariableType typeVariable) { HInstruction argument = compiledArguments[argumentIndex++]; localsHandler.updateLocal( localsHandler.getTypeVariableAsLocal(typeVariable), argument); }); } assert(argumentIndex == compiledArguments.length); elements = function.resolvedAst.elements; assert(elements != null); returnType = signature.type.returnType; stack = []; insertTraceCall(function); } void restoreState(AstInliningState state) { localsHandler = state.oldLocalsHandler; returnLocal = state.oldReturnLocal; inTryStatement = state.inTryStatement; elements = state.oldElements; returnType = state.oldReturnType; assert(stack.isEmpty); stack = state.oldStack; } /** * Run this builder on the body of the [function] to be inlined. */ void visitInlinedFunction(FunctionElement function) { potentiallyCheckInlinedParameterTypes(function); if (function.isGenerativeConstructor) { buildFactory(function); } else { ast.FunctionExpression functionNode = function.node; functionNode.body.accept(this); } } addInlinedInstantiation(DartType type) { if (type != null) { currentInlinedInstantiations.add(type); } } removeInlinedInstantiation(DartType type) { if (type != null) { currentInlinedInstantiations.removeLast(); } } bool providedArgumentsKnownToBeComplete(ast.Node currentNode) { /* When inlining the iterator methods generated for a [:for-in:] loop, the * [currentNode] is the [ForIn] tree. The compiler-generated iterator * invocations are known to have fully specified argument lists, no default * arguments are used. See invocations of [pushInvokeDynamic] in * [visitForIn]. */ return currentNode.asForIn() != null; } /** * In checked mode, generate type tests for the parameters of the inlined * function. */ void potentiallyCheckInlinedParameterTypes(FunctionElement function) { if (!_checkOrTrustTypes) return; FunctionSignature signature = function.functionSignature; signature.orderedForEachParameter((ParameterElement parameter) { HInstruction argument = localsHandler.readLocal(parameter); potentiallyCheckOrTrustType(argument, parameter.type); }); } /** * Documentation wanted -- johnniwinther * * Invariant: [constructors] must contain only implementation elements. */ void inlineSuperOrRedirect(FunctionElement callee, List compiledArguments, List constructors, Map fieldValues, FunctionElement caller) { callee = callee.implementation; compiler.withCurrentElement(callee, () { constructors.add(callee); ClassElement enclosingClass = callee.enclosingClass; if (backend.classNeedsRti(enclosingClass)) { // If [enclosingClass] needs RTI, we have to give a value to its // type parameters. ClassElement currentClass = caller.enclosingClass; // For a super constructor call, the type is the supertype of // [currentClass]. For a redirecting constructor, the type is // the current type. [InterfaceType.asInstanceOf] takes care // of both. InterfaceType type = currentClass.thisType.asInstanceOf(enclosingClass); type = localsHandler.substInContext(type); List arguments = type.typeArguments; List typeVariables = enclosingClass.typeVariables; if (!type.isRaw) { assert(arguments.length == typeVariables.length); Iterator variables = typeVariables.iterator; type.typeArguments.forEach((DartType argument) { variables.moveNext(); TypeVariableType typeVariable = variables.current; localsHandler.updateLocal( localsHandler.getTypeVariableAsLocal(typeVariable), analyzeTypeArgument(argument)); }); } else { // If the supertype is a raw type, we need to set to null the // type variables. for (TypeVariableType variable in typeVariables) { localsHandler.updateLocal( localsHandler.getTypeVariableAsLocal(variable), graph.addConstantNull(compiler)); } } } // For redirecting constructors, the fields have already been // initialized by the caller. if (callee.enclosingClass != caller.enclosingClass) { inlinedFrom(callee, () { buildFieldInitializers(callee.enclosingElement.implementation, fieldValues); }); } int index = 0; FunctionSignature params = callee.functionSignature; params.orderedForEachParameter((ParameterElement parameter) { HInstruction argument = compiledArguments[index++]; // Because we are inlining the initializer, we must update // what was given as parameter. This will be used in case // there is a parameter check expression in the initializer. parameters[parameter] = argument; localsHandler.updateLocal(parameter, argument); // Don't forget to update the field, if the parameter is of the // form [:this.x:]. if (parameter.isInitializingFormal) { InitializingFormalElement fieldParameterElement = parameter; fieldValues[fieldParameterElement.fieldElement] = argument; } }); // Build the initializers in the context of the new constructor. TreeElements oldElements = elements; ResolvedAst resolvedAst = callee.resolvedAst; elements = resolvedAst.elements; ClosureClassMap oldClosureData = localsHandler.closureData; ast.Node node = resolvedAst.node; ClosureClassMap newClosureData = compiler.closureToClassMapper.computeClosureToClassMapping( callee, node, elements); localsHandler.closureData = newClosureData; localsHandler.enterScope(node, callee); buildInitializers(callee, constructors, fieldValues); localsHandler.closureData = oldClosureData; elements = oldElements; }); } /** * Run through the initializers and inline all field initializers. Recursively * inlines super initializers. * * The constructors of the inlined initializers is added to [constructors] * with sub constructors having a lower index than super constructors. * * Invariant: The [constructor] and elements in [constructors] must all be * implementation elements. */ void buildInitializers(ConstructorElement constructor, List constructors, Map fieldValues) { assert(invariant(constructor, constructor.isImplementation)); if (constructor.isSynthesized) { List arguments = []; HInstruction compileArgument(ParameterElement parameter) { return localsHandler.readLocal(parameter); } Element target = constructor.definingConstructor.implementation; bool match = CallStructure.addForwardingElementArgumentsToList( constructor, arguments, target, compileArgument, handleConstantForOptionalParameter); if (!match) { if (compiler.elementHasCompileTimeError(constructor)) { return; } // If this fails, the selector we constructed for the call to a // forwarding constructor in a mixin application did not match the // constructor (which, for example, may happen when the libraries are // not compatible for private names, see issue 20394). compiler.internalError(constructor, 'forwarding constructor call does not match'); } inlineSuperOrRedirect( target, arguments, constructors, fieldValues, constructor); return; } ast.FunctionExpression functionNode = constructor.node; bool foundSuperOrRedirect = false; if (functionNode.initializers != null) { Link initializers = functionNode.initializers.nodes; for (Link link = initializers; !link.isEmpty; link = link.tail) { assert(link.head is ast.Send); if (link.head is !ast.SendSet) { // A super initializer or constructor redirection. foundSuperOrRedirect = true; ast.Send call = link.head; assert(ast.Initializers.isSuperConstructorCall(call) || ast.Initializers.isConstructorRedirect(call)); FunctionElement target = elements[call].implementation; CallStructure callStructure = elements.getSelector(call).callStructure; Link arguments = call.arguments; List compiledArguments; inlinedFrom(constructor, () { compiledArguments = makeStaticArgumentList(callStructure, arguments, target); }); inlineSuperOrRedirect(target, compiledArguments, constructors, fieldValues, constructor); } else { // A field initializer. ast.SendSet init = link.head; Link arguments = init.arguments; assert(!arguments.isEmpty && arguments.tail.isEmpty); inlinedFrom(constructor, () { visit(arguments.head); }); fieldValues[elements[init]] = pop(); } } } if (!foundSuperOrRedirect) { // No super initializer found. Try to find the default constructor if // the class is not Object. ClassElement enclosingClass = constructor.enclosingClass; ClassElement superClass = enclosingClass.superclass; if (!enclosingClass.isObject) { assert(superClass != null); assert(superClass.resolutionState == STATE_DONE); // TODO(johnniwinther): Should we find injected constructors as well? FunctionElement target = superClass.lookupDefaultConstructor(); if (target == null) { compiler.internalError(superClass, "No default constructor available."); } List arguments = CallStructure.NO_ARGS.makeArgumentsList( const Link(), target.implementation, null, handleConstantForOptionalParameter); inlineSuperOrRedirect(target, arguments, constructors, fieldValues, constructor); } } } /** * Run through the fields of [cls] and add their potential * initializers. * * Invariant: [classElement] must be an implementation element. */ void buildFieldInitializers(ClassElement classElement, Map fieldValues) { assert(invariant(classElement, classElement.isImplementation)); classElement.forEachInstanceField( (ClassElement enclosingClass, VariableElement member) { if (compiler.elementHasCompileTimeError(member)) return; compiler.withCurrentElement(member, () { TreeElements definitions = member.treeElements; ast.Node node = member.node; ast.Expression initializer = member.initializer; if (initializer == null) { // Unassigned fields of native classes are not initialized to // prevent overwriting pre-initialized native properties. if (!Elements.isNativeOrExtendsNative(classElement)) { fieldValues[member] = graph.addConstantNull(compiler); } } else { ast.Node right = initializer; TreeElements savedElements = elements; elements = definitions; // In case the field initializer uses closures, run the // closure to class mapper. compiler.closureToClassMapper.computeClosureToClassMapping( member, node, elements); inlinedFrom(member, () => right.accept(this)); elements = savedElements; fieldValues[member] = pop(); } }); }); } /** * Build the factory function corresponding to the constructor * [functionElement]: * - Initialize fields with the values of the field initializers of the * current constructor and super constructors or constructors redirected * to, starting from the current constructor. * - Call the constructor bodies, starting from the constructor(s) in the * super class(es). */ HGraph buildFactory(FunctionElement functionElement) { functionElement = functionElement.implementation; ClassElement classElement = functionElement.enclosingClass.implementation; bool isNativeUpgradeFactory = Elements.isNativeOrExtendsNative(classElement); ast.FunctionExpression function = functionElement.node; // Note that constructors (like any other static function) do not need // to deal with optional arguments. It is the callers job to provide all // arguments as if they were positional. if (inliningStack.isEmpty) { // The initializer list could contain closures. openFunction(functionElement, function); } Map fieldValues = new Map(); // Compile the possible initialization code for local fields and // super fields. buildFieldInitializers(classElement, fieldValues); // Compile field-parameters such as [:this.x:]. FunctionSignature params = functionElement.functionSignature; params.orderedForEachParameter((ParameterElement parameter) { if (parameter.isInitializingFormal) { // If the [element] is a field-parameter then // initialize the field element with its value. InitializingFormalElement fieldParameter = parameter; HInstruction parameterValue = localsHandler.readLocal(fieldParameter); fieldValues[fieldParameter.fieldElement] = parameterValue; } }); // Analyze the constructor and all referenced constructors and collect // initializers and constructor bodies. List constructors = [functionElement]; buildInitializers(functionElement, constructors, fieldValues); // Call the JavaScript constructor with the fields as argument. List constructorArguments = []; List fields = []; classElement.forEachInstanceField( (ClassElement enclosingClass, VariableElement member) { HInstruction value = fieldValues[member]; if (value == null) { // Uninitialized native fields are pre-initialized by the native // implementation. assert(invariant( member, isNativeUpgradeFactory || compiler.compilationFailed)); } else { fields.add(member); DartType type = localsHandler.substInContext(member.type); constructorArguments.add(potentiallyCheckOrTrustType(value, type)); } }, includeSuperAndInjectedMembers: true); InterfaceType type = classElement.thisType; TypeMask ssaType = new TypeMask.nonNullExact(classElement.declaration, compiler.world); List instantiatedTypes; addInlinedInstantiation(type); if (!currentInlinedInstantiations.isEmpty) { instantiatedTypes = new List.from(currentInlinedInstantiations); } HInstruction newObject; if (!isNativeUpgradeFactory) { newObject = new HForeignNew(classElement, ssaType, constructorArguments, instantiatedTypes); add(newObject); } else { // Bulk assign to the initialized fields. newObject = graph.explicitReceiverParameter; // Null guard ensures an error if we are being called from an explicit // 'new' of the constructor instead of via an upgrade. It is optimized out // if there are field initializers. add(new HFieldGet( null, newObject, backend.dynamicType, isAssignable: false)); for (int i = 0; i < fields.length; i++) { add(new HFieldSet(fields[i], newObject, constructorArguments[i])); } } removeInlinedInstantiation(type); // Create the runtime type information, if needed. if (backend.classNeedsRti(classElement)) { // Read the values of the type arguments and create a list to set on the // newly create object. We can identify the case where the new list // would be of the form: // [getTypeArgumentByIndex(this, 0), .., getTypeArgumentByIndex(this, k)] // and k is the number of type arguments of this. If this is the case, // we can simply copy the list from this. // These locals are modified by [isIndexedTypeArgumentGet]. HThis source; // The source of the type arguments. bool allIndexed = true; int expectedIndex = 0; ClassElement contextClass; // The class of `this`. int remainingTypeVariables; // The number of 'remaining type variables' // of `this`. /// Helper to identify instructions that read a type variable without /// substitution (that is, directly use the index). These instructions /// are of the form: /// HInvokeStatic(getTypeArgumentByIndex, this, index) /// /// Return `true` if [instruction] is of that form and the index is the /// next index in the sequence (held in [expectedIndex]). bool isIndexedTypeArgumentGet(HInstruction instruction) { if (instruction is! HInvokeStatic) return false; HInvokeStatic invoke = instruction; if (invoke.element != backend.getGetTypeArgumentByIndex()) { return false; } HConstant index = invoke.inputs[1]; HInstruction newSource = invoke.inputs[0]; if (newSource is! HThis) { return false; } if (source == null) { // This is the first match. Extract the context class for the type // variables and get the list of type variables to keep track of how // many arguments we need to process. source = newSource; contextClass = source.sourceElement.enclosingClass; remainingTypeVariables = contextClass.typeVariables.length; } else { assert(source == newSource); } // If there are no more type variables, then there are more type // arguments for the new object than the source has, and it can't be // a copy. Otherwise remove one argument. if (remainingTypeVariables == 0) return false; remainingTypeVariables--; // Check that the index is the one we expect. IntConstantValue constant = index.constant; return constant.primitiveValue == expectedIndex++; } List typeArguments = []; classElement.typeVariables.forEach((TypeVariableType typeVariable) { HInstruction argument = localsHandler.readLocal( localsHandler.getTypeVariableAsLocal(typeVariable)); if (allIndexed && !isIndexedTypeArgumentGet(argument)) { allIndexed = false; } typeArguments.add(argument); }); if (source != null && allIndexed && remainingTypeVariables == 0) { copyRuntimeTypeInfo(source, newObject); } else { newObject = callSetRuntimeTypeInfo(classElement, typeArguments, newObject); } } // Generate calls to the constructor bodies. HInstruction interceptor = null; for (int index = constructors.length - 1; index >= 0; index--) { FunctionElement constructor = constructors[index]; assert(invariant(functionElement, constructor.isImplementation)); ConstructorBodyElement body = getConstructorBody(constructor); if (body == null) continue; List bodyCallInputs = []; if (isNativeUpgradeFactory) { if (interceptor == null) { ConstantValue constant = new InterceptorConstantValue(classElement.thisType); interceptor = graph.addConstant(constant, compiler); } bodyCallInputs.add(interceptor); } bodyCallInputs.add(newObject); ResolvedAst resolvedAst = constructor.resolvedAst; TreeElements elements = resolvedAst.elements; ast.Node node = resolvedAst.node; ClosureClassMap parameterClosureData = compiler.closureToClassMapper.getMappingForNestedFunction(node); FunctionSignature functionSignature = body.functionSignature; // Provide the parameters to the generative constructor body. functionSignature.orderedForEachParameter((ParameterElement parameter) { // If [parameter] is boxed, it will be a field in the box passed as the // last parameter. So no need to directly pass it. if (!localsHandler.isBoxed(parameter)) { bodyCallInputs.add(localsHandler.readLocal(parameter)); } }); // If there are locals that escape (ie mutated in closures), we // pass the box to the constructor. // The box must be passed before any type variable. ClosureScope scopeData = parameterClosureData.capturingScopes[node]; if (scopeData != null) { bodyCallInputs.add(localsHandler.readLocal(scopeData.boxElement)); } // Type variables arguments must come after the box (if there is one). ClassElement currentClass = constructor.enclosingClass; if (backend.classNeedsRti(currentClass)) { // If [currentClass] needs RTI, we add the type variables as // parameters of the generative constructor body. currentClass.typeVariables.forEach((TypeVariableType argument) { // TODO(johnniwinther): Substitute [argument] with // `localsHandler.substInContext(argument)`. bodyCallInputs.add(localsHandler.readLocal( localsHandler.getTypeVariableAsLocal(argument))); }); } if (!isNativeUpgradeFactory && // TODO(13836): Fix inlining. tryInlineMethod(body, null, bodyCallInputs, function)) { pop(); } else { HInvokeConstructorBody invoke = new HInvokeConstructorBody( body.declaration, bodyCallInputs, backend.nonNullType); invoke.sideEffects = compiler.world.getSideEffectsOfElement(constructor); add(invoke); } } if (inliningStack.isEmpty) { closeAndGotoExit(new HReturn(newObject)); return closeFunction(); } else { localsHandler.updateLocal(returnLocal, newObject); return null; } } /** * Documentation wanted -- johnniwinther * * Invariant: [functionElement] must be the implementation element. */ void openFunction(Element element, ast.Node node) { assert(invariant(element, element.isImplementation)); HBasicBlock block = graph.addNewBlock(); open(graph.entry); localsHandler.startFunction(element, node); close(new HGoto()).addSuccessor(block); open(block); // Add the type parameters of the class as parameters of this method. This // must be done before adding the normal parameters, because their types // may contain references to type variables. var enclosing = element.enclosingElement; if ((element.isConstructor || element.isGenerativeConstructorBody) && backend.classNeedsRti(enclosing)) { enclosing.typeVariables.forEach((TypeVariableType typeVariable) { HParameterValue param = addParameter( typeVariable.element, backend.nonNullType); localsHandler.directLocals[ localsHandler.getTypeVariableAsLocal(typeVariable)] = param; }); } if (element is FunctionElement) { FunctionElement functionElement = element; FunctionSignature signature = functionElement.functionSignature; // Put the type checks in the first successor of the entry, // because that is where the type guards will also be inserted. // This way we ensure that a type guard will dominate the type // check. ClosureScope scopeData = localsHandler.closureData.capturingScopes[node]; signature.orderedForEachParameter((ParameterElement parameterElement) { if (element.isGenerativeConstructorBody) { if (scopeData != null && scopeData.isCapturedVariable(parameterElement)) { // The parameter will be a field in the box passed as the // last parameter. So no need to have it. return; } } HInstruction newParameter = localsHandler.directLocals[parameterElement]; if (!element.isConstructor || !(element as ConstructorElement).isRedirectingFactory) { // Redirection factories must not check their argument types. // Example: // // class A { // A(String foo) = A.b; // A(int foo) { print(foo); } // } // main() { // new A(499); // valid even in checked mode. // new A("foo"); // invalid in checked mode. // // Only the final target is allowed to check for the argument types. newParameter = potentiallyCheckOrTrustType(newParameter, parameterElement.type); } localsHandler.directLocals[parameterElement] = newParameter; }); returnType = signature.type.returnType; } else { // Otherwise it is a lazy initializer which does not have parameters. assert(element is VariableElement); } insertTraceCall(element); } insertTraceCall(Element element) { if (JavaScriptBackend.TRACE_CALLS) { if (element == backend.traceHelper) return; n(e) => e == null ? '' : e.name; String name = "${n(element.library)}:${n(element.enclosingClass)}." "${n(element)}"; HConstant nameConstant = addConstantString(name); add(new HInvokeStatic(backend.traceHelper, [nameConstant], backend.dynamicType)); } } /// Check that [type] is valid in the context of `localsHandler.contextClass`. /// This should only be called in assertions. bool assertTypeInContext(DartType type, [Spannable spannable]) { return invariant(spannable == null ? CURRENT_ELEMENT_SPANNABLE : spannable, () { ClassElement contextClass = Types.getClassContext(type); return contextClass == null || contextClass == localsHandler.contextClass; }, message: "Type '$type' is not valid context of " "${localsHandler.contextClass}."); } /// Build a [HTypeConversion] for convertion [original] to type [type]. /// /// Invariant: [type] must be valid in the context. /// See [LocalsHandler.substInContext]. HInstruction buildTypeConversion(HInstruction original, DartType type, int kind) { if (type == null) return original; type = type.unalias(compiler); assert(assertTypeInContext(type, original)); if (type.isInterfaceType && !type.treatAsRaw) { TypeMask subtype = new TypeMask.subtype(type.element, compiler.world); HInstruction representations = buildTypeArgumentRepresentations(type); add(representations); return new HTypeConversion.withTypeRepresentation(type, kind, subtype, original, representations); } else if (type.isTypeVariable) { TypeMask subtype = original.instructionType; HInstruction typeVariable = addTypeVariableReference(type); return new HTypeConversion.withTypeRepresentation(type, kind, subtype, original, typeVariable); } else if (type.isFunctionType) { String name = kind == HTypeConversion.CAST_TYPE_CHECK ? '_asCheck' : '_assertCheck'; List arguments = [buildFunctionType(type), original]; pushInvokeDynamic( null, new Selector.call(name, backend.jsHelperLibrary, 1), arguments); return new HTypeConversion(type, kind, original.instructionType, pop()); } else { return original.convertType(compiler, type, kind); } } HInstruction _trustType(HInstruction original, DartType type) { assert(compiler.trustTypeAnnotations); assert(type != null); type = localsHandler.substInContext(type); type = type.unalias(compiler); if (type.isDynamic) return original; if (!type.isInterfaceType) return original; // The type element is either a class or the void element. Element element = type.element; if (element == compiler.objectClass) return original; TypeMask mask = new TypeMask.subtype(element, compiler.world); return new HTypeKnown.pinned(mask, original); } HInstruction _checkType(HInstruction original, DartType type, int kind) { assert(compiler.enableTypeAssertions); assert(type != null); type = localsHandler.substInContext(type); HInstruction other = buildTypeConversion(original, type, kind); registry.registerIsCheck(type); return other; } HInstruction potentiallyCheckOrTrustType(HInstruction original, DartType type, { int kind: HTypeConversion.CHECKED_MODE_CHECK }) { if (type == null) return original; HInstruction checkedOrTrusted = original; if (compiler.trustTypeAnnotations) { checkedOrTrusted = _trustType(original, type); } else if (compiler.enableTypeAssertions) { checkedOrTrusted = _checkType(original, type, kind); } if (checkedOrTrusted == original) return original; add(checkedOrTrusted); return checkedOrTrusted; } void assertIsSubtype(ast.Node node, DartType subtype, DartType supertype, String message) { HInstruction subtypeInstruction = analyzeTypeArgument(localsHandler.substInContext(subtype)); HInstruction supertypeInstruction = analyzeTypeArgument(localsHandler.substInContext(supertype)); HInstruction messageInstruction = graph.addConstantString(new ast.DartString.literal(message), compiler); Element element = backend.getAssertIsSubtype(); var inputs = [subtypeInstruction, supertypeInstruction, messageInstruction]; HInstruction assertIsSubtype = new HInvokeStatic( element, inputs, subtypeInstruction.instructionType); registry.registerTypeVariableBoundsSubtypeCheck(subtype, supertype); add(assertIsSubtype); } HGraph closeFunction() { // TODO(kasperl): Make this goto an implicit return. if (!isAborted()) closeAndGotoExit(new HGoto()); graph.finalize(); return graph; } void push(HInstruction instruction) { add(instruction); stack.add(instruction); } void pushWithPosition(HInstruction instruction, ast.Node node) { push(attachPosition(instruction, node)); } HInstruction pop() { return stack.removeLast(); } void dup() { stack.add(stack.last); } HInstruction popBoolified() { HInstruction value = pop(); if (_checkOrTrustTypes) { return potentiallyCheckOrTrustType( value, compiler.boolClass.rawType, kind: HTypeConversion.BOOLEAN_CONVERSION_CHECK); } HInstruction result = new HBoolify(value, backend.boolType); add(result); return result; } HInstruction attachPosition(HInstruction target, ast.Node node) { if (generateSourceMap && node != null) { target.sourceInformation = sourceInformationForBeginToken(node); } return target; } SourceInformation sourceInformationForBeginToken(ast.Node node) { return new StartEndSourceInformation(sourceFileLocationForBeginToken(node)); } SourceInformation sourceInformationForBeginEndToken(ast.Node node) { return new StartEndSourceInformation( sourceFileLocationForBeginToken(node), sourceFileLocationForEndToken(node)); } SourceLocation sourceFileLocationForBeginToken(ast.Node node) => sourceFileLocationForToken(node, node.getBeginToken()); SourceLocation sourceFileLocationForEndToken(ast.Node node) => sourceFileLocationForToken(node, node.getEndToken()); SourceLocation sourceFileLocationForToken(ast.Node node, Token token) { SourceFile sourceFile = currentSourceFile(); SourceLocation location = new TokenSourceLocation(sourceFile, token, sourceElement.name); checkValidSourceFileLocation(location, sourceFile, token.charOffset); return location; } void visit(ast.Node node) { if (node != null) node.accept(this); } visitBlock(ast.Block node) { assert(!isAborted()); if (!isReachable) return; // This can only happen when inlining. for (Link link = node.statements.nodes; !link.isEmpty; link = link.tail) { visit(link.head); if (!isReachable) { // The block has been aborted by a return or a throw. if (!stack.isEmpty) { compiler.internalError(node, 'Non-empty instruction stack.'); } return; } } assert(!current.isClosed()); if (!stack.isEmpty) { compiler.internalError(node, 'Non-empty instruction stack.'); } } visitClassNode(ast.ClassNode node) { compiler.internalError(node, 'SsaBuilder.visitClassNode should not be called.'); } visitThrowExpression(ast.Expression expression) { bool old = inExpressionOfThrow; try { inExpressionOfThrow = true; visit(expression); } finally { inExpressionOfThrow = old; } } visitExpressionStatement(ast.ExpressionStatement node) { if (!isReachable) return; ast.Throw throwExpression = node.expression.asThrow(); if (throwExpression != null && inliningStack.isEmpty) { visitThrowExpression(throwExpression.expression); handleInTryStatement(); closeAndGotoExit(new HThrow(pop())); } else { visit(node.expression); pop(); } } /** * Creates a new loop-header block. The previous [current] block * is closed with an [HGoto] and replaced by the newly created block. * Also notifies the locals handler that we're entering a loop. */ JumpHandler beginLoopHeader(ast.Node node) { assert(!isAborted()); HBasicBlock previousBlock = close(new HGoto()); JumpHandler jumpHandler = createJumpHandler(node, isLoopJump: true); HBasicBlock loopEntry = graph.addNewLoopHeaderBlock( jumpHandler.target, jumpHandler.labels()); previousBlock.addSuccessor(loopEntry); open(loopEntry); localsHandler.beginLoopHeader(loopEntry); return jumpHandler; } /** * Ends the loop: * - creates a new block and adds it as successor to the [branchExitBlock] and * any blocks that end in break. * - opens the new block (setting as [current]). * - notifies the locals handler that we're exiting a loop. * [savedLocals] are the locals from the end of the loop condition. * [branchExitBlock] is the exit (branching) block of the condition. Generally * this is not the top of the loop, since this would lead to critical edges. * It is null for degenerate do-while loops that have * no back edge because they abort (throw/return/break in the body and have * no continues). */ void endLoop(HBasicBlock loopEntry, HBasicBlock branchExitBlock, JumpHandler jumpHandler, LocalsHandler savedLocals) { HBasicBlock loopExitBlock = addNewBlock(); List breakHandlers = []; // Collect data for the successors and the phis at each break. jumpHandler.forEachBreak((HBreak breakInstruction, LocalsHandler locals) { breakInstruction.block.addSuccessor(loopExitBlock); breakHandlers.add(locals); }); // The exit block is a successor of the loop condition if it is reached. // We don't add the successor in the case of a while/for loop that aborts // because the caller of endLoop will be wiring up a special empty else // block instead. if (branchExitBlock != null) { branchExitBlock.addSuccessor(loopExitBlock); } // Update the phis at the loop entry with the current values of locals. localsHandler.endLoop(loopEntry); // Start generating code for the exit block. open(loopExitBlock); // Create a new localsHandler for the loopExitBlock with the correct phis. if (!breakHandlers.isEmpty) { if (branchExitBlock != null) { // Add the values of the locals at the end of the condition block to // the phis. These are the values that flow to the exit if the // condition fails. breakHandlers.add(savedLocals); } localsHandler = savedLocals.mergeMultiple(breakHandlers, loopExitBlock); } else { localsHandler = savedLocals; } } HSubGraphBlockInformation wrapStatementGraph(SubGraph statements) { if (statements == null) return null; return new HSubGraphBlockInformation(statements); } HSubExpressionBlockInformation wrapExpressionGraph(SubExpression expression) { if (expression == null) return null; return new HSubExpressionBlockInformation(expression); } // For while loops, initializer and update are null. // The condition function must return a boolean result. // None of the functions must leave anything on the stack. void handleLoop(ast.Node loop, void initialize(), HInstruction condition(), void update(), void body()) { // Generate: // // loop-entry: // if (!) goto loop-exit; // // // goto loop-entry; // loop-exit: localsHandler.startLoop(loop); // The initializer. SubExpression initializerGraph = null; HBasicBlock startBlock; if (initialize != null) { HBasicBlock initializerBlock = openNewBlock(); startBlock = initializerBlock; initialize(); assert(!isAborted()); initializerGraph = new SubExpression(initializerBlock, current); } loopNesting++; JumpHandler jumpHandler = beginLoopHeader(loop); HLoopInformation loopInfo = current.loopInformation; HBasicBlock conditionBlock = current; if (startBlock == null) startBlock = conditionBlock; HInstruction conditionInstruction = condition(); HBasicBlock conditionEndBlock = close(new HLoopBranch(conditionInstruction)); SubExpression conditionExpression = new SubExpression(conditionBlock, conditionEndBlock); // Save the values of the local variables at the end of the condition // block. These are the values that will flow to the loop exit if the // condition fails. LocalsHandler savedLocals = new LocalsHandler.from(localsHandler); // The body. HBasicBlock beginBodyBlock = addNewBlock(); conditionEndBlock.addSuccessor(beginBodyBlock); open(beginBodyBlock); localsHandler.enterLoopBody(loop); body(); SubGraph bodyGraph = new SubGraph(beginBodyBlock, lastOpenedBlock); HBasicBlock bodyBlock = current; if (current != null) close(new HGoto()); SubExpression updateGraph; bool loopIsDegenerate = !jumpHandler.hasAnyContinue() && bodyBlock == null; if (!loopIsDegenerate) { // Update. // We create an update block, even when we are in a while loop. There the // update block is the jump-target for continue statements. We could avoid // the creation if there is no continue, but for now we always create it. HBasicBlock updateBlock = addNewBlock(); List continueHandlers = []; jumpHandler.forEachContinue((HContinue instruction, LocalsHandler locals) { instruction.block.addSuccessor(updateBlock); continueHandlers.add(locals); }); if (bodyBlock != null) { continueHandlers.add(localsHandler); bodyBlock.addSuccessor(updateBlock); } open(updateBlock); localsHandler = continueHandlers[0].mergeMultiple(continueHandlers, updateBlock); HLabeledBlockInformation labelInfo; List labels = jumpHandler.labels(); JumpTarget target = elements.getTargetDefinition(loop); if (!labels.isEmpty) { beginBodyBlock.setBlockFlow( new HLabeledBlockInformation( new HSubGraphBlockInformation(bodyGraph), jumpHandler.labels(), isContinue: true), updateBlock); } else if (target != null && target.isContinueTarget) { beginBodyBlock.setBlockFlow( new HLabeledBlockInformation.implicit( new HSubGraphBlockInformation(bodyGraph), target, isContinue: true), updateBlock); } localsHandler.enterLoopUpdates(loop); update(); HBasicBlock updateEndBlock = close(new HGoto()); // The back-edge completing the cycle. updateEndBlock.addSuccessor(conditionBlock); updateGraph = new SubExpression(updateBlock, updateEndBlock); // Avoid a critical edge from the condition to the loop-exit body. HBasicBlock conditionExitBlock = addNewBlock(); open(conditionExitBlock); close(new HGoto()); conditionEndBlock.addSuccessor(conditionExitBlock); endLoop(conditionBlock, conditionExitBlock, jumpHandler, savedLocals); conditionBlock.postProcessLoopHeader(); HLoopBlockInformation info = new HLoopBlockInformation( HLoopBlockInformation.loopType(loop), wrapExpressionGraph(initializerGraph), wrapExpressionGraph(conditionExpression), wrapStatementGraph(bodyGraph), wrapExpressionGraph(updateGraph), conditionBlock.loopInformation.target, conditionBlock.loopInformation.labels, sourceInformationForBeginEndToken(loop)); startBlock.setBlockFlow(info, current); loopInfo.loopBlockInformation = info; } else { // The body of the for/while loop always aborts, so there is no back edge. // We turn the code into: // if (condition) { // body; // } else { // // We always create an empty else block to avoid critical edges. // } // // If there is any break in the body, we attach a synthetic // label to the if. HBasicBlock elseBlock = addNewBlock(); open(elseBlock); close(new HGoto()); // Pass the elseBlock as the branchBlock, because that's the block we go // to just before leaving the 'loop'. endLoop(conditionBlock, elseBlock, jumpHandler, savedLocals); SubGraph elseGraph = new SubGraph(elseBlock, elseBlock); // Remove the loop information attached to the header. conditionBlock.loopInformation = null; // Remove the [HLoopBranch] instruction and replace it with // [HIf]. HInstruction condition = conditionEndBlock.last.inputs[0]; conditionEndBlock.addAtExit(new HIf(condition)); conditionEndBlock.addSuccessor(elseBlock); conditionEndBlock.remove(conditionEndBlock.last); HIfBlockInformation info = new HIfBlockInformation( wrapExpressionGraph(conditionExpression), wrapStatementGraph(bodyGraph), wrapStatementGraph(elseGraph)); conditionEndBlock.setBlockFlow(info, current); HIf ifBlock = conditionEndBlock.last; ifBlock.blockInformation = conditionEndBlock.blockFlow; // If the body has any break, attach a synthesized label to the // if block. if (jumpHandler.hasAnyBreak()) { JumpTarget target = elements.getTargetDefinition(loop); LabelDefinition label = target.addLabel(null, 'loop'); label.setBreakTarget(); SubGraph labelGraph = new SubGraph(conditionBlock, current); HLabeledBlockInformation labelInfo = new HLabeledBlockInformation( new HSubGraphBlockInformation(labelGraph), [label]); conditionBlock.setBlockFlow(labelInfo, current); jumpHandler.forEachBreak((HBreak breakInstruction, _) { HBasicBlock block = breakInstruction.block; block.addAtExit(new HBreak.toLabel(label)); block.remove(breakInstruction); }); } } jumpHandler.close(); loopNesting--; } visitFor(ast.For node) { assert(isReachable); assert(node.body != null); void buildInitializer() { ast.Node initializer = node.initializer; if (initializer == null) return; visit(initializer); if (initializer.asExpression() != null) { pop(); } } HInstruction buildCondition() { if (node.condition == null) { return graph.addConstantBool(true, compiler); } visit(node.condition); return popBoolified(); } void buildUpdate() { for (ast.Expression expression in node.update) { visit(expression); assert(!isAborted()); // The result of the update instruction isn't used, and can just // be dropped. HInstruction updateInstruction = pop(); } } void buildBody() { visit(node.body); } handleLoop(node, buildInitializer, buildCondition, buildUpdate, buildBody); } visitWhile(ast.While node) { assert(isReachable); HInstruction buildCondition() { visit(node.condition); return popBoolified(); } handleLoop(node, () {}, buildCondition, () {}, () { visit(node.body); }); } visitDoWhile(ast.DoWhile node) { assert(isReachable); LocalsHandler savedLocals = new LocalsHandler.from(localsHandler); localsHandler.startLoop(node); loopNesting++; JumpHandler jumpHandler = beginLoopHeader(node); HLoopInformation loopInfo = current.loopInformation; HBasicBlock loopEntryBlock = current; HBasicBlock bodyEntryBlock = current; JumpTarget target = elements.getTargetDefinition(node); bool hasContinues = target != null && target.isContinueTarget; if (hasContinues) { // Add extra block to hang labels on. // It doesn't currently work if they are on the same block as the // HLoopInfo. The handling of HLabeledBlockInformation will visit a // SubGraph that starts at the same block again, so the HLoopInfo is // either handled twice, or it's handled after the labeled block info, // both of which generate the wrong code. // Using a separate block is just a simple workaround. bodyEntryBlock = openNewBlock(); } localsHandler.enterLoopBody(node); visit(node.body); // If there are no continues we could avoid the creation of the condition // block. This could also lead to a block having multiple entries and exits. HBasicBlock bodyExitBlock; bool isAbortingBody = false; if (current != null) { bodyExitBlock = close(new HGoto()); } else { isAbortingBody = true; bodyExitBlock = lastOpenedBlock; } SubExpression conditionExpression; bool loopIsDegenerate = isAbortingBody && !hasContinues; if (!loopIsDegenerate) { HBasicBlock conditionBlock = addNewBlock(); List continueHandlers = []; jumpHandler.forEachContinue((HContinue instruction, LocalsHandler locals) { instruction.block.addSuccessor(conditionBlock); continueHandlers.add(locals); }); if (!isAbortingBody) { bodyExitBlock.addSuccessor(conditionBlock); } if (!continueHandlers.isEmpty) { if (!isAbortingBody) continueHandlers.add(localsHandler); localsHandler = savedLocals.mergeMultiple(continueHandlers, conditionBlock); SubGraph bodyGraph = new SubGraph(bodyEntryBlock, bodyExitBlock); List labels = jumpHandler.labels(); HSubGraphBlockInformation bodyInfo = new HSubGraphBlockInformation(bodyGraph); HLabeledBlockInformation info; if (!labels.isEmpty) { info = new HLabeledBlockInformation(bodyInfo, labels, isContinue: true); } else { info = new HLabeledBlockInformation.implicit(bodyInfo, target, isContinue: true); } bodyEntryBlock.setBlockFlow(info, conditionBlock); } open(conditionBlock); visit(node.condition); assert(!isAborted()); HInstruction conditionInstruction = popBoolified(); HBasicBlock conditionEndBlock = close( new HLoopBranch(conditionInstruction, HLoopBranch.DO_WHILE_LOOP)); HBasicBlock avoidCriticalEdge = addNewBlock(); conditionEndBlock.addSuccessor(avoidCriticalEdge); open(avoidCriticalEdge); close(new HGoto()); avoidCriticalEdge.addSuccessor(loopEntryBlock); // The back-edge. conditionExpression = new SubExpression(conditionBlock, conditionEndBlock); // Avoid a critical edge from the condition to the loop-exit body. HBasicBlock conditionExitBlock = addNewBlock(); open(conditionExitBlock); close(new HGoto()); conditionEndBlock.addSuccessor(conditionExitBlock); endLoop(loopEntryBlock, conditionExitBlock, jumpHandler, localsHandler); loopEntryBlock.postProcessLoopHeader(); SubGraph bodyGraph = new SubGraph(loopEntryBlock, bodyExitBlock); HLoopBlockInformation loopBlockInfo = new HLoopBlockInformation( HLoopBlockInformation.DO_WHILE_LOOP, null, wrapExpressionGraph(conditionExpression), wrapStatementGraph(bodyGraph), null, loopEntryBlock.loopInformation.target, loopEntryBlock.loopInformation.labels, sourceInformationForBeginEndToken(node)); loopEntryBlock.setBlockFlow(loopBlockInfo, current); loopInfo.loopBlockInformation = loopBlockInfo; } else { // Since the loop has no back edge, we remove the loop information on the // header. loopEntryBlock.loopInformation = null; if (jumpHandler.hasAnyBreak()) { // Null branchBlock because the body of the do-while loop always aborts, // so we never get to the condition. endLoop(loopEntryBlock, null, jumpHandler, localsHandler); // Since the body of the loop has a break, we attach a synthesized label // to the body. SubGraph bodyGraph = new SubGraph(bodyEntryBlock, bodyExitBlock); JumpTarget target = elements.getTargetDefinition(node); LabelDefinition label = target.addLabel(null, 'loop'); label.setBreakTarget(); HLabeledBlockInformation info = new HLabeledBlockInformation( new HSubGraphBlockInformation(bodyGraph), [label]); loopEntryBlock.setBlockFlow(info, current); jumpHandler.forEachBreak((HBreak breakInstruction, _) { HBasicBlock block = breakInstruction.block; block.addAtExit(new HBreak.toLabel(label)); block.remove(breakInstruction); }); } } jumpHandler.close(); loopNesting--; } visitFunctionExpression(ast.FunctionExpression node) { ClosureClassMap nestedClosureData = compiler.closureToClassMapper.getMappingForNestedFunction(node); assert(nestedClosureData != null); assert(nestedClosureData.closureClassElement != null); ClosureClassElement closureClassElement = nestedClosureData.closureClassElement; FunctionElement callElement = nestedClosureData.callElement; // TODO(ahe): This should be registered in codegen, not here. // TODO(johnniwinther): Is [registerStaticUse] equivalent to // [addToWorkList]? registry.registerStaticUse(callElement); // TODO(ahe): This should be registered in codegen, not here. registry.registerInstantiatedClass(closureClassElement); List capturedVariables = []; closureClassElement.closureFields.forEach((ClosureFieldElement field) { Local capturedLocal = nestedClosureData.getLocalVariableForClosureField(field); assert(capturedLocal != null); capturedVariables.add(localsHandler.readLocal(capturedLocal)); }); TypeMask type = new TypeMask.nonNullExact(compiler.functionClass, compiler.world); push(new HForeignNew(closureClassElement, type, capturedVariables)); Element methodElement = nestedClosureData.closureElement; if (compiler.backend.methodNeedsRti(methodElement)) { registry.registerClosureWithFreeTypeVariables(methodElement); } } visitFunctionDeclaration(ast.FunctionDeclaration node) { assert(isReachable); visit(node.function); LocalFunctionElement localFunction = elements.getFunctionDefinition(node.function); localsHandler.updateLocal(localFunction, pop()); } visitIdentifier(ast.Identifier node) { if (node.isThis()) { stack.add(localsHandler.readThis()); } else { compiler.internalError(node, "SsaFromAstMixin.visitIdentifier on non-this."); } } visitIf(ast.If node) { assert(isReachable); handleIf(node, () => visit(node.condition), () => visit(node.thenPart), node.elsePart != null ? () => visit(node.elsePart) : null); } void handleIf(ast.Node diagnosticNode, void visitCondition(), void visitThen(), void visitElse()) { SsaBranchBuilder branchBuilder = new SsaBranchBuilder(this, diagnosticNode); branchBuilder.handleIf(visitCondition, visitThen, visitElse); } void visitLogicalAndOr(ast.Send node, ast.Operator op) { SsaBranchBuilder branchBuilder = new SsaBranchBuilder(this, node); branchBuilder.handleLogicalAndOrWithLeftNode( node.receiver, () { visit(node.argumentsNode); }, isAnd: ("&&" == op.source)); } void visitLogicalNot(ast.Send node) { assert(node.argumentsNode is ast.Prefix); visit(node.receiver); HNot not = new HNot(popBoolified(), backend.boolType); pushWithPosition(not, node); } void visitUnarySend(ast.Send node, ast.Operator op) { assert(node.argumentsNode is ast.Prefix); visit(node.receiver); assert(!identical(op.token.kind, PLUS_TOKEN)); HInstruction operand = pop(); // See if we can constant-fold right away. This avoids rewrites later on. if (operand is HConstant) { UnaryOperation operation = constantSystem.lookupUnary(op.source); HConstant constant = operand; ConstantValue folded = operation.fold(constant.constant); if (folded != null) { stack.add(graph.addConstant(folded, compiler)); return; } } pushInvokeDynamic(node, elements.getSelector(node), [operand]); } void visitBinarySend(HInstruction left, ast.Operator op, HInstruction right, Selector selector, ast.Send send) { switch (op.source) { case "===": pushWithPosition( new HIdentity(left, right, null, backend.boolType), op); return; case "!==": HIdentity eq = new HIdentity(left, right, null, backend.boolType); add(eq); pushWithPosition(new HNot(eq, backend.boolType), op); return; } pushInvokeDynamic(send, selector, [left, right], location: op); if (op.source == '!=') { pushWithPosition(new HNot(popBoolified(), backend.boolType), op); } } HInstruction generateInstanceSendReceiver(ast.Send send) { assert(Elements.isInstanceSend(send, elements)); if (send.receiver == null) { return localsHandler.readThis(); } visit(send.receiver); return pop(); } String noSuchMethodTargetSymbolString(ErroneousElement error, [String prefix]) { String result = error.name; if (prefix == "set") return "$result="; return result; } /** * Returns a set of interceptor classes that contain the given * [selector]. */ void generateInstanceGetterWithCompiledReceiver(ast.Send send, Selector selector, HInstruction receiver) { assert(Elements.isInstanceSend(send, elements)); assert(selector.isGetter); pushInvokeDynamic(send, selector, [receiver]); } /// Inserts a call to checkDeferredIsLoaded if the send has a prefix that /// resolves to a deferred library. void generateIsDeferredLoadedCheckIfNeeded(ast.Send node) { DeferredLoadTask deferredTask = compiler.deferredLoadTask; PrefixElement prefixElement = deferredTask.deferredPrefixElement(node, elements); if (prefixElement != null) { String loadId = deferredTask.importDeferName[prefixElement.deferredImport]; HInstruction loadIdConstant = addConstantString(loadId); String uri = prefixElement.deferredImport.uri.dartString.slowToString(); HInstruction uriConstant = addConstantString(uri); Element helper = backend.getCheckDeferredIsLoaded(); pushInvokeStatic(node, helper, [loadIdConstant, uriConstant]); pop(); } } void generateGetter(ast.Send send, Element element) { if (element != null && element.isForeign(backend)) { visitForeignGetter(send); } else if (Elements.isStaticOrTopLevelField(element)) { ConstantExpression constant; if (element.isField && !element.isAssignable) { // A static final or const. Get its constant value and inline it if // the value can be compiled eagerly. constant = backend.constants.getConstantForVariable(element); } if (constant != null) { ConstantValue value = constant.value; HConstant instruction; // Constants that are referred via a deferred prefix should be referred // by reference. PrefixElement prefix = compiler.deferredLoadTask .deferredPrefixElement(send, elements); if (prefix != null) { instruction = graph.addDeferredConstant(value, prefix, compiler); } else { instruction = graph.addConstant(value, compiler); } stack.add(instruction); // The inferrer may have found a better type than the constant // handler in the case of lists, because the constant handler // does not look at elements in the list. TypeMask type = TypeMaskFactory.inferredTypeForElement(element, compiler); if (!type.containsAll(compiler.world) && !instruction.isConstantNull()) { // TODO(13429): The inferrer should know that an element // cannot be null. instruction.instructionType = type.nonNullable(); } } else if (element.isField && isLazilyInitialized(element)) { HInstruction instruction = new HLazyStatic( element, TypeMaskFactory.inferredTypeForElement(element, compiler)); push(instruction); } else { if (element.isGetter) { pushInvokeStatic(send, element, []); } else { // TODO(5346): Try to avoid the need for calling [declaration] before // creating an [HStatic]. HInstruction instruction = new HStatic( element.declaration, TypeMaskFactory.inferredTypeForElement(element, compiler)); push(instruction); } } } else if (Elements.isInstanceSend(send, elements)) { HInstruction receiver = generateInstanceSendReceiver(send); generateInstanceGetterWithCompiledReceiver( send, elements.getSelector(send), receiver); } else if (Elements.isStaticOrTopLevelFunction(element)) { // TODO(5346): Try to avoid the need for calling [declaration] before // creating an [HStatic]. push(new HStatic(element.declaration, backend.nonNullType)); // TODO(ahe): This should be registered in codegen. registry.registerGetOfStaticFunction(element.declaration); } else if (Elements.isErroneous(element)) { if (element is ErroneousElement) { // An erroneous element indicates an unresolved static getter. generateThrowNoSuchMethod( send, noSuchMethodTargetSymbolString(element, 'get'), argumentNodes: const Link()); } else { // TODO(ahe): Do something like the above, that is, emit a runtime // error. stack.add(graph.addConstantNull(compiler)); } } else { LocalElement local = element; stack.add(localsHandler.readLocal(local)); } } void generateInstanceSetterWithCompiledReceiver(ast.Send send, HInstruction receiver, HInstruction value, {Selector selector, ast.Node location}) { assert(send == null || Elements.isInstanceSend(send, elements)); if (selector == null) { assert(send != null); selector = elements.getSelector(send); } if (location == null) { assert(send != null); location = send; } assert(selector.isSetter); pushInvokeDynamic(location, selector, [receiver, value]); pop(); stack.add(value); } void generateNonInstanceSetter(ast.SendSet send, Element element, HInstruction value, {ast.Node location}) { assert(send == null || !Elements.isInstanceSend(send, elements)); if (location == null) { assert(send != null); location = send; } if (Elements.isStaticOrTopLevelField(element)) { if (element.isSetter) { pushInvokeStatic(location, element, [value]); pop(); } else { VariableElement field = element; value = potentiallyCheckOrTrustType(value, field.type); addWithPosition(new HStaticStore(element, value), location); } stack.add(value); } else if (Elements.isErroneous(element)) { if (element is ErroneousElement) { List arguments = send == null ? const [] : [value]; // An erroneous element indicates an unresolved static setter. generateThrowNoSuchMethod( location, noSuchMethodTargetSymbolString(element, 'set'), argumentValues: arguments); } else { // TODO(ahe): Do something like [generateWrongArgumentCountError]. stack.add(graph.addConstantNull(compiler)); } } else { stack.add(value); LocalElement local = element; // If the value does not already have a name, give it here. if (value.sourceElement == null) { value.sourceElement = local; } HInstruction checkedOrTrusted = potentiallyCheckOrTrustType(value, local.type); if (!identical(checkedOrTrusted, value)) { pop(); stack.add(checkedOrTrusted); } localsHandler.updateLocal(local, checkedOrTrusted); } } HInstruction invokeInterceptor(HInstruction receiver) { HInterceptor interceptor = new HInterceptor(receiver, backend.nonNullType); add(interceptor); return interceptor; } HForeignCode createForeign(js.Template code, TypeMask type, List inputs) { return new HForeignCode(code, type, inputs); } HLiteralList buildLiteralList(List inputs) { return new HLiteralList(inputs, backend.extendableArrayType); } // TODO(karlklose): change construction of the representations to be GVN'able // (dartbug.com/7182). HInstruction buildTypeArgumentRepresentations(DartType type) { // Compute the representation of the type arguments, including access // to the runtime type information for type variables as instructions. if (type.isTypeVariable) { return buildLiteralList([addTypeVariableReference(type)]); } else { assert(type.element.isClass); InterfaceType interface = type; List inputs = []; bool first = true; List templates = []; for (DartType argument in interface.typeArguments) { templates.add(rti.getTypeRepresentationWithHashes(argument, (variable) { HInstruction runtimeType = addTypeVariableReference(variable); inputs.add(runtimeType); })); } String template = '[${templates.join(', ')}]'; // TODO(sra): This is a fresh template each time. We can't let the // template manager build them. js.Template code = js.js.uncachedExpressionTemplate(template); HInstruction representation = createForeign(code, backend.readableArrayType, inputs); return representation; } } visitOperatorSend(ast.Send node) { ast.Operator op = node.selector; if ("[]" == op.source) { visitDynamicSend(node); } else if ("&&" == op.source || "||" == op.source) { visitLogicalAndOr(node, op); } else if ("!" == op.source) { visitLogicalNot(node); } else if (node.argumentsNode is ast.Prefix) { visitUnarySend(node, op); } else if ("is" == op.source) { visitIsSend(node); } else if ("as" == op.source) { visit(node.receiver); HInstruction expression = pop(); DartType type = elements.getType(node.typeAnnotationFromIsCheckOrCast); if (type.isMalformed) { ErroneousElement element = type.element; generateTypeError(node, element.message); } else { HInstruction converted = buildTypeConversion( expression, localsHandler.substInContext(type), HTypeConversion.CAST_TYPE_CHECK); if (converted != expression) add(converted); stack.add(converted); } } else { visit(node.receiver); visit(node.argumentsNode); var right = pop(); var left = pop(); visitBinarySend(left, op, right, elements.getSelector(node), node); } } void visitIsSend(ast.Send node) { visit(node.receiver); HInstruction expression = pop(); bool isNot = node.isIsNotCheck; DartType type = elements.getType(node.typeAnnotationFromIsCheckOrCast); HInstruction instruction = buildIsNode(node, type, expression); if (isNot) { add(instruction); instruction = new HNot(instruction, backend.boolType); } push(instruction); } HInstruction buildIsNode(ast.Node node, DartType type, HInstruction expression) { type = localsHandler.substInContext(type).unalias(compiler); if (type.isFunctionType) { List arguments = [buildFunctionType(type), expression]; pushInvokeDynamic( node, new Selector.call('_isTest', backend.jsHelperLibrary, 1), arguments); return new HIs.compound(type, expression, pop(), backend.boolType); } else if (type.isTypeVariable) { HInstruction runtimeType = addTypeVariableReference(type); Element helper = backend.getCheckSubtypeOfRuntimeType(); List inputs = [expression, runtimeType]; pushInvokeStatic(null, helper, inputs, backend.boolType); HInstruction call = pop(); return new HIs.variable(type, expression, call, backend.boolType); } else if (RuntimeTypes.hasTypeArguments(type)) { ClassElement element = type.element; Element helper = backend.getCheckSubtype(); HInstruction representations = buildTypeArgumentRepresentations(type); add(representations); String operator = backend.namer.operatorIs(element); HInstruction isFieldName = addConstantString(operator); HInstruction asFieldName = compiler.world.hasAnyStrictSubtype(element) ? addConstantString(backend.namer.substitutionName(element)) : graph.addConstantNull(compiler); List inputs = [expression, isFieldName, representations, asFieldName]; pushInvokeStatic(node, helper, inputs, backend.boolType); HInstruction call = pop(); return new HIs.compound(type, expression, call, backend.boolType); } else if (type.isMalformed) { ErroneousElement element = type.element; generateTypeError(node, element.message); HInstruction call = pop(); return new HIs.compound(type, expression, call, backend.boolType); } else { if (backend.hasDirectCheckFor(type)) { return new HIs.direct(type, expression, backend.boolType); } // The interceptor is not always needed. It is removed by optimization // when the receiver type or tested type permit. return new HIs.raw( type, expression, invokeInterceptor(expression), backend.boolType); } } HInstruction buildFunctionType(FunctionType type) { type.accept(new TypeBuilder(compiler.world), this); return pop(); } void addDynamicSendArgumentsToList(ast.Send node, List list) { CallStructure callStructure = elements.getSelector(node).callStructure; if (callStructure.namedArgumentCount == 0) { addGenericSendArgumentsToList(node.arguments, list); } else { // Visit positional arguments and add them to the list. Link arguments = node.arguments; int positionalArgumentCount = callStructure.positionalArgumentCount; for (int i = 0; i < positionalArgumentCount; arguments = arguments.tail, i++) { visit(arguments.head); list.add(pop()); } // Visit named arguments and add them into a temporary map. Map instructions = new Map(); List namedArguments = callStructure.namedArguments; int nameIndex = 0; for (; !arguments.isEmpty; arguments = arguments.tail) { visit(arguments.head); instructions[namedArguments[nameIndex++]] = pop(); } // Iterate through the named arguments to add them to the list // of instructions, in an order that can be shared with // selectors with the same named arguments. List orderedNames = callStructure.getOrderedNamedArguments(); for (String name in orderedNames) { list.add(instructions[name]); } } } /** * Returns a list with the evaluated [arguments] in the normalized order. * * Precondition: `this.applies(element, world)`. * Invariant: [element] must be an implementation element. */ List makeStaticArgumentList(CallStructure callStructure, Link arguments, FunctionElement element) { assert(invariant(element, element.isImplementation)); HInstruction compileArgument(ast.Node argument) { visit(argument); return pop(); } return callStructure.makeArgumentsList( arguments, element, compileArgument, handleConstantForOptionalParameter); } void addGenericSendArgumentsToList(Link link, List list) { for (; !link.isEmpty; link = link.tail) { visit(link.head); list.add(pop()); } } visitDynamicSend(ast.Send node) { Selector selector = elements.getSelector(node); List inputs = []; HInstruction receiver = generateInstanceSendReceiver(node); inputs.add(receiver); addDynamicSendArgumentsToList(node, inputs); pushInvokeDynamic(node, selector, inputs); if (selector.isSetter || selector.isIndexSet) { pop(); stack.add(inputs.last); } } visitClosureSend(ast.Send node) { Selector selector = elements.getSelector(node); assert(node.receiver == null); Element element = elements[node]; HInstruction closureTarget; if (element == null) { visit(node.selector); closureTarget = pop(); } else { LocalElement local = element; closureTarget = localsHandler.readLocal(local); } var inputs = []; inputs.add(closureTarget); addDynamicSendArgumentsToList(node, inputs); Selector closureSelector = new Selector.callClosureFrom(selector); pushWithPosition( new HInvokeClosure(closureSelector, inputs, backend.dynamicType), node); } void handleForeignJs(ast.Send node) { Link link = node.arguments; // If the invoke is on foreign code, don't visit the first // argument, which is the type, and the second argument, // which is the foreign code. if (link.isEmpty || link.tail.isEmpty) { compiler.internalError(node.argumentsNode, 'At least two arguments expected.'); } native.NativeBehavior nativeBehavior = compiler.enqueuer.resolution.nativeEnqueuer.getNativeBehaviorOf(node); List inputs = []; addGenericSendArgumentsToList(link.tail.tail, inputs); TypeMask ssaType = TypeMaskFactory.fromNativeBehavior(nativeBehavior, compiler); if (nativeBehavior.codeTemplate.isExpression) { push(new HForeignCode(nativeBehavior.codeTemplate, ssaType, inputs, effects: nativeBehavior.sideEffects, nativeBehavior: nativeBehavior)); } else { push(new HForeignCode(nativeBehavior.codeTemplate, ssaType, inputs, isStatement: true, effects: nativeBehavior.sideEffects, nativeBehavior: nativeBehavior, canThrow: true)); } } void handleJsStringConcat(ast.Send node) { List inputs = []; addGenericSendArgumentsToList(node.arguments, inputs); if (inputs.length != 2) { compiler.internalError(node.argumentsNode, 'Two arguments expected.'); } push(new HStringConcat(inputs[0], inputs[1], node, backend.stringType)); } void handleForeignJsCurrentIsolateContext(ast.Send node) { if (!node.arguments.isEmpty) { compiler.internalError(node, 'Too many arguments to JS_CURRENT_ISOLATE_CONTEXT.'); } if (!compiler.hasIsolateSupport) { // If the isolate library is not used, we just generate code // to fetch the current isolate. String name = backend.namer.currentIsolate; push(new HForeignCode(js.js.parseForeignJS(name), backend.dynamicType, [])); } else { // Call a helper method from the isolate library. The isolate // library uses its own isolate structure, that encapsulates // Leg's isolate. Element element = backend.isolateHelperLibrary.find('_currentIsolate'); if (element == null) { compiler.internalError(node, 'Isolate library and compiler mismatch.'); } pushInvokeStatic(null, element, [], backend.dynamicType); } } void handleForeingJsGetFlag(ast.Send node) { List arguments = node.arguments.toList(); ast.Node argument; switch (arguments.length) { case 0: compiler.reportError( node, MessageKind.GENERIC, {'text': 'Error: Expected one argument to JS_GET_FLAG.'}); return; case 1: argument = arguments[0]; break; default: for (int i = 1; i < arguments.length; i++) { compiler.reportError( arguments[i], MessageKind.GENERIC, {'text': 'Error: Extra argument to JS_GET_FLAG.'}); } return; } ast.LiteralString string = argument.asLiteralString(); if (string == null) { compiler.reportError( argument, MessageKind.GENERIC, {'text': 'Error: Expected a literal string.'}); } String name = string.dartString.slowToString(); bool value = false; switch (name) { case 'MUST_RETAIN_METADATA': value = backend.mustRetainMetadata; break; case 'USE_CONTENT_SECURITY_POLICY': value = compiler.useContentSecurityPolicy; break; default: compiler.reportError( node, MessageKind.GENERIC, {'text': 'Error: Unknown internal flag "$name".'}); } stack.add(graph.addConstantBool(value, compiler)); } void handleForeignJsGetName(ast.Send node) { List arguments = node.arguments.toList(); ast.Node argument; switch (arguments.length) { case 0: compiler.reportError( node, MessageKind.GENERIC, {'text': 'Error: Expected one argument to JS_GET_NAME.'}); return; case 1: argument = arguments[0]; break; default: for (int i = 1; i < arguments.length; i++) { compiler.reportError( arguments[i], MessageKind.GENERIC, {'text': 'Error: Extra argument to JS_GET_NAME.'}); } return; } Element element = elements[argument]; if (element == null || element is! FieldElement || element.enclosingClass != backend.jsGetNameEnum) { compiler.reportError( argument, MessageKind.GENERIC, {'text': 'Error: Expected a JsGetName enum value.'}); } EnumClassElement enumClass = element.enclosingClass; int index = enumClass.enumValues.indexOf(element); stack.add( addConstantString( backend.namer.getNameForJsGetName( argument, JsGetName.values[index]))); } void handleForeignJsEmbeddedGlobal(ast.Send node) { List arguments = node.arguments.toList(); ast.Node globalNameNode; switch (arguments.length) { case 0: case 1: compiler.reportError( node, MessageKind.GENERIC, {'text': 'Error: Expected two arguments to JS_EMBEDDED_GLOBAL.'}); return; case 2: // The type has been extracted earlier. We are only interested in the // name in this function. globalNameNode = arguments[1]; break; default: for (int i = 2; i < arguments.length; i++) { compiler.reportError( arguments[i], MessageKind.GENERIC, {'text': 'Error: Extra argument to JS_EMBEDDED_GLOBAL.'}); } return; } visit(arguments[1]); HInstruction globalNameHNode = pop(); if (!globalNameHNode.isConstantString()) { compiler.reportError( arguments[1], MessageKind.GENERIC, {'text': 'Error: Expected String as second argument ' 'to JS_EMBEDDED_GLOBAL.'}); return; } HConstant hConstant = globalNameHNode; StringConstantValue constant = hConstant.constant; String globalName = constant.primitiveValue.slowToString(); js.Template expr = js.js.expressionTemplateYielding( backend.emitter.generateEmbeddedGlobalAccess(globalName)); native.NativeBehavior nativeBehavior = compiler.enqueuer.resolution.nativeEnqueuer.getNativeBehaviorOf(node); TypeMask ssaType = TypeMaskFactory.fromNativeBehavior(nativeBehavior, compiler); push(new HForeignCode(expr, ssaType, const [])); } void handleJsInterceptorConstant(ast.Send node) { // Single argument must be a TypeConstant which is converted into a // InterceptorConstant. if (!node.arguments.isEmpty && node.arguments.tail.isEmpty) { ast.Node argument = node.arguments.head; visit(argument); HInstruction argumentInstruction = pop(); if (argumentInstruction is HConstant) { ConstantValue argumentConstant = argumentInstruction.constant; if (argumentConstant is TypeConstantValue) { ConstantValue constant = new InterceptorConstantValue(argumentConstant.representedType); HInstruction instruction = graph.addConstant(constant, compiler); stack.add(instruction); return; } } } compiler.reportError(node, MessageKind.WRONG_ARGUMENT_FOR_JS_INTERCEPTOR_CONSTANT); stack.add(graph.addConstantNull(compiler)); } void handleForeignJsCallInIsolate(ast.Send node) { Link link = node.arguments; if (!compiler.hasIsolateSupport) { // If the isolate library is not used, we just invoke the // closure. visit(link.tail.head); Selector selector = new Selector.callClosure(0); push(new HInvokeClosure(selector, [pop()], backend.dynamicType)); } else { // Call a helper method from the isolate library. Element element = backend.isolateHelperLibrary.find('_callInIsolate'); if (element == null) { compiler.internalError(node, 'Isolate library and compiler mismatch.'); } List inputs = []; addGenericSendArgumentsToList(link, inputs); pushInvokeStatic(node, element, inputs, backend.dynamicType); } } FunctionSignature handleForeignRawFunctionRef(ast.Send node, String name) { if (node.arguments.isEmpty || !node.arguments.tail.isEmpty) { compiler.internalError(node.argumentsNode, '"$name" requires exactly one argument.'); } ast.Node closure = node.arguments.head; Element element = elements[closure]; if (!Elements.isStaticOrTopLevelFunction(element)) { compiler.internalError(closure, '"$name" requires a static or top-level method.'); } FunctionElement function = element; // TODO(johnniwinther): Try to eliminate the need to distinguish declaration // and implementation signatures. Currently it is need because the // signatures have different elements for parameters. FunctionElement implementation = function.implementation; FunctionSignature params = implementation.functionSignature; if (params.optionalParameterCount != 0) { compiler.internalError(closure, '"$name" does not handle closure with optional parameters.'); } registry.registerStaticUse(element); push(new HForeignCode(js.js.expressionTemplateYielding( backend.emitter.staticFunctionAccess(element)), backend.dynamicType, [])); return params; } void handleForeignDartClosureToJs(ast.Send node, String name) { // TODO(ahe): This implements DART_CLOSURE_TO_JS and should probably take // care to wrap the closure in another closure that saves the current // isolate. handleForeignRawFunctionRef(node, name); } void handleForeignSetCurrentIsolate(ast.Send node) { if (node.arguments.isEmpty || !node.arguments.tail.isEmpty) { compiler.internalError(node.argumentsNode, 'Exactly one argument required.'); } visit(node.arguments.head); String isolateName = backend.namer.currentIsolate; SideEffects sideEffects = new SideEffects.empty(); sideEffects.setAllSideEffects(); push(new HForeignCode(js.js.parseForeignJS("$isolateName = #"), backend.dynamicType, [pop()], effects: sideEffects)); } void handleForeignDartObjectJsConstructorFunction(ast.Send node) { if (!node.arguments.isEmpty) { compiler.internalError(node.argumentsNode, 'Too many arguments.'); } push(new HForeignCode(js.js.expressionTemplateYielding( backend.emitter.typeAccess(compiler.objectClass)), backend.dynamicType, [])); } void handleForeignJsCurrentIsolate(ast.Send node) { if (!node.arguments.isEmpty) { compiler.internalError(node.argumentsNode, 'Too many arguments.'); } push(new HForeignCode(js.js.parseForeignJS(backend.namer.currentIsolate), backend.dynamicType, [])); } visitForeignSend(ast.Send node) { Selector selector = elements.getSelector(node); String name = selector.name; if (name == 'JS') { handleForeignJs(node); } else if (name == 'JS_CURRENT_ISOLATE_CONTEXT') { handleForeignJsCurrentIsolateContext(node); } else if (name == 'JS_CALL_IN_ISOLATE') { handleForeignJsCallInIsolate(node); } else if (name == 'DART_CLOSURE_TO_JS') { handleForeignDartClosureToJs(node, 'DART_CLOSURE_TO_JS'); } else if (name == 'RAW_DART_FUNCTION_REF') { handleForeignRawFunctionRef(node, 'RAW_DART_FUNCTION_REF'); } else if (name == 'JS_SET_CURRENT_ISOLATE') { handleForeignSetCurrentIsolate(node); } else if (name == 'JS_OPERATOR_IS_PREFIX') { // TODO(floitsch): this should be a JS_NAME. stack.add(addConstantString(backend.namer.operatorIsPrefix)); } else if (name == 'JS_OBJECT_CLASS_NAME') { // TODO(floitsch): this should be a JS_NAME. String name = backend.namer.runtimeTypeName(compiler.objectClass); stack.add(addConstantString(name)); } else if (name == 'JS_NULL_CLASS_NAME') { // TODO(floitsch): this should be a JS_NAME. String name = backend.namer.runtimeTypeName(compiler.nullClass); stack.add(addConstantString(name)); } else if (name == 'JS_FUNCTION_CLASS_NAME') { // TODO(floitsch): this should be a JS_NAME. String name = backend.namer.runtimeTypeName(compiler.functionClass); stack.add(addConstantString(name)); } else if (name == 'JS_OPERATOR_AS_PREFIX') { // TODO(floitsch): this should be a JS_NAME. stack.add(addConstantString(backend.namer.operatorAsPrefix)); } else if (name == 'JS_SIGNATURE_NAME') { // TODO(floitsch): this should be a JS_NAME. stack.add(addConstantString(backend.namer.operatorSignature)); } else if (name == 'JS_TYPEDEF_TAG') { // TODO(floitsch): this should be a JS_NAME. stack.add(addConstantString(backend.namer.typedefTag)); } else if (name == 'JS_FUNCTION_TYPE_TAG') { // TODO(floitsch): this should be a JS_NAME. stack.add(addConstantString(backend.namer.functionTypeTag)); } else if (name == 'JS_FUNCTION_TYPE_VOID_RETURN_TAG') { // TODO(floitsch): this should be a JS_NAME. stack.add(addConstantString(backend.namer.functionTypeVoidReturnTag)); } else if (name == 'JS_FUNCTION_TYPE_RETURN_TYPE_TAG') { // TODO(floitsch): this should be a JS_NAME. stack.add(addConstantString(backend.namer.functionTypeReturnTypeTag)); } else if (name == 'JS_FUNCTION_TYPE_REQUIRED_PARAMETERS_TAG') { // TODO(floitsch): this should be a JS_NAME. stack.add(addConstantString( backend.namer.functionTypeRequiredParametersTag)); } else if (name == 'JS_FUNCTION_TYPE_OPTIONAL_PARAMETERS_TAG') { // TODO(floitsch): this should be a JS_NAME. stack.add(addConstantString( backend.namer.functionTypeOptionalParametersTag)); } else if (name == 'JS_FUNCTION_TYPE_NAMED_PARAMETERS_TAG') { // TODO(floitsch): this should be a JS_NAME. stack.add(addConstantString( backend.namer.functionTypeNamedParametersTag)); } else if (name == 'JS_DART_OBJECT_CONSTRUCTOR') { handleForeignDartObjectJsConstructorFunction(node); } else if (name == 'JS_IS_INDEXABLE_FIELD_NAME') { // TODO(floitsch): this should be a JS_NAME. Element element = backend.findHelper('JavaScriptIndexingBehavior'); stack.add(addConstantString(backend.namer.operatorIs(element))); } else if (name == 'JS_CURRENT_ISOLATE') { handleForeignJsCurrentIsolate(node); } else if (name == 'JS_GET_NAME') { handleForeignJsGetName(node); } else if (name == 'JS_EMBEDDED_GLOBAL') { handleForeignJsEmbeddedGlobal(node); } else if (name == 'JS_GET_FLAG') { handleForeingJsGetFlag(node); } else if (name == 'JS_EFFECT') { stack.add(graph.addConstantNull(compiler)); } else if (name == 'JS_INTERCEPTOR_CONSTANT') { handleJsInterceptorConstant(node); } else if (name == 'JS_STRING_CONCAT') { handleJsStringConcat(node); } else { throw "Unknown foreign: ${selector}"; } } visitForeignGetter(ast.Send node) { Element element = elements[node]; // Until now we only handle these as getters. invariant(node, element.isDeferredLoaderGetter); FunctionElement deferredLoader = element; Element loadFunction = compiler.loadLibraryFunction; PrefixElement prefixElement = deferredLoader.enclosingElement; String loadId = compiler.deferredLoadTask .importDeferName[prefixElement.deferredImport]; var inputs = [graph.addConstantString( new ast.DartString.literal(loadId), compiler)]; push(new HInvokeStatic(loadFunction, inputs, backend.nonNullType, targetCanThrow: false)); } generateSuperNoSuchMethodSend(ast.Send node, Selector selector, List arguments) { String name = selector.name; ClassElement cls = currentNonClosureClass; Element element = cls.lookupSuperMember(Compiler.NO_SUCH_METHOD); if (compiler.enabledInvokeOn && element.enclosingElement.declaration != compiler.objectClass) { // Register the call as dynamic if [noSuchMethod] on the super // class is _not_ the default implementation from [Object], in // case the [noSuchMethod] implementation calls // [JSInvocationMirror._invokeOn]. registry.registerSelectorUse(selector.asUntyped); } String publicName = name; if (selector.isSetter) publicName += '='; ConstantValue nameConstant = constantSystem.createString( new ast.DartString.literal(publicName)); String internalName = backend.namer.invocationName(selector); ConstantValue internalNameConstant = constantSystem.createString(new ast.DartString.literal(internalName)); Element createInvocationMirror = backend.getCreateInvocationMirror(); var argumentsInstruction = buildLiteralList(arguments); add(argumentsInstruction); var argumentNames = new List(); for (String argumentName in selector.namedArguments) { ConstantValue argumentNameConstant = constantSystem.createString(new ast.DartString.literal(argumentName)); argumentNames.add(graph.addConstant(argumentNameConstant, compiler)); } var argumentNamesInstruction = buildLiteralList(argumentNames); add(argumentNamesInstruction); ConstantValue kindConstant = constantSystem.createInt(selector.invocationMirrorKind); pushInvokeStatic(null, createInvocationMirror, [graph.addConstant(nameConstant, compiler), graph.addConstant(internalNameConstant, compiler), graph.addConstant(kindConstant, compiler), argumentsInstruction, argumentNamesInstruction], backend.dynamicType); var inputs = [pop()]; push(buildInvokeSuper(compiler.noSuchMethodSelector, element, inputs)); } visitSuperSend(ast.Send node) { Selector selector = elements.getSelector(node); Element element = elements[node]; if (Elements.isUnresolved(element)) { List arguments = []; if (!node.isPropertyAccess) { addGenericSendArgumentsToList(node.arguments, arguments); } return generateSuperNoSuchMethodSend(node, selector, arguments); } List inputs = []; if (node.isPropertyAccess) { push(buildInvokeSuper(selector, element, inputs)); } else if (element.isFunction || element.isGenerativeConstructor) { if (selector.applies(element, compiler.world)) { // TODO(5347): Try to avoid the need for calling [implementation] before // calling [makeStaticArgumentList]. FunctionElement function = element.implementation; assert(selector.applies(function, compiler.world)); inputs = makeStaticArgumentList(selector.callStructure, node.arguments, function); push(buildInvokeSuper(selector, element, inputs)); } else if (element.isGenerativeConstructor) { generateWrongArgumentCountError(node, element, node.arguments); } else { addGenericSendArgumentsToList(node.arguments, inputs); generateSuperNoSuchMethodSend(node, selector, inputs); } } else { HInstruction target = buildInvokeSuper(selector, element, inputs); add(target); inputs = [target]; addDynamicSendArgumentsToList(node, inputs); Selector closureSelector = new Selector.callClosureFrom(selector); push(new HInvokeClosure(closureSelector, inputs, backend.dynamicType)); } } bool needsSubstitutionForTypeVariableAccess(ClassElement cls) { ClassWorld classWorld = compiler.world; if (classWorld.isUsedAsMixin(cls)) return true; Iterable subclasses = compiler.world.strictSubclassesOf(cls); return subclasses.any((ClassElement subclass) { return !rti.isTrivialSubstitution(subclass, cls); }); } /** * Generate code to extract the type arguments from the object, substitute * them as an instance of the type we are testing against (if necessary), and * extract the type argument by the index of the variable in the list of type * variables for that class. */ HInstruction readTypeVariable(ClassElement cls, TypeVariableElement variable) { assert(sourceElement.isInstanceMember); HInstruction target = localsHandler.readThis(); HConstant index = graph.addConstantInt( RuntimeTypes.getTypeVariableIndex(variable), compiler); if (needsSubstitutionForTypeVariableAccess(cls)) { // TODO(ahe): Creating a string here is unfortunate. It is slow (due to // string concatenation in the implementation), and may prevent // segmentation of '$'. String substitutionNameString = backend.namer.runtimeTypeName(cls); HInstruction substitutionName = graph.addConstantString( new ast.LiteralDartString(substitutionNameString), compiler); pushInvokeStatic(null, backend.getGetRuntimeTypeArgument(), [target, substitutionName, index], backend.dynamicType); } else { pushInvokeStatic(null, backend.getGetTypeArgumentByIndex(), [target, index], backend.dynamicType); } return pop(); } // TODO(karlklose): this is needed to avoid a bug where the resolved type is // not stored on a type annotation in the closure translator. Remove when // fixed. bool hasDirectLocal(Local local) { return !localsHandler.isAccessedDirectly(local) || localsHandler.directLocals[local] != null; } /** * Helper to create an instruction that gets the value of a type variable. */ HInstruction addTypeVariableReference(TypeVariableType type) { assert(assertTypeInContext(type)); Element member = sourceElement; bool isClosure = member.enclosingElement.isClosure; if (isClosure) { ClosureClassElement closureClass = member.enclosingElement; member = closureClass.methodElement; member = member.outermostEnclosingMemberOrTopLevel; } bool isInConstructorContext = member.isConstructor || member.isGenerativeConstructorBody; Local typeVariableLocal = localsHandler.getTypeVariableAsLocal(type); if (isClosure) { if (member.isFactoryConstructor || (isInConstructorContext && hasDirectLocal(typeVariableLocal))) { // The type variable is used from a closure in a factory constructor. // The value of the type argument is stored as a local on the closure // itself. return localsHandler.readLocal(typeVariableLocal); } else if (member.isFunction || member.isGetter || member.isSetter || isInConstructorContext) { // The type variable is stored on the "enclosing object" and needs to be // accessed using the this-reference in the closure. return readTypeVariable(member.enclosingClass, type.element); } else { assert(member.isField); // The type variable is stored in a parameter of the method. return localsHandler.readLocal(typeVariableLocal); } } else if (isInConstructorContext || // When [member] is a field, we can be either // generating a checked setter or inlining its // initializer in a constructor. An initializer is // never built standalone, so [isBuildingFor] will // always return true when seeing one. (member.isField && !isBuildingFor(member))) { // The type variable is stored in a parameter of the method. return localsHandler.readLocal(typeVariableLocal); } else if (member.isInstanceMember) { // The type variable is stored on the object. return readTypeVariable(member.enclosingClass, type.element); } else { compiler.internalError(type.element, 'Unexpected type variable in static context.'); return null; } } HInstruction analyzeTypeArgument(DartType argument) { assert(assertTypeInContext(argument)); if (argument.treatAsDynamic) { // Represent [dynamic] as [null]. return graph.addConstantNull(compiler); } if (argument.isTypeVariable) { return addTypeVariableReference(argument); } List inputs = []; String template = rti.getTypeRepresentationWithHashes(argument, (variable) { inputs.add(addTypeVariableReference(variable)); }); js.Template code = js.js.uncachedExpressionTemplate(template); HInstruction result = createForeign(code, backend.stringType, inputs); add(result); return result; } HInstruction handleListConstructor(InterfaceType type, ast.Node currentNode, HInstruction newObject) { if (!backend.classNeedsRti(type.element) || type.treatAsRaw) { return newObject; } List inputs = []; type = localsHandler.substInContext(type); type.typeArguments.forEach((DartType argument) { inputs.add(analyzeTypeArgument(argument)); }); // TODO(15489): Register at codegen. registry.registerInstantiatedType(type); return callSetRuntimeTypeInfo(type.element, inputs, newObject); } void copyRuntimeTypeInfo(HInstruction source, HInstruction target) { Element copyHelper = backend.getCopyTypeArguments(); pushInvokeStatic(null, copyHelper, [source, target]); pop(); } HInstruction callSetRuntimeTypeInfo(ClassElement element, List rtiInputs, HInstruction newObject) { if (!backend.classNeedsRti(element) || element.typeVariables.isEmpty) { return newObject; } HInstruction typeInfo = buildLiteralList(rtiInputs); add(typeInfo); // Set the runtime type information on the object. Element typeInfoSetterElement = backend.getSetRuntimeTypeInfo(); pushInvokeStatic( null, typeInfoSetterElement, [newObject, typeInfo], backend.dynamicType); // The new object will now be referenced through the // `setRuntimeTypeInfo` call. We therefore set the type of that // instruction to be of the object's type. assert(stack.last is HInvokeStatic || stack.last == newObject); stack.last.instructionType = newObject.instructionType; return pop(); } handleNewSend(ast.NewExpression node) { ast.Send send = node.send; generateIsDeferredLoadedCheckIfNeeded(send); bool isFixedList = false; bool isFixedListConstructorCall = Elements.isFixedListConstructorCall(elements[send], send, compiler); bool isGrowableListConstructorCall = Elements.isGrowableListConstructorCall(elements[send], send, compiler); TypeMask computeType(element) { Element originalElement = elements[send]; if (isFixedListConstructorCall || Elements.isFilledListConstructorCall( originalElement, send, compiler)) { isFixedList = true; TypeMask inferred = TypeMaskFactory.inferredForNode(sourceElement, send, compiler); return inferred.containsAll(compiler.world) ? backend.fixedArrayType : inferred; } else if (isGrowableListConstructorCall) { TypeMask inferred = TypeMaskFactory.inferredForNode(sourceElement, send, compiler); return inferred.containsAll(compiler.world) ? backend.extendableArrayType : inferred; } else if (Elements.isConstructorOfTypedArraySubclass( originalElement, compiler)) { isFixedList = true; TypeMask inferred = TypeMaskFactory.inferredForNode(sourceElement, send, compiler); ClassElement cls = element.enclosingClass; assert(cls.thisType.element.isNative); return inferred.containsAll(compiler.world) ? new TypeMask.nonNullExact(cls.thisType.element, compiler.world) : inferred; } else if (element.isGenerativeConstructor) { ClassElement cls = element.enclosingClass; return new TypeMask.nonNullExact(cls.thisType.element, compiler.world); } else { return TypeMaskFactory.inferredReturnTypeForElement( originalElement, compiler); } } Element constructor = elements[send]; CallStructure callStructure = elements.getSelector(send).callStructure; ConstructorElement constructorDeclaration = constructor; ConstructorElement constructorImplementation = constructor.implementation; constructor = constructorImplementation.effectiveTarget; final bool isSymbolConstructor = constructorDeclaration == compiler.symbolConstructor; final bool isJSArrayTypedConstructor = constructorDeclaration == backend.jsArrayTypedConstructor; if (isSymbolConstructor) { constructor = compiler.symbolValidatedConstructor; assert(invariant(send, constructor != null, message: 'Constructor Symbol.validated is missing')); callStructure = compiler.symbolValidatedConstructorSelector.callStructure; assert(invariant(send, callStructure != null, message: 'Constructor Symbol.validated is missing')); } bool isRedirected = constructorDeclaration.isRedirectingFactory; InterfaceType type = elements.getType(node); InterfaceType expectedType = constructorDeclaration.computeEffectiveTargetType(type); expectedType = localsHandler.substInContext(expectedType); if (compiler.elementHasCompileTimeError(constructor)) { // TODO(ahe): Do something like [generateWrongArgumentCountError]. stack.add(graph.addConstantNull(compiler)); return; } if (checkTypeVariableBounds(node, type)) return; var inputs = []; if (constructor.isGenerativeConstructor && Elements.isNativeOrExtendsNative(constructor.enclosingClass)) { // Native class generative constructors take a pre-constructed object. inputs.add(graph.addConstantNull(compiler)); } // TODO(5347): Try to avoid the need for calling [implementation] before // calling [makeStaticArgumentList]. if (!callStructure.signatureApplies(constructor.implementation)) { generateWrongArgumentCountError(send, constructor, send.arguments); return; } inputs.addAll(makeStaticArgumentList(callStructure, send.arguments, constructor.implementation)); TypeMask elementType = computeType(constructor); if (isFixedListConstructorCall) { if (!inputs[0].isNumber(compiler)) { HTypeConversion conversion = new HTypeConversion( null, HTypeConversion.ARGUMENT_TYPE_CHECK, backend.numType, inputs[0], null); add(conversion); inputs[0] = conversion; } js.Template code = js.js.parseForeignJS('Array(#)'); var behavior = new native.NativeBehavior(); behavior.typesReturned.add(expectedType); // The allocation can throw only if the given length is a double // or negative. bool canThrow = true; if (inputs[0].isInteger(compiler) && inputs[0] is HConstant) { var constant = inputs[0]; if (constant.constant.primitiveValue >= 0) canThrow = false; } HForeignCode foreign = new HForeignCode( code, elementType, inputs, nativeBehavior: behavior, canThrow: canThrow); push(foreign); TypesInferrer inferrer = compiler.typesTask.typesInferrer; if (inferrer.isFixedArrayCheckedForGrowable(send)) { js.Template code = js.js.parseForeignJS(r'#.fixed$length = Array'); // We set the instruction as [canThrow] to avoid it being dead code. // We need a finer grained side effect. add(new HForeignCode( code, backend.nullType, [stack.last], canThrow: true)); } } else if (isGrowableListConstructorCall) { push(buildLiteralList([])); stack.last.instructionType = elementType; } else { ClassElement cls = constructor.enclosingClass; if (cls.isAbstract && constructor.isGenerativeConstructor) { generateAbstractClassInstantiationError(send, cls.name); return; } potentiallyAddTypeArguments(inputs, cls, expectedType); addInlinedInstantiation(expectedType); pushInvokeStatic(node, constructor, inputs, elementType); removeInlinedInstantiation(expectedType); } HInstruction newInstance = stack.last; if (isFixedList) { // Overwrite the element type, in case the allocation site has // been inlined. newInstance.instructionType = elementType; JavaScriptItemCompilationContext context = work.compilationContext; context.allocatedFixedLists.add(newInstance); } // The List constructor forwards to a Dart static method that does // not know about the type argument. Therefore we special case // this constructor to have the setRuntimeTypeInfo called where // the 'new' is done. if (backend.classNeedsRti(compiler.listClass) && (isFixedListConstructorCall || isGrowableListConstructorCall || isJSArrayTypedConstructor)) { newInstance = handleListConstructor(type, send, pop()); stack.add(newInstance); } // Finally, if we called a redirecting factory constructor, check the type. if (isRedirected) { HInstruction checked = potentiallyCheckOrTrustType(newInstance, type); if (checked != newInstance) { pop(); stack.add(checked); } } } void potentiallyAddTypeArguments(List inputs, ClassElement cls, InterfaceType expectedType) { if (!backend.classNeedsRti(cls)) return; assert(expectedType.typeArguments.isEmpty || cls.typeVariables.length == expectedType.typeArguments.length); expectedType.typeArguments.forEach((DartType argument) { inputs.add(analyzeTypeArgument(argument)); }); } /// In checked mode checks the [type] of [node] to be well-bounded. The method /// returns [:true:] if an error can be statically determined. bool checkTypeVariableBounds(ast.NewExpression node, InterfaceType type) { if (!compiler.enableTypeAssertions) return false; Map> seenChecksMap = new Map>(); bool definitelyFails = false; addTypeVariableBoundCheck(GenericType instance, DartType typeArgument, TypeVariableType typeVariable, DartType bound) { if (definitelyFails) return; int subtypeRelation = compiler.types.computeSubtypeRelation(typeArgument, bound); if (subtypeRelation == Types.IS_SUBTYPE) return; String message = "Can't create an instance of malbounded type '$type': " "'${typeArgument}' is not a subtype of bound '${bound}' for " "type variable '${typeVariable}' of type " "${type == instance ? "'${type.element.thisType}'" : "'${instance.element.thisType}' on the supertype " "'${instance}' of '${type}'" }."; if (subtypeRelation == Types.NOT_SUBTYPE) { generateTypeError(node, message); definitelyFails = true; return; } else if (subtypeRelation == Types.MAYBE_SUBTYPE) { Set seenChecks = seenChecksMap.putIfAbsent(typeArgument, () => new Set()); if (!seenChecks.contains(bound)) { seenChecks.add(bound); assertIsSubtype(node, typeArgument, bound, message); } } } compiler.types.checkTypeVariableBounds(type, addTypeVariableBoundCheck); if (definitelyFails) { return true; } for (InterfaceType supertype in type.element.allSupertypes) { DartType instance = type.asInstanceOf(supertype.element); compiler.types.checkTypeVariableBounds(instance, addTypeVariableBoundCheck); if (definitelyFails) { return true; } } return false; } visitAssertSend(node) { if (!compiler.enableUserAssertions) { stack.add(graph.addConstantNull(compiler)); return; } // TODO(johnniwinther): Don't handle assert like a regular static call. // It breaks the selector name check since the assert helper method cannot // be called `assert` and therefore does not match the selector like a // regular method. visitStaticSend(node); } visitStaticSend(ast.Send node) { CallStructure callStructure = elements.getSelector(node).callStructure; Element element = elements[node]; if (elements.isAssert(node)) { element = backend.assertMethod; } if (element.isForeign(backend) && element.isFunction) { visitForeignSend(node); return; } if (element.isErroneous) { if (element is ErroneousElement) { // An erroneous element indicates that the funciton could not be // resolved (a warning has been issued). generateThrowNoSuchMethod(node, noSuchMethodTargetSymbolString(element), argumentNodes: node.arguments); } else { // TODO(ahe): Do something like [generateWrongArgumentCountError]. stack.add(graph.addConstantNull(compiler)); } return; } invariant(element, !element.isGenerativeConstructor); generateIsDeferredLoadedCheckIfNeeded(node); if (element.isFunction) { // TODO(5347): Try to avoid the need for calling [implementation] before // calling [makeStaticArgumentList]. if (!callStructure.signatureApplies(element.implementation)) { generateWrongArgumentCountError(node, element, node.arguments); return; } List inputs = makeStaticArgumentList(callStructure, node.arguments, element.implementation); if (element == compiler.identicalFunction) { pushWithPosition( new HIdentity(inputs[0], inputs[1], null, backend.boolType), node); return; } pushInvokeStatic(node, element, inputs); } else { generateGetter(node, element); List inputs = [pop()]; addDynamicSendArgumentsToList(node, inputs); Selector closureSelector = callStructure.callSelector; pushWithPosition( new HInvokeClosure(closureSelector, inputs, backend.dynamicType), node); } } HConstant addConstantString(String string) { ast.DartString dartString = new ast.DartString.literal(string); ConstantValue constant = constantSystem.createString(dartString); return graph.addConstant(constant, compiler); } visitTypePrefixSend(ast.Send node) { compiler.internalError(node, "visitTypePrefixSend should not be called."); } visitTypeLiteralSend(ast.Send node) { DartType type = elements.getTypeLiteralType(node); if (type.isInterfaceType || type.isTypedef || type.isDynamic) { // TODO(karlklose): add type representation if (node.isCall) { // The node itself is not a constant but we register the selector (the // identifier that refers to the class/typedef) as a constant. stack.add(addConstant(node.selector)); } else { stack.add(addConstant(node)); } } else if (type.isTypeVariable) { type = localsHandler.substInContext(type); HInstruction value = analyzeTypeArgument(type); pushInvokeStatic(node, backend.getRuntimeTypeToString(), [value], backend.stringType); pushInvokeStatic(node, backend.getCreateRuntimeType(), [pop()]); } else { internalError(node, 'unexpected type kind ${type.kind}'); } if (node.isCall) { // This send is of the form 'e(...)', where e is resolved to a type // reference. We create a regular closure call on the result of the type // reference instead of creating a NoSuchMethodError to avoid pulling it // in if it is not used (e.g., in a try/catch). HInstruction target = pop(); Selector selector = elements.getSelector(node); List inputs = [target]; addDynamicSendArgumentsToList(node, inputs); Selector closureSelector = new Selector.callClosureFrom(selector); push(new HInvokeClosure(closureSelector, inputs, backend.dynamicType)); } } visitGetterSend(ast.Send node) { generateIsDeferredLoadedCheckIfNeeded(node); generateGetter(node, elements[node]); } // TODO(antonm): migrate rest of SsaFromAstMixin to internalError. internalError(Spannable node, String reason) { compiler.internalError(node, reason); } void generateError(ast.Node node, String message, Element helper) { HInstruction errorMessage = addConstantString(message); pushInvokeStatic(node, helper, [errorMessage]); } void generateRuntimeError(ast.Node node, String message) { generateError(node, message, backend.getThrowRuntimeError()); } void generateTypeError(ast.Node node, String message) { generateError(node, message, backend.getThrowTypeError()); } void generateAbstractClassInstantiationError(ast.Node node, String message) { generateError(node, message, backend.getThrowAbstractClassInstantiationError()); } void generateThrowNoSuchMethod(ast.Node diagnosticNode, String methodName, {Link argumentNodes, List argumentValues, List existingArguments}) { Element helper = backend.getThrowNoSuchMethod(); ConstantValue receiverConstant = constantSystem.createString(new ast.DartString.empty()); HInstruction receiver = graph.addConstant(receiverConstant, compiler); ast.DartString dartString = new ast.DartString.literal(methodName); ConstantValue nameConstant = constantSystem.createString(dartString); HInstruction name = graph.addConstant(nameConstant, compiler); if (argumentValues == null) { argumentValues = []; argumentNodes.forEach((argumentNode) { visit(argumentNode); HInstruction value = pop(); argumentValues.add(value); }); } HInstruction arguments = buildLiteralList(argumentValues); add(arguments); HInstruction existingNamesList; if (existingArguments != null) { List existingNames = []; for (String name in existingArguments) { HInstruction nameConstant = graph.addConstantString(new ast.DartString.literal(name), compiler); existingNames.add(nameConstant); } existingNamesList = buildLiteralList(existingNames); add(existingNamesList); } else { existingNamesList = graph.addConstantNull(compiler); } pushInvokeStatic(diagnosticNode, helper, [receiver, name, arguments, existingNamesList]); } /** * Generate code to throw a [NoSuchMethodError] exception for calling a * method with a wrong number of arguments or mismatching named optional * arguments. */ void generateWrongArgumentCountError(ast.Node diagnosticNode, FunctionElement function, Link argumentNodes) { List existingArguments = []; FunctionSignature signature = function.functionSignature; signature.forEachParameter((Element parameter) { existingArguments.add(parameter.name); }); generateThrowNoSuchMethod(diagnosticNode, function.name, argumentNodes: argumentNodes, existingArguments: existingArguments); } visitNewExpression(ast.NewExpression node) { Element element = elements[node.send]; final bool isSymbolConstructor = element == compiler.symbolConstructor; if (!Elements.isErroneous(element)) { ConstructorElement function = element; element = function.effectiveTarget; } if (Elements.isErroneous(element)) { if (element is !ErroneousElement) { // TODO(ahe): Do something like [generateWrongArgumentCountError]. stack.add(graph.addConstantNull(compiler)); return; } ErroneousElement error = element; if (error.messageKind == MessageKind.CANNOT_FIND_CONSTRUCTOR) { generateThrowNoSuchMethod( node.send, noSuchMethodTargetSymbolString(error, 'constructor'), argumentNodes: node.send.arguments); } else { Message message = error.messageKind.message(error.messageArguments); generateRuntimeError(node.send, message.toString()); } } else if (node.isConst) { stack.add(addConstant(node)); if (isSymbolConstructor) { ConstructedConstantValue symbol = getConstantForNode(node); StringConstantValue stringConstant = symbol.fields.single; String nameString = stringConstant.toDartString().slowToString(); registry.registerConstSymbol(nameString); } } else { handleNewSend(node); } } void pushInvokeDynamic(ast.Node node, Selector selector, List arguments, {ast.Node location}) { if (location == null) location = node; // We prefer to not inline certain operations on indexables, // because the constant folder will handle them better and turn // them into simpler instructions that allow further // optimizations. bool isOptimizableOperationOnIndexable(Selector selector, Element element) { bool isLength = selector.isGetter && selector.name == "length"; if (isLength || selector.isIndex) { TypeMask type = new TypeMask.nonNullExact( element.enclosingClass.declaration, compiler.world); return type.satisfies(backend.jsIndexableClass, compiler.world); } else if (selector.isIndexSet) { TypeMask type = new TypeMask.nonNullExact( element.enclosingClass.declaration, compiler.world); return type.satisfies(backend.jsMutableIndexableClass, compiler.world); } else { return false; } } bool isOptimizableOperation(Selector selector, Element element) { ClassElement cls = element.enclosingClass; if (isOptimizableOperationOnIndexable(selector, element)) return true; if (!backend.interceptedClasses.contains(cls)) return false; if (selector.isOperator) return true; if (selector.isSetter) return true; if (selector.isIndex) return true; if (selector.isIndexSet) return true; if (element == backend.jsArrayAdd || element == backend.jsArrayRemoveLast || element == backend.jsStringSplit) { return true; } return false; } Element element = compiler.world.locateSingleElement(selector); if (element != null && !element.isField && !(element.isGetter && selector.isCall) && !(element.isFunction && selector.isGetter) && !isOptimizableOperation(selector, element)) { if (tryInlineMethod(element, selector, arguments, node)) { return; } } HInstruction receiver = arguments[0]; List inputs = []; bool isIntercepted = backend.isInterceptedSelector(selector); if (isIntercepted) { inputs.add(invokeInterceptor(receiver)); } inputs.addAll(arguments); TypeMask type = TypeMaskFactory.inferredTypeForSelector(selector, compiler); if (selector.isGetter) { pushWithPosition( new HInvokeDynamicGetter(selector, null, inputs, type), location); } else if (selector.isSetter) { pushWithPosition( new HInvokeDynamicSetter(selector, null, inputs, type), location); } else { pushWithPosition( new HInvokeDynamicMethod(selector, inputs, type, isIntercepted), location); } } void pushInvokeStatic(ast.Node location, Element element, List arguments, [TypeMask type]) { if (tryInlineMethod(element, null, arguments, location)) { return; } if (type == null) { type = TypeMaskFactory.inferredReturnTypeForElement(element, compiler); } bool targetCanThrow = !compiler.world.getCannotThrow(element); // TODO(5346): Try to avoid the need for calling [declaration] before // creating an [HInvokeStatic]. HInvokeStatic instruction = new HInvokeStatic( element.declaration, arguments, type, targetCanThrow: targetCanThrow); if (!currentInlinedInstantiations.isEmpty) { instruction.instantiatedTypes = new List.from( currentInlinedInstantiations); } instruction.sideEffects = compiler.world.getSideEffectsOfElement(element); if (location == null) { push(instruction); } else { pushWithPosition(instruction, location); } } HInstruction buildInvokeSuper(Selector selector, Element element, List arguments) { HInstruction receiver = localsHandler.readThis(); // TODO(5346): Try to avoid the need for calling [declaration] before // creating an [HStatic]. List inputs = []; if (backend.isInterceptedSelector(selector) && // Fields don't need an interceptor; consider generating HFieldGet/Set // instead. element.kind != ElementKind.FIELD) { inputs.add(invokeInterceptor(receiver)); } inputs.add(receiver); inputs.addAll(arguments); TypeMask type; if (!element.isGetter && selector.isGetter) { type = TypeMaskFactory.inferredTypeForElement(element, compiler); } else { type = TypeMaskFactory.inferredReturnTypeForElement(element, compiler); } HInstruction instruction = new HInvokeSuper( element, currentNonClosureClass, selector, inputs, type, isSetter: selector.isSetter || selector.isIndexSet); instruction.sideEffects = compiler.world.getSideEffectsOfSelector(selector); return instruction; } void handleComplexOperatorSend(ast.SendSet node, HInstruction receiver, Link arguments) { HInstruction rhs; if (node.isPrefix || node.isPostfix) { rhs = graph.addConstantInt(1, compiler); } else { visit(arguments.head); assert(arguments.tail.isEmpty); rhs = pop(); } visitBinarySend(receiver, node.assignmentOperator, rhs, elements.getOperatorSelectorInComplexSendSet(node), node); } visitSendSet(ast.SendSet node) { generateIsDeferredLoadedCheckIfNeeded(node); Element element = elements[node]; if (!Elements.isUnresolved(element) && element.impliesType) { ast.Identifier selector = node.selector; generateThrowNoSuchMethod(node, selector.source, argumentNodes: node.arguments); return; } ast.Operator op = node.assignmentOperator; if (node.isSuperCall) { HInstruction result; List setterInputs = []; if (identical(node.assignmentOperator.source, '=')) { addDynamicSendArgumentsToList(node, setterInputs); result = setterInputs.last; } else { Element getter = elements[node.selector]; List getterInputs = []; Link arguments = node.arguments; if (node.isIndex) { // If node is of the from [:super.foo[0] += 2:], the send has // two arguments: the index and the left hand side. We get // the index and add it as input of the getter and the // setter. visit(arguments.head); arguments = arguments.tail; HInstruction index = pop(); getterInputs.add(index); setterInputs.add(index); } HInstruction getterInstruction; Selector getterSelector = elements.getGetterSelectorInComplexSendSet(node); if (Elements.isUnresolved(getter)) { generateSuperNoSuchMethodSend( node, getterSelector, getterInputs); getterInstruction = pop(); } else { getterInstruction = buildInvokeSuper( getterSelector, getter, getterInputs); add(getterInstruction); } handleComplexOperatorSend(node, getterInstruction, arguments); setterInputs.add(pop()); if (node.isPostfix) { result = getterInstruction; } else { result = setterInputs.last; } } Selector setterSelector = elements.getSelector(node); if (Elements.isUnresolved(element) || !setterSelector.applies(element, compiler.world)) { generateSuperNoSuchMethodSend( node, setterSelector, setterInputs); pop(); } else { add(buildInvokeSuper(setterSelector, element, setterInputs)); } stack.add(result); } else if (node.isIndex) { if ("=" == op.source) { visitDynamicSend(node); } else { visit(node.receiver); HInstruction receiver = pop(); Link arguments = node.arguments; HInstruction index; if (node.isIndex) { visit(arguments.head); arguments = arguments.tail; index = pop(); } pushInvokeDynamic( node, elements.getGetterSelectorInComplexSendSet(node), [receiver, index]); HInstruction getterInstruction = pop(); handleComplexOperatorSend(node, getterInstruction, arguments); HInstruction value = pop(); pushInvokeDynamic( node, elements.getSelector(node), [receiver, index, value]); pop(); if (node.isPostfix) { stack.add(getterInstruction); } else { stack.add(value); } } } else if ("=" == op.source) { Link link = node.arguments; assert(!link.isEmpty && link.tail.isEmpty); if (Elements.isInstanceSend(node, elements)) { HInstruction receiver = generateInstanceSendReceiver(node); visit(link.head); generateInstanceSetterWithCompiledReceiver(node, receiver, pop()); } else { visit(link.head); generateNonInstanceSetter(node, element, pop()); } } else if (identical(op.source, "is")) { compiler.internalError(op, "is-operator as SendSet."); } else { assert("++" == op.source || "--" == op.source || node.assignmentOperator.source.endsWith("=")); // [receiver] is only used if the node is an instance send. HInstruction receiver = null; Element getter = elements[node.selector]; if (!Elements.isUnresolved(getter) && getter.impliesType) { ast.Identifier selector = node.selector; generateThrowNoSuchMethod(node, selector.source, argumentNodes: node.arguments); return; } else if (Elements.isInstanceSend(node, elements)) { receiver = generateInstanceSendReceiver(node); generateInstanceGetterWithCompiledReceiver( node, elements.getGetterSelectorInComplexSendSet(node), receiver); } else { generateGetter(node, getter); } HInstruction getterInstruction = pop(); handleComplexOperatorSend(node, getterInstruction, node.arguments); HInstruction value = pop(); assert(value != null); if (Elements.isInstanceSend(node, elements)) { assert(receiver != null); generateInstanceSetterWithCompiledReceiver(node, receiver, value); } else { assert(receiver == null); generateNonInstanceSetter(node, element, value); } if (node.isPostfix) { pop(); stack.add(getterInstruction); } } } void visitLiteralInt(ast.LiteralInt node) { stack.add(graph.addConstantInt(node.value, compiler)); } void visitLiteralDouble(ast.LiteralDouble node) { stack.add(graph.addConstantDouble(node.value, compiler)); } void visitLiteralBool(ast.LiteralBool node) { stack.add(graph.addConstantBool(node.value, compiler)); } void visitLiteralString(ast.LiteralString node) { stack.add(graph.addConstantString(node.dartString, compiler)); } void visitLiteralSymbol(ast.LiteralSymbol node) { stack.add(addConstant(node)); registry.registerConstSymbol(node.slowNameString); } void visitStringJuxtaposition(ast.StringJuxtaposition node) { if (!node.isInterpolation) { // This is a simple string with no interpolations. stack.add(graph.addConstantString(node.dartString, compiler)); return; } StringBuilderVisitor stringBuilder = new StringBuilderVisitor(this, node); stringBuilder.visit(node); stack.add(stringBuilder.result); } void visitLiteralNull(ast.LiteralNull node) { stack.add(graph.addConstantNull(compiler)); } visitNodeList(ast.NodeList node) { for (Link link = node.nodes; !link.isEmpty; link = link.tail) { if (isAborted()) { compiler.reportWarning(link.head, MessageKind.GENERIC, {'text': 'dead code'}); } else { visit(link.head); } } } void visitParenthesizedExpression(ast.ParenthesizedExpression node) { visit(node.expression); } visitOperator(ast.Operator node) { // Operators are intercepted in their surrounding Send nodes. compiler.internalError(node, 'SsaBuilder.visitOperator should not be called.'); } visitCascade(ast.Cascade node) { visit(node.expression); // Remove the result and reveal the duplicated receiver on the stack. pop(); } visitCascadeReceiver(ast.CascadeReceiver node) { visit(node.expression); dup(); } void handleInTryStatement() { if (!inTryStatement) return; HBasicBlock block = close(new HExitTry()); HBasicBlock newBlock = graph.addNewBlock(); block.addSuccessor(newBlock); open(newBlock); } visitRethrow(ast.Rethrow node) { HInstruction exception = rethrowableException; if (exception == null) { exception = graph.addConstantNull(compiler); compiler.internalError(node, 'rethrowableException should not be null.'); } handleInTryStatement(); closeAndGotoExit(new HThrow(exception, isRethrow: true)); } visitRedirectingFactoryBody(ast.RedirectingFactoryBody node) { ConstructorElement targetConstructor = elements.getRedirectingTargetConstructor(node).implementation; ConstructorElement redirectingConstructor = sourceElement.implementation; List inputs = []; FunctionSignature targetSignature = targetConstructor.functionSignature; FunctionSignature redirectingSignature = redirectingConstructor.functionSignature; redirectingSignature.forEachRequiredParameter((ParameterElement element) { inputs.add(localsHandler.readLocal(element)); }); List targetOptionals = targetSignature.orderedOptionalParameters; List redirectingOptionals = redirectingSignature.orderedOptionalParameters; int i = 0; for (; i < redirectingOptionals.length; i++) { ParameterElement parameter = redirectingOptionals[i]; inputs.add(localsHandler.readLocal(parameter)); } for (; i < targetOptionals.length; i++) { inputs.add(handleConstantForOptionalParameter(targetOptionals[i])); } ClassElement targetClass = targetConstructor.enclosingClass; if (backend.classNeedsRti(targetClass)) { ClassElement cls = redirectingConstructor.enclosingClass; InterfaceType targetType = redirectingConstructor.computeEffectiveTargetType(cls.thisType); targetType = localsHandler.substInContext(targetType); targetType.typeArguments.forEach((DartType argument) { inputs.add(analyzeTypeArgument(argument)); }); } pushInvokeStatic(node, targetConstructor, inputs); HInstruction value = pop(); emitReturn(value, node); } /// Returns true if the [type] is a valid return type for an asynchronous /// function. /// /// Asynchronous functions return a `Future`, and a valid return is thus /// either dynamic, Object, or Future. /// /// We do not accept the internal Future implementation class. bool isValidAsyncReturnType(DartType type) { assert (isBuildingAsyncFunction); // TODO(sigurdm): In an internal library a function could be declared: // // _FutureImpl foo async => 1; // // This should be valid (because the actual value returned from an async // function is a `_FutureImpl`), but currently false is returned in this // case. return type.isDynamic || type.isObject || (type is InterfaceType && type.element == compiler.futureClass); } visitReturn(ast.Return node) { if (identical(node.beginToken.stringValue, 'native')) { native.handleSsaNative(this, node.expression); return; } HInstruction value; if (node.expression == null) { value = graph.addConstantNull(compiler); } else { visit(node.expression); value = pop(); if (isBuildingAsyncFunction) { if (compiler.enableTypeAssertions && !isValidAsyncReturnType(returnType)) { String message = "Async function returned a Future, " "was declared to return a $returnType."; generateTypeError(node, message); pop(); return; } } else { value = potentiallyCheckOrTrustType(value, returnType); } } handleInTryStatement(); emitReturn(value, node); } visitThrow(ast.Throw node) { visitThrowExpression(node.expression); if (isReachable) { handleInTryStatement(); push(new HThrowExpression(pop())); isReachable = false; } } visitYield(ast.Yield node) { visit(node.expression); HInstruction yielded = pop(); add(new HYield(yielded, node.hasStar)); } visitAwait(ast.Await node) { visit(node.expression); HInstruction awaited = pop(); // TODO(herhut): Improve this type. push(new HAwait(awaited, new TypeMask.subclass(compiler.objectClass, compiler.world))); } visitTypeAnnotation(ast.TypeAnnotation node) { compiler.internalError(node, 'Visiting type annotation in SSA builder.'); } visitVariableDefinitions(ast.VariableDefinitions node) { assert(isReachable); for (Link link = node.definitions.nodes; !link.isEmpty; link = link.tail) { ast.Node definition = link.head; if (definition is ast.Identifier) { HInstruction initialValue = graph.addConstantNull(compiler); LocalElement local = elements[definition]; localsHandler.updateLocal(local, initialValue); } else { assert(definition is ast.SendSet); visitSendSet(definition); pop(); // Discard value. } } } HInstruction setRtiIfNeeded(HInstruction object, ast.Node node) { InterfaceType type = localsHandler.substInContext(elements.getType(node)); if (!backend.classNeedsRti(type.element) || type.treatAsRaw) { return object; } List arguments = []; for (DartType argument in type.typeArguments) { arguments.add(analyzeTypeArgument(argument)); } // TODO(15489): Register at codegen. registry.registerInstantiatedType(type); return callSetRuntimeTypeInfo(type.element, arguments, object); } visitLiteralList(ast.LiteralList node) { HInstruction instruction; if (node.isConst) { instruction = addConstant(node); } else { List inputs = []; for (Link link = node.elements.nodes; !link.isEmpty; link = link.tail) { visit(link.head); inputs.add(pop()); } instruction = buildLiteralList(inputs); add(instruction); instruction = setRtiIfNeeded(instruction, node); } TypeMask type = TypeMaskFactory.inferredForNode(sourceElement, node, compiler); if (!type.containsAll(compiler.world)) instruction.instructionType = type; stack.add(instruction); } visitConditional(ast.Conditional node) { SsaBranchBuilder brancher = new SsaBranchBuilder(this, node); brancher.handleConditional(() => visit(node.condition), () => visit(node.thenExpression), () => visit(node.elseExpression)); } visitStringInterpolation(ast.StringInterpolation node) { StringBuilderVisitor stringBuilder = new StringBuilderVisitor(this, node); stringBuilder.visit(node); stack.add(stringBuilder.result); } visitStringInterpolationPart(ast.StringInterpolationPart node) { // The parts are iterated in visitStringInterpolation. compiler.internalError(node, 'SsaBuilder.visitStringInterpolation should not be called.'); } visitEmptyStatement(ast.EmptyStatement node) { // Do nothing, empty statement. } visitModifiers(ast.Modifiers node) { compiler.unimplemented(node, 'SsaFromAstMixin.visitModifiers.'); } visitBreakStatement(ast.BreakStatement node) { assert(!isAborted()); handleInTryStatement(); JumpTarget target = elements.getTargetOf(node); assert(target != null); JumpHandler handler = jumpTargets[target]; assert(handler != null); if (node.target == null) { handler.generateBreak(); } else { LabelDefinition label = elements.getTargetLabel(node); handler.generateBreak(label); } } visitContinueStatement(ast.ContinueStatement node) { handleInTryStatement(); JumpTarget target = elements.getTargetOf(node); assert(target != null); JumpHandler handler = jumpTargets[target]; assert(handler != null); if (node.target == null) { handler.generateContinue(); } else { LabelDefinition label = elements.getTargetLabel(node); assert(label != null); handler.generateContinue(label); } } /** * Creates a [JumpHandler] for a statement. The node must be a jump * target. If there are no breaks or continues targeting the statement, * a special "null handler" is returned. * * [isLoopJump] is [:true:] when the jump handler is for a loop. This is used * to distinguish the synthetized loop created for a switch statement with * continue statements from simple switch statements. */ JumpHandler createJumpHandler(ast.Statement node, {bool isLoopJump}) { JumpTarget element = elements.getTargetDefinition(node); if (element == null || !identical(element.statement, node)) { // No breaks or continues to this node. return new NullJumpHandler(compiler); } if (isLoopJump && node is ast.SwitchStatement) { // Create a special jump handler for loops created for switch statements // with continue statements. return new SwitchCaseJumpHandler(this, element, node); } return new JumpHandler(this, element); } buildAsyncForIn(ast.ForIn node) { assert(node.isAsync); // The async-for is implemented with a StreamIterator. HInstruction streamIterator; visit(node.expression); HInstruction expression = pop(); pushInvokeStatic(node, backend.getStreamIteratorConstructor(), [expression, graph.addConstantNull(compiler)]); streamIterator = pop(); void buildInitializer() {} HInstruction buildCondition() { Selector selector = elements.getMoveNextSelector(node); pushInvokeDynamic(node, selector, [streamIterator]); HInstruction future = pop(); push(new HAwait(future, new TypeMask.subclass(compiler.objectClass, compiler.world))); return popBoolified(); } void buildBody() { Selector call = elements.getCurrentSelector(node); pushInvokeDynamic(node, call, [streamIterator]); ast.Node identifier = node.declaredIdentifier; Element variable = elements.getForInVariable(node); Selector selector = elements.getSelector(identifier); HInstruction value = pop(); if (identifier.asSend() != null && Elements.isInstanceSend(identifier, elements)) { HInstruction receiver = generateInstanceSendReceiver(identifier); assert(receiver != null); generateInstanceSetterWithCompiledReceiver( null, receiver, value, selector: selector, location: identifier); } else { generateNonInstanceSetter( null, variable, value, location: identifier); } pop(); // Pop the value pushed by the setter call. visit(node.body); } void buildUpdate() {}; buildProtectedByFinally(() { handleLoop(node, buildInitializer, buildCondition, buildUpdate, buildBody); }, () { pushInvokeDynamic(node, new Selector.call("cancel", null, 0), [streamIterator]); push(new HAwait(pop(), new TypeMask.subclass(compiler.objectClass, compiler.world))); pop(); }); } visitForIn(ast.ForIn node) { if (node.isAsync) { return buildAsyncForIn(node); } // Generate a structure equivalent to: // Iterator $iter = .iterator; // while ($iter.moveNext()) { // E = $iter.current; // // } // The iterator is shared between initializer, condition and body. HInstruction iterator; void buildInitializer() { Selector selector = elements.getIteratorSelector(node); visit(node.expression); HInstruction receiver = pop(); pushInvokeDynamic(node, selector, [receiver]); iterator = pop(); } HInstruction buildCondition() { Selector selector = elements.getMoveNextSelector(node); pushInvokeDynamic(node, selector, [iterator]); return popBoolified(); } void buildBody() { Selector call = elements.getCurrentSelector(node); pushInvokeDynamic(node, call, [iterator]); ast.Node identifier = node.declaredIdentifier; Element variable = elements.getForInVariable(node); Selector selector = elements.getSelector(identifier); HInstruction value = pop(); if (identifier.asSend() != null && Elements.isInstanceSend(identifier, elements)) { HInstruction receiver = generateInstanceSendReceiver(identifier); assert(receiver != null); generateInstanceSetterWithCompiledReceiver( null, receiver, value, selector: selector, location: identifier); } else { generateNonInstanceSetter(null, variable, value, location: identifier); } pop(); // Pop the value pushed by the setter call. visit(node.body); } handleLoop(node, buildInitializer, buildCondition, () {}, buildBody); } visitLabel(ast.Label node) { compiler.internalError(node, 'SsaFromAstMixin.visitLabel.'); } visitLabeledStatement(ast.LabeledStatement node) { ast.Statement body = node.statement; if (body is ast.Loop || body is ast.SwitchStatement || Elements.isUnusedLabel(node, elements)) { // Loops and switches handle their own labels. visit(body); return; } JumpTarget targetElement = elements.getTargetDefinition(body); LocalsHandler beforeLocals = new LocalsHandler.from(localsHandler); assert(targetElement.isBreakTarget); JumpHandler handler = new JumpHandler(this, targetElement); // Introduce a new basic block. HBasicBlock entryBlock = openNewBlock(); visit(body); SubGraph bodyGraph = new SubGraph(entryBlock, lastOpenedBlock); HBasicBlock joinBlock = graph.addNewBlock(); List breakHandlers = []; handler.forEachBreak((HBreak breakInstruction, LocalsHandler locals) { breakInstruction.block.addSuccessor(joinBlock); breakHandlers.add(locals); }); bool hasBreak = breakHandlers.length > 0; if (!isAborted()) { goto(current, joinBlock); breakHandlers.add(localsHandler); } open(joinBlock); localsHandler = beforeLocals.mergeMultiple(breakHandlers, joinBlock); if (hasBreak) { // There was at least one reachable break, so the label is needed. entryBlock.setBlockFlow( new HLabeledBlockInformation(new HSubGraphBlockInformation(bodyGraph), handler.labels()), joinBlock); } handler.close(); } visitLiteralMap(ast.LiteralMap node) { if (node.isConst) { stack.add(addConstant(node)); return; } List listInputs = []; for (Link link = node.entries.nodes; !link.isEmpty; link = link.tail) { visit(link.head); listInputs.add(pop()); listInputs.add(pop()); } ConstructorElement constructor; List inputs = []; if (listInputs.isEmpty) { constructor = backend.mapLiteralConstructorEmpty; } else { constructor = backend.mapLiteralConstructor; HLiteralList keyValuePairs = buildLiteralList(listInputs); add(keyValuePairs); inputs.add(keyValuePairs); } assert(constructor.isFactoryConstructor); ConstructorElement functionElement = constructor; constructor = functionElement.effectiveTarget; InterfaceType type = elements.getType(node); InterfaceType expectedType = functionElement.computeEffectiveTargetType(type); expectedType = localsHandler.substInContext(expectedType); ClassElement cls = constructor.enclosingClass; if (backend.classNeedsRti(cls)) { List typeVariable = cls.typeVariables; expectedType.typeArguments.forEach((DartType argument) { inputs.add(analyzeTypeArgument(argument)); }); } // The instruction type will always be a subtype of the mapLiteralClass, but // type inference might discover a more specific type, or find nothing (in // dart2js unit tests). TypeMask mapType = new TypeMask.nonNullSubtype(backend.mapLiteralClass, compiler.world); TypeMask returnTypeMask = TypeMaskFactory.inferredReturnTypeForElement( constructor, compiler); TypeMask instructionType = mapType.intersection(returnTypeMask, compiler.world); addInlinedInstantiation(expectedType); pushInvokeStatic(node, constructor, inputs, instructionType); removeInlinedInstantiation(expectedType); } visitLiteralMapEntry(ast.LiteralMapEntry node) { visit(node.value); visit(node.key); } visitNamedArgument(ast.NamedArgument node) { visit(node.expression); } Map buildSwitchCaseConstants( ast.SwitchStatement node) { Map constants = new Map(); for (ast.SwitchCase switchCase in node.cases) { for (ast.Node labelOrCase in switchCase.labelsAndCases) { if (labelOrCase is ast.CaseMatch) { ast.CaseMatch match = labelOrCase; ConstantValue constant = getConstantForNode(match.expression); constants[labelOrCase] = constant; } } } return constants; } visitSwitchStatement(ast.SwitchStatement node) { Map constants = buildSwitchCaseConstants(node); // The switch case indices must match those computed in // [SwitchCaseJumpHandler]. bool hasContinue = false; Map caseIndex = new Map(); int switchIndex = 1; bool hasDefault = false; for (ast.SwitchCase switchCase in node.cases) { for (ast.Node labelOrCase in switchCase.labelsAndCases) { ast.Node label = labelOrCase.asLabel(); if (label != null) { LabelDefinition labelElement = elements.getLabelDefinition(label); if (labelElement != null && labelElement.isContinueTarget) { hasContinue = true; } } } if (switchCase.isDefaultCase) { hasDefault = true; } caseIndex[switchCase] = switchIndex; switchIndex++; } if (!hasContinue) { // If the switch statement has no switch cases targeted by continue // statements we encode the switch statement directly. buildSimpleSwitchStatement(node, constants); } else { buildComplexSwitchStatement(node, constants, caseIndex, hasDefault); } } /** * Builds a simple switch statement which does not handle uses of continue * statements to labeled switch cases. */ void buildSimpleSwitchStatement(ast.SwitchStatement node, Map constants) { JumpHandler jumpHandler = createJumpHandler(node, isLoopJump: false); HInstruction buildExpression() { visit(node.expression); return pop(); } Iterable getConstants(ast.SwitchCase switchCase) { List constantList = []; for (ast.Node labelOrCase in switchCase.labelsAndCases) { if (labelOrCase is ast.CaseMatch) { constantList.add(constants[labelOrCase]); } } return constantList; } bool isDefaultCase(ast.SwitchCase switchCase) { return switchCase.isDefaultCase; } void buildSwitchCase(ast.SwitchCase node) { visit(node.statements); } handleSwitch(node, jumpHandler, buildExpression, node.cases, getConstants, isDefaultCase, buildSwitchCase); jumpHandler.close(); } /** * Builds a switch statement that can handle arbitrary uses of continue * statements to labeled switch cases. */ void buildComplexSwitchStatement(ast.SwitchStatement node, Map constants, Map caseIndex, bool hasDefault) { // If the switch statement has switch cases targeted by continue // statements we create the following encoding: // // switch (e) { // l_1: case e0: s_1; break; // l_2: case e1: s_2; continue l_i; // ... // l_n: default: s_n; continue l_j; // } // // is encoded as // // var target; // switch (e) { // case e1: target = 1; break; // case e2: target = 2; break; // ... // default: target = n; break; // } // l: while (true) { // switch (target) { // case 1: s_1; break l; // case 2: s_2; target = i; continue l; // ... // case n: s_n; target = j; continue l; // } // } JumpTarget switchTarget = elements.getTargetDefinition(node); HInstruction initialValue = graph.addConstantNull(compiler); localsHandler.updateLocal(switchTarget, initialValue); JumpHandler jumpHandler = createJumpHandler(node, isLoopJump: false); var switchCases = node.cases; if (!hasDefault) { // Use [:null:] as the marker for a synthetic default clause. // The synthetic default is added because otherwise, there would be no // good place to give a default value to the local. switchCases = node.cases.nodes.toList()..add(null); } HInstruction buildExpression() { visit(node.expression); return pop(); } Iterable getConstants(ast.SwitchCase switchCase) { List constantList = []; if (switchCase != null) { for (ast.Node labelOrCase in switchCase.labelsAndCases) { if (labelOrCase is ast.CaseMatch) { constantList.add(constants[labelOrCase]); } } } return constantList; } bool isDefaultCase(ast.SwitchCase switchCase) { return switchCase == null || switchCase.isDefaultCase; } void buildSwitchCase(ast.SwitchCase switchCase) { if (switchCase != null) { // Generate 'target = i; break;' for switch case i. int index = caseIndex[switchCase]; HInstruction value = graph.addConstantInt(index, compiler); localsHandler.updateLocal(switchTarget, value); } else { // Generate synthetic default case 'target = null; break;'. HInstruction value = graph.addConstantNull(compiler); localsHandler.updateLocal(switchTarget, value); } jumpTargets[switchTarget].generateBreak(); } handleSwitch(node, jumpHandler, buildExpression, switchCases, getConstants, isDefaultCase, buildSwitchCase); jumpHandler.close(); HInstruction buildCondition() => graph.addConstantBool(true, compiler); void buildSwitch() { HInstruction buildExpression() { return localsHandler.readLocal(switchTarget); } Iterable getConstants(ast.SwitchCase switchCase) { return [constantSystem.createInt(caseIndex[switchCase])]; } void buildSwitchCase(ast.SwitchCase switchCase) { visit(switchCase.statements); if (!isAborted()) { // Ensure that we break the loop if the case falls through. (This // is only possible for the last case.) jumpTargets[switchTarget].generateBreak(); } } // Pass a [NullJumpHandler] because the target for the contained break // is not the generated switch statement but instead the loop generated // in the call to [handleLoop] below. handleSwitch(node, new NullJumpHandler(compiler), buildExpression, node.cases, getConstants, (_) => false, // No case is default. buildSwitchCase); } void buildLoop() { handleLoop(node, () {}, buildCondition, () {}, buildSwitch); } if (hasDefault) { buildLoop(); } else { // If the switch statement has no default case, surround the loop with // a test of the target. void buildCondition() { js.Template code = js.js.parseForeignJS('#'); push(createForeign(code, backend.boolType, [localsHandler.readLocal(switchTarget)])); } handleIf(node, buildCondition, buildLoop, () => {}); } } /** * Creates a switch statement. * * [jumpHandler] is the [JumpHandler] for the created switch statement. * [buildExpression] creates the switch expression. * [switchCases] must be either an [Iterable] of [ast.SwitchCase] nodes or * a [Link] or a [ast.NodeList] of [ast.SwitchCase] nodes. * [getConstants] returns the set of constants for a switch case. * [isDefaultCase] returns [:true:] if the provided switch case should be * considered default for the created switch statement. * [buildSwitchCase] creates the statements for the switch case. */ void handleSwitch( ast.Node errorNode, JumpHandler jumpHandler, HInstruction buildExpression(), var switchCases, Iterable getConstants(ast.SwitchCase switchCase), bool isDefaultCase(ast.SwitchCase switchCase), void buildSwitchCase(ast.SwitchCase switchCase)) { Map