OLD | NEW |
| (Empty) |
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 | |
3 // BSD-style license that can be found in the LICENSE file. | |
4 | |
5 library kernel.transformations.closure_conversion; | |
6 | |
7 import '../ast.dart'; | |
8 import '../core_types.dart'; | |
9 import '../visitor.dart'; | |
10 | |
11 Program transformProgram(Program program) { | |
12 var captured = new CapturedVariables(); | |
13 captured.visitProgram(program); | |
14 | |
15 var convert = | |
16 new ClosureConverter(new CoreTypes(program), captured.variables); | |
17 return convert.visitProgram(program); | |
18 } | |
19 | |
20 class CapturedVariables extends RecursiveVisitor { | |
21 FunctionNode _currentFunction; | |
22 final Map<VariableDeclaration, FunctionNode> _function = | |
23 <VariableDeclaration, FunctionNode>{}; | |
24 | |
25 final Set<VariableDeclaration> variables = new Set<VariableDeclaration>(); | |
26 | |
27 visitFunctionNode(FunctionNode node) { | |
28 var saved = _currentFunction; | |
29 _currentFunction = node; | |
30 node.visitChildren(this); | |
31 _currentFunction = saved; | |
32 } | |
33 | |
34 visitVariableDeclaration(VariableDeclaration node) { | |
35 _function[node] = _currentFunction; | |
36 node.visitChildren(this); | |
37 } | |
38 | |
39 visitVariableGet(VariableGet node) { | |
40 if (_function[node.variable] != _currentFunction) { | |
41 variables.add(node.variable); | |
42 } | |
43 node.visitChildren(this); | |
44 } | |
45 | |
46 visitVariableSet(VariableSet node) { | |
47 if (_function[node.variable] != _currentFunction) { | |
48 variables.add(node.variable); | |
49 } | |
50 node.visitChildren(this); | |
51 } | |
52 } | |
53 | |
54 abstract class Context { | |
55 Expression get expression; | |
56 | |
57 void extend(VariableDeclaration variable, Expression value); | |
58 | |
59 Expression lookup(VariableDeclaration variable); | |
60 Expression assign(VariableDeclaration variable, Expression value); | |
61 | |
62 Context toClosureContext(VariableDeclaration parameter); | |
63 } | |
64 | |
65 class NoContext extends Context { | |
66 final ClosureConverter converter; | |
67 | |
68 NoContext(this.converter); | |
69 | |
70 Expression get expression => new NullLiteral(); | |
71 | |
72 void extend(VariableDeclaration variable, Expression value) { | |
73 converter.context = | |
74 new LocalContext(converter, this)..extend(variable, value); | |
75 } | |
76 | |
77 | |
78 Expression lookup(VariableDeclaration variable) { | |
79 throw 'Unbound NoContext.lookup($variable)'; | |
80 } | |
81 | |
82 Expression assign(VariableDeclaration variable, Expression value) { | |
83 throw 'Unbound NoContext.assign($variable, ...)'; | |
84 } | |
85 | |
86 Context toClosureContext(VariableDeclaration parameter) { | |
87 return new ClosureContext(converter, parameter, | |
88 <List<VariableDeclaration>>[]); | |
89 } | |
90 } | |
91 | |
92 | |
93 class LocalContext extends Context { | |
94 final ClosureConverter converter; | |
95 final Context parent; | |
96 final VariableDeclaration self; | |
97 final IntLiteral size; | |
98 final List<VariableDeclaration> variables = <VariableDeclaration>[]; | |
99 | |
100 LocalContext._internal(this.converter, this.parent, this.self, this.size); | |
101 | |
102 factory LocalContext(ClosureConverter converter, Context parent) { | |
103 Class contextClass = converter.internalContextClass; | |
104 assert(contextClass.constructors.length == 1); | |
105 IntLiteral zero = new IntLiteral(0); | |
106 VariableDeclaration declaration = | |
107 new VariableDeclaration.forValue( | |
108 new ConstructorInvocation(contextClass.constructors.first, | |
109 new Arguments(<Expression>[zero])), | |
110 type: new InterfaceType(contextClass)); | |
111 converter.insert(declaration); | |
112 converter.insert(new ExpressionStatement( | |
113 new PropertySet(new VariableGet(declaration), | |
114 new Name('parent'), | |
115 parent.expression))); | |
116 | |
117 return new LocalContext._internal(converter, parent, declaration, zero); | |
118 } | |
119 | |
120 Expression get expression => new VariableGet(self); | |
121 | |
122 void extend(VariableDeclaration variable, Expression value) { | |
123 converter.insert( | |
124 new ExpressionStatement( | |
125 new MethodInvocation( | |
126 expression, | |
127 new Name('[]='), | |
128 new Arguments( | |
129 <Expression>[new IntLiteral(variables.length), value])))); | |
130 ++size.value; | |
131 variables.add(variable); | |
132 } | |
133 | |
134 Expression lookup(VariableDeclaration variable) { | |
135 var index = variables.indexOf(variable); | |
136 return index == -1 | |
137 ? parent.lookup(variable) | |
138 : new MethodInvocation( | |
139 expression, | |
140 new Name('[]'), | |
141 new Arguments(<Expression>[new IntLiteral(index)])); | |
142 } | |
143 | |
144 Expression assign(VariableDeclaration variable, Expression value) { | |
145 var index = variables.indexOf(variable); | |
146 return index == -1 | |
147 ? parent.assign(variable, value) | |
148 : new MethodInvocation( | |
149 expression, | |
150 new Name('[]='), | |
151 new Arguments(<Expression>[new IntLiteral(index), value])); | |
152 } | |
153 | |
154 Context toClosureContext(VariableDeclaration parameter) { | |
155 List<List<VariableDeclaration>> variabless = <List<VariableDeclaration>>[]; | |
156 var current = this; | |
157 while (current != null && current is! NoContext) { | |
158 if (current is LocalContext) { | |
159 variabless.add(current.variables); | |
160 current = current.parent; | |
161 } else if (current is ClosureContext) { | |
162 variabless.addAll(current.variabless); | |
163 current = null; | |
164 } else if (current is LoopContext) { | |
165 // TODO. | |
166 current = current.parent; | |
167 } | |
168 } | |
169 return new ClosureContext(converter, parameter, variabless); | |
170 } | |
171 } | |
172 | |
173 class LoopContext { | |
174 final ClosureConverter converter; | |
175 final Context parent; | |
176 | |
177 LoopContext(this.converter, this.parent); | |
178 | |
179 void extend(VariableDeclaration variable, Expression value) { | |
180 converter.context = | |
181 new LocalContext(converter, parent)..extend(variable, value); | |
182 } | |
183 } | |
184 | |
185 class ClosureContext extends Context { | |
186 final ClosureConverter converter; | |
187 final VariableDeclaration self; | |
188 final List<List<VariableDeclaration>> variabless; | |
189 | |
190 ClosureContext(this.converter, this.self, this.variabless); | |
191 | |
192 Expression get expression => new VariableGet(self); | |
193 | |
194 void extend(VariableDeclaration variable, Expression value) { | |
195 converter.context = | |
196 new LocalContext(converter, this)..extend(variable, value); | |
197 } | |
198 | |
199 Expression lookup(VariableDeclaration variable) { | |
200 var context = expression; | |
201 for (var variables in variabless) { | |
202 var index = variables.indexOf(variable); | |
203 if (index != -1) { | |
204 return new MethodInvocation( | |
205 context, | |
206 new Name('[]'), | |
207 new Arguments(<Expression>[new IntLiteral(index)])); | |
208 } | |
209 context = new PropertyGet(context, new Name('parent')); | |
210 } | |
211 throw 'Unbound ClosureContext.lookup($variable)'; | |
212 } | |
213 | |
214 Expression assign(VariableDeclaration variable, Expression value) { | |
215 var context = expression; | |
216 for (var variables in variabless) { | |
217 var index = variables.indexOf(variable); | |
218 if (index != -1) { | |
219 return new MethodInvocation( | |
220 context, | |
221 new Name('[]='), | |
222 new Arguments(<Expression>[new IntLiteral(index), value])); | |
223 } | |
224 context = new PropertyGet(context, new Name('parent')); | |
225 } | |
226 throw 'Unbound ClosureContext.lookup($variable)'; | |
227 } | |
228 | |
229 Context toClosureContext(VariableDeclaration parameter) { | |
230 return new ClosureContext(converter, parameter, variabless); | |
231 } | |
232 } | |
233 | |
234 class ClosureConverter extends Transformer { | |
235 final CoreTypes coreTypes; | |
236 Class internalContextClass; | |
237 final Set<VariableDeclaration> captured; | |
238 | |
239 Block _currentBlock; | |
240 int _insertionIndex = 0; | |
241 | |
242 Context context; | |
243 | |
244 ClosureConverter(this.coreTypes, this.captured) { | |
245 internalContextClass = coreTypes.getCoreClass('dart:_internal', 'Context'); | |
246 } | |
247 | |
248 void insert(Statement statement) { | |
249 _currentBlock.statements.insert(_insertionIndex++, statement); | |
250 statement.parent = _currentBlock; | |
251 } | |
252 | |
253 TreeNode visitConstructor(Constructor node) { | |
254 return node; | |
255 } | |
256 | |
257 TreeNode visitFunctionDeclaration(FunctionDeclaration node) { | |
258 if (captured.contains(node.variable)) { | |
259 context.extend(node.variable, | |
260 new FunctionExpression(node.function)); | |
261 } | |
262 | |
263 Block savedBlock = _currentBlock; | |
264 int savedIndex = _insertionIndex; | |
265 Context savedContext = context; | |
266 | |
267 Statement body = node.function.body; | |
268 assert(body != null); | |
269 | |
270 if (body is Block) { | |
271 _currentBlock = body; | |
272 } else { | |
273 _currentBlock = new Block(<Statement>[body]); | |
274 node.function.body = body.parent = _currentBlock; | |
275 } | |
276 _insertionIndex = 0; | |
277 | |
278 // TODO: This is really the closure, not the context. | |
279 VariableDeclaration parameter = | |
280 new VariableDeclaration(null, | |
281 type: internalContextClass.rawType, | |
282 isFinal: true); | |
283 node.function.positionalParameters.insert(0, parameter); | |
284 parameter.parent = node.function; | |
285 ++node.function.requiredParameterCount; | |
286 context = context.toClosureContext(parameter); | |
287 | |
288 // Don't visit the children, because that included a variable declaration. | |
289 node.function = node.function.accept(this); | |
290 | |
291 _currentBlock = savedBlock; | |
292 _insertionIndex = savedIndex; | |
293 context = savedContext; | |
294 | |
295 return captured.contains(node.variable) ? null : node; | |
296 } | |
297 | |
298 TreeNode visitFunctionExpression(FunctionExpression node) { | |
299 Block savedBlock = _currentBlock; | |
300 int savedIndex = _insertionIndex; | |
301 Context savedContext = context; | |
302 | |
303 Statement body = node.function.body; | |
304 assert(body != null); | |
305 | |
306 if (body is Block) { | |
307 _currentBlock = body; | |
308 } else { | |
309 _currentBlock = new Block(<Statement>[body]); | |
310 node.function.body = body.parent = _currentBlock; | |
311 } | |
312 _insertionIndex = 0; | |
313 | |
314 // TODO: This is really the closure, not the context. | |
315 VariableDeclaration parameter = | |
316 new VariableDeclaration(null, | |
317 type: internalContextClass.rawType, | |
318 isFinal: true); | |
319 node.function.positionalParameters.insert(0, parameter); | |
320 parameter.parent = node.function; | |
321 ++node.function.requiredParameterCount; | |
322 context = context.toClosureContext(parameter); | |
323 | |
324 node.transformChildren(this); | |
325 | |
326 _currentBlock = savedBlock; | |
327 _insertionIndex = savedIndex; | |
328 context = savedContext; | |
329 | |
330 return node; | |
331 } | |
332 | |
333 TreeNode visitProcedure(Procedure node) { | |
334 assert(_currentBlock == null); | |
335 assert(_insertionIndex == 0); | |
336 assert(context == null); | |
337 | |
338 Statement body = node.function.body; | |
339 if (body == null) return node; | |
340 | |
341 // Ensure that the body is a block which becomes the current block. | |
342 if (body is Block) { | |
343 _currentBlock = body; | |
344 } else { | |
345 _currentBlock = new Block(<Statement>[body]); | |
346 node.function.body = body.parent = _currentBlock; | |
347 } | |
348 _insertionIndex = 0; | |
349 | |
350 // Start with no context. This happens after setting up _currentBlock | |
351 // so statements can be emitted into _currentBlock if necessary. | |
352 context = new NoContext(this); | |
353 | |
354 node.transformChildren(this); | |
355 | |
356 _currentBlock = null; | |
357 _insertionIndex = 0; | |
358 context = null; | |
359 return node; | |
360 } | |
361 | |
362 TreeNode visitLocalInitializer(LocalInitializer node) { | |
363 assert(!captured.contains(node.variable)); | |
364 node.transformChildren(this); | |
365 return node; | |
366 } | |
367 | |
368 TreeNode visitFunctionNode(FunctionNode node) { | |
369 transformList(node.typeParameters, this, node); | |
370 | |
371 void extend(VariableDeclaration parameter) { | |
372 context.extend(parameter, new VariableGet(parameter)); | |
373 } | |
374 // TODO: Can parameters contain initializers (e.g., for optional ones) that | |
375 // need to be closure converted? | |
376 node.positionalParameters.where(captured.contains).forEach(extend); | |
377 node.namedParameters.where(captured.contains).forEach(extend); | |
378 | |
379 assert(node.body != null); | |
380 node.body = node.body.accept(this); | |
381 node.body.parent = node; | |
382 return node; | |
383 } | |
384 | |
385 TreeNode visitBlock(Block node) { | |
386 Block savedBlock; | |
387 int savedIndex; | |
388 if (_currentBlock != node) { | |
389 savedBlock = _currentBlock; | |
390 savedIndex = _insertionIndex; | |
391 _currentBlock = node; | |
392 _insertionIndex = 0; | |
393 } | |
394 | |
395 while (_insertionIndex < _currentBlock.statements.length) { | |
396 assert(_currentBlock == node); | |
397 | |
398 var original = _currentBlock.statements[_insertionIndex]; | |
399 var transformed = original.accept(this); | |
400 assert(_currentBlock.statements[_insertionIndex] == original); | |
401 if (transformed == null) { | |
402 _currentBlock.statements.removeAt(_insertionIndex); | |
403 } else { | |
404 _currentBlock.statements[_insertionIndex++] = transformed; | |
405 transformed.parent = _currentBlock; | |
406 } | |
407 } | |
408 | |
409 if (savedBlock != null) { | |
410 _currentBlock = savedBlock; | |
411 _insertionIndex = savedIndex; | |
412 } | |
413 return node; | |
414 } | |
415 | |
416 TreeNode visitVariableDeclaration(VariableDeclaration node) { | |
417 node.transformChildren(this); | |
418 | |
419 if (!captured.contains(node)) return node; | |
420 context.extend(node, node.initializer ?? new NullLiteral()); | |
421 | |
422 // TODO(ahe): Return null here when the parent has been correctly | |
423 // rewritten. So far, only for-in is known to use this return value. | |
424 return new VariableDeclaration(null, initializer: new InvalidExpression()); | |
425 } | |
426 | |
427 TreeNode visitVariableGet(VariableGet node) { | |
428 return captured.contains(node.variable) | |
429 ? context.lookup(node.variable) | |
430 : node; | |
431 } | |
432 | |
433 TreeNode visitVariableSet(VariableSet node) { | |
434 node.transformChildren(this); | |
435 | |
436 return captured.contains(node.variable) | |
437 ? context.assign(node.variable, node.value) | |
438 : node; | |
439 } | |
440 } | |
OLD | NEW |