Chromium Code Reviews| Index: pkg/compiler/lib/src/closure.dart |
| diff --git a/pkg/compiler/lib/src/closure.dart b/pkg/compiler/lib/src/closure.dart |
| index 6320e1f7bbaea75450fccf13e2594ab2825f031e..28d49a6527e656b3032b8ec91aa8770e236234a3 100644 |
| --- a/pkg/compiler/lib/src/closure.dart |
| +++ b/pkg/compiler/lib/src/closure.dart |
| @@ -26,10 +26,15 @@ import 'util/util.dart'; |
| import 'world.dart' show ClosedWorldRefiner; |
| /// Where T is ir.Node or Node. |
| +// TODO(efortuna): Rename this class. |
| abstract class ClosureClassMaps<T> { |
| ClosureClassMap getMemberMap(MemberEntity member); |
|
Johnni Winther
2017/06/14 09:22:30
Do we still need these?
Emily Fortuna
2017/06/15 00:11:21
no, but I was going to delete them in a follow-up
|
| ClosureClassMap getLocalFunctionMap(Local localFunction); |
| + /// Look up information about the variables that have been mutated and are |
| + /// used inside the scope of [node]. |
| + ClosureRepresentationInfo getClosureRepresentationInfo(Entity member); |
|
Johnni Winther
2017/06/14 09:22:30
Add a TODO for me to split this into members and l
Emily Fortuna
2017/06/15 00:11:21
Done.
|
| + |
| /// Look up information about a loop, in case any variables it declares need |
| /// to be boxed/snapshotted. |
| LoopClosureRepresentationInfo getClosureRepresentationInfoForLoop(T loopNode); |
| @@ -76,8 +81,9 @@ class ClosureAnalysisInfo { |
| /// |
| /// var lst = []; |
| /// for (int i = 0; i < 5; i++) lst.add(()=>i); |
| +/// var result = list.map((f) => f()).toList(); |
| /// |
| -/// The result of `lst` will be [0, 1, 2, 3, 4], whereas were this JS code |
| +/// `result` will be [0, 1, 2, 3, 4], whereas were this JS code |
| /// the result would be [5, 5, 5, 5, 5]. Because of this difference we need to |
| /// create a closure for these sorts of loops to capture the variable's value at |
| /// each iteration, by boxing the iteration variable[s]. |
| @@ -93,6 +99,99 @@ class LoopClosureRepresentationInfo extends ClosureAnalysisInfo { |
| List<Local> get boxedVariables => const <Local>[]; |
| } |
| +/// Class that describes the actual mechanics of how the converted, rewritten |
| +/// closure is implemented. For example, for the following closure (named foo |
| +/// for convenience): |
| +/// |
| +/// var foo = (x) => y + x; |
| +/// |
| +/// We would produce the following class to control access to these variables in |
| +/// the following way (modulo naming of variables, assuming that y is modified |
| +/// elsewhere in its scope): |
| +/// |
| +/// class FooClosure { |
| +/// int y; |
| +/// FooClosure(this.y); |
| +/// call(x) => this.y + x; |
| +/// } |
| +/// |
| +/// and then to execute this closure, for example: |
| +/// |
| +/// var foo = new FooClosure(1); |
| +/// foo.call(2); |
| +/// |
| +/// if y is modified elsewhere within its scope, accesses to y anywhere in the |
| +/// code will be controlled via a box object. |
| +/// I expect this interface to get simpler in subsequent refactorings. |
|
Johnni Winther
2017/06/14 09:22:30
Maybe turn the last line into a TODO?
Emily Fortuna
2017/06/15 00:11:21
Done.
|
| +class ClosureRepresentationInfo { |
| + const ClosureRepresentationInfo(); |
| + |
| + /// The original local function before any translation. |
| + /// |
| + /// Will be null for methods. |
| + Local get closureEntity => null; |
| + |
| + /// Closures are rewritten in the form of classes that |
|
Johnni Winther
2017/06/14 09:22:30
Weird line break
Emily Fortuna
2017/06/15 00:11:21
Done.
|
| + /// have fields to control the redirection and editing of variables that are |
| + /// "captured" inside a scope (declared in an outer scope but used in an |
| + /// inside scope). So this returns the class entity that represents this |
| + /// particular rewritten closure. |
| + ClassEntity get closureClassEntity => null; |
| + |
| + /// The function that implements the [local] function as a `call` method on |
| + /// the closure class. |
| + FunctionEntity get callEntity => null; |
|
Johnni Winther
2017/06/14 09:22:30
Maybe rename to [callMethod]?
Emily Fortuna
2017/06/15 00:11:21
Done.
|
| + |
| + /// As shown in the example in the comments at the top of this class, we |
| + /// create fields in the closure class for each captured variable. This is an |
| + /// accessor to that set of fields. |
| + List<Local> get createdFieldEntities => const <Local>[]; |
| + |
| + /// Convenience reference pointer to the element representing `this`. |
| + /// It is only set for instance-members. |
| + Local get thisLocal => null; |
| + |
| + /// Convenience pointer to the field entity representation in the closure |
| + /// class of the element representing `this`. |
| + FieldEntity get thisFieldEntity => null; |
| + |
| + /// Returns true if this [variable] is used inside a `try` block or a `sync*` |
| + /// generator (this is important to know because boxing/redirection needs to |
| + /// happen for those local variables). |
| + /// |
| + /// Variables that are used in a try must be treated as boxed because the |
| + /// control flow can be non-linear. |
| + /// |
| + /// Also parameters to a `sync*` generator must be boxed, because of the way |
| + /// we rewrite sync* functions. See also comments in |
| + /// [ClosureClassMap.useLocal]. |
| + bool variableIsUsedInTryOrSync(Local variable) => false; |
| + |
| + /// Loop through every variable that has been captured in this closure. This |
| + /// consists of all the free variables (variables captured *just* in this |
| + /// closure) and all variables captured in nested scopes that we may be |
| + /// capturing as well. These nested scopes hold "boxes" to hold the executable |
| + /// context for that scope. |
| + void forEachCapturedVariable(f(Local from, FieldEntity to)) {} |
| + |
| + /// Loop through each variable that has been boxed in this closure class. Only |
| + /// captured variables that are mutated need to be "boxed" (which basically |
| + /// puts a thin layer between updates and reads to this variable to ensure |
| + /// that every place that accesses it gets the correct updated value). |
| + void forEachBoxedVariable(f(Local local, FieldEntity field)) {} |
| + |
| + void forEachFreeVariable(f(Local variable, FieldEntity field)) {} |
|
Johnni Winther
2017/06/14 09:22:30
Add docs
Emily Fortuna
2017/06/15 00:11:21
Done.
|
| + |
| + /// Return true if [variable] has been captured and mutated (other variables |
|
Johnni Winther
2017/06/14 09:22:30
(...) -> , if not [variable] doesn't need boxing.
|
| + /// not require boxing). |
| + bool isVariableBoxed(Local variable) => false; |
| + |
| + // TODO(efortuna): Remove this method. The old system was using |
| + // ClosureClassMaps for situations other than closure class maps, and that's |
| + // just confusing. |
| + bool get isClosure => false; |
| +} |
| + |
| class ClosureTask extends CompilerTask implements ClosureClassMaps<Node> { |
| Map<Node, ClosureScope> _closureInfoMap = <Node, ClosureScope>{}; |
| Map<Element, ClosureClassMap> _closureMappingCache = |
| @@ -111,6 +210,10 @@ class ClosureTask extends CompilerTask implements ClosureClassMaps<Node> { |
| return value == null ? const ClosureAnalysisInfo() : value; |
| } |
| + ClosureRepresentationInfo getClosureRepresentationInfo(Element member) { |
| + return getClosureToClassMapping(member); |
| + } |
| + |
| LoopClosureRepresentationInfo getClosureRepresentationInfoForLoop( |
| Node loopNode) { |
| var value = _closureInfoMap[loopNode]; |
| @@ -537,24 +640,24 @@ class ClosureScope |
| } |
| } |
| -class ClosureClassMap { |
| +class ClosureClassMap implements ClosureRepresentationInfo { |
| /// The local function element before any translation. |
| /// |
| /// Will be null for methods. |
| - final LocalFunctionElement closureElement; |
| + final LocalFunctionElement closureEntity; |
| - /// The synthesized closure class for [closureElement]. |
| + /// The synthesized closure class for [closureEntity]. |
| /// |
| - /// The closureClassElement will be null for methods that are not local |
| + /// The closureClassEntity will be null for methods that are not local |
| /// closures. |
| - final ClosureClassElement closureClassElement; |
| + final ClosureClassElement closureClassEntity; |
| - /// The synthesized `call` method of the [ closureClassElement]. |
| + /// The synthesized `call` method of the [closureClassEntity]. |
| /// |
| - /// The callElement will be null for methods that are not local closures. |
| - final MethodElement callElement; |
| + /// The callEntity will be null for methods that are not local closures. |
| + final MethodElement callEntity; |
| - /// The [thisElement] makes handling 'this' easier by treating it like any |
| + /// The [thisLocal] makes handling 'this' easier by treating it like any |
| /// other argument. It is only set for instance-members. |
| final ThisLocal thisLocal; |
| @@ -576,8 +679,17 @@ class ClosureClassMap { |
| // TODO(johnniwinther): Add variables to this only if the variable is mutated. |
| final Set<Local> variablesUsedInTryOrGenerator = new Set<Local>(); |
| - ClosureClassMap(this.closureElement, this.closureClassElement, |
| - this.callElement, this.thisLocal); |
| + ClosureClassMap(this.closureEntity, this.closureClassEntity, this.callEntity, |
| + this.thisLocal); |
| + |
| + List<Local> get createdFieldEntities { |
| + List<Local> fields = <Local>[]; |
| + if (closureClassEntity == null) return const <Local>[]; |
| + closureClassEntity.closureFields.forEach((field) { |
| + fields.add(field.local); |
| + }); |
| + return fields; |
| + } |
| void addFreeVariable(Local element) { |
| assert(freeVariableMap[element] == null); |
| @@ -594,14 +706,16 @@ class ClosureClassMap { |
| freeVariableMap.forEach(f); |
| } |
| - bool isVariableUsedInTryOrSync(Local variable) => |
| + FieldEntity get thisFieldEntity => freeVariableMap[thisLocal]; |
| + |
| + bool variableIsUsedInTryOrSync(Local variable) => |
| variablesUsedInTryOrGenerator.contains(variable); |
| Local getLocalVariableForClosureField(ClosureFieldElement field) { |
| return field.local; |
| } |
| - bool get isClosure => closureElement != null; |
| + bool get isClosure => closureEntity != null; |
| bool capturingScopesBox(Local variable) { |
| return capturingScopes.values.any((scope) { |
| @@ -778,7 +892,7 @@ class ClosureTranslator extends Visitor { |
| boxes.add(boxFieldElement.box); |
| } |
| }); |
| - ClosureClassElement closureClass = data.closureClassElement; |
| + ClosureClassElement closureClass = data.closureClassEntity; |
| assert(closureClass != null || (fieldCaptures.isEmpty && boxes.isEmpty)); |
| void addClosureField(Local local, String name) { |
| @@ -834,7 +948,7 @@ class ClosureTranslator extends Visitor { |
| // things in the builder. |
| // Note that nested (named) functions are immutable. |
| if (variable != closureData.thisLocal && |
| - variable != closureData.closureElement && |
| + variable != closureData.closureEntity && |
| variable is! TypeVariableLocal) { |
| closureData.variablesUsedInTryOrGenerator.add(variable); |
| } |
| @@ -1221,8 +1335,8 @@ class ClosureTranslator extends Visitor { |
| } |
| closureMappingCache[element] = closureData; |
| closureMappingCache[element.declaration] = closureData; |
| - if (closureData.callElement != null) { |
| - closureMappingCache[closureData.callElement] = closureData; |
| + if (closureData.callEntity != null) { |
| + closureMappingCache[closureData.callEntity] = closureData; |
| } |
| inNewScope(node, () { |