| OLD | NEW |
| (Empty) | |
| 1 // Copyright (c) 2016, 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 import '../closure.dart'; |
| 6 import '../common.dart'; |
| 7 import '../compiler.dart' show Compiler; |
| 8 import '../dart_types.dart'; |
| 9 import '../elements/elements.dart'; |
| 10 import '../io/source_information.dart'; |
| 11 import '../js/js.dart' as js; |
| 12 import '../js_backend/js_backend.dart'; |
| 13 import '../native/native.dart' as native; |
| 14 import '../tree/tree.dart' as ast; |
| 15 import '../types/types.dart'; |
| 16 import '../world.dart' show ClassWorld; |
| 17 import 'builder.dart' show SyntheticLocal; |
| 18 import 'graph_builder.dart'; |
| 19 import 'nodes.dart'; |
| 20 import 'types.dart'; |
| 21 |
| 22 /// Keeps track of locals (including parameters and phis) when building. The |
| 23 /// 'this' reference is treated as parameter and hence handled by this class, |
| 24 /// too. |
| 25 class LocalsHandler { |
| 26 /// The values of locals that can be directly accessed (without redirections |
| 27 /// to boxes or closure-fields). |
| 28 /// |
| 29 /// [directLocals] is iterated, so it is "insertion ordered" to make the |
| 30 /// iteration order a function only of insertions and not a function of |
| 31 /// e.g. Element hash codes. I'd prefer to use a SortedMap but some elements |
| 32 /// don't have source locations for [Elements.compareByPosition]. |
| 33 Map<Local, HInstruction> directLocals = new Map<Local, HInstruction>(); |
| 34 Map<Local, CapturedVariable> redirectionMapping = |
| 35 new Map<Local, CapturedVariable>(); |
| 36 final GraphBuilder builder; |
| 37 ClosureClassMap closureData; |
| 38 Map<TypeVariableType, TypeVariableLocal> typeVariableLocals = |
| 39 new Map<TypeVariableType, TypeVariableLocal>(); |
| 40 final ExecutableElement executableContext; |
| 41 |
| 42 /// The class that defines the current type environment or null if no type |
| 43 /// variables are in scope. |
| 44 ClassElement get contextClass => executableContext.contextClass; |
| 45 |
| 46 /// The type of the current instance, if concrete. |
| 47 /// |
| 48 /// This allows for handling fixed type argument in case of inlining. For |
| 49 /// instance, checking `'foo'` against `String` instead of `T` in `main`: |
| 50 /// |
| 51 /// class Foo<T> { |
| 52 /// T field; |
| 53 /// Foo(this.field); |
| 54 /// } |
| 55 /// main() { |
| 56 /// new Foo<String>('foo'); |
| 57 /// } |
| 58 /// |
| 59 /// [instanceType] is not used if it contains type variables, since these |
| 60 /// might not be in scope or from the current instance. |
| 61 /// |
| 62 final InterfaceType instanceType; |
| 63 |
| 64 final Compiler compiler; |
| 65 |
| 66 LocalsHandler(this.builder, this.executableContext, |
| 67 InterfaceType instanceType, this.compiler) |
| 68 : this.instanceType = |
| 69 instanceType == null || instanceType.containsTypeVariables |
| 70 ? null |
| 71 : instanceType; |
| 72 |
| 73 /// Substituted type variables occurring in [type] into the context of |
| 74 /// [contextClass]. |
| 75 DartType substInContext(DartType type) { |
| 76 if (contextClass != null) { |
| 77 ClassElement typeContext = Types.getClassContext(type); |
| 78 if (typeContext != null) { |
| 79 type = type.substByContext(contextClass.asInstanceOf(typeContext)); |
| 80 } |
| 81 } |
| 82 if (instanceType != null) { |
| 83 type = type.substByContext(instanceType); |
| 84 } |
| 85 return type; |
| 86 } |
| 87 |
| 88 get typesTask => compiler.typesTask; |
| 89 |
| 90 /// Creates a new [LocalsHandler] based on [other]. We only need to |
| 91 /// copy the [directLocals], since the other fields can be shared |
| 92 /// throughout the AST visit. |
| 93 LocalsHandler.from(LocalsHandler other) |
| 94 : directLocals = new Map<Local, HInstruction>.from(other.directLocals), |
| 95 redirectionMapping = other.redirectionMapping, |
| 96 executableContext = other.executableContext, |
| 97 instanceType = other.instanceType, |
| 98 builder = other.builder, |
| 99 closureData = other.closureData, |
| 100 compiler = other.compiler, |
| 101 activationVariables = other.activationVariables, |
| 102 cachedTypeOfThis = other.cachedTypeOfThis, |
| 103 cachedTypesOfCapturedVariables = other.cachedTypesOfCapturedVariables; |
| 104 |
| 105 /// Redirects accesses from element [from] to element [to]. The [to] element |
| 106 /// must be a boxed variable or a variable that is stored in a closure-field. |
| 107 void redirectElement(Local from, CapturedVariable to) { |
| 108 assert(redirectionMapping[from] == null); |
| 109 redirectionMapping[from] = to; |
| 110 assert(isStoredInClosureField(from) || isBoxed(from)); |
| 111 } |
| 112 |
| 113 HInstruction createBox() { |
| 114 // TODO(floitsch): Clean up this hack. Should we create a box-object by |
| 115 // just creating an empty object literal? |
| 116 JavaScriptBackend backend = compiler.backend; |
| 117 HInstruction box = new HForeignCode( |
| 118 js.js.parseForeignJS('{}'), backend.nonNullType, <HInstruction>[], |
| 119 nativeBehavior: native.NativeBehavior.PURE_ALLOCATION); |
| 120 builder.add(box); |
| 121 return box; |
| 122 } |
| 123 |
| 124 /// If the scope (function or loop) [node] has captured variables then this |
| 125 /// method creates a box and sets up the redirections. |
| 126 void enterScope(ast.Node node, Element element) { |
| 127 // See if any variable in the top-scope of the function is captured. If yes |
| 128 // we need to create a box-object. |
| 129 ClosureScope scopeData = closureData.capturingScopes[node]; |
| 130 if (scopeData == null) return; |
| 131 HInstruction box; |
| 132 // The scope has captured variables. |
| 133 if (element != null && element.isGenerativeConstructorBody) { |
| 134 // The box is passed as a parameter to a generative |
| 135 // constructor body. |
| 136 JavaScriptBackend backend = compiler.backend; |
| 137 box = builder.addParameter(scopeData.boxElement, backend.nonNullType); |
| 138 } else { |
| 139 box = createBox(); |
| 140 } |
| 141 // Add the box to the known locals. |
| 142 directLocals[scopeData.boxElement] = box; |
| 143 // Make sure that accesses to the boxed locals go into the box. We also |
| 144 // need to make sure that parameters are copied into the box if necessary. |
| 145 scopeData.forEachCapturedVariable( |
| 146 (LocalVariableElement from, BoxFieldElement to) { |
| 147 // The [from] can only be a parameter for function-scopes and not |
| 148 // loop scopes. |
| 149 if (from.isRegularParameter && !element.isGenerativeConstructorBody) { |
| 150 // Now that the redirection is set up, the update to the local will |
| 151 // write the parameter value into the box. |
| 152 // Store the captured parameter in the box. Get the current value |
| 153 // before we put the redirection in place. |
| 154 // We don't need to update the local for a generative |
| 155 // constructor body, because it receives a box that already |
| 156 // contains the updates as the last parameter. |
| 157 HInstruction instruction = readLocal(from); |
| 158 redirectElement(from, to); |
| 159 updateLocal(from, instruction); |
| 160 } else { |
| 161 redirectElement(from, to); |
| 162 } |
| 163 }); |
| 164 } |
| 165 |
| 166 /// Replaces the current box with a new box and copies over the given list |
| 167 /// of elements from the old box into the new box. |
| 168 void updateCaptureBox( |
| 169 BoxLocal boxElement, List<LocalVariableElement> toBeCopiedElements) { |
| 170 // Create a new box and copy over the values from the old box into the |
| 171 // new one. |
| 172 HInstruction oldBox = readLocal(boxElement); |
| 173 HInstruction newBox = createBox(); |
| 174 for (LocalVariableElement boxedVariable in toBeCopiedElements) { |
| 175 // [readLocal] uses the [boxElement] to find its box. By replacing it |
| 176 // behind its back we can still get to the old values. |
| 177 updateLocal(boxElement, oldBox); |
| 178 HInstruction oldValue = readLocal(boxedVariable); |
| 179 updateLocal(boxElement, newBox); |
| 180 updateLocal(boxedVariable, oldValue); |
| 181 } |
| 182 updateLocal(boxElement, newBox); |
| 183 } |
| 184 |
| 185 /// Documentation wanted -- johnniwinther |
| 186 /// |
| 187 /// Invariant: [function] must be an implementation element. |
| 188 void startFunction(AstElement element, ast.Node node) { |
| 189 assert(invariant(element, element.isImplementation)); |
| 190 closureData = compiler.closureToClassMapper |
| 191 .computeClosureToClassMapping(element.resolvedAst); |
| 192 |
| 193 if (element is FunctionElement) { |
| 194 FunctionElement functionElement = element; |
| 195 FunctionSignature params = functionElement.functionSignature; |
| 196 ClosureScope scopeData = closureData.capturingScopes[node]; |
| 197 params.orderedForEachParameter((ParameterElement parameterElement) { |
| 198 if (element.isGenerativeConstructorBody) { |
| 199 if (scopeData != null && |
| 200 scopeData.isCapturedVariable(parameterElement)) { |
| 201 // The parameter will be a field in the box passed as the |
| 202 // last parameter. So no need to have it. |
| 203 return; |
| 204 } |
| 205 } |
| 206 HInstruction parameter = builder.addParameter(parameterElement, |
| 207 TypeMaskFactory.inferredTypeForElement(parameterElement, compiler)); |
| 208 builder.parameters[parameterElement] = parameter; |
| 209 directLocals[parameterElement] = parameter; |
| 210 }); |
| 211 } |
| 212 |
| 213 enterScope(node, element); |
| 214 |
| 215 // If the freeVariableMapping is not empty, then this function was a |
| 216 // nested closure that captures variables. Redirect the captured |
| 217 // variables to fields in the closure. |
| 218 closureData.forEachFreeVariable((Local from, CapturedVariable to) { |
| 219 redirectElement(from, to); |
| 220 }); |
| 221 JavaScriptBackend backend = compiler.backend; |
| 222 if (closureData.isClosure) { |
| 223 // Inside closure redirect references to itself to [:this:]. |
| 224 HThis thisInstruction = |
| 225 new HThis(closureData.thisLocal, backend.nonNullType); |
| 226 builder.graph.thisInstruction = thisInstruction; |
| 227 builder.graph.entry.addAtEntry(thisInstruction); |
| 228 updateLocal(closureData.closureElement, thisInstruction); |
| 229 } else if (element.isInstanceMember) { |
| 230 // Once closures have been mapped to classes their instance members might |
| 231 // not have any thisElement if the closure was created inside a static |
| 232 // context. |
| 233 HThis thisInstruction = new HThis(closureData.thisLocal, getTypeOfThis()); |
| 234 builder.graph.thisInstruction = thisInstruction; |
| 235 builder.graph.entry.addAtEntry(thisInstruction); |
| 236 directLocals[closureData.thisLocal] = thisInstruction; |
| 237 } |
| 238 |
| 239 // If this method is an intercepted method, add the extra |
| 240 // parameter to it, that is the actual receiver for intercepted |
| 241 // classes, or the same as [:this:] for non-intercepted classes. |
| 242 ClassElement cls = element.enclosingClass; |
| 243 |
| 244 // When the class extends a native class, the instance is pre-constructed |
| 245 // and passed to the generative constructor factory function as a parameter. |
| 246 // Instead of allocating and initializing the object, the constructor |
| 247 // 'upgrades' the native subclass object by initializing the Dart fields. |
| 248 bool isNativeUpgradeFactory = |
| 249 element.isGenerativeConstructor && backend.isNativeOrExtendsNative(cls); |
| 250 if (backend.isInterceptedMethod(element)) { |
| 251 bool isInterceptorClass = backend.isInterceptorClass(cls.declaration); |
| 252 String name = isInterceptorClass ? 'receiver' : '_'; |
| 253 SyntheticLocal parameter = new SyntheticLocal(name, executableContext); |
| 254 HParameterValue value = new HParameterValue(parameter, getTypeOfThis()); |
| 255 builder.graph.explicitReceiverParameter = value; |
| 256 builder.graph.entry.addAfter(directLocals[closureData.thisLocal], value); |
| 257 if (builder.lastAddedParameter == null) { |
| 258 // If this is the first parameter inserted, make sure it stays first. |
| 259 builder.lastAddedParameter = value; |
| 260 } |
| 261 if (isInterceptorClass) { |
| 262 // Only use the extra parameter in intercepted classes. |
| 263 directLocals[closureData.thisLocal] = value; |
| 264 } |
| 265 } else if (isNativeUpgradeFactory) { |
| 266 SyntheticLocal parameter = |
| 267 new SyntheticLocal('receiver', executableContext); |
| 268 // Unlike `this`, receiver is nullable since direct calls to generative |
| 269 // constructor call the constructor with `null`. |
| 270 ClassWorld classWorld = compiler.world; |
| 271 HParameterValue value = |
| 272 new HParameterValue(parameter, new TypeMask.exact(cls, classWorld)); |
| 273 builder.graph.explicitReceiverParameter = value; |
| 274 builder.graph.entry.addAtEntry(value); |
| 275 } |
| 276 } |
| 277 |
| 278 /// Returns true if the local can be accessed directly. Boxed variables or |
| 279 /// captured variables that are stored in the closure-field return [:false:]. |
| 280 bool isAccessedDirectly(Local local) { |
| 281 assert(local != null); |
| 282 return !redirectionMapping.containsKey(local) && |
| 283 !closureData.variablesUsedInTryOrGenerator.contains(local); |
| 284 } |
| 285 |
| 286 bool isStoredInClosureField(Local local) { |
| 287 assert(local != null); |
| 288 if (isAccessedDirectly(local)) return false; |
| 289 CapturedVariable redirectTarget = redirectionMapping[local]; |
| 290 if (redirectTarget == null) return false; |
| 291 return redirectTarget is ClosureFieldElement; |
| 292 } |
| 293 |
| 294 bool isBoxed(Local local) { |
| 295 if (isAccessedDirectly(local)) return false; |
| 296 if (isStoredInClosureField(local)) return false; |
| 297 return redirectionMapping.containsKey(local); |
| 298 } |
| 299 |
| 300 bool isUsedInTryOrGenerator(Local local) { |
| 301 return closureData.variablesUsedInTryOrGenerator.contains(local); |
| 302 } |
| 303 |
| 304 /// Returns an [HInstruction] for the given element. If the element is |
| 305 /// boxed or stored in a closure then the method generates code to retrieve |
| 306 /// the value. |
| 307 HInstruction readLocal(Local local, {SourceInformation sourceInformation}) { |
| 308 if (isAccessedDirectly(local)) { |
| 309 if (directLocals[local] == null) { |
| 310 if (local is TypeVariableElement) { |
| 311 compiler.reporter.internalError(compiler.currentElement, |
| 312 "Runtime type information not available for $local."); |
| 313 } else { |
| 314 compiler.reporter.internalError( |
| 315 local, "Cannot find value $local in ${directLocals.keys}."); |
| 316 } |
| 317 } |
| 318 HInstruction value = directLocals[local]; |
| 319 if (sourceInformation != null) { |
| 320 value = new HRef(value, sourceInformation); |
| 321 builder.add(value); |
| 322 } |
| 323 return value; |
| 324 } else if (isStoredInClosureField(local)) { |
| 325 ClosureFieldElement redirect = redirectionMapping[local]; |
| 326 HInstruction receiver = readLocal(closureData.closureElement); |
| 327 TypeMask type = local is BoxLocal |
| 328 ? (compiler.backend as JavaScriptBackend).nonNullType |
| 329 : getTypeOfCapturedVariable(redirect); |
| 330 HInstruction fieldGet = new HFieldGet(redirect, receiver, type); |
| 331 builder.add(fieldGet); |
| 332 return fieldGet..sourceInformation = sourceInformation; |
| 333 } else if (isBoxed(local)) { |
| 334 BoxFieldElement redirect = redirectionMapping[local]; |
| 335 // In the function that declares the captured variable the box is |
| 336 // accessed as direct local. Inside the nested closure the box is |
| 337 // accessed through a closure-field. |
| 338 // Calling [readLocal] makes sure we generate the correct code to get |
| 339 // the box. |
| 340 HInstruction box = readLocal(redirect.box); |
| 341 HInstruction lookup = |
| 342 new HFieldGet(redirect, box, getTypeOfCapturedVariable(redirect)); |
| 343 builder.add(lookup); |
| 344 return lookup..sourceInformation = sourceInformation; |
| 345 } else { |
| 346 assert(isUsedInTryOrGenerator(local)); |
| 347 HLocalValue localValue = getLocal(local); |
| 348 HInstruction instruction = new HLocalGet( |
| 349 local, |
| 350 localValue, |
| 351 (compiler.backend as JavaScriptBackend).dynamicType, |
| 352 sourceInformation); |
| 353 builder.add(instruction); |
| 354 return instruction; |
| 355 } |
| 356 } |
| 357 |
| 358 HInstruction readThis() { |
| 359 HInstruction res = readLocal(closureData.thisLocal); |
| 360 if (res.instructionType == null) { |
| 361 res.instructionType = getTypeOfThis(); |
| 362 } |
| 363 return res; |
| 364 } |
| 365 |
| 366 HLocalValue getLocal(Local local, {SourceInformation sourceInformation}) { |
| 367 // If the element is a parameter, we already have a |
| 368 // HParameterValue for it. We cannot create another one because |
| 369 // it could then have another name than the real parameter. And |
| 370 // the other one would not know it is just a copy of the real |
| 371 // parameter. |
| 372 if (local is ParameterElement) { |
| 373 assert(invariant(local, builder.parameters.containsKey(local), |
| 374 message: "No local value for parameter $local in " |
| 375 "${builder.parameters}.")); |
| 376 return builder.parameters[local]; |
| 377 } |
| 378 |
| 379 return activationVariables.putIfAbsent(local, () { |
| 380 JavaScriptBackend backend = compiler.backend; |
| 381 HLocalValue localValue = new HLocalValue(local, backend.nonNullType) |
| 382 ..sourceInformation = sourceInformation; |
| 383 builder.graph.entry.addAtExit(localValue); |
| 384 return localValue; |
| 385 }); |
| 386 } |
| 387 |
| 388 Local getTypeVariableAsLocal(TypeVariableType type) { |
| 389 return typeVariableLocals.putIfAbsent(type, () { |
| 390 return new TypeVariableLocal(type, executableContext); |
| 391 }); |
| 392 } |
| 393 |
| 394 /// Sets the [element] to [value]. If the element is boxed or stored in a |
| 395 /// closure then the method generates code to set the value. |
| 396 void updateLocal(Local local, HInstruction value, |
| 397 {SourceInformation sourceInformation}) { |
| 398 if (value is HRef) { |
| 399 HRef ref = value; |
| 400 value = ref.value; |
| 401 } |
| 402 assert(!isStoredInClosureField(local)); |
| 403 if (isAccessedDirectly(local)) { |
| 404 directLocals[local] = value; |
| 405 } else if (isBoxed(local)) { |
| 406 BoxFieldElement redirect = redirectionMapping[local]; |
| 407 // The box itself could be captured, or be local. A local variable that |
| 408 // is captured will be boxed, but the box itself will be a local. |
| 409 // Inside the closure the box is stored in a closure-field and cannot |
| 410 // be accessed directly. |
| 411 HInstruction box = readLocal(redirect.box); |
| 412 builder.add(new HFieldSet(redirect, box, value) |
| 413 ..sourceInformation = sourceInformation); |
| 414 } else { |
| 415 assert(isUsedInTryOrGenerator(local)); |
| 416 HLocalValue localValue = getLocal(local); |
| 417 builder.add(new HLocalSet(local, localValue, value) |
| 418 ..sourceInformation = sourceInformation); |
| 419 } |
| 420 } |
| 421 |
| 422 /// This function, startLoop, must be called before visiting any children of |
| 423 /// the loop. In particular it needs to be called before executing the |
| 424 /// initializers. |
| 425 /// |
| 426 /// The [LocalsHandler] will make the boxes and updates at the right moment. |
| 427 /// The builder just needs to call [enterLoopBody] and [enterLoopUpdates] |
| 428 /// (for [ast.For] loops) at the correct places. For phi-handling |
| 429 /// [beginLoopHeader] and [endLoop] must also be called. |
| 430 /// |
| 431 /// The correct place for the box depends on the given loop. In most cases |
| 432 /// the box will be created when entering the loop-body: while, do-while, and |
| 433 /// for-in (assuming the call to [:next:] is inside the body) can always be |
| 434 /// constructed this way. |
| 435 /// |
| 436 /// Things are slightly more complicated for [ast.For] loops. If no declared |
| 437 /// loop variable is boxed then the loop-body approach works here too. If a |
| 438 /// loop-variable is boxed we need to introduce a new box for the |
| 439 /// loop-variable before we enter the initializer so that the initializer |
| 440 /// writes the values into the box. In any case we need to create the box |
| 441 /// before the condition since the condition could box the variable. |
| 442 /// Since the first box is created outside the actual loop we have a second |
| 443 /// location where a box is created: just before the updates. This is |
| 444 /// necessary since updates are considered to be part of the next iteration |
| 445 /// (and can again capture variables). |
| 446 /// |
| 447 /// For example the following Dart code prints 1 3 -- 3 4. |
| 448 /// |
| 449 /// var fs = []; |
| 450 /// for (var i = 0; i < 3; (f() { fs.add(f); print(i); i++; })()) { |
| 451 /// i++; |
| 452 /// } |
| 453 /// print("--"); |
| 454 /// for (var i = 0; i < 2; i++) fs[i](); |
| 455 /// |
| 456 /// We solve this by emitting the following code (only for [ast.For] loops): |
| 457 /// <Create box> <== move the first box creation outside the loop. |
| 458 /// <initializer>; |
| 459 /// loop-entry: |
| 460 /// if (!<condition>) goto loop-exit; |
| 461 /// <body> |
| 462 /// <update box> // create a new box and copy the captured loop-variables. |
| 463 /// <updates> |
| 464 /// goto loop-entry; |
| 465 /// loop-exit: |
| 466 void startLoop(ast.Node node) { |
| 467 ClosureScope scopeData = closureData.capturingScopes[node]; |
| 468 if (scopeData == null) return; |
| 469 if (scopeData.hasBoxedLoopVariables()) { |
| 470 // If there are boxed loop variables then we set up the box and |
| 471 // redirections already now. This way the initializer can write its |
| 472 // values into the box. |
| 473 // For other loops the box will be created when entering the body. |
| 474 enterScope(node, null); |
| 475 } |
| 476 } |
| 477 |
| 478 /// Create phis at the loop entry for local variables (ready for the values |
| 479 /// from the back edge). Populate the phis with the current values. |
| 480 void beginLoopHeader(HBasicBlock loopEntry) { |
| 481 // Create a copy because we modify the map while iterating over it. |
| 482 Map<Local, HInstruction> savedDirectLocals = |
| 483 new Map<Local, HInstruction>.from(directLocals); |
| 484 |
| 485 JavaScriptBackend backend = compiler.backend; |
| 486 // Create phis for all elements in the definitions environment. |
| 487 savedDirectLocals.forEach((Local local, HInstruction instruction) { |
| 488 if (isAccessedDirectly(local)) { |
| 489 // We know 'this' cannot be modified. |
| 490 if (local != closureData.thisLocal) { |
| 491 HPhi phi = |
| 492 new HPhi.singleInput(local, instruction, backend.dynamicType); |
| 493 loopEntry.addPhi(phi); |
| 494 directLocals[local] = phi; |
| 495 } else { |
| 496 directLocals[local] = instruction; |
| 497 } |
| 498 } |
| 499 }); |
| 500 } |
| 501 |
| 502 void enterLoopBody(ast.Node node) { |
| 503 ClosureScope scopeData = closureData.capturingScopes[node]; |
| 504 if (scopeData == null) return; |
| 505 // If there are no declared boxed loop variables then we did not create the |
| 506 // box before the initializer and we have to create the box now. |
| 507 if (!scopeData.hasBoxedLoopVariables()) { |
| 508 enterScope(node, null); |
| 509 } |
| 510 } |
| 511 |
| 512 void enterLoopUpdates(ast.Node node) { |
| 513 // If there are declared boxed loop variables then the updates might have |
| 514 // access to the box and we must switch to a new box before executing the |
| 515 // updates. |
| 516 // In all other cases a new box will be created when entering the body of |
| 517 // the next iteration. |
| 518 ClosureScope scopeData = closureData.capturingScopes[node]; |
| 519 if (scopeData == null) return; |
| 520 if (scopeData.hasBoxedLoopVariables()) { |
| 521 updateCaptureBox(scopeData.boxElement, scopeData.boxedLoopVariables); |
| 522 } |
| 523 } |
| 524 |
| 525 /// Goes through the phis created in beginLoopHeader entry and adds the |
| 526 /// input from the back edge (from the current value of directLocals) to them. |
| 527 void endLoop(HBasicBlock loopEntry) { |
| 528 // If the loop has an aborting body, we don't update the loop |
| 529 // phis. |
| 530 if (loopEntry.predecessors.length == 1) return; |
| 531 loopEntry.forEachPhi((HPhi phi) { |
| 532 Local element = phi.sourceElement; |
| 533 HInstruction postLoopDefinition = directLocals[element]; |
| 534 phi.addInput(postLoopDefinition); |
| 535 }); |
| 536 } |
| 537 |
| 538 /// Merge [otherLocals] into this locals handler, creating phi-nodes when |
| 539 /// there is a conflict. |
| 540 /// If a phi node is necessary, it will use this handler's instruction as the |
| 541 /// first input, and the otherLocals instruction as the second. |
| 542 void mergeWith(LocalsHandler otherLocals, HBasicBlock joinBlock) { |
| 543 // If an element is in one map but not the other we can safely |
| 544 // ignore it. It means that a variable was declared in the |
| 545 // block. Since variable declarations are scoped the declared |
| 546 // variable cannot be alive outside the block. Note: this is only |
| 547 // true for nodes where we do joins. |
| 548 Map<Local, HInstruction> joinedLocals = new Map<Local, HInstruction>(); |
| 549 JavaScriptBackend backend = compiler.backend; |
| 550 otherLocals.directLocals.forEach((Local local, HInstruction instruction) { |
| 551 // We know 'this' cannot be modified. |
| 552 if (local == closureData.thisLocal) { |
| 553 assert(directLocals[local] == instruction); |
| 554 joinedLocals[local] = instruction; |
| 555 } else { |
| 556 HInstruction mine = directLocals[local]; |
| 557 if (mine == null) return; |
| 558 if (identical(instruction, mine)) { |
| 559 joinedLocals[local] = instruction; |
| 560 } else { |
| 561 HInstruction phi = new HPhi.manyInputs( |
| 562 local, <HInstruction>[mine, instruction], backend.dynamicType); |
| 563 joinBlock.addPhi(phi); |
| 564 joinedLocals[local] = phi; |
| 565 } |
| 566 } |
| 567 }); |
| 568 directLocals = joinedLocals; |
| 569 } |
| 570 |
| 571 /// When control flow merges, this method can be used to merge several |
| 572 /// localsHandlers into a new one using phis. The new localsHandler is |
| 573 /// returned. Unless it is also in the list, the current localsHandler is not |
| 574 /// used for its values, only for its declared variables. This is a way to |
| 575 /// exclude local values from the result when they are no longer in scope. |
| 576 LocalsHandler mergeMultiple( |
| 577 List<LocalsHandler> localsHandlers, HBasicBlock joinBlock) { |
| 578 assert(localsHandlers.length > 0); |
| 579 if (localsHandlers.length == 1) return localsHandlers[0]; |
| 580 Map<Local, HInstruction> joinedLocals = new Map<Local, HInstruction>(); |
| 581 HInstruction thisValue = null; |
| 582 JavaScriptBackend backend = compiler.backend; |
| 583 directLocals.forEach((Local local, HInstruction instruction) { |
| 584 if (local != closureData.thisLocal) { |
| 585 HPhi phi = new HPhi.noInputs(local, backend.dynamicType); |
| 586 joinedLocals[local] = phi; |
| 587 joinBlock.addPhi(phi); |
| 588 } else { |
| 589 // We know that "this" never changes, if it's there. |
| 590 // Save it for later. While merging, there is no phi for "this", |
| 591 // so we don't have to special case it in the merge loop. |
| 592 thisValue = instruction; |
| 593 } |
| 594 }); |
| 595 for (LocalsHandler handler in localsHandlers) { |
| 596 handler.directLocals.forEach((Local local, HInstruction instruction) { |
| 597 HPhi phi = joinedLocals[local]; |
| 598 if (phi != null) { |
| 599 phi.addInput(instruction); |
| 600 } |
| 601 }); |
| 602 } |
| 603 if (thisValue != null) { |
| 604 // If there was a "this" for the scope, add it to the new locals. |
| 605 joinedLocals[closureData.thisLocal] = thisValue; |
| 606 } |
| 607 |
| 608 // Remove locals that are not in all handlers. |
| 609 directLocals = new Map<Local, HInstruction>(); |
| 610 joinedLocals.forEach((Local local, HInstruction instruction) { |
| 611 if (local != closureData.thisLocal && |
| 612 instruction.inputs.length != localsHandlers.length) { |
| 613 joinBlock.removePhi(instruction); |
| 614 } else { |
| 615 directLocals[local] = instruction; |
| 616 } |
| 617 }); |
| 618 return this; |
| 619 } |
| 620 |
| 621 TypeMask cachedTypeOfThis; |
| 622 |
| 623 TypeMask getTypeOfThis() { |
| 624 TypeMask result = cachedTypeOfThis; |
| 625 if (result == null) { |
| 626 ThisLocal local = closureData.thisLocal; |
| 627 ClassElement cls = local.enclosingClass; |
| 628 ClassWorld classWorld = compiler.world; |
| 629 if (classWorld.isUsedAsMixin(cls)) { |
| 630 // If the enclosing class is used as a mixin, [:this:] can be |
| 631 // of the class that mixins the enclosing class. These two |
| 632 // classes do not have a subclass relationship, so, for |
| 633 // simplicity, we mark the type as an interface type. |
| 634 result = new TypeMask.nonNullSubtype(cls.declaration, compiler.world); |
| 635 } else { |
| 636 result = new TypeMask.nonNullSubclass(cls.declaration, compiler.world); |
| 637 } |
| 638 cachedTypeOfThis = result; |
| 639 } |
| 640 return result; |
| 641 } |
| 642 |
| 643 Map<Element, TypeMask> cachedTypesOfCapturedVariables = |
| 644 new Map<Element, TypeMask>(); |
| 645 |
| 646 TypeMask getTypeOfCapturedVariable(Element element) { |
| 647 assert(element.isField); |
| 648 return cachedTypesOfCapturedVariables.putIfAbsent(element, () { |
| 649 return TypeMaskFactory.inferredTypeForElement(element, compiler); |
| 650 }); |
| 651 } |
| 652 |
| 653 /// Variables stored in the current activation. These variables are |
| 654 /// being updated in try/catch blocks, and should be |
| 655 /// accessed indirectly through [HLocalGet] and [HLocalSet]. |
| 656 Map<Local, HLocalValue> activationVariables = <Local, HLocalValue>{}; |
| 657 } |
| OLD | NEW |