| 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 |