OLD | NEW |
1 // Copyright (c) 2017, the Dart project authors. Please see the AUTHORS file | 1 // Copyright (c) 2016, the Dart project authors. Please see the AUTHORS file |
2 // for details. All rights reserved. Use of this source code is governed by a | 2 // for details. All rights reserved. Use of this source code is governed by a |
3 // BSD-style license that can be found in the LICENSE file. | 3 // BSD-style license that can be found in the LICENSE file. |
4 | 4 |
5 import 'package:kernel/ast.dart' as ir; | 5 import 'package:kernel/ast.dart' as ir; |
6 | 6 |
7 import '../closure.dart'; | 7 import '../closure.dart'; |
8 import '../common/tasks.dart'; | 8 import '../common/tasks.dart'; |
9 import '../elements/entities.dart'; | 9 import '../elements/entities.dart'; |
10 import '../kernel/element_map.dart'; | 10 import '../kernel/element_map.dart'; |
11 import '../world.dart'; | 11 import '../world.dart'; |
12 import 'elements.dart'; | |
13 import 'closure_visitors.dart'; | |
14 import 'locals.dart'; | 12 import 'locals.dart'; |
15 | 13 |
| 14 class KernelClosureDataBuilder extends ir.Visitor { |
| 15 final KernelToLocalsMap _localsMap; |
| 16 final KernelClosureRepresentationInfo info; |
| 17 |
| 18 bool _inTry = false; |
| 19 |
| 20 KernelClosureDataBuilder(this._localsMap, ThisLocal thisLocal) |
| 21 : info = new KernelClosureRepresentationInfo(thisLocal); |
| 22 |
| 23 @override |
| 24 defaultNode(ir.Node node) { |
| 25 node.visitChildren(this); |
| 26 } |
| 27 |
| 28 @override |
| 29 visitTryCatch(ir.TryCatch node) { |
| 30 bool oldInTry = _inTry; |
| 31 _inTry = true; |
| 32 node.visitChildren(this); |
| 33 _inTry = oldInTry; |
| 34 } |
| 35 |
| 36 @override |
| 37 visitTryFinally(ir.TryFinally node) { |
| 38 bool oldInTry = _inTry; |
| 39 _inTry = true; |
| 40 node.visitChildren(this); |
| 41 _inTry = oldInTry; |
| 42 } |
| 43 |
| 44 @override |
| 45 visitVariableGet(ir.VariableGet node) { |
| 46 if (_inTry) { |
| 47 info.registerUsedInTryOrSync(_localsMap.getLocal(node.variable)); |
| 48 } |
| 49 } |
| 50 |
| 51 @override |
| 52 visitVariableSet(ir.VariableSet node) { |
| 53 if (_inTry) { |
| 54 info.registerUsedInTryOrSync(_localsMap.getLocal(node.variable)); |
| 55 } |
| 56 node.visitChildren(this); |
| 57 } |
| 58 } |
| 59 |
16 /// Closure conversion code using our new Entity model. Closure conversion is | 60 /// Closure conversion code using our new Entity model. Closure conversion is |
17 /// necessary because the semantics of closures are slightly different in Dart | 61 /// necessary because the semantics of closures are slightly different in Dart |
18 /// than JavaScript. Closure conversion is separated out into two phases: | 62 /// than JavaScript. Closure conversion is separated out into two phases: |
19 /// generation of a new (temporary) representation to store where variables need | 63 /// generation of a new (temporary) representation to store where variables need |
20 /// to be hoisted/captured up at another level to re-write the closure, and then | 64 /// to be hoisted/captured up at another level to re-write the closure, and then |
21 /// the code generation phase where we generate elements and/or instructions to | 65 /// the code generation phase where we generate elements and/or instructions to |
22 /// represent this new code path. | 66 /// represent this new code path. |
23 /// | 67 /// |
24 /// For a general explanation of how closure conversion works at a high level, | 68 /// For a general explanation of how closure conversion works at a high level, |
25 /// check out: | 69 /// check out: |
26 /// http://siek.blogspot.com/2012/07/essence-of-closure-conversion.html or | 70 /// http://siek.blogspot.com/2012/07/essence-of-closure-conversion.html or |
27 /// http://matt.might.net/articles/closure-conversion/. | 71 /// http://matt.might.net/articles/closure-conversion/. |
28 // TODO(efortuna): Change inheritance hierarchy so that the | |
29 // ClosureConversionTask doesn't inherit from ClosureTask because it's just a | |
30 // glorified timer. | |
31 class KernelClosureConversionTask extends ClosureConversionTask<ir.Node> { | 72 class KernelClosureConversionTask extends ClosureConversionTask<ir.Node> { |
32 final KernelToElementMapForBuilding _elementMap; | 73 final KernelToElementMapForBuilding _elementMap; |
33 final GlobalLocalsMap _globalLocalsMap; | 74 final GlobalLocalsMap _globalLocalsMap; |
34 Map<ir.Node, ClosureScope> _closureScopeMap = <ir.Node, ClosureScope>{}; | 75 Map<Entity, ClosureRepresentationInfo> _infoMap = |
35 | |
36 Map<Entity, ClosureRepresentationInfo> _closureRepresentationMap = | |
37 <Entity, ClosureRepresentationInfo>{}; | 76 <Entity, ClosureRepresentationInfo>{}; |
38 | 77 |
39 /// Should only be used at the very beginning to ensure we are looking at the | 78 KernelClosureConversionTask( |
40 /// right kind of elements. | 79 Measurer measurer, this._elementMap, this._globalLocalsMap) |
41 // TODO(efortuna): Remove this map once we have one kernel backend strategy. | |
42 final JsToFrontendMap _kToJElementMap; | |
43 | |
44 KernelClosureConversionTask(Measurer measurer, this._elementMap, | |
45 this._kToJElementMap, this._globalLocalsMap) | |
46 : super(measurer); | 80 : super(measurer); |
47 | 81 |
48 /// The combined steps of generating our intermediate representation of | 82 /// The combined steps of generating our intermediate representation of |
49 /// closures that need to be rewritten and generating the element model. | 83 /// closures that need to be rewritten and generating the element model. |
50 /// Ultimately these two steps will be split apart with the second step | 84 /// Ultimately these two steps will be split apart with the second step |
51 /// happening later in compilation just before codegen. These steps are | 85 /// happening later in compilation just before codegen. These steps are |
52 /// combined here currently to provide a consistent interface to the rest of | 86 /// combined here currently to provide a consistent interface to the rest of |
53 /// the compiler until we are ready to separate these phases. | 87 /// the compiler until we are ready to separate these phases. |
54 @override | 88 @override |
55 void convertClosures(Iterable<MemberEntity> processedEntities, | 89 void convertClosures(Iterable<MemberEntity> processedEntities, |
56 ClosedWorldRefiner closedWorldRefiner) { | 90 ClosedWorldRefiner closedWorldRefiner) { |
57 var closuresToGenerate = <ir.Node, ScopeInfo>{}; | 91 // TODO(efortuna): implement. |
58 processedEntities.forEach((MemberEntity kEntity) { | |
59 MemberEntity entity = kEntity; | |
60 if (_kToJElementMap != null) { | |
61 entity = _kToJElementMap.toBackendMember(kEntity); | |
62 } | |
63 if (entity.isAbstract) return; | |
64 if (entity.isField && !entity.isInstanceMember) { | |
65 ir.Field field = _elementMap.getMemberNode(entity); | |
66 // Skip top-level/static fields without an initializer. | |
67 if (field.initializer == null) return; | |
68 } | |
69 _buildClosureModel(entity, closuresToGenerate, closedWorldRefiner); | |
70 }); | |
71 | |
72 for (ir.Node node in closuresToGenerate.keys) { | |
73 _produceSyntheticElements( | |
74 node, closuresToGenerate[node], closedWorldRefiner); | |
75 } | |
76 } | 92 } |
77 | 93 |
78 /// Inspect members and mark if those members capture any state that needs to | 94 /// TODO(johnniwinther,efortuna): Implement this. |
79 /// be marked as free variables. | 95 @override |
80 void _buildClosureModel( | 96 ClosureScope getClosureScope(ir.Node node) { |
81 MemberEntity entity, | 97 return const ClosureScope(); |
82 Map<ir.Node, ScopeInfo> closuresToGenerate, | |
83 ClosedWorldRefiner closedWorldRefiner) { | |
84 ir.Node node = _elementMap.getMemberNode(entity); | |
85 if (_closureScopeMap.keys.contains(node)) return; | |
86 ClosureScopeBuilder translator = new ClosureScopeBuilder(_closureScopeMap, | |
87 closuresToGenerate, _globalLocalsMap.getLocalsMap(entity), _elementMap); | |
88 if (entity.isField) { | |
89 if (node is ir.Field && node.initializer != null) { | |
90 translator.translateLazyInitializer(node); | |
91 } | |
92 } else { | |
93 assert(node is ir.Procedure || node is ir.Constructor); | |
94 translator.translateConstructorOrProcedure(node); | |
95 } | |
96 } | |
97 | |
98 /// Given what variables are captured at each point, construct closure classes | |
99 /// with fields containing the captured variables to replicate the Dart | |
100 /// closure semantics in JS. | |
101 void _produceSyntheticElements( | |
102 ir.Node node, ScopeInfo info, ClosedWorldRefiner closedWorldRefiner) { | |
103 Entity entity; | |
104 KernelClosureClass closureClass = | |
105 new KernelClosureClass.fromScopeInfo(info); | |
106 if (node is ir.FunctionNode) { | |
107 // We want the original declaration where that function is used to point | |
108 // to the correct closure class. | |
109 // TODO(efortuna): entity equivalent of element.declaration? | |
110 node = (node as ir.FunctionNode).parent; | |
111 _closureRepresentationMap[closureClass.callMethod] = closureClass; | |
112 } | |
113 | |
114 if (node is ir.Member) { | |
115 entity = _elementMap.getMember(node); | |
116 } else { | |
117 entity = _elementMap.getLocalFunction(node); | |
118 } | |
119 assert(entity != null); | |
120 | |
121 _closureRepresentationMap[entity] = closureClass; | |
122 } | 98 } |
123 | 99 |
124 @override | 100 @override |
125 ScopeInfo getScopeInfo(Entity entity) { | 101 ScopeInfo getScopeInfo(Entity entity) { |
| 102 // TODO(efortuna): Specialize this function from the one below. |
126 return getClosureRepresentationInfo(entity); | 103 return getClosureRepresentationInfo(entity); |
127 } | 104 } |
128 | 105 |
| 106 /// TODO(johnniwinther,efortuna): Implement this. |
129 @override | 107 @override |
130 // TODO(efortuna): Eventually closureScopeMap[node] should always be non-null, | 108 LoopClosureScope getLoopClosureScope(ir.Node loopNode) { |
131 // and we should just test that with an assert. | 109 return const LoopClosureScope(); |
132 ClosureScope getClosureScope(ir.Node node) => | 110 } |
133 _closureScopeMap[node] ?? const ClosureScope(); | |
134 | 111 |
135 @override | 112 @override |
136 // TODO(efortuna): Eventually closureScopeMap[node] should always be non-null, | 113 ClosureRepresentationInfo getClosureRepresentationInfo(Entity entity) { |
137 // and we should just test that with an assert. | 114 return _infoMap.putIfAbsent(entity, () { |
138 LoopClosureScope getLoopClosureScope(ir.Node loopNode) => | 115 if (entity is MemberEntity) { |
139 _closureScopeMap[node] ?? const LoopClosureScope(); | 116 ir.Member node = _elementMap.getMemberNode(entity); |
| 117 ThisLocal thisLocal; |
| 118 if (entity.isInstanceMember) { |
| 119 thisLocal = new ThisLocal(entity); |
| 120 } |
| 121 KernelClosureDataBuilder builder = new KernelClosureDataBuilder( |
| 122 _globalLocalsMap.getLocalsMap(entity), thisLocal); |
| 123 node.accept(builder); |
| 124 return builder.info; |
| 125 } |
140 | 126 |
141 @override | 127 /// TODO(johnniwinther,efortuna): Implement this. |
142 // TODO(efortuna): Eventually closureRepresentationMap[node] should always be | 128 return const ClosureRepresentationInfo(); |
143 // non-null, and we should just test that with an assert. | 129 }); |
144 ClosureRepresentationInfo getClosureRepresentationInfo(Entity entity) => | 130 } |
145 _closureRepresentationMap[entity] ?? const ClosureRepresentationInfo(); | |
146 } | 131 } |
147 | 132 |
148 class KernelScopeInfo extends ScopeInfo { | 133 // TODO(johnniwinther): Add unittest for the computed |
149 final Set<Local> localsUsedInTryOrSync; | 134 // [ClosureRepresentationInfo]. |
150 final Local thisLocal; | 135 class KernelClosureRepresentationInfo extends ClosureRepresentationInfo { |
151 final Set<Local> boxedVariables = new Set<Local>(); | 136 final ThisLocal thisLocal; |
| 137 final Set<Local> _localsUsedInTryOrSync = new Set<Local>(); |
152 | 138 |
153 /// The set of variables that were defined in another scope, but are used in | 139 KernelClosureRepresentationInfo(this.thisLocal); |
154 /// this scope. | |
155 Set<ir.VariableDeclaration> freeVariables = new Set<ir.VariableDeclaration>(); | |
156 | 140 |
157 KernelScopeInfo(this.thisLocal) : localsUsedInTryOrSync = new Set<Local>(); | 141 void registerUsedInTryOrSync(Local local) { |
158 | 142 _localsUsedInTryOrSync.add(local); |
159 KernelScopeInfo.from(this.thisLocal, KernelScopeInfo info) | |
160 : localsUsedInTryOrSync = info.variablesUsedInTryOrSync, | |
161 boxedVariables = info.boxedVariables; | |
162 | |
163 KernelScopeInfo.withBoxedVariables(this.boxedVariables, this.thisLocal); | |
164 | |
165 void forEachBoxedVariable(f(Local local, FieldEntity field)) { | |
166 boxedVariables.forEach((Local l) { | |
167 // TODO(efortuna): add FieldEntities as created. | |
168 f(l, null); | |
169 }); | |
170 } | 143 } |
171 | 144 |
172 bool localIsUsedInTryOrSync(Local variable) => | 145 bool variableIsUsedInTryOrSync(Local variable) => |
173 localsUsedInTryOrSync.contains(variable); | 146 _localsUsedInTryOrSync.contains(variable); |
174 | 147 |
175 String toString() { | 148 String toString() { |
176 StringBuffer sb = new StringBuffer(); | 149 StringBuffer sb = new StringBuffer(); |
177 sb.write('this=$thisLocal,'); | 150 sb.write('this=$thisLocal,'); |
178 sb.write('localsUsedInTryOrSync={${localsUsedInTryOrSync.join(', ')}}'); | 151 sb.write('localsUsedInTryOrSync={${_localsUsedInTryOrSync.join(', ')}}'); |
179 return sb.toString(); | 152 return sb.toString(); |
180 } | 153 } |
181 | |
182 bool isBoxed(Local variable) => boxedVariables.contains(variable); | |
183 } | 154 } |
184 | |
185 class KernelClosureScope extends KernelScopeInfo implements ClosureScope { | |
186 final Local context; | |
187 | |
188 KernelClosureScope(Set<Local> boxedVariables, this.context, Local thisLocal) | |
189 : super.withBoxedVariables(boxedVariables, thisLocal); | |
190 | |
191 bool get requiresContextBox => boxedVariables.isNotEmpty; | |
192 } | |
193 | |
194 class KernelLoopClosureScope extends KernelClosureScope | |
195 implements LoopClosureScope { | |
196 final List<Local> boxedLoopVariables; | |
197 | |
198 KernelLoopClosureScope(Set<Local> boxedVariables, this.boxedLoopVariables, | |
199 Local context, Local thisLocal) | |
200 : super(boxedVariables, context, thisLocal); | |
201 | |
202 bool get hasBoxedLoopVariables => boxedLoopVariables.isNotEmpty; | |
203 } | |
204 | |
205 // TODO(johnniwinther): Add unittest for the computed [ClosureClass]. | |
206 class KernelClosureClass extends KernelScopeInfo | |
207 implements ClosureRepresentationInfo { | |
208 KernelClosureClass.fromScopeInfo(ScopeInfo info) | |
209 : super.from(info.thisLocal, info); | |
210 | |
211 // TODO(efortuna): Implement. | |
212 Local get closureEntity => null; | |
213 | |
214 // TODO(efortuna): Implement. | |
215 ClassEntity get closureClassEntity => null; | |
216 | |
217 // TODO(efortuna): Implement. | |
218 FunctionEntity get callMethod => null; | |
219 | |
220 // TODO(efortuna): Implement. | |
221 List<Local> get createdFieldEntities => const <Local>[]; | |
222 | |
223 // TODO(efortuna): Implement. | |
224 FieldEntity get thisFieldEntity => null; | |
225 | |
226 // TODO(efortuna): Implement. | |
227 void forEachCapturedVariable(f(Local from, FieldEntity to)) {} | |
228 | |
229 // TODO(efortuna): Implement. | |
230 @override | |
231 void forEachBoxedVariable(f(Local local, FieldEntity field)) {} | |
232 | |
233 // TODO(efortuna): Implement. | |
234 void forEachFreeVariable(f(Local variable, FieldEntity field)) {} | |
235 | |
236 // TODO(efortuna): Implement. | |
237 bool isVariableBoxed(Local variable) => false; | |
238 | |
239 // TODO(efortuna): Implement. | |
240 // Why is this closure not actually a closure? Well, to properly call | |
241 // ourselves a closure, we need to register the new closure class with the | |
242 // ClosedWorldRefiner, which currently only takes elements. The change to | |
243 // that (and the subsequent adjustment here) will follow soon. | |
244 bool get isClosure => false; | |
245 } | |
OLD | NEW |