Chromium Code Reviews| Index: pkg/compiler/lib/src/ssa/locals_handler.dart |
| diff --git a/pkg/compiler/lib/src/ssa/locals_handler.dart b/pkg/compiler/lib/src/ssa/locals_handler.dart |
| new file mode 100644 |
| index 0000000000000000000000000000000000000000..dda5e1827e7e256082643017910d1b34d9629f37 |
| --- /dev/null |
| +++ b/pkg/compiler/lib/src/ssa/locals_handler.dart |
| @@ -0,0 +1,687 @@ |
| +// Copyright (c) 2016, the Dart project authors. Please see the AUTHORS file |
| +// for details. All rights reserved. Use of this source code is governed by a |
| +// BSD-style license that can be found in the LICENSE file. |
| + |
| +import '../closure.dart'; |
| +import '../common.dart'; |
| +import '../compiler.dart' show Compiler; |
| +import '../dart_types.dart'; |
| +import '../elements/elements.dart'; |
| +import '../io/source_information.dart'; |
| +import '../js/js.dart' as js; |
| +import '../js_backend/js_backend.dart'; |
| +import '../native/native.dart' as native; |
| +import '../tree/tree.dart' as ast; |
| +import '../types/types.dart'; |
| +import '../world.dart' show ClassWorld; |
| +import 'builder.dart' show SyntheticLocal; |
| +import 'graph_builder.dart'; |
| +import 'nodes.dart'; |
| +import 'types.dart'; |
| + |
| +/** |
|
Siggi Cherem (dart-lang)
2016/08/25 19:12:53
feel free to convert to /// comment style if you s
Harry Terkelsen
2016/08/25 20:12:57
Done.
|
| + * 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>(); |
| + final GraphBuilder 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; |
| + |
| + /// The type of the current instance, if concrete. |
| + /// |
| + /// This allows for handling fixed type argument in case of inlining. For |
| + /// instance, checking `'foo'` against `String` instead of `T` in `main`: |
| + /// |
| + /// class Foo<T> { |
| + /// T field; |
| + /// Foo(this.field); |
| + /// } |
| + /// main() { |
| + /// new Foo<String>('foo'); |
| + /// } |
| + /// |
| + /// [instanceType] is not used if it contains type variables, since these |
| + /// might not be in scope or from the current instance. |
| + /// |
| + final InterfaceType instanceType; |
| + |
| + final Compiler compiler; |
| + |
| + final SourceInformationBuilder sourceInformationBuilder; |
| + |
| + LocalsHandler(this.builder, this.executableContext, |
| + InterfaceType instanceType, this.compiler, this.sourceInformationBuilder) |
| + : this.instanceType = |
| + instanceType == null || instanceType.containsTypeVariables |
| + ? null |
| + : instanceType; |
| + |
| + /// 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)); |
| + } |
| + } |
| + if (instanceType != null) { |
| + type = type.substByContext(instanceType); |
| + } |
| + return type; |
| + } |
| + |
| + get typesTask => 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, |
| + instanceType = other.instanceType, |
| + builder = other.builder, |
| + closureData = other.closureData, |
| + compiler = other.compiler, |
| + sourceInformationBuilder = other.sourceInformationBuilder; |
| + |
| + /** |
| + * 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 = compiler.backend; |
| + HInstruction box = new HForeignCode( |
| + js.js.parseForeignJS('{}'), backend.nonNullType, <HInstruction>[], |
| + nativeBehavior: native.NativeBehavior.PURE_ALLOCATION); |
| + 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) { |
|
Siggi Cherem (dart-lang)
2016/08/25 19:12:53
one idea (feel free to defer for later): we could
Harry Terkelsen
2016/08/25 20:12:57
Acknowledged.
|
| + // 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 = compiler.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.isRegularParameter && !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(AstElement element, ast.Node node) { |
| + assert(invariant(element, element.isImplementation)); |
| + closureData = compiler.closureToClassMapper |
| + .computeClosureToClassMapping(element.resolvedAst); |
| + |
| + 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, 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 && backend.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, getTypeOfThis()); |
| + builder.graph.explicitReceiverParameter = value; |
| + builder.graph.entry.addAfter(directLocals[closureData.thisLocal], value); |
| + if (builder.lastAddedParameter == null) { |
| + // If this is the first parameter inserted, make sure it stays first. |
| + builder.lastAddedParameter = value; |
| + } |
| + if (isInterceptorClass) { |
| + // Only use the extra parameter in intercepted classes. |
| + directLocals[closureData.thisLocal] = value; |
| + } |
| + } else if (isNativeUpgradeFactory) { |
| + SyntheticLocal parameter = |
| + new SyntheticLocal('receiver', executableContext); |
| + // Unlike `this`, receiver is nullable since direct calls to generative |
| + // constructor call the constructor with `null`. |
| + ClassWorld classWorld = compiler.world; |
| + HParameterValue value = |
| + new HParameterValue(parameter, new TypeMask.exact(cls, classWorld)); |
| + builder.graph.explicitReceiverParameter = value; |
| + builder.graph.entry.addAtEntry(value); |
| + } |
| + } |
| + |
| + /** |
| + * Returns true if the local can be accessed directly. Boxed variables or |
| + * captured variables that are stored in the closure-field return [:false:]. |
| + */ |
| + bool isAccessedDirectly(Local local) { |
| + assert(local != null); |
| + return !redirectionMapping.containsKey(local) && |
| + !closureData.variablesUsedInTryOrGenerator.contains(local); |
| + } |
| + |
| + bool isStoredInClosureField(Local local) { |
| + assert(local != null); |
| + if (isAccessedDirectly(local)) return false; |
| + CapturedVariable redirectTarget = redirectionMapping[local]; |
| + if (redirectTarget == null) return false; |
| + return redirectTarget is ClosureFieldElement; |
| + } |
| + |
| + bool isBoxed(Local local) { |
| + if (isAccessedDirectly(local)) return false; |
| + if (isStoredInClosureField(local)) return false; |
| + return redirectionMapping.containsKey(local); |
| + } |
| + |
| + bool isUsedInTryOrGenerator(Local local) { |
| + return closureData.variablesUsedInTryOrGenerator.contains(local); |
| + } |
| + |
| + /** |
| + * Returns an [HInstruction] for the given element. If the element is |
| + * boxed or stored in a closure then the method generates code to retrieve |
| + * the value. |
| + */ |
| + HInstruction readLocal(Local local, {SourceInformation sourceInformation}) { |
| + if (isAccessedDirectly(local)) { |
| + if (directLocals[local] == null) { |
| + if (local is TypeVariableElement) { |
| + compiler.reporter.internalError(compiler.currentElement, |
| + "Runtime type information not available for $local."); |
| + } else { |
| + compiler.reporter.internalError( |
| + local, "Cannot find value $local in ${directLocals.keys}."); |
| + } |
| + } |
| + HInstruction value = directLocals[local]; |
| + if (sourceInformation != null) { |
| + value = new HRef(value, sourceInformation); |
| + builder.add(value); |
| + } |
| + return value; |
| + } else if (isStoredInClosureField(local)) { |
| + ClosureFieldElement redirect = redirectionMapping[local]; |
| + HInstruction receiver = readLocal(closureData.closureElement); |
| + TypeMask type = local is BoxLocal |
| + ? (compiler.backend as JavaScriptBackend).nonNullType |
| + : getTypeOfCapturedVariable(redirect); |
| + HInstruction fieldGet = new HFieldGet(redirect, receiver, type); |
| + builder.add(fieldGet); |
| + return fieldGet..sourceInformation = sourceInformation; |
| + } 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, getTypeOfCapturedVariable(redirect)); |
| + builder.add(lookup); |
| + return lookup..sourceInformation = sourceInformation; |
| + } else { |
| + assert(isUsedInTryOrGenerator(local)); |
| + HLocalValue localValue = getLocal(local); |
| + HInstruction instruction = new HLocalGet( |
| + local, |
| + localValue, |
| + (compiler.backend as JavaScriptBackend).dynamicType, |
| + sourceInformation); |
| + builder.add(instruction); |
| + return instruction; |
| + } |
| + } |
| + |
| + HInstruction readThis() { |
| + HInstruction res = readLocal(closureData.thisLocal); |
| + if (res.instructionType == null) { |
| + res.instructionType = getTypeOfThis(); |
| + } |
| + return res; |
| + } |
| + |
| + HLocalValue getLocal(Local local, {SourceInformation sourceInformation}) { |
| + // 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) { |
| + assert(invariant(local, builder.parameters.containsKey(local), |
| + message: "No local value for parameter $local in " |
| + "${builder.parameters}.")); |
| + return builder.parameters[local]; |
| + } |
| + |
| + return activationVariables.putIfAbsent(local, () { |
| + JavaScriptBackend backend = compiler.backend; |
| + HLocalValue localValue = new HLocalValue(local, backend.nonNullType) |
| + ..sourceInformation = sourceInformation; |
| + 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, |
| + {SourceInformation sourceInformation}) { |
| + if (value is HRef) { |
| + HRef ref = value; |
| + value = ref.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) |
| + ..sourceInformation = sourceInformation); |
| + } else { |
| + assert(isUsedInTryOrGenerator(local)); |
| + HLocalValue localValue = getLocal(local); |
| + builder.add(new HLocalSet(local, localValue, value) |
| + ..sourceInformation = sourceInformation); |
| + } |
| + } |
| + |
| + /** |
| + * 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 = compiler.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 = compiler.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 = compiler.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; |
| + } |
| + |
| + TypeMask cachedTypeOfThis; |
| + |
| + TypeMask getTypeOfThis() { |
| + TypeMask result = cachedTypeOfThis; |
| + if (result == null) { |
| + ThisLocal local = 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); |
| + }); |
| + } |
| + |
| + /// 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>{}; |
| +} |