| 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 library closureToClassMapper; | |
| 6 | |
| 7 import "elements/elements.dart"; | |
| 8 import "dart2jslib.dart"; | |
| 9 import "dart_types.dart"; | |
| 10 import "js_backend/js_backend.dart" show JavaScriptBackend; | |
| 11 import "scanner/scannerlib.dart" show Token; | |
| 12 import "tree/tree.dart"; | |
| 13 import "util/util.dart"; | |
| 14 import "elements/modelx.dart" | |
| 15 show BaseFunctionElementX, | |
| 16 ClassElementX, | |
| 17 ElementX, | |
| 18 LocalFunctionElementX; | |
| 19 import "elements/visitor.dart" show ElementVisitor; | |
| 20 | |
| 21 import 'universe/universe.dart' show | |
| 22 Universe; | |
| 23 | |
| 24 class ClosureNamer { | |
| 25 String getClosureVariableName(String name, int id) { | |
| 26 return "${name}_$id"; | |
| 27 } | |
| 28 | |
| 29 void forgetElement(Element element) {} | |
| 30 } | |
| 31 | |
| 32 class ClosureTask extends CompilerTask { | |
| 33 Map<Node, ClosureClassMap> closureMappingCache; | |
| 34 ClosureNamer namer; | |
| 35 ClosureTask(Compiler compiler, this.namer) | |
| 36 : closureMappingCache = new Map<Node, ClosureClassMap>(), | |
| 37 super(compiler); | |
| 38 | |
| 39 String get name => "Closure Simplifier"; | |
| 40 | |
| 41 ClosureClassMap computeClosureToClassMapping(Element element, | |
| 42 Node node, | |
| 43 TreeElements elements) { | |
| 44 return measure(() { | |
| 45 ClosureClassMap cached = closureMappingCache[node]; | |
| 46 if (cached != null) return cached; | |
| 47 | |
| 48 ClosureTranslator translator = | |
| 49 new ClosureTranslator(compiler, elements, closureMappingCache, namer); | |
| 50 | |
| 51 // The translator will store the computed closure-mappings inside the | |
| 52 // cache. One for given node and one for each nested closure. | |
| 53 if (node is FunctionExpression) { | |
| 54 translator.translateFunction(element, node); | |
| 55 } else if (element.isSynthesized) { | |
| 56 return new ClosureClassMap(null, null, null, new ThisLocal(element)); | |
| 57 } else { | |
| 58 assert(element.isField); | |
| 59 VariableElement field = element; | |
| 60 if (field.initializer != null) { | |
| 61 // The lazy initializer of a static. | |
| 62 translator.translateLazyInitializer(element, node, field.initializer); | |
| 63 } else { | |
| 64 assert(element.isInstanceMember); | |
| 65 closureMappingCache[node] = | |
| 66 new ClosureClassMap(null, null, null, new ThisLocal(element)); | |
| 67 } | |
| 68 } | |
| 69 assert(closureMappingCache[node] != null); | |
| 70 return closureMappingCache[node]; | |
| 71 }); | |
| 72 } | |
| 73 | |
| 74 ClosureClassMap getMappingForNestedFunction(FunctionExpression node) { | |
| 75 return measure(() { | |
| 76 ClosureClassMap nestedClosureData = closureMappingCache[node]; | |
| 77 if (nestedClosureData == null) { | |
| 78 compiler.internalError(node, "No closure cache."); | |
| 79 } | |
| 80 return nestedClosureData; | |
| 81 }); | |
| 82 } | |
| 83 | |
| 84 void forgetElement(var closure) { | |
| 85 ClosureClassElement cls; | |
| 86 if (closure is ClosureFieldElement) { | |
| 87 cls = closure.closureClass; | |
| 88 } else if (closure is SynthesizedCallMethodElementX) { | |
| 89 cls = closure.closureClass; | |
| 90 } else { | |
| 91 throw new SpannableAssertionFailure( | |
| 92 closure, 'Not a closure: $closure (${closure.runtimeType}).'); | |
| 93 } | |
| 94 namer.forgetElement(cls); | |
| 95 compiler.enqueuer.codegen.forgetElement(cls); | |
| 96 } | |
| 97 } | |
| 98 | |
| 99 /// Common interface for [BoxFieldElement] and [ClosureFieldElement] as | |
| 100 /// non-elements. | |
| 101 abstract class CapturedVariable {} | |
| 102 | |
| 103 // TODO(ahe): These classes continuously cause problems. We need to | |
| 104 // find a more general solution. | |
| 105 class ClosureFieldElement extends ElementX | |
| 106 implements VariableElement, CapturedVariable { | |
| 107 /// The source variable this element refers to. | |
| 108 final Local local; | |
| 109 | |
| 110 ClosureFieldElement(String name, | |
| 111 this.local, | |
| 112 ClosureClassElement enclosing) | |
| 113 : super(name, ElementKind.FIELD, enclosing); | |
| 114 | |
| 115 /// Use [closureClass] instead. | |
| 116 @deprecated | |
| 117 get enclosingElement => super.enclosingElement; | |
| 118 | |
| 119 ClosureClassElement get closureClass => super.enclosingElement; | |
| 120 | |
| 121 MemberElement get memberContext => closureClass.methodElement.memberContext; | |
| 122 | |
| 123 bool get hasNode => false; | |
| 124 | |
| 125 Node get node { | |
| 126 throw new SpannableAssertionFailure(local, | |
| 127 'Should not access node of ClosureFieldElement.'); | |
| 128 } | |
| 129 | |
| 130 bool get hasResolvedAst => hasTreeElements; | |
| 131 | |
| 132 ResolvedAst get resolvedAst { | |
| 133 return new ResolvedAst(this, null, treeElements); | |
| 134 } | |
| 135 | |
| 136 Expression get initializer { | |
| 137 throw new SpannableAssertionFailure(local, | |
| 138 'Should not access initializer of ClosureFieldElement.'); | |
| 139 } | |
| 140 | |
| 141 bool get isInstanceMember => true; | |
| 142 bool get isAssignable => false; | |
| 143 | |
| 144 DartType computeType(Compiler compiler) => type; | |
| 145 | |
| 146 DartType get type { | |
| 147 if (local is LocalElement) { | |
| 148 LocalElement element = local; | |
| 149 return element.type; | |
| 150 } | |
| 151 return const DynamicType(); | |
| 152 } | |
| 153 | |
| 154 String toString() => "ClosureFieldElement($name)"; | |
| 155 | |
| 156 accept(ElementVisitor visitor) => visitor.visitClosureFieldElement(this); | |
| 157 | |
| 158 Element get analyzableElement => closureClass.methodElement.analyzableElement; | |
| 159 } | |
| 160 | |
| 161 // TODO(ahe): These classes continuously cause problems. We need to find | |
| 162 // a more general solution. | |
| 163 class ClosureClassElement extends ClassElementX { | |
| 164 DartType rawType; | |
| 165 DartType thisType; | |
| 166 FunctionType callType; | |
| 167 /// Node that corresponds to this closure, used for source position. | |
| 168 final FunctionExpression node; | |
| 169 | |
| 170 /** | |
| 171 * The element for the declaration of the function expression. | |
| 172 */ | |
| 173 final LocalFunctionElement methodElement; | |
| 174 | |
| 175 final List<ClosureFieldElement> _closureFields = <ClosureFieldElement>[]; | |
| 176 | |
| 177 ClosureClassElement(this.node, | |
| 178 String name, | |
| 179 Compiler compiler, | |
| 180 LocalFunctionElement closure) | |
| 181 : this.methodElement = closure, | |
| 182 super(name, | |
| 183 closure.compilationUnit, | |
| 184 // By assigning a fresh class-id we make sure that the hashcode | |
| 185 // is unique, but also emit closure classes after all other | |
| 186 // classes (since the emitter sorts classes by their id). | |
| 187 compiler.getNextFreeClassId(), | |
| 188 STATE_DONE) { | |
| 189 JavaScriptBackend backend = compiler.backend; | |
| 190 ClassElement superclass = methodElement.isInstanceMember | |
| 191 ? backend.boundClosureClass | |
| 192 : backend.closureClass; | |
| 193 superclass.ensureResolved(compiler); | |
| 194 supertype = superclass.thisType; | |
| 195 interfaces = const Link<DartType>(); | |
| 196 thisType = rawType = new InterfaceType(this); | |
| 197 allSupertypesAndSelf = | |
| 198 superclass.allSupertypesAndSelf.extendClass(thisType); | |
| 199 callType = methodElement.type; | |
| 200 } | |
| 201 | |
| 202 Iterable<ClosureFieldElement> get closureFields => _closureFields; | |
| 203 | |
| 204 void addField(ClosureFieldElement field, DiagnosticListener listener) { | |
| 205 _closureFields.add(field); | |
| 206 addMember(field, listener); | |
| 207 } | |
| 208 | |
| 209 bool get hasNode => true; | |
| 210 | |
| 211 bool get isClosure => true; | |
| 212 | |
| 213 Token get position => node.getBeginToken(); | |
| 214 | |
| 215 Node parseNode(DiagnosticListener listener) => node; | |
| 216 | |
| 217 // A [ClosureClassElement] is nested inside a function or initializer in terms | |
| 218 // of [enclosingElement], but still has to be treated as a top-level | |
| 219 // element. | |
| 220 bool get isTopLevel => true; | |
| 221 | |
| 222 get enclosingElement => methodElement; | |
| 223 | |
| 224 accept(ElementVisitor visitor) => visitor.visitClosureClassElement(this); | |
| 225 } | |
| 226 | |
| 227 /// A local variable that contains the box object holding the [BoxFieldElement] | |
| 228 /// fields. | |
| 229 class BoxLocal extends Local { | |
| 230 final String name; | |
| 231 final ExecutableElement executableContext; | |
| 232 | |
| 233 BoxLocal(this.name, this.executableContext); | |
| 234 } | |
| 235 | |
| 236 // TODO(ngeoffray, ahe): These classes continuously cause problems. We need to | |
| 237 // find a more general solution. | |
| 238 class BoxFieldElement extends ElementX | |
| 239 implements TypedElement, CapturedVariable { | |
| 240 final BoxLocal box; | |
| 241 | |
| 242 BoxFieldElement(String name, this.variableElement, BoxLocal box) | |
| 243 : this.box = box, | |
| 244 super(name, ElementKind.FIELD, box.executableContext); | |
| 245 | |
| 246 DartType computeType(Compiler compiler) => type; | |
| 247 | |
| 248 DartType get type => variableElement.type; | |
| 249 | |
| 250 final VariableElement variableElement; | |
| 251 | |
| 252 accept(ElementVisitor visitor) => visitor.visitBoxFieldElement(this); | |
| 253 } | |
| 254 | |
| 255 /// A local variable used encode the direct (uncaptured) references to [this]. | |
| 256 class ThisLocal extends Local { | |
| 257 final ExecutableElement executableContext; | |
| 258 | |
| 259 ThisLocal(this.executableContext); | |
| 260 | |
| 261 String get name => 'this'; | |
| 262 | |
| 263 ClassElement get enclosingClass => executableContext.enclosingClass; | |
| 264 } | |
| 265 | |
| 266 /// Call method of a closure class. | |
| 267 class SynthesizedCallMethodElementX extends BaseFunctionElementX { | |
| 268 final LocalFunctionElement expression; | |
| 269 | |
| 270 SynthesizedCallMethodElementX(String name, | |
| 271 LocalFunctionElementX other, | |
| 272 ClosureClassElement enclosing) | |
| 273 : expression = other, | |
| 274 super(name, other.kind, other.modifiers, enclosing, false) { | |
| 275 functionSignatureCache = other.functionSignature; | |
| 276 } | |
| 277 | |
| 278 /// Use [closureClass] instead. | |
| 279 @deprecated | |
| 280 get enclosingElement => super.enclosingElement; | |
| 281 | |
| 282 ClosureClassElement get closureClass => super.enclosingElement; | |
| 283 | |
| 284 MemberElement get memberContext { | |
| 285 return closureClass.methodElement.memberContext; | |
| 286 } | |
| 287 | |
| 288 bool get hasNode => expression.hasNode; | |
| 289 | |
| 290 FunctionExpression get node => expression.node; | |
| 291 | |
| 292 FunctionExpression parseNode(DiagnosticListener listener) => node; | |
| 293 | |
| 294 ResolvedAst get resolvedAst { | |
| 295 return new ResolvedAst(this, node, treeElements); | |
| 296 } | |
| 297 | |
| 298 Element get analyzableElement => closureClass.methodElement.analyzableElement; | |
| 299 } | |
| 300 | |
| 301 // The box-element for a scope, and the captured variables that need to be | |
| 302 // stored in the box. | |
| 303 class ClosureScope { | |
| 304 BoxLocal boxElement; | |
| 305 Map<VariableElement, BoxFieldElement> _capturedVariableMapping; | |
| 306 | |
| 307 // If the scope is attached to a [For] contains the variables that are | |
| 308 // declared in the initializer of the [For] and that need to be boxed. | |
| 309 // Otherwise contains the empty List. | |
| 310 List<VariableElement> boxedLoopVariables = const <VariableElement>[]; | |
| 311 | |
| 312 ClosureScope(this.boxElement, this._capturedVariableMapping); | |
| 313 | |
| 314 bool hasBoxedLoopVariables() => !boxedLoopVariables.isEmpty; | |
| 315 | |
| 316 bool isCapturedVariable(VariableElement variable) { | |
| 317 return _capturedVariableMapping.containsKey(variable); | |
| 318 } | |
| 319 | |
| 320 void forEachCapturedVariable(f(LocalVariableElement variable, | |
| 321 BoxFieldElement boxField)) { | |
| 322 _capturedVariableMapping.forEach(f); | |
| 323 } | |
| 324 } | |
| 325 | |
| 326 class ClosureClassMap { | |
| 327 // The closure's element before any translation. Will be null for methods. | |
| 328 final LocalFunctionElement closureElement; | |
| 329 // The closureClassElement will be null for methods that are not local | |
| 330 // closures. | |
| 331 final ClosureClassElement closureClassElement; | |
| 332 // The callElement will be null for methods that are not local closures. | |
| 333 final FunctionElement callElement; | |
| 334 // The [thisElement] makes handling 'this' easier by treating it like any | |
| 335 // other argument. It is only set for instance-members. | |
| 336 final ThisLocal thisLocal; | |
| 337 | |
| 338 // Maps free locals, arguments and function elements to their captured | |
| 339 // copies. | |
| 340 final Map<Local, CapturedVariable> _freeVariableMapping = | |
| 341 new Map<Local, CapturedVariable>(); | |
| 342 | |
| 343 // Maps closure-fields to their captured elements. This is somehow the inverse | |
| 344 // mapping of [freeVariableMapping], but whereas [freeVariableMapping] does | |
| 345 // not deal with boxes, here we map instance-fields (which might represent | |
| 346 // boxes) to their boxElement. | |
| 347 final Map<ClosureFieldElement, Local> _closureFieldMapping = | |
| 348 new Map<ClosureFieldElement, Local>(); | |
| 349 | |
| 350 // Maps scopes ([Loop] and [FunctionExpression] nodes) to their | |
| 351 // [ClosureScope] which contains their box and the | |
| 352 // captured variables that are stored in the box. | |
| 353 // This map will be empty if the method/closure of this [ClosureData] does not | |
| 354 // contain any nested closure. | |
| 355 final Map<Node, ClosureScope> capturingScopes = new Map<Node, ClosureScope>(); | |
| 356 | |
| 357 final Set<Local> usedVariablesInTry = new Set<Local>(); | |
| 358 | |
| 359 ClosureClassMap(this.closureElement, | |
| 360 this.closureClassElement, | |
| 361 this.callElement, | |
| 362 this.thisLocal); | |
| 363 | |
| 364 void addFreeVariable(Local element) { | |
| 365 assert(_freeVariableMapping[element] == null); | |
| 366 _freeVariableMapping[element] = null; | |
| 367 } | |
| 368 | |
| 369 Iterable<Local> get freeVariables => _freeVariableMapping.keys; | |
| 370 | |
| 371 bool isFreeVariable(Local element) { | |
| 372 return _freeVariableMapping.containsKey(element); | |
| 373 } | |
| 374 | |
| 375 CapturedVariable getFreeVariableElement(Local element) { | |
| 376 return _freeVariableMapping[element]; | |
| 377 } | |
| 378 | |
| 379 /// Sets the free [variable] to be captured by the [boxField]. | |
| 380 void setFreeVariableBoxField(Local variable, | |
| 381 BoxFieldElement boxField) { | |
| 382 _freeVariableMapping[variable] = boxField; | |
| 383 } | |
| 384 | |
| 385 /// Sets the free [variable] to be captured by the [closureField]. | |
| 386 void setFreeVariableClosureField(Local variable, | |
| 387 ClosureFieldElement closureField) { | |
| 388 _freeVariableMapping[variable] = closureField; | |
| 389 } | |
| 390 | |
| 391 | |
| 392 void forEachFreeVariable(f(Local variable, | |
| 393 CapturedVariable field)) { | |
| 394 _freeVariableMapping.forEach(f); | |
| 395 } | |
| 396 | |
| 397 Local getLocalVariableForClosureField(ClosureFieldElement field) { | |
| 398 return _closureFieldMapping[field]; | |
| 399 } | |
| 400 | |
| 401 void setLocalVariableForClosureField(ClosureFieldElement field, | |
| 402 Local variable) { | |
| 403 _closureFieldMapping[field] = variable; | |
| 404 } | |
| 405 | |
| 406 bool get isClosure => closureElement != null; | |
| 407 | |
| 408 bool capturingScopesBox(Local variable) { | |
| 409 return capturingScopes.values.any((scope) { | |
| 410 return scope.boxedLoopVariables.contains(variable); | |
| 411 }); | |
| 412 } | |
| 413 | |
| 414 bool isVariableBoxed(Local variable) { | |
| 415 CapturedVariable copy = _freeVariableMapping[variable]; | |
| 416 if (copy is BoxFieldElement) { | |
| 417 return true; | |
| 418 } | |
| 419 return capturingScopesBox(variable); | |
| 420 } | |
| 421 | |
| 422 void forEachCapturedVariable(void f(Local variable, | |
| 423 CapturedVariable field)) { | |
| 424 _freeVariableMapping.forEach((variable, copy) { | |
| 425 if (variable is BoxLocal) return; | |
| 426 f(variable, copy); | |
| 427 }); | |
| 428 capturingScopes.values.forEach((ClosureScope scope) { | |
| 429 scope.forEachCapturedVariable(f); | |
| 430 }); | |
| 431 } | |
| 432 | |
| 433 void forEachBoxedVariable(void f(LocalVariableElement local, | |
| 434 BoxFieldElement field)) { | |
| 435 _freeVariableMapping.forEach((variable, copy) { | |
| 436 if (!isVariableBoxed(variable)) return; | |
| 437 f(variable, copy); | |
| 438 }); | |
| 439 capturingScopes.values.forEach((ClosureScope scope) { | |
| 440 scope.forEachCapturedVariable(f); | |
| 441 }); | |
| 442 } | |
| 443 | |
| 444 void removeMyselfFrom(Universe universe) { | |
| 445 _freeVariableMapping.values.forEach((e) { | |
| 446 universe.closurizedMembers.remove(e); | |
| 447 universe.fieldSetters.remove(e); | |
| 448 universe.fieldGetters.remove(e); | |
| 449 }); | |
| 450 } | |
| 451 } | |
| 452 | |
| 453 class ClosureTranslator extends Visitor { | |
| 454 final Compiler compiler; | |
| 455 final TreeElements elements; | |
| 456 int closureFieldCounter = 0; | |
| 457 int boxedFieldCounter = 0; | |
| 458 bool inTryStatement = false; | |
| 459 final Map<Node, ClosureClassMap> closureMappingCache; | |
| 460 | |
| 461 // Map of captured variables. Initially they will map to `null`. If | |
| 462 // a variable needs to be boxed then the scope declaring the variable | |
| 463 // will update this to mapping to the capturing [BoxFieldElement]. | |
| 464 Map<Local, BoxFieldElement> _capturedVariableMapping = | |
| 465 new Map<Local, BoxFieldElement>(); | |
| 466 | |
| 467 // List of encountered closures. | |
| 468 List<Expression> closures = <Expression>[]; | |
| 469 | |
| 470 // The local variables that have been declared in the current scope. | |
| 471 List<LocalVariableElement> scopeVariables; | |
| 472 | |
| 473 // Keep track of the mutated local variables so that we don't need to box | |
| 474 // non-mutated variables. | |
| 475 Set<LocalVariableElement> mutatedVariables = new Set<LocalVariableElement>(); | |
| 476 | |
| 477 MemberElement outermostElement; | |
| 478 ExecutableElement executableContext; | |
| 479 | |
| 480 // The closureData of the currentFunctionElement. | |
| 481 ClosureClassMap closureData; | |
| 482 | |
| 483 ClosureNamer namer; | |
| 484 | |
| 485 bool insideClosure = false; | |
| 486 | |
| 487 ClosureTranslator(this.compiler, | |
| 488 this.elements, | |
| 489 this.closureMappingCache, | |
| 490 this.namer); | |
| 491 | |
| 492 bool isCapturedVariable(Local element) { | |
| 493 return _capturedVariableMapping.containsKey(element); | |
| 494 } | |
| 495 | |
| 496 void addCapturedVariable(Node node, Local variable) { | |
| 497 if (_capturedVariableMapping[variable] != null) { | |
| 498 compiler.internalError(node, 'In closure analyzer.'); | |
| 499 } | |
| 500 _capturedVariableMapping[variable] = null; | |
| 501 } | |
| 502 | |
| 503 void setCapturedVariableBoxField(Local variable, | |
| 504 BoxFieldElement boxField) { | |
| 505 assert(isCapturedVariable(variable)); | |
| 506 _capturedVariableMapping[variable] = boxField; | |
| 507 } | |
| 508 | |
| 509 BoxFieldElement getCapturedVariableBoxField(Local variable) { | |
| 510 return _capturedVariableMapping[variable]; | |
| 511 } | |
| 512 | |
| 513 void translateFunction(Element element, FunctionExpression node) { | |
| 514 // For constructors the [element] and the [:elements[node]:] may differ. | |
| 515 // The [:elements[node]:] always points to the generative-constructor | |
| 516 // element, whereas the [element] might be the constructor-body element. | |
| 517 visit(node); // [visitFunctionExpression] will call [visitInvokable]. | |
| 518 // When variables need to be boxed their [_capturedVariableMapping] is | |
| 519 // updated, but we delay updating the similar freeVariableMapping in the | |
| 520 // closure datas that capture these variables. | |
| 521 // The closures don't have their fields (in the closure class) set, either. | |
| 522 updateClosures(); | |
| 523 } | |
| 524 | |
| 525 void translateLazyInitializer(VariableElement element, | |
| 526 VariableDefinitions node, | |
| 527 Expression initializer) { | |
| 528 visitInvokable(element, node, () { visit(initializer); }); | |
| 529 updateClosures(); | |
| 530 } | |
| 531 | |
| 532 // This function runs through all of the existing closures and updates their | |
| 533 // free variables to the boxed value. It also adds the field-elements to the | |
| 534 // class representing the closure. | |
| 535 void updateClosures() { | |
| 536 for (Expression closure in closures) { | |
| 537 // The captured variables that need to be stored in a field of the closure | |
| 538 // class. | |
| 539 Set<Local> fieldCaptures = new Set<Local>(); | |
| 540 Set<BoxLocal> boxes = new Set<BoxLocal>(); | |
| 541 ClosureClassMap data = closureMappingCache[closure]; | |
| 542 // We get a copy of the keys and iterate over it, to avoid modifications | |
| 543 // to the map while iterating over it. | |
| 544 Iterable<Local> freeVariables = data.freeVariables.toList(); | |
| 545 freeVariables.forEach((Local fromElement) { | |
| 546 assert(data.isFreeVariable(fromElement)); | |
| 547 assert(data.getFreeVariableElement(fromElement) == null); | |
| 548 assert(isCapturedVariable(fromElement)); | |
| 549 BoxFieldElement boxFieldElement = | |
| 550 getCapturedVariableBoxField(fromElement); | |
| 551 if (boxFieldElement == null) { | |
| 552 assert(fromElement is! BoxLocal); | |
| 553 // The variable has not been boxed. | |
| 554 fieldCaptures.add(fromElement); | |
| 555 } else { | |
| 556 // A boxed element. | |
| 557 data.setFreeVariableBoxField(fromElement, boxFieldElement); | |
| 558 boxes.add(boxFieldElement.box); | |
| 559 } | |
| 560 }); | |
| 561 ClosureClassElement closureClass = data.closureClassElement; | |
| 562 assert(closureClass != null || | |
| 563 (fieldCaptures.isEmpty && boxes.isEmpty)); | |
| 564 | |
| 565 void addClosureField(Local local, String name) { | |
| 566 ClosureFieldElement closureField = | |
| 567 new ClosureFieldElement(name, local, closureClass); | |
| 568 closureClass.addField(closureField, compiler); | |
| 569 data.setLocalVariableForClosureField(closureField, local); | |
| 570 data.setFreeVariableClosureField(local, closureField); | |
| 571 } | |
| 572 | |
| 573 // Add the box elements first so we get the same ordering. | |
| 574 // TODO(sra): What is the canonical order of multiple boxes? | |
| 575 for (BoxLocal capturedElement in boxes) { | |
| 576 addClosureField(capturedElement, capturedElement.name); | |
| 577 } | |
| 578 | |
| 579 /// Comparator for locals. Position boxes before elements. | |
| 580 int compareLocals(a, b) { | |
| 581 if (a is Element && b is Element) { | |
| 582 return Elements.compareByPosition(a, b); | |
| 583 } else if (a is Element) { | |
| 584 return 1; | |
| 585 } else if (b is Element) { | |
| 586 return -1; | |
| 587 } else { | |
| 588 return a.name.compareTo(b.name); | |
| 589 } | |
| 590 } | |
| 591 | |
| 592 for (Local capturedLocal in fieldCaptures.toList()..sort(compareLocals)) { | |
| 593 int id = closureFieldCounter++; | |
| 594 String name = namer.getClosureVariableName(capturedLocal.name, id); | |
| 595 addClosureField(capturedLocal, name); | |
| 596 } | |
| 597 closureClass.reverseBackendMembers(); | |
| 598 } | |
| 599 } | |
| 600 | |
| 601 void useLocal(Local variable) { | |
| 602 // If the element is not declared in the current function and the element | |
| 603 // is not the closure itself we need to mark the element as free variable. | |
| 604 // Note that the check on [insideClosure] is not just an | |
| 605 // optimization: factories have type parameters as function | |
| 606 // parameters, and type parameters are declared in the class, not | |
| 607 // the factory. | |
| 608 bool inCurrentContext(Local variable) { | |
| 609 return variable == executableContext || | |
| 610 variable.executableContext == executableContext; | |
| 611 } | |
| 612 | |
| 613 if (insideClosure && !inCurrentContext(variable)) { | |
| 614 closureData.addFreeVariable(variable); | |
| 615 } else if (inTryStatement) { | |
| 616 // Don't mark the this-element or a self-reference. This would complicate | |
| 617 // things in the builder. | |
| 618 // Note that nested (named) functions are immutable. | |
| 619 if (variable != closureData.thisLocal && | |
| 620 variable != closureData.closureElement) { | |
| 621 // TODO(ngeoffray): only do this if the variable is mutated. | |
| 622 closureData.usedVariablesInTry.add(variable); | |
| 623 } | |
| 624 } | |
| 625 } | |
| 626 | |
| 627 void useTypeVariableAsLocal(TypeVariableType typeVariable) { | |
| 628 useLocal(new TypeVariableLocal(typeVariable, outermostElement)); | |
| 629 } | |
| 630 | |
| 631 void declareLocal(LocalVariableElement element) { | |
| 632 scopeVariables.add(element); | |
| 633 } | |
| 634 | |
| 635 void registerNeedsThis() { | |
| 636 if (closureData.thisLocal != null) { | |
| 637 useLocal(closureData.thisLocal); | |
| 638 } | |
| 639 } | |
| 640 | |
| 641 visit(Node node) => node.accept(this); | |
| 642 | |
| 643 visitNode(Node node) => node.visitChildren(this); | |
| 644 | |
| 645 visitVariableDefinitions(VariableDefinitions node) { | |
| 646 if (node.type != null) { | |
| 647 visit(node.type); | |
| 648 } | |
| 649 for (Link<Node> link = node.definitions.nodes; | |
| 650 !link.isEmpty; | |
| 651 link = link.tail) { | |
| 652 Node definition = link.head; | |
| 653 LocalElement element = elements[definition]; | |
| 654 assert(element != null); | |
| 655 if (!element.isInitializingFormal) { | |
| 656 declareLocal(element); | |
| 657 } | |
| 658 // We still need to visit the right-hand sides of the init-assignments. | |
| 659 // For SendSets don't visit the left again. Otherwise it would be marked | |
| 660 // as mutated. | |
| 661 if (definition is Send) { | |
| 662 Send assignment = definition; | |
| 663 Node arguments = assignment.argumentsNode; | |
| 664 if (arguments != null) { | |
| 665 visit(arguments); | |
| 666 } | |
| 667 } else { | |
| 668 visit(definition); | |
| 669 } | |
| 670 } | |
| 671 } | |
| 672 | |
| 673 visitTypeAnnotation(TypeAnnotation node) { | |
| 674 MemberElement member = executableContext.memberContext; | |
| 675 DartType type = elements.getType(node); | |
| 676 // TODO(karlklose,johnniwinther): if the type is null, the annotation is | |
| 677 // from a parameter which has been analyzed before the method has been | |
| 678 // resolved and the result has been thrown away. | |
| 679 if (compiler.enableTypeAssertions && type != null && | |
| 680 type.containsTypeVariables) { | |
| 681 if (insideClosure && member.isFactoryConstructor) { | |
| 682 // This is a closure in a factory constructor. Since there is no | |
| 683 // [:this:], we have to mark the type arguments as free variables to | |
| 684 // capture them in the closure. | |
| 685 type.forEachTypeVariable((TypeVariableType variable) { | |
| 686 useTypeVariableAsLocal(variable); | |
| 687 }); | |
| 688 } | |
| 689 if (member.isInstanceMember && !member.isField) { | |
| 690 // In checked mode, using a type variable in a type annotation may lead | |
| 691 // to a runtime type check that needs to access the type argument and | |
| 692 // therefore the closure needs a this-element, if it is not in a field | |
| 693 // initializer; field initatializers are evaluated in a context where | |
| 694 // the type arguments are available in locals. | |
| 695 registerNeedsThis(); | |
| 696 } | |
| 697 } | |
| 698 } | |
| 699 | |
| 700 visitIdentifier(Identifier node) { | |
| 701 if (node.isThis()) { | |
| 702 registerNeedsThis(); | |
| 703 } else { | |
| 704 Element element = elements[node]; | |
| 705 if (element != null && element.isTypeVariable) { | |
| 706 if (outermostElement.isConstructor) { | |
| 707 TypeVariableElement typeVariable = element; | |
| 708 useTypeVariableAsLocal(typeVariable.type); | |
| 709 } else { | |
| 710 registerNeedsThis(); | |
| 711 } | |
| 712 } | |
| 713 } | |
| 714 node.visitChildren(this); | |
| 715 } | |
| 716 | |
| 717 visitSend(Send node) { | |
| 718 Element element = elements[node]; | |
| 719 if (Elements.isLocal(element)) { | |
| 720 LocalElement localElement = element; | |
| 721 useLocal(localElement); | |
| 722 } else if (element != null && element.isTypeVariable) { | |
| 723 TypeVariableElement variable = element; | |
| 724 analyzeType(variable.type); | |
| 725 } else if (node.receiver == null && | |
| 726 Elements.isInstanceSend(node, elements)) { | |
| 727 registerNeedsThis(); | |
| 728 } else if (node.isSuperCall) { | |
| 729 registerNeedsThis(); | |
| 730 } else if (node.isTypeTest || node.isTypeCast) { | |
| 731 TypeAnnotation annotation = node.typeAnnotationFromIsCheckOrCast; | |
| 732 DartType type = elements.getType(annotation); | |
| 733 analyzeType(type); | |
| 734 } else if (node.isTypeTest) { | |
| 735 DartType type = elements.getType(node.typeAnnotationFromIsCheckOrCast); | |
| 736 analyzeType(type); | |
| 737 } else if (node.isTypeCast) { | |
| 738 DartType type = elements.getType(node.arguments.head); | |
| 739 analyzeType(type); | |
| 740 } else if (elements.isAssert(node) && !compiler.enableUserAssertions) { | |
| 741 return; | |
| 742 } | |
| 743 node.visitChildren(this); | |
| 744 } | |
| 745 | |
| 746 visitSendSet(SendSet node) { | |
| 747 Element element = elements[node]; | |
| 748 if (Elements.isLocal(element)) { | |
| 749 mutatedVariables.add(element); | |
| 750 if (compiler.enableTypeAssertions) { | |
| 751 TypedElement typedElement = element; | |
| 752 analyzeTypeVariables(typedElement.type); | |
| 753 } | |
| 754 } | |
| 755 super.visitSendSet(node); | |
| 756 } | |
| 757 | |
| 758 visitNewExpression(NewExpression node) { | |
| 759 DartType type = elements.getType(node); | |
| 760 analyzeType(type); | |
| 761 node.visitChildren(this); | |
| 762 } | |
| 763 | |
| 764 void analyzeTypeVariables(DartType type) { | |
| 765 type.forEachTypeVariable((TypeVariableType typeVariable) { | |
| 766 // Field initializers are inlined and access the type variable as | |
| 767 // normal parameters. | |
| 768 if (!outermostElement.isField && | |
| 769 !outermostElement.isConstructor) { | |
| 770 registerNeedsThis(); | |
| 771 } else { | |
| 772 useTypeVariableAsLocal(typeVariable); | |
| 773 } | |
| 774 }); | |
| 775 } | |
| 776 | |
| 777 void analyzeType(DartType type) { | |
| 778 // TODO(johnniwinther): Find out why this can be null. | |
| 779 if (type == null) return; | |
| 780 if (outermostElement.isClassMember && | |
| 781 compiler.backend.classNeedsRti(outermostElement.enclosingClass)) { | |
| 782 if (outermostElement.isConstructor || | |
| 783 outermostElement.isField) { | |
| 784 analyzeTypeVariables(type); | |
| 785 } else if (outermostElement.isInstanceMember) { | |
| 786 if (type.containsTypeVariables) { | |
| 787 registerNeedsThis(); | |
| 788 } | |
| 789 } | |
| 790 } | |
| 791 } | |
| 792 | |
| 793 // If variables that are declared in the [node] scope are captured and need | |
| 794 // to be boxed create a box-element and update the [capturingScopes] in the | |
| 795 // current [closureData]. | |
| 796 // The boxed variables are updated in the [capturedVariableMapping]. | |
| 797 void attachCapturedScopeVariables(Node node) { | |
| 798 BoxLocal box = null; | |
| 799 Map<LocalVariableElement, BoxFieldElement> scopeMapping = | |
| 800 new Map<LocalVariableElement, BoxFieldElement>(); | |
| 801 | |
| 802 void boxCapturedVariable(LocalVariableElement variable) { | |
| 803 if (isCapturedVariable(variable)) { | |
| 804 if (box == null) { | |
| 805 // TODO(floitsch): construct better box names. | |
| 806 String boxName = | |
| 807 namer.getClosureVariableName('box', closureFieldCounter++); | |
| 808 box = new BoxLocal(boxName, executableContext); | |
| 809 } | |
| 810 String elementName = variable.name; | |
| 811 String boxedName = | |
| 812 namer.getClosureVariableName(elementName, boxedFieldCounter++); | |
| 813 // TODO(kasperl): Should this be a FieldElement instead? | |
| 814 BoxFieldElement boxed = new BoxFieldElement(boxedName, variable, box); | |
| 815 // No need to rename the fields of a box, so we give them a native name | |
| 816 // right now. | |
| 817 boxed.setFixedBackendName(boxedName); | |
| 818 scopeMapping[variable] = boxed; | |
| 819 setCapturedVariableBoxField(variable, boxed); | |
| 820 } | |
| 821 } | |
| 822 | |
| 823 for (LocalVariableElement variable in scopeVariables) { | |
| 824 // No need to box non-assignable elements. | |
| 825 if (!variable.isAssignable) continue; | |
| 826 if (!mutatedVariables.contains(variable)) continue; | |
| 827 boxCapturedVariable(variable); | |
| 828 } | |
| 829 if (!scopeMapping.isEmpty) { | |
| 830 ClosureScope scope = new ClosureScope(box, scopeMapping); | |
| 831 closureData.capturingScopes[node] = scope; | |
| 832 } | |
| 833 } | |
| 834 | |
| 835 void inNewScope(Node node, Function action) { | |
| 836 List<LocalVariableElement> oldScopeVariables = scopeVariables; | |
| 837 scopeVariables = <LocalVariableElement>[]; | |
| 838 action(); | |
| 839 attachCapturedScopeVariables(node); | |
| 840 mutatedVariables.removeAll(scopeVariables); | |
| 841 scopeVariables = oldScopeVariables; | |
| 842 } | |
| 843 | |
| 844 visitLoop(Loop node) { | |
| 845 inNewScope(node, () { | |
| 846 node.visitChildren(this); | |
| 847 }); | |
| 848 } | |
| 849 | |
| 850 visitFor(For node) { | |
| 851 visitLoop(node); | |
| 852 // See if we have declared loop variables that need to be boxed. | |
| 853 if (node.initializer == null) return; | |
| 854 VariableDefinitions definitions = node.initializer.asVariableDefinitions(); | |
| 855 if (definitions == null) return; | |
| 856 ClosureScope scopeData = closureData.capturingScopes[node]; | |
| 857 if (scopeData == null) return; | |
| 858 List<LocalVariableElement> result = <LocalVariableElement>[]; | |
| 859 for (Link<Node> link = definitions.definitions.nodes; | |
| 860 !link.isEmpty; | |
| 861 link = link.tail) { | |
| 862 Node definition = link.head; | |
| 863 LocalVariableElement element = elements[definition]; | |
| 864 if (isCapturedVariable(element)) { | |
| 865 result.add(element); | |
| 866 } | |
| 867 } | |
| 868 scopeData.boxedLoopVariables = result; | |
| 869 } | |
| 870 | |
| 871 /** Returns a non-unique name for the given closure element. */ | |
| 872 String computeClosureName(Element element) { | |
| 873 Link<String> parts = const Link<String>(); | |
| 874 String ownName = element.name; | |
| 875 if (ownName == null || ownName == "") { | |
| 876 parts = parts.prepend("closure"); | |
| 877 } else { | |
| 878 parts = parts.prepend(ownName); | |
| 879 } | |
| 880 for (Element enclosingElement = element.enclosingElement; | |
| 881 enclosingElement != null && | |
| 882 (enclosingElement.kind == ElementKind.GENERATIVE_CONSTRUCTOR_BODY | |
| 883 || enclosingElement.kind == ElementKind.GENERATIVE_CONSTRUCTOR | |
| 884 || enclosingElement.kind == ElementKind.CLASS | |
| 885 || enclosingElement.kind == ElementKind.FUNCTION | |
| 886 || enclosingElement.kind == ElementKind.GETTER | |
| 887 || enclosingElement.kind == ElementKind.SETTER); | |
| 888 enclosingElement = enclosingElement.enclosingElement) { | |
| 889 // TODO(johnniwinther): Simplify computed names. | |
| 890 if (enclosingElement.isGenerativeConstructor || | |
| 891 enclosingElement.isGenerativeConstructorBody || | |
| 892 enclosingElement.isFactoryConstructor) { | |
| 893 parts = parts.prepend( | |
| 894 Elements.reconstructConstructorName(enclosingElement)); | |
| 895 } else { | |
| 896 String surroundingName = | |
| 897 Elements.operatorNameToIdentifier(enclosingElement.name); | |
| 898 parts = parts.prepend(surroundingName); | |
| 899 } | |
| 900 // A generative constructors's parent is the class; the class name is | |
| 901 // already part of the generative constructor's name. | |
| 902 if (enclosingElement.kind == ElementKind.GENERATIVE_CONSTRUCTOR) break; | |
| 903 } | |
| 904 StringBuffer sb = new StringBuffer(); | |
| 905 parts.printOn(sb, '_'); | |
| 906 return sb.toString(); | |
| 907 } | |
| 908 | |
| 909 JavaScriptBackend get backend => compiler.backend; | |
| 910 | |
| 911 ClosureClassMap globalizeClosure(FunctionExpression node, | |
| 912 LocalFunctionElement element) { | |
| 913 String closureName = computeClosureName(element); | |
| 914 ClosureClassElement globalizedElement = new ClosureClassElement( | |
| 915 node, closureName, compiler, element); | |
| 916 FunctionElement callElement = | |
| 917 new SynthesizedCallMethodElementX(Compiler.CALL_OPERATOR_NAME, | |
| 918 element, | |
| 919 globalizedElement); | |
| 920 backend.maybeMarkClosureAsNeededForReflection(globalizedElement, callElement
, element); | |
| 921 MemberElement enclosing = element.memberContext; | |
| 922 enclosing.nestedClosures.add(callElement); | |
| 923 globalizedElement.addMember(callElement, compiler); | |
| 924 globalizedElement.computeAllClassMembers(compiler); | |
| 925 // The nested function's 'this' is the same as the one for the outer | |
| 926 // function. It could be [null] if we are inside a static method. | |
| 927 ThisLocal thisElement = closureData.thisLocal; | |
| 928 | |
| 929 return new ClosureClassMap(element, globalizedElement, | |
| 930 callElement, thisElement); | |
| 931 } | |
| 932 | |
| 933 void visitInvokable(ExecutableElement element, | |
| 934 Node node, | |
| 935 void visitChildren()) { | |
| 936 bool oldInsideClosure = insideClosure; | |
| 937 Element oldFunctionElement = executableContext; | |
| 938 ClosureClassMap oldClosureData = closureData; | |
| 939 | |
| 940 insideClosure = outermostElement != null; | |
| 941 LocalFunctionElement closure; | |
| 942 executableContext = element; | |
| 943 if (insideClosure) { | |
| 944 closure = element; | |
| 945 closures.add(node); | |
| 946 closureData = globalizeClosure(node, closure); | |
| 947 } else { | |
| 948 outermostElement = element; | |
| 949 ThisLocal thisElement = null; | |
| 950 if (element.isInstanceMember || element.isGenerativeConstructor) { | |
| 951 thisElement = new ThisLocal(element); | |
| 952 } | |
| 953 closureData = new ClosureClassMap(null, null, null, thisElement); | |
| 954 } | |
| 955 closureMappingCache[node] = closureData; | |
| 956 | |
| 957 inNewScope(node, () { | |
| 958 DartType type = element.type; | |
| 959 // If the method needs RTI, or checked mode is set, we need to | |
| 960 // escape the potential type variables used in that closure. | |
| 961 if (element is FunctionElement && | |
| 962 (compiler.backend.methodNeedsRti(element) || | |
| 963 compiler.enableTypeAssertions)) { | |
| 964 analyzeTypeVariables(type); | |
| 965 } | |
| 966 | |
| 967 visitChildren(); | |
| 968 }); | |
| 969 | |
| 970 | |
| 971 ClosureClassMap savedClosureData = closureData; | |
| 972 bool savedInsideClosure = insideClosure; | |
| 973 | |
| 974 // Restore old values. | |
| 975 insideClosure = oldInsideClosure; | |
| 976 closureData = oldClosureData; | |
| 977 executableContext = oldFunctionElement; | |
| 978 | |
| 979 // Mark all free variables as captured and use them in the outer function. | |
| 980 Iterable<Local> freeVariables = savedClosureData.freeVariables; | |
| 981 assert(freeVariables.isEmpty || savedInsideClosure); | |
| 982 for (Local freeVariable in freeVariables) { | |
| 983 addCapturedVariable(node, freeVariable); | |
| 984 useLocal(freeVariable); | |
| 985 } | |
| 986 } | |
| 987 | |
| 988 visitFunctionExpression(FunctionExpression node) { | |
| 989 Element element = elements[node]; | |
| 990 | |
| 991 if (element.isParameter) { | |
| 992 // TODO(ahe): This is a hack. This method should *not* call | |
| 993 // visitChildren. | |
| 994 return node.name.accept(this); | |
| 995 } | |
| 996 | |
| 997 visitInvokable(element, node, () { | |
| 998 // TODO(ahe): This is problematic. The backend should not repeat | |
| 999 // the work of the resolver. It is the resolver's job to create | |
| 1000 // parameters, etc. Other phases should only visit statements. | |
| 1001 if (node.parameters != null) node.parameters.accept(this); | |
| 1002 if (node.initializers != null) node.initializers.accept(this); | |
| 1003 if (node.body != null) node.body.accept(this); | |
| 1004 }); | |
| 1005 } | |
| 1006 | |
| 1007 visitTryStatement(TryStatement node) { | |
| 1008 // TODO(ngeoffray): implement finer grain state. | |
| 1009 bool oldInTryStatement = inTryStatement; | |
| 1010 inTryStatement = true; | |
| 1011 node.visitChildren(this); | |
| 1012 inTryStatement = oldInTryStatement; | |
| 1013 } | |
| 1014 } | |
| 1015 | |
| 1016 /// A type variable as a local variable. | |
| 1017 class TypeVariableLocal implements Local { | |
| 1018 final TypeVariableType typeVariable; | |
| 1019 final ExecutableElement executableContext; | |
| 1020 | |
| 1021 TypeVariableLocal(this.typeVariable, this.executableContext); | |
| 1022 | |
| 1023 String get name => typeVariable.name; | |
| 1024 | |
| 1025 int get hashCode => typeVariable.hashCode; | |
| 1026 | |
| 1027 bool operator ==(other) { | |
| 1028 if (other is! TypeVariableLocal) return false; | |
| 1029 return typeVariable == other.typeVariable; | |
| 1030 } | |
| 1031 } | |
| OLD | NEW |