Index: pkg/compiler/lib/src/ssa/builder.dart |
diff --git a/pkg/compiler/lib/src/ssa/builder.dart b/pkg/compiler/lib/src/ssa/builder.dart |
deleted file mode 100644 |
index abd5e29594869b5486b149f52854b583ddcb859b..0000000000000000000000000000000000000000 |
--- a/pkg/compiler/lib/src/ssa/builder.dart |
+++ /dev/null |
@@ -1,6578 +0,0 @@ |
-// 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; |
- |
-/// 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; |
- |
- String get name => 'SSA builder'; |
- |
- SsaBuilderTask(JavaScriptBackend backend) |
- : 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); |
- 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<Local, HInstruction> directLocals = |
- new Map<Local, HInstruction>(); |
- Map<Local, CapturedVariable> redirectionMapping = |
- new Map<Local, CapturedVariable>(); |
- SsaBuilder builder; |
- ClosureClassMap closureData; |
- Map<TypeVariableType, TypeVariableLocal> typeVariableLocals = |
- new Map<TypeVariableType, TypeVariableLocal>(); |
- 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<Local, HInstruction>.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 HForeign(js.js.parseForeignJS('{}'), |
- backend.nonNullType, |
- <HInstruction>[]); |
- 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<LocalVariableElement> 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.usedVariablesInTry.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 isUsedInTry(Local local) { |
- return closureData.usedVariablesInTry.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(isUsedInTry(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(isUsedInTry(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): |
- * <Create box> <== move the first box creation outside the loop. |
- * <initializer>; |
- * loop-entry: |
- * if (!<condition>) goto loop-exit; |
- * <body> |
- * <update box> // create a new box and copy the captured loop-variables. |
- * <updates> |
- * 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<Local, HInstruction> savedDirectLocals = |
- new Map<Local, HInstruction>.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<Local, HInstruction> joinedLocals = |
- new Map<Local, HInstruction>(); |
- 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, <HInstruction>[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<LocalsHandler> localsHandlers, |
- HBasicBlock joinBlock) { |
- assert(localsHandlers.length > 0); |
- if (localsHandlers.length == 1) return localsHandlers[0]; |
- Map<Local, HInstruction> joinedLocals = |
- new Map<Local, HInstruction>(); |
- 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<Local, HInstruction>(); |
- 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<LabelDefinition> 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<LabelDefinition> labels() => const <LabelDefinition>[]; |
- 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<JumpHandlerEntry> jumps; |
- |
- TargetJumpHandler(SsaBuilder builder, this.target) |
- : this.builder = builder, |
- jumps = <JumpHandlerEntry>[] { |
- 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<LabelDefinition> labels() { |
- List<LabelDefinition> result = null; |
- for (LabelDefinition element in target.labels) { |
- if (result == null) result = <LabelDefinition>[]; |
- result.add(element); |
- } |
- return (result == null) ? const <LabelDefinition>[] : 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<JumpTarget, int> targetIndexMap = new Map<JumpTarget, int>(); |
- |
- 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 ResolvedVisitor { |
- final Compiler compiler; |
- final JavaScriptBackend backend; |
- final ConstantSystem constantSystem; |
- final CodegenWorkItem work; |
- final RuntimeTypes rti; |
- |
- /* 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<Element> sourceElementStack = <Element>[]; |
- |
- LocalsHandler localsHandler; |
- |
- HInstruction rethrowableException; |
- |
- HParameterValue lastAddedParameter; |
- |
- Map<ParameterElement, HInstruction> parameters = |
- <ParameterElement, HInstruction>{}; |
- |
- Map<JumpTarget, JumpHandler> jumpTargets = <JumpTarget, JumpHandler>{}; |
- |
- /** |
- * 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<Local, HLocalValue> activationVariables = |
- <Local, HLocalValue>{}; |
- |
- // We build the Ssa graph by simulating a stack machine. |
- List<HInstruction> stack = <HInstruction>[]; |
- |
- SsaBuilder(JavaScriptBackend backend, |
- CodegenWorkItem work, |
- this.nativeEmitter) |
- : 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; |
- |
- 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( |
- SourceFileLocation 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<HInstruction> completeSendArgumentsList( |
- FunctionElement function, |
- Selector selector, |
- List<HInstruction> 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 [addStaticSendArgumentsToList]. |
- 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<HInstruction> completeDynamicSendArgumentsList( |
- Selector selector, |
- FunctionElement function, |
- List<HInstruction> providedArguments) { |
- assert(selector.applies(function, compiler.world)); |
- FunctionSignature signature = function.functionSignature; |
- List<HInstruction> compiledArguments = new List<HInstruction>( |
- 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<String> selectorArgumentNames = selector.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<HInstruction> 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; |
- 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 heuristicSayGoodToGo() { |
- // Don't inline recursivly |
- if (inliningStack.any((entry) => entry.function == function)) { |
- return false; |
- } |
- |
- if (inExpressionOfThrow) return false; |
- |
- if (element.isSynthesized) return true; |
- |
- if (cachedCanBeInlined == true) return cachedCanBeInlined; |
- |
- if (backend.functionsToAlwaysInline.contains(function)) { |
- // Inline this function regardless of it's size. |
- assert(InlineWeeder.canBeInlined(function.node, -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. |
- TypesInferrer inferrer = compiler.typesTask.typesInferrer; |
- if (inferrer.isCalledOnce(element) && allInlinedFunctionsCalledOnce) { |
- useMaxInliningNodes = false; |
- } |
- bool canInline; |
- ast.FunctionExpression functionNode = function.node; |
- canInline = InlineWeeder.canBeInlined( |
- functionNode, 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<HInstruction> 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; |
- } |
- |
- 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 |
- /// [HForeignNews] 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<DartType> currentInlinedInstantiations = <DartType>[]; |
- |
- final List<AstInliningState> inliningStack = <AstInliningState>[]; |
- |
- 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<Element, TypeMask> cachedTypesOfCapturedVariables = |
- new Map<Element, TypeMask>(); |
- |
- 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 = |
- potentiallyCheckType(parameter, field.type); |
- add(new HFieldSet(field, thisInstruction, value)); |
- return closeFunction(); |
- } |
- |
- HGraph buildLazyInitializer(VariableElement variable) { |
- ast.Node node = variable.node; |
- openFunction(variable, node); |
- assert(variable.initializer != null); |
- visit(variable.initializer); |
- HInstruction value = pop(); |
- value = potentiallyCheckType(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<HInstruction> 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 = <HInstruction>[]; |
- |
- 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 (!compiler.enableTypeAssertions) return; |
- |
- FunctionSignature signature = function.functionSignature; |
- signature.orderedForEachParameter((ParameterElement parameter) { |
- HInstruction argument = localsHandler.readLocal(parameter); |
- potentiallyCheckType(argument, parameter.type); |
- }); |
- } |
- |
- /** |
- * Documentation wanted -- johnniwinther |
- * |
- * Invariant: [constructors] must contain only implementation elements. |
- */ |
- void inlineSuperOrRedirect(FunctionElement callee, |
- List<HInstruction> compiledArguments, |
- List<FunctionElement> constructors, |
- Map<Element, HInstruction> 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<DartType> arguments = type.typeArguments; |
- List<DartType> typeVariables = enclosingClass.typeVariables; |
- if (!type.isRaw) { |
- assert(arguments.length == typeVariables.length); |
- Iterator<DartType> 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<FunctionElement> constructors, |
- Map<Element, HInstruction> fieldValues) { |
- assert(invariant(constructor, constructor.isImplementation)); |
- if (constructor.isSynthesized) { |
- List<HInstruction> arguments = <HInstruction>[]; |
- HInstruction compileArgument(ParameterElement parameter) { |
- return localsHandler.readLocal(parameter); |
- } |
- |
- Element target = constructor.definingConstructor.implementation; |
- bool match = Selector.addForwardingElementArgumentsToList( |
- constructor, |
- arguments, |
- target, |
- compileArgument, |
- handleConstantForOptionalParameter, |
- compiler.world); |
- if (!match) { |
- // 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<ast.Node> initializers = functionNode.initializers.nodes; |
- for (Link<ast.Node> 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; |
- Selector selector = elements.getSelector(call); |
- Link<ast.Node> arguments = call.arguments; |
- List<HInstruction> compiledArguments = new List<HInstruction>(); |
- inlinedFrom(constructor, () { |
- addStaticSendArgumentsToList(selector, |
- arguments, |
- target, |
- compiledArguments); |
- }); |
- inlineSuperOrRedirect(target, |
- compiledArguments, |
- constructors, |
- fieldValues, |
- constructor); |
- } else { |
- // A field initializer. |
- ast.SendSet init = link.head; |
- Link<ast.Node> 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); |
- Selector selector = |
- new Selector.callDefaultConstructor(enclosingClass.library); |
- // TODO(johnniwinther): Should we find injected constructors as well? |
- FunctionElement target = superClass.lookupConstructor(selector); |
- if (target == null) { |
- compiler.internalError(superClass, |
- "No default constructor available."); |
- } |
- List<HInstruction> arguments = <HInstruction>[]; |
- selector.addArgumentsToList(const Link<ast.Node>(), |
- arguments, |
- target.implementation, |
- null, |
- handleConstantForOptionalParameter, |
- compiler.world); |
- 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<Element, HInstruction> fieldValues) { |
- assert(invariant(classElement, classElement.isImplementation)); |
- classElement.forEachInstanceField( |
- (ClassElement enclosingClass, VariableElement member) { |
- 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<Element, HInstruction> fieldValues = new Map<Element, HInstruction>(); |
- |
- // 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<FunctionElement> constructors = <FunctionElement>[functionElement]; |
- buildInitializers(functionElement, constructors, fieldValues); |
- |
- // Call the JavaScript constructor with the fields as argument. |
- List<HInstruction> constructorArguments = <HInstruction>[]; |
- List<Element> fields = <Element>[]; |
- |
- classElement.forEachInstanceField( |
- (ClassElement enclosingClass, VariableElement member) { |
- HInstruction value = fieldValues[member]; |
- if (value == null) { |
- // Uninitialized native fields are pre-initialized by the native |
- // implementation. |
- assert(isNativeUpgradeFactory); |
- } else { |
- fields.add(member); |
- DartType type = localsHandler.substInContext(member.type); |
- constructorArguments.add(potentiallyCheckType(value, type)); |
- } |
- }, |
- includeSuperAndInjectedMembers: true); |
- |
- InterfaceType type = classElement.thisType; |
- TypeMask ssaType = |
- new TypeMask.nonNullExact(classElement.declaration, compiler.world); |
- List<DartType> instantiatedTypes; |
- addInlinedInstantiation(type); |
- if (!currentInlinedInstantiations.isEmpty) { |
- instantiatedTypes = new List<DartType>.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<HInstruction> typeArguments = <HInstruction>[]; |
- 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 = <HInstruction>[]; |
- 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)); |
- } |
- }); |
- |
- 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 there are locals that escape (ie mutated in closures), we |
- // pass the box to the constructor. |
- ClosureScope scopeData = parameterClosureData.capturingScopes[node]; |
- if (scopeData != null) { |
- bodyCallInputs.add(localsHandler.readLocal(scopeData.boxElement)); |
- } |
- |
- 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 = |
- potentiallyCheckType(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, |
- <HInstruction>[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 potentiallyBuildTypeHint(HInstruction original, DartType type) { |
- if (!compiler.trustTypeAnnotations || type == null) return original; |
- type = localsHandler.substInContext(type); |
- if (!type.isInterfaceType) return original; |
- TypeMask mask = new TypeMask.subtype(type.element, compiler.world); |
- var result = new HTypeKnown.pinned(mask, original); |
- return result; |
- } |
- |
- HInstruction potentiallyCheckType(HInstruction original, DartType type, |
- { int kind: HTypeConversion.CHECKED_MODE_CHECK }) { |
- if (!compiler.enableTypeAssertions) return original; |
- type = localsHandler.substInContext(type); |
- HInstruction other = buildTypeConversion(original, type, kind); |
- if (other != original) add(other); |
- registry.registerIsCheck(type); |
- return other; |
- } |
- |
- 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 = <HInstruction>[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 (compiler.enableTypeAssertions) { |
- return potentiallyCheckType( |
- 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 (node != null) { |
- target.sourcePosition = sourceFileLocationForBeginToken(node); |
- } |
- return target; |
- } |
- |
- SourceFileLocation sourceFileLocationForBeginToken(ast.Node node) => |
- sourceFileLocationForToken(node, node.getBeginToken()); |
- |
- SourceFileLocation sourceFileLocationForEndToken(ast.Node node) => |
- sourceFileLocationForToken(node, node.getEndToken()); |
- |
- SourceFileLocation sourceFileLocationForToken(ast.Node node, Token token) { |
- SourceFile sourceFile = currentSourceFile(); |
- SourceFileLocation location = |
- new TokenSourceFileLocation(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<ast.Node> 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<LocalsHandler> breakHandlers = <LocalsHandler>[]; |
- // 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: |
- // <initializer> |
- // loop-entry: |
- // if (!<condition>) goto loop-exit; |
- // <body> |
- // <updates> |
- // 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<LocalsHandler> continueHandlers = <LocalsHandler>[]; |
- 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<LabelDefinition> 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, |
- sourceFileLocationForBeginToken(loop), |
- sourceFileLocationForEndToken(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), |
- <LabelDefinition>[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<LocalsHandler> continueHandlers = <LocalsHandler>[]; |
- 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<LabelDefinition> 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, |
- sourceFileLocationForBeginToken(node), |
- sourceFileLocationForEndToken(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), <LabelDefinition>[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<HInstruction> capturedVariables = <HInstruction>[]; |
- 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 visitUnary(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 visitBinary(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, <HInstruction>[]); |
- } 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.isErroneousElement(element)) { |
- // An erroneous element indicates an unresolved static getter. |
- generateThrowNoSuchMethod(send, |
- noSuchMethodTargetSymbolString(element, 'get'), |
- argumentNodes: const Link<ast.Node>()); |
- } 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, <HInstruction>[value]); |
- pop(); |
- } else { |
- VariableElement field = element; |
- value = |
- potentiallyCheckType(value, field.type); |
- addWithPosition(new HStaticStore(element, value), location); |
- } |
- stack.add(value); |
- } else if (Elements.isErroneousElement(element)) { |
- List<HInstruction> arguments = |
- send == null ? const <HInstruction>[] : <HInstruction>[value]; |
- // An erroneous element indicates an unresolved static setter. |
- generateThrowNoSuchMethod(location, |
- noSuchMethodTargetSymbolString(element, 'set'), |
- argumentValues: arguments); |
- } 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 checked = |
- potentiallyCheckType(value, local.type); |
- if (!identical(checked, value)) { |
- pop(); |
- stack.add(checked); |
- } |
- HInstruction trusted = |
- potentiallyBuildTypeHint(checked, local.type); |
- if (!identical(trusted, checked)) { |
- pop(); |
- push(trusted); |
- } |
- |
- localsHandler.updateLocal(local, trusted); |
- } |
- } |
- |
- HInstruction invokeInterceptor(HInstruction receiver) { |
- HInterceptor interceptor = new HInterceptor(receiver, backend.nonNullType); |
- add(interceptor); |
- return interceptor; |
- } |
- |
- HForeign createForeign(js.Template code, |
- TypeMask type, |
- List<HInstruction> inputs) { |
- return new HForeign(code, type, inputs); |
- } |
- |
- HLiteralList buildLiteralList(List<HInstruction> 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(<HInstruction>[addTypeVariableReference(type)]); |
- } else { |
- assert(type.element.isClass); |
- InterfaceType interface = type; |
- List<HInstruction> inputs = <HInstruction>[]; |
- bool first = true; |
- List<String> templates = <String>[]; |
- 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) { |
- visitUnary(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(); |
- visitBinary(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<HInstruction> inputs = <HInstruction>[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<HInstruction> inputs = <HInstruction>[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); |
- } |
- // TODO(johnniwinther): Avoid interceptor if unneeded. |
- 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<HInstruction> list) { |
- Selector selector = elements.getSelector(node); |
- if (selector.namedArgumentCount == 0) { |
- addGenericSendArgumentsToList(node.arguments, list); |
- } else { |
- // Visit positional arguments and add them to the list. |
- Link<ast.Node> arguments = node.arguments; |
- int positionalArgumentCount = selector.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<String, HInstruction> instructions = |
- new Map<String, HInstruction>(); |
- List<String> namedArguments = selector.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<String> orderedNames = selector.getOrderedNamedArguments(); |
- for (String name in orderedNames) { |
- list.add(instructions[name]); |
- } |
- } |
- } |
- |
- /** |
- * Returns true if the arguments were compatible with the function signature. |
- * |
- * Invariant: [element] must be an implementation element. |
- */ |
- bool addStaticSendArgumentsToList(Selector selector, |
- Link<ast.Node> arguments, |
- FunctionElement element, |
- List<HInstruction> list) { |
- assert(invariant(element, element.isImplementation)); |
- |
- HInstruction compileArgument(ast.Node argument) { |
- visit(argument); |
- return pop(); |
- } |
- |
- return selector.addArgumentsToList(arguments, |
- list, |
- element, |
- compileArgument, |
- handleConstantForOptionalParameter, |
- compiler.world); |
- } |
- |
- void addGenericSendArgumentsToList(Link<ast.Node> link, List<HInstruction> list) { |
- for (; !link.isEmpty; link = link.tail) { |
- visit(link.head); |
- list.add(pop()); |
- } |
- } |
- |
- visitDynamicSend(ast.Send node) { |
- Selector selector = elements.getSelector(node); |
- |
- List<HInstruction> inputs = <HInstruction>[]; |
- 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 = <HInstruction>[]; |
- 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<ast.Node> 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<HInstruction> inputs = <HInstruction>[]; |
- addGenericSendArgumentsToList(link.tail.tail, inputs); |
- |
- TypeMask ssaType = |
- TypeMaskFactory.fromNativeBehavior(nativeBehavior, compiler); |
- |
- if (nativeBehavior.codeTemplate.isExpression) { |
- push(new HForeign(nativeBehavior.codeTemplate, ssaType, inputs, |
- effects: nativeBehavior.sideEffects, |
- nativeBehavior: nativeBehavior)); |
- } else { |
- push(new HForeign(nativeBehavior.codeTemplate, ssaType, inputs, |
- isStatement: true, |
- effects: nativeBehavior.sideEffects, |
- nativeBehavior: nativeBehavior, |
- canThrow: true)); |
- } |
- } |
- |
- void handleJsStringConcat(ast.Send node) { |
- List<HInstruction> inputs = <HInstruction>[]; |
- 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 HForeign(js.js.parseForeignJS(name), |
- backend.dynamicType, |
- <HInstruction>[])); |
- } 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<ast.Node> 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; |
- if (name == 'MUST_RETAIN_METADATA') { |
- value = backend.mustRetainMetadata; |
- } else { |
- compiler.reportError( |
- node, MessageKind.GENERIC, |
- {'text': 'Error: Unknown internal flag "$name".'}); |
- } |
- stack.add(graph.addConstantBool(value, compiler)); |
- } |
- |
- void handleForeignJsGetName(ast.Send node) { |
- List<ast.Node> 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; |
- } |
- ast.LiteralString string = argument.asLiteralString(); |
- if (string == null) { |
- compiler.reportError( |
- argument, MessageKind.GENERIC, |
- {'text': 'Error: Expected a literal string.'}); |
- } |
- stack.add( |
- addConstantString( |
- backend.namer.getNameForJsGetName( |
- argument, string.dartString.slowToString()))); |
- } |
- |
- void handleForeignJsEmbeddedGlobal(ast.Send node) { |
- List<ast.Node> 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 HForeign(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<ast.Node> 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, |
- <HInstruction>[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<HInstruction> inputs = <HInstruction>[]; |
- 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 HForeign(js.js.expressionTemplateYielding( |
- backend.namer.elementAccess(element)), |
- backend.dynamicType, |
- <HInstruction>[])); |
- 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 HForeign(js.js.parseForeignJS("$isolateName = #"), |
- backend.dynamicType, |
- <HInstruction>[pop()], |
- effects: sideEffects)); |
- } |
- |
- void handleForeignCreateIsolate(ast.Send node) { |
- if (!node.arguments.isEmpty) { |
- compiler.internalError(node.argumentsNode, 'Too many arguments.'); |
- } |
- String constructorName = backend.namer.isolateName; |
- push(new HForeign(js.js.parseForeignJS("new $constructorName()"), |
- backend.dynamicType, |
- <HInstruction>[])); |
- } |
- |
- void handleForeignDartObjectJsConstructorFunction(ast.Send node) { |
- if (!node.arguments.isEmpty) { |
- compiler.internalError(node.argumentsNode, 'Too many arguments.'); |
- } |
- push(new HForeign(js.js.expressionTemplateYielding( |
- backend.namer.elementAccess(compiler.objectClass)), |
- backend.dynamicType, |
- <HInstruction>[])); |
- } |
- |
- void handleForeignJsCurrentIsolate(ast.Send node) { |
- if (!node.arguments.isEmpty) { |
- compiler.internalError(node.argumentsNode, 'Too many arguments.'); |
- } |
- push(new HForeign(js.js.parseForeignJS(backend.namer.currentIsolate), |
- backend.dynamicType, |
- <HInstruction>[])); |
- } |
- |
- 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_CREATE_ISOLATE') { |
- handleForeignCreateIsolate(node); |
- } else if (name == 'JS_OPERATOR_IS_PREFIX') { |
- stack.add(addConstantString(backend.namer.operatorIsPrefix())); |
- } else if (name == 'JS_OBJECT_CLASS_NAME') { |
- String name = backend.namer.getRuntimeTypeName(compiler.objectClass); |
- stack.add(addConstantString(name)); |
- } else if (name == 'JS_NULL_CLASS_NAME') { |
- String name = backend.namer.getRuntimeTypeName(compiler.nullClass); |
- stack.add(addConstantString(name)); |
- } else if (name == 'JS_FUNCTION_CLASS_NAME') { |
- String name = backend.namer.getRuntimeTypeName(compiler.functionClass); |
- stack.add(addConstantString(name)); |
- } else if (name == 'JS_OPERATOR_AS_PREFIX') { |
- stack.add(addConstantString(backend.namer.operatorAsPrefix())); |
- } else if (name == 'JS_SIGNATURE_NAME') { |
- stack.add(addConstantString(backend.namer.operatorSignature())); |
- } else if (name == 'JS_FUNCTION_TYPE_TAG') { |
- stack.add(addConstantString(backend.namer.functionTypeTag())); |
- } else if (name == 'JS_FUNCTION_TYPE_VOID_RETURN_TAG') { |
- stack.add(addConstantString(backend.namer.functionTypeVoidReturnTag())); |
- } else if (name == 'JS_FUNCTION_TYPE_RETURN_TYPE_TAG') { |
- stack.add(addConstantString(backend.namer.functionTypeReturnTypeTag())); |
- } else if (name == |
- 'JS_FUNCTION_TYPE_REQUIRED_PARAMETERS_TAG') { |
- stack.add(addConstantString( |
- backend.namer.functionTypeRequiredParametersTag())); |
- } else if (name == |
- 'JS_FUNCTION_TYPE_OPTIONAL_PARAMETERS_TAG') { |
- stack.add(addConstantString( |
- backend.namer.functionTypeOptionalParametersTag())); |
- } else if (name == |
- 'JS_FUNCTION_TYPE_NAMED_PARAMETERS_TAG') { |
- stack.add(addConstantString( |
- backend.namer.functionTypeNamedParametersTag())); |
- } else if (name == 'JS_DART_OBJECT_CONSTRUCTOR') { |
- handleForeignDartObjectJsConstructorFunction(node); |
- } else if (name == 'JS_IS_INDEXABLE_FIELD_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<HInstruction> 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<HInstruction>(); |
- 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 = <HInstruction>[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<HInstruction> arguments = <HInstruction>[]; |
- if (!node.isPropertyAccess) { |
- addGenericSendArgumentsToList(node.arguments, arguments); |
- } |
- return generateSuperNoSuchMethodSend(node, selector, arguments); |
- } |
- List<HInstruction> inputs = <HInstruction>[]; |
- 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 [addStaticSendArgumentsToList]. |
- FunctionElement function = element.implementation; |
- bool succeeded = addStaticSendArgumentsToList(selector, node.arguments, |
- function, inputs); |
- assert(succeeded); |
- 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 = <HInstruction>[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<ClassElement> 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.getNameForRti(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 { |
- // TODO(ngeoffray): Match the VM behavior and throw an |
- // exception at runtime. |
- compiler.internalError(type.element, |
- 'Unimplemented unresolved type variable.'); |
- 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<HInstruction> inputs = <HInstruction>[]; |
- |
- 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<HInstruction> inputs = <HInstruction>[]; |
- 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<HInstruction> 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, |
- <HInstruction>[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]; |
- Selector selector = elements.getSelector(send); |
- 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')); |
- selector = compiler.symbolValidatedConstructorSelector; |
- assert(invariant(send, selector != 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 (checkTypeVariableBounds(node, type)) return; |
- |
- var inputs = <HInstruction>[]; |
- 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 [addStaticSendArgumentsToList]. |
- bool succeeded = addStaticSendArgumentsToList(selector, send.arguments, |
- constructor.implementation, |
- inputs); |
- if (!succeeded) { |
- generateWrongArgumentCountError(send, constructor, send.arguments); |
- return; |
- } |
- |
- if (constructor.isFactoryConstructor && |
- !expectedType.typeArguments.isEmpty) { |
- registry.registerFactoryWithTypeArguments(); |
- } |
- |
- 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; |
- } |
- HForeign foreign = new HForeign( |
- 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 = init'); |
- // We set the instruction as [canThrow] to avoid it being dead code. |
- // We need a finer grained side effect. |
- add(new HForeign( |
- code, backend.nullType, [stack.last], canThrow: true)); |
- } |
- } else if (isGrowableListConstructorCall) { |
- push(buildLiteralList(<HInstruction>[])); |
- 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 = potentiallyCheckType(newInstance, type); |
- if (checked != newInstance) { |
- pop(); |
- stack.add(checked); |
- } |
- } |
- } |
- |
- void potentiallyAddTypeArguments(List<HInstruction> 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<DartType, Set<DartType>> seenChecksMap = |
- new Map<DartType, Set<DartType>>(); |
- 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<DartType> seenChecks = |
- seenChecksMap.putIfAbsent(typeArgument, () => new Set<DartType>()); |
- 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; |
- } |
- |
- visitAssert(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) { |
- Selector selector = elements.getSelector(node); |
- Element element = elements[node]; |
- if (elements.isAssert(node)) { |
- element = backend.assertMethod; |
- } |
- if (element.isForeign(backend) && element.isFunction) { |
- visitForeignSend(node); |
- return; |
- } |
- if (element.isErroneous) { |
- // An erroneous element indicates that the funciton could not be resolved |
- // (a warning has been issued). |
- generateThrowNoSuchMethod(node, |
- noSuchMethodTargetSymbolString(element), |
- argumentNodes: node.arguments); |
- return; |
- } |
- invariant(element, !element.isGenerativeConstructor); |
- generateIsDeferredLoadedCheckIfNeeded(node); |
- if (element.isFunction) { |
- var inputs = <HInstruction>[]; |
- // TODO(5347): Try to avoid the need for calling [implementation] before |
- // calling [addStaticSendArgumentsToList]. |
- bool succeeded = addStaticSendArgumentsToList(selector, node.arguments, |
- element.implementation, |
- inputs); |
- if (!succeeded) { |
- generateWrongArgumentCountError(node, element, node.arguments); |
- return; |
- } |
- |
- 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<HInstruction> inputs = <HInstruction>[pop()]; |
- addDynamicSendArgumentsToList(node, inputs); |
- Selector closureSelector = new Selector.callClosureFrom(selector); |
- 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('unexpected type kind ${type.kind}', node: node); |
- } |
- 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<HInstruction> inputs = <HInstruction>[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(String reason, {ast.Node node}) { |
- 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<ast.Node> argumentNodes, |
- List<HInstruction> argumentValues, |
- List<String> 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 = <HInstruction>[]; |
- argumentNodes.forEach((argumentNode) { |
- visit(argumentNode); |
- HInstruction value = pop(); |
- argumentValues.add(value); |
- }); |
- } |
- HInstruction arguments = buildLiteralList(argumentValues); |
- add(arguments); |
- HInstruction existingNamesList; |
- if (existingArguments != null) { |
- List<HInstruction> existingNames = <HInstruction>[]; |
- 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<ast.Node> argumentNodes) { |
- List<String> existingArguments = <String>[]; |
- 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.isErroneousElement(element)) { |
- ConstructorElement function = element; |
- element = function.effectiveTarget; |
- } |
- if (Elements.isErroneousElement(element)) { |
- 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<HInstruction> 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<HInstruction> inputs = <HInstruction>[]; |
- 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<HInstruction> 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<DartType>.from( |
- currentInlinedInstantiations); |
- } |
- instruction.sideEffects = compiler.world.getSideEffectsOfElement(element); |
- if (location == null) { |
- push(instruction); |
- } else { |
- pushWithPosition(instruction, location); |
- } |
- } |
- |
- HInstruction buildInvokeSuper(Selector selector, |
- Element element, |
- List<HInstruction> arguments) { |
- HInstruction receiver = localsHandler.readThis(); |
- // TODO(5346): Try to avoid the need for calling [declaration] before |
- // creating an [HStatic]. |
- List<HInstruction> inputs = <HInstruction>[]; |
- 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<ast.Node> arguments) { |
- HInstruction rhs; |
- if (node.isPrefix || node.isPostfix) { |
- rhs = graph.addConstantInt(1, compiler); |
- } else { |
- visit(arguments.head); |
- assert(arguments.tail.isEmpty); |
- rhs = pop(); |
- } |
- visitBinary(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<HInstruction> setterInputs = <HInstruction>[]; |
- if (identical(node.assignmentOperator.source, '=')) { |
- addDynamicSendArgumentsToList(node, setterInputs); |
- result = setterInputs.last; |
- } else { |
- Element getter = elements[node.selector]; |
- List<HInstruction> getterInputs = <HInstruction>[]; |
- Link<ast.Node> 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<ast.Node> 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<ast.Node> 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<ast.Node> 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<HInstruction> inputs = <HInstruction>[]; |
- FunctionSignature targetSignature = targetConstructor.functionSignature; |
- FunctionSignature redirectingSignature = |
- redirectingConstructor.functionSignature; |
- redirectingSignature.forEachRequiredParameter((ParameterElement element) { |
- inputs.add(localsHandler.readLocal(element)); |
- }); |
- List<Element> targetOptionals = |
- targetSignature.orderedOptionalParameters; |
- List<Element> 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); |
- } |
- |
- 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(); |
- value = potentiallyCheckType(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) { |
- // Dummy implementation. |
- visit(node.expression); |
- pop(); |
- } |
- |
- visitAwait(ast.Await node) { |
- // Dummy implementation. |
- visit(node.expression); |
- } |
- |
- visitTypeAnnotation(ast.TypeAnnotation node) { |
- compiler.internalError(node, |
- 'Visiting type annotation in SSA builder.'); |
- } |
- |
- visitVariableDefinitions(ast.VariableDefinitions node) { |
- assert(isReachable); |
- for (Link<ast.Node> 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<HInstruction> arguments = <HInstruction>[]; |
- 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<HInstruction> inputs = <HInstruction>[]; |
- for (Link<ast.Node> 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); |
- } |
- |
- visitForIn(ast.ForIn node) { |
- // Generate a structure equivalent to: |
- // Iterator<E> $iter = <iterable>.iterator; |
- // while ($iter.moveNext()) { |
- // E <declaredIdentifier> = $iter.current; |
- // <body> |
- // } |
- |
- // 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<LocalsHandler> breakHandlers = <LocalsHandler>[]; |
- 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<HInstruction> listInputs = <HInstruction>[]; |
- for (Link<ast.Node> link = node.entries.nodes; |
- !link.isEmpty; |
- link = link.tail) { |
- visit(link.head); |
- listInputs.add(pop()); |
- listInputs.add(pop()); |
- } |
- |
- ConstructorElement constructor; |
- List<HInstruction> inputs = <HInstruction>[]; |
- |
- 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); |
- |
- if (constructor.isFactoryConstructor) { |
- registry.registerFactoryWithTypeArguments(); |
- } |
- |
- ClassElement cls = constructor.enclosingClass; |
- |
- if (backend.classNeedsRti(cls)) { |
- List<DartType> 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<ast.CaseMatch, ConstantValue> buildSwitchCaseConstants( |
- ast.SwitchStatement node) { |
- |
- Map<ast.CaseMatch, ConstantValue> constants = |
- new Map<ast.CaseMatch, ConstantValue>(); |
- 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<ast.CaseMatch, ConstantValue> constants = |
- buildSwitchCaseConstants(node); |
- |
- // The switch case indices must match those computed in |
- // [SwitchCaseJumpHandler]. |
- bool hasContinue = false; |
- Map<ast.SwitchCase, int> caseIndex = new Map<ast.SwitchCase, int>(); |
- 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<ast.CaseMatch, ConstantValue> constants) { |
- JumpHandler jumpHandler = createJumpHandler(node, isLoopJump: false); |
- HInstruction buildExpression() { |
- visit(node.expression); |
- return pop(); |
- } |
- Iterable<ConstantValue> getConstants(ast.SwitchCase switchCase) { |
- List<ConstantValue> constantList = <ConstantValue>[]; |
- 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<ast.CaseMatch, ConstantValue> constants, |
- Map<ast.SwitchCase, int> 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<ConstantValue> getConstants(ast.SwitchCase switchCase) { |
- List<ConstantValue> constantList = <ConstantValue>[]; |
- 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<ConstantValue> getConstants(ast.SwitchCase switchCase) { |
- return <ConstantValue>[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<ConstantValue> getConstants(ast.SwitchCase switchCase), |
- bool isDefaultCase(ast.SwitchCase switchCase), |
- void buildSwitchCase(ast.SwitchCase switchCase)) { |
- |
- Map<ast.CaseMatch, ConstantValue> constants = |
- new Map<ast.CaseMatch, ConstantValue>(); |
- |
- HBasicBlock expressionStart = openNewBlock(); |
- HInstruction expression = buildExpression(); |
- if (switchCases.isEmpty) { |
- return; |
- } |
- |
- HSwitch switchInstruction = new HSwitch(<HInstruction>[expression]); |
- HBasicBlock expressionEnd = close(switchInstruction); |
- LocalsHandler savedLocals = localsHandler; |
- |
- List<HStatementInformation> statements = <HStatementInformation>[]; |
- bool hasDefault = false; |
- Element getFallThroughErrorElement = backend.getFallThroughError(); |
- HasNextIterator<ast.Node> caseIterator = |
- new HasNextIterator<ast.Node>(switchCases.iterator); |
- while (caseIterator.hasNext) { |
- ast.SwitchCase switchCase = caseIterator.next(); |
- HBasicBlock block = graph.addNewBlock(); |
- for (ConstantValue constant in getConstants(switchCase)) { |
- HConstant hConstant = graph.addConstant(constant, compiler); |
- switchInstruction.inputs.add(hConstant); |
- hConstant.usedBy.add(switchInstruction); |
- expressionEnd.addSuccessor(block); |
- } |
- |
- if (isDefaultCase(switchCase)) { |
- // An HSwitch has n inputs and n+1 successors, the last being the |
- // default case. |
- expressionEnd.addSuccessor(block); |
- hasDefault = true; |
- } |
- open(block); |
- localsHandler = new LocalsHandler.from(savedLocals); |
- buildSwitchCase(switchCase); |
- if (!isAborted()) { |
- if (caseIterator.hasNext) { |
- pushInvokeStatic(switchCase, getFallThroughErrorElement, []); |
- HInstruction error = pop(); |
- closeAndGotoExit(new HThrow(error)); |
- } else if (!isDefaultCase(switchCase)) { |
- // If there is no default, we will add one later to avoid |
- // the critical edge. So we generate a break statement to make |
- // sure the last case does not fall through to the default case. |
- jumpHandler.generateBreak(); |
- } |
- } |
- statements.add( |
- new HSubGraphBlockInformation(new SubGraph(block, lastOpenedBlock))); |
- } |
- |
- // Add a join-block if necessary. |
- // We create [joinBlock] early, and then go through the cases that might |
- // want to jump to it. In each case, if we add [joinBlock] as a successor |
- // of another block, we also add an element to [caseHandlers] that is used |
- // to create the phis in [joinBlock]. |
- // If we never jump to the join block, [caseHandlers] will stay empty, and |
- // the join block is never added to the graph. |
- HBasicBlock joinBlock = new HBasicBlock(); |
- List<LocalsHandler> caseHandlers = <LocalsHandler>[]; |
- jumpHandler.forEachBreak((HBreak instruction, LocalsHandler locals) { |
- instruction.block.addSuccessor(joinBlock); |
- caseHandlers.add(locals); |
- }); |
- jumpHandler.forEachContinue((HContinue instruction, LocalsHandler locals) { |
- assert(invariant(errorNode, false, |
- message: 'Continue cannot target a switch.')); |
- }); |
- if (!isAborted()) { |
- current.close(new HGoto()); |
- lastOpenedBlock.addSuccessor(joinBlock); |
- caseHandlers.add(localsHandler); |
- } |
- if (!hasDefault) { |
- // Always create a default case, to avoid a critical edge in the |
- // graph. |
- HBasicBlock defaultCase = addNewBlock(); |
- expressionEnd.addSuccessor(defaultCase); |
- open(defaultCase); |
- close(new HGoto()); |
- defaultCase.addSuccessor(joinBlock); |
- caseHandlers.add(savedLocals); |
- statements.add(new HSubGraphBlockInformation(new SubGraph( |
- defaultCase, defaultCase))); |
- } |
- assert(caseHandlers.length == joinBlock.predecessors.length); |
- if (caseHandlers.length != 0) { |
- graph.addBlock(joinBlock); |
- open(joinBlock); |
- if (caseHandlers.length == 1) { |
- localsHandler = caseHandlers[0]; |
- } else { |
- localsHandler = savedLocals.mergeMultiple(caseHandlers, joinBlock); |
- } |
- } else { |
- // The joinblock is not used. |
- joinBlock = null; |
- } |
- |
- HSubExpressionBlockInformation expressionInfo = |
- new HSubExpressionBlockInformation(new SubExpression(expressionStart, |
- expressionEnd)); |
- expressionStart.setBlockFlow( |
- new HSwitchBlockInformation(expressionInfo, |
- statements, |
- jumpHandler.target, |
- jumpHandler.labels()), |
- joinBlock); |
- |
- jumpHandler.close(); |
- } |
- |
- visitSwitchCase(ast.SwitchCase node) { |
- compiler.internalError(node, 'SsaFromAstMixin.visitSwitchCase.'); |
- } |
- |
- visitCaseMatch(ast.CaseMatch node) { |
- compiler.internalError(node, 'SsaFromAstMixin.visitCaseMatch.'); |
- } |
- |
- visitTryStatement(ast.TryStatement node) { |
- // Save the current locals. The catch block and the finally block |
- // must not reuse the existing locals handler. None of the variables |
- // that have been defined in the body-block will be used, but for |
- // loops we will add (unnecessary) phis that will reference the body |
- // variables. This makes it look as if the variables were used |
- // in a non-dominated block. |
- LocalsHandler savedLocals = new LocalsHandler.from(localsHandler); |
- HBasicBlock enterBlock = openNewBlock(); |
- HTry tryInstruction = new HTry(); |
- close(tryInstruction); |
- bool oldInTryStatement = inTryStatement; |
- inTryStatement = true; |
- |
- HBasicBlock startTryBlock; |
- HBasicBlock endTryBlock; |
- HBasicBlock startCatchBlock; |
- HBasicBlock endCatchBlock; |
- HBasicBlock startFinallyBlock; |
- HBasicBlock endFinallyBlock; |
- |
- startTryBlock = graph.addNewBlock(); |
- open(startTryBlock); |
- visit(node.tryBlock); |
- // We use a [HExitTry] instead of a [HGoto] for the try block |
- // because it will have multiple successors: the join block, and |
- // the catch or finally block. |
- if (!isAborted()) endTryBlock = close(new HExitTry()); |
- SubGraph bodyGraph = new SubGraph(startTryBlock, lastOpenedBlock); |
- SubGraph catchGraph = null; |
- HLocalValue exception = null; |
- |
- if (!node.catchBlocks.isEmpty) { |
- localsHandler = new LocalsHandler.from(savedLocals); |
- startCatchBlock = graph.addNewBlock(); |
- open(startCatchBlock); |
- // Note that the name of this local is irrelevant. |
- SyntheticLocal local = |
- new SyntheticLocal('exception', localsHandler.executableContext); |
- exception = new HLocalValue(local, backend.nonNullType); |
- add(exception); |
- HInstruction oldRethrowableException = rethrowableException; |
- rethrowableException = exception; |
- |
- pushInvokeStatic(node, backend.getExceptionUnwrapper(), [exception]); |
- HInvokeStatic unwrappedException = pop(); |
- tryInstruction.exception = exception; |
- Link<ast.Node> link = node.catchBlocks.nodes; |
- |
- void pushCondition(ast.CatchBlock catchBlock) { |
- if (catchBlock.onKeyword != null) { |
- DartType type = elements.getType(catchBlock.type); |
- if (type == null) { |
- compiler.internalError(catchBlock.type, 'On with no type.'); |
- } |
- HInstruction condition = |
- buildIsNode(catchBlock.type, type, unwrappedException); |
- push(condition); |
- } else { |
- ast.VariableDefinitions declaration = catchBlock.formals.nodes.head; |
- HInstruction condition = null; |
- if (declaration.type == null) { |
- condition = graph.addConstantBool(true, compiler); |
- stack.add(condition); |
- } else { |
- // TODO(aprelev@gmail.com): Once old catch syntax is removed |
- // "if" condition above and this "else" branch should be deleted as |
- // type of declared variable won't matter for the catch |
- // condition. |
- DartType type = elements.getType(declaration.type); |
- if (type == null) { |
- compiler.internalError(catchBlock, 'Catch with unresolved type.'); |
- } |
- condition = buildIsNode(declaration.type, type, unwrappedException); |
- push(condition); |
- } |
- } |
- } |
- |
- void visitThen() { |
- ast.CatchBlock catchBlock = link.head; |
- link = link.tail; |
- if (catchBlock.exception != null) { |
- LocalVariableElement exceptionVariable = |
- elements[catchBlock.exception]; |
- localsHandler.updateLocal(exceptionVariable, |
- unwrappedException); |
- } |
- ast.Node trace = catchBlock.trace; |
- if (trace != null) { |
- pushInvokeStatic(trace, backend.getTraceFromException(), [exception]); |
- HInstruction traceInstruction = pop(); |
- LocalVariableElement traceVariable = elements[trace]; |
- localsHandler.updateLocal(traceVariable, traceInstruction); |
- } |
- visit(catchBlock); |
- } |
- |
- void visitElse() { |
- if (link.isEmpty) { |
- closeAndGotoExit(new HThrow(exception, isRethrow: true)); |
- } else { |
- ast.CatchBlock newBlock = link.head; |
- handleIf(node, |
- () { pushCondition(newBlock); }, |
- visitThen, visitElse); |
- } |
- } |
- |
- ast.CatchBlock firstBlock = link.head; |
- handleIf(node, () { pushCondition(firstBlock); }, visitThen, visitElse); |
- if (!isAborted()) endCatchBlock = close(new HGoto()); |
- |
- rethrowableException = oldRethrowableException; |
- tryInstruction.catchBlock = startCatchBlock; |
- catchGraph = new SubGraph(startCatchBlock, lastOpenedBlock); |
- } |
- |
- SubGraph finallyGraph = null; |
- if (node.finallyBlock != null) { |
- localsHandler = new LocalsHandler.from(savedLocals); |
- startFinallyBlock = graph.addNewBlock(); |
- open(startFinallyBlock); |
- visit(node.finallyBlock); |
- if (!isAborted()) endFinallyBlock = close(new HGoto()); |
- tryInstruction.finallyBlock = startFinallyBlock; |
- finallyGraph = new SubGraph(startFinallyBlock, lastOpenedBlock); |
- } |
- |
- HBasicBlock exitBlock = graph.addNewBlock(); |
- |
- addOptionalSuccessor(b1, b2) { if (b2 != null) b1.addSuccessor(b2); } |
- addExitTrySuccessor(successor) { |
- if (successor == null) return; |
- // Iterate over all blocks created inside this try/catch, and |
- // attach successor information to blocks that end with |
- // [HExitTry]. |
- for (int i = startTryBlock.id; i < successor.id; i++) { |
- HBasicBlock block = graph.blocks[i]; |
- var last = block.last; |
- if (last is HExitTry) { |
- block.addSuccessor(successor); |
- } |
- } |
- } |
- |
- // Setup all successors. The entry block that contains the [HTry] |
- // has 1) the body, 2) the catch, 3) the finally, and 4) the exit |
- // blocks as successors. |
- enterBlock.addSuccessor(startTryBlock); |
- addOptionalSuccessor(enterBlock, startCatchBlock); |
- addOptionalSuccessor(enterBlock, startFinallyBlock); |
- enterBlock.addSuccessor(exitBlock); |
- |
- // The body has either the catch or the finally block as successor. |
- if (endTryBlock != null) { |
- assert(startCatchBlock != null || startFinallyBlock != null); |
- endTryBlock.addSuccessor( |
- startCatchBlock != null ? startCatchBlock : startFinallyBlock); |
- endTryBlock.addSuccessor(exitBlock); |
- } |
- |
- // The catch block has either the finally or the exit block as |
- // successor. |
- if (endCatchBlock != null) { |
- endCatchBlock.addSuccessor( |
- startFinallyBlock != null ? startFinallyBlock : exitBlock); |
- } |
- |
- // The finally block has the exit block as successor. |
- if (endFinallyBlock != null) { |
- endFinallyBlock.addSuccessor(exitBlock); |
- } |
- |
- // If a block inside try/catch aborts (eg with a return statement), |
- // we explicitely mark this block a predecessor of the catch |
- // block and the finally block. |
- addExitTrySuccessor(startCatchBlock); |
- addExitTrySuccessor(startFinallyBlock); |
- |
- // Use the locals handler not altered by the catch and finally |
- // blocks. |
- localsHandler = savedLocals; |
- open(exitBlock); |
- enterBlock.setBlockFlow( |
- new HTryBlockInformation( |
- wrapStatementGraph(bodyGraph), |
- exception, |
- wrapStatementGraph(catchGraph), |
- wrapStatementGraph(finallyGraph)), |
- exitBlock); |
- inTryStatement = oldInTryStatement; |
- } |
- |
- visitCatchBlock(ast.CatchBlock node) { |
- visit(node.block); |
- } |
- |
- visitTypedef(ast.Typedef node) { |
- compiler.unimplemented(node, 'SsaFromAstMixin.visitTypedef.'); |
- } |
- |
- visitTypeVariable(ast.TypeVariable node) { |
- compiler.internalError(node, 'SsaFromAstMixin.visitTypeVariable.'); |
- } |
- |
- /** |
- * This method is invoked before inlining the body of [function] into this |
- * [SsaBuilder]. |
- */ |
- void enterInlinedMethod(FunctionElement function, |
- ast.Node _, |
- List<HInstruction> compiledArguments) { |
- TypesInferrer inferrer = compiler.typesTask.typesInferrer; |
- AstInliningState state = new AstInliningState( |
- function, returnLocal, returnType, elements, stack, localsHandler, |
- inTryStatement, |
- allInlinedFunctionsCalledOnce && inferrer.isCalledOnce(function)); |
- inliningStack.add(state); |
- |
- // Setting up the state of the (AST) builder is performed even when the |
- // inlined function is in IR, because the irInliner uses the [returnElement] |
- // of the AST builder. |
- setupStateForInlining(function, compiledArguments); |
- } |
- |
- void leaveInlinedMethod() { |
- HInstruction result = localsHandler.readLocal(returnLocal); |
- AstInliningState state = inliningStack.removeLast(); |
- restoreState(state); |
- stack.add(result); |
- } |
- |
- void doInline(FunctionElement function) { |
- visitInlinedFunction(function); |
- } |
- |
- void emitReturn(HInstruction value, ast.Node node) { |
- if (inliningStack.isEmpty) { |
- closeAndGotoExit(attachPosition(new HReturn(value), node)); |
- } else { |
- localsHandler.updateLocal(returnLocal, value); |
- } |
- } |
-} |
- |
-/** |
- * Visitor that handles generation of string literals (LiteralString, |
- * StringInterpolation), and otherwise delegates to the given visitor for |
- * non-literal subexpressions. |
- */ |
-class StringBuilderVisitor extends ast.Visitor { |
- final SsaBuilder builder; |
- final ast.Node diagnosticNode; |
- |
- /** |
- * The string value generated so far. |
- */ |
- HInstruction result = null; |
- |
- StringBuilderVisitor(this.builder, this.diagnosticNode); |
- |
- Compiler get compiler => builder.compiler; |
- |
- void visit(ast.Node node) { |
- node.accept(this); |
- } |
- |
- visitNode(ast.Node node) { |
- builder.compiler.internalError(node, 'Unexpected node.'); |
- } |
- |
- void visitExpression(ast.Node node) { |
- node.accept(builder); |
- HInstruction expression = builder.pop(); |
- |
- // We want to use HStringify when: |
- // 1. The value is known to be a primitive type, because it might get |
- // constant-folded and codegen has some tricks with JavaScript |
- // conversions. |
- // 2. The value can be primitive, because the library stringifier has |
- // fast-path code for most primitives. |
- if (expression.canBePrimitive(compiler)) { |
- append(stringify(node, expression)); |
- return; |
- } |
- |
- // If the `toString` method is guaranteed to return a string we can call it |
- // directly. |
- Selector selector = |
- new TypedSelector(expression.instructionType, |
- new Selector.call('toString', null, 0), compiler.world); |
- TypeMask type = TypeMaskFactory.inferredTypeForSelector(selector, compiler); |
- if (type.containsOnlyString(compiler.world)) { |
- builder.pushInvokeDynamic(node, selector, <HInstruction>[expression]); |
- append(builder.pop()); |
- return; |
- } |
- |
- append(stringify(node, expression)); |
- } |
- |
- void visitStringInterpolation(ast.StringInterpolation node) { |
- node.visitChildren(this); |
- } |
- |
- void visitStringInterpolationPart(ast.StringInterpolationPart node) { |
- visit(node.expression); |
- visit(node.string); |
- } |
- |
- void visitStringJuxtaposition(ast.StringJuxtaposition node) { |
- node.visitChildren(this); |
- } |
- |
- void visitNodeList(ast.NodeList node) { |
- node.visitChildren(this); |
- } |
- |
- void append(HInstruction expression) { |
- result = (result == null) ? expression : concat(result, expression); |
- } |
- |
- HInstruction concat(HInstruction left, HInstruction right) { |
- HInstruction instruction = new HStringConcat( |
- left, right, diagnosticNode, builder.backend.stringType); |
- builder.add(instruction); |
- return instruction; |
- } |
- |
- HInstruction stringify(ast.Node node, HInstruction expression) { |
- HInstruction instruction = |
- new HStringify(expression, node, builder.backend.stringType); |
- builder.add(instruction); |
- return instruction; |
- } |
-} |
- |
-/** |
- * This class visits the method that is a candidate for inlining and |
- * finds whether it is too difficult to inline. |
- */ |
-// TODO(karlklose): refactor to make it possible to distinguish between |
-// implementation restrictions (for example, we *can't* inline multiple returns) |
-// and heuristics (we *shouldn't* inline large functions). |
-class InlineWeeder extends ast.Visitor { |
- // Invariant: *INSIDE_LOOP* > *OUTSIDE_LOOP* |
- static const INLINING_NODES_OUTSIDE_LOOP = 18; |
- static const INLINING_NODES_OUTSIDE_LOOP_ARG_FACTOR = 3; |
- static const INLINING_NODES_INSIDE_LOOP = 42; |
- static const INLINING_NODES_INSIDE_LOOP_ARG_FACTOR = 4; |
- |
- bool seenReturn = false; |
- bool tooDifficult = false; |
- int nodeCount = 0; |
- final int maxInliningNodes; |
- final bool useMaxInliningNodes; |
- final bool allowLoops; |
- |
- InlineWeeder(this.maxInliningNodes, |
- this.useMaxInliningNodes, |
- this.allowLoops); |
- |
- static bool canBeInlined(ast.FunctionExpression functionExpression, |
- int maxInliningNodes, |
- bool useMaxInliningNodes, |
- {bool allowLoops: false}) { |
- InlineWeeder weeder = |
- new InlineWeeder(maxInliningNodes, useMaxInliningNodes, allowLoops); |
- weeder.visit(functionExpression.initializers); |
- weeder.visit(functionExpression.body); |
- return !weeder.tooDifficult; |
- } |
- |
- bool registerNode() { |
- if (!useMaxInliningNodes) return true; |
- if (nodeCount++ > maxInliningNodes) { |
- tooDifficult = true; |
- return false; |
- } else { |
- return true; |
- } |
- } |
- |
- void visit(ast.Node node) { |
- if (node != null) node.accept(this); |
- } |
- |
- void visitNode(ast.Node node) { |
- if (!registerNode()) return; |
- if (seenReturn) { |
- tooDifficult = true; |
- } else { |
- node.visitChildren(this); |
- } |
- } |
- |
- void visitFunctionExpression(ast.Node node) { |
- if (!registerNode()) return; |
- tooDifficult = true; |
- } |
- |
- void visitFunctionDeclaration(ast.Node node) { |
- if (!registerNode()) return; |
- tooDifficult = true; |
- } |
- |
- void visitSend(ast.Send node) { |
- if (!registerNode()) return; |
- node.visitChildren(this); |
- } |
- |
- visitLoop(ast.Node node) { |
- // It's actually not difficult to inline a method with a loop, but |
- // our measurements show that it's currently better to not inline a |
- // method that contains a loop. |
- if (!allowLoops) tooDifficult = true; |
- } |
- |
- void visitRedirectingFactoryBody(ast.RedirectingFactoryBody node) { |
- if (!registerNode()) return; |
- tooDifficult = true; |
- } |
- |
- void visitRethrow(ast.Rethrow node) { |
- if (!registerNode()) return; |
- tooDifficult = true; |
- } |
- |
- void visitReturn(ast.Return node) { |
- if (!registerNode()) return; |
- if (seenReturn |
- || identical(node.beginToken.stringValue, 'native')) { |
- tooDifficult = true; |
- return; |
- } |
- node.visitChildren(this); |
- seenReturn = true; |
- } |
- |
- void visitTryStatement(ast.Node node) { |
- if (!registerNode()) return; |
- tooDifficult = true; |
- } |
- |
- void visitThrow(ast.Throw node) { |
- if (!registerNode()) return; |
- // For now, we don't want to handle throw after a return even if |
- // it is in an "if". |
- if (seenReturn) { |
- tooDifficult = true; |
- } else { |
- node.visitChildren(this); |
- } |
- } |
-} |
- |
-abstract class InliningState { |
- /** |
- * Invariant: [function] must be an implementation element. |
- */ |
- final FunctionElement function; |
- |
- InliningState(this.function) { |
- assert(function.isImplementation); |
- } |
-} |
- |
-class AstInliningState extends InliningState { |
- final Local oldReturnLocal; |
- final DartType oldReturnType; |
- final TreeElements oldElements; |
- final List<HInstruction> oldStack; |
- final LocalsHandler oldLocalsHandler; |
- final bool inTryStatement; |
- final bool allFunctionsCalledOnce; |
- |
- AstInliningState(FunctionElement function, |
- this.oldReturnLocal, |
- this.oldReturnType, |
- this.oldElements, |
- this.oldStack, |
- this.oldLocalsHandler, |
- this.inTryStatement, |
- this.allFunctionsCalledOnce) |
- : super(function); |
-} |
- |
-class SsaBranch { |
- final SsaBranchBuilder branchBuilder; |
- final HBasicBlock block; |
- LocalsHandler startLocals; |
- LocalsHandler exitLocals; |
- SubGraph graph; |
- |
- SsaBranch(this.branchBuilder) : block = new HBasicBlock(); |
-} |
- |
-class SsaBranchBuilder { |
- final SsaBuilder builder; |
- final ast.Node diagnosticNode; |
- |
- SsaBranchBuilder(this.builder, [this.diagnosticNode]); |
- |
- Compiler get compiler => builder.compiler; |
- |
- void checkNotAborted() { |
- if (builder.isAborted()) { |
- compiler.unimplemented(diagnosticNode, "aborted control flow"); |
- } |
- } |
- |
- void buildCondition(void visitCondition(), |
- SsaBranch conditionBranch, |
- SsaBranch thenBranch, |
- SsaBranch elseBranch) { |
- startBranch(conditionBranch); |
- visitCondition(); |
- checkNotAborted(); |
- assert(identical(builder.current, builder.lastOpenedBlock)); |
- HInstruction conditionValue = builder.popBoolified(); |
- HIf branch = new HIf(conditionValue); |
- HBasicBlock conditionExitBlock = builder.current; |
- builder.close(branch); |
- conditionBranch.exitLocals = builder.localsHandler; |
- conditionExitBlock.addSuccessor(thenBranch.block); |
- conditionExitBlock.addSuccessor(elseBranch.block); |
- bool conditionBranchLocalsCanBeReused = |
- mergeLocals(conditionBranch, thenBranch, mayReuseFromLocals: true); |
- mergeLocals(conditionBranch, elseBranch, |
- mayReuseFromLocals: conditionBranchLocalsCanBeReused); |
- |
- conditionBranch.graph = |
- new SubExpression(conditionBranch.block, conditionExitBlock); |
- } |
- |
- /** |
- * Returns true if the locals of the [fromBranch] may be reused. A [:true:] |
- * return value implies that [mayReuseFromLocals] was set to [:true:]. |
- */ |
- bool mergeLocals(SsaBranch fromBranch, SsaBranch toBranch, |
- {bool mayReuseFromLocals}) { |
- LocalsHandler fromLocals = fromBranch.exitLocals; |
- if (toBranch.startLocals == null) { |
- if (mayReuseFromLocals) { |
- toBranch.startLocals = fromLocals; |
- return false; |
- } else { |
- toBranch.startLocals = new LocalsHandler.from(fromLocals); |
- return true; |
- } |
- } else { |
- toBranch.startLocals.mergeWith(fromLocals, toBranch.block); |
- return true; |
- } |
- } |
- |
- void startBranch(SsaBranch branch) { |
- builder.graph.addBlock(branch.block); |
- builder.localsHandler = branch.startLocals; |
- builder.open(branch.block); |
- } |
- |
- HInstruction buildBranch(SsaBranch branch, |
- void visitBranch(), |
- SsaBranch joinBranch, |
- bool isExpression) { |
- startBranch(branch); |
- visitBranch(); |
- branch.graph = new SubGraph(branch.block, builder.lastOpenedBlock); |
- branch.exitLocals = builder.localsHandler; |
- if (!builder.isAborted()) { |
- builder.goto(builder.current, joinBranch.block); |
- mergeLocals(branch, joinBranch, mayReuseFromLocals: true); |
- } |
- if (isExpression) { |
- checkNotAborted(); |
- return builder.pop(); |
- } |
- return null; |
- } |
- |
- handleIf(void visitCondition(), void visitThen(), void visitElse()) { |
- if (visitElse == null) { |
- // Make sure to have an else part to avoid a critical edge. A |
- // critical edge is an edge that connects a block with multiple |
- // successors to a block with multiple predecessors. We avoid |
- // such edges because they prevent inserting copies during code |
- // generation of phi instructions. |
- visitElse = () {}; |
- } |
- |
- _handleDiamondBranch(visitCondition, visitThen, visitElse, false); |
- } |
- |
- handleConditional(void visitCondition(), void visitThen(), void visitElse()) { |
- assert(visitElse != null); |
- _handleDiamondBranch(visitCondition, visitThen, visitElse, true); |
- } |
- |
- void handleLogicalAndOr(void left(), void right(), {bool isAnd}) { |
- // x && y is transformed into: |
- // t0 = boolify(x); |
- // if (t0) { |
- // t1 = boolify(y); |
- // } |
- // result = phi(t1, false); |
- // |
- // x || y is transformed into: |
- // t0 = boolify(x); |
- // if (not(t0)) { |
- // t1 = boolify(y); |
- // } |
- // result = phi(t1, true); |
- HInstruction boolifiedLeft; |
- HInstruction boolifiedRight; |
- |
- void visitCondition() { |
- left(); |
- boolifiedLeft = builder.popBoolified(); |
- builder.stack.add(boolifiedLeft); |
- if (!isAnd) { |
- builder.push(new HNot(builder.pop(), builder.backend.boolType)); |
- } |
- } |
- |
- void visitThen() { |
- right(); |
- boolifiedRight = builder.popBoolified(); |
- } |
- |
- handleIf(visitCondition, visitThen, null); |
- HConstant notIsAnd = |
- builder.graph.addConstantBool(!isAnd, builder.compiler); |
- JavaScriptBackend backend = builder.backend; |
- HPhi result = new HPhi.manyInputs(null, |
- <HInstruction>[boolifiedRight, notIsAnd], |
- backend.dynamicType); |
- builder.current.addPhi(result); |
- builder.stack.add(result); |
- } |
- |
- void handleLogicalAndOrWithLeftNode(ast.Node left, |
- void visitRight(), |
- {bool isAnd}) { |
- // This method is similar to [handleLogicalAndOr] but optimizes the case |
- // where left is a logical "and" or logical "or". |
- // |
- // For example (x && y) && z is transformed into x && (y && z): |
- // t0 = boolify(x); |
- // if (t0) { |
- // t1 = boolify(y); |
- // if (t1) { |
- // t2 = boolify(z); |
- // } |
- // t3 = phi(t2, false); |
- // } |
- // result = phi(t3, false); |
- |
- ast.Send send = left.asSend(); |
- if (send != null && |
- (isAnd ? send.isLogicalAnd : send.isLogicalOr)) { |
- ast.Node newLeft = send.receiver; |
- Link<ast.Node> link = send.argumentsNode.nodes; |
- assert(link.tail.isEmpty); |
- ast.Node middle = link.head; |
- handleLogicalAndOrWithLeftNode( |
- newLeft, |
- () => handleLogicalAndOrWithLeftNode(middle, visitRight, |
- isAnd: isAnd), |
- isAnd: isAnd); |
- } else { |
- handleLogicalAndOr(() => builder.visit(left), visitRight, isAnd: isAnd); |
- } |
- } |
- |
- void _handleDiamondBranch(void visitCondition(), |
- void visitThen(), |
- void visitElse(), |
- bool isExpression) { |
- SsaBranch conditionBranch = new SsaBranch(this); |
- SsaBranch thenBranch = new SsaBranch(this); |
- SsaBranch elseBranch = new SsaBranch(this); |
- SsaBranch joinBranch = new SsaBranch(this); |
- |
- conditionBranch.startLocals = builder.localsHandler; |
- builder.goto(builder.current, conditionBranch.block); |
- |
- buildCondition(visitCondition, conditionBranch, thenBranch, elseBranch); |
- HInstruction thenValue = |
- buildBranch(thenBranch, visitThen, joinBranch, isExpression); |
- HInstruction elseValue = |
- buildBranch(elseBranch, visitElse, joinBranch, isExpression); |
- |
- if (isExpression) { |
- assert(thenValue != null && elseValue != null); |
- JavaScriptBackend backend = builder.backend; |
- HPhi phi = new HPhi.manyInputs( |
- null, <HInstruction>[thenValue, elseValue], backend.dynamicType); |
- joinBranch.block.addPhi(phi); |
- builder.stack.add(phi); |
- } |
- |
- HBasicBlock thenBlock = thenBranch.block; |
- HBasicBlock elseBlock = elseBranch.block; |
- HBasicBlock joinBlock; |
- // If at least one branch did not abort, open the joinBranch. |
- if (!joinBranch.block.predecessors.isEmpty) { |
- startBranch(joinBranch); |
- joinBlock = joinBranch.block; |
- } |
- |
- HIfBlockInformation info = |
- new HIfBlockInformation( |
- new HSubExpressionBlockInformation(conditionBranch.graph), |
- new HSubGraphBlockInformation(thenBranch.graph), |
- new HSubGraphBlockInformation(elseBranch.graph)); |
- |
- HBasicBlock conditionStartBlock = conditionBranch.block; |
- conditionStartBlock.setBlockFlow(info, joinBlock); |
- SubGraph conditionGraph = conditionBranch.graph; |
- HIf branch = conditionGraph.end.last; |
- assert(branch is HIf); |
- branch.blockInformation = conditionStartBlock.blockFlow; |
- } |
-} |
- |
-class TypeBuilder implements DartTypeVisitor<dynamic, SsaBuilder> { |
- final ClassWorld classWorld; |
- |
- TypeBuilder(this.classWorld); |
- |
- void visitType(DartType type, _) { |
- throw 'Internal error $type'; |
- } |
- |
- void visitVoidType(VoidType type, SsaBuilder builder) { |
- ClassElement cls = builder.backend.findHelper('VoidRuntimeType'); |
- builder.push(new HVoidType(type, new TypeMask.exact(cls, classWorld))); |
- } |
- |
- void visitTypeVariableType(TypeVariableType type, |
- SsaBuilder builder) { |
- ClassElement cls = builder.backend.findHelper('RuntimeType'); |
- TypeMask instructionType = new TypeMask.subclass(cls, classWorld); |
- if (!builder.sourceElement.enclosingElement.isClosure && |
- builder.sourceElement.isInstanceMember) { |
- HInstruction receiver = builder.localsHandler.readThis(); |
- builder.push(new HReadTypeVariable(type, receiver, instructionType)); |
- } else { |
- builder.push( |
- new HReadTypeVariable.noReceiver( |
- type, builder.addTypeVariableReference(type), instructionType)); |
- } |
- } |
- |
- void visitFunctionType(FunctionType type, SsaBuilder builder) { |
- type.returnType.accept(this, builder); |
- HInstruction returnType = builder.pop(); |
- List<HInstruction> inputs = <HInstruction>[returnType]; |
- |
- for (DartType parameter in type.parameterTypes) { |
- parameter.accept(this, builder); |
- inputs.add(builder.pop()); |
- } |
- |
- for (DartType parameter in type.optionalParameterTypes) { |
- parameter.accept(this, builder); |
- inputs.add(builder.pop()); |
- } |
- |
- List<DartType> namedParameterTypes = type.namedParameterTypes; |
- List<String> names = type.namedParameters; |
- for (int index = 0; index < names.length; index++) { |
- ast.DartString dartString = new ast.DartString.literal(names[index]); |
- inputs.add( |
- builder.graph.addConstantString(dartString, builder.compiler)); |
- namedParameterTypes[index].accept(this, builder); |
- inputs.add(builder.pop()); |
- } |
- |
- ClassElement cls = builder.backend.findHelper('RuntimeFunctionType'); |
- builder.push(new HFunctionType(inputs, type, |
- new TypeMask.exact(cls, classWorld))); |
- } |
- |
- void visitMalformedType(MalformedType type, SsaBuilder builder) { |
- visitDynamicType(const DynamicType(), builder); |
- } |
- |
- void visitStatementType(StatementType type, SsaBuilder builder) { |
- throw 'not implemented visitStatementType($type)'; |
- } |
- |
- void visitGenericType(GenericType type, SsaBuilder builder) { |
- throw 'not implemented visitGenericType($type)'; |
- } |
- |
- void visitInterfaceType(InterfaceType type, SsaBuilder builder) { |
- List<HInstruction> inputs = <HInstruction>[]; |
- for (DartType typeArgument in type.typeArguments) { |
- typeArgument.accept(this, builder); |
- inputs.add(builder.pop()); |
- } |
- ClassElement cls; |
- if (type.typeArguments.isEmpty) { |
- cls = builder.backend.findHelper('RuntimeTypePlain'); |
- } else { |
- cls = builder.backend.findHelper('RuntimeTypeGeneric'); |
- } |
- builder.push(new HInterfaceType(inputs, type, |
- new TypeMask.exact(cls, classWorld))); |
- } |
- |
- void visitTypedefType(TypedefType type, SsaBuilder builder) { |
- DartType unaliased = type.unalias(builder.compiler); |
- if (unaliased is TypedefType) throw 'unable to unalias $type'; |
- unaliased.accept(this, builder); |
- } |
- |
- void visitDynamicType(DynamicType type, SsaBuilder builder) { |
- JavaScriptBackend backend = builder.compiler.backend; |
- ClassElement cls = backend.findHelper('DynamicRuntimeType'); |
- builder.push(new HDynamicType(type, new TypeMask.exact(cls, classWorld))); |
- } |
-} |