OLD | NEW |
| (Empty) |
1 // Copyright (c) 2014, the Dart project authors. Please see the AUTHORS file | |
2 // for details. All rights reserved. Use of this source code is governed by a | |
3 // BSD-style license that can be found in the LICENSE file. | |
4 | |
5 library statement_rewriter; | |
6 | |
7 import 'tree_ir_nodes.dart'; | |
8 | |
9 /** | |
10 * Performs the following transformations on the tree: | |
11 * - Assignment propagation | |
12 * - If-to-conditional conversion | |
13 * - Flatten nested ifs | |
14 * - Break inlining | |
15 * - Redirect breaks | |
16 * | |
17 * The above transformations all eliminate statements from the tree, and may | |
18 * introduce redexes of each other. | |
19 * | |
20 * | |
21 * ASSIGNMENT PROPAGATION: | |
22 * Single-use definitions are propagated to their use site when possible. | |
23 * For example: | |
24 * | |
25 * { v0 = foo(); return v0; } | |
26 * ==> | |
27 * return foo() | |
28 * | |
29 * After translating out of CPS, all intermediate values are bound by [Assign]. | |
30 * This transformation propagates such definitions to their uses when it is | |
31 * safe and profitable. Bindings are processed "on demand" when their uses are | |
32 * seen, but are only processed once to keep this transformation linear in | |
33 * the size of the tree. | |
34 * | |
35 * The transformation builds an environment containing [Assign] bindings that | |
36 * are in scope. These bindings have yet-untranslated definitions. When a use | |
37 * is encountered the transformation determines if it is safe and profitable | |
38 * to propagate the definition to its use. If so, it is removed from the | |
39 * environment and the definition is recursively processed (in the | |
40 * new environment at the use site) before being propagated. | |
41 * | |
42 * See [visitVariable] for the implementation of the heuristic for propagating | |
43 * a definition. | |
44 * | |
45 * | |
46 * IF-TO-CONDITIONAL CONVERSION: | |
47 * If-statement are converted to conditional expressions when possible. | |
48 * For example: | |
49 * | |
50 * if (v0) { v1 = foo(); break L } else { v1 = bar(); break L } | |
51 * ==> | |
52 * { v1 = v0 ? foo() : bar(); break L } | |
53 * | |
54 * This can lead to inlining of L, which in turn can lead to further propagation | |
55 * of the variable v1. | |
56 * | |
57 * See [visitIf]. | |
58 * | |
59 * | |
60 * FLATTEN NESTED IFS: | |
61 * An if inside an if is converted to an if with a logical operator. | |
62 * For example: | |
63 * | |
64 * if (E1) { if (E2) {S} else break L } else break L | |
65 * ==> | |
66 * if (E1 && E2) {S} else break L | |
67 * | |
68 * This may lead to inlining of L. | |
69 * | |
70 * | |
71 * BREAK INLINING: | |
72 * Single-use labels are inlined at [Break] statements. | |
73 * For example: | |
74 * | |
75 * L0: { v0 = foo(); break L0 }; return v0; | |
76 * ==> | |
77 * v0 = foo(); return v0; | |
78 * | |
79 * This can lead to propagation of v0. | |
80 * | |
81 * See [visitBreak] and [visitLabeledStatement]. | |
82 * | |
83 * | |
84 * REDIRECT BREAKS: | |
85 * Labeled statements whose next is a break become flattened and all breaks | |
86 * to their label are redirected. | |
87 * For example, where 'jump' is either break or continue: | |
88 * | |
89 * L0: {... break L0 ...}; jump L1 | |
90 * ==> | |
91 * {... jump L1 ...} | |
92 * | |
93 * This may trigger a flattening of nested ifs in case the eliminated label | |
94 * separated two ifs. | |
95 */ | |
96 class StatementRewriter extends Visitor<Statement, Expression> { | |
97 // The binding environment. The rightmost element of the list is the nearest | |
98 // available enclosing binding. | |
99 List<Assign> environment; | |
100 | |
101 /// Substitution map for labels. Any break to a label L should be substituted | |
102 /// for a break to L' if L maps to L'. | |
103 Map<Label, Jump> labelRedirects = <Label, Jump>{}; | |
104 | |
105 /// Returns the redirect target of [label] or [label] itself if it should not | |
106 /// be redirected. | |
107 Jump redirect(Jump jump) { | |
108 Jump newJump = labelRedirects[jump.target]; | |
109 return newJump != null ? newJump : jump; | |
110 } | |
111 | |
112 void rewrite(FunctionDefinition definition) { | |
113 if (definition.isAbstract) return; | |
114 | |
115 environment = <Assign>[]; | |
116 definition.body = visitStatement(definition.body); | |
117 | |
118 // TODO(kmillikin): Allow definitions that are not propagated. Here, | |
119 // this means rebuilding the binding with a recursively unnamed definition, | |
120 // or else introducing a variable definition and an assignment. | |
121 assert(environment.isEmpty); | |
122 } | |
123 | |
124 Expression visitExpression(Expression e) => e.processed ? e : e.accept(this); | |
125 | |
126 Expression visitVariable(Variable node) { | |
127 // Propagate a variable's definition to its use site if: | |
128 // 1. It has a single use, to avoid code growth and potential duplication | |
129 // of side effects, AND | |
130 // 2. It was the most recent expression evaluated so that we do not | |
131 // reorder expressions with side effects. | |
132 if (!environment.isEmpty && | |
133 environment.last.variable == node && | |
134 environment.last.hasExactlyOneUse) { | |
135 return visitExpression(environment.removeLast().definition); | |
136 } | |
137 // If the definition could not be propagated, leave the variable use. | |
138 return node; | |
139 } | |
140 | |
141 | |
142 Statement visitAssign(Assign node) { | |
143 environment.add(node); | |
144 Statement next = visitStatement(node.next); | |
145 | |
146 if (!environment.isEmpty && environment.last == node) { | |
147 // The definition could not be propagated. Residualize the let binding. | |
148 node.next = next; | |
149 environment.removeLast(); | |
150 node.definition = visitExpression(node.definition); | |
151 return node; | |
152 } | |
153 assert(!environment.contains(node)); | |
154 return next; | |
155 } | |
156 | |
157 Expression visitInvokeStatic(InvokeStatic node) { | |
158 // Process arguments right-to-left, the opposite of evaluation order. | |
159 for (int i = node.arguments.length - 1; i >= 0; --i) { | |
160 node.arguments[i] = visitExpression(node.arguments[i]); | |
161 } | |
162 return node; | |
163 } | |
164 | |
165 Expression visitInvokeMethod(InvokeMethod node) { | |
166 for (int i = node.arguments.length - 1; i >= 0; --i) { | |
167 node.arguments[i] = visitExpression(node.arguments[i]); | |
168 } | |
169 node.receiver = visitExpression(node.receiver); | |
170 return node; | |
171 } | |
172 | |
173 Expression visitInvokeSuperMethod(InvokeSuperMethod node) { | |
174 for (int i = node.arguments.length - 1; i >= 0; --i) { | |
175 node.arguments[i] = visitExpression(node.arguments[i]); | |
176 } | |
177 return node; | |
178 } | |
179 | |
180 Expression visitInvokeConstructor(InvokeConstructor node) { | |
181 for (int i = node.arguments.length - 1; i >= 0; --i) { | |
182 node.arguments[i] = visitExpression(node.arguments[i]); | |
183 } | |
184 return node; | |
185 } | |
186 | |
187 Expression visitConcatenateStrings(ConcatenateStrings node) { | |
188 for (int i = node.arguments.length - 1; i >= 0; --i) { | |
189 node.arguments[i] = visitExpression(node.arguments[i]); | |
190 } | |
191 return node; | |
192 } | |
193 | |
194 Expression visitConditional(Conditional node) { | |
195 node.condition = visitExpression(node.condition); | |
196 | |
197 List<Assign> savedEnvironment = environment; | |
198 environment = <Assign>[]; | |
199 node.thenExpression = visitExpression(node.thenExpression); | |
200 assert(environment.isEmpty); | |
201 node.elseExpression = visitExpression(node.elseExpression); | |
202 assert(environment.isEmpty); | |
203 environment = savedEnvironment; | |
204 | |
205 return node; | |
206 } | |
207 | |
208 Expression visitLogicalOperator(LogicalOperator node) { | |
209 node.left = visitExpression(node.left); | |
210 | |
211 environment.add(null); // impure expressions may not propagate across branch | |
212 node.right = visitExpression(node.right); | |
213 environment.removeLast(); | |
214 | |
215 return node; | |
216 } | |
217 | |
218 Expression visitNot(Not node) { | |
219 node.operand = visitExpression(node.operand); | |
220 return node; | |
221 } | |
222 | |
223 Expression visitFunctionExpression(FunctionExpression node) { | |
224 new StatementRewriter().rewrite(node.definition); | |
225 return node; | |
226 } | |
227 | |
228 Statement visitFunctionDeclaration(FunctionDeclaration node) { | |
229 new StatementRewriter().rewrite(node.definition); | |
230 node.next = visitStatement(node.next); | |
231 return node; | |
232 } | |
233 | |
234 Statement visitReturn(Return node) { | |
235 node.value = visitExpression(node.value); | |
236 return node; | |
237 } | |
238 | |
239 | |
240 Statement visitBreak(Break node) { | |
241 // Redirect through chain of breaks. | |
242 // Note that useCount was accounted for at visitLabeledStatement. | |
243 // Note redirect may return either a Break or Continue statement. | |
244 Jump jump = redirect(node); | |
245 if (jump is Break && jump.target.useCount == 1) { | |
246 --jump.target.useCount; | |
247 return visitStatement(jump.target.binding.next); | |
248 } | |
249 return jump; | |
250 } | |
251 | |
252 Statement visitContinue(Continue node) { | |
253 return node; | |
254 } | |
255 | |
256 Statement visitLabeledStatement(LabeledStatement node) { | |
257 if (node.next is Jump) { | |
258 // Eliminate label if next is a break or continue statement | |
259 // Breaks to this label are redirected to the outer label. | |
260 // Note that breakCount for the two labels is updated proactively here | |
261 // so breaks can reliably tell if they should inline their target. | |
262 Jump next = node.next; | |
263 Jump newJump = redirect(next); | |
264 labelRedirects[node.label] = newJump; | |
265 newJump.target.useCount += node.label.useCount - 1; | |
266 node.label.useCount = 0; | |
267 Statement result = visitStatement(node.body); | |
268 labelRedirects.remove(node.label); // Save some space. | |
269 return result; | |
270 } | |
271 | |
272 node.body = visitStatement(node.body); | |
273 | |
274 if (node.label.useCount == 0) { | |
275 // Eliminate the label if next was inlined at a break | |
276 return node.body; | |
277 } | |
278 | |
279 // Do not propagate assignments into the successor statements, since they | |
280 // may be overwritten by assignments in the body. | |
281 List<Assign> savedEnvironment = environment; | |
282 environment = <Assign>[]; | |
283 node.next = visitStatement(node.next); | |
284 environment = savedEnvironment; | |
285 | |
286 return node; | |
287 } | |
288 | |
289 Statement visitIf(If node) { | |
290 node.condition = visitExpression(node.condition); | |
291 | |
292 // Do not propagate assignments into branches. Doing so will lead to code | |
293 // duplication. | |
294 // TODO(kmillikin): Rethink this. Propagating some assignments (e.g., | |
295 // constants or variables) is benign. If they can occur here, they should | |
296 // be handled well. | |
297 List<Assign> savedEnvironment = environment; | |
298 environment = <Assign>[]; | |
299 node.thenStatement = visitStatement(node.thenStatement); | |
300 assert(environment.isEmpty); | |
301 node.elseStatement = visitStatement(node.elseStatement); | |
302 assert(environment.isEmpty); | |
303 environment = savedEnvironment; | |
304 | |
305 tryCollapseIf(node); | |
306 | |
307 Statement reduced = combineStatementsWithSubexpressions( | |
308 node.thenStatement, | |
309 node.elseStatement, | |
310 (t,f) => new Conditional(node.condition, t, f)..processed = true); | |
311 if (reduced != null) { | |
312 if (reduced.next is Break) { | |
313 // In case the break can now be inlined. | |
314 reduced = visitStatement(reduced); | |
315 } | |
316 return reduced; | |
317 } | |
318 | |
319 return node; | |
320 } | |
321 | |
322 Statement visitWhileTrue(WhileTrue node) { | |
323 // Do not propagate assignments into loops. Doing so is not safe for | |
324 // variables modified in the loop (the initial value will be propagated). | |
325 List<Assign> savedEnvironment = environment; | |
326 environment = <Assign>[]; | |
327 node.body = visitStatement(node.body); | |
328 assert(environment.isEmpty); | |
329 environment = savedEnvironment; | |
330 return node; | |
331 } | |
332 | |
333 Statement visitWhileCondition(WhileCondition node) { | |
334 // Not introduced yet | |
335 throw "Unexpected WhileCondition in StatementRewriter"; | |
336 } | |
337 | |
338 Expression visitConstant(Constant node) { | |
339 return node; | |
340 } | |
341 | |
342 Expression visitThis(This node) { | |
343 return node; | |
344 } | |
345 | |
346 Expression visitReifyTypeVar(ReifyTypeVar node) { | |
347 return node; | |
348 } | |
349 | |
350 Expression visitLiteralList(LiteralList node) { | |
351 // Process values right-to-left, the opposite of evaluation order. | |
352 for (int i = node.values.length - 1; i >= 0; --i) { | |
353 node.values[i] = visitExpression(node.values[i]); | |
354 } | |
355 return node; | |
356 } | |
357 | |
358 Expression visitLiteralMap(LiteralMap node) { | |
359 // Process arguments right-to-left, the opposite of evaluation order. | |
360 for (LiteralMapEntry entry in node.entries.reversed) { | |
361 entry.value = visitExpression(entry.value); | |
362 entry.key = visitExpression(entry.key); | |
363 } | |
364 return node; | |
365 } | |
366 | |
367 Expression visitTypeOperator(TypeOperator node) { | |
368 node.receiver = visitExpression(node.receiver); | |
369 return node; | |
370 } | |
371 | |
372 Statement visitExpressionStatement(ExpressionStatement node) { | |
373 node.expression = visitExpression(node.expression); | |
374 // Do not allow propagation of assignments past an expression evaluated | |
375 // for its side effects because it risks reordering side effects. | |
376 // TODO(kmillikin): Rethink this. Some propagation is benign, e.g., | |
377 // constants, variables, or other pure values that are not destroyed by | |
378 // the expression statement. If they can occur here they should be | |
379 // handled well. | |
380 List<Assign> savedEnvironment = environment; | |
381 environment = <Assign>[]; | |
382 node.next = visitStatement(node.next); | |
383 assert(environment.isEmpty); | |
384 environment = savedEnvironment; | |
385 return node; | |
386 } | |
387 | |
388 /// If [s] and [t] are similar statements we extract their subexpressions | |
389 /// and returns a new statement of the same type using expressions combined | |
390 /// with the [combine] callback. For example: | |
391 /// | |
392 /// combineStatements(Return E1, Return E2) = Return combine(E1, E2) | |
393 /// | |
394 /// If [combine] returns E1 then the unified statement is equivalent to [s], | |
395 /// and if [combine] returns E2 the unified statement is equivalence to [t]. | |
396 /// | |
397 /// It is guaranteed that no side effects occur between the beginning of the | |
398 /// statement and the position of the combined expression. | |
399 /// | |
400 /// Returns null if the statements are too different. | |
401 /// | |
402 /// If non-null is returned, the caller MUST discard [s] and [t] and use | |
403 /// the returned statement instead. | |
404 static Statement combineStatementsWithSubexpressions( | |
405 Statement s, | |
406 Statement t, | |
407 Expression combine(Expression s, Expression t)) { | |
408 if (s is Return && t is Return) { | |
409 return new Return(combine(s.value, t.value)); | |
410 } | |
411 if (s is Assign && t is Assign && s.variable == t.variable) { | |
412 Statement next = combineStatements(s.next, t.next); | |
413 if (next != null) { | |
414 --t.variable.writeCount; // Two assignments become one. | |
415 return new Assign(s.variable, | |
416 combine(s.definition, t.definition), | |
417 next); | |
418 } | |
419 } | |
420 if (s is ExpressionStatement && t is ExpressionStatement) { | |
421 Statement next = combineStatements(s.next, t.next); | |
422 if (next != null) { | |
423 return new ExpressionStatement(combine(s.expression, t.expression), | |
424 next); | |
425 } | |
426 } | |
427 return null; | |
428 } | |
429 | |
430 /// Returns a statement equivalent to both [s] and [t], or null if [s] and | |
431 /// [t] are incompatible. | |
432 /// If non-null is returned, the caller MUST discard [s] and [t] and use | |
433 /// the returned statement instead. | |
434 /// If two breaks are combined, the label's break counter will be decremented. | |
435 static Statement combineStatements(Statement s, Statement t) { | |
436 if (s is Break && t is Break && s.target == t.target) { | |
437 --t.target.useCount; // Two breaks become one. | |
438 return s; | |
439 } | |
440 if (s is Continue && t is Continue && s.target == t.target) { | |
441 --t.target.useCount; // Two continues become one. | |
442 return s; | |
443 } | |
444 if (s is Return && t is Return) { | |
445 Expression e = combineExpressions(s.value, t.value); | |
446 if (e != null) { | |
447 return new Return(e); | |
448 } | |
449 } | |
450 return null; | |
451 } | |
452 | |
453 /// Returns an expression equivalent to both [e1] and [e2]. | |
454 /// If non-null is returned, the caller must discard [e1] and [e2] and use | |
455 /// the resulting expression in the tree. | |
456 static Expression combineExpressions(Expression e1, Expression e2) { | |
457 if (e1 is Variable && e1 == e2) { | |
458 --e1.readCount; // Two references become one. | |
459 return e1; | |
460 } | |
461 if (e1 is Constant && e2 is Constant && e1.value == e2.value) { | |
462 return e1; | |
463 } | |
464 return null; | |
465 } | |
466 | |
467 /// Try to collapse nested ifs using && and || expressions. | |
468 /// For example: | |
469 /// | |
470 /// if (E1) { if (E2) S else break L } else break L | |
471 /// ==> | |
472 /// if (E1 && E2) S else break L | |
473 /// | |
474 /// [branch1] and [branch2] control the position of the S statement. | |
475 /// | |
476 /// Returns true if another collapse redex might have been introduced. | |
477 void tryCollapseIf(If node) { | |
478 // Repeatedly try to collapse nested ifs. | |
479 // The transformation is shrinking (destroys an if) so it remains linear. | |
480 // Here is an example where more than one iteration is required: | |
481 // | |
482 // if (E1) | |
483 // if (E2) break L2 else break L1 | |
484 // else | |
485 // break L1 | |
486 // | |
487 // L1.target ::= | |
488 // if (E3) S else break L2 | |
489 // | |
490 // After first collapse: | |
491 // | |
492 // if (E1 && E2) | |
493 // break L2 | |
494 // else | |
495 // {if (E3) S else break L2} (inlined from break L1) | |
496 // | |
497 // We can then do another collapse using the inlined nested if. | |
498 bool changed = true; | |
499 while (changed) { | |
500 changed = false; | |
501 if (tryCollapseIfAux(node, true, true)) { | |
502 changed = true; | |
503 } | |
504 if (tryCollapseIfAux(node, true, false)) { | |
505 changed = true; | |
506 } | |
507 if (tryCollapseIfAux(node, false, true)) { | |
508 changed = true; | |
509 } | |
510 if (tryCollapseIfAux(node, false, false)) { | |
511 changed = true; | |
512 } | |
513 } | |
514 } | |
515 | |
516 bool tryCollapseIfAux(If outerIf, bool branch1, bool branch2) { | |
517 // NOTE: We name variables here as if S is in the then-then position. | |
518 Statement outerThen = getBranch(outerIf, branch1); | |
519 Statement outerElse = getBranch(outerIf, !branch1); | |
520 if (outerThen is If && outerElse is Break) { | |
521 If innerIf = outerThen; | |
522 Statement innerThen = getBranch(innerIf, branch2); | |
523 Statement innerElse = getBranch(innerIf, !branch2); | |
524 if (innerElse is Break && innerElse.target == outerElse.target) { | |
525 // We always put S in the then branch of the result, and adjust the | |
526 // condition expression if S was actually found in the else branch(es). | |
527 outerIf.condition = new LogicalOperator.and( | |
528 makeCondition(outerIf.condition, branch1), | |
529 makeCondition(innerIf.condition, branch2)); | |
530 outerIf.thenStatement = innerThen; | |
531 --innerElse.target.useCount; | |
532 | |
533 // Try to inline the remaining break. Do not propagate assignments. | |
534 List<Assign> savedEnvironment = environment; | |
535 environment = <Assign>[]; | |
536 outerIf.elseStatement = visitStatement(outerElse); | |
537 assert(environment.isEmpty); | |
538 environment = savedEnvironment; | |
539 | |
540 return outerIf.elseStatement is If && innerThen is Break; | |
541 } | |
542 } | |
543 return false; | |
544 } | |
545 | |
546 Expression makeCondition(Expression e, bool polarity) { | |
547 return polarity ? e : new Not(e); | |
548 } | |
549 | |
550 Statement getBranch(If node, bool polarity) { | |
551 return polarity ? node.thenStatement : node.elseStatement; | |
552 } | |
553 } | |
OLD | NEW |