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 part of ssa; | |
6 | |
7 /// A synthetic local variable only used with the SSA graph. | |
8 /// | |
9 /// For instance used for holding return value of function or the exception of a | |
10 /// try-catch statement. | |
11 class SyntheticLocal extends Local { | |
12 final String name; | |
13 final ExecutableElement executableContext; | |
14 | |
15 SyntheticLocal(this.name, this.executableContext); | |
16 } | |
17 | |
18 class SsaBuilderTask extends CompilerTask { | |
19 final CodeEmitterTask emitter; | |
20 final JavaScriptBackend backend; | |
21 | |
22 String get name => 'SSA builder'; | |
23 | |
24 SsaBuilderTask(JavaScriptBackend backend) | |
25 : emitter = backend.emitter, | |
26 backend = backend, | |
27 super(backend.compiler); | |
28 | |
29 HGraph build(CodegenWorkItem work) { | |
30 return measure(() { | |
31 Element element = work.element.implementation; | |
32 return compiler.withCurrentElement(element, () { | |
33 HInstruction.idCounter = 0; | |
34 SsaBuilder builder = | |
35 new SsaBuilder(backend, work, emitter.nativeEmitter); | |
36 HGraph graph; | |
37 ElementKind kind = element.kind; | |
38 if (kind == ElementKind.GENERATIVE_CONSTRUCTOR) { | |
39 graph = compileConstructor(builder, work); | |
40 } else if (kind == ElementKind.GENERATIVE_CONSTRUCTOR_BODY || | |
41 kind == ElementKind.FUNCTION || | |
42 kind == ElementKind.GETTER || | |
43 kind == ElementKind.SETTER) { | |
44 graph = builder.buildMethod(element); | |
45 } else if (kind == ElementKind.FIELD) { | |
46 if (element.isInstanceMember) { | |
47 assert(compiler.enableTypeAssertions); | |
48 graph = builder.buildCheckedSetter(element); | |
49 } else { | |
50 graph = builder.buildLazyInitializer(element); | |
51 } | |
52 } else { | |
53 compiler.internalError(element, 'Unexpected element kind $kind.'); | |
54 } | |
55 assert(graph.isValid()); | |
56 if (!identical(kind, ElementKind.FIELD)) { | |
57 FunctionElement function = element; | |
58 FunctionSignature signature = function.functionSignature; | |
59 signature.forEachOptionalParameter((ParameterElement parameter) { | |
60 // This ensures the default value will be computed. | |
61 ConstantValue constant = | |
62 backend.constants.getConstantForVariable(parameter).value; | |
63 CodegenRegistry registry = work.registry; | |
64 registry.registerCompileTimeConstant(constant); | |
65 }); | |
66 } | |
67 if (compiler.tracer.isEnabled) { | |
68 String name; | |
69 if (element.isClassMember) { | |
70 String className = element.enclosingClass.name; | |
71 String memberName = element.name; | |
72 name = "$className.$memberName"; | |
73 if (element.isGenerativeConstructorBody) { | |
74 name = "$name (body)"; | |
75 } | |
76 } else { | |
77 name = "${element.name}"; | |
78 } | |
79 compiler.tracer.traceCompilation( | |
80 name, work.compilationContext); | |
81 compiler.tracer.traceGraph('builder', graph); | |
82 } | |
83 return graph; | |
84 }); | |
85 }); | |
86 } | |
87 | |
88 HGraph compileConstructor(SsaBuilder builder, CodegenWorkItem work) { | |
89 return builder.buildFactory(work.element); | |
90 } | |
91 } | |
92 | |
93 /** | |
94 * Keeps track of locals (including parameters and phis) when building. The | |
95 * 'this' reference is treated as parameter and hence handled by this class, | |
96 * too. | |
97 */ | |
98 class LocalsHandler { | |
99 /** | |
100 * The values of locals that can be directly accessed (without redirections | |
101 * to boxes or closure-fields). | |
102 * | |
103 * [directLocals] is iterated, so it is "insertion ordered" to make the | |
104 * iteration order a function only of insertions and not a function of | |
105 * e.g. Element hash codes. I'd prefer to use a SortedMap but some elements | |
106 * don't have source locations for [Elements.compareByPosition]. | |
107 */ | |
108 Map<Local, HInstruction> directLocals = | |
109 new Map<Local, HInstruction>(); | |
110 Map<Local, CapturedVariable> redirectionMapping = | |
111 new Map<Local, CapturedVariable>(); | |
112 SsaBuilder builder; | |
113 ClosureClassMap closureData; | |
114 Map<TypeVariableType, TypeVariableLocal> typeVariableLocals = | |
115 new Map<TypeVariableType, TypeVariableLocal>(); | |
116 final ExecutableElement executableContext; | |
117 | |
118 /// The class that defines the current type environment or null if no type | |
119 /// variables are in scope. | |
120 ClassElement get contextClass => executableContext.contextClass; | |
121 | |
122 LocalsHandler(this.builder, this.executableContext); | |
123 | |
124 /// Substituted type variables occurring in [type] into the context of | |
125 /// [contextClass]. | |
126 DartType substInContext(DartType type) { | |
127 if (contextClass != null) { | |
128 ClassElement typeContext = Types.getClassContext(type); | |
129 if (typeContext != null) { | |
130 type = type.substByContext( | |
131 contextClass.asInstanceOf(typeContext)); | |
132 } | |
133 } | |
134 return type; | |
135 } | |
136 | |
137 get typesTask => builder.compiler.typesTask; | |
138 | |
139 /** | |
140 * Creates a new [LocalsHandler] based on [other]. We only need to | |
141 * copy the [directLocals], since the other fields can be shared | |
142 * throughout the AST visit. | |
143 */ | |
144 LocalsHandler.from(LocalsHandler other) | |
145 : directLocals = new Map<Local, HInstruction>.from(other.directLocals), | |
146 redirectionMapping = other.redirectionMapping, | |
147 executableContext = other.executableContext, | |
148 builder = other.builder, | |
149 closureData = other.closureData; | |
150 | |
151 /** | |
152 * Redirects accesses from element [from] to element [to]. The [to] element | |
153 * must be a boxed variable or a variable that is stored in a closure-field. | |
154 */ | |
155 void redirectElement(Local from, CapturedVariable to) { | |
156 assert(redirectionMapping[from] == null); | |
157 redirectionMapping[from] = to; | |
158 assert(isStoredInClosureField(from) || isBoxed(from)); | |
159 } | |
160 | |
161 HInstruction createBox() { | |
162 // TODO(floitsch): Clean up this hack. Should we create a box-object by | |
163 // just creating an empty object literal? | |
164 JavaScriptBackend backend = builder.backend; | |
165 HInstruction box = new HForeign(js.js.parseForeignJS('{}'), | |
166 backend.nonNullType, | |
167 <HInstruction>[]); | |
168 builder.add(box); | |
169 return box; | |
170 } | |
171 | |
172 /** | |
173 * If the scope (function or loop) [node] has captured variables then this | |
174 * method creates a box and sets up the redirections. | |
175 */ | |
176 void enterScope(ast.Node node, Element element) { | |
177 // See if any variable in the top-scope of the function is captured. If yes | |
178 // we need to create a box-object. | |
179 ClosureScope scopeData = closureData.capturingScopes[node]; | |
180 if (scopeData == null) return; | |
181 HInstruction box; | |
182 // The scope has captured variables. | |
183 if (element != null && element.isGenerativeConstructorBody) { | |
184 // The box is passed as a parameter to a generative | |
185 // constructor body. | |
186 JavaScriptBackend backend = builder.backend; | |
187 box = builder.addParameter(scopeData.boxElement, backend.nonNullType); | |
188 } else { | |
189 box = createBox(); | |
190 } | |
191 // Add the box to the known locals. | |
192 directLocals[scopeData.boxElement] = box; | |
193 // Make sure that accesses to the boxed locals go into the box. We also | |
194 // need to make sure that parameters are copied into the box if necessary. | |
195 scopeData.forEachCapturedVariable( | |
196 (LocalVariableElement from, BoxFieldElement to) { | |
197 // The [from] can only be a parameter for function-scopes and not | |
198 // loop scopes. | |
199 if (from.isParameter && !element.isGenerativeConstructorBody) { | |
200 // Now that the redirection is set up, the update to the local will | |
201 // write the parameter value into the box. | |
202 // Store the captured parameter in the box. Get the current value | |
203 // before we put the redirection in place. | |
204 // We don't need to update the local for a generative | |
205 // constructor body, because it receives a box that already | |
206 // contains the updates as the last parameter. | |
207 HInstruction instruction = readLocal(from); | |
208 redirectElement(from, to); | |
209 updateLocal(from, instruction); | |
210 } else { | |
211 redirectElement(from, to); | |
212 } | |
213 }); | |
214 } | |
215 | |
216 /** | |
217 * Replaces the current box with a new box and copies over the given list | |
218 * of elements from the old box into the new box. | |
219 */ | |
220 void updateCaptureBox(BoxLocal boxElement, | |
221 List<LocalVariableElement> toBeCopiedElements) { | |
222 // Create a new box and copy over the values from the old box into the | |
223 // new one. | |
224 HInstruction oldBox = readLocal(boxElement); | |
225 HInstruction newBox = createBox(); | |
226 for (LocalVariableElement boxedVariable in toBeCopiedElements) { | |
227 // [readLocal] uses the [boxElement] to find its box. By replacing it | |
228 // behind its back we can still get to the old values. | |
229 updateLocal(boxElement, oldBox); | |
230 HInstruction oldValue = readLocal(boxedVariable); | |
231 updateLocal(boxElement, newBox); | |
232 updateLocal(boxedVariable, oldValue); | |
233 } | |
234 updateLocal(boxElement, newBox); | |
235 } | |
236 | |
237 /** | |
238 * Documentation wanted -- johnniwinther | |
239 * | |
240 * Invariant: [function] must be an implementation element. | |
241 */ | |
242 void startFunction(Element element, ast.Node node) { | |
243 assert(invariant(element, element.isImplementation)); | |
244 Compiler compiler = builder.compiler; | |
245 closureData = compiler.closureToClassMapper.computeClosureToClassMapping( | |
246 element, node, builder.elements); | |
247 | |
248 if (element is FunctionElement) { | |
249 FunctionElement functionElement = element; | |
250 FunctionSignature params = functionElement.functionSignature; | |
251 ClosureScope scopeData = closureData.capturingScopes[node]; | |
252 params.orderedForEachParameter((ParameterElement parameterElement) { | |
253 if (element.isGenerativeConstructorBody) { | |
254 if (scopeData != null && | |
255 scopeData.isCapturedVariable(parameterElement)) { | |
256 // The parameter will be a field in the box passed as the | |
257 // last parameter. So no need to have it. | |
258 return; | |
259 } | |
260 } | |
261 HInstruction parameter = builder.addParameter( | |
262 parameterElement, | |
263 TypeMaskFactory.inferredTypeForElement(parameterElement, compiler)); | |
264 builder.parameters[parameterElement] = parameter; | |
265 directLocals[parameterElement] = parameter; | |
266 }); | |
267 } | |
268 | |
269 enterScope(node, element); | |
270 | |
271 // If the freeVariableMapping is not empty, then this function was a | |
272 // nested closure that captures variables. Redirect the captured | |
273 // variables to fields in the closure. | |
274 closureData.forEachFreeVariable((Local from, CapturedVariable to) { | |
275 redirectElement(from, to); | |
276 }); | |
277 JavaScriptBackend backend = compiler.backend; | |
278 if (closureData.isClosure) { | |
279 // Inside closure redirect references to itself to [:this:]. | |
280 HThis thisInstruction = new HThis(closureData.thisLocal, | |
281 backend.nonNullType); | |
282 builder.graph.thisInstruction = thisInstruction; | |
283 builder.graph.entry.addAtEntry(thisInstruction); | |
284 updateLocal(closureData.closureElement, thisInstruction); | |
285 } else if (element.isInstanceMember) { | |
286 // Once closures have been mapped to classes their instance members might | |
287 // not have any thisElement if the closure was created inside a static | |
288 // context. | |
289 HThis thisInstruction = new HThis( | |
290 closureData.thisLocal, builder.getTypeOfThis()); | |
291 builder.graph.thisInstruction = thisInstruction; | |
292 builder.graph.entry.addAtEntry(thisInstruction); | |
293 directLocals[closureData.thisLocal] = thisInstruction; | |
294 } | |
295 | |
296 // If this method is an intercepted method, add the extra | |
297 // parameter to it, that is the actual receiver for intercepted | |
298 // classes, or the same as [:this:] for non-intercepted classes. | |
299 ClassElement cls = element.enclosingClass; | |
300 | |
301 // When the class extends a native class, the instance is pre-constructed | |
302 // and passed to the generative constructor factory function as a parameter. | |
303 // Instead of allocating and initializing the object, the constructor | |
304 // 'upgrades' the native subclass object by initializing the Dart fields. | |
305 bool isNativeUpgradeFactory = element.isGenerativeConstructor | |
306 && Elements.isNativeOrExtendsNative(cls); | |
307 if (backend.isInterceptedMethod(element)) { | |
308 bool isInterceptorClass = backend.isInterceptorClass(cls.declaration); | |
309 String name = isInterceptorClass ? 'receiver' : '_'; | |
310 SyntheticLocal parameter = new SyntheticLocal(name, executableContext); | |
311 HParameterValue value = | |
312 new HParameterValue(parameter, builder.getTypeOfThis()); | |
313 builder.graph.explicitReceiverParameter = value; | |
314 builder.graph.entry.addAfter( | |
315 directLocals[closureData.thisLocal], value); | |
316 if (isInterceptorClass) { | |
317 // Only use the extra parameter in intercepted classes. | |
318 directLocals[closureData.thisLocal] = value; | |
319 } | |
320 } else if (isNativeUpgradeFactory) { | |
321 SyntheticLocal parameter = | |
322 new SyntheticLocal('receiver', executableContext); | |
323 // Unlike `this`, receiver is nullable since direct calls to generative | |
324 // constructor call the constructor with `null`. | |
325 ClassWorld classWorld = compiler.world; | |
326 HParameterValue value = | |
327 new HParameterValue(parameter, new TypeMask.exact(cls, classWorld)); | |
328 builder.graph.explicitReceiverParameter = value; | |
329 builder.graph.entry.addAtEntry(value); | |
330 } | |
331 } | |
332 | |
333 /** | |
334 * Returns true if the local can be accessed directly. Boxed variables or | |
335 * captured variables that are stored in the closure-field return [:false:]. | |
336 */ | |
337 bool isAccessedDirectly(Local local) { | |
338 assert(local != null); | |
339 return !redirectionMapping.containsKey(local) | |
340 && !closureData.usedVariablesInTry.contains(local); | |
341 } | |
342 | |
343 bool isStoredInClosureField(Local local) { | |
344 assert(local != null); | |
345 if (isAccessedDirectly(local)) return false; | |
346 CapturedVariable redirectTarget = redirectionMapping[local]; | |
347 if (redirectTarget == null) return false; | |
348 return redirectTarget is ClosureFieldElement; | |
349 } | |
350 | |
351 bool isBoxed(Local local) { | |
352 if (isAccessedDirectly(local)) return false; | |
353 if (isStoredInClosureField(local)) return false; | |
354 return redirectionMapping.containsKey(local); | |
355 } | |
356 | |
357 bool isUsedInTry(Local local) { | |
358 return closureData.usedVariablesInTry.contains(local); | |
359 } | |
360 | |
361 /** | |
362 * Returns an [HInstruction] for the given element. If the element is | |
363 * boxed or stored in a closure then the method generates code to retrieve | |
364 * the value. | |
365 */ | |
366 HInstruction readLocal(Local local) { | |
367 if (isAccessedDirectly(local)) { | |
368 if (directLocals[local] == null) { | |
369 if (local is TypeVariableElement) { | |
370 builder.compiler.internalError(builder.compiler.currentElement, | |
371 "Runtime type information not available for $local."); | |
372 } else { | |
373 builder.compiler.internalError(local, | |
374 "Cannot find value $local."); | |
375 } | |
376 } | |
377 return directLocals[local]; | |
378 } else if (isStoredInClosureField(local)) { | |
379 ClosureFieldElement redirect = redirectionMapping[local]; | |
380 HInstruction receiver = readLocal(closureData.closureElement); | |
381 TypeMask type = local is BoxLocal | |
382 ? builder.backend.nonNullType | |
383 : builder.getTypeOfCapturedVariable(redirect); | |
384 HInstruction fieldGet = new HFieldGet(redirect, receiver, type); | |
385 builder.add(fieldGet); | |
386 return fieldGet; | |
387 } else if (isBoxed(local)) { | |
388 BoxFieldElement redirect = redirectionMapping[local]; | |
389 // In the function that declares the captured variable the box is | |
390 // accessed as direct local. Inside the nested closure the box is | |
391 // accessed through a closure-field. | |
392 // Calling [readLocal] makes sure we generate the correct code to get | |
393 // the box. | |
394 HInstruction box = readLocal(redirect.box); | |
395 HInstruction lookup = new HFieldGet( | |
396 redirect, box, builder.getTypeOfCapturedVariable(redirect)); | |
397 builder.add(lookup); | |
398 return lookup; | |
399 } else { | |
400 assert(isUsedInTry(local)); | |
401 HLocalValue localValue = getLocal(local); | |
402 HInstruction instruction = new HLocalGet( | |
403 local, localValue, builder.backend.dynamicType); | |
404 builder.add(instruction); | |
405 return instruction; | |
406 } | |
407 } | |
408 | |
409 HInstruction readThis() { | |
410 HInstruction res = readLocal(closureData.thisLocal); | |
411 if (res.instructionType == null) { | |
412 res.instructionType = builder.getTypeOfThis(); | |
413 } | |
414 return res; | |
415 } | |
416 | |
417 HLocalValue getLocal(Local local) { | |
418 // If the element is a parameter, we already have a | |
419 // HParameterValue for it. We cannot create another one because | |
420 // it could then have another name than the real parameter. And | |
421 // the other one would not know it is just a copy of the real | |
422 // parameter. | |
423 if (local is ParameterElement) return builder.parameters[local]; | |
424 | |
425 return builder.activationVariables.putIfAbsent(local, () { | |
426 JavaScriptBackend backend = builder.backend; | |
427 HLocalValue localValue = new HLocalValue(local, backend.nonNullType); | |
428 builder.graph.entry.addAtExit(localValue); | |
429 return localValue; | |
430 }); | |
431 } | |
432 | |
433 Local getTypeVariableAsLocal(TypeVariableType type) { | |
434 return typeVariableLocals.putIfAbsent(type, () { | |
435 return new TypeVariableLocal(type, executableContext); | |
436 }); | |
437 } | |
438 | |
439 /** | |
440 * Sets the [element] to [value]. If the element is boxed or stored in a | |
441 * closure then the method generates code to set the value. | |
442 */ | |
443 void updateLocal(Local local, HInstruction value) { | |
444 assert(!isStoredInClosureField(local)); | |
445 if (isAccessedDirectly(local)) { | |
446 directLocals[local] = value; | |
447 } else if (isBoxed(local)) { | |
448 BoxFieldElement redirect = redirectionMapping[local]; | |
449 // The box itself could be captured, or be local. A local variable that | |
450 // is captured will be boxed, but the box itself will be a local. | |
451 // Inside the closure the box is stored in a closure-field and cannot | |
452 // be accessed directly. | |
453 HInstruction box = readLocal(redirect.box); | |
454 builder.add(new HFieldSet(redirect, box, value)); | |
455 } else { | |
456 assert(isUsedInTry(local)); | |
457 HLocalValue localValue = getLocal(local); | |
458 builder.add(new HLocalSet(local, localValue, value)); | |
459 } | |
460 } | |
461 | |
462 /** | |
463 * This function, startLoop, must be called before visiting any children of | |
464 * the loop. In particular it needs to be called before executing the | |
465 * initializers. | |
466 * | |
467 * The [LocalsHandler] will make the boxes and updates at the right moment. | |
468 * The builder just needs to call [enterLoopBody] and [enterLoopUpdates] | |
469 * (for [ast.For] loops) at the correct places. For phi-handling | |
470 * [beginLoopHeader] and [endLoop] must also be called. | |
471 * | |
472 * The correct place for the box depends on the given loop. In most cases | |
473 * the box will be created when entering the loop-body: while, do-while, and | |
474 * for-in (assuming the call to [:next:] is inside the body) can always be | |
475 * constructed this way. | |
476 * | |
477 * Things are slightly more complicated for [ast.For] loops. If no declared | |
478 * loop variable is boxed then the loop-body approach works here too. If a | |
479 * loop-variable is boxed we need to introduce a new box for the | |
480 * loop-variable before we enter the initializer so that the initializer | |
481 * writes the values into the box. In any case we need to create the box | |
482 * before the condition since the condition could box the variable. | |
483 * Since the first box is created outside the actual loop we have a second | |
484 * location where a box is created: just before the updates. This is | |
485 * necessary since updates are considered to be part of the next iteration | |
486 * (and can again capture variables). | |
487 * | |
488 * For example the following Dart code prints 1 3 -- 3 4. | |
489 * | |
490 * var fs = []; | |
491 * for (var i = 0; i < 3; (f() { fs.add(f); print(i); i++; })()) { | |
492 * i++; | |
493 * } | |
494 * print("--"); | |
495 * for (var i = 0; i < 2; i++) fs[i](); | |
496 * | |
497 * We solve this by emitting the following code (only for [ast.For] loops): | |
498 * <Create box> <== move the first box creation outside the loop. | |
499 * <initializer>; | |
500 * loop-entry: | |
501 * if (!<condition>) goto loop-exit; | |
502 * <body> | |
503 * <update box> // create a new box and copy the captured loop-variables. | |
504 * <updates> | |
505 * goto loop-entry; | |
506 * loop-exit: | |
507 */ | |
508 void startLoop(ast.Node node) { | |
509 ClosureScope scopeData = closureData.capturingScopes[node]; | |
510 if (scopeData == null) return; | |
511 if (scopeData.hasBoxedLoopVariables()) { | |
512 // If there are boxed loop variables then we set up the box and | |
513 // redirections already now. This way the initializer can write its | |
514 // values into the box. | |
515 // For other loops the box will be created when entering the body. | |
516 enterScope(node, null); | |
517 } | |
518 } | |
519 | |
520 /** | |
521 * Create phis at the loop entry for local variables (ready for the values | |
522 * from the back edge). Populate the phis with the current values. | |
523 */ | |
524 void beginLoopHeader(HBasicBlock loopEntry) { | |
525 // Create a copy because we modify the map while iterating over it. | |
526 Map<Local, HInstruction> savedDirectLocals = | |
527 new Map<Local, HInstruction>.from(directLocals); | |
528 | |
529 JavaScriptBackend backend = builder.backend; | |
530 // Create phis for all elements in the definitions environment. | |
531 savedDirectLocals.forEach((Local local, | |
532 HInstruction instruction) { | |
533 if (isAccessedDirectly(local)) { | |
534 // We know 'this' cannot be modified. | |
535 if (local != closureData.thisLocal) { | |
536 HPhi phi = new HPhi.singleInput( | |
537 local, instruction, backend.dynamicType); | |
538 loopEntry.addPhi(phi); | |
539 directLocals[local] = phi; | |
540 } else { | |
541 directLocals[local] = instruction; | |
542 } | |
543 } | |
544 }); | |
545 } | |
546 | |
547 void enterLoopBody(ast.Node node) { | |
548 ClosureScope scopeData = closureData.capturingScopes[node]; | |
549 if (scopeData == null) return; | |
550 // If there are no declared boxed loop variables then we did not create the | |
551 // box before the initializer and we have to create the box now. | |
552 if (!scopeData.hasBoxedLoopVariables()) { | |
553 enterScope(node, null); | |
554 } | |
555 } | |
556 | |
557 void enterLoopUpdates(ast.Node node) { | |
558 // If there are declared boxed loop variables then the updates might have | |
559 // access to the box and we must switch to a new box before executing the | |
560 // updates. | |
561 // In all other cases a new box will be created when entering the body of | |
562 // the next iteration. | |
563 ClosureScope scopeData = closureData.capturingScopes[node]; | |
564 if (scopeData == null) return; | |
565 if (scopeData.hasBoxedLoopVariables()) { | |
566 updateCaptureBox(scopeData.boxElement, scopeData.boxedLoopVariables); | |
567 } | |
568 } | |
569 | |
570 /** | |
571 * Goes through the phis created in beginLoopHeader entry and adds the | |
572 * input from the back edge (from the current value of directLocals) to them. | |
573 */ | |
574 void endLoop(HBasicBlock loopEntry) { | |
575 // If the loop has an aborting body, we don't update the loop | |
576 // phis. | |
577 if (loopEntry.predecessors.length == 1) return; | |
578 loopEntry.forEachPhi((HPhi phi) { | |
579 Local element = phi.sourceElement; | |
580 HInstruction postLoopDefinition = directLocals[element]; | |
581 phi.addInput(postLoopDefinition); | |
582 }); | |
583 } | |
584 | |
585 /** | |
586 * Merge [otherLocals] into this locals handler, creating phi-nodes when | |
587 * there is a conflict. | |
588 * If a phi node is necessary, it will use this handler's instruction as the | |
589 * first input, and the otherLocals instruction as the second. | |
590 */ | |
591 void mergeWith(LocalsHandler otherLocals, HBasicBlock joinBlock) { | |
592 // If an element is in one map but not the other we can safely | |
593 // ignore it. It means that a variable was declared in the | |
594 // block. Since variable declarations are scoped the declared | |
595 // variable cannot be alive outside the block. Note: this is only | |
596 // true for nodes where we do joins. | |
597 Map<Local, HInstruction> joinedLocals = | |
598 new Map<Local, HInstruction>(); | |
599 JavaScriptBackend backend = builder.backend; | |
600 otherLocals.directLocals.forEach((Local local, | |
601 HInstruction instruction) { | |
602 // We know 'this' cannot be modified. | |
603 if (local == closureData.thisLocal) { | |
604 assert(directLocals[local] == instruction); | |
605 joinedLocals[local] = instruction; | |
606 } else { | |
607 HInstruction mine = directLocals[local]; | |
608 if (mine == null) return; | |
609 if (identical(instruction, mine)) { | |
610 joinedLocals[local] = instruction; | |
611 } else { | |
612 HInstruction phi = new HPhi.manyInputs( | |
613 local, <HInstruction>[mine, instruction], backend.dynamicType); | |
614 joinBlock.addPhi(phi); | |
615 joinedLocals[local] = phi; | |
616 } | |
617 } | |
618 }); | |
619 directLocals = joinedLocals; | |
620 } | |
621 | |
622 /** | |
623 * When control flow merges, this method can be used to merge several | |
624 * localsHandlers into a new one using phis. The new localsHandler is | |
625 * returned. Unless it is also in the list, the current localsHandler is not | |
626 * used for its values, only for its declared variables. This is a way to | |
627 * exclude local values from the result when they are no longer in scope. | |
628 */ | |
629 LocalsHandler mergeMultiple(List<LocalsHandler> localsHandlers, | |
630 HBasicBlock joinBlock) { | |
631 assert(localsHandlers.length > 0); | |
632 if (localsHandlers.length == 1) return localsHandlers[0]; | |
633 Map<Local, HInstruction> joinedLocals = | |
634 new Map<Local, HInstruction>(); | |
635 HInstruction thisValue = null; | |
636 JavaScriptBackend backend = builder.backend; | |
637 directLocals.forEach((Local local, HInstruction instruction) { | |
638 if (local != closureData.thisLocal) { | |
639 HPhi phi = new HPhi.noInputs(local, backend.dynamicType); | |
640 joinedLocals[local] = phi; | |
641 joinBlock.addPhi(phi); | |
642 } else { | |
643 // We know that "this" never changes, if it's there. | |
644 // Save it for later. While merging, there is no phi for "this", | |
645 // so we don't have to special case it in the merge loop. | |
646 thisValue = instruction; | |
647 } | |
648 }); | |
649 for (LocalsHandler handler in localsHandlers) { | |
650 handler.directLocals.forEach((Local local, | |
651 HInstruction instruction) { | |
652 HPhi phi = joinedLocals[local]; | |
653 if (phi != null) { | |
654 phi.addInput(instruction); | |
655 } | |
656 }); | |
657 } | |
658 if (thisValue != null) { | |
659 // If there was a "this" for the scope, add it to the new locals. | |
660 joinedLocals[closureData.thisLocal] = thisValue; | |
661 } | |
662 | |
663 // Remove locals that are not in all handlers. | |
664 directLocals = new Map<Local, HInstruction>(); | |
665 joinedLocals.forEach((Local local, | |
666 HInstruction instruction) { | |
667 if (local != closureData.thisLocal | |
668 && instruction.inputs.length != localsHandlers.length) { | |
669 joinBlock.removePhi(instruction); | |
670 } else { | |
671 directLocals[local] = instruction; | |
672 } | |
673 }); | |
674 return this; | |
675 } | |
676 } | |
677 | |
678 | |
679 // Represents a single break/continue instruction. | |
680 class JumpHandlerEntry { | |
681 final HJump jumpInstruction; | |
682 final LocalsHandler locals; | |
683 bool isBreak() => jumpInstruction is HBreak; | |
684 bool isContinue() => jumpInstruction is HContinue; | |
685 JumpHandlerEntry(this.jumpInstruction, this.locals); | |
686 } | |
687 | |
688 | |
689 abstract class JumpHandler { | |
690 factory JumpHandler(SsaBuilder builder, JumpTarget target) { | |
691 return new TargetJumpHandler(builder, target); | |
692 } | |
693 void generateBreak([LabelDefinition label]); | |
694 void generateContinue([LabelDefinition label]); | |
695 void forEachBreak(void action(HBreak instruction, LocalsHandler locals)); | |
696 void forEachContinue(void action(HContinue instruction, | |
697 LocalsHandler locals)); | |
698 bool hasAnyContinue(); | |
699 bool hasAnyBreak(); | |
700 void close(); | |
701 final JumpTarget target; | |
702 List<LabelDefinition> labels(); | |
703 } | |
704 | |
705 // Insert break handler used to avoid null checks when a target isn't | |
706 // used as the target of a break, and therefore doesn't need a break | |
707 // handler associated with it. | |
708 class NullJumpHandler implements JumpHandler { | |
709 final Compiler compiler; | |
710 | |
711 NullJumpHandler(this.compiler); | |
712 | |
713 void generateBreak([LabelDefinition label]) { | |
714 compiler.internalError(CURRENT_ELEMENT_SPANNABLE, | |
715 'NullJumpHandler.generateBreak should not be called.'); | |
716 } | |
717 | |
718 void generateContinue([LabelDefinition label]) { | |
719 compiler.internalError(CURRENT_ELEMENT_SPANNABLE, | |
720 'NullJumpHandler.generateContinue should not be called.'); | |
721 } | |
722 | |
723 void forEachBreak(Function ignored) { } | |
724 void forEachContinue(Function ignored) { } | |
725 void close() { } | |
726 bool hasAnyContinue() => false; | |
727 bool hasAnyBreak() => false; | |
728 | |
729 List<LabelDefinition> labels() => const <LabelDefinition>[]; | |
730 JumpTarget get target => null; | |
731 } | |
732 | |
733 // Records breaks until a target block is available. | |
734 // Breaks are always forward jumps. | |
735 // Continues in loops are implemented as breaks of the body. | |
736 // Continues in switches is currently not handled. | |
737 class TargetJumpHandler implements JumpHandler { | |
738 final SsaBuilder builder; | |
739 final JumpTarget target; | |
740 final List<JumpHandlerEntry> jumps; | |
741 | |
742 TargetJumpHandler(SsaBuilder builder, this.target) | |
743 : this.builder = builder, | |
744 jumps = <JumpHandlerEntry>[] { | |
745 assert(builder.jumpTargets[target] == null); | |
746 builder.jumpTargets[target] = this; | |
747 } | |
748 | |
749 void generateBreak([LabelDefinition label]) { | |
750 HInstruction breakInstruction; | |
751 if (label == null) { | |
752 breakInstruction = new HBreak(target); | |
753 } else { | |
754 breakInstruction = new HBreak.toLabel(label); | |
755 } | |
756 LocalsHandler locals = new LocalsHandler.from(builder.localsHandler); | |
757 builder.close(breakInstruction); | |
758 jumps.add(new JumpHandlerEntry(breakInstruction, locals)); | |
759 } | |
760 | |
761 void generateContinue([LabelDefinition label]) { | |
762 HInstruction continueInstruction; | |
763 if (label == null) { | |
764 continueInstruction = new HContinue(target); | |
765 } else { | |
766 continueInstruction = new HContinue.toLabel(label); | |
767 // Switch case continue statements must be handled by the | |
768 // [SwitchCaseJumpHandler]. | |
769 assert(label.target.statement is! ast.SwitchCase); | |
770 } | |
771 LocalsHandler locals = new LocalsHandler.from(builder.localsHandler); | |
772 builder.close(continueInstruction); | |
773 jumps.add(new JumpHandlerEntry(continueInstruction, locals)); | |
774 } | |
775 | |
776 void forEachBreak(Function action) { | |
777 for (JumpHandlerEntry entry in jumps) { | |
778 if (entry.isBreak()) action(entry.jumpInstruction, entry.locals); | |
779 } | |
780 } | |
781 | |
782 void forEachContinue(Function action) { | |
783 for (JumpHandlerEntry entry in jumps) { | |
784 if (entry.isContinue()) action(entry.jumpInstruction, entry.locals); | |
785 } | |
786 } | |
787 | |
788 bool hasAnyContinue() { | |
789 for (JumpHandlerEntry entry in jumps) { | |
790 if (entry.isContinue()) return true; | |
791 } | |
792 return false; | |
793 } | |
794 | |
795 bool hasAnyBreak() { | |
796 for (JumpHandlerEntry entry in jumps) { | |
797 if (entry.isBreak()) return true; | |
798 } | |
799 return false; | |
800 } | |
801 | |
802 void close() { | |
803 // The mapping from TargetElement to JumpHandler is no longer needed. | |
804 builder.jumpTargets.remove(target); | |
805 } | |
806 | |
807 List<LabelDefinition> labels() { | |
808 List<LabelDefinition> result = null; | |
809 for (LabelDefinition element in target.labels) { | |
810 if (result == null) result = <LabelDefinition>[]; | |
811 result.add(element); | |
812 } | |
813 return (result == null) ? const <LabelDefinition>[] : result; | |
814 } | |
815 } | |
816 | |
817 /// Special [JumpHandler] implementation used to handle continue statements | |
818 /// targeting switch cases. | |
819 class SwitchCaseJumpHandler extends TargetJumpHandler { | |
820 /// Map from switch case targets to indices used to encode the flow of the | |
821 /// switch case loop. | |
822 final Map<JumpTarget, int> targetIndexMap = new Map<JumpTarget, int>(); | |
823 | |
824 SwitchCaseJumpHandler(SsaBuilder builder, | |
825 JumpTarget target, | |
826 ast.SwitchStatement node) | |
827 : super(builder, target) { | |
828 // The switch case indices must match those computed in | |
829 // [SsaFromAstMixin.buildSwitchCaseConstants]. | |
830 // Switch indices are 1-based so we can bypass the synthetic loop when no | |
831 // cases match simply by branching on the index (which defaults to null). | |
832 int switchIndex = 1; | |
833 for (ast.SwitchCase switchCase in node.cases) { | |
834 for (ast.Node labelOrCase in switchCase.labelsAndCases) { | |
835 ast.Node label = labelOrCase.asLabel(); | |
836 if (label != null) { | |
837 LabelDefinition labelElement = | |
838 builder.elements.getLabelDefinition(label); | |
839 if (labelElement != null && labelElement.isContinueTarget) { | |
840 JumpTarget continueTarget = labelElement.target; | |
841 targetIndexMap[continueTarget] = switchIndex; | |
842 assert(builder.jumpTargets[continueTarget] == null); | |
843 builder.jumpTargets[continueTarget] = this; | |
844 } | |
845 } | |
846 } | |
847 switchIndex++; | |
848 } | |
849 } | |
850 | |
851 void generateBreak([LabelDefinition label]) { | |
852 if (label == null) { | |
853 // Creates a special break instruction for the synthetic loop generated | |
854 // for a switch statement with continue statements. See | |
855 // [SsaFromAstMixin.buildComplexSwitchStatement] for detail. | |
856 | |
857 HInstruction breakInstruction = | |
858 new HBreak(target, breakSwitchContinueLoop: true); | |
859 LocalsHandler locals = new LocalsHandler.from(builder.localsHandler); | |
860 builder.close(breakInstruction); | |
861 jumps.add(new JumpHandlerEntry(breakInstruction, locals)); | |
862 } else { | |
863 super.generateBreak(label); | |
864 } | |
865 } | |
866 | |
867 bool isContinueToSwitchCase(LabelDefinition label) { | |
868 return label != null && targetIndexMap.containsKey(label.target); | |
869 } | |
870 | |
871 void generateContinue([LabelDefinition label]) { | |
872 if (isContinueToSwitchCase(label)) { | |
873 // Creates the special instructions 'label = i; continue l;' used in | |
874 // switch statements with continue statements. See | |
875 // [SsaFromAstMixin.buildComplexSwitchStatement] for detail. | |
876 | |
877 assert(label != null); | |
878 HInstruction value = builder.graph.addConstantInt( | |
879 targetIndexMap[label.target], | |
880 builder.compiler); | |
881 builder.localsHandler.updateLocal(target, value); | |
882 | |
883 assert(label.target.labels.contains(label)); | |
884 HInstruction continueInstruction = new HContinue(target); | |
885 LocalsHandler locals = new LocalsHandler.from(builder.localsHandler); | |
886 builder.close(continueInstruction); | |
887 jumps.add(new JumpHandlerEntry(continueInstruction, locals)); | |
888 } else { | |
889 super.generateContinue(label); | |
890 } | |
891 } | |
892 | |
893 void close() { | |
894 // The mapping from TargetElement to JumpHandler is no longer needed. | |
895 for (JumpTarget target in targetIndexMap.keys) { | |
896 builder.jumpTargets.remove(target); | |
897 } | |
898 super.close(); | |
899 } | |
900 } | |
901 | |
902 /** | |
903 * This class builds SSA nodes for functions represented in AST. | |
904 */ | |
905 class SsaBuilder extends ResolvedVisitor { | |
906 final Compiler compiler; | |
907 final JavaScriptBackend backend; | |
908 final ConstantSystem constantSystem; | |
909 final CodegenWorkItem work; | |
910 final RuntimeTypes rti; | |
911 | |
912 /* This field is used by the native handler. */ | |
913 final NativeEmitter nativeEmitter; | |
914 | |
915 final HGraph graph = new HGraph(); | |
916 | |
917 /** | |
918 * The current block to add instructions to. Might be null, if we are | |
919 * visiting dead code, but see [isReachable]. | |
920 */ | |
921 HBasicBlock _current; | |
922 | |
923 HBasicBlock get current => _current; | |
924 | |
925 void set current(c) { | |
926 isReachable = c != null; | |
927 _current = c; | |
928 } | |
929 | |
930 /** | |
931 * The most recently opened block. Has the same value as [current] while | |
932 * the block is open, but unlike [current], it isn't cleared when the | |
933 * current block is closed. | |
934 */ | |
935 HBasicBlock lastOpenedBlock; | |
936 | |
937 /** | |
938 * Indicates whether the current block is dead (because it has a throw or a | |
939 * return further up). If this is false, then [current] may be null. If the | |
940 * block is dead then it may also be aborted, but for simplicity we only | |
941 * abort on statement boundaries, not in the middle of expressions. See | |
942 * isAborted. | |
943 */ | |
944 bool isReachable = true; | |
945 | |
946 /** | |
947 * True if we are visiting the expression of a throw statement; we assume this | |
948 * is a slow path. | |
949 */ | |
950 bool inExpressionOfThrow = false; | |
951 | |
952 /** | |
953 * The loop nesting is consulted when inlining a function invocation in | |
954 * [tryInlineMethod]. The inlining heuristics take this information into | |
955 * account. | |
956 */ | |
957 int loopNesting = 0; | |
958 | |
959 /** | |
960 * This stack contains declaration elements of the functions being built | |
961 * or inlined by this builder. | |
962 */ | |
963 final List<Element> sourceElementStack = <Element>[]; | |
964 | |
965 LocalsHandler localsHandler; | |
966 | |
967 HInstruction rethrowableException; | |
968 | |
969 HParameterValue lastAddedParameter; | |
970 | |
971 Map<ParameterElement, HInstruction> parameters = | |
972 <ParameterElement, HInstruction>{}; | |
973 | |
974 Map<JumpTarget, JumpHandler> jumpTargets = <JumpTarget, JumpHandler>{}; | |
975 | |
976 /** | |
977 * Variables stored in the current activation. These variables are | |
978 * being updated in try/catch blocks, and should be | |
979 * accessed indirectly through [HLocalGet] and [HLocalSet]. | |
980 */ | |
981 Map<Local, HLocalValue> activationVariables = | |
982 <Local, HLocalValue>{}; | |
983 | |
984 // We build the Ssa graph by simulating a stack machine. | |
985 List<HInstruction> stack = <HInstruction>[]; | |
986 | |
987 SsaBuilder(JavaScriptBackend backend, | |
988 CodegenWorkItem work, | |
989 this.nativeEmitter) | |
990 : this.compiler = backend.compiler, | |
991 this.backend = backend, | |
992 this.constantSystem = backend.constantSystem, | |
993 this.work = work, | |
994 this.rti = backend.rti, | |
995 super(work.resolutionTree) { | |
996 localsHandler = new LocalsHandler(this, work.element); | |
997 sourceElementStack.add(work.element); | |
998 } | |
999 | |
1000 CodegenRegistry get registry => work.registry; | |
1001 | |
1002 /// Returns the current source element. | |
1003 /// | |
1004 /// The returned element is a declaration element. | |
1005 // TODO(johnniwinther): Check that all usages of sourceElement agree on | |
1006 // implementation/declaration distinction. | |
1007 Element get sourceElement => sourceElementStack.last; | |
1008 | |
1009 HBasicBlock addNewBlock() { | |
1010 HBasicBlock block = graph.addNewBlock(); | |
1011 // If adding a new block during building of an expression, it is due to | |
1012 // conditional expressions or short-circuit logical operators. | |
1013 return block; | |
1014 } | |
1015 | |
1016 void open(HBasicBlock block) { | |
1017 block.open(); | |
1018 current = block; | |
1019 lastOpenedBlock = block; | |
1020 } | |
1021 | |
1022 HBasicBlock close(HControlFlow end) { | |
1023 HBasicBlock result = current; | |
1024 current.close(end); | |
1025 current = null; | |
1026 return result; | |
1027 } | |
1028 | |
1029 HBasicBlock closeAndGotoExit(HControlFlow end) { | |
1030 HBasicBlock result = current; | |
1031 current.close(end); | |
1032 current = null; | |
1033 result.addSuccessor(graph.exit); | |
1034 return result; | |
1035 } | |
1036 | |
1037 void goto(HBasicBlock from, HBasicBlock to) { | |
1038 from.close(new HGoto()); | |
1039 from.addSuccessor(to); | |
1040 } | |
1041 | |
1042 bool isAborted() { | |
1043 return current == null; | |
1044 } | |
1045 | |
1046 /** | |
1047 * Creates a new block, transitions to it from any current block, and | |
1048 * opens the new block. | |
1049 */ | |
1050 HBasicBlock openNewBlock() { | |
1051 HBasicBlock newBlock = addNewBlock(); | |
1052 if (!isAborted()) goto(current, newBlock); | |
1053 open(newBlock); | |
1054 return newBlock; | |
1055 } | |
1056 | |
1057 void add(HInstruction instruction) { | |
1058 current.add(instruction); | |
1059 } | |
1060 | |
1061 void addWithPosition(HInstruction instruction, ast.Node node) { | |
1062 add(attachPosition(instruction, node)); | |
1063 } | |
1064 | |
1065 SourceFile currentSourceFile() { | |
1066 return sourceElement.implementation.compilationUnit.script.file; | |
1067 } | |
1068 | |
1069 void checkValidSourceFileLocation( | |
1070 SourceFileLocation location, SourceFile sourceFile, int offset) { | |
1071 if (!location.isValid()) { | |
1072 throw MessageKind.INVALID_SOURCE_FILE_LOCATION.message( | |
1073 {'offset': offset, | |
1074 'fileName': sourceFile.filename, | |
1075 'length': sourceFile.length}); | |
1076 } | |
1077 } | |
1078 | |
1079 /** | |
1080 * Returns a complete argument list for a call of [function]. | |
1081 */ | |
1082 List<HInstruction> completeSendArgumentsList( | |
1083 FunctionElement function, | |
1084 Selector selector, | |
1085 List<HInstruction> providedArguments, | |
1086 ast.Node currentNode) { | |
1087 assert(invariant(function, function.isImplementation)); | |
1088 assert(providedArguments != null); | |
1089 | |
1090 bool isInstanceMember = function.isInstanceMember; | |
1091 // For static calls, [providedArguments] is complete, default arguments | |
1092 // have been included if necessary, see [addStaticSendArgumentsToList]. | |
1093 if (!isInstanceMember | |
1094 || currentNode == null // In erroneous code, currentNode can be null. | |
1095 || providedArgumentsKnownToBeComplete(currentNode) | |
1096 || function.isGenerativeConstructorBody | |
1097 || selector.isGetter) { | |
1098 // For these cases, the provided argument list is known to be complete. | |
1099 return providedArguments; | |
1100 } else { | |
1101 return completeDynamicSendArgumentsList( | |
1102 selector, function, providedArguments); | |
1103 } | |
1104 } | |
1105 | |
1106 /** | |
1107 * Returns a complete argument list for a dynamic call of [function]. The | |
1108 * initial argument list [providedArguments], created by | |
1109 * [addDynamicSendArgumentsToList], does not include values for default | |
1110 * arguments used in the call. The reason is that the target function (which | |
1111 * defines the defaults) is not known. | |
1112 * | |
1113 * However, inlining can only be performed when the target function can be | |
1114 * resolved statically. The defaults can therefore be included at this point. | |
1115 * | |
1116 * The [providedArguments] list contains first all positional arguments, then | |
1117 * the provided named arguments (the named arguments that are defined in the | |
1118 * [selector]) in a specific order (see [addDynamicSendArgumentsToList]). | |
1119 */ | |
1120 List<HInstruction> completeDynamicSendArgumentsList( | |
1121 Selector selector, | |
1122 FunctionElement function, | |
1123 List<HInstruction> providedArguments) { | |
1124 assert(selector.applies(function, compiler.world)); | |
1125 FunctionSignature signature = function.functionSignature; | |
1126 List<HInstruction> compiledArguments = new List<HInstruction>( | |
1127 signature.parameterCount + 1); // Plus one for receiver. | |
1128 | |
1129 compiledArguments[0] = providedArguments[0]; // Receiver. | |
1130 int index = 1; | |
1131 for (; index <= signature.requiredParameterCount; index++) { | |
1132 compiledArguments[index] = providedArguments[index]; | |
1133 } | |
1134 if (!signature.optionalParametersAreNamed) { | |
1135 signature.forEachOptionalParameter((element) { | |
1136 if (index < providedArguments.length) { | |
1137 compiledArguments[index] = providedArguments[index]; | |
1138 } else { | |
1139 compiledArguments[index] = | |
1140 handleConstantForOptionalParameter(element); | |
1141 } | |
1142 index++; | |
1143 }); | |
1144 } else { | |
1145 /* Example: | |
1146 * void foo(a, {b, d, c}) | |
1147 * foo(0, d = 1, b = 2) | |
1148 * | |
1149 * providedArguments = [0, 2, 1] | |
1150 * selectorArgumentNames = [b, d] | |
1151 * signature.orderedOptionalParameters = [b, c, d] | |
1152 * | |
1153 * For each parameter name in the signature, if the argument name matches | |
1154 * we use the next provided argument, otherwise we get the default. | |
1155 */ | |
1156 List<String> selectorArgumentNames = selector.getOrderedNamedArguments(); | |
1157 int namedArgumentIndex = 0; | |
1158 int firstProvidedNamedArgument = index; | |
1159 signature.orderedOptionalParameters.forEach((element) { | |
1160 if (namedArgumentIndex < selectorArgumentNames.length && | |
1161 element.name == selectorArgumentNames[namedArgumentIndex]) { | |
1162 // The named argument was provided in the function invocation. | |
1163 compiledArguments[index] = providedArguments[ | |
1164 firstProvidedNamedArgument + namedArgumentIndex++]; | |
1165 } else { | |
1166 compiledArguments[index] = | |
1167 handleConstantForOptionalParameter(element); | |
1168 } | |
1169 index++; | |
1170 }); | |
1171 } | |
1172 return compiledArguments; | |
1173 } | |
1174 | |
1175 /** | |
1176 * Try to inline [element] within the currect context of the builder. The | |
1177 * insertion point is the state of the builder. | |
1178 */ | |
1179 bool tryInlineMethod(Element element, | |
1180 Selector selector, | |
1181 List<HInstruction> providedArguments, | |
1182 ast.Node currentNode) { | |
1183 // TODO(johnniwinther): Register this on the [registry]. Currently the | |
1184 // [CodegenRegistry] calls the enqueuer, but [element] should _not_ be | |
1185 // enqueued. | |
1186 backend.registerStaticUse(element, compiler.enqueuer.codegen); | |
1187 | |
1188 // Ensure that [element] is an implementation element. | |
1189 element = element.implementation; | |
1190 FunctionElement function = element; | |
1191 bool insideLoop = loopNesting > 0 || graph.calledInLoop; | |
1192 | |
1193 // Bail out early if the inlining decision is in the cache and we can't | |
1194 // inline (no need to check the hard constraints). | |
1195 bool cachedCanBeInlined = | |
1196 backend.inlineCache.canInline(function, insideLoop: insideLoop); | |
1197 if (cachedCanBeInlined == false) return false; | |
1198 | |
1199 bool meetsHardConstraints() { | |
1200 // Don't inline from one output unit to another. If something is deferred | |
1201 // it is to save space in the loading code. | |
1202 if (!compiler.deferredLoadTask | |
1203 .inSameOutputUnit(element,compiler.currentElement)) { | |
1204 return false; | |
1205 } | |
1206 if (compiler.disableInlining) return false; | |
1207 | |
1208 assert(selector != null | |
1209 || Elements.isStaticOrTopLevel(element) | |
1210 || element.isGenerativeConstructorBody); | |
1211 if (selector != null && !selector.applies(function, compiler.world)) { | |
1212 return false; | |
1213 } | |
1214 | |
1215 // Don't inline operator== methods if the parameter can be null. | |
1216 if (element.name == '==') { | |
1217 if (element.enclosingClass != compiler.objectClass | |
1218 && providedArguments[1].canBeNull()) { | |
1219 return false; | |
1220 } | |
1221 } | |
1222 | |
1223 // Generative constructors of native classes should not be called directly | |
1224 // and have an extra argument that causes problems with inlining. | |
1225 if (element.isGenerativeConstructor | |
1226 && Elements.isNativeOrExtendsNative(element.enclosingClass)) { | |
1227 return false; | |
1228 } | |
1229 | |
1230 // A generative constructor body is not seen by global analysis, | |
1231 // so we should not query for its type. | |
1232 if (!element.isGenerativeConstructorBody) { | |
1233 // Don't inline if the return type was inferred to be non-null empty. | |
1234 // This means that the function always throws an exception. | |
1235 TypeMask returnType = | |
1236 compiler.typesTask.getGuaranteedReturnTypeOfElement(element); | |
1237 if (returnType != null | |
1238 && returnType.isEmpty | |
1239 && !returnType.isNullable) { | |
1240 isReachable = false; | |
1241 return false; | |
1242 } | |
1243 } | |
1244 | |
1245 return true; | |
1246 } | |
1247 | |
1248 bool heuristicSayGoodToGo() { | |
1249 // Don't inline recursivly | |
1250 if (inliningStack.any((entry) => entry.function == function)) { | |
1251 return false; | |
1252 } | |
1253 | |
1254 if (inExpressionOfThrow) return false; | |
1255 | |
1256 if (element.isSynthesized) return true; | |
1257 | |
1258 if (cachedCanBeInlined == true) return cachedCanBeInlined; | |
1259 | |
1260 if (backend.functionsToAlwaysInline.contains(function)) { | |
1261 // Inline this function regardless of it's size. | |
1262 assert(InlineWeeder.canBeInlined(function.node, -1, false, | |
1263 allowLoops: true)); | |
1264 return true; | |
1265 } | |
1266 | |
1267 int numParameters = function.functionSignature.parameterCount; | |
1268 int maxInliningNodes; | |
1269 bool useMaxInliningNodes = true; | |
1270 if (insideLoop) { | |
1271 maxInliningNodes = InlineWeeder.INLINING_NODES_INSIDE_LOOP + | |
1272 InlineWeeder.INLINING_NODES_INSIDE_LOOP_ARG_FACTOR * numParameters; | |
1273 } else { | |
1274 maxInliningNodes = InlineWeeder.INLINING_NODES_OUTSIDE_LOOP + | |
1275 InlineWeeder.INLINING_NODES_OUTSIDE_LOOP_ARG_FACTOR * numParameters; | |
1276 } | |
1277 | |
1278 // If a method is called only once, and all the methods in the | |
1279 // inlining stack are called only once as well, we know we will | |
1280 // save on output size by inlining this method. | |
1281 TypesInferrer inferrer = compiler.typesTask.typesInferrer; | |
1282 if (inferrer.isCalledOnce(element) && allInlinedFunctionsCalledOnce) { | |
1283 useMaxInliningNodes = false; | |
1284 } | |
1285 bool canInline; | |
1286 ast.FunctionExpression functionNode = function.node; | |
1287 canInline = InlineWeeder.canBeInlined( | |
1288 functionNode, maxInliningNodes, useMaxInliningNodes); | |
1289 if (canInline) { | |
1290 backend.inlineCache.markAsInlinable(element, insideLoop: insideLoop); | |
1291 } else { | |
1292 backend.inlineCache.markAsNonInlinable(element, insideLoop: insideLoop); | |
1293 } | |
1294 return canInline; | |
1295 } | |
1296 | |
1297 void doInlining() { | |
1298 // Add an explicit null check on the receiver before doing the | |
1299 // inlining. We use [element] to get the same name in the | |
1300 // NoSuchMethodError message as if we had called it. | |
1301 if (element.isInstanceMember | |
1302 && !element.isGenerativeConstructorBody | |
1303 && (selector.mask == null || selector.mask.isNullable)) { | |
1304 addWithPosition( | |
1305 new HFieldGet(null, providedArguments[0], backend.dynamicType, | |
1306 isAssignable: false), | |
1307 currentNode); | |
1308 } | |
1309 List<HInstruction> compiledArguments = completeSendArgumentsList( | |
1310 function, selector, providedArguments, currentNode); | |
1311 enterInlinedMethod(function, currentNode, compiledArguments); | |
1312 inlinedFrom(function, () { | |
1313 if (!isReachable) { | |
1314 emitReturn(graph.addConstantNull(compiler), null); | |
1315 } else { | |
1316 doInline(function); | |
1317 } | |
1318 }); | |
1319 leaveInlinedMethod(); | |
1320 } | |
1321 | |
1322 if (meetsHardConstraints() && heuristicSayGoodToGo()) { | |
1323 doInlining(); | |
1324 registry.registerInlining( | |
1325 element, | |
1326 compiler.currentElement); | |
1327 return true; | |
1328 } | |
1329 | |
1330 return false; | |
1331 } | |
1332 | |
1333 bool get allInlinedFunctionsCalledOnce { | |
1334 return inliningStack.isEmpty || inliningStack.last.allFunctionsCalledOnce; | |
1335 } | |
1336 | |
1337 inlinedFrom(Element element, f()) { | |
1338 assert(element is FunctionElement || element is VariableElement); | |
1339 return compiler.withCurrentElement(element, () { | |
1340 // The [sourceElementStack] contains declaration elements. | |
1341 sourceElementStack.add(element.declaration); | |
1342 var result = f(); | |
1343 sourceElementStack.removeLast(); | |
1344 return result; | |
1345 }); | |
1346 } | |
1347 | |
1348 HInstruction handleConstantForOptionalParameter(Element parameter) { | |
1349 ConstantExpression constant = | |
1350 backend.constants.getConstantForVariable(parameter); | |
1351 assert(invariant(parameter, constant != null, | |
1352 message: 'No constant computed for $parameter')); | |
1353 return graph.addConstant(constant.value, compiler); | |
1354 } | |
1355 | |
1356 Element get currentNonClosureClass { | |
1357 ClassElement cls = sourceElement.enclosingClass; | |
1358 if (cls != null && cls.isClosure) { | |
1359 var closureClass = cls; | |
1360 return closureClass.methodElement.enclosingClass; | |
1361 } else { | |
1362 return cls; | |
1363 } | |
1364 } | |
1365 | |
1366 /** | |
1367 * Returns whether this builder is building code for [element]. | |
1368 */ | |
1369 bool isBuildingFor(Element element) { | |
1370 return work.element == element; | |
1371 } | |
1372 | |
1373 /// A stack of [DartType]s the have been seen during inlining of factory | |
1374 /// constructors. These types are preserved in [HInvokeStatic]s and | |
1375 /// [HForeignNews] inside the inline code and registered during code | |
1376 /// generation for these nodes. | |
1377 // TODO(karlklose): consider removing this and keeping the (substituted) | |
1378 // types of the type variables in an environment (like the [LocalsHandler]). | |
1379 final List<DartType> currentInlinedInstantiations = <DartType>[]; | |
1380 | |
1381 final List<AstInliningState> inliningStack = <AstInliningState>[]; | |
1382 | |
1383 Local returnLocal; | |
1384 DartType returnType; | |
1385 | |
1386 bool inTryStatement = false; | |
1387 | |
1388 ConstantValue getConstantForNode(ast.Node node) { | |
1389 ConstantExpression constant = | |
1390 backend.constants.getConstantForNode(node, elements); | |
1391 assert(invariant(node, constant != null, | |
1392 message: 'No constant computed for $node')); | |
1393 return constant.value; | |
1394 } | |
1395 | |
1396 HInstruction addConstant(ast.Node node) { | |
1397 return graph.addConstant(getConstantForNode(node), compiler); | |
1398 } | |
1399 | |
1400 bool isLazilyInitialized(VariableElement element) { | |
1401 ConstantExpression initialValue = | |
1402 backend.constants.getConstantForVariable(element); | |
1403 return initialValue == null; | |
1404 } | |
1405 | |
1406 TypeMask cachedTypeOfThis; | |
1407 | |
1408 TypeMask getTypeOfThis() { | |
1409 TypeMask result = cachedTypeOfThis; | |
1410 if (result == null) { | |
1411 ThisLocal local = localsHandler.closureData.thisLocal; | |
1412 ClassElement cls = local.enclosingClass; | |
1413 ClassWorld classWorld = compiler.world; | |
1414 if (classWorld.isUsedAsMixin(cls)) { | |
1415 // If the enclosing class is used as a mixin, [:this:] can be | |
1416 // of the class that mixins the enclosing class. These two | |
1417 // classes do not have a subclass relationship, so, for | |
1418 // simplicity, we mark the type as an interface type. | |
1419 result = new TypeMask.nonNullSubtype(cls.declaration, compiler.world); | |
1420 } else { | |
1421 result = new TypeMask.nonNullSubclass(cls.declaration, compiler.world); | |
1422 } | |
1423 cachedTypeOfThis = result; | |
1424 } | |
1425 return result; | |
1426 } | |
1427 | |
1428 Map<Element, TypeMask> cachedTypesOfCapturedVariables = | |
1429 new Map<Element, TypeMask>(); | |
1430 | |
1431 TypeMask getTypeOfCapturedVariable(Element element) { | |
1432 assert(element.isField); | |
1433 return cachedTypesOfCapturedVariables.putIfAbsent(element, () { | |
1434 return TypeMaskFactory.inferredTypeForElement(element, compiler); | |
1435 }); | |
1436 } | |
1437 | |
1438 /** | |
1439 * Documentation wanted -- johnniwinther | |
1440 * | |
1441 * Invariant: [functionElement] must be an implementation element. | |
1442 */ | |
1443 HGraph buildMethod(FunctionElement functionElement) { | |
1444 assert(invariant(functionElement, functionElement.isImplementation)); | |
1445 graph.calledInLoop = compiler.world.isCalledInLoop(functionElement); | |
1446 ast.FunctionExpression function = functionElement.node; | |
1447 assert(function != null); | |
1448 assert(!function.modifiers.isExternal); | |
1449 assert(elements.getFunctionDefinition(function) != null); | |
1450 openFunction(functionElement, function); | |
1451 String name = functionElement.name; | |
1452 // If [functionElement] is `operator==` we explicitely add a null check at | |
1453 // the beginning of the method. This is to avoid having call sites do the | |
1454 // null check. | |
1455 if (name == '==') { | |
1456 if (!backend.operatorEqHandlesNullArgument(functionElement)) { | |
1457 handleIf( | |
1458 function, | |
1459 () { | |
1460 HParameterValue parameter = parameters.values.first; | |
1461 push(new HIdentity( | |
1462 parameter, graph.addConstantNull(compiler), null, | |
1463 backend.boolType)); | |
1464 }, | |
1465 () { | |
1466 closeAndGotoExit(new HReturn( | |
1467 graph.addConstantBool(false, compiler))); | |
1468 }, | |
1469 null); | |
1470 } | |
1471 } | |
1472 function.body.accept(this); | |
1473 return closeFunction(); | |
1474 } | |
1475 | |
1476 HGraph buildCheckedSetter(VariableElement field) { | |
1477 openFunction(field, field.node); | |
1478 HInstruction thisInstruction = localsHandler.readThis(); | |
1479 // Use dynamic type because the type computed by the inferrer is | |
1480 // narrowed to the type annotation. | |
1481 HInstruction parameter = new HParameterValue(field, backend.dynamicType); | |
1482 // Add the parameter as the last instruction of the entry block. | |
1483 // If the method is intercepted, we want the actual receiver | |
1484 // to be the first parameter. | |
1485 graph.entry.addBefore(graph.entry.last, parameter); | |
1486 HInstruction value = | |
1487 potentiallyCheckType(parameter, field.type); | |
1488 add(new HFieldSet(field, thisInstruction, value)); | |
1489 return closeFunction(); | |
1490 } | |
1491 | |
1492 HGraph buildLazyInitializer(VariableElement variable) { | |
1493 ast.Node node = variable.node; | |
1494 openFunction(variable, node); | |
1495 assert(variable.initializer != null); | |
1496 visit(variable.initializer); | |
1497 HInstruction value = pop(); | |
1498 value = potentiallyCheckType(value, variable.type); | |
1499 closeAndGotoExit(new HReturn(value)); | |
1500 return closeFunction(); | |
1501 } | |
1502 | |
1503 /** | |
1504 * Returns the constructor body associated with the given constructor or | |
1505 * creates a new constructor body, if none can be found. | |
1506 * | |
1507 * Returns [:null:] if the constructor does not have a body. | |
1508 */ | |
1509 ConstructorBodyElement getConstructorBody(FunctionElement constructor) { | |
1510 assert(constructor.isGenerativeConstructor); | |
1511 assert(invariant(constructor, constructor.isImplementation)); | |
1512 if (constructor.isSynthesized) return null; | |
1513 ast.FunctionExpression node = constructor.node; | |
1514 // If we know the body doesn't have any code, we don't generate it. | |
1515 if (!node.hasBody()) return null; | |
1516 if (node.hasEmptyBody()) return null; | |
1517 ClassElement classElement = constructor.enclosingClass; | |
1518 ConstructorBodyElement bodyElement; | |
1519 classElement.forEachBackendMember((Element backendMember) { | |
1520 if (backendMember.isGenerativeConstructorBody) { | |
1521 ConstructorBodyElement body = backendMember; | |
1522 if (body.constructor == constructor) { | |
1523 // TODO(kasperl): Find a way of stopping the iteration | |
1524 // through the backend members. | |
1525 bodyElement = backendMember; | |
1526 } | |
1527 } | |
1528 }); | |
1529 if (bodyElement == null) { | |
1530 bodyElement = new ConstructorBodyElementX(constructor); | |
1531 classElement.addBackendMember(bodyElement); | |
1532 | |
1533 if (constructor.isPatch) { | |
1534 // Create origin body element for patched constructors. | |
1535 ConstructorBodyElementX patch = bodyElement; | |
1536 ConstructorBodyElementX origin = | |
1537 new ConstructorBodyElementX(constructor.origin); | |
1538 origin.applyPatch(patch); | |
1539 classElement.origin.addBackendMember(bodyElement.origin); | |
1540 } | |
1541 } | |
1542 assert(bodyElement.isGenerativeConstructorBody); | |
1543 return bodyElement; | |
1544 } | |
1545 | |
1546 HParameterValue addParameter(Entity parameter, TypeMask type) { | |
1547 assert(inliningStack.isEmpty); | |
1548 HParameterValue result = new HParameterValue(parameter, type); | |
1549 if (lastAddedParameter == null) { | |
1550 graph.entry.addBefore(graph.entry.first, result); | |
1551 } else { | |
1552 graph.entry.addAfter(lastAddedParameter, result); | |
1553 } | |
1554 lastAddedParameter = result; | |
1555 return result; | |
1556 } | |
1557 | |
1558 /** | |
1559 * This method sets up the local state of the builder for inlining [function]. | |
1560 * The arguments of the function are inserted into the [localsHandler]. | |
1561 * | |
1562 * When inlining a function, [:return:] statements are not emitted as | |
1563 * [HReturn] instructions. Instead, the value of a synthetic element is | |
1564 * updated in the [localsHandler]. This function creates such an element and | |
1565 * stores it in the [returnLocal] field. | |
1566 */ | |
1567 void setupStateForInlining(FunctionElement function, | |
1568 List<HInstruction> compiledArguments) { | |
1569 localsHandler = new LocalsHandler(this, function); | |
1570 localsHandler.closureData = | |
1571 compiler.closureToClassMapper.computeClosureToClassMapping( | |
1572 function, function.node, elements); | |
1573 returnLocal = new SyntheticLocal("result", function); | |
1574 localsHandler.updateLocal(returnLocal, | |
1575 graph.addConstantNull(compiler)); | |
1576 | |
1577 inTryStatement = false; // TODO(lry): why? Document. | |
1578 | |
1579 int argumentIndex = 0; | |
1580 if (function.isInstanceMember) { | |
1581 localsHandler.updateLocal(localsHandler.closureData.thisLocal, | |
1582 compiledArguments[argumentIndex++]); | |
1583 } | |
1584 | |
1585 FunctionSignature signature = function.functionSignature; | |
1586 signature.orderedForEachParameter((ParameterElement parameter) { | |
1587 HInstruction argument = compiledArguments[argumentIndex++]; | |
1588 localsHandler.updateLocal(parameter, argument); | |
1589 }); | |
1590 | |
1591 ClassElement enclosing = function.enclosingClass; | |
1592 if ((function.isConstructor || function.isGenerativeConstructorBody) | |
1593 && backend.classNeedsRti(enclosing)) { | |
1594 enclosing.typeVariables.forEach((TypeVariableType typeVariable) { | |
1595 HInstruction argument = compiledArguments[argumentIndex++]; | |
1596 localsHandler.updateLocal( | |
1597 localsHandler.getTypeVariableAsLocal(typeVariable), argument); | |
1598 }); | |
1599 } | |
1600 assert(argumentIndex == compiledArguments.length); | |
1601 | |
1602 elements = function.resolvedAst.elements; | |
1603 assert(elements != null); | |
1604 returnType = signature.type.returnType; | |
1605 stack = <HInstruction>[]; | |
1606 | |
1607 insertTraceCall(function); | |
1608 } | |
1609 | |
1610 void restoreState(AstInliningState state) { | |
1611 localsHandler = state.oldLocalsHandler; | |
1612 returnLocal = state.oldReturnLocal; | |
1613 inTryStatement = state.inTryStatement; | |
1614 elements = state.oldElements; | |
1615 returnType = state.oldReturnType; | |
1616 assert(stack.isEmpty); | |
1617 stack = state.oldStack; | |
1618 } | |
1619 | |
1620 /** | |
1621 * Run this builder on the body of the [function] to be inlined. | |
1622 */ | |
1623 void visitInlinedFunction(FunctionElement function) { | |
1624 potentiallyCheckInlinedParameterTypes(function); | |
1625 if (function.isGenerativeConstructor) { | |
1626 buildFactory(function); | |
1627 } else { | |
1628 ast.FunctionExpression functionNode = function.node; | |
1629 functionNode.body.accept(this); | |
1630 } | |
1631 } | |
1632 | |
1633 | |
1634 addInlinedInstantiation(DartType type) { | |
1635 if (type != null) { | |
1636 currentInlinedInstantiations.add(type); | |
1637 } | |
1638 } | |
1639 | |
1640 removeInlinedInstantiation(DartType type) { | |
1641 if (type != null) { | |
1642 currentInlinedInstantiations.removeLast(); | |
1643 } | |
1644 } | |
1645 | |
1646 bool providedArgumentsKnownToBeComplete(ast.Node currentNode) { | |
1647 /* When inlining the iterator methods generated for a [:for-in:] loop, the | |
1648 * [currentNode] is the [ForIn] tree. The compiler-generated iterator | |
1649 * invocations are known to have fully specified argument lists, no default | |
1650 * arguments are used. See invocations of [pushInvokeDynamic] in | |
1651 * [visitForIn]. | |
1652 */ | |
1653 return currentNode.asForIn() != null; | |
1654 } | |
1655 | |
1656 /** | |
1657 * In checked mode, generate type tests for the parameters of the inlined | |
1658 * function. | |
1659 */ | |
1660 void potentiallyCheckInlinedParameterTypes(FunctionElement function) { | |
1661 if (!compiler.enableTypeAssertions) return; | |
1662 | |
1663 FunctionSignature signature = function.functionSignature; | |
1664 signature.orderedForEachParameter((ParameterElement parameter) { | |
1665 HInstruction argument = localsHandler.readLocal(parameter); | |
1666 potentiallyCheckType(argument, parameter.type); | |
1667 }); | |
1668 } | |
1669 | |
1670 /** | |
1671 * Documentation wanted -- johnniwinther | |
1672 * | |
1673 * Invariant: [constructors] must contain only implementation elements. | |
1674 */ | |
1675 void inlineSuperOrRedirect(FunctionElement callee, | |
1676 List<HInstruction> compiledArguments, | |
1677 List<FunctionElement> constructors, | |
1678 Map<Element, HInstruction> fieldValues, | |
1679 FunctionElement caller) { | |
1680 callee = callee.implementation; | |
1681 compiler.withCurrentElement(callee, () { | |
1682 constructors.add(callee); | |
1683 ClassElement enclosingClass = callee.enclosingClass; | |
1684 if (backend.classNeedsRti(enclosingClass)) { | |
1685 // If [enclosingClass] needs RTI, we have to give a value to its | |
1686 // type parameters. | |
1687 ClassElement currentClass = caller.enclosingClass; | |
1688 // For a super constructor call, the type is the supertype of | |
1689 // [currentClass]. For a redirecting constructor, the type is | |
1690 // the current type. [InterfaceType.asInstanceOf] takes care | |
1691 // of both. | |
1692 InterfaceType type = currentClass.thisType.asInstanceOf(enclosingClass); | |
1693 type = localsHandler.substInContext(type); | |
1694 List<DartType> arguments = type.typeArguments; | |
1695 List<DartType> typeVariables = enclosingClass.typeVariables; | |
1696 if (!type.isRaw) { | |
1697 assert(arguments.length == typeVariables.length); | |
1698 Iterator<DartType> variables = typeVariables.iterator; | |
1699 type.typeArguments.forEach((DartType argument) { | |
1700 variables.moveNext(); | |
1701 TypeVariableType typeVariable = variables.current; | |
1702 localsHandler.updateLocal( | |
1703 localsHandler.getTypeVariableAsLocal(typeVariable), | |
1704 analyzeTypeArgument(argument)); | |
1705 }); | |
1706 } else { | |
1707 // If the supertype is a raw type, we need to set to null the | |
1708 // type variables. | |
1709 for (TypeVariableType variable in typeVariables) { | |
1710 localsHandler.updateLocal( | |
1711 localsHandler.getTypeVariableAsLocal(variable), | |
1712 graph.addConstantNull(compiler)); | |
1713 } | |
1714 } | |
1715 } | |
1716 | |
1717 // For redirecting constructors, the fields have already been | |
1718 // initialized by the caller. | |
1719 if (callee.enclosingClass != caller.enclosingClass) { | |
1720 inlinedFrom(callee, () { | |
1721 buildFieldInitializers(callee.enclosingElement.implementation, | |
1722 fieldValues); | |
1723 }); | |
1724 } | |
1725 | |
1726 int index = 0; | |
1727 FunctionSignature params = callee.functionSignature; | |
1728 params.orderedForEachParameter((ParameterElement parameter) { | |
1729 HInstruction argument = compiledArguments[index++]; | |
1730 // Because we are inlining the initializer, we must update | |
1731 // what was given as parameter. This will be used in case | |
1732 // there is a parameter check expression in the initializer. | |
1733 parameters[parameter] = argument; | |
1734 localsHandler.updateLocal(parameter, argument); | |
1735 // Don't forget to update the field, if the parameter is of the | |
1736 // form [:this.x:]. | |
1737 if (parameter.isInitializingFormal) { | |
1738 InitializingFormalElement fieldParameterElement = parameter; | |
1739 fieldValues[fieldParameterElement.fieldElement] = argument; | |
1740 } | |
1741 }); | |
1742 | |
1743 // Build the initializers in the context of the new constructor. | |
1744 TreeElements oldElements = elements; | |
1745 ResolvedAst resolvedAst = callee.resolvedAst; | |
1746 elements = resolvedAst.elements; | |
1747 ClosureClassMap oldClosureData = localsHandler.closureData; | |
1748 ast.Node node = resolvedAst.node; | |
1749 ClosureClassMap newClosureData = | |
1750 compiler.closureToClassMapper.computeClosureToClassMapping( | |
1751 callee, node, elements); | |
1752 localsHandler.closureData = newClosureData; | |
1753 localsHandler.enterScope(node, callee); | |
1754 buildInitializers(callee, constructors, fieldValues); | |
1755 localsHandler.closureData = oldClosureData; | |
1756 elements = oldElements; | |
1757 }); | |
1758 } | |
1759 | |
1760 /** | |
1761 * Run through the initializers and inline all field initializers. Recursively | |
1762 * inlines super initializers. | |
1763 * | |
1764 * The constructors of the inlined initializers is added to [constructors] | |
1765 * with sub constructors having a lower index than super constructors. | |
1766 * | |
1767 * Invariant: The [constructor] and elements in [constructors] must all be | |
1768 * implementation elements. | |
1769 */ | |
1770 void buildInitializers(ConstructorElement constructor, | |
1771 List<FunctionElement> constructors, | |
1772 Map<Element, HInstruction> fieldValues) { | |
1773 assert(invariant(constructor, constructor.isImplementation)); | |
1774 if (constructor.isSynthesized) { | |
1775 List<HInstruction> arguments = <HInstruction>[]; | |
1776 HInstruction compileArgument(ParameterElement parameter) { | |
1777 return localsHandler.readLocal(parameter); | |
1778 } | |
1779 | |
1780 Element target = constructor.definingConstructor.implementation; | |
1781 bool match = Selector.addForwardingElementArgumentsToList( | |
1782 constructor, | |
1783 arguments, | |
1784 target, | |
1785 compileArgument, | |
1786 handleConstantForOptionalParameter, | |
1787 compiler.world); | |
1788 if (!match) { | |
1789 // If this fails, the selector we constructed for the call to a | |
1790 // forwarding constructor in a mixin application did not match the | |
1791 // constructor (which, for example, may happen when the libraries are | |
1792 // not compatible for private names, see issue 20394). | |
1793 compiler.internalError(constructor, | |
1794 'forwarding constructor call does not match'); | |
1795 } | |
1796 inlineSuperOrRedirect( | |
1797 target, | |
1798 arguments, | |
1799 constructors, | |
1800 fieldValues, | |
1801 constructor); | |
1802 return; | |
1803 } | |
1804 ast.FunctionExpression functionNode = constructor.node; | |
1805 | |
1806 bool foundSuperOrRedirect = false; | |
1807 if (functionNode.initializers != null) { | |
1808 Link<ast.Node> initializers = functionNode.initializers.nodes; | |
1809 for (Link<ast.Node> link = initializers; !link.isEmpty; link = link.tail)
{ | |
1810 assert(link.head is ast.Send); | |
1811 if (link.head is !ast.SendSet) { | |
1812 // A super initializer or constructor redirection. | |
1813 foundSuperOrRedirect = true; | |
1814 ast.Send call = link.head; | |
1815 assert(ast.Initializers.isSuperConstructorCall(call) || | |
1816 ast.Initializers.isConstructorRedirect(call)); | |
1817 FunctionElement target = elements[call].implementation; | |
1818 Selector selector = elements.getSelector(call); | |
1819 Link<ast.Node> arguments = call.arguments; | |
1820 List<HInstruction> compiledArguments = new List<HInstruction>(); | |
1821 inlinedFrom(constructor, () { | |
1822 addStaticSendArgumentsToList(selector, | |
1823 arguments, | |
1824 target, | |
1825 compiledArguments); | |
1826 }); | |
1827 inlineSuperOrRedirect(target, | |
1828 compiledArguments, | |
1829 constructors, | |
1830 fieldValues, | |
1831 constructor); | |
1832 } else { | |
1833 // A field initializer. | |
1834 ast.SendSet init = link.head; | |
1835 Link<ast.Node> arguments = init.arguments; | |
1836 assert(!arguments.isEmpty && arguments.tail.isEmpty); | |
1837 inlinedFrom(constructor, () { | |
1838 visit(arguments.head); | |
1839 }); | |
1840 fieldValues[elements[init]] = pop(); | |
1841 } | |
1842 } | |
1843 } | |
1844 | |
1845 if (!foundSuperOrRedirect) { | |
1846 // No super initializer found. Try to find the default constructor if | |
1847 // the class is not Object. | |
1848 ClassElement enclosingClass = constructor.enclosingClass; | |
1849 ClassElement superClass = enclosingClass.superclass; | |
1850 if (!enclosingClass.isObject) { | |
1851 assert(superClass != null); | |
1852 assert(superClass.resolutionState == STATE_DONE); | |
1853 Selector selector = | |
1854 new Selector.callDefaultConstructor(enclosingClass.library); | |
1855 // TODO(johnniwinther): Should we find injected constructors as well? | |
1856 FunctionElement target = superClass.lookupConstructor(selector); | |
1857 if (target == null) { | |
1858 compiler.internalError(superClass, | |
1859 "No default constructor available."); | |
1860 } | |
1861 List<HInstruction> arguments = <HInstruction>[]; | |
1862 selector.addArgumentsToList(const Link<ast.Node>(), | |
1863 arguments, | |
1864 target.implementation, | |
1865 null, | |
1866 handleConstantForOptionalParameter, | |
1867 compiler.world); | |
1868 inlineSuperOrRedirect(target, | |
1869 arguments, | |
1870 constructors, | |
1871 fieldValues, | |
1872 constructor); | |
1873 } | |
1874 } | |
1875 } | |
1876 | |
1877 /** | |
1878 * Run through the fields of [cls] and add their potential | |
1879 * initializers. | |
1880 * | |
1881 * Invariant: [classElement] must be an implementation element. | |
1882 */ | |
1883 void buildFieldInitializers(ClassElement classElement, | |
1884 Map<Element, HInstruction> fieldValues) { | |
1885 assert(invariant(classElement, classElement.isImplementation)); | |
1886 classElement.forEachInstanceField( | |
1887 (ClassElement enclosingClass, VariableElement member) { | |
1888 compiler.withCurrentElement(member, () { | |
1889 TreeElements definitions = member.treeElements; | |
1890 ast.Node node = member.node; | |
1891 ast.Expression initializer = member.initializer; | |
1892 if (initializer == null) { | |
1893 // Unassigned fields of native classes are not initialized to | |
1894 // prevent overwriting pre-initialized native properties. | |
1895 if (!Elements.isNativeOrExtendsNative(classElement)) { | |
1896 fieldValues[member] = graph.addConstantNull(compiler); | |
1897 } | |
1898 } else { | |
1899 ast.Node right = initializer; | |
1900 TreeElements savedElements = elements; | |
1901 elements = definitions; | |
1902 // In case the field initializer uses closures, run the | |
1903 // closure to class mapper. | |
1904 compiler.closureToClassMapper.computeClosureToClassMapping( | |
1905 member, node, elements); | |
1906 inlinedFrom(member, () => right.accept(this)); | |
1907 elements = savedElements; | |
1908 fieldValues[member] = pop(); | |
1909 } | |
1910 }); | |
1911 }); | |
1912 } | |
1913 | |
1914 /** | |
1915 * Build the factory function corresponding to the constructor | |
1916 * [functionElement]: | |
1917 * - Initialize fields with the values of the field initializers of the | |
1918 * current constructor and super constructors or constructors redirected | |
1919 * to, starting from the current constructor. | |
1920 * - Call the constructor bodies, starting from the constructor(s) in the | |
1921 * super class(es). | |
1922 */ | |
1923 HGraph buildFactory(FunctionElement functionElement) { | |
1924 functionElement = functionElement.implementation; | |
1925 ClassElement classElement = | |
1926 functionElement.enclosingClass.implementation; | |
1927 bool isNativeUpgradeFactory = | |
1928 Elements.isNativeOrExtendsNative(classElement); | |
1929 ast.FunctionExpression function = functionElement.node; | |
1930 // Note that constructors (like any other static function) do not need | |
1931 // to deal with optional arguments. It is the callers job to provide all | |
1932 // arguments as if they were positional. | |
1933 | |
1934 if (inliningStack.isEmpty) { | |
1935 // The initializer list could contain closures. | |
1936 openFunction(functionElement, function); | |
1937 } | |
1938 | |
1939 Map<Element, HInstruction> fieldValues = new Map<Element, HInstruction>(); | |
1940 | |
1941 // Compile the possible initialization code for local fields and | |
1942 // super fields. | |
1943 buildFieldInitializers(classElement, fieldValues); | |
1944 | |
1945 // Compile field-parameters such as [:this.x:]. | |
1946 FunctionSignature params = functionElement.functionSignature; | |
1947 params.orderedForEachParameter((ParameterElement parameter) { | |
1948 if (parameter.isInitializingFormal) { | |
1949 // If the [element] is a field-parameter then | |
1950 // initialize the field element with its value. | |
1951 InitializingFormalElement fieldParameter = parameter; | |
1952 HInstruction parameterValue = | |
1953 localsHandler.readLocal(fieldParameter); | |
1954 fieldValues[fieldParameter.fieldElement] = parameterValue; | |
1955 } | |
1956 }); | |
1957 | |
1958 // Analyze the constructor and all referenced constructors and collect | |
1959 // initializers and constructor bodies. | |
1960 List<FunctionElement> constructors = <FunctionElement>[functionElement]; | |
1961 buildInitializers(functionElement, constructors, fieldValues); | |
1962 | |
1963 // Call the JavaScript constructor with the fields as argument. | |
1964 List<HInstruction> constructorArguments = <HInstruction>[]; | |
1965 List<Element> fields = <Element>[]; | |
1966 | |
1967 classElement.forEachInstanceField( | |
1968 (ClassElement enclosingClass, VariableElement member) { | |
1969 HInstruction value = fieldValues[member]; | |
1970 if (value == null) { | |
1971 // Uninitialized native fields are pre-initialized by the native | |
1972 // implementation. | |
1973 assert(isNativeUpgradeFactory); | |
1974 } else { | |
1975 fields.add(member); | |
1976 DartType type = localsHandler.substInContext(member.type); | |
1977 constructorArguments.add(potentiallyCheckType(value, type)); | |
1978 } | |
1979 }, | |
1980 includeSuperAndInjectedMembers: true); | |
1981 | |
1982 InterfaceType type = classElement.thisType; | |
1983 TypeMask ssaType = | |
1984 new TypeMask.nonNullExact(classElement.declaration, compiler.world); | |
1985 List<DartType> instantiatedTypes; | |
1986 addInlinedInstantiation(type); | |
1987 if (!currentInlinedInstantiations.isEmpty) { | |
1988 instantiatedTypes = new List<DartType>.from(currentInlinedInstantiations); | |
1989 } | |
1990 | |
1991 HInstruction newObject; | |
1992 if (!isNativeUpgradeFactory) { | |
1993 newObject = new HForeignNew(classElement, | |
1994 ssaType, | |
1995 constructorArguments, | |
1996 instantiatedTypes); | |
1997 add(newObject); | |
1998 } else { | |
1999 // Bulk assign to the initialized fields. | |
2000 newObject = graph.explicitReceiverParameter; | |
2001 // Null guard ensures an error if we are being called from an explicit | |
2002 // 'new' of the constructor instead of via an upgrade. It is optimized out | |
2003 // if there are field initializers. | |
2004 add(new HFieldGet( | |
2005 null, newObject, backend.dynamicType, isAssignable: false)); | |
2006 for (int i = 0; i < fields.length; i++) { | |
2007 add(new HFieldSet(fields[i], newObject, constructorArguments[i])); | |
2008 } | |
2009 } | |
2010 removeInlinedInstantiation(type); | |
2011 // Create the runtime type information, if needed. | |
2012 if (backend.classNeedsRti(classElement)) { | |
2013 // Read the values of the type arguments and create a list to set on the | |
2014 // newly create object. We can identify the case where the new list | |
2015 // would be of the form: | |
2016 // [getTypeArgumentByIndex(this, 0), .., getTypeArgumentByIndex(this, k)] | |
2017 // and k is the number of type arguments of this. If this is the case, | |
2018 // we can simply copy the list from this. | |
2019 | |
2020 // These locals are modified by [isIndexedTypeArgumentGet]. | |
2021 HThis source; // The source of the type arguments. | |
2022 bool allIndexed = true; | |
2023 int expectedIndex = 0; | |
2024 ClassElement contextClass; // The class of `this`. | |
2025 int remainingTypeVariables; // The number of 'remaining type variables' | |
2026 // of `this`. | |
2027 | |
2028 /// Helper to identify instructions that read a type variable without | |
2029 /// substitution (that is, directly use the index). These instructions | |
2030 /// are of the form: | |
2031 /// HInvokeStatic(getTypeArgumentByIndex, this, index) | |
2032 /// | |
2033 /// Return `true` if [instruction] is of that form and the index is the | |
2034 /// next index in the sequence (held in [expectedIndex]). | |
2035 bool isIndexedTypeArgumentGet(HInstruction instruction) { | |
2036 if (instruction is! HInvokeStatic) return false; | |
2037 HInvokeStatic invoke = instruction; | |
2038 if (invoke.element != backend.getGetTypeArgumentByIndex()) { | |
2039 return false; | |
2040 } | |
2041 HConstant index = invoke.inputs[1]; | |
2042 HInstruction newSource = invoke.inputs[0]; | |
2043 if (newSource is! HThis) { | |
2044 return false; | |
2045 } | |
2046 if (source == null) { | |
2047 // This is the first match. Extract the context class for the type | |
2048 // variables and get the list of type variables to keep track of how | |
2049 // many arguments we need to process. | |
2050 source = newSource; | |
2051 contextClass = source.sourceElement.enclosingClass; | |
2052 remainingTypeVariables = contextClass.typeVariables.length; | |
2053 } else { | |
2054 assert(source == newSource); | |
2055 } | |
2056 // If there are no more type variables, then there are more type | |
2057 // arguments for the new object than the source has, and it can't be | |
2058 // a copy. Otherwise remove one argument. | |
2059 if (remainingTypeVariables == 0) return false; | |
2060 remainingTypeVariables--; | |
2061 // Check that the index is the one we expect. | |
2062 IntConstantValue constant = index.constant; | |
2063 return constant.primitiveValue == expectedIndex++; | |
2064 } | |
2065 | |
2066 List<HInstruction> typeArguments = <HInstruction>[]; | |
2067 classElement.typeVariables.forEach((TypeVariableType typeVariable) { | |
2068 HInstruction argument = localsHandler.readLocal( | |
2069 localsHandler.getTypeVariableAsLocal(typeVariable)); | |
2070 if (allIndexed && !isIndexedTypeArgumentGet(argument)) { | |
2071 allIndexed = false; | |
2072 } | |
2073 typeArguments.add(argument); | |
2074 }); | |
2075 | |
2076 if (source != null && allIndexed && remainingTypeVariables == 0) { | |
2077 copyRuntimeTypeInfo(source, newObject); | |
2078 } else { | |
2079 newObject = | |
2080 callSetRuntimeTypeInfo(classElement, typeArguments, newObject); | |
2081 } | |
2082 } | |
2083 | |
2084 // Generate calls to the constructor bodies. | |
2085 HInstruction interceptor = null; | |
2086 for (int index = constructors.length - 1; index >= 0; index--) { | |
2087 FunctionElement constructor = constructors[index]; | |
2088 assert(invariant(functionElement, constructor.isImplementation)); | |
2089 ConstructorBodyElement body = getConstructorBody(constructor); | |
2090 if (body == null) continue; | |
2091 | |
2092 List bodyCallInputs = <HInstruction>[]; | |
2093 if (isNativeUpgradeFactory) { | |
2094 if (interceptor == null) { | |
2095 ConstantValue constant = | |
2096 new InterceptorConstantValue(classElement.thisType); | |
2097 interceptor = graph.addConstant(constant, compiler); | |
2098 } | |
2099 bodyCallInputs.add(interceptor); | |
2100 } | |
2101 bodyCallInputs.add(newObject); | |
2102 ResolvedAst resolvedAst = constructor.resolvedAst; | |
2103 TreeElements elements = resolvedAst.elements; | |
2104 ast.Node node = resolvedAst.node; | |
2105 ClosureClassMap parameterClosureData = | |
2106 compiler.closureToClassMapper.getMappingForNestedFunction(node); | |
2107 | |
2108 FunctionSignature functionSignature = body.functionSignature; | |
2109 // Provide the parameters to the generative constructor body. | |
2110 functionSignature.orderedForEachParameter((ParameterElement parameter) { | |
2111 // If [parameter] is boxed, it will be a field in the box passed as the | |
2112 // last parameter. So no need to directly pass it. | |
2113 if (!localsHandler.isBoxed(parameter)) { | |
2114 bodyCallInputs.add(localsHandler.readLocal(parameter)); | |
2115 } | |
2116 }); | |
2117 | |
2118 ClassElement currentClass = constructor.enclosingClass; | |
2119 if (backend.classNeedsRti(currentClass)) { | |
2120 // If [currentClass] needs RTI, we add the type variables as | |
2121 // parameters of the generative constructor body. | |
2122 currentClass.typeVariables.forEach((TypeVariableType argument) { | |
2123 // TODO(johnniwinther): Substitute [argument] with | |
2124 // `localsHandler.substInContext(argument)`. | |
2125 bodyCallInputs.add(localsHandler.readLocal( | |
2126 localsHandler.getTypeVariableAsLocal(argument))); | |
2127 }); | |
2128 } | |
2129 | |
2130 // If there are locals that escape (ie mutated in closures), we | |
2131 // pass the box to the constructor. | |
2132 ClosureScope scopeData = parameterClosureData.capturingScopes[node]; | |
2133 if (scopeData != null) { | |
2134 bodyCallInputs.add(localsHandler.readLocal(scopeData.boxElement)); | |
2135 } | |
2136 | |
2137 if (!isNativeUpgradeFactory && // TODO(13836): Fix inlining. | |
2138 tryInlineMethod(body, null, bodyCallInputs, function)) { | |
2139 pop(); | |
2140 } else { | |
2141 HInvokeConstructorBody invoke = new HInvokeConstructorBody( | |
2142 body.declaration, bodyCallInputs, backend.nonNullType); | |
2143 invoke.sideEffects = | |
2144 compiler.world.getSideEffectsOfElement(constructor); | |
2145 add(invoke); | |
2146 } | |
2147 } | |
2148 if (inliningStack.isEmpty) { | |
2149 closeAndGotoExit(new HReturn(newObject)); | |
2150 return closeFunction(); | |
2151 } else { | |
2152 localsHandler.updateLocal(returnLocal, newObject); | |
2153 return null; | |
2154 } | |
2155 } | |
2156 | |
2157 /** | |
2158 * Documentation wanted -- johnniwinther | |
2159 * | |
2160 * Invariant: [functionElement] must be the implementation element. | |
2161 */ | |
2162 void openFunction(Element element, ast.Node node) { | |
2163 assert(invariant(element, element.isImplementation)); | |
2164 HBasicBlock block = graph.addNewBlock(); | |
2165 open(graph.entry); | |
2166 | |
2167 localsHandler.startFunction(element, node); | |
2168 close(new HGoto()).addSuccessor(block); | |
2169 | |
2170 open(block); | |
2171 | |
2172 // Add the type parameters of the class as parameters of this method. This | |
2173 // must be done before adding the normal parameters, because their types | |
2174 // may contain references to type variables. | |
2175 var enclosing = element.enclosingElement; | |
2176 if ((element.isConstructor || element.isGenerativeConstructorBody) | |
2177 && backend.classNeedsRti(enclosing)) { | |
2178 enclosing.typeVariables.forEach((TypeVariableType typeVariable) { | |
2179 HParameterValue param = addParameter( | |
2180 typeVariable.element, backend.nonNullType); | |
2181 localsHandler.directLocals[ | |
2182 localsHandler.getTypeVariableAsLocal(typeVariable)] = param; | |
2183 }); | |
2184 } | |
2185 | |
2186 if (element is FunctionElement) { | |
2187 FunctionElement functionElement = element; | |
2188 FunctionSignature signature = functionElement.functionSignature; | |
2189 | |
2190 // Put the type checks in the first successor of the entry, | |
2191 // because that is where the type guards will also be inserted. | |
2192 // This way we ensure that a type guard will dominate the type | |
2193 // check. | |
2194 ClosureScope scopeData = | |
2195 localsHandler.closureData.capturingScopes[node]; | |
2196 signature.orderedForEachParameter((ParameterElement parameterElement) { | |
2197 if (element.isGenerativeConstructorBody) { | |
2198 if (scopeData != null && | |
2199 scopeData.isCapturedVariable(parameterElement)) { | |
2200 // The parameter will be a field in the box passed as the | |
2201 // last parameter. So no need to have it. | |
2202 return; | |
2203 } | |
2204 } | |
2205 HInstruction newParameter = | |
2206 localsHandler.directLocals[parameterElement]; | |
2207 if (!element.isConstructor || | |
2208 !(element as ConstructorElement).isRedirectingFactory) { | |
2209 // Redirection factories must not check their argument types. | |
2210 // Example: | |
2211 // | |
2212 // class A { | |
2213 // A(String foo) = A.b; | |
2214 // A(int foo) { print(foo); } | |
2215 // } | |
2216 // main() { | |
2217 // new A(499); // valid even in checked mode. | |
2218 // new A("foo"); // invalid in checked mode. | |
2219 // | |
2220 // Only the final target is allowed to check for the argument types. | |
2221 newParameter = | |
2222 potentiallyCheckType(newParameter, parameterElement.type); | |
2223 } | |
2224 localsHandler.directLocals[parameterElement] = newParameter; | |
2225 }); | |
2226 | |
2227 returnType = signature.type.returnType; | |
2228 } else { | |
2229 // Otherwise it is a lazy initializer which does not have parameters. | |
2230 assert(element is VariableElement); | |
2231 } | |
2232 | |
2233 insertTraceCall(element); | |
2234 } | |
2235 | |
2236 insertTraceCall(Element element) { | |
2237 if (JavaScriptBackend.TRACE_CALLS) { | |
2238 if (element == backend.traceHelper) return; | |
2239 n(e) => e == null ? '' : e.name; | |
2240 String name = "${n(element.library)}:${n(element.enclosingClass)}." | |
2241 "${n(element)}"; | |
2242 HConstant nameConstant = addConstantString(name); | |
2243 add(new HInvokeStatic(backend.traceHelper, | |
2244 <HInstruction>[nameConstant], | |
2245 backend.dynamicType)); | |
2246 } | |
2247 } | |
2248 | |
2249 /// Check that [type] is valid in the context of `localsHandler.contextClass`. | |
2250 /// This should only be called in assertions. | |
2251 bool assertTypeInContext(DartType type, [Spannable spannable]) { | |
2252 return invariant(spannable == null ? CURRENT_ELEMENT_SPANNABLE : spannable, | |
2253 () { | |
2254 ClassElement contextClass = Types.getClassContext(type); | |
2255 return contextClass == null || | |
2256 contextClass == localsHandler.contextClass; | |
2257 }, | |
2258 message: "Type '$type' is not valid context of " | |
2259 "${localsHandler.contextClass}."); | |
2260 } | |
2261 | |
2262 /// Build a [HTypeConversion] for convertion [original] to type [type]. | |
2263 /// | |
2264 /// Invariant: [type] must be valid in the context. | |
2265 /// See [LocalsHandler.substInContext]. | |
2266 HInstruction buildTypeConversion(HInstruction original, | |
2267 DartType type, | |
2268 int kind) { | |
2269 if (type == null) return original; | |
2270 type = type.unalias(compiler); | |
2271 assert(assertTypeInContext(type, original)); | |
2272 if (type.isInterfaceType && !type.treatAsRaw) { | |
2273 TypeMask subtype = new TypeMask.subtype(type.element, compiler.world); | |
2274 HInstruction representations = buildTypeArgumentRepresentations(type); | |
2275 add(representations); | |
2276 return new HTypeConversion.withTypeRepresentation(type, kind, subtype, | |
2277 original, representations); | |
2278 } else if (type.isTypeVariable) { | |
2279 TypeMask subtype = original.instructionType; | |
2280 HInstruction typeVariable = addTypeVariableReference(type); | |
2281 return new HTypeConversion.withTypeRepresentation(type, kind, subtype, | |
2282 original, typeVariable); | |
2283 } else if (type.isFunctionType) { | |
2284 String name = kind == HTypeConversion.CAST_TYPE_CHECK | |
2285 ? '_asCheck' : '_assertCheck'; | |
2286 | |
2287 List arguments = [buildFunctionType(type), original]; | |
2288 pushInvokeDynamic( | |
2289 null, | |
2290 new Selector.call(name, backend.jsHelperLibrary, 1), | |
2291 arguments); | |
2292 | |
2293 return new HTypeConversion(type, kind, original.instructionType, pop()); | |
2294 } else { | |
2295 return original.convertType(compiler, type, kind); | |
2296 } | |
2297 } | |
2298 | |
2299 HInstruction potentiallyBuildTypeHint(HInstruction original, DartType type) { | |
2300 if (!compiler.trustTypeAnnotations || type == null) return original; | |
2301 type = localsHandler.substInContext(type); | |
2302 if (!type.isInterfaceType) return original; | |
2303 TypeMask mask = new TypeMask.subtype(type.element, compiler.world); | |
2304 var result = new HTypeKnown.pinned(mask, original); | |
2305 return result; | |
2306 } | |
2307 | |
2308 HInstruction potentiallyCheckType(HInstruction original, DartType type, | |
2309 { int kind: HTypeConversion.CHECKED_MODE_CHECK }) { | |
2310 if (!compiler.enableTypeAssertions) return original; | |
2311 type = localsHandler.substInContext(type); | |
2312 HInstruction other = buildTypeConversion(original, type, kind); | |
2313 if (other != original) add(other); | |
2314 registry.registerIsCheck(type); | |
2315 return other; | |
2316 } | |
2317 | |
2318 void assertIsSubtype(ast.Node node, DartType subtype, DartType supertype, | |
2319 String message) { | |
2320 HInstruction subtypeInstruction = | |
2321 analyzeTypeArgument(localsHandler.substInContext(subtype)); | |
2322 HInstruction supertypeInstruction = | |
2323 analyzeTypeArgument(localsHandler.substInContext(supertype)); | |
2324 HInstruction messageInstruction = | |
2325 graph.addConstantString(new ast.DartString.literal(message), compiler); | |
2326 Element element = backend.getAssertIsSubtype(); | |
2327 var inputs = <HInstruction>[subtypeInstruction, supertypeInstruction, | |
2328 messageInstruction]; | |
2329 HInstruction assertIsSubtype = new HInvokeStatic( | |
2330 element, inputs, subtypeInstruction.instructionType); | |
2331 registry.registerTypeVariableBoundsSubtypeCheck(subtype, supertype); | |
2332 add(assertIsSubtype); | |
2333 } | |
2334 | |
2335 HGraph closeFunction() { | |
2336 // TODO(kasperl): Make this goto an implicit return. | |
2337 if (!isAborted()) closeAndGotoExit(new HGoto()); | |
2338 graph.finalize(); | |
2339 return graph; | |
2340 } | |
2341 | |
2342 void push(HInstruction instruction) { | |
2343 add(instruction); | |
2344 stack.add(instruction); | |
2345 } | |
2346 | |
2347 void pushWithPosition(HInstruction instruction, ast.Node node) { | |
2348 push(attachPosition(instruction, node)); | |
2349 } | |
2350 | |
2351 HInstruction pop() { | |
2352 return stack.removeLast(); | |
2353 } | |
2354 | |
2355 void dup() { | |
2356 stack.add(stack.last); | |
2357 } | |
2358 | |
2359 HInstruction popBoolified() { | |
2360 HInstruction value = pop(); | |
2361 if (compiler.enableTypeAssertions) { | |
2362 return potentiallyCheckType( | |
2363 value, | |
2364 compiler.boolClass.rawType, | |
2365 kind: HTypeConversion.BOOLEAN_CONVERSION_CHECK); | |
2366 } | |
2367 HInstruction result = new HBoolify(value, backend.boolType); | |
2368 add(result); | |
2369 return result; | |
2370 } | |
2371 | |
2372 HInstruction attachPosition(HInstruction target, ast.Node node) { | |
2373 if (node != null) { | |
2374 target.sourcePosition = sourceFileLocationForBeginToken(node); | |
2375 } | |
2376 return target; | |
2377 } | |
2378 | |
2379 SourceFileLocation sourceFileLocationForBeginToken(ast.Node node) => | |
2380 sourceFileLocationForToken(node, node.getBeginToken()); | |
2381 | |
2382 SourceFileLocation sourceFileLocationForEndToken(ast.Node node) => | |
2383 sourceFileLocationForToken(node, node.getEndToken()); | |
2384 | |
2385 SourceFileLocation sourceFileLocationForToken(ast.Node node, Token token) { | |
2386 SourceFile sourceFile = currentSourceFile(); | |
2387 SourceFileLocation location = | |
2388 new TokenSourceFileLocation(sourceFile, token, sourceElement.name); | |
2389 checkValidSourceFileLocation(location, sourceFile, token.charOffset); | |
2390 return location; | |
2391 } | |
2392 | |
2393 void visit(ast.Node node) { | |
2394 if (node != null) node.accept(this); | |
2395 } | |
2396 | |
2397 visitBlock(ast.Block node) { | |
2398 assert(!isAborted()); | |
2399 if (!isReachable) return; // This can only happen when inlining. | |
2400 for (Link<ast.Node> link = node.statements.nodes; | |
2401 !link.isEmpty; | |
2402 link = link.tail) { | |
2403 visit(link.head); | |
2404 if (!isReachable) { | |
2405 // The block has been aborted by a return or a throw. | |
2406 if (!stack.isEmpty) { | |
2407 compiler.internalError(node, 'Non-empty instruction stack.'); | |
2408 } | |
2409 return; | |
2410 } | |
2411 } | |
2412 assert(!current.isClosed()); | |
2413 if (!stack.isEmpty) { | |
2414 compiler.internalError(node, 'Non-empty instruction stack.'); | |
2415 } | |
2416 } | |
2417 | |
2418 visitClassNode(ast.ClassNode node) { | |
2419 compiler.internalError(node, | |
2420 'SsaBuilder.visitClassNode should not be called.'); | |
2421 } | |
2422 | |
2423 visitThrowExpression(ast.Expression expression) { | |
2424 bool old = inExpressionOfThrow; | |
2425 try { | |
2426 inExpressionOfThrow = true; | |
2427 visit(expression); | |
2428 } finally { | |
2429 inExpressionOfThrow = old; | |
2430 } | |
2431 } | |
2432 | |
2433 visitExpressionStatement(ast.ExpressionStatement node) { | |
2434 if (!isReachable) return; | |
2435 ast.Throw throwExpression = node.expression.asThrow(); | |
2436 if (throwExpression != null && inliningStack.isEmpty) { | |
2437 visitThrowExpression(throwExpression.expression); | |
2438 handleInTryStatement(); | |
2439 closeAndGotoExit(new HThrow(pop())); | |
2440 } else { | |
2441 visit(node.expression); | |
2442 pop(); | |
2443 } | |
2444 } | |
2445 | |
2446 /** | |
2447 * Creates a new loop-header block. The previous [current] block | |
2448 * is closed with an [HGoto] and replaced by the newly created block. | |
2449 * Also notifies the locals handler that we're entering a loop. | |
2450 */ | |
2451 JumpHandler beginLoopHeader(ast.Node node) { | |
2452 assert(!isAborted()); | |
2453 HBasicBlock previousBlock = close(new HGoto()); | |
2454 | |
2455 JumpHandler jumpHandler = createJumpHandler(node, isLoopJump: true); | |
2456 HBasicBlock loopEntry = graph.addNewLoopHeaderBlock( | |
2457 jumpHandler.target, | |
2458 jumpHandler.labels()); | |
2459 previousBlock.addSuccessor(loopEntry); | |
2460 open(loopEntry); | |
2461 | |
2462 localsHandler.beginLoopHeader(loopEntry); | |
2463 return jumpHandler; | |
2464 } | |
2465 | |
2466 /** | |
2467 * Ends the loop: | |
2468 * - creates a new block and adds it as successor to the [branchExitBlock] and | |
2469 * any blocks that end in break. | |
2470 * - opens the new block (setting as [current]). | |
2471 * - notifies the locals handler that we're exiting a loop. | |
2472 * [savedLocals] are the locals from the end of the loop condition. | |
2473 * [branchExitBlock] is the exit (branching) block of the condition. Generally | |
2474 * this is not the top of the loop, since this would lead to critical edges. | |
2475 * It is null for degenerate do-while loops that have | |
2476 * no back edge because they abort (throw/return/break in the body and have | |
2477 * no continues). | |
2478 */ | |
2479 void endLoop(HBasicBlock loopEntry, | |
2480 HBasicBlock branchExitBlock, | |
2481 JumpHandler jumpHandler, | |
2482 LocalsHandler savedLocals) { | |
2483 HBasicBlock loopExitBlock = addNewBlock(); | |
2484 | |
2485 List<LocalsHandler> breakHandlers = <LocalsHandler>[]; | |
2486 // Collect data for the successors and the phis at each break. | |
2487 jumpHandler.forEachBreak((HBreak breakInstruction, LocalsHandler locals) { | |
2488 breakInstruction.block.addSuccessor(loopExitBlock); | |
2489 breakHandlers.add(locals); | |
2490 }); | |
2491 | |
2492 // The exit block is a successor of the loop condition if it is reached. | |
2493 // We don't add the successor in the case of a while/for loop that aborts | |
2494 // because the caller of endLoop will be wiring up a special empty else | |
2495 // block instead. | |
2496 if (branchExitBlock != null) { | |
2497 branchExitBlock.addSuccessor(loopExitBlock); | |
2498 } | |
2499 // Update the phis at the loop entry with the current values of locals. | |
2500 localsHandler.endLoop(loopEntry); | |
2501 | |
2502 // Start generating code for the exit block. | |
2503 open(loopExitBlock); | |
2504 | |
2505 // Create a new localsHandler for the loopExitBlock with the correct phis. | |
2506 if (!breakHandlers.isEmpty) { | |
2507 if (branchExitBlock != null) { | |
2508 // Add the values of the locals at the end of the condition block to | |
2509 // the phis. These are the values that flow to the exit if the | |
2510 // condition fails. | |
2511 breakHandlers.add(savedLocals); | |
2512 } | |
2513 localsHandler = savedLocals.mergeMultiple(breakHandlers, loopExitBlock); | |
2514 } else { | |
2515 localsHandler = savedLocals; | |
2516 } | |
2517 } | |
2518 | |
2519 HSubGraphBlockInformation wrapStatementGraph(SubGraph statements) { | |
2520 if (statements == null) return null; | |
2521 return new HSubGraphBlockInformation(statements); | |
2522 } | |
2523 | |
2524 HSubExpressionBlockInformation wrapExpressionGraph(SubExpression expression) { | |
2525 if (expression == null) return null; | |
2526 return new HSubExpressionBlockInformation(expression); | |
2527 } | |
2528 | |
2529 // For while loops, initializer and update are null. | |
2530 // The condition function must return a boolean result. | |
2531 // None of the functions must leave anything on the stack. | |
2532 void handleLoop(ast.Node loop, | |
2533 void initialize(), | |
2534 HInstruction condition(), | |
2535 void update(), | |
2536 void body()) { | |
2537 // Generate: | |
2538 // <initializer> | |
2539 // loop-entry: | |
2540 // if (!<condition>) goto loop-exit; | |
2541 // <body> | |
2542 // <updates> | |
2543 // goto loop-entry; | |
2544 // loop-exit: | |
2545 | |
2546 localsHandler.startLoop(loop); | |
2547 | |
2548 // The initializer. | |
2549 SubExpression initializerGraph = null; | |
2550 HBasicBlock startBlock; | |
2551 if (initialize != null) { | |
2552 HBasicBlock initializerBlock = openNewBlock(); | |
2553 startBlock = initializerBlock; | |
2554 initialize(); | |
2555 assert(!isAborted()); | |
2556 initializerGraph = | |
2557 new SubExpression(initializerBlock, current); | |
2558 } | |
2559 | |
2560 loopNesting++; | |
2561 JumpHandler jumpHandler = beginLoopHeader(loop); | |
2562 HLoopInformation loopInfo = current.loopInformation; | |
2563 HBasicBlock conditionBlock = current; | |
2564 if (startBlock == null) startBlock = conditionBlock; | |
2565 | |
2566 HInstruction conditionInstruction = condition(); | |
2567 HBasicBlock conditionEndBlock = | |
2568 close(new HLoopBranch(conditionInstruction)); | |
2569 SubExpression conditionExpression = | |
2570 new SubExpression(conditionBlock, conditionEndBlock); | |
2571 | |
2572 // Save the values of the local variables at the end of the condition | |
2573 // block. These are the values that will flow to the loop exit if the | |
2574 // condition fails. | |
2575 LocalsHandler savedLocals = new LocalsHandler.from(localsHandler); | |
2576 | |
2577 // The body. | |
2578 HBasicBlock beginBodyBlock = addNewBlock(); | |
2579 conditionEndBlock.addSuccessor(beginBodyBlock); | |
2580 open(beginBodyBlock); | |
2581 | |
2582 localsHandler.enterLoopBody(loop); | |
2583 body(); | |
2584 | |
2585 SubGraph bodyGraph = new SubGraph(beginBodyBlock, lastOpenedBlock); | |
2586 HBasicBlock bodyBlock = current; | |
2587 if (current != null) close(new HGoto()); | |
2588 | |
2589 SubExpression updateGraph; | |
2590 | |
2591 bool loopIsDegenerate = !jumpHandler.hasAnyContinue() && bodyBlock == null; | |
2592 if (!loopIsDegenerate) { | |
2593 // Update. | |
2594 // We create an update block, even when we are in a while loop. There the | |
2595 // update block is the jump-target for continue statements. We could avoid | |
2596 // the creation if there is no continue, but for now we always create it. | |
2597 HBasicBlock updateBlock = addNewBlock(); | |
2598 | |
2599 List<LocalsHandler> continueHandlers = <LocalsHandler>[]; | |
2600 jumpHandler.forEachContinue((HContinue instruction, | |
2601 LocalsHandler locals) { | |
2602 instruction.block.addSuccessor(updateBlock); | |
2603 continueHandlers.add(locals); | |
2604 }); | |
2605 | |
2606 | |
2607 if (bodyBlock != null) { | |
2608 continueHandlers.add(localsHandler); | |
2609 bodyBlock.addSuccessor(updateBlock); | |
2610 } | |
2611 | |
2612 open(updateBlock); | |
2613 localsHandler = | |
2614 continueHandlers[0].mergeMultiple(continueHandlers, updateBlock); | |
2615 | |
2616 HLabeledBlockInformation labelInfo; | |
2617 List<LabelDefinition> labels = jumpHandler.labels(); | |
2618 JumpTarget target = elements.getTargetDefinition(loop); | |
2619 if (!labels.isEmpty) { | |
2620 beginBodyBlock.setBlockFlow( | |
2621 new HLabeledBlockInformation( | |
2622 new HSubGraphBlockInformation(bodyGraph), | |
2623 jumpHandler.labels(), | |
2624 isContinue: true), | |
2625 updateBlock); | |
2626 } else if (target != null && target.isContinueTarget) { | |
2627 beginBodyBlock.setBlockFlow( | |
2628 new HLabeledBlockInformation.implicit( | |
2629 new HSubGraphBlockInformation(bodyGraph), | |
2630 target, | |
2631 isContinue: true), | |
2632 updateBlock); | |
2633 } | |
2634 | |
2635 localsHandler.enterLoopUpdates(loop); | |
2636 | |
2637 update(); | |
2638 | |
2639 HBasicBlock updateEndBlock = close(new HGoto()); | |
2640 // The back-edge completing the cycle. | |
2641 updateEndBlock.addSuccessor(conditionBlock); | |
2642 updateGraph = new SubExpression(updateBlock, updateEndBlock); | |
2643 | |
2644 // Avoid a critical edge from the condition to the loop-exit body. | |
2645 HBasicBlock conditionExitBlock = addNewBlock(); | |
2646 open(conditionExitBlock); | |
2647 close(new HGoto()); | |
2648 conditionEndBlock.addSuccessor(conditionExitBlock); | |
2649 | |
2650 endLoop(conditionBlock, conditionExitBlock, jumpHandler, savedLocals); | |
2651 | |
2652 conditionBlock.postProcessLoopHeader(); | |
2653 HLoopBlockInformation info = | |
2654 new HLoopBlockInformation( | |
2655 HLoopBlockInformation.loopType(loop), | |
2656 wrapExpressionGraph(initializerGraph), | |
2657 wrapExpressionGraph(conditionExpression), | |
2658 wrapStatementGraph(bodyGraph), | |
2659 wrapExpressionGraph(updateGraph), | |
2660 conditionBlock.loopInformation.target, | |
2661 conditionBlock.loopInformation.labels, | |
2662 sourceFileLocationForBeginToken(loop), | |
2663 sourceFileLocationForEndToken(loop)); | |
2664 | |
2665 startBlock.setBlockFlow(info, current); | |
2666 loopInfo.loopBlockInformation = info; | |
2667 } else { | |
2668 // The body of the for/while loop always aborts, so there is no back edge. | |
2669 // We turn the code into: | |
2670 // if (condition) { | |
2671 // body; | |
2672 // } else { | |
2673 // // We always create an empty else block to avoid critical edges. | |
2674 // } | |
2675 // | |
2676 // If there is any break in the body, we attach a synthetic | |
2677 // label to the if. | |
2678 HBasicBlock elseBlock = addNewBlock(); | |
2679 open(elseBlock); | |
2680 close(new HGoto()); | |
2681 // Pass the elseBlock as the branchBlock, because that's the block we go | |
2682 // to just before leaving the 'loop'. | |
2683 endLoop(conditionBlock, elseBlock, jumpHandler, savedLocals); | |
2684 | |
2685 SubGraph elseGraph = new SubGraph(elseBlock, elseBlock); | |
2686 // Remove the loop information attached to the header. | |
2687 conditionBlock.loopInformation = null; | |
2688 | |
2689 // Remove the [HLoopBranch] instruction and replace it with | |
2690 // [HIf]. | |
2691 HInstruction condition = conditionEndBlock.last.inputs[0]; | |
2692 conditionEndBlock.addAtExit(new HIf(condition)); | |
2693 conditionEndBlock.addSuccessor(elseBlock); | |
2694 conditionEndBlock.remove(conditionEndBlock.last); | |
2695 HIfBlockInformation info = | |
2696 new HIfBlockInformation( | |
2697 wrapExpressionGraph(conditionExpression), | |
2698 wrapStatementGraph(bodyGraph), | |
2699 wrapStatementGraph(elseGraph)); | |
2700 | |
2701 conditionEndBlock.setBlockFlow(info, current); | |
2702 HIf ifBlock = conditionEndBlock.last; | |
2703 ifBlock.blockInformation = conditionEndBlock.blockFlow; | |
2704 | |
2705 // If the body has any break, attach a synthesized label to the | |
2706 // if block. | |
2707 if (jumpHandler.hasAnyBreak()) { | |
2708 JumpTarget target = elements.getTargetDefinition(loop); | |
2709 LabelDefinition label = target.addLabel(null, 'loop'); | |
2710 label.setBreakTarget(); | |
2711 SubGraph labelGraph = new SubGraph(conditionBlock, current); | |
2712 HLabeledBlockInformation labelInfo = new HLabeledBlockInformation( | |
2713 new HSubGraphBlockInformation(labelGraph), | |
2714 <LabelDefinition>[label]); | |
2715 | |
2716 conditionBlock.setBlockFlow(labelInfo, current); | |
2717 | |
2718 jumpHandler.forEachBreak((HBreak breakInstruction, _) { | |
2719 HBasicBlock block = breakInstruction.block; | |
2720 block.addAtExit(new HBreak.toLabel(label)); | |
2721 block.remove(breakInstruction); | |
2722 }); | |
2723 } | |
2724 } | |
2725 jumpHandler.close(); | |
2726 loopNesting--; | |
2727 } | |
2728 | |
2729 visitFor(ast.For node) { | |
2730 assert(isReachable); | |
2731 assert(node.body != null); | |
2732 void buildInitializer() { | |
2733 ast.Node initializer = node.initializer; | |
2734 if (initializer == null) return; | |
2735 visit(initializer); | |
2736 if (initializer.asExpression() != null) { | |
2737 pop(); | |
2738 } | |
2739 } | |
2740 HInstruction buildCondition() { | |
2741 if (node.condition == null) { | |
2742 return graph.addConstantBool(true, compiler); | |
2743 } | |
2744 visit(node.condition); | |
2745 return popBoolified(); | |
2746 } | |
2747 void buildUpdate() { | |
2748 for (ast.Expression expression in node.update) { | |
2749 visit(expression); | |
2750 assert(!isAborted()); | |
2751 // The result of the update instruction isn't used, and can just | |
2752 // be dropped. | |
2753 HInstruction updateInstruction = pop(); | |
2754 } | |
2755 } | |
2756 void buildBody() { | |
2757 visit(node.body); | |
2758 } | |
2759 handleLoop(node, buildInitializer, buildCondition, buildUpdate, buildBody); | |
2760 } | |
2761 | |
2762 visitWhile(ast.While node) { | |
2763 assert(isReachable); | |
2764 HInstruction buildCondition() { | |
2765 visit(node.condition); | |
2766 return popBoolified(); | |
2767 } | |
2768 handleLoop(node, | |
2769 () {}, | |
2770 buildCondition, | |
2771 () {}, | |
2772 () { visit(node.body); }); | |
2773 } | |
2774 | |
2775 visitDoWhile(ast.DoWhile node) { | |
2776 assert(isReachable); | |
2777 LocalsHandler savedLocals = new LocalsHandler.from(localsHandler); | |
2778 localsHandler.startLoop(node); | |
2779 loopNesting++; | |
2780 JumpHandler jumpHandler = beginLoopHeader(node); | |
2781 HLoopInformation loopInfo = current.loopInformation; | |
2782 HBasicBlock loopEntryBlock = current; | |
2783 HBasicBlock bodyEntryBlock = current; | |
2784 JumpTarget target = elements.getTargetDefinition(node); | |
2785 bool hasContinues = target != null && target.isContinueTarget; | |
2786 if (hasContinues) { | |
2787 // Add extra block to hang labels on. | |
2788 // It doesn't currently work if they are on the same block as the | |
2789 // HLoopInfo. The handling of HLabeledBlockInformation will visit a | |
2790 // SubGraph that starts at the same block again, so the HLoopInfo is | |
2791 // either handled twice, or it's handled after the labeled block info, | |
2792 // both of which generate the wrong code. | |
2793 // Using a separate block is just a simple workaround. | |
2794 bodyEntryBlock = openNewBlock(); | |
2795 } | |
2796 localsHandler.enterLoopBody(node); | |
2797 visit(node.body); | |
2798 | |
2799 // If there are no continues we could avoid the creation of the condition | |
2800 // block. This could also lead to a block having multiple entries and exits. | |
2801 HBasicBlock bodyExitBlock; | |
2802 bool isAbortingBody = false; | |
2803 if (current != null) { | |
2804 bodyExitBlock = close(new HGoto()); | |
2805 } else { | |
2806 isAbortingBody = true; | |
2807 bodyExitBlock = lastOpenedBlock; | |
2808 } | |
2809 | |
2810 SubExpression conditionExpression; | |
2811 bool loopIsDegenerate = isAbortingBody && !hasContinues; | |
2812 if (!loopIsDegenerate) { | |
2813 HBasicBlock conditionBlock = addNewBlock(); | |
2814 | |
2815 List<LocalsHandler> continueHandlers = <LocalsHandler>[]; | |
2816 jumpHandler.forEachContinue((HContinue instruction, | |
2817 LocalsHandler locals) { | |
2818 instruction.block.addSuccessor(conditionBlock); | |
2819 continueHandlers.add(locals); | |
2820 }); | |
2821 | |
2822 if (!isAbortingBody) { | |
2823 bodyExitBlock.addSuccessor(conditionBlock); | |
2824 } | |
2825 | |
2826 if (!continueHandlers.isEmpty) { | |
2827 if (!isAbortingBody) continueHandlers.add(localsHandler); | |
2828 localsHandler = | |
2829 savedLocals.mergeMultiple(continueHandlers, conditionBlock); | |
2830 SubGraph bodyGraph = new SubGraph(bodyEntryBlock, bodyExitBlock); | |
2831 List<LabelDefinition> labels = jumpHandler.labels(); | |
2832 HSubGraphBlockInformation bodyInfo = | |
2833 new HSubGraphBlockInformation(bodyGraph); | |
2834 HLabeledBlockInformation info; | |
2835 if (!labels.isEmpty) { | |
2836 info = new HLabeledBlockInformation(bodyInfo, labels, | |
2837 isContinue: true); | |
2838 } else { | |
2839 info = new HLabeledBlockInformation.implicit(bodyInfo, target, | |
2840 isContinue: true); | |
2841 } | |
2842 bodyEntryBlock.setBlockFlow(info, conditionBlock); | |
2843 } | |
2844 open(conditionBlock); | |
2845 | |
2846 visit(node.condition); | |
2847 assert(!isAborted()); | |
2848 HInstruction conditionInstruction = popBoolified(); | |
2849 HBasicBlock conditionEndBlock = close( | |
2850 new HLoopBranch(conditionInstruction, HLoopBranch.DO_WHILE_LOOP)); | |
2851 | |
2852 HBasicBlock avoidCriticalEdge = addNewBlock(); | |
2853 conditionEndBlock.addSuccessor(avoidCriticalEdge); | |
2854 open(avoidCriticalEdge); | |
2855 close(new HGoto()); | |
2856 avoidCriticalEdge.addSuccessor(loopEntryBlock); // The back-edge. | |
2857 | |
2858 conditionExpression = | |
2859 new SubExpression(conditionBlock, conditionEndBlock); | |
2860 | |
2861 // Avoid a critical edge from the condition to the loop-exit body. | |
2862 HBasicBlock conditionExitBlock = addNewBlock(); | |
2863 open(conditionExitBlock); | |
2864 close(new HGoto()); | |
2865 conditionEndBlock.addSuccessor(conditionExitBlock); | |
2866 | |
2867 endLoop(loopEntryBlock, conditionExitBlock, jumpHandler, localsHandler); | |
2868 | |
2869 loopEntryBlock.postProcessLoopHeader(); | |
2870 SubGraph bodyGraph = new SubGraph(loopEntryBlock, bodyExitBlock); | |
2871 HLoopBlockInformation loopBlockInfo = | |
2872 new HLoopBlockInformation( | |
2873 HLoopBlockInformation.DO_WHILE_LOOP, | |
2874 null, | |
2875 wrapExpressionGraph(conditionExpression), | |
2876 wrapStatementGraph(bodyGraph), | |
2877 null, | |
2878 loopEntryBlock.loopInformation.target, | |
2879 loopEntryBlock.loopInformation.labels, | |
2880 sourceFileLocationForBeginToken(node), | |
2881 sourceFileLocationForEndToken(node)); | |
2882 loopEntryBlock.setBlockFlow(loopBlockInfo, current); | |
2883 loopInfo.loopBlockInformation = loopBlockInfo; | |
2884 } else { | |
2885 // Since the loop has no back edge, we remove the loop information on the | |
2886 // header. | |
2887 loopEntryBlock.loopInformation = null; | |
2888 | |
2889 if (jumpHandler.hasAnyBreak()) { | |
2890 // Null branchBlock because the body of the do-while loop always aborts, | |
2891 // so we never get to the condition. | |
2892 endLoop(loopEntryBlock, null, jumpHandler, localsHandler); | |
2893 | |
2894 // Since the body of the loop has a break, we attach a synthesized label | |
2895 // to the body. | |
2896 SubGraph bodyGraph = new SubGraph(bodyEntryBlock, bodyExitBlock); | |
2897 JumpTarget target = elements.getTargetDefinition(node); | |
2898 LabelDefinition label = target.addLabel(null, 'loop'); | |
2899 label.setBreakTarget(); | |
2900 HLabeledBlockInformation info = new HLabeledBlockInformation( | |
2901 new HSubGraphBlockInformation(bodyGraph), <LabelDefinition>[label]); | |
2902 loopEntryBlock.setBlockFlow(info, current); | |
2903 jumpHandler.forEachBreak((HBreak breakInstruction, _) { | |
2904 HBasicBlock block = breakInstruction.block; | |
2905 block.addAtExit(new HBreak.toLabel(label)); | |
2906 block.remove(breakInstruction); | |
2907 }); | |
2908 } | |
2909 } | |
2910 jumpHandler.close(); | |
2911 loopNesting--; | |
2912 } | |
2913 | |
2914 visitFunctionExpression(ast.FunctionExpression node) { | |
2915 ClosureClassMap nestedClosureData = | |
2916 compiler.closureToClassMapper.getMappingForNestedFunction(node); | |
2917 assert(nestedClosureData != null); | |
2918 assert(nestedClosureData.closureClassElement != null); | |
2919 ClosureClassElement closureClassElement = | |
2920 nestedClosureData.closureClassElement; | |
2921 FunctionElement callElement = nestedClosureData.callElement; | |
2922 // TODO(ahe): This should be registered in codegen, not here. | |
2923 // TODO(johnniwinther): Is [registerStaticUse] equivalent to | |
2924 // [addToWorkList]? | |
2925 registry.registerStaticUse(callElement); | |
2926 // TODO(ahe): This should be registered in codegen, not here. | |
2927 registry.registerInstantiatedClass(closureClassElement); | |
2928 | |
2929 List<HInstruction> capturedVariables = <HInstruction>[]; | |
2930 closureClassElement.closureFields.forEach((ClosureFieldElement field) { | |
2931 Local capturedLocal = | |
2932 nestedClosureData.getLocalVariableForClosureField(field); | |
2933 assert(capturedLocal != null); | |
2934 capturedVariables.add(localsHandler.readLocal(capturedLocal)); | |
2935 }); | |
2936 | |
2937 TypeMask type = | |
2938 new TypeMask.nonNullExact(compiler.functionClass, compiler.world); | |
2939 push(new HForeignNew(closureClassElement, type, capturedVariables)); | |
2940 | |
2941 Element methodElement = nestedClosureData.closureElement; | |
2942 if (compiler.backend.methodNeedsRti(methodElement)) { | |
2943 registry.registerClosureWithFreeTypeVariables(methodElement); | |
2944 } | |
2945 } | |
2946 | |
2947 visitFunctionDeclaration(ast.FunctionDeclaration node) { | |
2948 assert(isReachable); | |
2949 visit(node.function); | |
2950 LocalFunctionElement localFunction = | |
2951 elements.getFunctionDefinition(node.function); | |
2952 localsHandler.updateLocal(localFunction, pop()); | |
2953 } | |
2954 | |
2955 visitIdentifier(ast.Identifier node) { | |
2956 if (node.isThis()) { | |
2957 stack.add(localsHandler.readThis()); | |
2958 } else { | |
2959 compiler.internalError(node, | |
2960 "SsaFromAstMixin.visitIdentifier on non-this."); | |
2961 } | |
2962 } | |
2963 | |
2964 visitIf(ast.If node) { | |
2965 assert(isReachable); | |
2966 handleIf(node, | |
2967 () => visit(node.condition), | |
2968 () => visit(node.thenPart), | |
2969 node.elsePart != null ? () => visit(node.elsePart) : null); | |
2970 } | |
2971 | |
2972 void handleIf(ast.Node diagnosticNode, | |
2973 void visitCondition(), void visitThen(), void visitElse()) { | |
2974 SsaBranchBuilder branchBuilder = new SsaBranchBuilder(this, diagnosticNode); | |
2975 branchBuilder.handleIf(visitCondition, visitThen, visitElse); | |
2976 } | |
2977 | |
2978 void visitLogicalAndOr(ast.Send node, ast.Operator op) { | |
2979 SsaBranchBuilder branchBuilder = new SsaBranchBuilder(this, node); | |
2980 branchBuilder.handleLogicalAndOrWithLeftNode( | |
2981 node.receiver, | |
2982 () { visit(node.argumentsNode); }, | |
2983 isAnd: ("&&" == op.source)); | |
2984 } | |
2985 | |
2986 void visitLogicalNot(ast.Send node) { | |
2987 assert(node.argumentsNode is ast.Prefix); | |
2988 visit(node.receiver); | |
2989 HNot not = new HNot(popBoolified(), backend.boolType); | |
2990 pushWithPosition(not, node); | |
2991 } | |
2992 | |
2993 void visitUnary(ast.Send node, ast.Operator op) { | |
2994 assert(node.argumentsNode is ast.Prefix); | |
2995 visit(node.receiver); | |
2996 assert(!identical(op.token.kind, PLUS_TOKEN)); | |
2997 HInstruction operand = pop(); | |
2998 | |
2999 // See if we can constant-fold right away. This avoids rewrites later on. | |
3000 if (operand is HConstant) { | |
3001 UnaryOperation operation = constantSystem.lookupUnary(op.source); | |
3002 HConstant constant = operand; | |
3003 ConstantValue folded = operation.fold(constant.constant); | |
3004 if (folded != null) { | |
3005 stack.add(graph.addConstant(folded, compiler)); | |
3006 return; | |
3007 } | |
3008 } | |
3009 | |
3010 pushInvokeDynamic(node, elements.getSelector(node), [operand]); | |
3011 } | |
3012 | |
3013 void visitBinary(HInstruction left, | |
3014 ast.Operator op, | |
3015 HInstruction right, | |
3016 Selector selector, | |
3017 ast.Send send) { | |
3018 switch (op.source) { | |
3019 case "===": | |
3020 pushWithPosition( | |
3021 new HIdentity(left, right, null, backend.boolType), op); | |
3022 return; | |
3023 case "!==": | |
3024 HIdentity eq = new HIdentity(left, right, null, backend.boolType); | |
3025 add(eq); | |
3026 pushWithPosition(new HNot(eq, backend.boolType), op); | |
3027 return; | |
3028 } | |
3029 | |
3030 pushInvokeDynamic(send, selector, [left, right], location: op); | |
3031 if (op.source == '!=') { | |
3032 pushWithPosition(new HNot(popBoolified(), backend.boolType), op); | |
3033 } | |
3034 } | |
3035 | |
3036 HInstruction generateInstanceSendReceiver(ast.Send send) { | |
3037 assert(Elements.isInstanceSend(send, elements)); | |
3038 if (send.receiver == null) { | |
3039 return localsHandler.readThis(); | |
3040 } | |
3041 visit(send.receiver); | |
3042 return pop(); | |
3043 } | |
3044 | |
3045 String noSuchMethodTargetSymbolString(ErroneousElement error, | |
3046 [String prefix]) { | |
3047 String result = error.name; | |
3048 if (prefix == "set") return "$result="; | |
3049 return result; | |
3050 } | |
3051 | |
3052 /** | |
3053 * Returns a set of interceptor classes that contain the given | |
3054 * [selector]. | |
3055 */ | |
3056 void generateInstanceGetterWithCompiledReceiver(ast.Send send, | |
3057 Selector selector, | |
3058 HInstruction receiver) { | |
3059 assert(Elements.isInstanceSend(send, elements)); | |
3060 assert(selector.isGetter); | |
3061 pushInvokeDynamic(send, selector, [receiver]); | |
3062 } | |
3063 | |
3064 /// Inserts a call to checkDeferredIsLoaded if the send has a prefix that | |
3065 /// resolves to a deferred library. | |
3066 void generateIsDeferredLoadedCheckIfNeeded(ast.Send node) { | |
3067 DeferredLoadTask deferredTask = compiler.deferredLoadTask; | |
3068 PrefixElement prefixElement = | |
3069 deferredTask.deferredPrefixElement(node, elements); | |
3070 if (prefixElement != null) { | |
3071 String loadId = | |
3072 deferredTask.importDeferName[prefixElement.deferredImport]; | |
3073 HInstruction loadIdConstant = addConstantString(loadId); | |
3074 String uri = prefixElement.deferredImport.uri.dartString.slowToString(); | |
3075 HInstruction uriConstant = addConstantString(uri); | |
3076 Element helper = backend.getCheckDeferredIsLoaded(); | |
3077 pushInvokeStatic(node, helper, [loadIdConstant, uriConstant]); | |
3078 pop(); | |
3079 } | |
3080 } | |
3081 | |
3082 void generateGetter(ast.Send send, Element element) { | |
3083 if (element != null && element.isForeign(backend)) { | |
3084 visitForeignGetter(send); | |
3085 } else if (Elements.isStaticOrTopLevelField(element)) { | |
3086 ConstantExpression constant; | |
3087 if (element.isField && !element.isAssignable) { | |
3088 // A static final or const. Get its constant value and inline it if | |
3089 // the value can be compiled eagerly. | |
3090 constant = backend.constants.getConstantForVariable(element); | |
3091 } | |
3092 if (constant != null) { | |
3093 ConstantValue value = constant.value; | |
3094 HConstant instruction; | |
3095 // Constants that are referred via a deferred prefix should be referred | |
3096 // by reference. | |
3097 PrefixElement prefix = compiler.deferredLoadTask | |
3098 .deferredPrefixElement(send, elements); | |
3099 if (prefix != null) { | |
3100 instruction = graph.addDeferredConstant(value, prefix, compiler); | |
3101 } else { | |
3102 instruction = graph.addConstant(value, compiler); | |
3103 } | |
3104 stack.add(instruction); | |
3105 // The inferrer may have found a better type than the constant | |
3106 // handler in the case of lists, because the constant handler | |
3107 // does not look at elements in the list. | |
3108 TypeMask type = | |
3109 TypeMaskFactory.inferredTypeForElement(element, compiler); | |
3110 if (!type.containsAll(compiler.world) && | |
3111 !instruction.isConstantNull()) { | |
3112 // TODO(13429): The inferrer should know that an element | |
3113 // cannot be null. | |
3114 instruction.instructionType = type.nonNullable(); | |
3115 } | |
3116 } else if (element.isField && isLazilyInitialized(element)) { | |
3117 HInstruction instruction = new HLazyStatic( | |
3118 element, | |
3119 TypeMaskFactory.inferredTypeForElement(element, compiler)); | |
3120 push(instruction); | |
3121 } else { | |
3122 if (element.isGetter) { | |
3123 pushInvokeStatic(send, element, <HInstruction>[]); | |
3124 } else { | |
3125 // TODO(5346): Try to avoid the need for calling [declaration] before | |
3126 // creating an [HStatic]. | |
3127 HInstruction instruction = new HStatic( | |
3128 element.declaration, | |
3129 TypeMaskFactory.inferredTypeForElement(element, compiler)); | |
3130 push(instruction); | |
3131 } | |
3132 } | |
3133 } else if (Elements.isInstanceSend(send, elements)) { | |
3134 HInstruction receiver = generateInstanceSendReceiver(send); | |
3135 generateInstanceGetterWithCompiledReceiver( | |
3136 send, elements.getSelector(send), receiver); | |
3137 } else if (Elements.isStaticOrTopLevelFunction(element)) { | |
3138 // TODO(5346): Try to avoid the need for calling [declaration] before | |
3139 // creating an [HStatic]. | |
3140 push(new HStatic(element.declaration, backend.nonNullType)); | |
3141 // TODO(ahe): This should be registered in codegen. | |
3142 registry.registerGetOfStaticFunction(element.declaration); | |
3143 } else if (Elements.isErroneousElement(element)) { | |
3144 // An erroneous element indicates an unresolved static getter. | |
3145 generateThrowNoSuchMethod(send, | |
3146 noSuchMethodTargetSymbolString(element, 'get'), | |
3147 argumentNodes: const Link<ast.Node>()); | |
3148 } else { | |
3149 LocalElement local = element; | |
3150 stack.add(localsHandler.readLocal(local)); | |
3151 } | |
3152 } | |
3153 | |
3154 void generateInstanceSetterWithCompiledReceiver(ast.Send send, | |
3155 HInstruction receiver, | |
3156 HInstruction value, | |
3157 {Selector selector, | |
3158 ast.Node location}) { | |
3159 assert(send == null || Elements.isInstanceSend(send, elements)); | |
3160 if (selector == null) { | |
3161 assert(send != null); | |
3162 selector = elements.getSelector(send); | |
3163 } | |
3164 if (location == null) { | |
3165 assert(send != null); | |
3166 location = send; | |
3167 } | |
3168 assert(selector.isSetter); | |
3169 pushInvokeDynamic(location, selector, [receiver, value]); | |
3170 pop(); | |
3171 stack.add(value); | |
3172 } | |
3173 | |
3174 void generateNonInstanceSetter(ast.SendSet send, | |
3175 Element element, | |
3176 HInstruction value, | |
3177 {ast.Node location}) { | |
3178 assert(send == null || !Elements.isInstanceSend(send, elements)); | |
3179 if (location == null) { | |
3180 assert(send != null); | |
3181 location = send; | |
3182 } | |
3183 if (Elements.isStaticOrTopLevelField(element)) { | |
3184 if (element.isSetter) { | |
3185 pushInvokeStatic(location, element, <HInstruction>[value]); | |
3186 pop(); | |
3187 } else { | |
3188 VariableElement field = element; | |
3189 value = | |
3190 potentiallyCheckType(value, field.type); | |
3191 addWithPosition(new HStaticStore(element, value), location); | |
3192 } | |
3193 stack.add(value); | |
3194 } else if (Elements.isErroneousElement(element)) { | |
3195 List<HInstruction> arguments = | |
3196 send == null ? const <HInstruction>[] : <HInstruction>[value]; | |
3197 // An erroneous element indicates an unresolved static setter. | |
3198 generateThrowNoSuchMethod(location, | |
3199 noSuchMethodTargetSymbolString(element, 'set'), | |
3200 argumentValues: arguments); | |
3201 } else { | |
3202 stack.add(value); | |
3203 LocalElement local = element; | |
3204 // If the value does not already have a name, give it here. | |
3205 if (value.sourceElement == null) { | |
3206 value.sourceElement = local; | |
3207 } | |
3208 HInstruction checked = | |
3209 potentiallyCheckType(value, local.type); | |
3210 if (!identical(checked, value)) { | |
3211 pop(); | |
3212 stack.add(checked); | |
3213 } | |
3214 HInstruction trusted = | |
3215 potentiallyBuildTypeHint(checked, local.type); | |
3216 if (!identical(trusted, checked)) { | |
3217 pop(); | |
3218 push(trusted); | |
3219 } | |
3220 | |
3221 localsHandler.updateLocal(local, trusted); | |
3222 } | |
3223 } | |
3224 | |
3225 HInstruction invokeInterceptor(HInstruction receiver) { | |
3226 HInterceptor interceptor = new HInterceptor(receiver, backend.nonNullType); | |
3227 add(interceptor); | |
3228 return interceptor; | |
3229 } | |
3230 | |
3231 HForeign createForeign(js.Template code, | |
3232 TypeMask type, | |
3233 List<HInstruction> inputs) { | |
3234 return new HForeign(code, type, inputs); | |
3235 } | |
3236 | |
3237 HLiteralList buildLiteralList(List<HInstruction> inputs) { | |
3238 return new HLiteralList(inputs, backend.extendableArrayType); | |
3239 } | |
3240 | |
3241 // TODO(karlklose): change construction of the representations to be GVN'able | |
3242 // (dartbug.com/7182). | |
3243 HInstruction buildTypeArgumentRepresentations(DartType type) { | |
3244 // Compute the representation of the type arguments, including access | |
3245 // to the runtime type information for type variables as instructions. | |
3246 if (type.isTypeVariable) { | |
3247 return buildLiteralList(<HInstruction>[addTypeVariableReference(type)]); | |
3248 } else { | |
3249 assert(type.element.isClass); | |
3250 InterfaceType interface = type; | |
3251 List<HInstruction> inputs = <HInstruction>[]; | |
3252 bool first = true; | |
3253 List<String> templates = <String>[]; | |
3254 for (DartType argument in interface.typeArguments) { | |
3255 templates.add(rti.getTypeRepresentationWithHashes(argument, (variable) { | |
3256 HInstruction runtimeType = addTypeVariableReference(variable); | |
3257 inputs.add(runtimeType); | |
3258 })); | |
3259 } | |
3260 String template = '[${templates.join(', ')}]'; | |
3261 // TODO(sra): This is a fresh template each time. We can't let the | |
3262 // template manager build them. | |
3263 js.Template code = js.js.uncachedExpressionTemplate(template); | |
3264 HInstruction representation = | |
3265 createForeign(code, backend.readableArrayType, inputs); | |
3266 return representation; | |
3267 } | |
3268 } | |
3269 | |
3270 visitOperatorSend(ast.Send node) { | |
3271 ast.Operator op = node.selector; | |
3272 if ("[]" == op.source) { | |
3273 visitDynamicSend(node); | |
3274 } else if ("&&" == op.source || | |
3275 "||" == op.source) { | |
3276 visitLogicalAndOr(node, op); | |
3277 } else if ("!" == op.source) { | |
3278 visitLogicalNot(node); | |
3279 } else if (node.argumentsNode is ast.Prefix) { | |
3280 visitUnary(node, op); | |
3281 } else if ("is" == op.source) { | |
3282 visitIsSend(node); | |
3283 } else if ("as" == op.source) { | |
3284 visit(node.receiver); | |
3285 HInstruction expression = pop(); | |
3286 DartType type = elements.getType(node.typeAnnotationFromIsCheckOrCast); | |
3287 if (type.isMalformed) { | |
3288 ErroneousElement element = type.element; | |
3289 generateTypeError(node, element.message); | |
3290 } else { | |
3291 HInstruction converted = buildTypeConversion( | |
3292 expression, | |
3293 localsHandler.substInContext(type), | |
3294 HTypeConversion.CAST_TYPE_CHECK); | |
3295 if (converted != expression) add(converted); | |
3296 stack.add(converted); | |
3297 } | |
3298 } else { | |
3299 visit(node.receiver); | |
3300 visit(node.argumentsNode); | |
3301 var right = pop(); | |
3302 var left = pop(); | |
3303 visitBinary(left, op, right, elements.getSelector(node), node); | |
3304 } | |
3305 } | |
3306 | |
3307 void visitIsSend(ast.Send node) { | |
3308 visit(node.receiver); | |
3309 HInstruction expression = pop(); | |
3310 bool isNot = node.isIsNotCheck; | |
3311 DartType type = elements.getType(node.typeAnnotationFromIsCheckOrCast); | |
3312 HInstruction instruction = buildIsNode(node, type, expression); | |
3313 if (isNot) { | |
3314 add(instruction); | |
3315 instruction = new HNot(instruction, backend.boolType); | |
3316 } | |
3317 push(instruction); | |
3318 } | |
3319 | |
3320 HInstruction buildIsNode(ast.Node node, | |
3321 DartType type, | |
3322 HInstruction expression) { | |
3323 type = localsHandler.substInContext(type).unalias(compiler); | |
3324 if (type.isFunctionType) { | |
3325 List arguments = [buildFunctionType(type), expression]; | |
3326 pushInvokeDynamic( | |
3327 node, new Selector.call('_isTest', backend.jsHelperLibrary, 1), | |
3328 arguments); | |
3329 return new HIs.compound(type, expression, pop(), backend.boolType); | |
3330 } else if (type.isTypeVariable) { | |
3331 HInstruction runtimeType = addTypeVariableReference(type); | |
3332 Element helper = backend.getCheckSubtypeOfRuntimeType(); | |
3333 List<HInstruction> inputs = <HInstruction>[expression, runtimeType]; | |
3334 pushInvokeStatic(null, helper, inputs, backend.boolType); | |
3335 HInstruction call = pop(); | |
3336 return new HIs.variable(type, expression, call, backend.boolType); | |
3337 } else if (RuntimeTypes.hasTypeArguments(type)) { | |
3338 ClassElement element = type.element; | |
3339 Element helper = backend.getCheckSubtype(); | |
3340 HInstruction representations = | |
3341 buildTypeArgumentRepresentations(type); | |
3342 add(representations); | |
3343 String operator = backend.namer.operatorIs(element); | |
3344 HInstruction isFieldName = addConstantString(operator); | |
3345 HInstruction asFieldName = compiler.world.hasAnyStrictSubtype(element) | |
3346 ? addConstantString(backend.namer.substitutionName(element)) | |
3347 : graph.addConstantNull(compiler); | |
3348 List<HInstruction> inputs = <HInstruction>[expression, | |
3349 isFieldName, | |
3350 representations, | |
3351 asFieldName]; | |
3352 pushInvokeStatic(node, helper, inputs, backend.boolType); | |
3353 HInstruction call = pop(); | |
3354 return new HIs.compound(type, expression, call, backend.boolType); | |
3355 } else if (type.isMalformed) { | |
3356 ErroneousElement element = type.element; | |
3357 generateTypeError(node, element.message); | |
3358 HInstruction call = pop(); | |
3359 return new HIs.compound(type, expression, call, backend.boolType); | |
3360 } else { | |
3361 if (backend.hasDirectCheckFor(type)) { | |
3362 return new HIs.direct(type, expression, backend.boolType); | |
3363 } | |
3364 // TODO(johnniwinther): Avoid interceptor if unneeded. | |
3365 return new HIs.raw( | |
3366 type, expression, invokeInterceptor(expression), backend.boolType); | |
3367 } | |
3368 } | |
3369 | |
3370 HInstruction buildFunctionType(FunctionType type) { | |
3371 type.accept(new TypeBuilder(compiler.world), this); | |
3372 return pop(); | |
3373 } | |
3374 | |
3375 void addDynamicSendArgumentsToList(ast.Send node, List<HInstruction> list) { | |
3376 Selector selector = elements.getSelector(node); | |
3377 if (selector.namedArgumentCount == 0) { | |
3378 addGenericSendArgumentsToList(node.arguments, list); | |
3379 } else { | |
3380 // Visit positional arguments and add them to the list. | |
3381 Link<ast.Node> arguments = node.arguments; | |
3382 int positionalArgumentCount = selector.positionalArgumentCount; | |
3383 for (int i = 0; | |
3384 i < positionalArgumentCount; | |
3385 arguments = arguments.tail, i++) { | |
3386 visit(arguments.head); | |
3387 list.add(pop()); | |
3388 } | |
3389 | |
3390 // Visit named arguments and add them into a temporary map. | |
3391 Map<String, HInstruction> instructions = | |
3392 new Map<String, HInstruction>(); | |
3393 List<String> namedArguments = selector.namedArguments; | |
3394 int nameIndex = 0; | |
3395 for (; !arguments.isEmpty; arguments = arguments.tail) { | |
3396 visit(arguments.head); | |
3397 instructions[namedArguments[nameIndex++]] = pop(); | |
3398 } | |
3399 | |
3400 // Iterate through the named arguments to add them to the list | |
3401 // of instructions, in an order that can be shared with | |
3402 // selectors with the same named arguments. | |
3403 List<String> orderedNames = selector.getOrderedNamedArguments(); | |
3404 for (String name in orderedNames) { | |
3405 list.add(instructions[name]); | |
3406 } | |
3407 } | |
3408 } | |
3409 | |
3410 /** | |
3411 * Returns true if the arguments were compatible with the function signature. | |
3412 * | |
3413 * Invariant: [element] must be an implementation element. | |
3414 */ | |
3415 bool addStaticSendArgumentsToList(Selector selector, | |
3416 Link<ast.Node> arguments, | |
3417 FunctionElement element, | |
3418 List<HInstruction> list) { | |
3419 assert(invariant(element, element.isImplementation)); | |
3420 | |
3421 HInstruction compileArgument(ast.Node argument) { | |
3422 visit(argument); | |
3423 return pop(); | |
3424 } | |
3425 | |
3426 return selector.addArgumentsToList(arguments, | |
3427 list, | |
3428 element, | |
3429 compileArgument, | |
3430 handleConstantForOptionalParameter, | |
3431 compiler.world); | |
3432 } | |
3433 | |
3434 void addGenericSendArgumentsToList(Link<ast.Node> link, List<HInstruction> lis
t) { | |
3435 for (; !link.isEmpty; link = link.tail) { | |
3436 visit(link.head); | |
3437 list.add(pop()); | |
3438 } | |
3439 } | |
3440 | |
3441 visitDynamicSend(ast.Send node) { | |
3442 Selector selector = elements.getSelector(node); | |
3443 | |
3444 List<HInstruction> inputs = <HInstruction>[]; | |
3445 HInstruction receiver = generateInstanceSendReceiver(node); | |
3446 inputs.add(receiver); | |
3447 addDynamicSendArgumentsToList(node, inputs); | |
3448 | |
3449 pushInvokeDynamic(node, selector, inputs); | |
3450 if (selector.isSetter || selector.isIndexSet) { | |
3451 pop(); | |
3452 stack.add(inputs.last); | |
3453 } | |
3454 } | |
3455 | |
3456 visitClosureSend(ast.Send node) { | |
3457 Selector selector = elements.getSelector(node); | |
3458 assert(node.receiver == null); | |
3459 Element element = elements[node]; | |
3460 HInstruction closureTarget; | |
3461 if (element == null) { | |
3462 visit(node.selector); | |
3463 closureTarget = pop(); | |
3464 } else { | |
3465 LocalElement local = element; | |
3466 closureTarget = localsHandler.readLocal(local); | |
3467 } | |
3468 var inputs = <HInstruction>[]; | |
3469 inputs.add(closureTarget); | |
3470 addDynamicSendArgumentsToList(node, inputs); | |
3471 Selector closureSelector = new Selector.callClosureFrom(selector); | |
3472 pushWithPosition( | |
3473 new HInvokeClosure(closureSelector, inputs, backend.dynamicType), | |
3474 node); | |
3475 } | |
3476 | |
3477 void handleForeignJs(ast.Send node) { | |
3478 Link<ast.Node> link = node.arguments; | |
3479 // If the invoke is on foreign code, don't visit the first | |
3480 // argument, which is the type, and the second argument, | |
3481 // which is the foreign code. | |
3482 if (link.isEmpty || link.tail.isEmpty) { | |
3483 compiler.internalError(node.argumentsNode, | |
3484 'At least two arguments expected.'); | |
3485 } | |
3486 native.NativeBehavior nativeBehavior = | |
3487 compiler.enqueuer.resolution.nativeEnqueuer.getNativeBehaviorOf(node); | |
3488 | |
3489 List<HInstruction> inputs = <HInstruction>[]; | |
3490 addGenericSendArgumentsToList(link.tail.tail, inputs); | |
3491 | |
3492 TypeMask ssaType = | |
3493 TypeMaskFactory.fromNativeBehavior(nativeBehavior, compiler); | |
3494 | |
3495 if (nativeBehavior.codeTemplate.isExpression) { | |
3496 push(new HForeign(nativeBehavior.codeTemplate, ssaType, inputs, | |
3497 effects: nativeBehavior.sideEffects, | |
3498 nativeBehavior: nativeBehavior)); | |
3499 } else { | |
3500 push(new HForeign(nativeBehavior.codeTemplate, ssaType, inputs, | |
3501 isStatement: true, | |
3502 effects: nativeBehavior.sideEffects, | |
3503 nativeBehavior: nativeBehavior, | |
3504 canThrow: true)); | |
3505 } | |
3506 } | |
3507 | |
3508 void handleJsStringConcat(ast.Send node) { | |
3509 List<HInstruction> inputs = <HInstruction>[]; | |
3510 addGenericSendArgumentsToList(node.arguments, inputs); | |
3511 if (inputs.length != 2) { | |
3512 compiler.internalError(node.argumentsNode, 'Two arguments expected.'); | |
3513 } | |
3514 push(new HStringConcat(inputs[0], inputs[1], node, backend.stringType)); | |
3515 } | |
3516 | |
3517 void handleForeignJsCurrentIsolateContext(ast.Send node) { | |
3518 if (!node.arguments.isEmpty) { | |
3519 compiler.internalError(node, | |
3520 'Too many arguments to JS_CURRENT_ISOLATE_CONTEXT.'); | |
3521 } | |
3522 | |
3523 if (!compiler.hasIsolateSupport) { | |
3524 // If the isolate library is not used, we just generate code | |
3525 // to fetch the current isolate. | |
3526 String name = backend.namer.currentIsolate; | |
3527 push(new HForeign(js.js.parseForeignJS(name), | |
3528 backend.dynamicType, | |
3529 <HInstruction>[])); | |
3530 } else { | |
3531 // Call a helper method from the isolate library. The isolate | |
3532 // library uses its own isolate structure, that encapsulates | |
3533 // Leg's isolate. | |
3534 Element element = backend.isolateHelperLibrary.find('_currentIsolate'); | |
3535 if (element == null) { | |
3536 compiler.internalError(node, | |
3537 'Isolate library and compiler mismatch.'); | |
3538 } | |
3539 pushInvokeStatic(null, element, [], backend.dynamicType); | |
3540 } | |
3541 } | |
3542 | |
3543 void handleForeingJsGetFlag(ast.Send node) { | |
3544 List<ast.Node> arguments = node.arguments.toList(); | |
3545 ast.Node argument; | |
3546 switch (arguments.length) { | |
3547 case 0: | |
3548 compiler.reportError( | |
3549 node, MessageKind.GENERIC, | |
3550 {'text': 'Error: Expected one argument to JS_GET_FLAG.'}); | |
3551 return; | |
3552 case 1: | |
3553 argument = arguments[0]; | |
3554 break; | |
3555 default: | |
3556 for (int i = 1; i < arguments.length; i++) { | |
3557 compiler.reportError( | |
3558 arguments[i], MessageKind.GENERIC, | |
3559 {'text': 'Error: Extra argument to JS_GET_FLAG.'}); | |
3560 } | |
3561 return; | |
3562 } | |
3563 ast.LiteralString string = argument.asLiteralString(); | |
3564 if (string == null) { | |
3565 compiler.reportError( | |
3566 argument, MessageKind.GENERIC, | |
3567 {'text': 'Error: Expected a literal string.'}); | |
3568 } | |
3569 String name = string.dartString.slowToString(); | |
3570 bool value = false; | |
3571 if (name == 'MUST_RETAIN_METADATA') { | |
3572 value = backend.mustRetainMetadata; | |
3573 } else { | |
3574 compiler.reportError( | |
3575 node, MessageKind.GENERIC, | |
3576 {'text': 'Error: Unknown internal flag "$name".'}); | |
3577 } | |
3578 stack.add(graph.addConstantBool(value, compiler)); | |
3579 } | |
3580 | |
3581 void handleForeignJsGetName(ast.Send node) { | |
3582 List<ast.Node> arguments = node.arguments.toList(); | |
3583 ast.Node argument; | |
3584 switch (arguments.length) { | |
3585 case 0: | |
3586 compiler.reportError( | |
3587 node, MessageKind.GENERIC, | |
3588 {'text': 'Error: Expected one argument to JS_GET_NAME.'}); | |
3589 return; | |
3590 case 1: | |
3591 argument = arguments[0]; | |
3592 break; | |
3593 default: | |
3594 for (int i = 1; i < arguments.length; i++) { | |
3595 compiler.reportError( | |
3596 arguments[i], MessageKind.GENERIC, | |
3597 {'text': 'Error: Extra argument to JS_GET_NAME.'}); | |
3598 } | |
3599 return; | |
3600 } | |
3601 ast.LiteralString string = argument.asLiteralString(); | |
3602 if (string == null) { | |
3603 compiler.reportError( | |
3604 argument, MessageKind.GENERIC, | |
3605 {'text': 'Error: Expected a literal string.'}); | |
3606 } | |
3607 stack.add( | |
3608 addConstantString( | |
3609 backend.namer.getNameForJsGetName( | |
3610 argument, string.dartString.slowToString()))); | |
3611 } | |
3612 | |
3613 void handleForeignJsEmbeddedGlobal(ast.Send node) { | |
3614 List<ast.Node> arguments = node.arguments.toList(); | |
3615 ast.Node globalNameNode; | |
3616 switch (arguments.length) { | |
3617 case 0: | |
3618 case 1: | |
3619 compiler.reportError( | |
3620 node, MessageKind.GENERIC, | |
3621 {'text': 'Error: Expected two arguments to JS_EMBEDDED_GLOBAL.'}); | |
3622 return; | |
3623 case 2: | |
3624 // The type has been extracted earlier. We are only interested in the | |
3625 // name in this function. | |
3626 globalNameNode = arguments[1]; | |
3627 break; | |
3628 default: | |
3629 for (int i = 2; i < arguments.length; i++) { | |
3630 compiler.reportError( | |
3631 arguments[i], MessageKind.GENERIC, | |
3632 {'text': 'Error: Extra argument to JS_EMBEDDED_GLOBAL.'}); | |
3633 } | |
3634 return; | |
3635 } | |
3636 visit(arguments[1]); | |
3637 HInstruction globalNameHNode = pop(); | |
3638 if (!globalNameHNode.isConstantString()) { | |
3639 compiler.reportError( | |
3640 arguments[1], MessageKind.GENERIC, | |
3641 {'text': 'Error: Expected String as second argument ' | |
3642 'to JS_EMBEDDED_GLOBAL.'}); | |
3643 return; | |
3644 } | |
3645 HConstant hConstant = globalNameHNode; | |
3646 StringConstantValue constant = hConstant.constant; | |
3647 String globalName = constant.primitiveValue.slowToString(); | |
3648 js.Template expr = js.js.expressionTemplateYielding( | |
3649 backend.emitter.generateEmbeddedGlobalAccess(globalName)); | |
3650 native.NativeBehavior nativeBehavior = | |
3651 compiler.enqueuer.resolution.nativeEnqueuer.getNativeBehaviorOf(node); | |
3652 TypeMask ssaType = | |
3653 TypeMaskFactory.fromNativeBehavior(nativeBehavior, compiler); | |
3654 push(new HForeign(expr, ssaType, const [])); | |
3655 } | |
3656 | |
3657 void handleJsInterceptorConstant(ast.Send node) { | |
3658 // Single argument must be a TypeConstant which is converted into a | |
3659 // InterceptorConstant. | |
3660 if (!node.arguments.isEmpty && node.arguments.tail.isEmpty) { | |
3661 ast.Node argument = node.arguments.head; | |
3662 visit(argument); | |
3663 HInstruction argumentInstruction = pop(); | |
3664 if (argumentInstruction is HConstant) { | |
3665 ConstantValue argumentConstant = argumentInstruction.constant; | |
3666 if (argumentConstant is TypeConstantValue) { | |
3667 ConstantValue constant = | |
3668 new InterceptorConstantValue(argumentConstant.representedType); | |
3669 HInstruction instruction = graph.addConstant(constant, compiler); | |
3670 stack.add(instruction); | |
3671 return; | |
3672 } | |
3673 } | |
3674 } | |
3675 compiler.reportError(node, | |
3676 MessageKind.WRONG_ARGUMENT_FOR_JS_INTERCEPTOR_CONSTANT); | |
3677 stack.add(graph.addConstantNull(compiler)); | |
3678 } | |
3679 | |
3680 void handleForeignJsCallInIsolate(ast.Send node) { | |
3681 Link<ast.Node> link = node.arguments; | |
3682 if (!compiler.hasIsolateSupport) { | |
3683 // If the isolate library is not used, we just invoke the | |
3684 // closure. | |
3685 visit(link.tail.head); | |
3686 Selector selector = new Selector.callClosure(0); | |
3687 push(new HInvokeClosure(selector, | |
3688 <HInstruction>[pop()], | |
3689 backend.dynamicType)); | |
3690 } else { | |
3691 // Call a helper method from the isolate library. | |
3692 Element element = backend.isolateHelperLibrary.find('_callInIsolate'); | |
3693 if (element == null) { | |
3694 compiler.internalError(node, | |
3695 'Isolate library and compiler mismatch.'); | |
3696 } | |
3697 List<HInstruction> inputs = <HInstruction>[]; | |
3698 addGenericSendArgumentsToList(link, inputs); | |
3699 pushInvokeStatic(node, element, inputs, backend.dynamicType); | |
3700 } | |
3701 } | |
3702 | |
3703 FunctionSignature handleForeignRawFunctionRef(ast.Send node, String name) { | |
3704 if (node.arguments.isEmpty || !node.arguments.tail.isEmpty) { | |
3705 compiler.internalError(node.argumentsNode, | |
3706 '"$name" requires exactly one argument.'); | |
3707 } | |
3708 ast.Node closure = node.arguments.head; | |
3709 Element element = elements[closure]; | |
3710 if (!Elements.isStaticOrTopLevelFunction(element)) { | |
3711 compiler.internalError(closure, | |
3712 '"$name" requires a static or top-level method.'); | |
3713 } | |
3714 FunctionElement function = element; | |
3715 // TODO(johnniwinther): Try to eliminate the need to distinguish declaration | |
3716 // and implementation signatures. Currently it is need because the | |
3717 // signatures have different elements for parameters. | |
3718 FunctionElement implementation = function.implementation; | |
3719 FunctionSignature params = implementation.functionSignature; | |
3720 if (params.optionalParameterCount != 0) { | |
3721 compiler.internalError(closure, | |
3722 '"$name" does not handle closure with optional parameters.'); | |
3723 } | |
3724 | |
3725 registry.registerStaticUse(element); | |
3726 push(new HForeign(js.js.expressionTemplateYielding( | |
3727 backend.namer.elementAccess(element)), | |
3728 backend.dynamicType, | |
3729 <HInstruction>[])); | |
3730 return params; | |
3731 } | |
3732 | |
3733 void handleForeignDartClosureToJs(ast.Send node, String name) { | |
3734 // TODO(ahe): This implements DART_CLOSURE_TO_JS and should probably take | |
3735 // care to wrap the closure in another closure that saves the current | |
3736 // isolate. | |
3737 handleForeignRawFunctionRef(node, name); | |
3738 } | |
3739 | |
3740 void handleForeignSetCurrentIsolate(ast.Send node) { | |
3741 if (node.arguments.isEmpty || !node.arguments.tail.isEmpty) { | |
3742 compiler.internalError(node.argumentsNode, | |
3743 'Exactly one argument required.'); | |
3744 } | |
3745 visit(node.arguments.head); | |
3746 String isolateName = backend.namer.currentIsolate; | |
3747 SideEffects sideEffects = new SideEffects.empty(); | |
3748 sideEffects.setAllSideEffects(); | |
3749 push(new HForeign(js.js.parseForeignJS("$isolateName = #"), | |
3750 backend.dynamicType, | |
3751 <HInstruction>[pop()], | |
3752 effects: sideEffects)); | |
3753 } | |
3754 | |
3755 void handleForeignCreateIsolate(ast.Send node) { | |
3756 if (!node.arguments.isEmpty) { | |
3757 compiler.internalError(node.argumentsNode, 'Too many arguments.'); | |
3758 } | |
3759 String constructorName = backend.namer.isolateName; | |
3760 push(new HForeign(js.js.parseForeignJS("new $constructorName()"), | |
3761 backend.dynamicType, | |
3762 <HInstruction>[])); | |
3763 } | |
3764 | |
3765 void handleForeignDartObjectJsConstructorFunction(ast.Send node) { | |
3766 if (!node.arguments.isEmpty) { | |
3767 compiler.internalError(node.argumentsNode, 'Too many arguments.'); | |
3768 } | |
3769 push(new HForeign(js.js.expressionTemplateYielding( | |
3770 backend.namer.elementAccess(compiler.objectClass)), | |
3771 backend.dynamicType, | |
3772 <HInstruction>[])); | |
3773 } | |
3774 | |
3775 void handleForeignJsCurrentIsolate(ast.Send node) { | |
3776 if (!node.arguments.isEmpty) { | |
3777 compiler.internalError(node.argumentsNode, 'Too many arguments.'); | |
3778 } | |
3779 push(new HForeign(js.js.parseForeignJS(backend.namer.currentIsolate), | |
3780 backend.dynamicType, | |
3781 <HInstruction>[])); | |
3782 } | |
3783 | |
3784 visitForeignSend(ast.Send node) { | |
3785 Selector selector = elements.getSelector(node); | |
3786 String name = selector.name; | |
3787 if (name == 'JS') { | |
3788 handleForeignJs(node); | |
3789 } else if (name == 'JS_CURRENT_ISOLATE_CONTEXT') { | |
3790 handleForeignJsCurrentIsolateContext(node); | |
3791 } else if (name == 'JS_CALL_IN_ISOLATE') { | |
3792 handleForeignJsCallInIsolate(node); | |
3793 } else if (name == 'DART_CLOSURE_TO_JS') { | |
3794 handleForeignDartClosureToJs(node, 'DART_CLOSURE_TO_JS'); | |
3795 } else if (name == 'RAW_DART_FUNCTION_REF') { | |
3796 handleForeignRawFunctionRef(node, 'RAW_DART_FUNCTION_REF'); | |
3797 } else if (name == 'JS_SET_CURRENT_ISOLATE') { | |
3798 handleForeignSetCurrentIsolate(node); | |
3799 } else if (name == 'JS_CREATE_ISOLATE') { | |
3800 handleForeignCreateIsolate(node); | |
3801 } else if (name == 'JS_OPERATOR_IS_PREFIX') { | |
3802 stack.add(addConstantString(backend.namer.operatorIsPrefix())); | |
3803 } else if (name == 'JS_OBJECT_CLASS_NAME') { | |
3804 String name = backend.namer.getRuntimeTypeName(compiler.objectClass); | |
3805 stack.add(addConstantString(name)); | |
3806 } else if (name == 'JS_NULL_CLASS_NAME') { | |
3807 String name = backend.namer.getRuntimeTypeName(compiler.nullClass); | |
3808 stack.add(addConstantString(name)); | |
3809 } else if (name == 'JS_FUNCTION_CLASS_NAME') { | |
3810 String name = backend.namer.getRuntimeTypeName(compiler.functionClass); | |
3811 stack.add(addConstantString(name)); | |
3812 } else if (name == 'JS_OPERATOR_AS_PREFIX') { | |
3813 stack.add(addConstantString(backend.namer.operatorAsPrefix())); | |
3814 } else if (name == 'JS_SIGNATURE_NAME') { | |
3815 stack.add(addConstantString(backend.namer.operatorSignature())); | |
3816 } else if (name == 'JS_FUNCTION_TYPE_TAG') { | |
3817 stack.add(addConstantString(backend.namer.functionTypeTag())); | |
3818 } else if (name == 'JS_FUNCTION_TYPE_VOID_RETURN_TAG') { | |
3819 stack.add(addConstantString(backend.namer.functionTypeVoidReturnTag())); | |
3820 } else if (name == 'JS_FUNCTION_TYPE_RETURN_TYPE_TAG') { | |
3821 stack.add(addConstantString(backend.namer.functionTypeReturnTypeTag())); | |
3822 } else if (name == | |
3823 'JS_FUNCTION_TYPE_REQUIRED_PARAMETERS_TAG') { | |
3824 stack.add(addConstantString( | |
3825 backend.namer.functionTypeRequiredParametersTag())); | |
3826 } else if (name == | |
3827 'JS_FUNCTION_TYPE_OPTIONAL_PARAMETERS_TAG') { | |
3828 stack.add(addConstantString( | |
3829 backend.namer.functionTypeOptionalParametersTag())); | |
3830 } else if (name == | |
3831 'JS_FUNCTION_TYPE_NAMED_PARAMETERS_TAG') { | |
3832 stack.add(addConstantString( | |
3833 backend.namer.functionTypeNamedParametersTag())); | |
3834 } else if (name == 'JS_DART_OBJECT_CONSTRUCTOR') { | |
3835 handleForeignDartObjectJsConstructorFunction(node); | |
3836 } else if (name == 'JS_IS_INDEXABLE_FIELD_NAME') { | |
3837 Element element = backend.findHelper('JavaScriptIndexingBehavior'); | |
3838 stack.add(addConstantString(backend.namer.operatorIs(element))); | |
3839 } else if (name == 'JS_CURRENT_ISOLATE') { | |
3840 handleForeignJsCurrentIsolate(node); | |
3841 } else if (name == 'JS_GET_NAME') { | |
3842 handleForeignJsGetName(node); | |
3843 } else if (name == 'JS_EMBEDDED_GLOBAL') { | |
3844 handleForeignJsEmbeddedGlobal(node); | |
3845 } else if (name == 'JS_GET_FLAG') { | |
3846 handleForeingJsGetFlag(node); | |
3847 } else if (name == 'JS_EFFECT') { | |
3848 stack.add(graph.addConstantNull(compiler)); | |
3849 } else if (name == 'JS_INTERCEPTOR_CONSTANT') { | |
3850 handleJsInterceptorConstant(node); | |
3851 } else if (name == 'JS_STRING_CONCAT') { | |
3852 handleJsStringConcat(node); | |
3853 } else { | |
3854 throw "Unknown foreign: ${selector}"; | |
3855 } | |
3856 } | |
3857 | |
3858 visitForeignGetter(ast.Send node) { | |
3859 Element element = elements[node]; | |
3860 // Until now we only handle these as getters. | |
3861 invariant(node, element.isDeferredLoaderGetter); | |
3862 FunctionElement deferredLoader = element; | |
3863 Element loadFunction = compiler.loadLibraryFunction; | |
3864 PrefixElement prefixElement = deferredLoader.enclosingElement; | |
3865 String loadId = compiler.deferredLoadTask | |
3866 .importDeferName[prefixElement.deferredImport]; | |
3867 var inputs = [graph.addConstantString( | |
3868 new ast.DartString.literal(loadId), compiler)]; | |
3869 push(new HInvokeStatic(loadFunction, inputs, backend.nonNullType, | |
3870 targetCanThrow: false)); | |
3871 } | |
3872 | |
3873 generateSuperNoSuchMethodSend(ast.Send node, | |
3874 Selector selector, | |
3875 List<HInstruction> arguments) { | |
3876 String name = selector.name; | |
3877 | |
3878 ClassElement cls = currentNonClosureClass; | |
3879 Element element = cls.lookupSuperMember(Compiler.NO_SUCH_METHOD); | |
3880 if (compiler.enabledInvokeOn | |
3881 && element.enclosingElement.declaration != compiler.objectClass) { | |
3882 // Register the call as dynamic if [noSuchMethod] on the super | |
3883 // class is _not_ the default implementation from [Object], in | |
3884 // case the [noSuchMethod] implementation calls | |
3885 // [JSInvocationMirror._invokeOn]. | |
3886 registry.registerSelectorUse(selector.asUntyped); | |
3887 } | |
3888 String publicName = name; | |
3889 if (selector.isSetter) publicName += '='; | |
3890 | |
3891 ConstantValue nameConstant = constantSystem.createString( | |
3892 new ast.DartString.literal(publicName)); | |
3893 | |
3894 String internalName = backend.namer.invocationName(selector); | |
3895 ConstantValue internalNameConstant = | |
3896 constantSystem.createString(new ast.DartString.literal(internalName)); | |
3897 | |
3898 Element createInvocationMirror = backend.getCreateInvocationMirror(); | |
3899 var argumentsInstruction = buildLiteralList(arguments); | |
3900 add(argumentsInstruction); | |
3901 | |
3902 var argumentNames = new List<HInstruction>(); | |
3903 for (String argumentName in selector.namedArguments) { | |
3904 ConstantValue argumentNameConstant = | |
3905 constantSystem.createString(new ast.DartString.literal(argumentName)); | |
3906 argumentNames.add(graph.addConstant(argumentNameConstant, compiler)); | |
3907 } | |
3908 var argumentNamesInstruction = buildLiteralList(argumentNames); | |
3909 add(argumentNamesInstruction); | |
3910 | |
3911 ConstantValue kindConstant = | |
3912 constantSystem.createInt(selector.invocationMirrorKind); | |
3913 | |
3914 pushInvokeStatic(null, | |
3915 createInvocationMirror, | |
3916 [graph.addConstant(nameConstant, compiler), | |
3917 graph.addConstant(internalNameConstant, compiler), | |
3918 graph.addConstant(kindConstant, compiler), | |
3919 argumentsInstruction, | |
3920 argumentNamesInstruction], | |
3921 backend.dynamicType); | |
3922 | |
3923 var inputs = <HInstruction>[pop()]; | |
3924 push(buildInvokeSuper(compiler.noSuchMethodSelector, element, inputs)); | |
3925 } | |
3926 | |
3927 visitSuperSend(ast.Send node) { | |
3928 Selector selector = elements.getSelector(node); | |
3929 Element element = elements[node]; | |
3930 if (Elements.isUnresolved(element)) { | |
3931 List<HInstruction> arguments = <HInstruction>[]; | |
3932 if (!node.isPropertyAccess) { | |
3933 addGenericSendArgumentsToList(node.arguments, arguments); | |
3934 } | |
3935 return generateSuperNoSuchMethodSend(node, selector, arguments); | |
3936 } | |
3937 List<HInstruction> inputs = <HInstruction>[]; | |
3938 if (node.isPropertyAccess) { | |
3939 push(buildInvokeSuper(selector, element, inputs)); | |
3940 } else if (element.isFunction || element.isGenerativeConstructor) { | |
3941 if (selector.applies(element, compiler.world)) { | |
3942 // TODO(5347): Try to avoid the need for calling [implementation] before | |
3943 // calling [addStaticSendArgumentsToList]. | |
3944 FunctionElement function = element.implementation; | |
3945 bool succeeded = addStaticSendArgumentsToList(selector, node.arguments, | |
3946 function, inputs); | |
3947 assert(succeeded); | |
3948 push(buildInvokeSuper(selector, element, inputs)); | |
3949 } else if (element.isGenerativeConstructor) { | |
3950 generateWrongArgumentCountError(node, element, node.arguments); | |
3951 } else { | |
3952 addGenericSendArgumentsToList(node.arguments, inputs); | |
3953 generateSuperNoSuchMethodSend(node, selector, inputs); | |
3954 } | |
3955 } else { | |
3956 HInstruction target = buildInvokeSuper(selector, element, inputs); | |
3957 add(target); | |
3958 inputs = <HInstruction>[target]; | |
3959 addDynamicSendArgumentsToList(node, inputs); | |
3960 Selector closureSelector = new Selector.callClosureFrom(selector); | |
3961 push(new HInvokeClosure(closureSelector, inputs, backend.dynamicType)); | |
3962 } | |
3963 } | |
3964 | |
3965 bool needsSubstitutionForTypeVariableAccess(ClassElement cls) { | |
3966 ClassWorld classWorld = compiler.world; | |
3967 if (classWorld.isUsedAsMixin(cls)) return true; | |
3968 | |
3969 Iterable<ClassElement> subclasses = compiler.world.strictSubclassesOf(cls); | |
3970 return subclasses.any((ClassElement subclass) { | |
3971 return !rti.isTrivialSubstitution(subclass, cls); | |
3972 }); | |
3973 } | |
3974 | |
3975 /** | |
3976 * Generate code to extract the type arguments from the object, substitute | |
3977 * them as an instance of the type we are testing against (if necessary), and | |
3978 * extract the type argument by the index of the variable in the list of type | |
3979 * variables for that class. | |
3980 */ | |
3981 HInstruction readTypeVariable(ClassElement cls, | |
3982 TypeVariableElement variable) { | |
3983 assert(sourceElement.isInstanceMember); | |
3984 | |
3985 HInstruction target = localsHandler.readThis(); | |
3986 HConstant index = graph.addConstantInt( | |
3987 RuntimeTypes.getTypeVariableIndex(variable), | |
3988 compiler); | |
3989 | |
3990 if (needsSubstitutionForTypeVariableAccess(cls)) { | |
3991 // TODO(ahe): Creating a string here is unfortunate. It is slow (due to | |
3992 // string concatenation in the implementation), and may prevent | |
3993 // segmentation of '$'. | |
3994 String substitutionNameString = backend.namer.getNameForRti(cls); | |
3995 HInstruction substitutionName = graph.addConstantString( | |
3996 new ast.LiteralDartString(substitutionNameString), compiler); | |
3997 pushInvokeStatic(null, | |
3998 backend.getGetRuntimeTypeArgument(), | |
3999 [target, substitutionName, index], | |
4000 backend.dynamicType); | |
4001 } else { | |
4002 pushInvokeStatic(null, backend.getGetTypeArgumentByIndex(), | |
4003 [target, index], | |
4004 backend.dynamicType); | |
4005 } | |
4006 return pop(); | |
4007 } | |
4008 | |
4009 // TODO(karlklose): this is needed to avoid a bug where the resolved type is | |
4010 // not stored on a type annotation in the closure translator. Remove when | |
4011 // fixed. | |
4012 bool hasDirectLocal(Local local) { | |
4013 return !localsHandler.isAccessedDirectly(local) || | |
4014 localsHandler.directLocals[local] != null; | |
4015 } | |
4016 | |
4017 /** | |
4018 * Helper to create an instruction that gets the value of a type variable. | |
4019 */ | |
4020 HInstruction addTypeVariableReference(TypeVariableType type) { | |
4021 assert(assertTypeInContext(type)); | |
4022 Element member = sourceElement; | |
4023 bool isClosure = member.enclosingElement.isClosure; | |
4024 if (isClosure) { | |
4025 ClosureClassElement closureClass = member.enclosingElement; | |
4026 member = closureClass.methodElement; | |
4027 member = member.outermostEnclosingMemberOrTopLevel; | |
4028 } | |
4029 bool isInConstructorContext = member.isConstructor || | |
4030 member.isGenerativeConstructorBody; | |
4031 Local typeVariableLocal = localsHandler.getTypeVariableAsLocal(type); | |
4032 if (isClosure) { | |
4033 if (member.isFactoryConstructor || | |
4034 (isInConstructorContext && hasDirectLocal(typeVariableLocal))) { | |
4035 // The type variable is used from a closure in a factory constructor. | |
4036 // The value of the type argument is stored as a local on the closure | |
4037 // itself. | |
4038 return localsHandler.readLocal(typeVariableLocal); | |
4039 } else if (member.isFunction || | |
4040 member.isGetter || | |
4041 member.isSetter || | |
4042 isInConstructorContext) { | |
4043 // The type variable is stored on the "enclosing object" and needs to be | |
4044 // accessed using the this-reference in the closure. | |
4045 return readTypeVariable(member.enclosingClass, type.element); | |
4046 } else { | |
4047 assert(member.isField); | |
4048 // The type variable is stored in a parameter of the method. | |
4049 return localsHandler.readLocal(typeVariableLocal); | |
4050 } | |
4051 } else if (isInConstructorContext || | |
4052 // When [member] is a field, we can be either | |
4053 // generating a checked setter or inlining its | |
4054 // initializer in a constructor. An initializer is | |
4055 // never built standalone, so [isBuildingFor] will | |
4056 // always return true when seeing one. | |
4057 (member.isField && !isBuildingFor(member))) { | |
4058 // The type variable is stored in a parameter of the method. | |
4059 return localsHandler.readLocal(typeVariableLocal); | |
4060 } else if (member.isInstanceMember) { | |
4061 // The type variable is stored on the object. | |
4062 return readTypeVariable(member.enclosingClass, | |
4063 type.element); | |
4064 } else { | |
4065 // TODO(ngeoffray): Match the VM behavior and throw an | |
4066 // exception at runtime. | |
4067 compiler.internalError(type.element, | |
4068 'Unimplemented unresolved type variable.'); | |
4069 return null; | |
4070 } | |
4071 } | |
4072 | |
4073 HInstruction analyzeTypeArgument(DartType argument) { | |
4074 assert(assertTypeInContext(argument)); | |
4075 if (argument.treatAsDynamic) { | |
4076 // Represent [dynamic] as [null]. | |
4077 return graph.addConstantNull(compiler); | |
4078 } | |
4079 | |
4080 if (argument.isTypeVariable) { | |
4081 return addTypeVariableReference(argument); | |
4082 } | |
4083 | |
4084 List<HInstruction> inputs = <HInstruction>[]; | |
4085 | |
4086 String template = rti.getTypeRepresentationWithHashes(argument, (variable) { | |
4087 inputs.add(addTypeVariableReference(variable)); | |
4088 }); | |
4089 | |
4090 js.Template code = js.js.uncachedExpressionTemplate(template); | |
4091 HInstruction result = createForeign(code, backend.stringType, inputs); | |
4092 add(result); | |
4093 return result; | |
4094 } | |
4095 | |
4096 HInstruction handleListConstructor(InterfaceType type, | |
4097 ast.Node currentNode, | |
4098 HInstruction newObject) { | |
4099 if (!backend.classNeedsRti(type.element) || type.treatAsRaw) { | |
4100 return newObject; | |
4101 } | |
4102 List<HInstruction> inputs = <HInstruction>[]; | |
4103 type = localsHandler.substInContext(type); | |
4104 type.typeArguments.forEach((DartType argument) { | |
4105 inputs.add(analyzeTypeArgument(argument)); | |
4106 }); | |
4107 // TODO(15489): Register at codegen. | |
4108 registry.registerInstantiatedType(type); | |
4109 return callSetRuntimeTypeInfo(type.element, inputs, newObject); | |
4110 } | |
4111 | |
4112 void copyRuntimeTypeInfo(HInstruction source, HInstruction target) { | |
4113 Element copyHelper = backend.getCopyTypeArguments(); | |
4114 pushInvokeStatic(null, copyHelper, [source, target]); | |
4115 pop(); | |
4116 } | |
4117 | |
4118 HInstruction callSetRuntimeTypeInfo(ClassElement element, | |
4119 List<HInstruction> rtiInputs, | |
4120 HInstruction newObject) { | |
4121 if (!backend.classNeedsRti(element) || element.typeVariables.isEmpty) { | |
4122 return newObject; | |
4123 } | |
4124 | |
4125 HInstruction typeInfo = buildLiteralList(rtiInputs); | |
4126 add(typeInfo); | |
4127 | |
4128 // Set the runtime type information on the object. | |
4129 Element typeInfoSetterElement = backend.getSetRuntimeTypeInfo(); | |
4130 pushInvokeStatic( | |
4131 null, | |
4132 typeInfoSetterElement, | |
4133 <HInstruction>[newObject, typeInfo], | |
4134 backend.dynamicType); | |
4135 | |
4136 // The new object will now be referenced through the | |
4137 // `setRuntimeTypeInfo` call. We therefore set the type of that | |
4138 // instruction to be of the object's type. | |
4139 assert(stack.last is HInvokeStatic || stack.last == newObject); | |
4140 stack.last.instructionType = newObject.instructionType; | |
4141 return pop(); | |
4142 } | |
4143 | |
4144 handleNewSend(ast.NewExpression node) { | |
4145 ast.Send send = node.send; | |
4146 generateIsDeferredLoadedCheckIfNeeded(send); | |
4147 | |
4148 bool isFixedList = false; | |
4149 bool isFixedListConstructorCall = | |
4150 Elements.isFixedListConstructorCall(elements[send], send, compiler); | |
4151 bool isGrowableListConstructorCall = | |
4152 Elements.isGrowableListConstructorCall(elements[send], send, compiler); | |
4153 | |
4154 TypeMask computeType(element) { | |
4155 Element originalElement = elements[send]; | |
4156 if (isFixedListConstructorCall | |
4157 || Elements.isFilledListConstructorCall( | |
4158 originalElement, send, compiler)) { | |
4159 isFixedList = true; | |
4160 TypeMask inferred = | |
4161 TypeMaskFactory.inferredForNode(sourceElement, send, compiler); | |
4162 return inferred.containsAll(compiler.world) | |
4163 ? backend.fixedArrayType | |
4164 : inferred; | |
4165 } else if (isGrowableListConstructorCall) { | |
4166 TypeMask inferred = | |
4167 TypeMaskFactory.inferredForNode(sourceElement, send, compiler); | |
4168 return inferred.containsAll(compiler.world) | |
4169 ? backend.extendableArrayType | |
4170 : inferred; | |
4171 } else if (Elements.isConstructorOfTypedArraySubclass( | |
4172 originalElement, compiler)) { | |
4173 isFixedList = true; | |
4174 TypeMask inferred = | |
4175 TypeMaskFactory.inferredForNode(sourceElement, send, compiler); | |
4176 ClassElement cls = element.enclosingClass; | |
4177 assert(cls.thisType.element.isNative); | |
4178 return inferred.containsAll(compiler.world) | |
4179 ? new TypeMask.nonNullExact(cls.thisType.element, compiler.world) | |
4180 : inferred; | |
4181 } else if (element.isGenerativeConstructor) { | |
4182 ClassElement cls = element.enclosingClass; | |
4183 return new TypeMask.nonNullExact(cls.thisType.element, compiler.world); | |
4184 } else { | |
4185 return TypeMaskFactory.inferredReturnTypeForElement( | |
4186 originalElement, compiler); | |
4187 } | |
4188 } | |
4189 | |
4190 Element constructor = elements[send]; | |
4191 Selector selector = elements.getSelector(send); | |
4192 ConstructorElement constructorDeclaration = constructor; | |
4193 ConstructorElement constructorImplementation = constructor.implementation; | |
4194 constructor = constructorImplementation.effectiveTarget; | |
4195 | |
4196 final bool isSymbolConstructor = | |
4197 constructorDeclaration == compiler.symbolConstructor; | |
4198 final bool isJSArrayTypedConstructor = | |
4199 constructorDeclaration == backend.jsArrayTypedConstructor; | |
4200 | |
4201 if (isSymbolConstructor) { | |
4202 constructor = compiler.symbolValidatedConstructor; | |
4203 assert(invariant(send, constructor != null, | |
4204 message: 'Constructor Symbol.validated is missing')); | |
4205 selector = compiler.symbolValidatedConstructorSelector; | |
4206 assert(invariant(send, selector != null, | |
4207 message: 'Constructor Symbol.validated is missing')); | |
4208 } | |
4209 | |
4210 bool isRedirected = constructorDeclaration.isRedirectingFactory; | |
4211 InterfaceType type = elements.getType(node); | |
4212 InterfaceType expectedType = | |
4213 constructorDeclaration.computeEffectiveTargetType(type); | |
4214 expectedType = localsHandler.substInContext(expectedType); | |
4215 | |
4216 if (checkTypeVariableBounds(node, type)) return; | |
4217 | |
4218 var inputs = <HInstruction>[]; | |
4219 if (constructor.isGenerativeConstructor && | |
4220 Elements.isNativeOrExtendsNative(constructor.enclosingClass)) { | |
4221 // Native class generative constructors take a pre-constructed object. | |
4222 inputs.add(graph.addConstantNull(compiler)); | |
4223 } | |
4224 // TODO(5347): Try to avoid the need for calling [implementation] before | |
4225 // calling [addStaticSendArgumentsToList]. | |
4226 bool succeeded = addStaticSendArgumentsToList(selector, send.arguments, | |
4227 constructor.implementation, | |
4228 inputs); | |
4229 if (!succeeded) { | |
4230 generateWrongArgumentCountError(send, constructor, send.arguments); | |
4231 return; | |
4232 } | |
4233 | |
4234 if (constructor.isFactoryConstructor && | |
4235 !expectedType.typeArguments.isEmpty) { | |
4236 registry.registerFactoryWithTypeArguments(); | |
4237 } | |
4238 | |
4239 TypeMask elementType = computeType(constructor); | |
4240 if (isFixedListConstructorCall) { | |
4241 if (!inputs[0].isNumber(compiler)) { | |
4242 HTypeConversion conversion = new HTypeConversion( | |
4243 null, HTypeConversion.ARGUMENT_TYPE_CHECK, backend.numType, | |
4244 inputs[0], null); | |
4245 add(conversion); | |
4246 inputs[0] = conversion; | |
4247 } | |
4248 js.Template code = js.js.parseForeignJS('Array(#)'); | |
4249 var behavior = new native.NativeBehavior(); | |
4250 behavior.typesReturned.add(expectedType); | |
4251 // The allocation can throw only if the given length is a double | |
4252 // or negative. | |
4253 bool canThrow = true; | |
4254 if (inputs[0].isInteger(compiler) && inputs[0] is HConstant) { | |
4255 var constant = inputs[0]; | |
4256 if (constant.constant.primitiveValue >= 0) canThrow = false; | |
4257 } | |
4258 HForeign foreign = new HForeign( | |
4259 code, elementType, inputs, nativeBehavior: behavior, | |
4260 canThrow: canThrow); | |
4261 push(foreign); | |
4262 TypesInferrer inferrer = compiler.typesTask.typesInferrer; | |
4263 if (inferrer.isFixedArrayCheckedForGrowable(send)) { | |
4264 js.Template code = js.js.parseForeignJS(r'#.fixed$length = init'); | |
4265 // We set the instruction as [canThrow] to avoid it being dead code. | |
4266 // We need a finer grained side effect. | |
4267 add(new HForeign( | |
4268 code, backend.nullType, [stack.last], canThrow: true)); | |
4269 } | |
4270 } else if (isGrowableListConstructorCall) { | |
4271 push(buildLiteralList(<HInstruction>[])); | |
4272 stack.last.instructionType = elementType; | |
4273 } else { | |
4274 ClassElement cls = constructor.enclosingClass; | |
4275 if (cls.isAbstract && constructor.isGenerativeConstructor) { | |
4276 generateAbstractClassInstantiationError(send, cls.name); | |
4277 return; | |
4278 } | |
4279 potentiallyAddTypeArguments(inputs, cls, expectedType); | |
4280 | |
4281 addInlinedInstantiation(expectedType); | |
4282 pushInvokeStatic(node, constructor, inputs, elementType); | |
4283 removeInlinedInstantiation(expectedType); | |
4284 } | |
4285 HInstruction newInstance = stack.last; | |
4286 if (isFixedList) { | |
4287 // Overwrite the element type, in case the allocation site has | |
4288 // been inlined. | |
4289 newInstance.instructionType = elementType; | |
4290 JavaScriptItemCompilationContext context = work.compilationContext; | |
4291 context.allocatedFixedLists.add(newInstance); | |
4292 } | |
4293 | |
4294 // The List constructor forwards to a Dart static method that does | |
4295 // not know about the type argument. Therefore we special case | |
4296 // this constructor to have the setRuntimeTypeInfo called where | |
4297 // the 'new' is done. | |
4298 if (backend.classNeedsRti(compiler.listClass) && | |
4299 (isFixedListConstructorCall || isGrowableListConstructorCall || | |
4300 isJSArrayTypedConstructor)) { | |
4301 newInstance = handleListConstructor(type, send, pop()); | |
4302 stack.add(newInstance); | |
4303 } | |
4304 | |
4305 // Finally, if we called a redirecting factory constructor, check the type. | |
4306 if (isRedirected) { | |
4307 HInstruction checked = potentiallyCheckType(newInstance, type); | |
4308 if (checked != newInstance) { | |
4309 pop(); | |
4310 stack.add(checked); | |
4311 } | |
4312 } | |
4313 } | |
4314 | |
4315 void potentiallyAddTypeArguments(List<HInstruction> inputs, ClassElement cls, | |
4316 InterfaceType expectedType) { | |
4317 if (!backend.classNeedsRti(cls)) return; | |
4318 assert(expectedType.typeArguments.isEmpty || | |
4319 cls.typeVariables.length == expectedType.typeArguments.length); | |
4320 expectedType.typeArguments.forEach((DartType argument) { | |
4321 inputs.add(analyzeTypeArgument(argument)); | |
4322 }); | |
4323 } | |
4324 | |
4325 /// In checked mode checks the [type] of [node] to be well-bounded. The method | |
4326 /// returns [:true:] if an error can be statically determined. | |
4327 bool checkTypeVariableBounds(ast.NewExpression node, InterfaceType type) { | |
4328 if (!compiler.enableTypeAssertions) return false; | |
4329 | |
4330 Map<DartType, Set<DartType>> seenChecksMap = | |
4331 new Map<DartType, Set<DartType>>(); | |
4332 bool definitelyFails = false; | |
4333 | |
4334 addTypeVariableBoundCheck(GenericType instance, | |
4335 DartType typeArgument, | |
4336 TypeVariableType typeVariable, | |
4337 DartType bound) { | |
4338 if (definitelyFails) return; | |
4339 | |
4340 int subtypeRelation = compiler.types.computeSubtypeRelation(typeArgument,
bound); | |
4341 if (subtypeRelation == Types.IS_SUBTYPE) return; | |
4342 | |
4343 String message = | |
4344 "Can't create an instance of malbounded type '$type': " | |
4345 "'${typeArgument}' is not a subtype of bound '${bound}' for " | |
4346 "type variable '${typeVariable}' of type " | |
4347 "${type == instance | |
4348 ? "'${type.element.thisType}'" | |
4349 : "'${instance.element.thisType}' on the supertype " | |
4350 "'${instance}' of '${type}'" | |
4351 }."; | |
4352 if (subtypeRelation == Types.NOT_SUBTYPE) { | |
4353 generateTypeError(node, message); | |
4354 definitelyFails = true; | |
4355 return; | |
4356 } else if (subtypeRelation == Types.MAYBE_SUBTYPE) { | |
4357 Set<DartType> seenChecks = | |
4358 seenChecksMap.putIfAbsent(typeArgument, () => new Set<DartType>()); | |
4359 if (!seenChecks.contains(bound)) { | |
4360 seenChecks.add(bound); | |
4361 assertIsSubtype(node, typeArgument, bound, message); | |
4362 } | |
4363 } | |
4364 } | |
4365 | |
4366 compiler.types.checkTypeVariableBounds(type, addTypeVariableBoundCheck); | |
4367 if (definitelyFails) { | |
4368 return true; | |
4369 } | |
4370 for (InterfaceType supertype in type.element.allSupertypes) { | |
4371 DartType instance = type.asInstanceOf(supertype.element); | |
4372 compiler.types.checkTypeVariableBounds(instance, | |
4373 addTypeVariableBoundCheck); | |
4374 if (definitelyFails) { | |
4375 return true; | |
4376 } | |
4377 } | |
4378 return false; | |
4379 } | |
4380 | |
4381 visitAssert(node) { | |
4382 if (!compiler.enableUserAssertions) { | |
4383 stack.add(graph.addConstantNull(compiler)); | |
4384 return; | |
4385 } | |
4386 // TODO(johnniwinther): Don't handle assert like a regular static call. | |
4387 // It breaks the selector name check since the assert helper method cannot | |
4388 // be called `assert` and therefore does not match the selector like a | |
4389 // regular method. | |
4390 visitStaticSend(node); | |
4391 } | |
4392 | |
4393 visitStaticSend(ast.Send node) { | |
4394 Selector selector = elements.getSelector(node); | |
4395 Element element = elements[node]; | |
4396 if (elements.isAssert(node)) { | |
4397 element = backend.assertMethod; | |
4398 } | |
4399 if (element.isForeign(backend) && element.isFunction) { | |
4400 visitForeignSend(node); | |
4401 return; | |
4402 } | |
4403 if (element.isErroneous) { | |
4404 // An erroneous element indicates that the funciton could not be resolved | |
4405 // (a warning has been issued). | |
4406 generateThrowNoSuchMethod(node, | |
4407 noSuchMethodTargetSymbolString(element), | |
4408 argumentNodes: node.arguments); | |
4409 return; | |
4410 } | |
4411 invariant(element, !element.isGenerativeConstructor); | |
4412 generateIsDeferredLoadedCheckIfNeeded(node); | |
4413 if (element.isFunction) { | |
4414 var inputs = <HInstruction>[]; | |
4415 // TODO(5347): Try to avoid the need for calling [implementation] before | |
4416 // calling [addStaticSendArgumentsToList]. | |
4417 bool succeeded = addStaticSendArgumentsToList(selector, node.arguments, | |
4418 element.implementation, | |
4419 inputs); | |
4420 if (!succeeded) { | |
4421 generateWrongArgumentCountError(node, element, node.arguments); | |
4422 return; | |
4423 } | |
4424 | |
4425 if (element == compiler.identicalFunction) { | |
4426 pushWithPosition( | |
4427 new HIdentity(inputs[0], inputs[1], null, backend.boolType), node); | |
4428 return; | |
4429 } | |
4430 | |
4431 pushInvokeStatic(node, element, inputs); | |
4432 } else { | |
4433 generateGetter(node, element); | |
4434 List<HInstruction> inputs = <HInstruction>[pop()]; | |
4435 addDynamicSendArgumentsToList(node, inputs); | |
4436 Selector closureSelector = new Selector.callClosureFrom(selector); | |
4437 pushWithPosition( | |
4438 new HInvokeClosure(closureSelector, inputs, backend.dynamicType), | |
4439 node); | |
4440 } | |
4441 } | |
4442 | |
4443 HConstant addConstantString(String string) { | |
4444 ast.DartString dartString = new ast.DartString.literal(string); | |
4445 ConstantValue constant = constantSystem.createString(dartString); | |
4446 return graph.addConstant(constant, compiler); | |
4447 } | |
4448 | |
4449 visitTypePrefixSend(ast.Send node) { | |
4450 compiler.internalError(node, "visitTypePrefixSend should not be called."); | |
4451 } | |
4452 | |
4453 visitTypeLiteralSend(ast.Send node) { | |
4454 DartType type = elements.getTypeLiteralType(node); | |
4455 if (type.isInterfaceType || type.isTypedef || type.isDynamic) { | |
4456 // TODO(karlklose): add type representation | |
4457 if (node.isCall) { | |
4458 // The node itself is not a constant but we register the selector (the | |
4459 // identifier that refers to the class/typedef) as a constant. | |
4460 stack.add(addConstant(node.selector)); | |
4461 } else { | |
4462 stack.add(addConstant(node)); | |
4463 } | |
4464 } else if (type.isTypeVariable) { | |
4465 type = localsHandler.substInContext(type); | |
4466 HInstruction value = analyzeTypeArgument(type); | |
4467 pushInvokeStatic(node, | |
4468 backend.getRuntimeTypeToString(), | |
4469 [value], | |
4470 backend.stringType); | |
4471 pushInvokeStatic(node, | |
4472 backend.getCreateRuntimeType(), | |
4473 [pop()]); | |
4474 } else { | |
4475 internalError('unexpected type kind ${type.kind}', node: node); | |
4476 } | |
4477 if (node.isCall) { | |
4478 // This send is of the form 'e(...)', where e is resolved to a type | |
4479 // reference. We create a regular closure call on the result of the type | |
4480 // reference instead of creating a NoSuchMethodError to avoid pulling it | |
4481 // in if it is not used (e.g., in a try/catch). | |
4482 HInstruction target = pop(); | |
4483 Selector selector = elements.getSelector(node); | |
4484 List<HInstruction> inputs = <HInstruction>[target]; | |
4485 addDynamicSendArgumentsToList(node, inputs); | |
4486 Selector closureSelector = new Selector.callClosureFrom(selector); | |
4487 push(new HInvokeClosure(closureSelector, inputs, backend.dynamicType)); | |
4488 } | |
4489 } | |
4490 | |
4491 visitGetterSend(ast.Send node) { | |
4492 generateIsDeferredLoadedCheckIfNeeded(node); | |
4493 generateGetter(node, elements[node]); | |
4494 } | |
4495 | |
4496 // TODO(antonm): migrate rest of SsaFromAstMixin to internalError. | |
4497 internalError(String reason, {ast.Node node}) { | |
4498 compiler.internalError(node, reason); | |
4499 } | |
4500 | |
4501 void generateError(ast.Node node, String message, Element helper) { | |
4502 HInstruction errorMessage = addConstantString(message); | |
4503 pushInvokeStatic(node, helper, [errorMessage]); | |
4504 } | |
4505 | |
4506 void generateRuntimeError(ast.Node node, String message) { | |
4507 generateError(node, message, backend.getThrowRuntimeError()); | |
4508 } | |
4509 | |
4510 void generateTypeError(ast.Node node, String message) { | |
4511 generateError(node, message, backend.getThrowTypeError()); | |
4512 } | |
4513 | |
4514 void generateAbstractClassInstantiationError(ast.Node node, String message) { | |
4515 generateError(node, | |
4516 message, | |
4517 backend.getThrowAbstractClassInstantiationError()); | |
4518 } | |
4519 | |
4520 void generateThrowNoSuchMethod(ast.Node diagnosticNode, | |
4521 String methodName, | |
4522 {Link<ast.Node> argumentNodes, | |
4523 List<HInstruction> argumentValues, | |
4524 List<String> existingArguments}) { | |
4525 Element helper = backend.getThrowNoSuchMethod(); | |
4526 ConstantValue receiverConstant = | |
4527 constantSystem.createString(new ast.DartString.empty()); | |
4528 HInstruction receiver = graph.addConstant(receiverConstant, compiler); | |
4529 ast.DartString dartString = new ast.DartString.literal(methodName); | |
4530 ConstantValue nameConstant = constantSystem.createString(dartString); | |
4531 HInstruction name = graph.addConstant(nameConstant, compiler); | |
4532 if (argumentValues == null) { | |
4533 argumentValues = <HInstruction>[]; | |
4534 argumentNodes.forEach((argumentNode) { | |
4535 visit(argumentNode); | |
4536 HInstruction value = pop(); | |
4537 argumentValues.add(value); | |
4538 }); | |
4539 } | |
4540 HInstruction arguments = buildLiteralList(argumentValues); | |
4541 add(arguments); | |
4542 HInstruction existingNamesList; | |
4543 if (existingArguments != null) { | |
4544 List<HInstruction> existingNames = <HInstruction>[]; | |
4545 for (String name in existingArguments) { | |
4546 HInstruction nameConstant = | |
4547 graph.addConstantString(new ast.DartString.literal(name), compiler); | |
4548 existingNames.add(nameConstant); | |
4549 } | |
4550 existingNamesList = buildLiteralList(existingNames); | |
4551 add(existingNamesList); | |
4552 } else { | |
4553 existingNamesList = graph.addConstantNull(compiler); | |
4554 } | |
4555 pushInvokeStatic(diagnosticNode, | |
4556 helper, | |
4557 [receiver, name, arguments, existingNamesList]); | |
4558 } | |
4559 | |
4560 /** | |
4561 * Generate code to throw a [NoSuchMethodError] exception for calling a | |
4562 * method with a wrong number of arguments or mismatching named optional | |
4563 * arguments. | |
4564 */ | |
4565 void generateWrongArgumentCountError(ast.Node diagnosticNode, | |
4566 FunctionElement function, | |
4567 Link<ast.Node> argumentNodes) { | |
4568 List<String> existingArguments = <String>[]; | |
4569 FunctionSignature signature = function.functionSignature; | |
4570 signature.forEachParameter((Element parameter) { | |
4571 existingArguments.add(parameter.name); | |
4572 }); | |
4573 generateThrowNoSuchMethod(diagnosticNode, | |
4574 function.name, | |
4575 argumentNodes: argumentNodes, | |
4576 existingArguments: existingArguments); | |
4577 } | |
4578 | |
4579 visitNewExpression(ast.NewExpression node) { | |
4580 Element element = elements[node.send]; | |
4581 final bool isSymbolConstructor = element == compiler.symbolConstructor; | |
4582 if (!Elements.isErroneousElement(element)) { | |
4583 ConstructorElement function = element; | |
4584 element = function.effectiveTarget; | |
4585 } | |
4586 if (Elements.isErroneousElement(element)) { | |
4587 ErroneousElement error = element; | |
4588 if (error.messageKind == MessageKind.CANNOT_FIND_CONSTRUCTOR) { | |
4589 generateThrowNoSuchMethod( | |
4590 node.send, | |
4591 noSuchMethodTargetSymbolString(error, 'constructor'), | |
4592 argumentNodes: node.send.arguments); | |
4593 } else { | |
4594 Message message = error.messageKind.message(error.messageArguments); | |
4595 generateRuntimeError(node.send, message.toString()); | |
4596 } | |
4597 } else if (node.isConst) { | |
4598 stack.add(addConstant(node)); | |
4599 if (isSymbolConstructor) { | |
4600 ConstructedConstantValue symbol = getConstantForNode(node); | |
4601 StringConstantValue stringConstant = symbol.fields.single; | |
4602 String nameString = stringConstant.toDartString().slowToString(); | |
4603 registry.registerConstSymbol(nameString); | |
4604 } | |
4605 } else { | |
4606 handleNewSend(node); | |
4607 } | |
4608 } | |
4609 | |
4610 void pushInvokeDynamic(ast.Node node, | |
4611 Selector selector, | |
4612 List<HInstruction> arguments, | |
4613 {ast.Node location}) { | |
4614 if (location == null) location = node; | |
4615 | |
4616 // We prefer to not inline certain operations on indexables, | |
4617 // because the constant folder will handle them better and turn | |
4618 // them into simpler instructions that allow further | |
4619 // optimizations. | |
4620 bool isOptimizableOperationOnIndexable(Selector selector, Element element) { | |
4621 bool isLength = selector.isGetter | |
4622 && selector.name == "length"; | |
4623 if (isLength || selector.isIndex) { | |
4624 TypeMask type = new TypeMask.nonNullExact( | |
4625 element.enclosingClass.declaration, compiler.world); | |
4626 return type.satisfies(backend.jsIndexableClass, compiler.world); | |
4627 } else if (selector.isIndexSet) { | |
4628 TypeMask type = new TypeMask.nonNullExact( | |
4629 element.enclosingClass.declaration, compiler.world); | |
4630 return type.satisfies(backend.jsMutableIndexableClass, compiler.world); | |
4631 } else { | |
4632 return false; | |
4633 } | |
4634 } | |
4635 | |
4636 bool isOptimizableOperation(Selector selector, Element element) { | |
4637 ClassElement cls = element.enclosingClass; | |
4638 if (isOptimizableOperationOnIndexable(selector, element)) return true; | |
4639 if (!backend.interceptedClasses.contains(cls)) return false; | |
4640 if (selector.isOperator) return true; | |
4641 if (selector.isSetter) return true; | |
4642 if (selector.isIndex) return true; | |
4643 if (selector.isIndexSet) return true; | |
4644 if (element == backend.jsArrayAdd | |
4645 || element == backend.jsArrayRemoveLast | |
4646 || element == backend.jsStringSplit) { | |
4647 return true; | |
4648 } | |
4649 return false; | |
4650 } | |
4651 | |
4652 Element element = compiler.world.locateSingleElement(selector); | |
4653 if (element != null | |
4654 && !element.isField | |
4655 && !(element.isGetter && selector.isCall) | |
4656 && !(element.isFunction && selector.isGetter) | |
4657 && !isOptimizableOperation(selector, element)) { | |
4658 if (tryInlineMethod(element, selector, arguments, node)) { | |
4659 return; | |
4660 } | |
4661 } | |
4662 | |
4663 HInstruction receiver = arguments[0]; | |
4664 List<HInstruction> inputs = <HInstruction>[]; | |
4665 bool isIntercepted = backend.isInterceptedSelector(selector); | |
4666 if (isIntercepted) { | |
4667 inputs.add(invokeInterceptor(receiver)); | |
4668 } | |
4669 inputs.addAll(arguments); | |
4670 TypeMask type = TypeMaskFactory.inferredTypeForSelector(selector, compiler); | |
4671 if (selector.isGetter) { | |
4672 pushWithPosition( | |
4673 new HInvokeDynamicGetter(selector, null, inputs, type), | |
4674 location); | |
4675 } else if (selector.isSetter) { | |
4676 pushWithPosition( | |
4677 new HInvokeDynamicSetter(selector, null, inputs, type), | |
4678 location); | |
4679 } else { | |
4680 pushWithPosition( | |
4681 new HInvokeDynamicMethod(selector, inputs, type, isIntercepted), | |
4682 location); | |
4683 } | |
4684 } | |
4685 | |
4686 void pushInvokeStatic(ast.Node location, | |
4687 Element element, | |
4688 List<HInstruction> arguments, | |
4689 [TypeMask type]) { | |
4690 if (tryInlineMethod(element, null, arguments, location)) { | |
4691 return; | |
4692 } | |
4693 | |
4694 if (type == null) { | |
4695 type = TypeMaskFactory.inferredReturnTypeForElement(element, compiler); | |
4696 } | |
4697 bool targetCanThrow = !compiler.world.getCannotThrow(element); | |
4698 // TODO(5346): Try to avoid the need for calling [declaration] before | |
4699 // creating an [HInvokeStatic]. | |
4700 HInvokeStatic instruction = new HInvokeStatic( | |
4701 element.declaration, arguments, type, targetCanThrow: targetCanThrow); | |
4702 if (!currentInlinedInstantiations.isEmpty) { | |
4703 instruction.instantiatedTypes = new List<DartType>.from( | |
4704 currentInlinedInstantiations); | |
4705 } | |
4706 instruction.sideEffects = compiler.world.getSideEffectsOfElement(element); | |
4707 if (location == null) { | |
4708 push(instruction); | |
4709 } else { | |
4710 pushWithPosition(instruction, location); | |
4711 } | |
4712 } | |
4713 | |
4714 HInstruction buildInvokeSuper(Selector selector, | |
4715 Element element, | |
4716 List<HInstruction> arguments) { | |
4717 HInstruction receiver = localsHandler.readThis(); | |
4718 // TODO(5346): Try to avoid the need for calling [declaration] before | |
4719 // creating an [HStatic]. | |
4720 List<HInstruction> inputs = <HInstruction>[]; | |
4721 if (backend.isInterceptedSelector(selector) && | |
4722 // Fields don't need an interceptor; consider generating HFieldGet/Set | |
4723 // instead. | |
4724 element.kind != ElementKind.FIELD) { | |
4725 inputs.add(invokeInterceptor(receiver)); | |
4726 } | |
4727 inputs.add(receiver); | |
4728 inputs.addAll(arguments); | |
4729 TypeMask type; | |
4730 if (!element.isGetter && selector.isGetter) { | |
4731 type = TypeMaskFactory.inferredTypeForElement(element, compiler); | |
4732 } else { | |
4733 type = TypeMaskFactory.inferredReturnTypeForElement(element, compiler); | |
4734 } | |
4735 HInstruction instruction = new HInvokeSuper( | |
4736 element, | |
4737 currentNonClosureClass, | |
4738 selector, | |
4739 inputs, | |
4740 type, | |
4741 isSetter: selector.isSetter || selector.isIndexSet); | |
4742 instruction.sideEffects = compiler.world.getSideEffectsOfSelector(selector); | |
4743 return instruction; | |
4744 } | |
4745 | |
4746 void handleComplexOperatorSend(ast.SendSet node, | |
4747 HInstruction receiver, | |
4748 Link<ast.Node> arguments) { | |
4749 HInstruction rhs; | |
4750 if (node.isPrefix || node.isPostfix) { | |
4751 rhs = graph.addConstantInt(1, compiler); | |
4752 } else { | |
4753 visit(arguments.head); | |
4754 assert(arguments.tail.isEmpty); | |
4755 rhs = pop(); | |
4756 } | |
4757 visitBinary(receiver, node.assignmentOperator, rhs, | |
4758 elements.getOperatorSelectorInComplexSendSet(node), node); | |
4759 } | |
4760 | |
4761 visitSendSet(ast.SendSet node) { | |
4762 generateIsDeferredLoadedCheckIfNeeded(node); | |
4763 Element element = elements[node]; | |
4764 if (!Elements.isUnresolved(element) && element.impliesType) { | |
4765 ast.Identifier selector = node.selector; | |
4766 generateThrowNoSuchMethod(node, selector.source, | |
4767 argumentNodes: node.arguments); | |
4768 return; | |
4769 } | |
4770 ast.Operator op = node.assignmentOperator; | |
4771 if (node.isSuperCall) { | |
4772 HInstruction result; | |
4773 List<HInstruction> setterInputs = <HInstruction>[]; | |
4774 if (identical(node.assignmentOperator.source, '=')) { | |
4775 addDynamicSendArgumentsToList(node, setterInputs); | |
4776 result = setterInputs.last; | |
4777 } else { | |
4778 Element getter = elements[node.selector]; | |
4779 List<HInstruction> getterInputs = <HInstruction>[]; | |
4780 Link<ast.Node> arguments = node.arguments; | |
4781 if (node.isIndex) { | |
4782 // If node is of the from [:super.foo[0] += 2:], the send has | |
4783 // two arguments: the index and the left hand side. We get | |
4784 // the index and add it as input of the getter and the | |
4785 // setter. | |
4786 visit(arguments.head); | |
4787 arguments = arguments.tail; | |
4788 HInstruction index = pop(); | |
4789 getterInputs.add(index); | |
4790 setterInputs.add(index); | |
4791 } | |
4792 HInstruction getterInstruction; | |
4793 Selector getterSelector = | |
4794 elements.getGetterSelectorInComplexSendSet(node); | |
4795 if (Elements.isUnresolved(getter)) { | |
4796 generateSuperNoSuchMethodSend( | |
4797 node, | |
4798 getterSelector, | |
4799 getterInputs); | |
4800 getterInstruction = pop(); | |
4801 } else { | |
4802 getterInstruction = buildInvokeSuper( | |
4803 getterSelector, getter, getterInputs); | |
4804 add(getterInstruction); | |
4805 } | |
4806 handleComplexOperatorSend(node, getterInstruction, arguments); | |
4807 setterInputs.add(pop()); | |
4808 | |
4809 if (node.isPostfix) { | |
4810 result = getterInstruction; | |
4811 } else { | |
4812 result = setterInputs.last; | |
4813 } | |
4814 } | |
4815 Selector setterSelector = elements.getSelector(node); | |
4816 if (Elements.isUnresolved(element) | |
4817 || !setterSelector.applies(element, compiler.world)) { | |
4818 generateSuperNoSuchMethodSend( | |
4819 node, setterSelector, setterInputs); | |
4820 pop(); | |
4821 } else { | |
4822 add(buildInvokeSuper(setterSelector, element, setterInputs)); | |
4823 } | |
4824 stack.add(result); | |
4825 } else if (node.isIndex) { | |
4826 if ("=" == op.source) { | |
4827 visitDynamicSend(node); | |
4828 } else { | |
4829 visit(node.receiver); | |
4830 HInstruction receiver = pop(); | |
4831 Link<ast.Node> arguments = node.arguments; | |
4832 HInstruction index; | |
4833 if (node.isIndex) { | |
4834 visit(arguments.head); | |
4835 arguments = arguments.tail; | |
4836 index = pop(); | |
4837 } | |
4838 | |
4839 pushInvokeDynamic( | |
4840 node, | |
4841 elements.getGetterSelectorInComplexSendSet(node), | |
4842 [receiver, index]); | |
4843 HInstruction getterInstruction = pop(); | |
4844 | |
4845 handleComplexOperatorSend(node, getterInstruction, arguments); | |
4846 HInstruction value = pop(); | |
4847 | |
4848 pushInvokeDynamic( | |
4849 node, elements.getSelector(node), [receiver, index, value]); | |
4850 pop(); | |
4851 | |
4852 if (node.isPostfix) { | |
4853 stack.add(getterInstruction); | |
4854 } else { | |
4855 stack.add(value); | |
4856 } | |
4857 } | |
4858 } else if ("=" == op.source) { | |
4859 Link<ast.Node> link = node.arguments; | |
4860 assert(!link.isEmpty && link.tail.isEmpty); | |
4861 if (Elements.isInstanceSend(node, elements)) { | |
4862 HInstruction receiver = generateInstanceSendReceiver(node); | |
4863 visit(link.head); | |
4864 generateInstanceSetterWithCompiledReceiver(node, receiver, pop()); | |
4865 } else { | |
4866 visit(link.head); | |
4867 generateNonInstanceSetter(node, element, pop()); | |
4868 } | |
4869 } else if (identical(op.source, "is")) { | |
4870 compiler.internalError(op, "is-operator as SendSet."); | |
4871 } else { | |
4872 assert("++" == op.source || "--" == op.source || | |
4873 node.assignmentOperator.source.endsWith("=")); | |
4874 | |
4875 // [receiver] is only used if the node is an instance send. | |
4876 HInstruction receiver = null; | |
4877 Element getter = elements[node.selector]; | |
4878 | |
4879 if (!Elements.isUnresolved(getter) && getter.impliesType) { | |
4880 ast.Identifier selector = node.selector; | |
4881 generateThrowNoSuchMethod(node, selector.source, | |
4882 argumentNodes: node.arguments); | |
4883 return; | |
4884 } else if (Elements.isInstanceSend(node, elements)) { | |
4885 receiver = generateInstanceSendReceiver(node); | |
4886 generateInstanceGetterWithCompiledReceiver( | |
4887 node, elements.getGetterSelectorInComplexSendSet(node), receiver); | |
4888 } else { | |
4889 generateGetter(node, getter); | |
4890 } | |
4891 HInstruction getterInstruction = pop(); | |
4892 handleComplexOperatorSend(node, getterInstruction, node.arguments); | |
4893 HInstruction value = pop(); | |
4894 assert(value != null); | |
4895 if (Elements.isInstanceSend(node, elements)) { | |
4896 assert(receiver != null); | |
4897 generateInstanceSetterWithCompiledReceiver(node, receiver, value); | |
4898 } else { | |
4899 assert(receiver == null); | |
4900 generateNonInstanceSetter(node, element, value); | |
4901 } | |
4902 if (node.isPostfix) { | |
4903 pop(); | |
4904 stack.add(getterInstruction); | |
4905 } | |
4906 } | |
4907 } | |
4908 | |
4909 void visitLiteralInt(ast.LiteralInt node) { | |
4910 stack.add(graph.addConstantInt(node.value, compiler)); | |
4911 } | |
4912 | |
4913 void visitLiteralDouble(ast.LiteralDouble node) { | |
4914 stack.add(graph.addConstantDouble(node.value, compiler)); | |
4915 } | |
4916 | |
4917 void visitLiteralBool(ast.LiteralBool node) { | |
4918 stack.add(graph.addConstantBool(node.value, compiler)); | |
4919 } | |
4920 | |
4921 void visitLiteralString(ast.LiteralString node) { | |
4922 stack.add(graph.addConstantString(node.dartString, compiler)); | |
4923 } | |
4924 | |
4925 void visitLiteralSymbol(ast.LiteralSymbol node) { | |
4926 stack.add(addConstant(node)); | |
4927 registry.registerConstSymbol(node.slowNameString); | |
4928 } | |
4929 | |
4930 void visitStringJuxtaposition(ast.StringJuxtaposition node) { | |
4931 if (!node.isInterpolation) { | |
4932 // This is a simple string with no interpolations. | |
4933 stack.add(graph.addConstantString(node.dartString, compiler)); | |
4934 return; | |
4935 } | |
4936 StringBuilderVisitor stringBuilder = new StringBuilderVisitor(this, node); | |
4937 stringBuilder.visit(node); | |
4938 stack.add(stringBuilder.result); | |
4939 } | |
4940 | |
4941 void visitLiteralNull(ast.LiteralNull node) { | |
4942 stack.add(graph.addConstantNull(compiler)); | |
4943 } | |
4944 | |
4945 visitNodeList(ast.NodeList node) { | |
4946 for (Link<ast.Node> link = node.nodes; !link.isEmpty; link = link.tail) { | |
4947 if (isAborted()) { | |
4948 compiler.reportWarning(link.head, | |
4949 MessageKind.GENERIC, {'text': 'dead code'}); | |
4950 } else { | |
4951 visit(link.head); | |
4952 } | |
4953 } | |
4954 } | |
4955 | |
4956 void visitParenthesizedExpression(ast.ParenthesizedExpression node) { | |
4957 visit(node.expression); | |
4958 } | |
4959 | |
4960 visitOperator(ast.Operator node) { | |
4961 // Operators are intercepted in their surrounding Send nodes. | |
4962 compiler.internalError(node, | |
4963 'SsaBuilder.visitOperator should not be called.'); | |
4964 } | |
4965 | |
4966 visitCascade(ast.Cascade node) { | |
4967 visit(node.expression); | |
4968 // Remove the result and reveal the duplicated receiver on the stack. | |
4969 pop(); | |
4970 } | |
4971 | |
4972 visitCascadeReceiver(ast.CascadeReceiver node) { | |
4973 visit(node.expression); | |
4974 dup(); | |
4975 } | |
4976 | |
4977 void handleInTryStatement() { | |
4978 if (!inTryStatement) return; | |
4979 HBasicBlock block = close(new HExitTry()); | |
4980 HBasicBlock newBlock = graph.addNewBlock(); | |
4981 block.addSuccessor(newBlock); | |
4982 open(newBlock); | |
4983 } | |
4984 | |
4985 visitRethrow(ast.Rethrow node) { | |
4986 HInstruction exception = rethrowableException; | |
4987 if (exception == null) { | |
4988 exception = graph.addConstantNull(compiler); | |
4989 compiler.internalError(node, | |
4990 'rethrowableException should not be null.'); | |
4991 } | |
4992 handleInTryStatement(); | |
4993 closeAndGotoExit(new HThrow(exception, isRethrow: true)); | |
4994 } | |
4995 | |
4996 visitRedirectingFactoryBody(ast.RedirectingFactoryBody node) { | |
4997 ConstructorElement targetConstructor = | |
4998 elements.getRedirectingTargetConstructor(node).implementation; | |
4999 ConstructorElement redirectingConstructor = sourceElement.implementation; | |
5000 List<HInstruction> inputs = <HInstruction>[]; | |
5001 FunctionSignature targetSignature = targetConstructor.functionSignature; | |
5002 FunctionSignature redirectingSignature = | |
5003 redirectingConstructor.functionSignature; | |
5004 redirectingSignature.forEachRequiredParameter((ParameterElement element) { | |
5005 inputs.add(localsHandler.readLocal(element)); | |
5006 }); | |
5007 List<Element> targetOptionals = | |
5008 targetSignature.orderedOptionalParameters; | |
5009 List<Element> redirectingOptionals = | |
5010 redirectingSignature.orderedOptionalParameters; | |
5011 int i = 0; | |
5012 for (; i < redirectingOptionals.length; i++) { | |
5013 ParameterElement parameter = redirectingOptionals[i]; | |
5014 inputs.add(localsHandler.readLocal(parameter)); | |
5015 } | |
5016 for (; i < targetOptionals.length; i++) { | |
5017 inputs.add(handleConstantForOptionalParameter(targetOptionals[i])); | |
5018 } | |
5019 ClassElement targetClass = targetConstructor.enclosingClass; | |
5020 if (backend.classNeedsRti(targetClass)) { | |
5021 ClassElement cls = redirectingConstructor.enclosingClass; | |
5022 InterfaceType targetType = | |
5023 redirectingConstructor.computeEffectiveTargetType(cls.thisType); | |
5024 targetType = localsHandler.substInContext(targetType); | |
5025 targetType.typeArguments.forEach((DartType argument) { | |
5026 inputs.add(analyzeTypeArgument(argument)); | |
5027 }); | |
5028 } | |
5029 pushInvokeStatic(node, targetConstructor, inputs); | |
5030 HInstruction value = pop(); | |
5031 emitReturn(value, node); | |
5032 } | |
5033 | |
5034 visitReturn(ast.Return node) { | |
5035 if (identical(node.beginToken.stringValue, 'native')) { | |
5036 native.handleSsaNative(this, node.expression); | |
5037 return; | |
5038 } | |
5039 HInstruction value; | |
5040 if (node.expression == null) { | |
5041 value = graph.addConstantNull(compiler); | |
5042 } else { | |
5043 visit(node.expression); | |
5044 value = pop(); | |
5045 value = potentiallyCheckType(value, returnType); | |
5046 } | |
5047 | |
5048 handleInTryStatement(); | |
5049 emitReturn(value, node); | |
5050 } | |
5051 | |
5052 visitThrow(ast.Throw node) { | |
5053 visitThrowExpression(node.expression); | |
5054 if (isReachable) { | |
5055 handleInTryStatement(); | |
5056 push(new HThrowExpression(pop())); | |
5057 isReachable = false; | |
5058 } | |
5059 } | |
5060 | |
5061 visitYield(ast.Yield node) { | |
5062 // Dummy implementation. | |
5063 visit(node.expression); | |
5064 pop(); | |
5065 } | |
5066 | |
5067 visitAwait(ast.Await node) { | |
5068 // Dummy implementation. | |
5069 visit(node.expression); | |
5070 } | |
5071 | |
5072 visitTypeAnnotation(ast.TypeAnnotation node) { | |
5073 compiler.internalError(node, | |
5074 'Visiting type annotation in SSA builder.'); | |
5075 } | |
5076 | |
5077 visitVariableDefinitions(ast.VariableDefinitions node) { | |
5078 assert(isReachable); | |
5079 for (Link<ast.Node> link = node.definitions.nodes; | |
5080 !link.isEmpty; | |
5081 link = link.tail) { | |
5082 ast.Node definition = link.head; | |
5083 if (definition is ast.Identifier) { | |
5084 HInstruction initialValue = graph.addConstantNull(compiler); | |
5085 LocalElement local = elements[definition]; | |
5086 localsHandler.updateLocal(local, initialValue); | |
5087 } else { | |
5088 assert(definition is ast.SendSet); | |
5089 visitSendSet(definition); | |
5090 pop(); // Discard value. | |
5091 } | |
5092 } | |
5093 } | |
5094 | |
5095 HInstruction setRtiIfNeeded(HInstruction object, ast.Node node) { | |
5096 InterfaceType type = localsHandler.substInContext(elements.getType(node)); | |
5097 if (!backend.classNeedsRti(type.element) || type.treatAsRaw) { | |
5098 return object; | |
5099 } | |
5100 List<HInstruction> arguments = <HInstruction>[]; | |
5101 for (DartType argument in type.typeArguments) { | |
5102 arguments.add(analyzeTypeArgument(argument)); | |
5103 } | |
5104 // TODO(15489): Register at codegen. | |
5105 registry.registerInstantiatedType(type); | |
5106 return callSetRuntimeTypeInfo(type.element, arguments, object); | |
5107 } | |
5108 | |
5109 visitLiteralList(ast.LiteralList node) { | |
5110 HInstruction instruction; | |
5111 | |
5112 if (node.isConst) { | |
5113 instruction = addConstant(node); | |
5114 } else { | |
5115 List<HInstruction> inputs = <HInstruction>[]; | |
5116 for (Link<ast.Node> link = node.elements.nodes; | |
5117 !link.isEmpty; | |
5118 link = link.tail) { | |
5119 visit(link.head); | |
5120 inputs.add(pop()); | |
5121 } | |
5122 instruction = buildLiteralList(inputs); | |
5123 add(instruction); | |
5124 instruction = setRtiIfNeeded(instruction, node); | |
5125 } | |
5126 | |
5127 TypeMask type = | |
5128 TypeMaskFactory.inferredForNode(sourceElement, node, compiler); | |
5129 if (!type.containsAll(compiler.world)) instruction.instructionType = type; | |
5130 stack.add(instruction); | |
5131 } | |
5132 | |
5133 visitConditional(ast.Conditional node) { | |
5134 SsaBranchBuilder brancher = new SsaBranchBuilder(this, node); | |
5135 brancher.handleConditional(() => visit(node.condition), | |
5136 () => visit(node.thenExpression), | |
5137 () => visit(node.elseExpression)); | |
5138 } | |
5139 | |
5140 visitStringInterpolation(ast.StringInterpolation node) { | |
5141 StringBuilderVisitor stringBuilder = new StringBuilderVisitor(this, node); | |
5142 stringBuilder.visit(node); | |
5143 stack.add(stringBuilder.result); | |
5144 } | |
5145 | |
5146 visitStringInterpolationPart(ast.StringInterpolationPart node) { | |
5147 // The parts are iterated in visitStringInterpolation. | |
5148 compiler.internalError(node, | |
5149 'SsaBuilder.visitStringInterpolation should not be called.'); | |
5150 } | |
5151 | |
5152 visitEmptyStatement(ast.EmptyStatement node) { | |
5153 // Do nothing, empty statement. | |
5154 } | |
5155 | |
5156 visitModifiers(ast.Modifiers node) { | |
5157 compiler.unimplemented(node, 'SsaFromAstMixin.visitModifiers.'); | |
5158 } | |
5159 | |
5160 visitBreakStatement(ast.BreakStatement node) { | |
5161 assert(!isAborted()); | |
5162 handleInTryStatement(); | |
5163 JumpTarget target = elements.getTargetOf(node); | |
5164 assert(target != null); | |
5165 JumpHandler handler = jumpTargets[target]; | |
5166 assert(handler != null); | |
5167 if (node.target == null) { | |
5168 handler.generateBreak(); | |
5169 } else { | |
5170 LabelDefinition label = elements.getTargetLabel(node); | |
5171 handler.generateBreak(label); | |
5172 } | |
5173 } | |
5174 | |
5175 visitContinueStatement(ast.ContinueStatement node) { | |
5176 handleInTryStatement(); | |
5177 JumpTarget target = elements.getTargetOf(node); | |
5178 assert(target != null); | |
5179 JumpHandler handler = jumpTargets[target]; | |
5180 assert(handler != null); | |
5181 if (node.target == null) { | |
5182 handler.generateContinue(); | |
5183 } else { | |
5184 LabelDefinition label = elements.getTargetLabel(node); | |
5185 assert(label != null); | |
5186 handler.generateContinue(label); | |
5187 } | |
5188 } | |
5189 | |
5190 /** | |
5191 * Creates a [JumpHandler] for a statement. The node must be a jump | |
5192 * target. If there are no breaks or continues targeting the statement, | |
5193 * a special "null handler" is returned. | |
5194 * | |
5195 * [isLoopJump] is [:true:] when the jump handler is for a loop. This is used | |
5196 * to distinguish the synthetized loop created for a switch statement with | |
5197 * continue statements from simple switch statements. | |
5198 */ | |
5199 JumpHandler createJumpHandler(ast.Statement node, {bool isLoopJump}) { | |
5200 JumpTarget element = elements.getTargetDefinition(node); | |
5201 if (element == null || !identical(element.statement, node)) { | |
5202 // No breaks or continues to this node. | |
5203 return new NullJumpHandler(compiler); | |
5204 } | |
5205 if (isLoopJump && node is ast.SwitchStatement) { | |
5206 // Create a special jump handler for loops created for switch statements | |
5207 // with continue statements. | |
5208 return new SwitchCaseJumpHandler(this, element, node); | |
5209 } | |
5210 return new JumpHandler(this, element); | |
5211 } | |
5212 | |
5213 visitForIn(ast.ForIn node) { | |
5214 // Generate a structure equivalent to: | |
5215 // Iterator<E> $iter = <iterable>.iterator; | |
5216 // while ($iter.moveNext()) { | |
5217 // E <declaredIdentifier> = $iter.current; | |
5218 // <body> | |
5219 // } | |
5220 | |
5221 // The iterator is shared between initializer, condition and body. | |
5222 HInstruction iterator; | |
5223 void buildInitializer() { | |
5224 Selector selector = elements.getIteratorSelector(node); | |
5225 visit(node.expression); | |
5226 HInstruction receiver = pop(); | |
5227 pushInvokeDynamic(node, selector, [receiver]); | |
5228 iterator = pop(); | |
5229 } | |
5230 HInstruction buildCondition() { | |
5231 Selector selector = elements.getMoveNextSelector(node); | |
5232 pushInvokeDynamic(node, selector, [iterator]); | |
5233 return popBoolified(); | |
5234 } | |
5235 void buildBody() { | |
5236 Selector call = elements.getCurrentSelector(node); | |
5237 pushInvokeDynamic(node, call, [iterator]); | |
5238 | |
5239 ast.Node identifier = node.declaredIdentifier; | |
5240 Element variable = elements.getForInVariable(node); | |
5241 Selector selector = elements.getSelector(identifier); | |
5242 | |
5243 HInstruction value = pop(); | |
5244 if (identifier.asSend() != null | |
5245 && Elements.isInstanceSend(identifier, elements)) { | |
5246 HInstruction receiver = generateInstanceSendReceiver(identifier); | |
5247 assert(receiver != null); | |
5248 generateInstanceSetterWithCompiledReceiver( | |
5249 null, | |
5250 receiver, | |
5251 value, | |
5252 selector: selector, | |
5253 location: identifier); | |
5254 } else { | |
5255 generateNonInstanceSetter(null, variable, value, location: identifier); | |
5256 } | |
5257 pop(); // Pop the value pushed by the setter call. | |
5258 | |
5259 visit(node.body); | |
5260 } | |
5261 handleLoop(node, buildInitializer, buildCondition, () {}, buildBody); | |
5262 } | |
5263 | |
5264 visitLabel(ast.Label node) { | |
5265 compiler.internalError(node, 'SsaFromAstMixin.visitLabel.'); | |
5266 } | |
5267 | |
5268 visitLabeledStatement(ast.LabeledStatement node) { | |
5269 ast.Statement body = node.statement; | |
5270 if (body is ast.Loop | |
5271 || body is ast.SwitchStatement | |
5272 || Elements.isUnusedLabel(node, elements)) { | |
5273 // Loops and switches handle their own labels. | |
5274 visit(body); | |
5275 return; | |
5276 } | |
5277 JumpTarget targetElement = elements.getTargetDefinition(body); | |
5278 LocalsHandler beforeLocals = new LocalsHandler.from(localsHandler); | |
5279 assert(targetElement.isBreakTarget); | |
5280 JumpHandler handler = new JumpHandler(this, targetElement); | |
5281 // Introduce a new basic block. | |
5282 HBasicBlock entryBlock = openNewBlock(); | |
5283 visit(body); | |
5284 SubGraph bodyGraph = new SubGraph(entryBlock, lastOpenedBlock); | |
5285 | |
5286 HBasicBlock joinBlock = graph.addNewBlock(); | |
5287 List<LocalsHandler> breakHandlers = <LocalsHandler>[]; | |
5288 handler.forEachBreak((HBreak breakInstruction, LocalsHandler locals) { | |
5289 breakInstruction.block.addSuccessor(joinBlock); | |
5290 breakHandlers.add(locals); | |
5291 }); | |
5292 bool hasBreak = breakHandlers.length > 0; | |
5293 if (!isAborted()) { | |
5294 goto(current, joinBlock); | |
5295 breakHandlers.add(localsHandler); | |
5296 } | |
5297 open(joinBlock); | |
5298 localsHandler = beforeLocals.mergeMultiple(breakHandlers, joinBlock); | |
5299 | |
5300 if (hasBreak) { | |
5301 // There was at least one reachable break, so the label is needed. | |
5302 entryBlock.setBlockFlow( | |
5303 new HLabeledBlockInformation(new HSubGraphBlockInformation(bodyGraph), | |
5304 handler.labels()), | |
5305 joinBlock); | |
5306 } | |
5307 handler.close(); | |
5308 } | |
5309 | |
5310 visitLiteralMap(ast.LiteralMap node) { | |
5311 if (node.isConst) { | |
5312 stack.add(addConstant(node)); | |
5313 return; | |
5314 } | |
5315 List<HInstruction> listInputs = <HInstruction>[]; | |
5316 for (Link<ast.Node> link = node.entries.nodes; | |
5317 !link.isEmpty; | |
5318 link = link.tail) { | |
5319 visit(link.head); | |
5320 listInputs.add(pop()); | |
5321 listInputs.add(pop()); | |
5322 } | |
5323 | |
5324 ConstructorElement constructor; | |
5325 List<HInstruction> inputs = <HInstruction>[]; | |
5326 | |
5327 if (listInputs.isEmpty) { | |
5328 constructor = backend.mapLiteralConstructorEmpty; | |
5329 } else { | |
5330 constructor = backend.mapLiteralConstructor; | |
5331 HLiteralList keyValuePairs = buildLiteralList(listInputs); | |
5332 add(keyValuePairs); | |
5333 inputs.add(keyValuePairs); | |
5334 } | |
5335 | |
5336 assert(constructor.isFactoryConstructor); | |
5337 | |
5338 ConstructorElement functionElement = constructor; | |
5339 constructor = functionElement.effectiveTarget; | |
5340 | |
5341 InterfaceType type = elements.getType(node); | |
5342 InterfaceType expectedType = | |
5343 functionElement.computeEffectiveTargetType(type); | |
5344 expectedType = localsHandler.substInContext(expectedType); | |
5345 | |
5346 if (constructor.isFactoryConstructor) { | |
5347 registry.registerFactoryWithTypeArguments(); | |
5348 } | |
5349 | |
5350 ClassElement cls = constructor.enclosingClass; | |
5351 | |
5352 if (backend.classNeedsRti(cls)) { | |
5353 List<DartType> typeVariable = cls.typeVariables; | |
5354 expectedType.typeArguments.forEach((DartType argument) { | |
5355 inputs.add(analyzeTypeArgument(argument)); | |
5356 }); | |
5357 } | |
5358 | |
5359 // The instruction type will always be a subtype of the mapLiteralClass, but | |
5360 // type inference might discover a more specific type, or find nothing (in | |
5361 // dart2js unit tests). | |
5362 TypeMask mapType = | |
5363 new TypeMask.nonNullSubtype(backend.mapLiteralClass, compiler.world); | |
5364 TypeMask returnTypeMask = TypeMaskFactory.inferredReturnTypeForElement( | |
5365 constructor, compiler); | |
5366 TypeMask instructionType = | |
5367 mapType.intersection(returnTypeMask, compiler.world); | |
5368 | |
5369 addInlinedInstantiation(expectedType); | |
5370 pushInvokeStatic(node, constructor, inputs, instructionType); | |
5371 removeInlinedInstantiation(expectedType); | |
5372 } | |
5373 | |
5374 visitLiteralMapEntry(ast.LiteralMapEntry node) { | |
5375 visit(node.value); | |
5376 visit(node.key); | |
5377 } | |
5378 | |
5379 visitNamedArgument(ast.NamedArgument node) { | |
5380 visit(node.expression); | |
5381 } | |
5382 | |
5383 Map<ast.CaseMatch, ConstantValue> buildSwitchCaseConstants( | |
5384 ast.SwitchStatement node) { | |
5385 | |
5386 Map<ast.CaseMatch, ConstantValue> constants = | |
5387 new Map<ast.CaseMatch, ConstantValue>(); | |
5388 for (ast.SwitchCase switchCase in node.cases) { | |
5389 for (ast.Node labelOrCase in switchCase.labelsAndCases) { | |
5390 if (labelOrCase is ast.CaseMatch) { | |
5391 ast.CaseMatch match = labelOrCase; | |
5392 ConstantValue constant = getConstantForNode(match.expression); | |
5393 constants[labelOrCase] = constant; | |
5394 } | |
5395 } | |
5396 } | |
5397 return constants; | |
5398 } | |
5399 | |
5400 visitSwitchStatement(ast.SwitchStatement node) { | |
5401 Map<ast.CaseMatch, ConstantValue> constants = | |
5402 buildSwitchCaseConstants(node); | |
5403 | |
5404 // The switch case indices must match those computed in | |
5405 // [SwitchCaseJumpHandler]. | |
5406 bool hasContinue = false; | |
5407 Map<ast.SwitchCase, int> caseIndex = new Map<ast.SwitchCase, int>(); | |
5408 int switchIndex = 1; | |
5409 bool hasDefault = false; | |
5410 for (ast.SwitchCase switchCase in node.cases) { | |
5411 for (ast.Node labelOrCase in switchCase.labelsAndCases) { | |
5412 ast.Node label = labelOrCase.asLabel(); | |
5413 if (label != null) { | |
5414 LabelDefinition labelElement = elements.getLabelDefinition(label); | |
5415 if (labelElement != null && labelElement.isContinueTarget) { | |
5416 hasContinue = true; | |
5417 } | |
5418 } | |
5419 } | |
5420 if (switchCase.isDefaultCase) { | |
5421 hasDefault = true; | |
5422 } | |
5423 caseIndex[switchCase] = switchIndex; | |
5424 switchIndex++; | |
5425 } | |
5426 if (!hasContinue) { | |
5427 // If the switch statement has no switch cases targeted by continue | |
5428 // statements we encode the switch statement directly. | |
5429 buildSimpleSwitchStatement(node, constants); | |
5430 } else { | |
5431 buildComplexSwitchStatement(node, constants, caseIndex, hasDefault); | |
5432 } | |
5433 } | |
5434 | |
5435 /** | |
5436 * Builds a simple switch statement which does not handle uses of continue | |
5437 * statements to labeled switch cases. | |
5438 */ | |
5439 void buildSimpleSwitchStatement(ast.SwitchStatement node, | |
5440 Map<ast.CaseMatch, ConstantValue> constants) { | |
5441 JumpHandler jumpHandler = createJumpHandler(node, isLoopJump: false); | |
5442 HInstruction buildExpression() { | |
5443 visit(node.expression); | |
5444 return pop(); | |
5445 } | |
5446 Iterable<ConstantValue> getConstants(ast.SwitchCase switchCase) { | |
5447 List<ConstantValue> constantList = <ConstantValue>[]; | |
5448 for (ast.Node labelOrCase in switchCase.labelsAndCases) { | |
5449 if (labelOrCase is ast.CaseMatch) { | |
5450 constantList.add(constants[labelOrCase]); | |
5451 } | |
5452 } | |
5453 return constantList; | |
5454 } | |
5455 bool isDefaultCase(ast.SwitchCase switchCase) { | |
5456 return switchCase.isDefaultCase; | |
5457 } | |
5458 void buildSwitchCase(ast.SwitchCase node) { | |
5459 visit(node.statements); | |
5460 } | |
5461 handleSwitch(node, | |
5462 jumpHandler, | |
5463 buildExpression, | |
5464 node.cases, | |
5465 getConstants, | |
5466 isDefaultCase, | |
5467 buildSwitchCase); | |
5468 jumpHandler.close(); | |
5469 } | |
5470 | |
5471 /** | |
5472 * Builds a switch statement that can handle arbitrary uses of continue | |
5473 * statements to labeled switch cases. | |
5474 */ | |
5475 void buildComplexSwitchStatement(ast.SwitchStatement node, | |
5476 Map<ast.CaseMatch, ConstantValue> constants, | |
5477 Map<ast.SwitchCase, int> caseIndex, | |
5478 bool hasDefault) { | |
5479 // If the switch statement has switch cases targeted by continue | |
5480 // statements we create the following encoding: | |
5481 // | |
5482 // switch (e) { | |
5483 // l_1: case e0: s_1; break; | |
5484 // l_2: case e1: s_2; continue l_i; | |
5485 // ... | |
5486 // l_n: default: s_n; continue l_j; | |
5487 // } | |
5488 // | |
5489 // is encoded as | |
5490 // | |
5491 // var target; | |
5492 // switch (e) { | |
5493 // case e1: target = 1; break; | |
5494 // case e2: target = 2; break; | |
5495 // ... | |
5496 // default: target = n; break; | |
5497 // } | |
5498 // l: while (true) { | |
5499 // switch (target) { | |
5500 // case 1: s_1; break l; | |
5501 // case 2: s_2; target = i; continue l; | |
5502 // ... | |
5503 // case n: s_n; target = j; continue l; | |
5504 // } | |
5505 // } | |
5506 | |
5507 JumpTarget switchTarget = elements.getTargetDefinition(node); | |
5508 HInstruction initialValue = graph.addConstantNull(compiler); | |
5509 localsHandler.updateLocal(switchTarget, initialValue); | |
5510 | |
5511 JumpHandler jumpHandler = createJumpHandler(node, isLoopJump: false); | |
5512 var switchCases = node.cases; | |
5513 if (!hasDefault) { | |
5514 // Use [:null:] as the marker for a synthetic default clause. | |
5515 // The synthetic default is added because otherwise, there would be no | |
5516 // good place to give a default value to the local. | |
5517 switchCases = node.cases.nodes.toList()..add(null); | |
5518 } | |
5519 HInstruction buildExpression() { | |
5520 visit(node.expression); | |
5521 return pop(); | |
5522 } | |
5523 Iterable<ConstantValue> getConstants(ast.SwitchCase switchCase) { | |
5524 List<ConstantValue> constantList = <ConstantValue>[]; | |
5525 if (switchCase != null) { | |
5526 for (ast.Node labelOrCase in switchCase.labelsAndCases) { | |
5527 if (labelOrCase is ast.CaseMatch) { | |
5528 constantList.add(constants[labelOrCase]); | |
5529 } | |
5530 } | |
5531 } | |
5532 return constantList; | |
5533 } | |
5534 bool isDefaultCase(ast.SwitchCase switchCase) { | |
5535 return switchCase == null || switchCase.isDefaultCase; | |
5536 } | |
5537 void buildSwitchCase(ast.SwitchCase switchCase) { | |
5538 if (switchCase != null) { | |
5539 // Generate 'target = i; break;' for switch case i. | |
5540 int index = caseIndex[switchCase]; | |
5541 HInstruction value = graph.addConstantInt(index, compiler); | |
5542 localsHandler.updateLocal(switchTarget, value); | |
5543 } else { | |
5544 // Generate synthetic default case 'target = null; break;'. | |
5545 HInstruction value = graph.addConstantNull(compiler); | |
5546 localsHandler.updateLocal(switchTarget, value); | |
5547 } | |
5548 jumpTargets[switchTarget].generateBreak(); | |
5549 } | |
5550 handleSwitch(node, | |
5551 jumpHandler, | |
5552 buildExpression, | |
5553 switchCases, | |
5554 getConstants, | |
5555 isDefaultCase, | |
5556 buildSwitchCase); | |
5557 jumpHandler.close(); | |
5558 | |
5559 HInstruction buildCondition() => | |
5560 graph.addConstantBool(true, compiler); | |
5561 | |
5562 void buildSwitch() { | |
5563 HInstruction buildExpression() { | |
5564 return localsHandler.readLocal(switchTarget); | |
5565 } | |
5566 Iterable<ConstantValue> getConstants(ast.SwitchCase switchCase) { | |
5567 return <ConstantValue>[constantSystem.createInt(caseIndex[switchCase])]; | |
5568 } | |
5569 void buildSwitchCase(ast.SwitchCase switchCase) { | |
5570 visit(switchCase.statements); | |
5571 if (!isAborted()) { | |
5572 // Ensure that we break the loop if the case falls through. (This | |
5573 // is only possible for the last case.) | |
5574 jumpTargets[switchTarget].generateBreak(); | |
5575 } | |
5576 } | |
5577 // Pass a [NullJumpHandler] because the target for the contained break | |
5578 // is not the generated switch statement but instead the loop generated | |
5579 // in the call to [handleLoop] below. | |
5580 handleSwitch(node, | |
5581 new NullJumpHandler(compiler), | |
5582 buildExpression, node.cases, getConstants, | |
5583 (_) => false, // No case is default. | |
5584 buildSwitchCase); | |
5585 } | |
5586 | |
5587 void buildLoop() { | |
5588 handleLoop(node, | |
5589 () {}, | |
5590 buildCondition, | |
5591 () {}, | |
5592 buildSwitch); | |
5593 } | |
5594 | |
5595 if (hasDefault) { | |
5596 buildLoop(); | |
5597 } else { | |
5598 // If the switch statement has no default case, surround the loop with | |
5599 // a test of the target. | |
5600 void buildCondition() { | |
5601 js.Template code = js.js.parseForeignJS('#'); | |
5602 push(createForeign(code, | |
5603 backend.boolType, | |
5604 [localsHandler.readLocal(switchTarget)])); | |
5605 } | |
5606 handleIf(node, buildCondition, buildLoop, () => {}); | |
5607 } | |
5608 } | |
5609 | |
5610 /** | |
5611 * Creates a switch statement. | |
5612 * | |
5613 * [jumpHandler] is the [JumpHandler] for the created switch statement. | |
5614 * [buildExpression] creates the switch expression. | |
5615 * [switchCases] must be either an [Iterable] of [ast.SwitchCase] nodes or | |
5616 * a [Link] or a [ast.NodeList] of [ast.SwitchCase] nodes. | |
5617 * [getConstants] returns the set of constants for a switch case. | |
5618 * [isDefaultCase] returns [:true:] if the provided switch case should be | |
5619 * considered default for the created switch statement. | |
5620 * [buildSwitchCase] creates the statements for the switch case. | |
5621 */ | |
5622 void handleSwitch( | |
5623 ast.Node errorNode, | |
5624 JumpHandler jumpHandler, | |
5625 HInstruction buildExpression(), | |
5626 var switchCases, | |
5627 Iterable<ConstantValue> getConstants(ast.SwitchCase switchCase), | |
5628 bool isDefaultCase(ast.SwitchCase switchCase), | |
5629 void buildSwitchCase(ast.SwitchCase switchCase)) { | |
5630 | |
5631 Map<ast.CaseMatch, ConstantValue> constants = | |
5632 new Map<ast.CaseMatch, ConstantValue>(); | |
5633 | |
5634 HBasicBlock expressionStart = openNewBlock(); | |
5635 HInstruction expression = buildExpression(); | |
5636 if (switchCases.isEmpty) { | |
5637 return; | |
5638 } | |
5639 | |
5640 HSwitch switchInstruction = new HSwitch(<HInstruction>[expression]); | |
5641 HBasicBlock expressionEnd = close(switchInstruction); | |
5642 LocalsHandler savedLocals = localsHandler; | |
5643 | |
5644 List<HStatementInformation> statements = <HStatementInformation>[]; | |
5645 bool hasDefault = false; | |
5646 Element getFallThroughErrorElement = backend.getFallThroughError(); | |
5647 HasNextIterator<ast.Node> caseIterator = | |
5648 new HasNextIterator<ast.Node>(switchCases.iterator); | |
5649 while (caseIterator.hasNext) { | |
5650 ast.SwitchCase switchCase = caseIterator.next(); | |
5651 HBasicBlock block = graph.addNewBlock(); | |
5652 for (ConstantValue constant in getConstants(switchCase)) { | |
5653 HConstant hConstant = graph.addConstant(constant, compiler); | |
5654 switchInstruction.inputs.add(hConstant); | |
5655 hConstant.usedBy.add(switchInstruction); | |
5656 expressionEnd.addSuccessor(block); | |
5657 } | |
5658 | |
5659 if (isDefaultCase(switchCase)) { | |
5660 // An HSwitch has n inputs and n+1 successors, the last being the | |
5661 // default case. | |
5662 expressionEnd.addSuccessor(block); | |
5663 hasDefault = true; | |
5664 } | |
5665 open(block); | |
5666 localsHandler = new LocalsHandler.from(savedLocals); | |
5667 buildSwitchCase(switchCase); | |
5668 if (!isAborted()) { | |
5669 if (caseIterator.hasNext) { | |
5670 pushInvokeStatic(switchCase, getFallThroughErrorElement, []); | |
5671 HInstruction error = pop(); | |
5672 closeAndGotoExit(new HThrow(error)); | |
5673 } else if (!isDefaultCase(switchCase)) { | |
5674 // If there is no default, we will add one later to avoid | |
5675 // the critical edge. So we generate a break statement to make | |
5676 // sure the last case does not fall through to the default case. | |
5677 jumpHandler.generateBreak(); | |
5678 } | |
5679 } | |
5680 statements.add( | |
5681 new HSubGraphBlockInformation(new SubGraph(block, lastOpenedBlock))); | |
5682 } | |
5683 | |
5684 // Add a join-block if necessary. | |
5685 // We create [joinBlock] early, and then go through the cases that might | |
5686 // want to jump to it. In each case, if we add [joinBlock] as a successor | |
5687 // of another block, we also add an element to [caseHandlers] that is used | |
5688 // to create the phis in [joinBlock]. | |
5689 // If we never jump to the join block, [caseHandlers] will stay empty, and | |
5690 // the join block is never added to the graph. | |
5691 HBasicBlock joinBlock = new HBasicBlock(); | |
5692 List<LocalsHandler> caseHandlers = <LocalsHandler>[]; | |
5693 jumpHandler.forEachBreak((HBreak instruction, LocalsHandler locals) { | |
5694 instruction.block.addSuccessor(joinBlock); | |
5695 caseHandlers.add(locals); | |
5696 }); | |
5697 jumpHandler.forEachContinue((HContinue instruction, LocalsHandler locals) { | |
5698 assert(invariant(errorNode, false, | |
5699 message: 'Continue cannot target a switch.')); | |
5700 }); | |
5701 if (!isAborted()) { | |
5702 current.close(new HGoto()); | |
5703 lastOpenedBlock.addSuccessor(joinBlock); | |
5704 caseHandlers.add(localsHandler); | |
5705 } | |
5706 if (!hasDefault) { | |
5707 // Always create a default case, to avoid a critical edge in the | |
5708 // graph. | |
5709 HBasicBlock defaultCase = addNewBlock(); | |
5710 expressionEnd.addSuccessor(defaultCase); | |
5711 open(defaultCase); | |
5712 close(new HGoto()); | |
5713 defaultCase.addSuccessor(joinBlock); | |
5714 caseHandlers.add(savedLocals); | |
5715 statements.add(new HSubGraphBlockInformation(new SubGraph( | |
5716 defaultCase, defaultCase))); | |
5717 } | |
5718 assert(caseHandlers.length == joinBlock.predecessors.length); | |
5719 if (caseHandlers.length != 0) { | |
5720 graph.addBlock(joinBlock); | |
5721 open(joinBlock); | |
5722 if (caseHandlers.length == 1) { | |
5723 localsHandler = caseHandlers[0]; | |
5724 } else { | |
5725 localsHandler = savedLocals.mergeMultiple(caseHandlers, joinBlock); | |
5726 } | |
5727 } else { | |
5728 // The joinblock is not used. | |
5729 joinBlock = null; | |
5730 } | |
5731 | |
5732 HSubExpressionBlockInformation expressionInfo = | |
5733 new HSubExpressionBlockInformation(new SubExpression(expressionStart, | |
5734 expressionEnd)); | |
5735 expressionStart.setBlockFlow( | |
5736 new HSwitchBlockInformation(expressionInfo, | |
5737 statements, | |
5738 jumpHandler.target, | |
5739 jumpHandler.labels()), | |
5740 joinBlock); | |
5741 | |
5742 jumpHandler.close(); | |
5743 } | |
5744 | |
5745 visitSwitchCase(ast.SwitchCase node) { | |
5746 compiler.internalError(node, 'SsaFromAstMixin.visitSwitchCase.'); | |
5747 } | |
5748 | |
5749 visitCaseMatch(ast.CaseMatch node) { | |
5750 compiler.internalError(node, 'SsaFromAstMixin.visitCaseMatch.'); | |
5751 } | |
5752 | |
5753 visitTryStatement(ast.TryStatement node) { | |
5754 // Save the current locals. The catch block and the finally block | |
5755 // must not reuse the existing locals handler. None of the variables | |
5756 // that have been defined in the body-block will be used, but for | |
5757 // loops we will add (unnecessary) phis that will reference the body | |
5758 // variables. This makes it look as if the variables were used | |
5759 // in a non-dominated block. | |
5760 LocalsHandler savedLocals = new LocalsHandler.from(localsHandler); | |
5761 HBasicBlock enterBlock = openNewBlock(); | |
5762 HTry tryInstruction = new HTry(); | |
5763 close(tryInstruction); | |
5764 bool oldInTryStatement = inTryStatement; | |
5765 inTryStatement = true; | |
5766 | |
5767 HBasicBlock startTryBlock; | |
5768 HBasicBlock endTryBlock; | |
5769 HBasicBlock startCatchBlock; | |
5770 HBasicBlock endCatchBlock; | |
5771 HBasicBlock startFinallyBlock; | |
5772 HBasicBlock endFinallyBlock; | |
5773 | |
5774 startTryBlock = graph.addNewBlock(); | |
5775 open(startTryBlock); | |
5776 visit(node.tryBlock); | |
5777 // We use a [HExitTry] instead of a [HGoto] for the try block | |
5778 // because it will have multiple successors: the join block, and | |
5779 // the catch or finally block. | |
5780 if (!isAborted()) endTryBlock = close(new HExitTry()); | |
5781 SubGraph bodyGraph = new SubGraph(startTryBlock, lastOpenedBlock); | |
5782 SubGraph catchGraph = null; | |
5783 HLocalValue exception = null; | |
5784 | |
5785 if (!node.catchBlocks.isEmpty) { | |
5786 localsHandler = new LocalsHandler.from(savedLocals); | |
5787 startCatchBlock = graph.addNewBlock(); | |
5788 open(startCatchBlock); | |
5789 // Note that the name of this local is irrelevant. | |
5790 SyntheticLocal local = | |
5791 new SyntheticLocal('exception', localsHandler.executableContext); | |
5792 exception = new HLocalValue(local, backend.nonNullType); | |
5793 add(exception); | |
5794 HInstruction oldRethrowableException = rethrowableException; | |
5795 rethrowableException = exception; | |
5796 | |
5797 pushInvokeStatic(node, backend.getExceptionUnwrapper(), [exception]); | |
5798 HInvokeStatic unwrappedException = pop(); | |
5799 tryInstruction.exception = exception; | |
5800 Link<ast.Node> link = node.catchBlocks.nodes; | |
5801 | |
5802 void pushCondition(ast.CatchBlock catchBlock) { | |
5803 if (catchBlock.onKeyword != null) { | |
5804 DartType type = elements.getType(catchBlock.type); | |
5805 if (type == null) { | |
5806 compiler.internalError(catchBlock.type, 'On with no type.'); | |
5807 } | |
5808 HInstruction condition = | |
5809 buildIsNode(catchBlock.type, type, unwrappedException); | |
5810 push(condition); | |
5811 } else { | |
5812 ast.VariableDefinitions declaration = catchBlock.formals.nodes.head; | |
5813 HInstruction condition = null; | |
5814 if (declaration.type == null) { | |
5815 condition = graph.addConstantBool(true, compiler); | |
5816 stack.add(condition); | |
5817 } else { | |
5818 // TODO(aprelev@gmail.com): Once old catch syntax is removed | |
5819 // "if" condition above and this "else" branch should be deleted as | |
5820 // type of declared variable won't matter for the catch | |
5821 // condition. | |
5822 DartType type = elements.getType(declaration.type); | |
5823 if (type == null) { | |
5824 compiler.internalError(catchBlock, 'Catch with unresolved type.'); | |
5825 } | |
5826 condition = buildIsNode(declaration.type, type, unwrappedException); | |
5827 push(condition); | |
5828 } | |
5829 } | |
5830 } | |
5831 | |
5832 void visitThen() { | |
5833 ast.CatchBlock catchBlock = link.head; | |
5834 link = link.tail; | |
5835 if (catchBlock.exception != null) { | |
5836 LocalVariableElement exceptionVariable = | |
5837 elements[catchBlock.exception]; | |
5838 localsHandler.updateLocal(exceptionVariable, | |
5839 unwrappedException); | |
5840 } | |
5841 ast.Node trace = catchBlock.trace; | |
5842 if (trace != null) { | |
5843 pushInvokeStatic(trace, backend.getTraceFromException(), [exception]); | |
5844 HInstruction traceInstruction = pop(); | |
5845 LocalVariableElement traceVariable = elements[trace]; | |
5846 localsHandler.updateLocal(traceVariable, traceInstruction); | |
5847 } | |
5848 visit(catchBlock); | |
5849 } | |
5850 | |
5851 void visitElse() { | |
5852 if (link.isEmpty) { | |
5853 closeAndGotoExit(new HThrow(exception, isRethrow: true)); | |
5854 } else { | |
5855 ast.CatchBlock newBlock = link.head; | |
5856 handleIf(node, | |
5857 () { pushCondition(newBlock); }, | |
5858 visitThen, visitElse); | |
5859 } | |
5860 } | |
5861 | |
5862 ast.CatchBlock firstBlock = link.head; | |
5863 handleIf(node, () { pushCondition(firstBlock); }, visitThen, visitElse); | |
5864 if (!isAborted()) endCatchBlock = close(new HGoto()); | |
5865 | |
5866 rethrowableException = oldRethrowableException; | |
5867 tryInstruction.catchBlock = startCatchBlock; | |
5868 catchGraph = new SubGraph(startCatchBlock, lastOpenedBlock); | |
5869 } | |
5870 | |
5871 SubGraph finallyGraph = null; | |
5872 if (node.finallyBlock != null) { | |
5873 localsHandler = new LocalsHandler.from(savedLocals); | |
5874 startFinallyBlock = graph.addNewBlock(); | |
5875 open(startFinallyBlock); | |
5876 visit(node.finallyBlock); | |
5877 if (!isAborted()) endFinallyBlock = close(new HGoto()); | |
5878 tryInstruction.finallyBlock = startFinallyBlock; | |
5879 finallyGraph = new SubGraph(startFinallyBlock, lastOpenedBlock); | |
5880 } | |
5881 | |
5882 HBasicBlock exitBlock = graph.addNewBlock(); | |
5883 | |
5884 addOptionalSuccessor(b1, b2) { if (b2 != null) b1.addSuccessor(b2); } | |
5885 addExitTrySuccessor(successor) { | |
5886 if (successor == null) return; | |
5887 // Iterate over all blocks created inside this try/catch, and | |
5888 // attach successor information to blocks that end with | |
5889 // [HExitTry]. | |
5890 for (int i = startTryBlock.id; i < successor.id; i++) { | |
5891 HBasicBlock block = graph.blocks[i]; | |
5892 var last = block.last; | |
5893 if (last is HExitTry) { | |
5894 block.addSuccessor(successor); | |
5895 } | |
5896 } | |
5897 } | |
5898 | |
5899 // Setup all successors. The entry block that contains the [HTry] | |
5900 // has 1) the body, 2) the catch, 3) the finally, and 4) the exit | |
5901 // blocks as successors. | |
5902 enterBlock.addSuccessor(startTryBlock); | |
5903 addOptionalSuccessor(enterBlock, startCatchBlock); | |
5904 addOptionalSuccessor(enterBlock, startFinallyBlock); | |
5905 enterBlock.addSuccessor(exitBlock); | |
5906 | |
5907 // The body has either the catch or the finally block as successor. | |
5908 if (endTryBlock != null) { | |
5909 assert(startCatchBlock != null || startFinallyBlock != null); | |
5910 endTryBlock.addSuccessor( | |
5911 startCatchBlock != null ? startCatchBlock : startFinallyBlock); | |
5912 endTryBlock.addSuccessor(exitBlock); | |
5913 } | |
5914 | |
5915 // The catch block has either the finally or the exit block as | |
5916 // successor. | |
5917 if (endCatchBlock != null) { | |
5918 endCatchBlock.addSuccessor( | |
5919 startFinallyBlock != null ? startFinallyBlock : exitBlock); | |
5920 } | |
5921 | |
5922 // The finally block has the exit block as successor. | |
5923 if (endFinallyBlock != null) { | |
5924 endFinallyBlock.addSuccessor(exitBlock); | |
5925 } | |
5926 | |
5927 // If a block inside try/catch aborts (eg with a return statement), | |
5928 // we explicitely mark this block a predecessor of the catch | |
5929 // block and the finally block. | |
5930 addExitTrySuccessor(startCatchBlock); | |
5931 addExitTrySuccessor(startFinallyBlock); | |
5932 | |
5933 // Use the locals handler not altered by the catch and finally | |
5934 // blocks. | |
5935 localsHandler = savedLocals; | |
5936 open(exitBlock); | |
5937 enterBlock.setBlockFlow( | |
5938 new HTryBlockInformation( | |
5939 wrapStatementGraph(bodyGraph), | |
5940 exception, | |
5941 wrapStatementGraph(catchGraph), | |
5942 wrapStatementGraph(finallyGraph)), | |
5943 exitBlock); | |
5944 inTryStatement = oldInTryStatement; | |
5945 } | |
5946 | |
5947 visitCatchBlock(ast.CatchBlock node) { | |
5948 visit(node.block); | |
5949 } | |
5950 | |
5951 visitTypedef(ast.Typedef node) { | |
5952 compiler.unimplemented(node, 'SsaFromAstMixin.visitTypedef.'); | |
5953 } | |
5954 | |
5955 visitTypeVariable(ast.TypeVariable node) { | |
5956 compiler.internalError(node, 'SsaFromAstMixin.visitTypeVariable.'); | |
5957 } | |
5958 | |
5959 /** | |
5960 * This method is invoked before inlining the body of [function] into this | |
5961 * [SsaBuilder]. | |
5962 */ | |
5963 void enterInlinedMethod(FunctionElement function, | |
5964 ast.Node _, | |
5965 List<HInstruction> compiledArguments) { | |
5966 TypesInferrer inferrer = compiler.typesTask.typesInferrer; | |
5967 AstInliningState state = new AstInliningState( | |
5968 function, returnLocal, returnType, elements, stack, localsHandler, | |
5969 inTryStatement, | |
5970 allInlinedFunctionsCalledOnce && inferrer.isCalledOnce(function)); | |
5971 inliningStack.add(state); | |
5972 | |
5973 // Setting up the state of the (AST) builder is performed even when the | |
5974 // inlined function is in IR, because the irInliner uses the [returnElement] | |
5975 // of the AST builder. | |
5976 setupStateForInlining(function, compiledArguments); | |
5977 } | |
5978 | |
5979 void leaveInlinedMethod() { | |
5980 HInstruction result = localsHandler.readLocal(returnLocal); | |
5981 AstInliningState state = inliningStack.removeLast(); | |
5982 restoreState(state); | |
5983 stack.add(result); | |
5984 } | |
5985 | |
5986 void doInline(FunctionElement function) { | |
5987 visitInlinedFunction(function); | |
5988 } | |
5989 | |
5990 void emitReturn(HInstruction value, ast.Node node) { | |
5991 if (inliningStack.isEmpty) { | |
5992 closeAndGotoExit(attachPosition(new HReturn(value), node)); | |
5993 } else { | |
5994 localsHandler.updateLocal(returnLocal, value); | |
5995 } | |
5996 } | |
5997 } | |
5998 | |
5999 /** | |
6000 * Visitor that handles generation of string literals (LiteralString, | |
6001 * StringInterpolation), and otherwise delegates to the given visitor for | |
6002 * non-literal subexpressions. | |
6003 */ | |
6004 class StringBuilderVisitor extends ast.Visitor { | |
6005 final SsaBuilder builder; | |
6006 final ast.Node diagnosticNode; | |
6007 | |
6008 /** | |
6009 * The string value generated so far. | |
6010 */ | |
6011 HInstruction result = null; | |
6012 | |
6013 StringBuilderVisitor(this.builder, this.diagnosticNode); | |
6014 | |
6015 Compiler get compiler => builder.compiler; | |
6016 | |
6017 void visit(ast.Node node) { | |
6018 node.accept(this); | |
6019 } | |
6020 | |
6021 visitNode(ast.Node node) { | |
6022 builder.compiler.internalError(node, 'Unexpected node.'); | |
6023 } | |
6024 | |
6025 void visitExpression(ast.Node node) { | |
6026 node.accept(builder); | |
6027 HInstruction expression = builder.pop(); | |
6028 | |
6029 // We want to use HStringify when: | |
6030 // 1. The value is known to be a primitive type, because it might get | |
6031 // constant-folded and codegen has some tricks with JavaScript | |
6032 // conversions. | |
6033 // 2. The value can be primitive, because the library stringifier has | |
6034 // fast-path code for most primitives. | |
6035 if (expression.canBePrimitive(compiler)) { | |
6036 append(stringify(node, expression)); | |
6037 return; | |
6038 } | |
6039 | |
6040 // If the `toString` method is guaranteed to return a string we can call it | |
6041 // directly. | |
6042 Selector selector = | |
6043 new TypedSelector(expression.instructionType, | |
6044 new Selector.call('toString', null, 0), compiler.world); | |
6045 TypeMask type = TypeMaskFactory.inferredTypeForSelector(selector, compiler); | |
6046 if (type.containsOnlyString(compiler.world)) { | |
6047 builder.pushInvokeDynamic(node, selector, <HInstruction>[expression]); | |
6048 append(builder.pop()); | |
6049 return; | |
6050 } | |
6051 | |
6052 append(stringify(node, expression)); | |
6053 } | |
6054 | |
6055 void visitStringInterpolation(ast.StringInterpolation node) { | |
6056 node.visitChildren(this); | |
6057 } | |
6058 | |
6059 void visitStringInterpolationPart(ast.StringInterpolationPart node) { | |
6060 visit(node.expression); | |
6061 visit(node.string); | |
6062 } | |
6063 | |
6064 void visitStringJuxtaposition(ast.StringJuxtaposition node) { | |
6065 node.visitChildren(this); | |
6066 } | |
6067 | |
6068 void visitNodeList(ast.NodeList node) { | |
6069 node.visitChildren(this); | |
6070 } | |
6071 | |
6072 void append(HInstruction expression) { | |
6073 result = (result == null) ? expression : concat(result, expression); | |
6074 } | |
6075 | |
6076 HInstruction concat(HInstruction left, HInstruction right) { | |
6077 HInstruction instruction = new HStringConcat( | |
6078 left, right, diagnosticNode, builder.backend.stringType); | |
6079 builder.add(instruction); | |
6080 return instruction; | |
6081 } | |
6082 | |
6083 HInstruction stringify(ast.Node node, HInstruction expression) { | |
6084 HInstruction instruction = | |
6085 new HStringify(expression, node, builder.backend.stringType); | |
6086 builder.add(instruction); | |
6087 return instruction; | |
6088 } | |
6089 } | |
6090 | |
6091 /** | |
6092 * This class visits the method that is a candidate for inlining and | |
6093 * finds whether it is too difficult to inline. | |
6094 */ | |
6095 // TODO(karlklose): refactor to make it possible to distinguish between | |
6096 // implementation restrictions (for example, we *can't* inline multiple returns) | |
6097 // and heuristics (we *shouldn't* inline large functions). | |
6098 class InlineWeeder extends ast.Visitor { | |
6099 // Invariant: *INSIDE_LOOP* > *OUTSIDE_LOOP* | |
6100 static const INLINING_NODES_OUTSIDE_LOOP = 18; | |
6101 static const INLINING_NODES_OUTSIDE_LOOP_ARG_FACTOR = 3; | |
6102 static const INLINING_NODES_INSIDE_LOOP = 42; | |
6103 static const INLINING_NODES_INSIDE_LOOP_ARG_FACTOR = 4; | |
6104 | |
6105 bool seenReturn = false; | |
6106 bool tooDifficult = false; | |
6107 int nodeCount = 0; | |
6108 final int maxInliningNodes; | |
6109 final bool useMaxInliningNodes; | |
6110 final bool allowLoops; | |
6111 | |
6112 InlineWeeder(this.maxInliningNodes, | |
6113 this.useMaxInliningNodes, | |
6114 this.allowLoops); | |
6115 | |
6116 static bool canBeInlined(ast.FunctionExpression functionExpression, | |
6117 int maxInliningNodes, | |
6118 bool useMaxInliningNodes, | |
6119 {bool allowLoops: false}) { | |
6120 InlineWeeder weeder = | |
6121 new InlineWeeder(maxInliningNodes, useMaxInliningNodes, allowLoops); | |
6122 weeder.visit(functionExpression.initializers); | |
6123 weeder.visit(functionExpression.body); | |
6124 return !weeder.tooDifficult; | |
6125 } | |
6126 | |
6127 bool registerNode() { | |
6128 if (!useMaxInliningNodes) return true; | |
6129 if (nodeCount++ > maxInliningNodes) { | |
6130 tooDifficult = true; | |
6131 return false; | |
6132 } else { | |
6133 return true; | |
6134 } | |
6135 } | |
6136 | |
6137 void visit(ast.Node node) { | |
6138 if (node != null) node.accept(this); | |
6139 } | |
6140 | |
6141 void visitNode(ast.Node node) { | |
6142 if (!registerNode()) return; | |
6143 if (seenReturn) { | |
6144 tooDifficult = true; | |
6145 } else { | |
6146 node.visitChildren(this); | |
6147 } | |
6148 } | |
6149 | |
6150 void visitFunctionExpression(ast.Node node) { | |
6151 if (!registerNode()) return; | |
6152 tooDifficult = true; | |
6153 } | |
6154 | |
6155 void visitFunctionDeclaration(ast.Node node) { | |
6156 if (!registerNode()) return; | |
6157 tooDifficult = true; | |
6158 } | |
6159 | |
6160 void visitSend(ast.Send node) { | |
6161 if (!registerNode()) return; | |
6162 node.visitChildren(this); | |
6163 } | |
6164 | |
6165 visitLoop(ast.Node node) { | |
6166 // It's actually not difficult to inline a method with a loop, but | |
6167 // our measurements show that it's currently better to not inline a | |
6168 // method that contains a loop. | |
6169 if (!allowLoops) tooDifficult = true; | |
6170 } | |
6171 | |
6172 void visitRedirectingFactoryBody(ast.RedirectingFactoryBody node) { | |
6173 if (!registerNode()) return; | |
6174 tooDifficult = true; | |
6175 } | |
6176 | |
6177 void visitRethrow(ast.Rethrow node) { | |
6178 if (!registerNode()) return; | |
6179 tooDifficult = true; | |
6180 } | |
6181 | |
6182 void visitReturn(ast.Return node) { | |
6183 if (!registerNode()) return; | |
6184 if (seenReturn | |
6185 || identical(node.beginToken.stringValue, 'native')) { | |
6186 tooDifficult = true; | |
6187 return; | |
6188 } | |
6189 node.visitChildren(this); | |
6190 seenReturn = true; | |
6191 } | |
6192 | |
6193 void visitTryStatement(ast.Node node) { | |
6194 if (!registerNode()) return; | |
6195 tooDifficult = true; | |
6196 } | |
6197 | |
6198 void visitThrow(ast.Throw node) { | |
6199 if (!registerNode()) return; | |
6200 // For now, we don't want to handle throw after a return even if | |
6201 // it is in an "if". | |
6202 if (seenReturn) { | |
6203 tooDifficult = true; | |
6204 } else { | |
6205 node.visitChildren(this); | |
6206 } | |
6207 } | |
6208 } | |
6209 | |
6210 abstract class InliningState { | |
6211 /** | |
6212 * Invariant: [function] must be an implementation element. | |
6213 */ | |
6214 final FunctionElement function; | |
6215 | |
6216 InliningState(this.function) { | |
6217 assert(function.isImplementation); | |
6218 } | |
6219 } | |
6220 | |
6221 class AstInliningState extends InliningState { | |
6222 final Local oldReturnLocal; | |
6223 final DartType oldReturnType; | |
6224 final TreeElements oldElements; | |
6225 final List<HInstruction> oldStack; | |
6226 final LocalsHandler oldLocalsHandler; | |
6227 final bool inTryStatement; | |
6228 final bool allFunctionsCalledOnce; | |
6229 | |
6230 AstInliningState(FunctionElement function, | |
6231 this.oldReturnLocal, | |
6232 this.oldReturnType, | |
6233 this.oldElements, | |
6234 this.oldStack, | |
6235 this.oldLocalsHandler, | |
6236 this.inTryStatement, | |
6237 this.allFunctionsCalledOnce) | |
6238 : super(function); | |
6239 } | |
6240 | |
6241 class SsaBranch { | |
6242 final SsaBranchBuilder branchBuilder; | |
6243 final HBasicBlock block; | |
6244 LocalsHandler startLocals; | |
6245 LocalsHandler exitLocals; | |
6246 SubGraph graph; | |
6247 | |
6248 SsaBranch(this.branchBuilder) : block = new HBasicBlock(); | |
6249 } | |
6250 | |
6251 class SsaBranchBuilder { | |
6252 final SsaBuilder builder; | |
6253 final ast.Node diagnosticNode; | |
6254 | |
6255 SsaBranchBuilder(this.builder, [this.diagnosticNode]); | |
6256 | |
6257 Compiler get compiler => builder.compiler; | |
6258 | |
6259 void checkNotAborted() { | |
6260 if (builder.isAborted()) { | |
6261 compiler.unimplemented(diagnosticNode, "aborted control flow"); | |
6262 } | |
6263 } | |
6264 | |
6265 void buildCondition(void visitCondition(), | |
6266 SsaBranch conditionBranch, | |
6267 SsaBranch thenBranch, | |
6268 SsaBranch elseBranch) { | |
6269 startBranch(conditionBranch); | |
6270 visitCondition(); | |
6271 checkNotAborted(); | |
6272 assert(identical(builder.current, builder.lastOpenedBlock)); | |
6273 HInstruction conditionValue = builder.popBoolified(); | |
6274 HIf branch = new HIf(conditionValue); | |
6275 HBasicBlock conditionExitBlock = builder.current; | |
6276 builder.close(branch); | |
6277 conditionBranch.exitLocals = builder.localsHandler; | |
6278 conditionExitBlock.addSuccessor(thenBranch.block); | |
6279 conditionExitBlock.addSuccessor(elseBranch.block); | |
6280 bool conditionBranchLocalsCanBeReused = | |
6281 mergeLocals(conditionBranch, thenBranch, mayReuseFromLocals: true); | |
6282 mergeLocals(conditionBranch, elseBranch, | |
6283 mayReuseFromLocals: conditionBranchLocalsCanBeReused); | |
6284 | |
6285 conditionBranch.graph = | |
6286 new SubExpression(conditionBranch.block, conditionExitBlock); | |
6287 } | |
6288 | |
6289 /** | |
6290 * Returns true if the locals of the [fromBranch] may be reused. A [:true:] | |
6291 * return value implies that [mayReuseFromLocals] was set to [:true:]. | |
6292 */ | |
6293 bool mergeLocals(SsaBranch fromBranch, SsaBranch toBranch, | |
6294 {bool mayReuseFromLocals}) { | |
6295 LocalsHandler fromLocals = fromBranch.exitLocals; | |
6296 if (toBranch.startLocals == null) { | |
6297 if (mayReuseFromLocals) { | |
6298 toBranch.startLocals = fromLocals; | |
6299 return false; | |
6300 } else { | |
6301 toBranch.startLocals = new LocalsHandler.from(fromLocals); | |
6302 return true; | |
6303 } | |
6304 } else { | |
6305 toBranch.startLocals.mergeWith(fromLocals, toBranch.block); | |
6306 return true; | |
6307 } | |
6308 } | |
6309 | |
6310 void startBranch(SsaBranch branch) { | |
6311 builder.graph.addBlock(branch.block); | |
6312 builder.localsHandler = branch.startLocals; | |
6313 builder.open(branch.block); | |
6314 } | |
6315 | |
6316 HInstruction buildBranch(SsaBranch branch, | |
6317 void visitBranch(), | |
6318 SsaBranch joinBranch, | |
6319 bool isExpression) { | |
6320 startBranch(branch); | |
6321 visitBranch(); | |
6322 branch.graph = new SubGraph(branch.block, builder.lastOpenedBlock); | |
6323 branch.exitLocals = builder.localsHandler; | |
6324 if (!builder.isAborted()) { | |
6325 builder.goto(builder.current, joinBranch.block); | |
6326 mergeLocals(branch, joinBranch, mayReuseFromLocals: true); | |
6327 } | |
6328 if (isExpression) { | |
6329 checkNotAborted(); | |
6330 return builder.pop(); | |
6331 } | |
6332 return null; | |
6333 } | |
6334 | |
6335 handleIf(void visitCondition(), void visitThen(), void visitElse()) { | |
6336 if (visitElse == null) { | |
6337 // Make sure to have an else part to avoid a critical edge. A | |
6338 // critical edge is an edge that connects a block with multiple | |
6339 // successors to a block with multiple predecessors. We avoid | |
6340 // such edges because they prevent inserting copies during code | |
6341 // generation of phi instructions. | |
6342 visitElse = () {}; | |
6343 } | |
6344 | |
6345 _handleDiamondBranch(visitCondition, visitThen, visitElse, false); | |
6346 } | |
6347 | |
6348 handleConditional(void visitCondition(), void visitThen(), void visitElse()) { | |
6349 assert(visitElse != null); | |
6350 _handleDiamondBranch(visitCondition, visitThen, visitElse, true); | |
6351 } | |
6352 | |
6353 void handleLogicalAndOr(void left(), void right(), {bool isAnd}) { | |
6354 // x && y is transformed into: | |
6355 // t0 = boolify(x); | |
6356 // if (t0) { | |
6357 // t1 = boolify(y); | |
6358 // } | |
6359 // result = phi(t1, false); | |
6360 // | |
6361 // x || y is transformed into: | |
6362 // t0 = boolify(x); | |
6363 // if (not(t0)) { | |
6364 // t1 = boolify(y); | |
6365 // } | |
6366 // result = phi(t1, true); | |
6367 HInstruction boolifiedLeft; | |
6368 HInstruction boolifiedRight; | |
6369 | |
6370 void visitCondition() { | |
6371 left(); | |
6372 boolifiedLeft = builder.popBoolified(); | |
6373 builder.stack.add(boolifiedLeft); | |
6374 if (!isAnd) { | |
6375 builder.push(new HNot(builder.pop(), builder.backend.boolType)); | |
6376 } | |
6377 } | |
6378 | |
6379 void visitThen() { | |
6380 right(); | |
6381 boolifiedRight = builder.popBoolified(); | |
6382 } | |
6383 | |
6384 handleIf(visitCondition, visitThen, null); | |
6385 HConstant notIsAnd = | |
6386 builder.graph.addConstantBool(!isAnd, builder.compiler); | |
6387 JavaScriptBackend backend = builder.backend; | |
6388 HPhi result = new HPhi.manyInputs(null, | |
6389 <HInstruction>[boolifiedRight, notIsAnd], | |
6390 backend.dynamicType); | |
6391 builder.current.addPhi(result); | |
6392 builder.stack.add(result); | |
6393 } | |
6394 | |
6395 void handleLogicalAndOrWithLeftNode(ast.Node left, | |
6396 void visitRight(), | |
6397 {bool isAnd}) { | |
6398 // This method is similar to [handleLogicalAndOr] but optimizes the case | |
6399 // where left is a logical "and" or logical "or". | |
6400 // | |
6401 // For example (x && y) && z is transformed into x && (y && z): | |
6402 // t0 = boolify(x); | |
6403 // if (t0) { | |
6404 // t1 = boolify(y); | |
6405 // if (t1) { | |
6406 // t2 = boolify(z); | |
6407 // } | |
6408 // t3 = phi(t2, false); | |
6409 // } | |
6410 // result = phi(t3, false); | |
6411 | |
6412 ast.Send send = left.asSend(); | |
6413 if (send != null && | |
6414 (isAnd ? send.isLogicalAnd : send.isLogicalOr)) { | |
6415 ast.Node newLeft = send.receiver; | |
6416 Link<ast.Node> link = send.argumentsNode.nodes; | |
6417 assert(link.tail.isEmpty); | |
6418 ast.Node middle = link.head; | |
6419 handleLogicalAndOrWithLeftNode( | |
6420 newLeft, | |
6421 () => handleLogicalAndOrWithLeftNode(middle, visitRight, | |
6422 isAnd: isAnd), | |
6423 isAnd: isAnd); | |
6424 } else { | |
6425 handleLogicalAndOr(() => builder.visit(left), visitRight, isAnd: isAnd); | |
6426 } | |
6427 } | |
6428 | |
6429 void _handleDiamondBranch(void visitCondition(), | |
6430 void visitThen(), | |
6431 void visitElse(), | |
6432 bool isExpression) { | |
6433 SsaBranch conditionBranch = new SsaBranch(this); | |
6434 SsaBranch thenBranch = new SsaBranch(this); | |
6435 SsaBranch elseBranch = new SsaBranch(this); | |
6436 SsaBranch joinBranch = new SsaBranch(this); | |
6437 | |
6438 conditionBranch.startLocals = builder.localsHandler; | |
6439 builder.goto(builder.current, conditionBranch.block); | |
6440 | |
6441 buildCondition(visitCondition, conditionBranch, thenBranch, elseBranch); | |
6442 HInstruction thenValue = | |
6443 buildBranch(thenBranch, visitThen, joinBranch, isExpression); | |
6444 HInstruction elseValue = | |
6445 buildBranch(elseBranch, visitElse, joinBranch, isExpression); | |
6446 | |
6447 if (isExpression) { | |
6448 assert(thenValue != null && elseValue != null); | |
6449 JavaScriptBackend backend = builder.backend; | |
6450 HPhi phi = new HPhi.manyInputs( | |
6451 null, <HInstruction>[thenValue, elseValue], backend.dynamicType); | |
6452 joinBranch.block.addPhi(phi); | |
6453 builder.stack.add(phi); | |
6454 } | |
6455 | |
6456 HBasicBlock thenBlock = thenBranch.block; | |
6457 HBasicBlock elseBlock = elseBranch.block; | |
6458 HBasicBlock joinBlock; | |
6459 // If at least one branch did not abort, open the joinBranch. | |
6460 if (!joinBranch.block.predecessors.isEmpty) { | |
6461 startBranch(joinBranch); | |
6462 joinBlock = joinBranch.block; | |
6463 } | |
6464 | |
6465 HIfBlockInformation info = | |
6466 new HIfBlockInformation( | |
6467 new HSubExpressionBlockInformation(conditionBranch.graph), | |
6468 new HSubGraphBlockInformation(thenBranch.graph), | |
6469 new HSubGraphBlockInformation(elseBranch.graph)); | |
6470 | |
6471 HBasicBlock conditionStartBlock = conditionBranch.block; | |
6472 conditionStartBlock.setBlockFlow(info, joinBlock); | |
6473 SubGraph conditionGraph = conditionBranch.graph; | |
6474 HIf branch = conditionGraph.end.last; | |
6475 assert(branch is HIf); | |
6476 branch.blockInformation = conditionStartBlock.blockFlow; | |
6477 } | |
6478 } | |
6479 | |
6480 class TypeBuilder implements DartTypeVisitor<dynamic, SsaBuilder> { | |
6481 final ClassWorld classWorld; | |
6482 | |
6483 TypeBuilder(this.classWorld); | |
6484 | |
6485 void visitType(DartType type, _) { | |
6486 throw 'Internal error $type'; | |
6487 } | |
6488 | |
6489 void visitVoidType(VoidType type, SsaBuilder builder) { | |
6490 ClassElement cls = builder.backend.findHelper('VoidRuntimeType'); | |
6491 builder.push(new HVoidType(type, new TypeMask.exact(cls, classWorld))); | |
6492 } | |
6493 | |
6494 void visitTypeVariableType(TypeVariableType type, | |
6495 SsaBuilder builder) { | |
6496 ClassElement cls = builder.backend.findHelper('RuntimeType'); | |
6497 TypeMask instructionType = new TypeMask.subclass(cls, classWorld); | |
6498 if (!builder.sourceElement.enclosingElement.isClosure && | |
6499 builder.sourceElement.isInstanceMember) { | |
6500 HInstruction receiver = builder.localsHandler.readThis(); | |
6501 builder.push(new HReadTypeVariable(type, receiver, instructionType)); | |
6502 } else { | |
6503 builder.push( | |
6504 new HReadTypeVariable.noReceiver( | |
6505 type, builder.addTypeVariableReference(type), instructionType)); | |
6506 } | |
6507 } | |
6508 | |
6509 void visitFunctionType(FunctionType type, SsaBuilder builder) { | |
6510 type.returnType.accept(this, builder); | |
6511 HInstruction returnType = builder.pop(); | |
6512 List<HInstruction> inputs = <HInstruction>[returnType]; | |
6513 | |
6514 for (DartType parameter in type.parameterTypes) { | |
6515 parameter.accept(this, builder); | |
6516 inputs.add(builder.pop()); | |
6517 } | |
6518 | |
6519 for (DartType parameter in type.optionalParameterTypes) { | |
6520 parameter.accept(this, builder); | |
6521 inputs.add(builder.pop()); | |
6522 } | |
6523 | |
6524 List<DartType> namedParameterTypes = type.namedParameterTypes; | |
6525 List<String> names = type.namedParameters; | |
6526 for (int index = 0; index < names.length; index++) { | |
6527 ast.DartString dartString = new ast.DartString.literal(names[index]); | |
6528 inputs.add( | |
6529 builder.graph.addConstantString(dartString, builder.compiler)); | |
6530 namedParameterTypes[index].accept(this, builder); | |
6531 inputs.add(builder.pop()); | |
6532 } | |
6533 | |
6534 ClassElement cls = builder.backend.findHelper('RuntimeFunctionType'); | |
6535 builder.push(new HFunctionType(inputs, type, | |
6536 new TypeMask.exact(cls, classWorld))); | |
6537 } | |
6538 | |
6539 void visitMalformedType(MalformedType type, SsaBuilder builder) { | |
6540 visitDynamicType(const DynamicType(), builder); | |
6541 } | |
6542 | |
6543 void visitStatementType(StatementType type, SsaBuilder builder) { | |
6544 throw 'not implemented visitStatementType($type)'; | |
6545 } | |
6546 | |
6547 void visitGenericType(GenericType type, SsaBuilder builder) { | |
6548 throw 'not implemented visitGenericType($type)'; | |
6549 } | |
6550 | |
6551 void visitInterfaceType(InterfaceType type, SsaBuilder builder) { | |
6552 List<HInstruction> inputs = <HInstruction>[]; | |
6553 for (DartType typeArgument in type.typeArguments) { | |
6554 typeArgument.accept(this, builder); | |
6555 inputs.add(builder.pop()); | |
6556 } | |
6557 ClassElement cls; | |
6558 if (type.typeArguments.isEmpty) { | |
6559 cls = builder.backend.findHelper('RuntimeTypePlain'); | |
6560 } else { | |
6561 cls = builder.backend.findHelper('RuntimeTypeGeneric'); | |
6562 } | |
6563 builder.push(new HInterfaceType(inputs, type, | |
6564 new TypeMask.exact(cls, classWorld))); | |
6565 } | |
6566 | |
6567 void visitTypedefType(TypedefType type, SsaBuilder builder) { | |
6568 DartType unaliased = type.unalias(builder.compiler); | |
6569 if (unaliased is TypedefType) throw 'unable to unalias $type'; | |
6570 unaliased.accept(this, builder); | |
6571 } | |
6572 | |
6573 void visitDynamicType(DynamicType type, SsaBuilder builder) { | |
6574 JavaScriptBackend backend = builder.compiler.backend; | |
6575 ClassElement cls = backend.findHelper('DynamicRuntimeType'); | |
6576 builder.push(new HDynamicType(type, new TypeMask.exact(cls, classWorld))); | |
6577 } | |
6578 } | |
OLD | NEW |