OLD | NEW |
| (Empty) |
1 // Copyright (c) 2012, 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 closureToClassMapper; | |
6 | |
7 import "elements/elements.dart"; | |
8 import "dart2jslib.dart"; | |
9 import "dart_types.dart"; | |
10 import "js_backend/js_backend.dart" show JavaScriptBackend; | |
11 import "scanner/scannerlib.dart" show Token; | |
12 import "tree/tree.dart"; | |
13 import "util/util.dart"; | |
14 import "elements/modelx.dart" | |
15 show BaseFunctionElementX, | |
16 ClassElementX, | |
17 ElementX, | |
18 LocalFunctionElementX; | |
19 import "elements/visitor.dart" show ElementVisitor; | |
20 | |
21 import 'universe/universe.dart' show | |
22 Universe; | |
23 | |
24 class ClosureNamer { | |
25 String getClosureVariableName(String name, int id) { | |
26 return "${name}_$id"; | |
27 } | |
28 | |
29 void forgetElement(Element element) {} | |
30 } | |
31 | |
32 class ClosureTask extends CompilerTask { | |
33 Map<Node, ClosureClassMap> closureMappingCache; | |
34 ClosureNamer namer; | |
35 ClosureTask(Compiler compiler, this.namer) | |
36 : closureMappingCache = new Map<Node, ClosureClassMap>(), | |
37 super(compiler); | |
38 | |
39 String get name => "Closure Simplifier"; | |
40 | |
41 ClosureClassMap computeClosureToClassMapping(Element element, | |
42 Node node, | |
43 TreeElements elements) { | |
44 return measure(() { | |
45 ClosureClassMap cached = closureMappingCache[node]; | |
46 if (cached != null) return cached; | |
47 | |
48 ClosureTranslator translator = | |
49 new ClosureTranslator(compiler, elements, closureMappingCache, namer); | |
50 | |
51 // The translator will store the computed closure-mappings inside the | |
52 // cache. One for given node and one for each nested closure. | |
53 if (node is FunctionExpression) { | |
54 translator.translateFunction(element, node); | |
55 } else if (element.isSynthesized) { | |
56 return new ClosureClassMap(null, null, null, new ThisLocal(element)); | |
57 } else { | |
58 assert(element.isField); | |
59 VariableElement field = element; | |
60 if (field.initializer != null) { | |
61 // The lazy initializer of a static. | |
62 translator.translateLazyInitializer(element, node, field.initializer); | |
63 } else { | |
64 assert(element.isInstanceMember); | |
65 closureMappingCache[node] = | |
66 new ClosureClassMap(null, null, null, new ThisLocal(element)); | |
67 } | |
68 } | |
69 assert(closureMappingCache[node] != null); | |
70 return closureMappingCache[node]; | |
71 }); | |
72 } | |
73 | |
74 ClosureClassMap getMappingForNestedFunction(FunctionExpression node) { | |
75 return measure(() { | |
76 ClosureClassMap nestedClosureData = closureMappingCache[node]; | |
77 if (nestedClosureData == null) { | |
78 compiler.internalError(node, "No closure cache."); | |
79 } | |
80 return nestedClosureData; | |
81 }); | |
82 } | |
83 | |
84 void forgetElement(var closure) { | |
85 ClosureClassElement cls; | |
86 if (closure is ClosureFieldElement) { | |
87 cls = closure.closureClass; | |
88 } else if (closure is SynthesizedCallMethodElementX) { | |
89 cls = closure.closureClass; | |
90 } else { | |
91 throw new SpannableAssertionFailure( | |
92 closure, 'Not a closure: $closure (${closure.runtimeType}).'); | |
93 } | |
94 namer.forgetElement(cls); | |
95 compiler.enqueuer.codegen.forgetElement(cls); | |
96 } | |
97 } | |
98 | |
99 /// Common interface for [BoxFieldElement] and [ClosureFieldElement] as | |
100 /// non-elements. | |
101 abstract class CapturedVariable {} | |
102 | |
103 // TODO(ahe): These classes continuously cause problems. We need to | |
104 // find a more general solution. | |
105 class ClosureFieldElement extends ElementX | |
106 implements VariableElement, CapturedVariable { | |
107 /// The source variable this element refers to. | |
108 final Local local; | |
109 | |
110 ClosureFieldElement(String name, | |
111 this.local, | |
112 ClosureClassElement enclosing) | |
113 : super(name, ElementKind.FIELD, enclosing); | |
114 | |
115 /// Use [closureClass] instead. | |
116 @deprecated | |
117 get enclosingElement => super.enclosingElement; | |
118 | |
119 ClosureClassElement get closureClass => super.enclosingElement; | |
120 | |
121 MemberElement get memberContext => closureClass.methodElement.memberContext; | |
122 | |
123 bool get hasNode => false; | |
124 | |
125 Node get node { | |
126 throw new SpannableAssertionFailure(local, | |
127 'Should not access node of ClosureFieldElement.'); | |
128 } | |
129 | |
130 bool get hasResolvedAst => hasTreeElements; | |
131 | |
132 ResolvedAst get resolvedAst { | |
133 return new ResolvedAst(this, null, treeElements); | |
134 } | |
135 | |
136 Expression get initializer { | |
137 throw new SpannableAssertionFailure(local, | |
138 'Should not access initializer of ClosureFieldElement.'); | |
139 } | |
140 | |
141 bool get isInstanceMember => true; | |
142 bool get isAssignable => false; | |
143 | |
144 DartType computeType(Compiler compiler) => type; | |
145 | |
146 DartType get type { | |
147 if (local is LocalElement) { | |
148 LocalElement element = local; | |
149 return element.type; | |
150 } | |
151 return const DynamicType(); | |
152 } | |
153 | |
154 String toString() => "ClosureFieldElement($name)"; | |
155 | |
156 accept(ElementVisitor visitor) => visitor.visitClosureFieldElement(this); | |
157 | |
158 Element get analyzableElement => closureClass.methodElement.analyzableElement; | |
159 } | |
160 | |
161 // TODO(ahe): These classes continuously cause problems. We need to find | |
162 // a more general solution. | |
163 class ClosureClassElement extends ClassElementX { | |
164 DartType rawType; | |
165 DartType thisType; | |
166 FunctionType callType; | |
167 /// Node that corresponds to this closure, used for source position. | |
168 final FunctionExpression node; | |
169 | |
170 /** | |
171 * The element for the declaration of the function expression. | |
172 */ | |
173 final LocalFunctionElement methodElement; | |
174 | |
175 final List<ClosureFieldElement> _closureFields = <ClosureFieldElement>[]; | |
176 | |
177 ClosureClassElement(this.node, | |
178 String name, | |
179 Compiler compiler, | |
180 LocalFunctionElement closure) | |
181 : this.methodElement = closure, | |
182 super(name, | |
183 closure.compilationUnit, | |
184 // By assigning a fresh class-id we make sure that the hashcode | |
185 // is unique, but also emit closure classes after all other | |
186 // classes (since the emitter sorts classes by their id). | |
187 compiler.getNextFreeClassId(), | |
188 STATE_DONE) { | |
189 JavaScriptBackend backend = compiler.backend; | |
190 ClassElement superclass = methodElement.isInstanceMember | |
191 ? backend.boundClosureClass | |
192 : backend.closureClass; | |
193 superclass.ensureResolved(compiler); | |
194 supertype = superclass.thisType; | |
195 interfaces = const Link<DartType>(); | |
196 thisType = rawType = new InterfaceType(this); | |
197 allSupertypesAndSelf = | |
198 superclass.allSupertypesAndSelf.extendClass(thisType); | |
199 callType = methodElement.type; | |
200 } | |
201 | |
202 Iterable<ClosureFieldElement> get closureFields => _closureFields; | |
203 | |
204 void addField(ClosureFieldElement field, DiagnosticListener listener) { | |
205 _closureFields.add(field); | |
206 addMember(field, listener); | |
207 } | |
208 | |
209 bool get hasNode => true; | |
210 | |
211 bool get isClosure => true; | |
212 | |
213 Token get position => node.getBeginToken(); | |
214 | |
215 Node parseNode(DiagnosticListener listener) => node; | |
216 | |
217 // A [ClosureClassElement] is nested inside a function or initializer in terms | |
218 // of [enclosingElement], but still has to be treated as a top-level | |
219 // element. | |
220 bool get isTopLevel => true; | |
221 | |
222 get enclosingElement => methodElement; | |
223 | |
224 accept(ElementVisitor visitor) => visitor.visitClosureClassElement(this); | |
225 } | |
226 | |
227 /// A local variable that contains the box object holding the [BoxFieldElement] | |
228 /// fields. | |
229 class BoxLocal extends Local { | |
230 final String name; | |
231 final ExecutableElement executableContext; | |
232 | |
233 BoxLocal(this.name, this.executableContext); | |
234 } | |
235 | |
236 // TODO(ngeoffray, ahe): These classes continuously cause problems. We need to | |
237 // find a more general solution. | |
238 class BoxFieldElement extends ElementX | |
239 implements TypedElement, CapturedVariable { | |
240 final BoxLocal box; | |
241 | |
242 BoxFieldElement(String name, this.variableElement, BoxLocal box) | |
243 : this.box = box, | |
244 super(name, ElementKind.FIELD, box.executableContext); | |
245 | |
246 DartType computeType(Compiler compiler) => type; | |
247 | |
248 DartType get type => variableElement.type; | |
249 | |
250 final VariableElement variableElement; | |
251 | |
252 accept(ElementVisitor visitor) => visitor.visitBoxFieldElement(this); | |
253 } | |
254 | |
255 /// A local variable used encode the direct (uncaptured) references to [this]. | |
256 class ThisLocal extends Local { | |
257 final ExecutableElement executableContext; | |
258 | |
259 ThisLocal(this.executableContext); | |
260 | |
261 String get name => 'this'; | |
262 | |
263 ClassElement get enclosingClass => executableContext.enclosingClass; | |
264 } | |
265 | |
266 /// Call method of a closure class. | |
267 class SynthesizedCallMethodElementX extends BaseFunctionElementX { | |
268 final LocalFunctionElement expression; | |
269 | |
270 SynthesizedCallMethodElementX(String name, | |
271 LocalFunctionElementX other, | |
272 ClosureClassElement enclosing) | |
273 : expression = other, | |
274 super(name, other.kind, other.modifiers, enclosing, false) { | |
275 functionSignatureCache = other.functionSignature; | |
276 } | |
277 | |
278 /// Use [closureClass] instead. | |
279 @deprecated | |
280 get enclosingElement => super.enclosingElement; | |
281 | |
282 ClosureClassElement get closureClass => super.enclosingElement; | |
283 | |
284 MemberElement get memberContext { | |
285 return closureClass.methodElement.memberContext; | |
286 } | |
287 | |
288 bool get hasNode => expression.hasNode; | |
289 | |
290 FunctionExpression get node => expression.node; | |
291 | |
292 FunctionExpression parseNode(DiagnosticListener listener) => node; | |
293 | |
294 ResolvedAst get resolvedAst { | |
295 return new ResolvedAst(this, node, treeElements); | |
296 } | |
297 | |
298 Element get analyzableElement => closureClass.methodElement.analyzableElement; | |
299 } | |
300 | |
301 // The box-element for a scope, and the captured variables that need to be | |
302 // stored in the box. | |
303 class ClosureScope { | |
304 BoxLocal boxElement; | |
305 Map<VariableElement, BoxFieldElement> _capturedVariableMapping; | |
306 | |
307 // If the scope is attached to a [For] contains the variables that are | |
308 // declared in the initializer of the [For] and that need to be boxed. | |
309 // Otherwise contains the empty List. | |
310 List<VariableElement> boxedLoopVariables = const <VariableElement>[]; | |
311 | |
312 ClosureScope(this.boxElement, this._capturedVariableMapping); | |
313 | |
314 bool hasBoxedLoopVariables() => !boxedLoopVariables.isEmpty; | |
315 | |
316 bool isCapturedVariable(VariableElement variable) { | |
317 return _capturedVariableMapping.containsKey(variable); | |
318 } | |
319 | |
320 void forEachCapturedVariable(f(LocalVariableElement variable, | |
321 BoxFieldElement boxField)) { | |
322 _capturedVariableMapping.forEach(f); | |
323 } | |
324 } | |
325 | |
326 class ClosureClassMap { | |
327 // The closure's element before any translation. Will be null for methods. | |
328 final LocalFunctionElement closureElement; | |
329 // The closureClassElement will be null for methods that are not local | |
330 // closures. | |
331 final ClosureClassElement closureClassElement; | |
332 // The callElement will be null for methods that are not local closures. | |
333 final FunctionElement callElement; | |
334 // The [thisElement] makes handling 'this' easier by treating it like any | |
335 // other argument. It is only set for instance-members. | |
336 final ThisLocal thisLocal; | |
337 | |
338 // Maps free locals, arguments and function elements to their captured | |
339 // copies. | |
340 final Map<Local, CapturedVariable> _freeVariableMapping = | |
341 new Map<Local, CapturedVariable>(); | |
342 | |
343 // Maps closure-fields to their captured elements. This is somehow the inverse | |
344 // mapping of [freeVariableMapping], but whereas [freeVariableMapping] does | |
345 // not deal with boxes, here we map instance-fields (which might represent | |
346 // boxes) to their boxElement. | |
347 final Map<ClosureFieldElement, Local> _closureFieldMapping = | |
348 new Map<ClosureFieldElement, Local>(); | |
349 | |
350 // Maps scopes ([Loop] and [FunctionExpression] nodes) to their | |
351 // [ClosureScope] which contains their box and the | |
352 // captured variables that are stored in the box. | |
353 // This map will be empty if the method/closure of this [ClosureData] does not | |
354 // contain any nested closure. | |
355 final Map<Node, ClosureScope> capturingScopes = new Map<Node, ClosureScope>(); | |
356 | |
357 final Set<Local> usedVariablesInTry = new Set<Local>(); | |
358 | |
359 ClosureClassMap(this.closureElement, | |
360 this.closureClassElement, | |
361 this.callElement, | |
362 this.thisLocal); | |
363 | |
364 void addFreeVariable(Local element) { | |
365 assert(_freeVariableMapping[element] == null); | |
366 _freeVariableMapping[element] = null; | |
367 } | |
368 | |
369 Iterable<Local> get freeVariables => _freeVariableMapping.keys; | |
370 | |
371 bool isFreeVariable(Local element) { | |
372 return _freeVariableMapping.containsKey(element); | |
373 } | |
374 | |
375 CapturedVariable getFreeVariableElement(Local element) { | |
376 return _freeVariableMapping[element]; | |
377 } | |
378 | |
379 /// Sets the free [variable] to be captured by the [boxField]. | |
380 void setFreeVariableBoxField(Local variable, | |
381 BoxFieldElement boxField) { | |
382 _freeVariableMapping[variable] = boxField; | |
383 } | |
384 | |
385 /// Sets the free [variable] to be captured by the [closureField]. | |
386 void setFreeVariableClosureField(Local variable, | |
387 ClosureFieldElement closureField) { | |
388 _freeVariableMapping[variable] = closureField; | |
389 } | |
390 | |
391 | |
392 void forEachFreeVariable(f(Local variable, | |
393 CapturedVariable field)) { | |
394 _freeVariableMapping.forEach(f); | |
395 } | |
396 | |
397 Local getLocalVariableForClosureField(ClosureFieldElement field) { | |
398 return _closureFieldMapping[field]; | |
399 } | |
400 | |
401 void setLocalVariableForClosureField(ClosureFieldElement field, | |
402 Local variable) { | |
403 _closureFieldMapping[field] = variable; | |
404 } | |
405 | |
406 bool get isClosure => closureElement != null; | |
407 | |
408 bool capturingScopesBox(Local variable) { | |
409 return capturingScopes.values.any((scope) { | |
410 return scope.boxedLoopVariables.contains(variable); | |
411 }); | |
412 } | |
413 | |
414 bool isVariableBoxed(Local variable) { | |
415 CapturedVariable copy = _freeVariableMapping[variable]; | |
416 if (copy is BoxFieldElement) { | |
417 return true; | |
418 } | |
419 return capturingScopesBox(variable); | |
420 } | |
421 | |
422 void forEachCapturedVariable(void f(Local variable, | |
423 CapturedVariable field)) { | |
424 _freeVariableMapping.forEach((variable, copy) { | |
425 if (variable is BoxLocal) return; | |
426 f(variable, copy); | |
427 }); | |
428 capturingScopes.values.forEach((ClosureScope scope) { | |
429 scope.forEachCapturedVariable(f); | |
430 }); | |
431 } | |
432 | |
433 void forEachBoxedVariable(void f(LocalVariableElement local, | |
434 BoxFieldElement field)) { | |
435 _freeVariableMapping.forEach((variable, copy) { | |
436 if (!isVariableBoxed(variable)) return; | |
437 f(variable, copy); | |
438 }); | |
439 capturingScopes.values.forEach((ClosureScope scope) { | |
440 scope.forEachCapturedVariable(f); | |
441 }); | |
442 } | |
443 | |
444 void removeMyselfFrom(Universe universe) { | |
445 _freeVariableMapping.values.forEach((e) { | |
446 universe.closurizedMembers.remove(e); | |
447 universe.fieldSetters.remove(e); | |
448 universe.fieldGetters.remove(e); | |
449 }); | |
450 } | |
451 } | |
452 | |
453 class ClosureTranslator extends Visitor { | |
454 final Compiler compiler; | |
455 final TreeElements elements; | |
456 int closureFieldCounter = 0; | |
457 int boxedFieldCounter = 0; | |
458 bool inTryStatement = false; | |
459 final Map<Node, ClosureClassMap> closureMappingCache; | |
460 | |
461 // Map of captured variables. Initially they will map to `null`. If | |
462 // a variable needs to be boxed then the scope declaring the variable | |
463 // will update this to mapping to the capturing [BoxFieldElement]. | |
464 Map<Local, BoxFieldElement> _capturedVariableMapping = | |
465 new Map<Local, BoxFieldElement>(); | |
466 | |
467 // List of encountered closures. | |
468 List<Expression> closures = <Expression>[]; | |
469 | |
470 // The local variables that have been declared in the current scope. | |
471 List<LocalVariableElement> scopeVariables; | |
472 | |
473 // Keep track of the mutated local variables so that we don't need to box | |
474 // non-mutated variables. | |
475 Set<LocalVariableElement> mutatedVariables = new Set<LocalVariableElement>(); | |
476 | |
477 MemberElement outermostElement; | |
478 ExecutableElement executableContext; | |
479 | |
480 // The closureData of the currentFunctionElement. | |
481 ClosureClassMap closureData; | |
482 | |
483 ClosureNamer namer; | |
484 | |
485 bool insideClosure = false; | |
486 | |
487 ClosureTranslator(this.compiler, | |
488 this.elements, | |
489 this.closureMappingCache, | |
490 this.namer); | |
491 | |
492 bool isCapturedVariable(Local element) { | |
493 return _capturedVariableMapping.containsKey(element); | |
494 } | |
495 | |
496 void addCapturedVariable(Node node, Local variable) { | |
497 if (_capturedVariableMapping[variable] != null) { | |
498 compiler.internalError(node, 'In closure analyzer.'); | |
499 } | |
500 _capturedVariableMapping[variable] = null; | |
501 } | |
502 | |
503 void setCapturedVariableBoxField(Local variable, | |
504 BoxFieldElement boxField) { | |
505 assert(isCapturedVariable(variable)); | |
506 _capturedVariableMapping[variable] = boxField; | |
507 } | |
508 | |
509 BoxFieldElement getCapturedVariableBoxField(Local variable) { | |
510 return _capturedVariableMapping[variable]; | |
511 } | |
512 | |
513 void translateFunction(Element element, FunctionExpression node) { | |
514 // For constructors the [element] and the [:elements[node]:] may differ. | |
515 // The [:elements[node]:] always points to the generative-constructor | |
516 // element, whereas the [element] might be the constructor-body element. | |
517 visit(node); // [visitFunctionExpression] will call [visitInvokable]. | |
518 // When variables need to be boxed their [_capturedVariableMapping] is | |
519 // updated, but we delay updating the similar freeVariableMapping in the | |
520 // closure datas that capture these variables. | |
521 // The closures don't have their fields (in the closure class) set, either. | |
522 updateClosures(); | |
523 } | |
524 | |
525 void translateLazyInitializer(VariableElement element, | |
526 VariableDefinitions node, | |
527 Expression initializer) { | |
528 visitInvokable(element, node, () { visit(initializer); }); | |
529 updateClosures(); | |
530 } | |
531 | |
532 // This function runs through all of the existing closures and updates their | |
533 // free variables to the boxed value. It also adds the field-elements to the | |
534 // class representing the closure. | |
535 void updateClosures() { | |
536 for (Expression closure in closures) { | |
537 // The captured variables that need to be stored in a field of the closure | |
538 // class. | |
539 Set<Local> fieldCaptures = new Set<Local>(); | |
540 Set<BoxLocal> boxes = new Set<BoxLocal>(); | |
541 ClosureClassMap data = closureMappingCache[closure]; | |
542 // We get a copy of the keys and iterate over it, to avoid modifications | |
543 // to the map while iterating over it. | |
544 Iterable<Local> freeVariables = data.freeVariables.toList(); | |
545 freeVariables.forEach((Local fromElement) { | |
546 assert(data.isFreeVariable(fromElement)); | |
547 assert(data.getFreeVariableElement(fromElement) == null); | |
548 assert(isCapturedVariable(fromElement)); | |
549 BoxFieldElement boxFieldElement = | |
550 getCapturedVariableBoxField(fromElement); | |
551 if (boxFieldElement == null) { | |
552 assert(fromElement is! BoxLocal); | |
553 // The variable has not been boxed. | |
554 fieldCaptures.add(fromElement); | |
555 } else { | |
556 // A boxed element. | |
557 data.setFreeVariableBoxField(fromElement, boxFieldElement); | |
558 boxes.add(boxFieldElement.box); | |
559 } | |
560 }); | |
561 ClosureClassElement closureClass = data.closureClassElement; | |
562 assert(closureClass != null || | |
563 (fieldCaptures.isEmpty && boxes.isEmpty)); | |
564 | |
565 void addClosureField(Local local, String name) { | |
566 ClosureFieldElement closureField = | |
567 new ClosureFieldElement(name, local, closureClass); | |
568 closureClass.addField(closureField, compiler); | |
569 data.setLocalVariableForClosureField(closureField, local); | |
570 data.setFreeVariableClosureField(local, closureField); | |
571 } | |
572 | |
573 // Add the box elements first so we get the same ordering. | |
574 // TODO(sra): What is the canonical order of multiple boxes? | |
575 for (BoxLocal capturedElement in boxes) { | |
576 addClosureField(capturedElement, capturedElement.name); | |
577 } | |
578 | |
579 /// Comparator for locals. Position boxes before elements. | |
580 int compareLocals(a, b) { | |
581 if (a is Element && b is Element) { | |
582 return Elements.compareByPosition(a, b); | |
583 } else if (a is Element) { | |
584 return 1; | |
585 } else if (b is Element) { | |
586 return -1; | |
587 } else { | |
588 return a.name.compareTo(b.name); | |
589 } | |
590 } | |
591 | |
592 for (Local capturedLocal in fieldCaptures.toList()..sort(compareLocals)) { | |
593 int id = closureFieldCounter++; | |
594 String name = namer.getClosureVariableName(capturedLocal.name, id); | |
595 addClosureField(capturedLocal, name); | |
596 } | |
597 closureClass.reverseBackendMembers(); | |
598 } | |
599 } | |
600 | |
601 void useLocal(Local variable) { | |
602 // If the element is not declared in the current function and the element | |
603 // is not the closure itself we need to mark the element as free variable. | |
604 // Note that the check on [insideClosure] is not just an | |
605 // optimization: factories have type parameters as function | |
606 // parameters, and type parameters are declared in the class, not | |
607 // the factory. | |
608 bool inCurrentContext(Local variable) { | |
609 return variable == executableContext || | |
610 variable.executableContext == executableContext; | |
611 } | |
612 | |
613 if (insideClosure && !inCurrentContext(variable)) { | |
614 closureData.addFreeVariable(variable); | |
615 } else if (inTryStatement) { | |
616 // Don't mark the this-element or a self-reference. This would complicate | |
617 // things in the builder. | |
618 // Note that nested (named) functions are immutable. | |
619 if (variable != closureData.thisLocal && | |
620 variable != closureData.closureElement) { | |
621 // TODO(ngeoffray): only do this if the variable is mutated. | |
622 closureData.usedVariablesInTry.add(variable); | |
623 } | |
624 } | |
625 } | |
626 | |
627 void useTypeVariableAsLocal(TypeVariableType typeVariable) { | |
628 useLocal(new TypeVariableLocal(typeVariable, outermostElement)); | |
629 } | |
630 | |
631 void declareLocal(LocalVariableElement element) { | |
632 scopeVariables.add(element); | |
633 } | |
634 | |
635 void registerNeedsThis() { | |
636 if (closureData.thisLocal != null) { | |
637 useLocal(closureData.thisLocal); | |
638 } | |
639 } | |
640 | |
641 visit(Node node) => node.accept(this); | |
642 | |
643 visitNode(Node node) => node.visitChildren(this); | |
644 | |
645 visitVariableDefinitions(VariableDefinitions node) { | |
646 if (node.type != null) { | |
647 visit(node.type); | |
648 } | |
649 for (Link<Node> link = node.definitions.nodes; | |
650 !link.isEmpty; | |
651 link = link.tail) { | |
652 Node definition = link.head; | |
653 LocalElement element = elements[definition]; | |
654 assert(element != null); | |
655 if (!element.isInitializingFormal) { | |
656 declareLocal(element); | |
657 } | |
658 // We still need to visit the right-hand sides of the init-assignments. | |
659 // For SendSets don't visit the left again. Otherwise it would be marked | |
660 // as mutated. | |
661 if (definition is Send) { | |
662 Send assignment = definition; | |
663 Node arguments = assignment.argumentsNode; | |
664 if (arguments != null) { | |
665 visit(arguments); | |
666 } | |
667 } else { | |
668 visit(definition); | |
669 } | |
670 } | |
671 } | |
672 | |
673 visitTypeAnnotation(TypeAnnotation node) { | |
674 MemberElement member = executableContext.memberContext; | |
675 DartType type = elements.getType(node); | |
676 // TODO(karlklose,johnniwinther): if the type is null, the annotation is | |
677 // from a parameter which has been analyzed before the method has been | |
678 // resolved and the result has been thrown away. | |
679 if (compiler.enableTypeAssertions && type != null && | |
680 type.containsTypeVariables) { | |
681 if (insideClosure && member.isFactoryConstructor) { | |
682 // This is a closure in a factory constructor. Since there is no | |
683 // [:this:], we have to mark the type arguments as free variables to | |
684 // capture them in the closure. | |
685 type.forEachTypeVariable((TypeVariableType variable) { | |
686 useTypeVariableAsLocal(variable); | |
687 }); | |
688 } | |
689 if (member.isInstanceMember && !member.isField) { | |
690 // In checked mode, using a type variable in a type annotation may lead | |
691 // to a runtime type check that needs to access the type argument and | |
692 // therefore the closure needs a this-element, if it is not in a field | |
693 // initializer; field initatializers are evaluated in a context where | |
694 // the type arguments are available in locals. | |
695 registerNeedsThis(); | |
696 } | |
697 } | |
698 } | |
699 | |
700 visitIdentifier(Identifier node) { | |
701 if (node.isThis()) { | |
702 registerNeedsThis(); | |
703 } else { | |
704 Element element = elements[node]; | |
705 if (element != null && element.isTypeVariable) { | |
706 if (outermostElement.isConstructor) { | |
707 TypeVariableElement typeVariable = element; | |
708 useTypeVariableAsLocal(typeVariable.type); | |
709 } else { | |
710 registerNeedsThis(); | |
711 } | |
712 } | |
713 } | |
714 node.visitChildren(this); | |
715 } | |
716 | |
717 visitSend(Send node) { | |
718 Element element = elements[node]; | |
719 if (Elements.isLocal(element)) { | |
720 LocalElement localElement = element; | |
721 useLocal(localElement); | |
722 } else if (element != null && element.isTypeVariable) { | |
723 TypeVariableElement variable = element; | |
724 analyzeType(variable.type); | |
725 } else if (node.receiver == null && | |
726 Elements.isInstanceSend(node, elements)) { | |
727 registerNeedsThis(); | |
728 } else if (node.isSuperCall) { | |
729 registerNeedsThis(); | |
730 } else if (node.isTypeTest || node.isTypeCast) { | |
731 TypeAnnotation annotation = node.typeAnnotationFromIsCheckOrCast; | |
732 DartType type = elements.getType(annotation); | |
733 analyzeType(type); | |
734 } else if (node.isTypeTest) { | |
735 DartType type = elements.getType(node.typeAnnotationFromIsCheckOrCast); | |
736 analyzeType(type); | |
737 } else if (node.isTypeCast) { | |
738 DartType type = elements.getType(node.arguments.head); | |
739 analyzeType(type); | |
740 } else if (elements.isAssert(node) && !compiler.enableUserAssertions) { | |
741 return; | |
742 } | |
743 node.visitChildren(this); | |
744 } | |
745 | |
746 visitSendSet(SendSet node) { | |
747 Element element = elements[node]; | |
748 if (Elements.isLocal(element)) { | |
749 mutatedVariables.add(element); | |
750 if (compiler.enableTypeAssertions) { | |
751 TypedElement typedElement = element; | |
752 analyzeTypeVariables(typedElement.type); | |
753 } | |
754 } | |
755 super.visitSendSet(node); | |
756 } | |
757 | |
758 visitNewExpression(NewExpression node) { | |
759 DartType type = elements.getType(node); | |
760 analyzeType(type); | |
761 node.visitChildren(this); | |
762 } | |
763 | |
764 void analyzeTypeVariables(DartType type) { | |
765 type.forEachTypeVariable((TypeVariableType typeVariable) { | |
766 // Field initializers are inlined and access the type variable as | |
767 // normal parameters. | |
768 if (!outermostElement.isField && | |
769 !outermostElement.isConstructor) { | |
770 registerNeedsThis(); | |
771 } else { | |
772 useTypeVariableAsLocal(typeVariable); | |
773 } | |
774 }); | |
775 } | |
776 | |
777 void analyzeType(DartType type) { | |
778 // TODO(johnniwinther): Find out why this can be null. | |
779 if (type == null) return; | |
780 if (outermostElement.isClassMember && | |
781 compiler.backend.classNeedsRti(outermostElement.enclosingClass)) { | |
782 if (outermostElement.isConstructor || | |
783 outermostElement.isField) { | |
784 analyzeTypeVariables(type); | |
785 } else if (outermostElement.isInstanceMember) { | |
786 if (type.containsTypeVariables) { | |
787 registerNeedsThis(); | |
788 } | |
789 } | |
790 } | |
791 } | |
792 | |
793 // If variables that are declared in the [node] scope are captured and need | |
794 // to be boxed create a box-element and update the [capturingScopes] in the | |
795 // current [closureData]. | |
796 // The boxed variables are updated in the [capturedVariableMapping]. | |
797 void attachCapturedScopeVariables(Node node) { | |
798 BoxLocal box = null; | |
799 Map<LocalVariableElement, BoxFieldElement> scopeMapping = | |
800 new Map<LocalVariableElement, BoxFieldElement>(); | |
801 | |
802 void boxCapturedVariable(LocalVariableElement variable) { | |
803 if (isCapturedVariable(variable)) { | |
804 if (box == null) { | |
805 // TODO(floitsch): construct better box names. | |
806 String boxName = | |
807 namer.getClosureVariableName('box', closureFieldCounter++); | |
808 box = new BoxLocal(boxName, executableContext); | |
809 } | |
810 String elementName = variable.name; | |
811 String boxedName = | |
812 namer.getClosureVariableName(elementName, boxedFieldCounter++); | |
813 // TODO(kasperl): Should this be a FieldElement instead? | |
814 BoxFieldElement boxed = new BoxFieldElement(boxedName, variable, box); | |
815 // No need to rename the fields of a box, so we give them a native name | |
816 // right now. | |
817 boxed.setFixedBackendName(boxedName); | |
818 scopeMapping[variable] = boxed; | |
819 setCapturedVariableBoxField(variable, boxed); | |
820 } | |
821 } | |
822 | |
823 for (LocalVariableElement variable in scopeVariables) { | |
824 // No need to box non-assignable elements. | |
825 if (!variable.isAssignable) continue; | |
826 if (!mutatedVariables.contains(variable)) continue; | |
827 boxCapturedVariable(variable); | |
828 } | |
829 if (!scopeMapping.isEmpty) { | |
830 ClosureScope scope = new ClosureScope(box, scopeMapping); | |
831 closureData.capturingScopes[node] = scope; | |
832 } | |
833 } | |
834 | |
835 void inNewScope(Node node, Function action) { | |
836 List<LocalVariableElement> oldScopeVariables = scopeVariables; | |
837 scopeVariables = <LocalVariableElement>[]; | |
838 action(); | |
839 attachCapturedScopeVariables(node); | |
840 mutatedVariables.removeAll(scopeVariables); | |
841 scopeVariables = oldScopeVariables; | |
842 } | |
843 | |
844 visitLoop(Loop node) { | |
845 inNewScope(node, () { | |
846 node.visitChildren(this); | |
847 }); | |
848 } | |
849 | |
850 visitFor(For node) { | |
851 visitLoop(node); | |
852 // See if we have declared loop variables that need to be boxed. | |
853 if (node.initializer == null) return; | |
854 VariableDefinitions definitions = node.initializer.asVariableDefinitions(); | |
855 if (definitions == null) return; | |
856 ClosureScope scopeData = closureData.capturingScopes[node]; | |
857 if (scopeData == null) return; | |
858 List<LocalVariableElement> result = <LocalVariableElement>[]; | |
859 for (Link<Node> link = definitions.definitions.nodes; | |
860 !link.isEmpty; | |
861 link = link.tail) { | |
862 Node definition = link.head; | |
863 LocalVariableElement element = elements[definition]; | |
864 if (isCapturedVariable(element)) { | |
865 result.add(element); | |
866 } | |
867 } | |
868 scopeData.boxedLoopVariables = result; | |
869 } | |
870 | |
871 /** Returns a non-unique name for the given closure element. */ | |
872 String computeClosureName(Element element) { | |
873 Link<String> parts = const Link<String>(); | |
874 String ownName = element.name; | |
875 if (ownName == null || ownName == "") { | |
876 parts = parts.prepend("closure"); | |
877 } else { | |
878 parts = parts.prepend(ownName); | |
879 } | |
880 for (Element enclosingElement = element.enclosingElement; | |
881 enclosingElement != null && | |
882 (enclosingElement.kind == ElementKind.GENERATIVE_CONSTRUCTOR_BODY | |
883 || enclosingElement.kind == ElementKind.GENERATIVE_CONSTRUCTOR | |
884 || enclosingElement.kind == ElementKind.CLASS | |
885 || enclosingElement.kind == ElementKind.FUNCTION | |
886 || enclosingElement.kind == ElementKind.GETTER | |
887 || enclosingElement.kind == ElementKind.SETTER); | |
888 enclosingElement = enclosingElement.enclosingElement) { | |
889 // TODO(johnniwinther): Simplify computed names. | |
890 if (enclosingElement.isGenerativeConstructor || | |
891 enclosingElement.isGenerativeConstructorBody || | |
892 enclosingElement.isFactoryConstructor) { | |
893 parts = parts.prepend( | |
894 Elements.reconstructConstructorName(enclosingElement)); | |
895 } else { | |
896 String surroundingName = | |
897 Elements.operatorNameToIdentifier(enclosingElement.name); | |
898 parts = parts.prepend(surroundingName); | |
899 } | |
900 // A generative constructors's parent is the class; the class name is | |
901 // already part of the generative constructor's name. | |
902 if (enclosingElement.kind == ElementKind.GENERATIVE_CONSTRUCTOR) break; | |
903 } | |
904 StringBuffer sb = new StringBuffer(); | |
905 parts.printOn(sb, '_'); | |
906 return sb.toString(); | |
907 } | |
908 | |
909 JavaScriptBackend get backend => compiler.backend; | |
910 | |
911 ClosureClassMap globalizeClosure(FunctionExpression node, | |
912 LocalFunctionElement element) { | |
913 String closureName = computeClosureName(element); | |
914 ClosureClassElement globalizedElement = new ClosureClassElement( | |
915 node, closureName, compiler, element); | |
916 FunctionElement callElement = | |
917 new SynthesizedCallMethodElementX(Compiler.CALL_OPERATOR_NAME, | |
918 element, | |
919 globalizedElement); | |
920 backend.maybeMarkClosureAsNeededForReflection(globalizedElement, callElement
, element); | |
921 MemberElement enclosing = element.memberContext; | |
922 enclosing.nestedClosures.add(callElement); | |
923 globalizedElement.addMember(callElement, compiler); | |
924 globalizedElement.computeAllClassMembers(compiler); | |
925 // The nested function's 'this' is the same as the one for the outer | |
926 // function. It could be [null] if we are inside a static method. | |
927 ThisLocal thisElement = closureData.thisLocal; | |
928 | |
929 return new ClosureClassMap(element, globalizedElement, | |
930 callElement, thisElement); | |
931 } | |
932 | |
933 void visitInvokable(ExecutableElement element, | |
934 Node node, | |
935 void visitChildren()) { | |
936 bool oldInsideClosure = insideClosure; | |
937 Element oldFunctionElement = executableContext; | |
938 ClosureClassMap oldClosureData = closureData; | |
939 | |
940 insideClosure = outermostElement != null; | |
941 LocalFunctionElement closure; | |
942 executableContext = element; | |
943 if (insideClosure) { | |
944 closure = element; | |
945 closures.add(node); | |
946 closureData = globalizeClosure(node, closure); | |
947 } else { | |
948 outermostElement = element; | |
949 ThisLocal thisElement = null; | |
950 if (element.isInstanceMember || element.isGenerativeConstructor) { | |
951 thisElement = new ThisLocal(element); | |
952 } | |
953 closureData = new ClosureClassMap(null, null, null, thisElement); | |
954 } | |
955 closureMappingCache[node] = closureData; | |
956 | |
957 inNewScope(node, () { | |
958 DartType type = element.type; | |
959 // If the method needs RTI, or checked mode is set, we need to | |
960 // escape the potential type variables used in that closure. | |
961 if (element is FunctionElement && | |
962 (compiler.backend.methodNeedsRti(element) || | |
963 compiler.enableTypeAssertions)) { | |
964 analyzeTypeVariables(type); | |
965 } | |
966 | |
967 visitChildren(); | |
968 }); | |
969 | |
970 | |
971 ClosureClassMap savedClosureData = closureData; | |
972 bool savedInsideClosure = insideClosure; | |
973 | |
974 // Restore old values. | |
975 insideClosure = oldInsideClosure; | |
976 closureData = oldClosureData; | |
977 executableContext = oldFunctionElement; | |
978 | |
979 // Mark all free variables as captured and use them in the outer function. | |
980 Iterable<Local> freeVariables = savedClosureData.freeVariables; | |
981 assert(freeVariables.isEmpty || savedInsideClosure); | |
982 for (Local freeVariable in freeVariables) { | |
983 addCapturedVariable(node, freeVariable); | |
984 useLocal(freeVariable); | |
985 } | |
986 } | |
987 | |
988 visitFunctionExpression(FunctionExpression node) { | |
989 Element element = elements[node]; | |
990 | |
991 if (element.isParameter) { | |
992 // TODO(ahe): This is a hack. This method should *not* call | |
993 // visitChildren. | |
994 return node.name.accept(this); | |
995 } | |
996 | |
997 visitInvokable(element, node, () { | |
998 // TODO(ahe): This is problematic. The backend should not repeat | |
999 // the work of the resolver. It is the resolver's job to create | |
1000 // parameters, etc. Other phases should only visit statements. | |
1001 if (node.parameters != null) node.parameters.accept(this); | |
1002 if (node.initializers != null) node.initializers.accept(this); | |
1003 if (node.body != null) node.body.accept(this); | |
1004 }); | |
1005 } | |
1006 | |
1007 visitTryStatement(TryStatement node) { | |
1008 // TODO(ngeoffray): implement finer grain state. | |
1009 bool oldInTryStatement = inTryStatement; | |
1010 inTryStatement = true; | |
1011 node.visitChildren(this); | |
1012 inTryStatement = oldInTryStatement; | |
1013 } | |
1014 } | |
1015 | |
1016 /// A type variable as a local variable. | |
1017 class TypeVariableLocal implements Local { | |
1018 final TypeVariableType typeVariable; | |
1019 final ExecutableElement executableContext; | |
1020 | |
1021 TypeVariableLocal(this.typeVariable, this.executableContext); | |
1022 | |
1023 String get name => typeVariable.name; | |
1024 | |
1025 int get hashCode => typeVariable.hashCode; | |
1026 | |
1027 bool operator ==(other) { | |
1028 if (other is! TypeVariableLocal) return false; | |
1029 return typeVariable == other.typeVariable; | |
1030 } | |
1031 } | |
OLD | NEW |