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