| OLD | NEW |
| 1 // Copyright (c) 2012, the Dart project authors. Please see the AUTHORS file | 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 | 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 #library("closureToClassMapper"); |
| 6 |
| 7 #import("elements/elements.dart"); |
| 8 #import("leg.dart"); |
| 9 #import("scanner/scannerlib.dart"); |
| 10 #import("tree/tree.dart"); |
| 11 #import("util/util.dart"); |
| 12 |
| 13 class ClosureTask extends CompilerTask { |
| 14 Map<Node, ClosureClassMap> closureMappingCache; |
| 15 ClosureTask(Compiler compiler) |
| 16 : closureMappingCache = new Map<Node, ClosureClassMap>(), |
| 17 super(compiler); |
| 18 |
| 19 String get name() => "Closure Simplifier"; |
| 20 |
| 21 ClosureClassMap computeClosureToClassMapping(FunctionExpression node, |
| 22 TreeElements elements) { |
| 23 return measure(() { |
| 24 ClosureClassMap cached = closureMappingCache[node]; |
| 25 if (cached !== null) return cached; |
| 26 |
| 27 ClosureTranslator translator = |
| 28 new ClosureTranslator(compiler, elements, closureMappingCache); |
| 29 // The translator will store the computed closure-mappings inside the |
| 30 // cache. One for given method and one for each nested closure. |
| 31 translator.translate(node); |
| 32 assert(closureMappingCache[node] != null); |
| 33 return closureMappingCache[node]; |
| 34 }); |
| 35 } |
| 36 |
| 37 ClosureClassMap getMappingForNestedFunction(FunctionExpression node) { |
| 38 return measure(() { |
| 39 ClosureClassMap nestedClosureData = closureMappingCache[node]; |
| 40 if (nestedClosureData === null) { |
| 41 // TODO(floitsch): we can only assume that the reason for not having a |
| 42 // closure data here is, because the function is inside an initializer. |
| 43 compiler.unimplemented("Closures inside initializers", node: node); |
| 44 } |
| 45 return nestedClosureData; |
| 46 }); |
| 47 } |
| 48 } |
| 49 |
| 5 class ClosureFieldElement extends Element { | 50 class ClosureFieldElement extends Element { |
| 6 ClosureFieldElement(SourceString name, ClassElement enclosing) | 51 ClosureFieldElement(SourceString name, ClassElement enclosing) |
| 7 : super(name, ElementKind.FIELD, enclosing); | 52 : super(name, ElementKind.FIELD, enclosing); |
| 8 | 53 |
| 9 bool isInstanceMember() => true; | 54 bool isInstanceMember() => true; |
| 10 bool isAssignable() => false; | 55 bool isAssignable() => false; |
| 11 | 56 |
| 12 String toString() => "ClosureFieldElement($name)"; | 57 String toString() => "ClosureFieldElement($name)"; |
| 13 } | 58 } |
| 14 | 59 |
| (...skipping 42 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 57 // declared in the initializer of the [For] and that need to be boxed. | 102 // declared in the initializer of the [For] and that need to be boxed. |
| 58 // Otherwise contains the empty List. | 103 // Otherwise contains the empty List. |
| 59 List<Element> boxedLoopVariables; | 104 List<Element> boxedLoopVariables; |
| 60 | 105 |
| 61 ClosureScope(this.boxElement, this.capturedVariableMapping) | 106 ClosureScope(this.boxElement, this.capturedVariableMapping) |
| 62 : boxedLoopVariables = const <Element>[]; | 107 : boxedLoopVariables = const <Element>[]; |
| 63 | 108 |
| 64 bool hasBoxedLoopVariables() => !boxedLoopVariables.isEmpty(); | 109 bool hasBoxedLoopVariables() => !boxedLoopVariables.isEmpty(); |
| 65 } | 110 } |
| 66 | 111 |
| 67 class ClosureData { | 112 class ClosureClassMap { |
| 68 // The closure's element before any translation. Will be null for methods. | 113 // The closure's element before any translation. Will be null for methods. |
| 69 final FunctionElement closureElement; | 114 final FunctionElement closureElement; |
| 70 // The closureClassElement will be null for methods that are not local | 115 // The closureClassElement will be null for methods that are not local |
| 71 // closures. | 116 // closures. |
| 72 final ClassElement closureClassElement; | 117 final ClassElement closureClassElement; |
| 73 // The callElement will be null for methods that are not local closures. | 118 // The callElement will be null for methods that are not local closures. |
| 74 final FunctionElement callElement; | 119 final FunctionElement callElement; |
| 75 // The [thisElement] makes handling 'this' easier by treating it like any | 120 // The [thisElement] makes handling 'this' easier by treating it like any |
| 76 // other argument. It is only set for instance-members. | 121 // other argument. It is only set for instance-members. |
| 77 final ThisElement thisElement; | 122 final ThisElement thisElement; |
| 78 | 123 |
| 79 // Maps free locals, arguments and function elements to their captured | 124 // Maps free locals, arguments and function elements to their captured |
| 80 // copies. | 125 // copies. |
| 81 final Map<Element, Element> freeVariableMapping; | 126 final Map<Element, Element> freeVariableMapping; |
| 82 // Maps closure-fields to their captured elements. This is somehow the inverse | 127 // Maps closure-fields to their captured elements. This is somehow the inverse |
| 83 // mapping of [freeVariableMapping], but whereas [freeVariableMapping] does | 128 // mapping of [freeVariableMapping], but whereas [freeVariableMapping] does |
| 84 // not deal with boxes, here we map instance-fields (which might represent | 129 // not deal with boxes, here we map instance-fields (which might represent |
| 85 // boxes) to their boxElement. | 130 // boxes) to their boxElement. |
| 86 final Map<Element, Element> capturedFieldMapping; | 131 final Map<Element, Element> capturedFieldMapping; |
| 87 | 132 |
| 88 // Maps scopes ([Loop] and [FunctionExpression] nodes) to their | 133 // Maps scopes ([Loop] and [FunctionExpression] nodes) to their |
| 89 // [ClosureScope] which contains their box and the | 134 // [ClosureScope] which contains their box and the |
| 90 // captured variables that are stored in the box. | 135 // captured variables that are stored in the box. |
| 91 // This map will be empty if the method/closure of this [ClosureData] does not | 136 // This map will be empty if the method/closure of this [ClosureData] does not |
| 92 // contain any nested closure. | 137 // contain any nested closure. |
| 93 final Map<Node, ClosureScope> capturingScopes; | 138 final Map<Node, ClosureScope> capturingScopes; |
| 94 | 139 |
| 95 final Set<Element> usedVariablesInTry; | 140 final Set<Element> usedVariablesInTry; |
| 96 | 141 |
| 97 ClosureData(this.closureElement, | 142 ClosureClassMap(this.closureElement, |
| 98 this.closureClassElement, | 143 this.closureClassElement, |
| 99 this.callElement, | 144 this.callElement, |
| 100 this.thisElement) | 145 this.thisElement) |
| 101 : this.freeVariableMapping = new Map<Element, Element>(), | 146 : this.freeVariableMapping = new Map<Element, Element>(), |
| 102 this.capturedFieldMapping = new Map<Element, Element>(), | 147 this.capturedFieldMapping = new Map<Element, Element>(), |
| 103 this.capturingScopes = new Map<Node, ClosureScope>(), | 148 this.capturingScopes = new Map<Node, ClosureScope>(), |
| 104 this.usedVariablesInTry = new Set<Element>(); | 149 this.usedVariablesInTry = new Set<Element>(); |
| 105 | 150 |
| 106 bool isClosure() => closureElement !== null; | 151 bool isClosure() => closureElement !== null; |
| 107 } | 152 } |
| 108 | 153 |
| 109 class ClosureTranslator extends AbstractVisitor { | 154 class ClosureTranslator extends AbstractVisitor { |
| 110 final SsaBuilder builder; | 155 final Compiler compiler; |
| 111 final TreeElements elements; | 156 final TreeElements elements; |
| 112 int closureFieldCounter = 0; | 157 int closureFieldCounter = 0; |
| 113 bool inTryStatement = false; | 158 bool inTryStatement = false; |
| 114 final Map<Node, ClosureData> closureDataCache; | 159 final Map<Node, ClosureClassMap> closureMappingCache; |
| 115 | 160 |
| 116 // Map of captured variables. Initially they will map to themselves. If | 161 // Map of captured variables. Initially they will map to themselves. If |
| 117 // a variable needs to be boxed then the scope declaring the variable | 162 // a variable needs to be boxed then the scope declaring the variable |
| 118 // will update this mapping. | 163 // will update this mapping. |
| 119 Map<Element, Element> capturedVariableMapping; | 164 Map<Element, Element> capturedVariableMapping; |
| 120 // List of encountered closures. | 165 // List of encountered closures. |
| 121 List<FunctionExpression> closures; | 166 List<FunctionExpression> closures; |
| 122 | 167 |
| 123 // The variables that have been declared in the current scope. | 168 // The variables that have been declared in the current scope. |
| 124 List<Element> scopeVariables; | 169 List<Element> scopeVariables; |
| 125 | 170 |
| 126 // Keep track of the mutated variables so that we don't need to box | 171 // Keep track of the mutated variables so that we don't need to box |
| 127 // non-mutated variables. | 172 // non-mutated variables. |
| 128 Set<Element> mutatedVariables; | 173 Set<Element> mutatedVariables; |
| 129 | 174 |
| 130 FunctionElement currentFunctionElement; | 175 FunctionElement currentFunctionElement; |
| 131 // The closureData of the currentFunctionElement. | 176 // The closureData of the currentFunctionElement. |
| 132 ClosureData closureData; | 177 ClosureClassMap closureData; |
| 133 | 178 |
| 134 bool insideClosure = false; | 179 bool insideClosure = false; |
| 135 | 180 |
| 136 Compiler get compiler() => builder.compiler; | 181 ClosureTranslator(this.compiler, this.elements, this.closureMappingCache) |
| 182 : capturedVariableMapping = new Map<Element, Element>(), |
| 183 closures = <FunctionExpression>[], |
| 184 mutatedVariables = new Set<Element>(); |
| 137 | 185 |
| 138 ClosureTranslator(SsaBuilder builder) | 186 void translate(Node node) { |
| 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); | 187 visit(node); |
| 153 // When variables need to be boxed their [capturedVariableMapping] is | 188 // When variables need to be boxed their [capturedVariableMapping] is |
| 154 // updated, but we delay updating the similar freeVariableMapping in the | 189 // updated, but we delay updating the similar freeVariableMapping in the |
| 155 // closure datas that capture these variables. | 190 // closure datas that capture these variables. |
| 156 // The closures don't have their fields (in the closure class) set, either. | 191 // The closures don't have their fields (in the closure class) set, either. |
| 157 updateClosures(); | 192 updateClosures(); |
| 158 | |
| 159 return closureDataCache[node]; | |
| 160 } | 193 } |
| 161 | 194 |
| 162 // This function runs through all of the existing closures and updates their | 195 // 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 | 196 // 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 | 197 // class representing the closure. At the same time it fills the |
| 165 // [capturedFieldMapping]. | 198 // [capturedFieldMapping]. |
| 166 void updateClosures() { | 199 void updateClosures() { |
| 167 for (FunctionExpression closure in closures) { | 200 for (FunctionExpression closure in closures) { |
| 168 // The captured variables that need to be stored in a field of the closure | 201 // The captured variables that need to be stored in a field of the closure |
| 169 // class. | 202 // class. |
| 170 Set<Element> fieldCaptures = new Set<Element>(); | 203 Set<Element> fieldCaptures = new Set<Element>(); |
| 171 ClosureData data = closureDataCache[closure]; | 204 ClosureClassMap data = closureMappingCache[closure]; |
| 172 Map<Element, Element> freeVariableMapping = data.freeVariableMapping; | 205 Map<Element, Element> freeVariableMapping = data.freeVariableMapping; |
| 173 // We get a copy of the keys and iterate over it, to avoid modifications | 206 // We get a copy of the keys and iterate over it, to avoid modifications |
| 174 // to the map while iterating over it. | 207 // to the map while iterating over it. |
| 175 freeVariableMapping.getKeys().forEach((Element fromElement) { | 208 freeVariableMapping.getKeys().forEach((Element fromElement) { |
| 176 assert(fromElement == freeVariableMapping[fromElement]); | 209 assert(fromElement == freeVariableMapping[fromElement]); |
| 177 Element updatedElement = capturedVariableMapping[fromElement]; | 210 Element updatedElement = capturedVariableMapping[fromElement]; |
| 178 assert(updatedElement !== null); | 211 assert(updatedElement !== null); |
| 179 if (fromElement == updatedElement) { | 212 if (fromElement == updatedElement) { |
| 180 assert(freeVariableMapping[fromElement] == updatedElement); | 213 assert(freeVariableMapping[fromElement] == updatedElement); |
| 181 assert(Elements.isLocal(updatedElement)); | 214 assert(Elements.isLocal(updatedElement)); |
| (...skipping 174 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 356 link = link.tail) { | 389 link = link.tail) { |
| 357 Node definition = link.head; | 390 Node definition = link.head; |
| 358 Element element = elements[definition]; | 391 Element element = elements[definition]; |
| 359 if (capturedVariableMapping.containsKey(element)) { | 392 if (capturedVariableMapping.containsKey(element)) { |
| 360 result.add(element); | 393 result.add(element); |
| 361 }; | 394 }; |
| 362 } | 395 } |
| 363 scopeData.boxedLoopVariables = result; | 396 scopeData.boxedLoopVariables = result; |
| 364 } | 397 } |
| 365 | 398 |
| 366 ClosureData globalizeClosure(FunctionExpression node, Element element) { | 399 ClosureClassMap globalizeClosure(FunctionExpression node, Element element) { |
| 367 SourceString closureName = | 400 SourceString closureName = |
| 368 new SourceString(compiler.namer.closureName(element)); | 401 new SourceString(compiler.namer.closureName(element)); |
| 369 ClassElement globalizedElement = new ClosureClassElement( | 402 ClassElement globalizedElement = new ClosureClassElement( |
| 370 closureName, compiler, element.getCompilationUnit()); | 403 closureName, compiler, element.getCompilationUnit()); |
| 371 FunctionElement callElement = | 404 FunctionElement callElement = |
| 372 new FunctionElement.from(compiler.namer.CLOSURE_INVOCATION_NAME, | 405 new FunctionElement.from(compiler.namer.CLOSURE_INVOCATION_NAME, |
| 373 element, | 406 element, |
| 374 globalizedElement); | 407 globalizedElement); |
| 375 globalizedElement.backendMembers = | 408 globalizedElement.backendMembers = |
| 376 const EmptyLink<Element>().prepend(callElement); | 409 const EmptyLink<Element>().prepend(callElement); |
| 377 // The nested function's 'this' is the same as the one for the outer | 410 // 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. | 411 // function. It could be [null] if we are inside a static method. |
| 379 Element thisElement = closureData.thisElement; | 412 Element thisElement = closureData.thisElement; |
| 380 return new ClosureData(element, globalizedElement, | 413 return new ClosureClassMap(element, globalizedElement, |
| 381 callElement, thisElement); | 414 callElement, thisElement); |
| 382 } | 415 } |
| 383 | 416 |
| 384 visitFunctionExpression(FunctionExpression node) { | 417 visitFunctionExpression(FunctionExpression node) { |
| 385 Element element = elements[node]; | 418 Element element = elements[node]; |
| 386 if (element.kind === ElementKind.PARAMETER) { | 419 if (element.kind === ElementKind.PARAMETER) { |
| 387 // TODO(ahe): This is a hack. This method should *not* call | 420 // TODO(ahe): This is a hack. This method should *not* call |
| 388 // visitChildren. | 421 // visitChildren. |
| 389 return node.name.accept(this); | 422 return node.name.accept(this); |
| 390 } | 423 } |
| 391 bool isClosure = (closureData !== null); | 424 bool isClosure = (closureData !== null); |
| 392 | 425 |
| 393 if (isClosure) closures.add(node); | 426 if (isClosure) closures.add(node); |
| 394 | 427 |
| 395 bool oldInsideClosure = insideClosure; | 428 bool oldInsideClosure = insideClosure; |
| 396 FunctionElement oldFunctionElement = currentFunctionElement; | 429 FunctionElement oldFunctionElement = currentFunctionElement; |
| 397 ClosureData oldClosureData = closureData; | 430 ClosureClassMap oldClosureData = closureData; |
| 398 | 431 |
| 399 insideClosure = isClosure; | 432 insideClosure = isClosure; |
| 400 currentFunctionElement = elements[node]; | 433 currentFunctionElement = elements[node]; |
| 401 if (insideClosure) { | 434 if (insideClosure) { |
| 402 closureData = globalizeClosure(node, element); | 435 closureData = globalizeClosure(node, element); |
| 403 } else { | 436 } else { |
| 404 Element thisElement = null; | 437 Element thisElement = null; |
| 405 // TODO(floitsch): we should not need to look for generative constructors. | 438 // 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 | 439 // At the moment we store only one ClosureData for both the factory and |
| 407 // the body. | 440 // the body. |
| 408 if (element.isInstanceMember() || | 441 if (element.isInstanceMember() || |
| 409 element.kind == ElementKind.GENERATIVE_CONSTRUCTOR) { | 442 element.kind == ElementKind.GENERATIVE_CONSTRUCTOR) { |
| 410 // TODO(floitsch): currently all variables are considered to be | 443 // TODO(floitsch): currently all variables are considered to be |
| 411 // declared in the GENERATIVE_CONSTRUCTOR. Including the 'this'. | 444 // declared in the GENERATIVE_CONSTRUCTOR. Including the 'this'. |
| 412 Element thisEnclosingElement = element; | 445 Element thisEnclosingElement = element; |
| 413 if (element.kind === ElementKind.GENERATIVE_CONSTRUCTOR_BODY) { | 446 if (element.kind === ElementKind.GENERATIVE_CONSTRUCTOR_BODY) { |
| 414 ConstructorBodyElement body = element; | 447 ConstructorBodyElement body = element; |
| 415 thisEnclosingElement = body.constructor; | 448 thisEnclosingElement = body.constructor; |
| 416 } | 449 } |
| 417 thisElement = new ThisElement(thisEnclosingElement); | 450 thisElement = new ThisElement(thisEnclosingElement); |
| 418 } | 451 } |
| 419 closureData = new ClosureData(null, null, null, thisElement); | 452 closureData = new ClosureClassMap(null, null, null, thisElement); |
| 420 } | 453 } |
| 421 | 454 |
| 422 inNewScope(node, () { | 455 inNewScope(node, () { |
| 423 // We have to declare the implicit 'this' parameter. | 456 // We have to declare the implicit 'this' parameter. |
| 424 if (!insideClosure && closureData.thisElement !== null) { | 457 if (!insideClosure && closureData.thisElement !== null) { |
| 425 declareLocal(closureData.thisElement); | 458 declareLocal(closureData.thisElement); |
| 426 } | 459 } |
| 427 // If we are inside a named closure we have to declare ourselve. For | 460 // 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 | 461 // simplicity we declare the local even if the closure does not have a |
| 429 // name. | 462 // name. |
| 430 // It will simply not be used. | 463 // It will simply not be used. |
| 431 if (insideClosure) { | 464 if (insideClosure) { |
| 432 declareLocal(element); | 465 declareLocal(element); |
| 433 } | 466 } |
| 434 | 467 |
| 435 // TODO(ahe): This is problematic. The backend should not repeat | 468 // TODO(ahe): This is problematic. The backend should not repeat |
| 436 // the work of the resolver. It is the resolver's job to create | 469 // the work of the resolver. It is the resolver's job to create |
| 437 // parameters, etc. Other phases should only visit statements. | 470 // parameters, etc. Other phases should only visit statements. |
| 438 // TODO(floitsch): we avoid visiting the initializers on purpose so that | 471 // TODO(floitsch): we avoid visiting the initializers on purpose so that |
| 439 // we get an error-message later in the builder. | 472 // we get an error-message later in the builder. |
| 440 if (node.parameters !== null) node.parameters.accept(this); | 473 if (node.parameters !== null) node.parameters.accept(this); |
| 441 if (node.body !== null) node.body.accept(this); | 474 if (node.body !== null) node.body.accept(this); |
| 442 }); | 475 }); |
| 443 | 476 |
| 444 closureDataCache[node] = closureData; | 477 closureMappingCache[node] = closureData; |
| 445 | 478 |
| 446 ClosureData savedClosureData = closureData; | 479 ClosureClassMap savedClosureData = closureData; |
| 447 bool savedInsideClosure = insideClosure; | 480 bool savedInsideClosure = insideClosure; |
| 448 | 481 |
| 449 // Restore old values. | 482 // Restore old values. |
| 450 insideClosure = oldInsideClosure; | 483 insideClosure = oldInsideClosure; |
| 451 closureData = oldClosureData; | 484 closureData = oldClosureData; |
| 452 currentFunctionElement = oldFunctionElement; | 485 currentFunctionElement = oldFunctionElement; |
| 453 | 486 |
| 454 // Mark all free variables as captured and use them in the outer function. | 487 // Mark all free variables as captured and use them in the outer function. |
| 455 List<Element> freeVariables = | 488 List<Element> freeVariables = |
| 456 savedClosureData.freeVariableMapping.getKeys(); | 489 savedClosureData.freeVariableMapping.getKeys(); |
| (...skipping 14 matching lines...) Expand all Loading... |
| 471 } | 504 } |
| 472 | 505 |
| 473 visitTryStatement(TryStatement node) { | 506 visitTryStatement(TryStatement node) { |
| 474 // TODO(ngeoffray): implement finer grain state. | 507 // TODO(ngeoffray): implement finer grain state. |
| 475 bool oldInTryStatement = inTryStatement; | 508 bool oldInTryStatement = inTryStatement; |
| 476 inTryStatement = true; | 509 inTryStatement = true; |
| 477 node.visitChildren(this); | 510 node.visitChildren(this); |
| 478 inTryStatement = oldInTryStatement; | 511 inTryStatement = oldInTryStatement; |
| 479 } | 512 } |
| 480 } | 513 } |
| OLD | NEW |