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 class ClosureFieldElement extends Element { | |
6 ClosureFieldElement(SourceString name, ClassElement enclosing) | |
7 : super(name, ElementKind.FIELD, enclosing); | |
8 | |
9 bool isInstanceMember() => true; | |
10 bool isAssignable() => false; | |
11 | |
12 String toString() => "ClosureFieldElement($name)"; | |
13 } | |
14 | |
15 class ClosureClassElement extends ClassElement { | |
16 ClosureClassElement(Compiler compiler, Element enclosingElement) | |
17 : super(compiler.closureClass.name, enclosingElement) { | |
18 isResolved = true; | |
19 compiler.closureClass.ensureResolved(compiler); | |
20 supertype = compiler.closureClass.computeType(compiler); | |
21 } | |
22 } | |
23 | |
24 // The box-element for a scope, and the captured variables that need to be | |
25 // stored in the box. | |
26 class ClosureScope { | |
27 Element boxElement; | |
28 Map<Element, Element> capturedVariableMapping; | |
29 // If the scope is attached to a [For] contains the variables that are | |
30 // declared in the initializer of the [For] and that need to be boxed. | |
31 // Otherwise contains the empty List. | |
32 List<Element> boxedLoopVariables; | |
33 | |
34 ClosureScope(this.boxElement, this.capturedVariableMapping) | |
35 : boxedLoopVariables = const <Element>[]; | |
36 | |
37 bool hasBoxedLoopVariables() => !boxedLoopVariables.isEmpty(); | |
38 } | |
39 | |
40 class ClosureData { | |
41 // The closure's element before any translation. Will be null for methods. | |
42 final FunctionElement closureElement; | |
43 // The closureClassElement will be null for methods that are not local | |
44 // closures. | |
45 final ClassElement closureClassElement; | |
46 // The callElement will be null for methods that are not local closures. | |
47 final FunctionElement callElement; | |
48 // The [thisElement] makes handling 'this' easier by treating it like any | |
49 // other argument. It is only set for instance-members. | |
50 final Element thisElement; | |
51 | |
52 // Maps free locals, arguments and function elements to their captured | |
53 // copies. | |
54 final Map<Element, Element> freeVariableMapping; | |
55 // Maps closure-fields to their captured elements. This is somehow the inverse | |
56 // mapping of [freeVariableMapping], but whereas [freeVariableMapping] does | |
57 // not deal with boxes, here we map instance-fields (which might represent | |
58 // boxes) to their boxElement. | |
59 final Map<Element, Element> capturedFieldMapping; | |
60 | |
61 // Maps scopes ([Loop] and [FunctionExpression] nodes) to their | |
62 // [ClosureScope] which contains their box and the | |
63 // captured variables that are stored in the box. | |
64 // This map will be empty if the method/closure of this [ClosureData] does not | |
65 // contain any nested closure. | |
66 final Map<Node, ClosureScope> capturingScopes; | |
67 | |
68 final Set<Element> usedVariablesInTry; | |
69 | |
70 ClosureData(this.closureElement, | |
71 this.closureClassElement, | |
72 this.callElement, | |
73 this.thisElement) | |
74 : this.freeVariableMapping = new Map<Element, Element>(), | |
75 this.capturedFieldMapping = new Map<Element, Element>(), | |
76 this.capturingScopes = new Map<Node, ClosureScope>(), | |
77 this.usedVariablesInTry = new Set<Element>(); | |
78 | |
79 bool isClosure() => closureElement !== null; | |
80 } | |
81 | |
82 class ClosureTranslator extends AbstractVisitor { | |
83 final Compiler compiler; | |
84 final TreeElements elements; | |
85 int boxCounter = 0; | |
86 bool inTryCatchOrFinally = false; | |
87 final Map<Node, ClosureData> closureDataCache; | |
88 | |
89 // Map of captured variables. Initially they will map to themselves. If | |
90 // a variable needs to be boxed then the scope declaring the variable | |
91 // will update this mapping. | |
92 Map<Element, Element> capturedVariableMapping; | |
93 // List of encountered closures. | |
94 List<FunctionExpression> closures; | |
95 | |
96 // The variables that have been declared in the current scope. | |
97 List<Element> scopeVariables; | |
98 | |
99 FunctionElement currentFunctionElement; | |
100 // The closureData of the currentFunctionElement. | |
101 ClosureData closureData; | |
102 | |
103 bool insideClosure = false; | |
104 | |
105 ClosureTranslator(Compiler compiler, this.elements) | |
106 : capturedVariableMapping = new Map<Element, Element>(), | |
107 closures = <FunctionExpression>[], | |
108 this.compiler = compiler, | |
109 this.closureDataCache = compiler.builder.closureDataCache; | |
110 | |
111 ClosureData translate(Node node) { | |
112 // Closures have already been analyzed when visiting the surrounding | |
113 // method/function. This also shortcuts for bailout functions. | |
114 ClosureData cached = closureDataCache[node]; | |
115 if (cached !== null) return cached; | |
116 | |
117 visit(node); | |
118 // When variables need to be boxed their [capturedVariableMapping] is | |
119 // updated, but we delay updating the similar freeVariableMapping in the | |
120 // closure datas that capture these variables. | |
121 // The closures don't have their fields (in the closure class) set, either. | |
122 updateClosures(); | |
123 | |
124 return closureDataCache[node]; | |
125 } | |
126 | |
127 // This function runs through all of the existing closures and updates their | |
128 // free variables to the boxed value. It also adds the field-elements to the | |
129 // class representing the closure. At the same time it fills the | |
130 // [capturedFieldMapping]. | |
131 void updateClosures() { | |
132 for (FunctionExpression closure in closures) { | |
133 // The captured variables that need to be stored in a field of the closure | |
134 // class. | |
135 Set<Element> fieldCaptures = new Set<Element>(); | |
136 ClosureData data = closureDataCache[closure]; | |
137 Map<Element, Element> freeVariableMapping = data.freeVariableMapping; | |
138 // We get a copy of the keys and iterate over it, to avoid modifications | |
139 // to the map while iterating over it. | |
140 freeVariableMapping.getKeys().forEach((Element fromElement) { | |
141 assert(fromElement == freeVariableMapping[fromElement]); | |
142 Element updatedElement = capturedVariableMapping[fromElement]; | |
143 assert(updatedElement !== null); | |
144 if (fromElement == updatedElement) { | |
145 assert(freeVariableMapping[fromElement] == updatedElement); | |
146 assert(Elements.isLocal(updatedElement)); | |
147 // The variable has not been boxed. | |
148 fieldCaptures.add(updatedElement); | |
149 } else { | |
150 // A boxed element. | |
151 freeVariableMapping[fromElement] = updatedElement; | |
152 Element boxElement = updatedElement.enclosingElement; | |
153 assert(boxElement.kind == ElementKind.VARIABLE); | |
154 fieldCaptures.add(boxElement); | |
155 } | |
156 }); | |
157 ClassElement closureElement = data.closureClassElement; | |
158 assert(closureElement != null || fieldCaptures.isEmpty()); | |
159 for (Element boxElement in fieldCaptures) { | |
160 Element fieldElement = | |
161 new ClosureFieldElement(boxElement.name, closureElement); | |
162 closureElement.backendMembers = | |
163 closureElement.backendMembers.prepend(fieldElement); | |
164 data.capturedFieldMapping[fieldElement] = boxElement; | |
165 freeVariableMapping[boxElement] = fieldElement; | |
166 } | |
167 } | |
168 } | |
169 | |
170 void useLocal(Element element) { | |
171 // TODO(floitsch): replace this with a general solution. | |
172 Element functionElement = currentFunctionElement; | |
173 if (functionElement.kind === ElementKind.GENERATIVE_CONSTRUCTOR_BODY) { | |
174 ConstructorBodyElement body = functionElement; | |
175 functionElement = body.constructor; | |
176 } | |
177 // If the element is not declared in the current function and the element | |
178 // is not the closure itself we need to mark the element as free variable. | |
179 if (element.enclosingElement != functionElement && | |
180 element != functionElement) { | |
181 assert(closureData.freeVariableMapping[element] == null || | |
182 closureData.freeVariableMapping[element] == element); | |
183 closureData.freeVariableMapping[element] = element; | |
184 } else if (inTryCatchOrFinally) { | |
185 // Don't mark the this-element. This would complicate things in the | |
186 // builder. | |
187 if (element != closureData.thisElement) { | |
188 // TODO(ngeoffray): only do this if the variable is mutated. | |
189 closureData.usedVariablesInTry.add(element); | |
190 } | |
191 } | |
192 } | |
193 | |
194 void declareLocal(Element element) { | |
195 scopeVariables.add(element); | |
196 } | |
197 | |
198 visit(Node node) => node.accept(this); | |
199 | |
200 visitNode(Node node) => node.visitChildren(this); | |
201 | |
202 visitVariableDefinitions(VariableDefinitions node) { | |
203 for (Link<Node> link = node.definitions.nodes; | |
204 !link.isEmpty(); | |
205 link = link.tail) { | |
206 Node definition = link.head; | |
207 Element element = elements[definition]; | |
208 assert(element !== null); | |
209 declareLocal(element); | |
210 } | |
211 // We still need to visit the right-hand sides of the init-assignments. | |
212 // Simply visit all children. We will visit the locals again and make them | |
213 // used, but that should not be a problem. | |
214 node.visitChildren(this); | |
215 } | |
216 | |
217 visitIdentifier(Identifier node) { | |
218 if (node.isThis()) { | |
219 useLocal(closureData.thisElement); | |
220 } | |
221 node.visitChildren(this); | |
222 } | |
223 | |
224 visitSend(Send node) { | |
225 Element element = elements[node]; | |
226 if (Elements.isLocal(element)) { | |
227 useLocal(element); | |
228 } else if (node.receiver === null && | |
229 Elements.isInstanceSend(node, elements)) { | |
230 useLocal(closureData.thisElement); | |
231 } | |
232 node.visitChildren(this); | |
233 } | |
234 | |
235 // If variables that are declared in the [node] scope are captured and need | |
236 // to be boxed create a box-element and update the [capturingScopes] in the | |
237 // current [closureData]. | |
238 // The boxed variables are updated in the [capturedVariableMapping]. | |
239 void attachCapturedScopeVariables(Node node) { | |
240 Element box = null; | |
241 Map<Element, Element> scopeMapping = new Map<Element, Element>(); | |
242 for (Element element in scopeVariables) { | |
243 if (capturedVariableMapping.containsKey(element)) { | |
244 if (box == null) { | |
245 // TODO(floitsch): construct better box names. | |
246 SourceString boxName = new SourceString("box${boxCounter++}"); | |
247 box = new Element(boxName, | |
248 ElementKind.VARIABLE, | |
249 currentFunctionElement); | |
250 } | |
251 // TODO(floitsch): construct better boxed names. | |
252 String elementName = element.name.slowToString(); | |
253 // We are currently using the name in an HForeign which could replace | |
254 // "$X" with something else. | |
255 String escaped = elementName.replaceAll("\$", "_"); | |
256 SourceString boxedName = new SourceString("${escaped}_${boxCounter++}"); | |
257 Element boxed = new Element(boxedName, ElementKind.FIELD, box); | |
258 scopeMapping[element] = boxed; | |
259 capturedVariableMapping[element] = boxed; | |
260 } | |
261 } | |
262 if (!scopeMapping.isEmpty()) { | |
263 ClosureScope scope = new ClosureScope(box, scopeMapping); | |
264 closureData.capturingScopes[node] = scope; | |
265 } | |
266 } | |
267 | |
268 visitLoop(Loop node) { | |
269 List<Element> oldScopeVariables = scopeVariables; | |
270 scopeVariables = new List<Element>(); | |
271 node.visitChildren(this); | |
272 attachCapturedScopeVariables(node); | |
273 scopeVariables = oldScopeVariables; | |
274 } | |
275 | |
276 visitFor(For node) { | |
277 visitLoop(node); | |
278 // See if we have declared loop variables that need to be boxed. | |
279 if (node.initializer === null) return; | |
280 VariableDefinitions definitions = node.initializer.asVariableDefinitions(); | |
281 if (definitions == null) return; | |
282 ClosureScope scopeData = closureData.capturingScopes[node]; | |
283 if (scopeData === null) return; | |
284 List<Element> result = <Element>[]; | |
285 for (Link<Node> link = definitions.definitions.nodes; | |
286 !link.isEmpty(); | |
287 link = link.tail) { | |
288 Node definition = link.head; | |
289 Element element = elements[definition]; | |
290 if (capturedVariableMapping.containsKey(element)) { | |
291 result.add(element); | |
292 }; | |
293 } | |
294 scopeData.boxedLoopVariables = result; | |
295 } | |
296 | |
297 ClosureData globalizeClosure(FunctionExpression node) { | |
298 FunctionElement element = elements[node]; | |
299 ClassElement globalizedElement = | |
300 new ClosureClassElement(compiler, element.getCompilationUnit()); | |
301 FunctionElement callElement = | |
302 new FunctionElement.from(Namer.CLOSURE_INVOCATION_NAME, | |
303 element, | |
304 globalizedElement); | |
305 globalizedElement.backendMembers = | |
306 const EmptyLink<Element>().prepend(callElement); | |
307 // The nested function's 'this' is the same as the one for the outer | |
308 // function. It could be [null] if we are inside a static method. | |
309 Element thisElement = closureData.thisElement; | |
310 return new ClosureData(element, globalizedElement, | |
311 callElement, thisElement); | |
312 } | |
313 | |
314 visitFunctionExpression(FunctionExpression node) { | |
315 Element element = elements[node]; | |
316 if (element.kind === ElementKind.PARAMETER) { | |
317 // TODO(ahe): This is a hack. This method should *not* call | |
318 // visitChildren. | |
319 return node.name.accept(this); | |
320 } | |
321 bool isClosure = (closureData !== null); | |
322 | |
323 if (isClosure) closures.add(node); | |
324 | |
325 bool oldInsideClosure = insideClosure; | |
326 FunctionElement oldFunctionElement = currentFunctionElement; | |
327 ClosureData oldClosureData = closureData; | |
328 List<Element> oldScopeVariables = scopeVariables; | |
329 | |
330 | |
331 insideClosure = isClosure; | |
332 currentFunctionElement = elements[node]; | |
333 if (insideClosure) { | |
334 closureData = globalizeClosure(node); | |
335 } else { | |
336 Element thisElement = null; | |
337 // TODO(floitsch): we should not need to look for generative constructors. | |
338 // At the moment we store only one ClosureData for both the factory and | |
339 // the body. | |
340 if (element.isInstanceMember() || | |
341 element.kind == ElementKind.GENERATIVE_CONSTRUCTOR) { | |
342 // TODO(floitsch): currently all variables are considered to be | |
343 // declared in the GENERATIVE_CONSTRUCTOR. Including the 'this'. | |
344 Element thisEnclosingElement = element; | |
345 if (element.kind === ElementKind.GENERATIVE_CONSTRUCTOR_BODY) { | |
346 ConstructorBodyElement body = element; | |
347 thisEnclosingElement = body.constructor; | |
348 } | |
349 thisElement = new Element(const SourceString("this"), | |
350 ElementKind.PARAMETER, | |
351 thisEnclosingElement); | |
352 } | |
353 closureData = new ClosureData(null, null, null, thisElement); | |
354 } | |
355 scopeVariables = new List<Element>(); | |
356 | |
357 // TODO(floitsch): a named function is visible from inside itself. Add | |
358 // the element to the block. | |
359 | |
360 // We have to declare the implicit 'this' parameter. | |
361 if (!insideClosure && closureData.thisElement !== null) { | |
362 declareLocal(closureData.thisElement); | |
363 } | |
364 // If we are inside a named closure we have to declare ourselve. For | |
365 // simplicity we declare the local even if the closure does not have a name | |
366 // It will simply not be used. | |
367 if (insideClosure) { | |
368 declareLocal(element); | |
369 } | |
370 | |
371 // TODO(ahe): This is problematic. The backend should not repeat | |
372 // the work of the resolver. It is the resolver's job to create | |
373 // parameters, etc. Other phases should only visit statements. | |
374 // TODO(floitsch): we avoid visiting the initializers on purpose so that we | |
375 // get an error-message later in the builder. | |
376 if (node.parameters !== null) node.parameters.accept(this); | |
377 if (node.body !== null) node.body.accept(this); | |
378 | |
379 attachCapturedScopeVariables(node); | |
380 | |
381 closureDataCache[node] = closureData; | |
382 | |
383 ClosureData savedClosureData = closureData; | |
384 bool savedInsideClosure = insideClosure; | |
385 | |
386 // Restore old values. | |
387 scopeVariables = oldScopeVariables; | |
388 insideClosure = oldInsideClosure; | |
389 closureData = oldClosureData; | |
390 currentFunctionElement = oldFunctionElement; | |
391 | |
392 // Mark all free variables as captured and use them in the outer function. | |
393 List<Element> freeVariables = | |
394 savedClosureData.freeVariableMapping.getKeys(); | |
395 assert(freeVariables.isEmpty() || savedInsideClosure); | |
396 for (Element freeElement in freeVariables) { | |
397 if (capturedVariableMapping[freeElement] != null && | |
398 capturedVariableMapping[freeElement] != freeElement) { | |
399 compiler.internalError('In closure analyzer', node: node); | |
400 } | |
401 capturedVariableMapping[freeElement] = freeElement; | |
402 useLocal(freeElement); | |
403 } | |
404 } | |
405 | |
406 visitFunctionDeclaration(FunctionDeclaration node) { | |
407 node.visitChildren(this); | |
408 declareLocal(elements[node]); | |
409 } | |
410 | |
411 visitTryStatement(TryStatement node) { | |
412 // TODO(ngeoffray): implement finer grain state. | |
413 inTryCatchOrFinally = true; | |
414 node.visitChildren(this); | |
415 inTryCatchOrFinally = false; | |
416 } | |
417 } | |
OLD | NEW |