| 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(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 } | |
| OLD | NEW |