Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(294)

Side by Side Diff: lib/compiler/implementation/ssa/closure.dart

Issue 10855146: Make the closure-to-class translator more accessible. It can now be (Closed) Base URL: https://dart.googlecode.com/svn/branches/bleeding_edge/dart
Patch Set: Address comments Created 8 years, 4 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch | Annotate | Revision Log
« no previous file with comments | « lib/compiler/implementation/ssa/builder.dart ('k') | lib/compiler/implementation/ssa/ssa.dart » ('j') | no next file with comments »
Toggle Intra-line Diffs ('i') | Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
OLDNEW
(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(SourceString name,
17 Compiler compiler,
18 Element enclosingElement)
19 : super(name,
20 enclosingElement,
21 // By assigning a fresh class-id we make sure that the hashcode
22 // is unique, but also emit closure classes after all other
23 // classes (since the emitter sorts classes by their id).
24 compiler.getNextFreeClassId()) {
25 // We assign twice to [supertypeLoadState] as it contains asserts
26 // which enforce certain sequence of transitions.
27 supertypeLoadState = ClassElement.STATE_STARTED;
28 supertypeLoadState = ClassElement.STATE_DONE;
29 // Same as for [supertypeLoadState] above.
30 resolutionState = ClassElement.STATE_STARTED;
31 resolutionState = ClassElement.STATE_DONE;
32 compiler.closureClass.ensureResolved(compiler);
33 supertype = compiler.closureClass.computeType(compiler);
34 interfaces = const EmptyLink<Type>();
35 }
36 bool isClosure() => true;
37 }
38
39 class BoxElement extends Element {
40 BoxElement(SourceString name, Element enclosingElement)
41 : super(name, ElementKind.VARIABLE, enclosingElement);
42 }
43
44 class ThisElement extends Element {
45 ThisElement(Element enclosing)
46 : super(const SourceString('this'), ElementKind.PARAMETER, enclosing);
47
48 bool isAssignable() => false;
49 }
50
51 // The box-element for a scope, and the captured variables that need to be
52 // stored in the box.
53 class ClosureScope {
54 Element boxElement;
55 Map<Element, Element> capturedVariableMapping;
56 // If the scope is attached to a [For] contains the variables that are
57 // declared in the initializer of the [For] and that need to be boxed.
58 // Otherwise contains the empty List.
59 List<Element> boxedLoopVariables;
60
61 ClosureScope(this.boxElement, this.capturedVariableMapping)
62 : boxedLoopVariables = const <Element>[];
63
64 bool hasBoxedLoopVariables() => !boxedLoopVariables.isEmpty();
65 }
66
67 class ClosureData {
68 // The closure's element before any translation. Will be null for methods.
69 final FunctionElement closureElement;
70 // The closureClassElement will be null for methods that are not local
71 // closures.
72 final ClassElement closureClassElement;
73 // The callElement will be null for methods that are not local closures.
74 final FunctionElement callElement;
75 // The [thisElement] makes handling 'this' easier by treating it like any
76 // other argument. It is only set for instance-members.
77 final ThisElement thisElement;
78
79 // Maps free locals, arguments and function elements to their captured
80 // copies.
81 final Map<Element, Element> freeVariableMapping;
82 // Maps closure-fields to their captured elements. This is somehow the inverse
83 // mapping of [freeVariableMapping], but whereas [freeVariableMapping] does
84 // not deal with boxes, here we map instance-fields (which might represent
85 // boxes) to their boxElement.
86 final Map<Element, Element> capturedFieldMapping;
87
88 // Maps scopes ([Loop] and [FunctionExpression] nodes) to their
89 // [ClosureScope] which contains their box and the
90 // captured variables that are stored in the box.
91 // This map will be empty if the method/closure of this [ClosureData] does not
92 // contain any nested closure.
93 final Map<Node, ClosureScope> capturingScopes;
94
95 final Set<Element> usedVariablesInTry;
96
97 ClosureData(this.closureElement,
98 this.closureClassElement,
99 this.callElement,
100 this.thisElement)
101 : this.freeVariableMapping = new Map<Element, Element>(),
102 this.capturedFieldMapping = new Map<Element, Element>(),
103 this.capturingScopes = new Map<Node, ClosureScope>(),
104 this.usedVariablesInTry = new Set<Element>();
105
106 bool isClosure() => closureElement !== null;
107 }
108
109 class ClosureTranslator extends AbstractVisitor {
110 final SsaBuilder builder;
111 final TreeElements elements;
112 int closureFieldCounter = 0;
113 bool inTryStatement = false;
114 final Map<Node, ClosureData> closureDataCache;
115
116 // Map of captured variables. Initially they will map to themselves. If
117 // a variable needs to be boxed then the scope declaring the variable
118 // will update this mapping.
119 Map<Element, Element> capturedVariableMapping;
120 // List of encountered closures.
121 List<FunctionExpression> closures;
122
123 // The variables that have been declared in the current scope.
124 List<Element> scopeVariables;
125
126 // Keep track of the mutated variables so that we don't need to box
127 // non-mutated variables.
128 Set<Element> mutatedVariables;
129
130 FunctionElement currentFunctionElement;
131 // The closureData of the currentFunctionElement.
132 ClosureData closureData;
133
134 bool insideClosure = false;
135
136 Compiler get compiler() => builder.compiler;
137
138 ClosureTranslator(SsaBuilder builder)
139 : this.builder = builder,
140 this.elements = builder.elements,
141 capturedVariableMapping = new Map<Element, Element>(),
142 closures = <FunctionExpression>[],
143 mutatedVariables = new Set<Element>(),
144 this.closureDataCache = builder.builder.closureDataCache;
145
146 ClosureData translate(Node node) {
147 // Closures have already been analyzed when visiting the surrounding
148 // method/function. This also shortcuts for bailout functions.
149 ClosureData cached = closureDataCache[node];
150 if (cached !== null) return cached;
151
152 visit(node);
153 // When variables need to be boxed their [capturedVariableMapping] is
154 // updated, but we delay updating the similar freeVariableMapping in the
155 // closure datas that capture these variables.
156 // The closures don't have their fields (in the closure class) set, either.
157 updateClosures();
158
159 return closureDataCache[node];
160 }
161
162 // This function runs through all of the existing closures and updates their
163 // free variables to the boxed value. It also adds the field-elements to the
164 // class representing the closure. At the same time it fills the
165 // [capturedFieldMapping].
166 void updateClosures() {
167 for (FunctionExpression closure in closures) {
168 // The captured variables that need to be stored in a field of the closure
169 // class.
170 Set<Element> fieldCaptures = new Set<Element>();
171 ClosureData data = closureDataCache[closure];
172 Map<Element, Element> freeVariableMapping = data.freeVariableMapping;
173 // We get a copy of the keys and iterate over it, to avoid modifications
174 // to the map while iterating over it.
175 freeVariableMapping.getKeys().forEach((Element fromElement) {
176 assert(fromElement == freeVariableMapping[fromElement]);
177 Element updatedElement = capturedVariableMapping[fromElement];
178 assert(updatedElement !== null);
179 if (fromElement == updatedElement) {
180 assert(freeVariableMapping[fromElement] == updatedElement);
181 assert(Elements.isLocal(updatedElement));
182 // The variable has not been boxed.
183 fieldCaptures.add(updatedElement);
184 } else {
185 // A boxed element.
186 freeVariableMapping[fromElement] = updatedElement;
187 Element boxElement = updatedElement.enclosingElement;
188 assert(boxElement.kind == ElementKind.VARIABLE);
189 fieldCaptures.add(boxElement);
190 }
191 });
192 ClassElement closureElement = data.closureClassElement;
193 assert(closureElement != null || fieldCaptures.isEmpty());
194 for (Element capturedElement in fieldCaptures) {
195 SourceString name;
196 if (capturedElement is BoxElement) {
197 // The name is already mangled.
198 name = capturedElement.name;
199 } else {
200 int id = closureFieldCounter++;
201 name = new SourceString("${capturedElement.name.slowToString()}_$id");
202 }
203 Element fieldElement = new ClosureFieldElement(name, closureElement);
204 closureElement.backendMembers =
205 closureElement.backendMembers.prepend(fieldElement);
206 data.capturedFieldMapping[fieldElement] = capturedElement;
207 freeVariableMapping[capturedElement] = fieldElement;
208 }
209 }
210 }
211
212 void useLocal(Element element) {
213 // TODO(floitsch): replace this with a general solution.
214 Element functionElement = currentFunctionElement;
215 if (functionElement.kind === ElementKind.GENERATIVE_CONSTRUCTOR_BODY) {
216 ConstructorBodyElement body = functionElement;
217 functionElement = body.constructor;
218 }
219 // If the element is not declared in the current function and the element
220 // is not the closure itself we need to mark the element as free variable.
221 if (element.enclosingElement != functionElement &&
222 element != functionElement) {
223 assert(closureData.freeVariableMapping[element] == null ||
224 closureData.freeVariableMapping[element] == element);
225 closureData.freeVariableMapping[element] = element;
226 } else if (inTryStatement) {
227 // Don't mark the this-element. This would complicate things in the
228 // builder.
229 if (element != closureData.thisElement) {
230 // TODO(ngeoffray): only do this if the variable is mutated.
231 closureData.usedVariablesInTry.add(element);
232 }
233 }
234 }
235
236 void declareLocal(Element element) {
237 scopeVariables.add(element);
238 }
239
240 visit(Node node) => node.accept(this);
241
242 visitNode(Node node) => node.visitChildren(this);
243
244 visitVariableDefinitions(VariableDefinitions node) {
245 for (Link<Node> link = node.definitions.nodes;
246 !link.isEmpty();
247 link = link.tail) {
248 Node definition = link.head;
249 Element element = elements[definition];
250 assert(element !== null);
251 declareLocal(element);
252 // We still need to visit the right-hand sides of the init-assignments.
253 // For SendSets don't visit the left again. Otherwise it would be marked
254 // as mutated.
255 if (definition is SendSet) {
256 SendSet assignment = definition;
257 visit(assignment.argumentsNode);
258 } else {
259 visit(definition);
260 }
261 }
262 }
263
264 visitIdentifier(Identifier node) {
265 if (node.isThis()) {
266 useLocal(closureData.thisElement);
267 }
268 node.visitChildren(this);
269 }
270
271 visitSend(Send node) {
272 Element element = elements[node];
273 if (Elements.isLocal(element)) {
274 useLocal(element);
275 } else if (node.receiver === null &&
276 Elements.isInstanceSend(node, elements)) {
277 useLocal(closureData.thisElement);
278 } else if (node.isSuperCall) {
279 useLocal(closureData.thisElement);
280 }
281 node.visitChildren(this);
282 }
283
284 visitSendSet(SendSet node) {
285 Element element = elements[node];
286 if (Elements.isLocal(element)) {
287 mutatedVariables.add(element);
288 }
289 super.visitSendSet(node);
290 }
291
292 // If variables that are declared in the [node] scope are captured and need
293 // to be boxed create a box-element and update the [capturingScopes] in the
294 // current [closureData].
295 // The boxed variables are updated in the [capturedVariableMapping].
296 void attachCapturedScopeVariables(Node node) {
297 Element box = null;
298 Map<Element, Element> scopeMapping = new Map<Element, Element>();
299 for (Element element in scopeVariables) {
300 // No need to box non-assignable elements.
301 if (!element.isAssignable()) continue;
302 if (!mutatedVariables.contains(element)) continue;
303 if (capturedVariableMapping.containsKey(element)) {
304 if (box == null) {
305 // TODO(floitsch): construct better box names.
306 SourceString boxName =
307 new SourceString("box_${closureFieldCounter++}");
308 box = new BoxElement(boxName, currentFunctionElement);
309 }
310 // TODO(floitsch): construct better boxed names.
311 String elementName = element.name.slowToString();
312 // We are currently using the name in an HForeign which could replace
313 // "$X" with something else.
314 String escaped = elementName.replaceAll("\$", "_");
315 SourceString boxedName =
316 new SourceString("${escaped}_${closureFieldCounter++}");
317 Element boxed = new Element(boxedName, ElementKind.FIELD, box);
318 scopeMapping[element] = boxed;
319 capturedVariableMapping[element] = boxed;
320 }
321 }
322 if (!scopeMapping.isEmpty()) {
323 ClosureScope scope = new ClosureScope(box, scopeMapping);
324 closureData.capturingScopes[node] = scope;
325 }
326 }
327
328 void inNewScope(Node node, Function action) {
329 List<Element> oldScopeVariables = scopeVariables;
330 scopeVariables = new List<Element>();
331 action();
332 attachCapturedScopeVariables(node);
333 for (Element element in scopeVariables) {
334 mutatedVariables.remove(element);
335 }
336 scopeVariables = oldScopeVariables;
337 }
338
339 visitLoop(Loop node) {
340 inNewScope(node, () {
341 node.visitChildren(this);
342 });
343 }
344
345 visitFor(For node) {
346 visitLoop(node);
347 // See if we have declared loop variables that need to be boxed.
348 if (node.initializer === null) return;
349 VariableDefinitions definitions = node.initializer.asVariableDefinitions();
350 if (definitions == null) return;
351 ClosureScope scopeData = closureData.capturingScopes[node];
352 if (scopeData === null) return;
353 List<Element> result = <Element>[];
354 for (Link<Node> link = definitions.definitions.nodes;
355 !link.isEmpty();
356 link = link.tail) {
357 Node definition = link.head;
358 Element element = elements[definition];
359 if (capturedVariableMapping.containsKey(element)) {
360 result.add(element);
361 };
362 }
363 scopeData.boxedLoopVariables = result;
364 }
365
366 ClosureData globalizeClosure(FunctionExpression node, Element element) {
367 SourceString closureName =
368 new SourceString(compiler.namer.closureName(element));
369 ClassElement globalizedElement = new ClosureClassElement(
370 closureName, compiler, element.getCompilationUnit());
371 FunctionElement callElement =
372 new FunctionElement.from(compiler.namer.CLOSURE_INVOCATION_NAME,
373 element,
374 globalizedElement);
375 globalizedElement.backendMembers =
376 const EmptyLink<Element>().prepend(callElement);
377 // The nested function's 'this' is the same as the one for the outer
378 // function. It could be [null] if we are inside a static method.
379 Element thisElement = closureData.thisElement;
380 return new ClosureData(element, globalizedElement,
381 callElement, thisElement);
382 }
383
384 visitFunctionExpression(FunctionExpression node) {
385 Element element = elements[node];
386 if (element.kind === ElementKind.PARAMETER) {
387 // TODO(ahe): This is a hack. This method should *not* call
388 // visitChildren.
389 return node.name.accept(this);
390 }
391 bool isClosure = (closureData !== null);
392
393 if (isClosure) closures.add(node);
394
395 bool oldInsideClosure = insideClosure;
396 FunctionElement oldFunctionElement = currentFunctionElement;
397 ClosureData oldClosureData = closureData;
398
399 insideClosure = isClosure;
400 currentFunctionElement = elements[node];
401 if (insideClosure) {
402 closureData = globalizeClosure(node, element);
403 } else {
404 Element thisElement = null;
405 // TODO(floitsch): we should not need to look for generative constructors.
406 // At the moment we store only one ClosureData for both the factory and
407 // the body.
408 if (element.isInstanceMember() ||
409 element.kind == ElementKind.GENERATIVE_CONSTRUCTOR) {
410 // TODO(floitsch): currently all variables are considered to be
411 // declared in the GENERATIVE_CONSTRUCTOR. Including the 'this'.
412 Element thisEnclosingElement = element;
413 if (element.kind === ElementKind.GENERATIVE_CONSTRUCTOR_BODY) {
414 ConstructorBodyElement body = element;
415 thisEnclosingElement = body.constructor;
416 }
417 thisElement = new ThisElement(thisEnclosingElement);
418 }
419 closureData = new ClosureData(null, null, null, thisElement);
420 }
421
422 inNewScope(node, () {
423 // We have to declare the implicit 'this' parameter.
424 if (!insideClosure && closureData.thisElement !== null) {
425 declareLocal(closureData.thisElement);
426 }
427 // If we are inside a named closure we have to declare ourselve. For
428 // simplicity we declare the local even if the closure does not have a
429 // name.
430 // It will simply not be used.
431 if (insideClosure) {
432 declareLocal(element);
433 }
434
435 // TODO(ahe): This is problematic. The backend should not repeat
436 // the work of the resolver. It is the resolver's job to create
437 // parameters, etc. Other phases should only visit statements.
438 // TODO(floitsch): we avoid visiting the initializers on purpose so that
439 // we get an error-message later in the builder.
440 if (node.parameters !== null) node.parameters.accept(this);
441 if (node.body !== null) node.body.accept(this);
442 });
443
444 closureDataCache[node] = closureData;
445
446 ClosureData savedClosureData = closureData;
447 bool savedInsideClosure = insideClosure;
448
449 // Restore old values.
450 insideClosure = oldInsideClosure;
451 closureData = oldClosureData;
452 currentFunctionElement = oldFunctionElement;
453
454 // Mark all free variables as captured and use them in the outer function.
455 List<Element> freeVariables =
456 savedClosureData.freeVariableMapping.getKeys();
457 assert(freeVariables.isEmpty() || savedInsideClosure);
458 for (Element freeElement in freeVariables) {
459 if (capturedVariableMapping[freeElement] != null &&
460 capturedVariableMapping[freeElement] != freeElement) {
461 compiler.internalError('In closure analyzer', node: node);
462 }
463 capturedVariableMapping[freeElement] = freeElement;
464 useLocal(freeElement);
465 }
466 }
467
468 visitFunctionDeclaration(FunctionDeclaration node) {
469 node.visitChildren(this);
470 declareLocal(elements[node]);
471 }
472
473 visitTryStatement(TryStatement node) {
474 // TODO(ngeoffray): implement finer grain state.
475 bool oldInTryStatement = inTryStatement;
476 inTryStatement = true;
477 node.visitChildren(this);
478 inTryStatement = oldInTryStatement;
479 }
480 }
OLDNEW
« no previous file with comments | « lib/compiler/implementation/ssa/builder.dart ('k') | lib/compiler/implementation/ssa/ssa.dart » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698