Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(166)

Side by Side Diff: pkg/compiler/lib/src/tree_ir/optimization/statement_rewriter.dart

Issue 2246623002: Delete CPS IR (Closed) Base URL: git@github.com:dart-lang/sdk.git@master
Patch Set: Created 4 years, 4 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch
OLDNEW
(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 tree_ir.optimization.statement_rewriter;
6
7 import '../../elements/elements.dart';
8 import '../../io/source_information.dart';
9 import '../../js/placeholder_safety.dart';
10 import '../tree_ir_nodes.dart';
11 import 'optimization.dart' show Pass;
12
13 /**
14 * Translates to direct-style.
15 *
16 * In addition to the general IR constraints (see [CheckTreeIntegrity]),
17 * the input is assumed to satisfy the following criteria:
18 *
19 * All expressions other than those nested in [Assign] or [ExpressionStatement]
20 * must be simple. A [VariableUse] and [This] is a simple expression.
21 * The right-hand of an [Assign] may not be an [Assign].
22 *
23 * Moreover, every variable must either be an SSA variable or a mutable
24 * variable, and must satisfy the corresponding criteria:
25 *
26 * SSA VARIABLE:
27 * An SSA variable must have a unique definition site, which is either an
28 * assignment or label. In case of a label, its target must act as the unique
29 * reaching definition of that variable at all uses of the variable and at
30 * all other label targets where the variable is in scope.
31 *
32 * (The second criterion is to ensure that we can move a use of an SSA variable
33 * across a label without changing its reaching definition).
34 *
35 * MUTABLE VARIABLE:
36 * Uses of mutable variables are considered complex expressions, and hence must
37 * not be nested in other expressions. Assignments to mutable variables must
38 * have simple right-hand sides.
39 *
40 * ----
41 *
42 * This pass performs the following transformations on the tree:
43 * - Assignment inlining
44 * - Assignment expression propagation
45 * - If-to-conditional conversion
46 * - Flatten nested ifs
47 * - Break inlining
48 * - Redirect breaks
49 *
50 * The above transformations all eliminate statements from the tree, and may
51 * introduce redexes of each other.
52 *
53 *
54 * ASSIGNMENT INLINING:
55 * Single-use definitions are inlined at their use site when possible.
56 * For example:
57 *
58 * { v0 = foo(); return v0; }
59 * ==>
60 * return foo()
61 *
62 * After translating out of CPS, all intermediate values are bound by [Assign].
63 * This transformation propagates such definitions to their uses when it is
64 * safe and profitable. Bindings are processed "on demand" when their uses are
65 * seen, but are only processed once to keep this transformation linear in
66 * the size of the tree.
67 *
68 * The transformation builds an environment containing [Assign] bindings that
69 * are in scope. These bindings have yet-untranslated definitions. When a use
70 * is encountered the transformation determines if it is safe and profitable
71 * to propagate the definition to its use. If so, it is removed from the
72 * environment and the definition is recursively processed (in the
73 * new environment at the use site) before being propagated.
74 *
75 * See [visitVariableUse] for the implementation of the heuristic for
76 * propagating a definition.
77 *
78 *
79 * ASSIGNMENT EXPRESSION PROPAGATION:
80 * Definitions with multiple uses are propagated to their first use site
81 * when possible. For example:
82 *
83 * { v0 = foo(); bar(v0); return v0; }
84 * ==>
85 * { bar(v0 = foo()); return v0; }
86 *
87 * Note that the [RestoreInitializers] phase will later undo this rewrite
88 * in cases where it prevents an assignment from being pulled into an
89 * initializer.
90 *
91 *
92 * IF-TO-CONDITIONAL CONVERSION:
93 * If-statement are converted to conditional expressions when possible.
94 * For example:
95 *
96 * if (v0) { v1 = foo(); break L } else { v1 = bar(); break L }
97 * ==>
98 * { v1 = v0 ? foo() : bar(); break L }
99 *
100 * This can lead to inlining of L, which in turn can lead to further propagation
101 * of the variable v1.
102 *
103 * See [visitIf].
104 *
105 *
106 * FLATTEN NESTED IFS:
107 * An if inside an if is converted to an if with a logical operator.
108 * For example:
109 *
110 * if (E1) { if (E2) {S} else break L } else break L
111 * ==>
112 * if (E1 && E2) {S} else break L
113 *
114 * This may lead to inlining of L.
115 *
116 *
117 * BREAK INLINING:
118 * Single-use labels are inlined at [Break] statements.
119 * For example:
120 *
121 * L0: { v0 = foo(); break L0 }; return v0;
122 * ==>
123 * v0 = foo(); return v0;
124 *
125 * This can lead to propagation of v0.
126 *
127 * See [visitBreak] and [visitLabeledStatement].
128 *
129 *
130 * REDIRECT BREAKS:
131 * Labeled statements whose next is a break become flattened and all breaks
132 * to their label are redirected.
133 * For example, where 'jump' is either break or continue:
134 *
135 * L0: {... break L0 ...}; jump L1
136 * ==>
137 * {... jump L1 ...}
138 *
139 * This may trigger a flattening of nested ifs in case the eliminated label
140 * separated two ifs.
141 */
142 class StatementRewriter extends Transformer implements Pass {
143 String get passName => 'Statement rewriter';
144
145 @override
146 void rewrite(FunctionDefinition node) {
147 node.parameters.forEach(pushDominatingAssignment);
148 node.body = visitStatement(node.body);
149 node.parameters.forEach(popDominatingAssignment);
150 }
151
152 /// The most recently evaluated impure expressions, with the most recent
153 /// expression being last.
154 ///
155 /// Most importantly, this contains [Assign] expressions that we attempt to
156 /// inline at their use site. It also contains other impure expressions that
157 /// we can propagate to a variable use if they are known to return the value
158 /// of that variable.
159 ///
160 /// Assignments with constant right-hand sides (see [isEffectivelyConstant])
161 /// are not considered impure and are put in [constantEnvironment] instead.
162 ///
163 /// Except for [Conditional]s, expressions in the environment have
164 /// not been processed, and all their subexpressions must therefore be
165 /// variables uses.
166 List<Expression> environment = <Expression>[];
167
168 /// Binding environment for variables that are assigned to effectively
169 /// constant expressions (see [isEffectivelyConstant]).
170 Map<Variable, Expression> constantEnvironment = <Variable, Expression>{};
171
172 /// Substitution map for labels. Any break to a label L should be substituted
173 /// for a break to L' if L maps to L'.
174 Map<Label, Jump> labelRedirects = <Label, Jump>{};
175
176 /// Number of uses of the given variable that are still unseen.
177 /// Used to detect the first use of a variable (since we do backwards
178 /// traversal, the first use is the last one seen).
179 Map<Variable, int> unseenUses = <Variable, int>{};
180
181 /// Number of assignments to a given variable that dominate the current
182 /// position.
183 ///
184 /// Pure expressions will not be inlined if it uses a variable with more than
185 /// one dominating assignment, because the reaching definition of the used
186 /// variable might have changed since it was put in the environment.
187 final Map<Variable, int> dominatingAssignments = <Variable, int>{};
188
189 /// A set of labels that can be safely inlined at their use.
190 ///
191 /// The successor statements for labeled statements that have only one break
192 /// from them are normally rewritten inline at the site of the break. This
193 /// is not safe if the code would be moved inside the scope of an exception
194 /// handler (i.e., if the code would be moved into a try from outside it).
195 Set<Label> safeForInlining = new Set<Label>();
196
197 /// If the top element is true, assignments of form "x = CONST" may be
198 /// propagated into a following occurence of CONST. This may confuse the JS
199 /// engine so it is disabled in some cases.
200 final List<bool> allowRhsPropagation = <bool>[true];
201
202 bool get isRhsPropagationAllowed => allowRhsPropagation.last;
203
204 /// Returns the redirect target of [jump] or [jump] itself if it should not
205 /// be redirected.
206 Jump redirect(Jump jump) {
207 Jump newJump = labelRedirects[jump.target];
208 return newJump != null ? newJump : jump;
209 }
210
211 void inEmptyEnvironment(void action(), {bool keepConstants: true}) {
212 List oldEnvironment = environment;
213 Map oldConstantEnvironment = constantEnvironment;
214 environment = <Expression>[];
215 if (!keepConstants) {
216 constantEnvironment = <Variable, Expression>{};
217 }
218 action();
219 assert(environment.isEmpty);
220 environment = oldEnvironment;
221 if (!keepConstants) {
222 constantEnvironment = oldConstantEnvironment;
223 }
224 }
225
226 /// Left-hand side of the given assignment, or `null` if not an assignment.
227 Variable getLeftHand(Expression e) {
228 return e is Assign ? e.variable : null;
229 }
230
231 /// If the given expression always returns the value of one of its
232 /// subexpressions, returns that subexpression, otherwise `null`.
233 Expression getValueSubexpression(Expression e) {
234 if (e is SetField) return e.value;
235 return null;
236 }
237
238 /// If the given expression always returns the value of one of its
239 /// subexpressions, and that subexpression is a variable use, returns that
240 /// variable. Otherwise `null`.
241 Variable getRightHandVariable(Expression e) {
242 Expression value = getValueSubexpression(e);
243 return value is VariableUse ? value.variable : null;
244 }
245
246 Constant getRightHandConstant(Expression e) {
247 Expression value = getValueSubexpression(e);
248 return value is Constant ? value : null;
249 }
250
251 /// True if the given expression (taken from [constantEnvironment]) uses a
252 /// variable that might have been reassigned since [node] was evaluated.
253 bool hasUnsafeVariableUse(Expression node) {
254 bool wasFound = false;
255 VariableUseVisitor.visit(node, (VariableUse use) {
256 if (dominatingAssignments[use.variable] > 1) {
257 wasFound = true;
258 }
259 });
260 return wasFound;
261 }
262
263 void pushDominatingAssignment(Variable variable) {
264 if (variable != null) {
265 dominatingAssignments.putIfAbsent(variable, () => 0);
266 ++dominatingAssignments[variable];
267 }
268 }
269
270 void popDominatingAssignment(Variable variable) {
271 if (variable != null) {
272 --dominatingAssignments[variable];
273 }
274 }
275
276 @override
277 Expression visitVariableUse(VariableUse node) {
278 // Count of number of unseen uses remaining.
279 unseenUses.putIfAbsent(node.variable, () => node.variable.readCount);
280 --unseenUses[node.variable];
281
282 // We traverse the tree right-to-left, so when we have seen all uses,
283 // it means we are looking at the first use.
284 assert(unseenUses[node.variable] < node.variable.readCount);
285 assert(unseenUses[node.variable] >= 0);
286
287 // We cannot reliably find the first dynamic use of a variable that is
288 // accessed from a JS function in a foreign code fragment.
289 if (node.variable.isCaptured) return node;
290
291 bool isFirstUse = unseenUses[node.variable] == 0;
292
293 // Propagate constant to use site.
294 Expression constant = constantEnvironment[node.variable];
295 if (constant != null && !hasUnsafeVariableUse(constant)) {
296 --node.variable.readCount;
297 return visitExpression(constant);
298 }
299
300 // Try to propagate another expression into this variable use.
301 if (!environment.isEmpty) {
302 Expression binding = environment.last;
303
304 // Is this variable assigned by the most recently evaluated impure
305 // expression?
306 //
307 // If so, propagate the assignment, e.g:
308 //
309 // { x = foo(); bar(x, x) } ==> bar(x = foo(), x)
310 //
311 // We must ensure that no other uses separate this use from the
312 // assignment. We therefore only propagate assignments into the first use.
313 //
314 // Note that if this is only use, `visitAssign` will then remove the
315 // redundant assignment.
316 if (getLeftHand(binding) == node.variable && isFirstUse) {
317 environment.removeLast();
318 --node.variable.readCount;
319 return visitExpression(binding);
320 }
321
322 // Is the most recently evaluated impure expression known to have the
323 // value of this variable?
324 //
325 // If so, we can replace this use with the impure expression, e.g:
326 //
327 // { E.foo = x; bar(x) } ==> bar(E.foo = x)
328 //
329 if (isRhsPropagationAllowed &&
330 getRightHandVariable(binding) == node.variable) {
331 environment.removeLast();
332 --node.variable.readCount;
333 return visitExpression(binding);
334 }
335 }
336
337 // If the definition could not be propagated, leave the variable use.
338 return node;
339 }
340
341 /// True if [exp] contains a use of a variable that was assigned to by the
342 /// most recently evaluated impure expression (constant assignments are not
343 /// considered impure).
344 ///
345 /// This implies that the assignment can be propagated into this use unless
346 /// the use is moved further away.
347 ///
348 /// In this case, we will refrain from moving [exp] across other impure
349 /// expressions, even when this is safe, because doing so would immediately
350 /// prevent the previous expression from propagating, canceling out the
351 /// benefit we might otherwise gain from propagating [exp].
352 ///
353 /// [exp] must be an unprocessed expression, i.e. either a [Conditional] or
354 /// an expression whose subexpressions are all variable uses.
355 bool usesRecentlyAssignedVariable(Expression exp) {
356 if (environment.isEmpty) return false;
357 Variable variable = getLeftHand(environment.last);
358 if (variable == null) return false;
359 IsVariableUsedVisitor visitor = new IsVariableUsedVisitor(variable);
360 visitor.visitExpression(exp);
361 return visitor.wasFound;
362 }
363
364 /// Returns true if [exp] has no side effects and has a constant value within
365 /// any given activation of the enclosing method.
366 bool isEffectivelyConstant(Expression exp) {
367 // TODO(asgerf): Can be made more aggressive e.g. by checking conditional
368 // expressions recursively. Determine if that is a valuable optimization
369 // and/or if it is better handled at the CPS level.
370 return exp is Constant ||
371 exp is This ||
372 exp is CreateInvocationMirror ||
373 exp is CreateInstance ||
374 exp is CreateBox ||
375 exp is TypeExpression ||
376 exp is GetStatic && exp.element.isFunction ||
377 exp is Interceptor ||
378 exp is ApplyBuiltinOperator ||
379 exp is VariableUse && constantEnvironment.containsKey(exp.variable);
380 }
381
382 /// True if [node] is an assignment that can be propagated as a constant.
383 bool isEffectivelyConstantAssignment(Expression node) {
384 return node is Assign &&
385 node.variable.writeCount == 1 &&
386 isEffectivelyConstant(node.value);
387 }
388
389 Statement visitExpressionStatement(ExpressionStatement inputNode) {
390 // Analyze chains of expression statements.
391 // To avoid deep recursion, [processExpressionStatement] returns a callback
392 // to invoke after its successor node has been processed.
393 // These callbacks are stored in a list and invoked in reverse at the end.
394 List<Function> stack = [];
395 Statement node = inputNode;
396 while (node is ExpressionStatement) {
397 stack.add(processExpressionStatement(node));
398 node = node.next;
399 }
400 Statement result = visitStatement(node);
401 for (Function fun in stack.reversed) {
402 result = fun(result);
403 }
404 return result;
405 }
406
407 /// Attempts to propagate an assignment in an expression statement.
408 ///
409 /// Returns a callback to be invoked after the sucessor statement has
410 /// been processed.
411 Function processExpressionStatement(ExpressionStatement stmt) {
412 Variable leftHand = getLeftHand(stmt.expression);
413 pushDominatingAssignment(leftHand);
414 if (isEffectivelyConstantAssignment(stmt.expression) &&
415 !usesRecentlyAssignedVariable(stmt.expression)) {
416 Assign assign = stmt.expression;
417 // Handle constant assignments specially.
418 // They are always safe to propagate (though we should avoid duplication).
419 // Moreover, they should not prevent other expressions from propagating.
420 if (assign.variable.readCount == 1) {
421 // A single-use constant should always be propagated to its use site.
422 constantEnvironment[assign.variable] = assign.value;
423 return (Statement next) {
424 popDominatingAssignment(leftHand);
425 if (assign.variable.readCount > 0) {
426 // The assignment could not be propagated into the successor,
427 // either because it [hasUnsafeVariableUse] or because the
428 // use is outside the current try block, and we do not currently
429 // support constant propagation out of a try block.
430 constantEnvironment.remove(assign.variable);
431 assign.value = visitExpression(assign.value);
432 stmt.next = next;
433 return stmt;
434 } else {
435 --assign.variable.writeCount;
436 return next;
437 }
438 };
439 } else {
440 // With more than one use, we cannot propagate the constant.
441 // Visit the following statement without polluting [environment] so
442 // that any preceding non-constant assignments might still propagate.
443 return (Statement next) {
444 stmt.next = next;
445 popDominatingAssignment(leftHand);
446 assign.value = visitExpression(assign.value);
447 return stmt;
448 };
449 }
450 } else {
451 // Try to propagate the expression, and block previous impure expressions
452 // until this has propagated.
453 environment.add(stmt.expression);
454 return (Statement next) {
455 stmt.next = next;
456 popDominatingAssignment(leftHand);
457 if (!environment.isEmpty && environment.last == stmt.expression) {
458 // Retain the expression statement.
459 environment.removeLast();
460 stmt.expression = visitExpression(stmt.expression);
461 return stmt;
462 } else {
463 // Expression was propagated into the successor.
464 return stmt.next;
465 }
466 };
467 }
468 }
469
470 Expression visitAssign(Assign node) {
471 allowRhsPropagation.add(true);
472 node.value = visitExpression(node.value);
473 allowRhsPropagation.removeLast();
474 // Remove assignments to variables without any uses. This can happen
475 // because the assignment was propagated into its use, e.g:
476 //
477 // { x = foo(); bar(x) } ==> bar(x = foo()) ==> bar(foo())
478 //
479 if (node.variable.readCount == 0) {
480 --node.variable.writeCount;
481 return node.value;
482 }
483 return node;
484 }
485
486 /// Process nodes right-to-left, the opposite of evaluation order in the case
487 /// of argument lists..
488 void _rewriteList(List<Node> nodes, {bool rhsPropagation: true}) {
489 allowRhsPropagation.add(rhsPropagation);
490 for (int i = nodes.length - 1; i >= 0; --i) {
491 nodes[i] = visitExpression(nodes[i]);
492 }
493 allowRhsPropagation.removeLast();
494 }
495
496 Expression visitInvokeStatic(InvokeStatic node) {
497 _rewriteList(node.arguments);
498 return node;
499 }
500
501 Expression visitInvokeMethod(InvokeMethod node) {
502 if (node.receiverIsNotNull) {
503 _rewriteList(node.arguments);
504 node.receiver = visitExpression(node.receiver);
505 } else {
506 // Impure expressions cannot be propagated across the method lookup,
507 // because it throws when the receiver is null.
508 inEmptyEnvironment(() {
509 _rewriteList(node.arguments);
510 });
511 node.receiver = visitExpression(node.receiver);
512 }
513 return node;
514 }
515
516 Expression visitOneShotInterceptor(OneShotInterceptor node) {
517 _rewriteList(node.arguments);
518 return node;
519 }
520
521 Expression visitApplyBuiltinMethod(ApplyBuiltinMethod node) {
522 if (node.receiverIsNotNull) {
523 _rewriteList(node.arguments);
524 node.receiver = visitExpression(node.receiver);
525 } else {
526 // Impure expressions cannot be propagated across the method lookup,
527 // because it throws when the receiver is null.
528 inEmptyEnvironment(() {
529 _rewriteList(node.arguments);
530 });
531 node.receiver = visitExpression(node.receiver);
532 }
533 return node;
534 }
535
536 Expression visitInvokeMethodDirectly(InvokeMethodDirectly node) {
537 _rewriteList(node.arguments);
538 // The target function might not exist before the enclosing class has been
539 // instantitated for the first time. If the receiver might be the first
540 // instantiation of its class, we cannot propgate it into the receiver
541 // expression, because the target function is evaluated before the receiver.
542 // Calls to constructor bodies are compiled so that the receiver is
543 // evaluated first, so they are safe.
544 if (node.target is! ConstructorBodyElement) {
545 inEmptyEnvironment(() {
546 node.receiver = visitExpression(node.receiver);
547 });
548 } else {
549 node.receiver = visitExpression(node.receiver);
550 }
551 return node;
552 }
553
554 Expression visitInvokeConstructor(InvokeConstructor node) {
555 _rewriteList(node.arguments);
556 return node;
557 }
558
559 Expression visitConditional(Conditional node) {
560 // Conditional expressions do not exist in the input, but they are
561 // introduced by if-to-conditional conversion.
562 // Their subexpressions have already been processed; do not reprocess them.
563 //
564 // Note that this can only happen for conditional expressions. It is an
565 // error for any other type of expression to be visited twice or to be
566 // created and then visited. We use this special treatment of conditionals
567 // to allow for assignment inlining after if-to-conditional conversion.
568 //
569 // There are several reasons we should not reprocess the subexpressions:
570 //
571 // - It will mess up the [seenUses] counter, since a single use will be
572 // counted twice.
573 //
574 // - Other visit methods assume that all subexpressions are variable uses
575 // because they come fresh out of the tree IR builder.
576 //
577 // - Reprocessing can be expensive.
578 //
579 return node;
580 }
581
582 Expression visitLogicalOperator(LogicalOperator node) {
583 // Impure expressions may not propagate across the branch.
584 inEmptyEnvironment(() {
585 node.right = visitExpression(node.right);
586 });
587 node.left = visitExpression(node.left);
588 return node;
589 }
590
591 Expression visitNot(Not node) {
592 node.operand = visitExpression(node.operand);
593 return node;
594 }
595
596 bool isNullConstant(Expression node) {
597 return node is Constant && node.value.isNull;
598 }
599
600 Statement visitReturn(Return node) {
601 if (!isNullConstant(node.value)) {
602 // Do not chain assignments into a null return.
603 node.value = visitExpression(node.value);
604 }
605 return node;
606 }
607
608 Statement visitThrow(Throw node) {
609 node.value = visitExpression(node.value);
610 return node;
611 }
612
613 Statement visitUnreachable(Unreachable node) {
614 return node;
615 }
616
617 Statement visitBreak(Break node) {
618 // Redirect through chain of breaks.
619 // Note that useCount was accounted for at visitLabeledStatement.
620 // Note redirect may return either a Break or Continue statement.
621 Jump jump = redirect(node);
622 if (jump is Break &&
623 jump.target.useCount == 1 &&
624 safeForInlining.contains(jump.target)) {
625 --jump.target.useCount;
626 return visitStatement(jump.target.binding.next);
627 }
628 return jump;
629 }
630
631 Statement visitContinue(Continue node) {
632 return node;
633 }
634
635 Statement visitLabeledStatement(LabeledStatement node) {
636 if (node.next is Jump) {
637 // Eliminate label if next is a break or continue statement
638 // Breaks to this label are redirected to the outer label.
639 // Note that breakCount for the two labels is updated proactively here
640 // so breaks can reliably tell if they should inline their target.
641 Jump next = node.next;
642 Jump newJump = redirect(next);
643 labelRedirects[node.label] = newJump;
644 newJump.target.useCount += node.label.useCount - 1;
645 node.label.useCount = 0;
646 Statement result = visitStatement(node.body);
647 labelRedirects.remove(node.label); // Save some space.
648 return result;
649 }
650
651 safeForInlining.add(node.label);
652 node.body = visitStatement(node.body);
653 safeForInlining.remove(node.label);
654
655 if (node.label.useCount == 0) {
656 // Eliminate the label if next was inlined at a break
657 return node.body;
658 }
659
660 // Do not propagate assignments into the successor statements, since they
661 // may be overwritten by assignments in the body.
662 inEmptyEnvironment(() {
663 node.next = visitStatement(node.next);
664 });
665
666 return node;
667 }
668
669 Statement visitIf(If node) {
670 // Do not propagate assignments into branches.
671 inEmptyEnvironment(() {
672 node.thenStatement = visitStatement(node.thenStatement);
673 node.elseStatement = visitStatement(node.elseStatement);
674 });
675
676 node.condition = visitExpression(node.condition);
677
678 inEmptyEnvironment(() {
679 tryCollapseIf(node);
680 });
681
682 Statement reduced = combineStatementsInBranches(
683 node.thenStatement, node.elseStatement, node.condition);
684 if (reduced != null) {
685 return reduced;
686 }
687
688 return node;
689 }
690
691 Statement visitWhileTrue(WhileTrue node) {
692 // Do not propagate assignments into loops. Doing so is not safe for
693 // variables modified in the loop (the initial value will be propagated).
694 // Do not propagate effective constant expressions into loops, since
695 // computing them is not free (e.g. interceptors are expensive).
696 inEmptyEnvironment(() {
697 node.body = visitStatement(node.body);
698 }, keepConstants: false);
699 return node;
700 }
701
702 Statement visitFor(For node) {
703 // Not introduced yet
704 throw "Unexpected For in StatementRewriter";
705 }
706
707 Statement visitTry(Try node) {
708 inEmptyEnvironment(() {
709 Set<Label> saved = safeForInlining;
710 safeForInlining = new Set<Label>();
711 node.tryBody = visitStatement(node.tryBody);
712 safeForInlining = saved;
713 node.catchParameters.forEach(pushDominatingAssignment);
714 node.catchBody = visitStatement(node.catchBody);
715 node.catchParameters.forEach(popDominatingAssignment);
716 });
717 return node;
718 }
719
720 Expression visitConstant(Constant node) {
721 if (isRhsPropagationAllowed && !environment.isEmpty) {
722 Constant constant = getRightHandConstant(environment.last);
723 if (constant != null && constant.value == node.value) {
724 return visitExpression(environment.removeLast());
725 }
726 }
727 return node;
728 }
729
730 Expression visitThis(This node) {
731 return node;
732 }
733
734 Expression visitLiteralList(LiteralList node) {
735 _rewriteList(node.values);
736 return node;
737 }
738
739 Expression visitTypeOperator(TypeOperator node) {
740 _rewriteList(node.typeArguments);
741 node.value = visitExpression(node.value);
742 return node;
743 }
744
745 bool isCompoundableBuiltin(Expression e) {
746 return e is ApplyBuiltinOperator &&
747 e.arguments.length >= 2 &&
748 isCompoundableOperator(e.operator);
749 }
750
751 /// Converts a compoundable operator application into the right-hand side for
752 /// use in a compound assignment, discarding the left-hand value.
753 ///
754 /// For example, for `x + y + z` it returns `y + z`.
755 Expression contractCompoundableBuiltin(ApplyBuiltinOperator e) {
756 assert(isCompoundableBuiltin(e));
757 if (e.arguments.length > 2) {
758 assert(e.operator == BuiltinOperator.StringConcatenate);
759 return new ApplyBuiltinOperator(
760 e.operator, e.arguments.skip(1).toList(), e.sourceInformation);
761 } else {
762 return e.arguments[1];
763 }
764 }
765
766 void destroyVariableUse(VariableUse node) {
767 --node.variable.readCount;
768 }
769
770 Expression visitSetField(SetField node) {
771 allowRhsPropagation.add(true);
772 node.value = visitExpression(node.value);
773 if (isCompoundableBuiltin(node.value)) {
774 ApplyBuiltinOperator rhs = node.value;
775 Expression left = rhs.arguments[0];
776 if (left is GetField &&
777 left.field == node.field &&
778 samePrimary(left.object, node.object)) {
779 destroyPrimaryExpression(left.object);
780 node.compound = rhs.operator;
781 node.value = contractCompoundableBuiltin(rhs);
782 }
783 }
784 node.object = visitExpression(node.object);
785 allowRhsPropagation.removeLast();
786 return node;
787 }
788
789 Expression visitGetField(GetField node) {
790 node.object = visitExpression(node.object);
791 return node;
792 }
793
794 Expression visitGetStatic(GetStatic node) {
795 return node;
796 }
797
798 Expression visitSetStatic(SetStatic node) {
799 allowRhsPropagation.add(true);
800 node.value = visitExpression(node.value);
801 if (isCompoundableBuiltin(node.value)) {
802 ApplyBuiltinOperator rhs = node.value;
803 Expression left = rhs.arguments[0];
804 if (left is GetStatic &&
805 left.element == node.element &&
806 !left.useLazyGetter) {
807 node.compound = rhs.operator;
808 node.value = contractCompoundableBuiltin(rhs);
809 }
810 }
811 allowRhsPropagation.removeLast();
812 return node;
813 }
814
815 Expression visitGetTypeTestProperty(GetTypeTestProperty node) {
816 node.object = visitExpression(node.object);
817 return node;
818 }
819
820 Expression visitCreateBox(CreateBox node) {
821 return node;
822 }
823
824 Expression visitCreateInstance(CreateInstance node) {
825 if (node.typeInformation != null) {
826 node.typeInformation = visitExpression(node.typeInformation);
827 }
828 _rewriteList(node.arguments);
829 return node;
830 }
831
832 Expression visitReifyRuntimeType(ReifyRuntimeType node) {
833 node.value = visitExpression(node.value);
834 return node;
835 }
836
837 Expression visitReadTypeVariable(ReadTypeVariable node) {
838 node.target = visitExpression(node.target);
839 return node;
840 }
841
842 Expression visitTypeExpression(TypeExpression node) {
843 _rewriteList(node.arguments);
844 return node;
845 }
846
847 Expression visitCreateInvocationMirror(CreateInvocationMirror node) {
848 _rewriteList(node.arguments);
849 return node;
850 }
851
852 Expression visitInterceptor(Interceptor node) {
853 node.input = visitExpression(node.input);
854 return node;
855 }
856
857 Expression visitGetLength(GetLength node) {
858 node.object = visitExpression(node.object);
859 return node;
860 }
861
862 Expression visitGetIndex(GetIndex node) {
863 node.index = visitExpression(node.index);
864 node.object = visitExpression(node.object);
865 return node;
866 }
867
868 Expression visitSetIndex(SetIndex node) {
869 node.value = visitExpression(node.value);
870 if (isCompoundableBuiltin(node.value)) {
871 ApplyBuiltinOperator rhs = node.value;
872 Expression left = rhs.arguments[0];
873 if (left is GetIndex &&
874 samePrimary(left.object, node.object) &&
875 samePrimary(left.index, node.index)) {
876 destroyPrimaryExpression(left.object);
877 destroyPrimaryExpression(left.index);
878 node.compound = rhs.operator;
879 node.value = contractCompoundableBuiltin(rhs);
880 }
881 }
882 node.index = visitExpression(node.index);
883 node.object = visitExpression(node.object);
884 return node;
885 }
886
887 /// True if [operator] is a binary operator that always has the same value
888 /// if its arguments are swapped.
889 bool isSymmetricOperator(BuiltinOperator operator) {
890 switch (operator) {
891 case BuiltinOperator.StrictEq:
892 case BuiltinOperator.StrictNeq:
893 case BuiltinOperator.LooseEq:
894 case BuiltinOperator.LooseNeq:
895 case BuiltinOperator.NumAnd:
896 case BuiltinOperator.NumOr:
897 case BuiltinOperator.NumXor:
898 case BuiltinOperator.NumAdd:
899 case BuiltinOperator.NumMultiply:
900 return true;
901 default:
902 return false;
903 }
904 }
905
906 /// If [operator] is a commutable binary operator, returns the commuted
907 /// operator, possibly the operator itself, otherwise returns `null`.
908 BuiltinOperator commuteBinaryOperator(BuiltinOperator operator) {
909 if (isSymmetricOperator(operator)) {
910 // Symmetric operators are their own commutes.
911 return operator;
912 }
913 switch (operator) {
914 case BuiltinOperator.NumLt:
915 return BuiltinOperator.NumGt;
916 case BuiltinOperator.NumLe:
917 return BuiltinOperator.NumGe;
918 case BuiltinOperator.NumGt:
919 return BuiltinOperator.NumLt;
920 case BuiltinOperator.NumGe:
921 return BuiltinOperator.NumLe;
922 default:
923 return null;
924 }
925 }
926
927 /// Built-in binary operators are commuted when it is safe and can enable an
928 /// assignment propagation. For example:
929 ///
930 /// var x = foo();
931 /// var y = bar();
932 /// var z = y < x;
933 ///
934 /// ==>
935 ///
936 /// var z = foo() > bar();
937 ///
938 /// foo() must be evaluated before bar(), so the propagation is only possible
939 /// by commuting the operator.
940 Expression visitApplyBuiltinOperator(ApplyBuiltinOperator node) {
941 if (!environment.isEmpty && getLeftHand(environment.last) != null) {
942 Variable propagatableVariable = getLeftHand(environment.last);
943 BuiltinOperator commuted = commuteBinaryOperator(node.operator);
944 if (commuted != null) {
945 // Only binary operators can commute.
946 assert(node.arguments.length == 2);
947 Expression left = node.arguments[0];
948 if (left is VariableUse && propagatableVariable == left.variable) {
949 Expression right = node.arguments[1];
950 if (right is This ||
951 (right is VariableUse &&
952 propagatableVariable != right.variable &&
953 !constantEnvironment.containsKey(right.variable))) {
954 // An assignment can be propagated if we commute the operator.
955 node.operator = commuted;
956 node.arguments[0] = right;
957 node.arguments[1] = left;
958 }
959 }
960 }
961 }
962 // Avoid code like `p == (q.f = null)`. JS operators with a constant operand
963 // can sometimes be compiled to a specialized instruction in the JS engine,
964 // so retain syntactically constant operands.
965 _rewriteList(node.arguments, rhsPropagation: false);
966 return node;
967 }
968
969 /// If [s] and [t] are similar statements we extract their subexpressions
970 /// and returns a new statement of the same type using expressions combined
971 /// with the [combine] callback. For example:
972 ///
973 /// combineStatements(Return E1, Return E2) = Return combine(E1, E2)
974 ///
975 /// If [combine] returns E1 then the unified statement is equivalent to [s],
976 /// and if [combine] returns E2 the unified statement is equivalence to [t].
977 ///
978 /// It is guaranteed that no side effects occur between the beginning of the
979 /// statement and the position of the combined expression.
980 ///
981 /// Returns null if the statements are too different.
982 ///
983 /// If non-null is returned, the caller MUST discard [s] and [t] and use
984 /// the returned statement instead.
985 Statement combineStatementsInBranches(
986 Statement s, Statement t, Expression condition) {
987 if (s is Return && t is Return) {
988 return new Return(new Conditional(condition, s.value, t.value));
989 }
990 if (s is ExpressionStatement && t is ExpressionStatement) {
991 // Combine the two expressions and the two successor statements.
992 //
993 // C ? {E1 ; S1} : {E2 ; S2}
994 // ==>
995 // (C ? E1 : E2) : combine(S1, S2)
996 //
997 // If E1 and E2 are assignments, we want to propagate these into the
998 // combined statement.
999 //
1000 // It might not be possible to combine the statements, so we combine the
1001 // expressions, put the result in the environment, and then uncombine the
1002 // expressions if the statements could not be combined.
1003
1004 // Combine the expressions.
1005 CombinedExpressions values =
1006 combineAsConditional(s.expression, t.expression, condition);
1007
1008 // Put this into the environment and try to combine the statements.
1009 // We are not in risk of reprocessing the original subexpressions because
1010 // the combined expression will always hide them inside a Conditional.
1011 environment.add(values.combined);
1012
1013 Variable leftHand = getLeftHand(values.combined);
1014 pushDominatingAssignment(leftHand);
1015 Statement next = combineStatements(s.next, t.next);
1016 popDominatingAssignment(leftHand);
1017
1018 if (next == null) {
1019 // Statements could not be combined.
1020 // Restore the environment and uncombine expressions again.
1021 environment.removeLast();
1022 values.uncombine();
1023 return null;
1024 } else if (!environment.isEmpty && environment.last == values.combined) {
1025 // Statements were combined but the combined expression could not be
1026 // propagated. Leave it as an expression statement here.
1027 environment.removeLast();
1028 s.expression = values.combined;
1029 s.next = next;
1030 return s;
1031 } else {
1032 // Statements were combined and the combined expressions were
1033 // propagated into the combined statement.
1034 return next;
1035 }
1036 }
1037 return null;
1038 }
1039
1040 /// Creates the expression `[condition] ? [s] : [t]` or an equivalent
1041 /// expression if something better can be done.
1042 ///
1043 /// In particular, assignments will be merged as follows:
1044 ///
1045 /// C ? (v = E1) : (v = E2)
1046 /// ==>
1047 /// v = C ? E1 : E2
1048 ///
1049 /// The latter form is more compact and can also be inlined.
1050 CombinedExpressions combineAsConditional(
1051 Expression s, Expression t, Expression condition) {
1052 if (s is Assign && t is Assign && s.variable == t.variable) {
1053 Expression values = new Conditional(condition, s.value, t.value);
1054 return new CombinedAssigns(s, t, new CombinedExpressions(values));
1055 }
1056 return new CombinedExpressions(new Conditional(condition, s, t));
1057 }
1058
1059 /// Returns a statement equivalent to both [s] and [t], or null if [s] and
1060 /// [t] are incompatible.
1061 /// If non-null is returned, the caller MUST discard [s] and [t] and use
1062 /// the returned statement instead.
1063 /// If two breaks are combined, the label's break counter will be decremented.
1064 Statement combineStatements(Statement s, Statement t) {
1065 if (s is Break && t is Break && s.target == t.target) {
1066 --t.target.useCount; // Two breaks become one.
1067 if (s.target.useCount == 1 && safeForInlining.contains(s.target)) {
1068 // Only one break remains; inline it.
1069 --s.target.useCount;
1070 return visitStatement(s.target.binding.next);
1071 }
1072 return s;
1073 }
1074 if (s is Continue && t is Continue && s.target == t.target) {
1075 --t.target.useCount; // Two continues become one.
1076 return s;
1077 }
1078 if (s is Return && t is Return) {
1079 CombinedExpressions values = combineExpressions(s.value, t.value);
1080 if (values != null) {
1081 // TODO(johnniwinther): Handle multiple source informations.
1082 SourceInformation sourceInformation = s.sourceInformation != null
1083 ? s.sourceInformation
1084 : t.sourceInformation;
1085 return new Return(values.combined,
1086 sourceInformation: sourceInformation);
1087 }
1088 }
1089 if (s is ExpressionStatement && t is ExpressionStatement) {
1090 CombinedExpressions values =
1091 combineExpressions(s.expression, t.expression);
1092 if (values == null) return null;
1093 environment.add(values.combined);
1094 Variable leftHand = getLeftHand(values.combined);
1095 pushDominatingAssignment(leftHand);
1096 Statement next = combineStatements(s.next, t.next);
1097 popDominatingAssignment(leftHand);
1098 if (next == null) {
1099 // The successors could not be combined.
1100 // Restore the environment and uncombine the values again.
1101 assert(environment.last == values.combined);
1102 environment.removeLast();
1103 values.uncombine();
1104 return null;
1105 } else if (!environment.isEmpty && environment.last == values.combined) {
1106 // The successors were combined but the combined expressions were not
1107 // propagated. Leave the combined expression as a statement.
1108 environment.removeLast();
1109 s.expression = values.combined;
1110 s.next = next;
1111 return s;
1112 } else {
1113 // The successors were combined, and the combined expressions were
1114 // propagated into the successors.
1115 return next;
1116 }
1117 }
1118 return null;
1119 }
1120
1121 /// Returns an expression equivalent to both [e1] and [e2].
1122 /// If non-null is returned, the caller must discard [e1] and [e2] and use
1123 /// the resulting expression in the tree.
1124 CombinedExpressions combineExpressions(Expression e1, Expression e2) {
1125 if (e1 is VariableUse && e2 is VariableUse && e1.variable == e2.variable) {
1126 return new CombinedUses(e1, e2);
1127 }
1128 if (e1 is Assign && e2 is Assign && e1.variable == e2.variable) {
1129 CombinedExpressions values = combineExpressions(e1.value, e2.value);
1130 if (values != null) {
1131 return new CombinedAssigns(e1, e2, values);
1132 }
1133 }
1134 if (e1 is Constant && e2 is Constant && e1.value == e2.value) {
1135 return new CombinedExpressions(e1);
1136 }
1137 return null;
1138 }
1139
1140 /// Try to collapse nested ifs using && and || expressions.
1141 /// For example:
1142 ///
1143 /// if (E1) { if (E2) S else break L } else break L
1144 /// ==>
1145 /// if (E1 && E2) S else break L
1146 ///
1147 /// [branch1] and [branch2] control the position of the S statement.
1148 ///
1149 /// Must be called with an empty environment.
1150 void tryCollapseIf(If node) {
1151 assert(environment.isEmpty);
1152 // Repeatedly try to collapse nested ifs.
1153 // The transformation is shrinking (destroys an if) so it remains linear.
1154 // Here is an example where more than one iteration is required:
1155 //
1156 // if (E1)
1157 // if (E2) break L2 else break L1
1158 // else
1159 // break L1
1160 //
1161 // L1.target ::=
1162 // if (E3) S else break L2
1163 //
1164 // After first collapse:
1165 //
1166 // if (E1 && E2)
1167 // break L2
1168 // else
1169 // {if (E3) S else break L2} (inlined from break L1)
1170 //
1171 // We can then do another collapse using the inlined nested if.
1172 bool changed = true;
1173 while (changed) {
1174 changed = false;
1175 if (tryCollapseIfAux(node, true, true)) {
1176 changed = true;
1177 }
1178 if (tryCollapseIfAux(node, true, false)) {
1179 changed = true;
1180 }
1181 if (tryCollapseIfAux(node, false, true)) {
1182 changed = true;
1183 }
1184 if (tryCollapseIfAux(node, false, false)) {
1185 changed = true;
1186 }
1187 }
1188 }
1189
1190 bool tryCollapseIfAux(If outerIf, bool branch1, bool branch2) {
1191 // NOTE: We name variables here as if S is in the then-then position.
1192 Statement outerThen = getBranch(outerIf, branch1);
1193 Statement outerElse = getBranch(outerIf, !branch1);
1194 if (outerThen is If) {
1195 If innerIf = outerThen;
1196 Statement innerThen = getBranch(innerIf, branch2);
1197 Statement innerElse = getBranch(innerIf, !branch2);
1198 Statement combinedElse = combineStatements(innerElse, outerElse);
1199 if (combinedElse != null) {
1200 // We always put S in the then branch of the result, and adjust the
1201 // condition expression if S was actually found in the else branch(es).
1202 outerIf.condition = new LogicalOperator.and(
1203 makeCondition(outerIf.condition, branch1),
1204 makeCondition(innerIf.condition, branch2));
1205 outerIf.thenStatement = innerThen;
1206 outerIf.elseStatement = combinedElse;
1207 return outerIf.elseStatement is If;
1208 }
1209 }
1210 return false;
1211 }
1212
1213 Expression makeCondition(Expression e, bool polarity) {
1214 return polarity ? e : new Not(e);
1215 }
1216
1217 Statement getBranch(If node, bool polarity) {
1218 return polarity ? node.thenStatement : node.elseStatement;
1219 }
1220
1221 void handleForeignCode(ForeignCode node) {
1222 // Some arguments will get inserted in a JS code template. The arguments
1223 // will not always be evaluated (e.g. the second placeholder in the template
1224 // '# && #').
1225 bool isNullable(int position) => node.nullableArguments[position];
1226
1227 int safeArguments =
1228 PlaceholderSafetyAnalysis.analyze(node.codeTemplate.ast, isNullable);
1229 inEmptyEnvironment(() {
1230 for (int i = node.arguments.length - 1; i >= safeArguments; --i) {
1231 node.arguments[i] = visitExpression(node.arguments[i]);
1232 }
1233 });
1234 for (int i = safeArguments - 1; i >= 0; --i) {
1235 node.arguments[i] = visitExpression(node.arguments[i]);
1236 }
1237 }
1238
1239 @override
1240 Expression visitForeignExpression(ForeignExpression node) {
1241 handleForeignCode(node);
1242 return node;
1243 }
1244
1245 @override
1246 Statement visitForeignStatement(ForeignStatement node) {
1247 handleForeignCode(node);
1248 return node;
1249 }
1250
1251 @override
1252 Expression visitAwait(Await node) {
1253 node.input = visitExpression(node.input);
1254 return node;
1255 }
1256
1257 @override
1258 Statement visitYield(Yield node) {
1259 node.next = visitStatement(node.next);
1260 node.input = visitExpression(node.input);
1261 return node;
1262 }
1263
1264 @override
1265 Statement visitReceiverCheck(ReceiverCheck node) {
1266 inEmptyEnvironment(() {
1267 node.next = visitStatement(node.next);
1268 });
1269 if (node.condition != null) {
1270 inEmptyEnvironment(() {
1271 // Value occurs in conditional context.
1272 node.value = visitExpression(node.value);
1273 });
1274 node.condition = visitExpression(node.condition);
1275 } else {
1276 node.value = visitExpression(node.value);
1277 }
1278 return node;
1279 }
1280 }
1281
1282 /// Result of combining two expressions, with the potential for reverting the
1283 /// combination.
1284 ///
1285 /// Reverting a combination is done by calling [uncombine]. In this case,
1286 /// both the original expressions should remain in the tree, and the [combined]
1287 /// expression should be orphaned.
1288 ///
1289 /// Explicitly reverting a combination is necessary to maintain variable
1290 /// reference counts.
1291 abstract class CombinedExpressions {
1292 Expression get combined;
1293 void uncombine();
1294
1295 factory CombinedExpressions(Expression e) = GenericCombinedExpressions;
1296 }
1297
1298 /// Combines assignments of form `[variable] := E1` and `[variable] := E2` into
1299 /// a single assignment of form `[variable] := combine(E1, E2)`.
1300 class CombinedAssigns implements CombinedExpressions {
1301 Assign assign1, assign2;
1302 CombinedExpressions value;
1303 Expression combined;
1304
1305 CombinedAssigns(this.assign1, this.assign2, this.value) {
1306 assert(assign1.variable == assign2.variable);
1307 assign1.variable.writeCount -= 2; // Destroy the two original assignemnts.
1308 combined = new Assign(assign1.variable, value.combined);
1309 }
1310
1311 void uncombine() {
1312 value.uncombine();
1313 ++assign1.variable.writeCount; // Restore original reference count.
1314 }
1315 }
1316
1317 /// Combines two variable uses into one.
1318 class CombinedUses implements CombinedExpressions {
1319 VariableUse use1, use2;
1320 Expression combined;
1321
1322 CombinedUses(this.use1, this.use2) {
1323 assert(use1.variable == use2.variable);
1324 use1.variable.readCount -= 2; // Destroy both the original uses.
1325 combined = new VariableUse(use1.variable);
1326 }
1327
1328 void uncombine() {
1329 ++use1.variable.readCount; // Restore original reference count.
1330 }
1331 }
1332
1333 /// Result of combining two expressions that do not affect reference counting.
1334 class GenericCombinedExpressions implements CombinedExpressions {
1335 Expression combined;
1336
1337 GenericCombinedExpressions(this.combined);
1338
1339 void uncombine() {}
1340 }
1341
1342 /// Looks for uses of a specific variable.
1343 ///
1344 /// Note that this visitor is only applied to expressions where all
1345 /// sub-expressions are known to be variable uses, so there is no risk of
1346 /// explosive reprocessing.
1347 class IsVariableUsedVisitor extends RecursiveVisitor {
1348 Variable variable;
1349 bool wasFound = false;
1350
1351 IsVariableUsedVisitor(this.variable);
1352
1353 visitVariableUse(VariableUse node) {
1354 if (node.variable == variable) {
1355 wasFound = true;
1356 }
1357 }
1358 }
1359
1360 typedef VariableUseCallback(VariableUse use);
1361
1362 class VariableUseVisitor extends RecursiveVisitor {
1363 VariableUseCallback callback;
1364
1365 VariableUseVisitor(this.callback);
1366
1367 visitVariableUse(VariableUse use) => callback(use);
1368
1369 static void visit(Expression node, VariableUseCallback callback) {
1370 new VariableUseVisitor(callback).visitExpression(node);
1371 }
1372 }
1373
1374 bool sameVariable(Expression e1, Expression e2) {
1375 return e1 is VariableUse && e2 is VariableUse && e1.variable == e2.variable;
1376 }
1377
1378 /// True if [e1] and [e2] are primary expressions (expressions without
1379 /// subexpressions) with the same value.
1380 bool samePrimary(Expression e1, Expression e2) {
1381 return sameVariable(e1, e2) || (e1 is This && e2 is This);
1382 }
1383
1384 /// Decrement the reference count for [e] if it is a variable use.
1385 void destroyPrimaryExpression(Expression e) {
1386 if (e is VariableUse) {
1387 --e.variable.readCount;
1388 } else {
1389 assert(e is This);
1390 }
1391 }
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698