Chromium Code Reviews| OLD | NEW |
|---|---|
| 1 // Copyright (c) 2014, the Dart project authors. Please see the AUTHORS file | 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 | 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. | 3 // BSD-style license that can be found in the LICENSE file. |
| 4 | 4 |
| 5 library dart_tree; | 5 library dart_tree; |
| 6 | 6 |
| 7 import '../dart2jslib.dart' as dart2js; | 7 import '../dart2jslib.dart' as dart2js; |
| 8 import '../elements/elements.dart' | 8 import '../elements/elements.dart' |
| 9 show Element, FunctionElement, FunctionSignature, ParameterElement, | 9 show Element, FunctionElement, FunctionSignature, ParameterElement, |
| 10 ClassElement; | 10 ClassElement; |
| (...skipping 167 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 178 | 178 |
| 179 final bool isPure = false; // invokes toString | 179 final bool isPure = false; // invokes toString |
| 180 | 180 |
| 181 accept(Visitor visitor) => visitor.visitConcatenateStrings(this); | 181 accept(Visitor visitor) => visitor.visitConcatenateStrings(this); |
| 182 } | 182 } |
| 183 | 183 |
| 184 /** | 184 /** |
| 185 * A constant. | 185 * A constant. |
| 186 */ | 186 */ |
| 187 class Constant extends Expression { | 187 class Constant extends Expression { |
| 188 final dart2js.Constant value; | 188 dart2js.Constant value; |
| 189 | 189 |
| 190 Constant(this.value); | 190 Constant(this.value); |
| 191 | 191 |
| 192 final bool isPure = true; | 192 final bool isPure = true; |
| 193 | 193 |
| 194 accept(Visitor visitor) => visitor.visitConstant(this); | 194 accept(Visitor visitor) => visitor.visitConstant(this); |
| 195 } | 195 } |
| 196 | 196 |
| 197 /// A conditional expression. | 197 /// A conditional expression. |
| 198 class Conditional extends Expression { | 198 class Conditional extends Expression { |
| 199 Expression condition; | 199 Expression condition; |
| 200 Expression thenExpression; | 200 Expression thenExpression; |
| 201 Expression elseExpression; | 201 Expression elseExpression; |
| 202 | 202 |
| 203 Conditional(this.condition, this.thenExpression, this.elseExpression); | 203 Conditional(this.condition, this.thenExpression, this.elseExpression); |
| 204 | 204 |
| 205 // TODO(asgerf): Repeatedly computing isPure is potentially expensive, | 205 // TODO(asgerf): Repeatedly computing isPure is potentially expensive, |
| 206 // but caching isPure in a field is dangerous because a subexpression could | 206 // but caching isPure in a field is dangerous because a subexpression could |
| 207 // become impure during a transformation (e.g. assignment propagation). | 207 // become impure during a transformation (e.g. assignment propagation). |
| 208 // Improve the situation somehow. | 208 // Improve the situation somehow. |
| 209 bool get isPure => condition.isPure && | 209 bool get isPure => condition.isPure && |
| 210 thenExpression.isPure && | 210 thenExpression.isPure && |
| 211 elseExpression.isPure; | 211 elseExpression.isPure; |
| 212 | 212 |
| 213 accept(Visitor visitor) => visitor.visitConditional(this); | 213 accept(Visitor visitor) => visitor.visitConditional(this); |
| 214 } | 214 } |
| 215 | 215 |
| 216 /// An && or || expression. The operator is internally represented as a boolean | |
| 217 /// [isAnd] to simplify rewriting of logical operators. | |
| 218 class LogicalOperator extends Expression { | |
| 219 Expression left; | |
| 220 bool isAnd; | |
| 221 Expression right; | |
| 222 | |
| 223 LogicalOperator(this.left, this.right, this.isAnd); | |
| 224 LogicalOperator.and(this.left, this.right) : isAnd = true; | |
| 225 LogicalOperator.or(this.left, this.right) : isAnd = false; | |
| 226 | |
| 227 String get operator => isAnd ? '&&' : '||'; | |
| 228 | |
| 229 bool get isPure => left.isPure && right.isPure; | |
| 230 | |
| 231 accept(Visitor visitor) => visitor.visitLogicalOperator(this); | |
| 232 } | |
| 233 | |
| 234 /// Logical negation. | |
| 235 class Not extends Expression { | |
| 236 Expression operand; | |
| 237 | |
| 238 Not(this.operand); | |
| 239 | |
| 240 bool get isPure => operand.isPure; | |
| 241 | |
| 242 accept(Visitor visitor) => visitor.visitNot(this); | |
| 243 } | |
| 244 | |
| 216 /** | 245 /** |
| 217 * A labeled statement. Breaks to the label within the labeled statement | 246 * A labeled statement. Breaks to the label within the labeled statement |
| 218 * target the successor statement. | 247 * target the successor statement. |
| 219 */ | 248 */ |
| 220 class LabeledStatement extends Statement { | 249 class LabeledStatement extends Statement { |
| 221 Statement next; | 250 Statement next; |
| 222 final Label label; | 251 final Label label; |
| 223 Statement body; | 252 Statement body; |
| 224 | 253 |
| 225 LabeledStatement(this.label, this.body, this.next) { | 254 LabeledStatement(this.label, this.body, this.next) { |
| (...skipping 97 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 323 | 352 |
| 324 abstract class Visitor<S, E> { | 353 abstract class Visitor<S, E> { |
| 325 E visitExpression(Expression e) => e.accept(this); | 354 E visitExpression(Expression e) => e.accept(this); |
| 326 E visitVariable(Variable node); | 355 E visitVariable(Variable node); |
| 327 E visitInvokeStatic(InvokeStatic node); | 356 E visitInvokeStatic(InvokeStatic node); |
| 328 E visitInvokeMethod(InvokeMethod node); | 357 E visitInvokeMethod(InvokeMethod node); |
| 329 E visitInvokeConstructor(InvokeConstructor node); | 358 E visitInvokeConstructor(InvokeConstructor node); |
| 330 E visitConcatenateStrings(ConcatenateStrings node); | 359 E visitConcatenateStrings(ConcatenateStrings node); |
| 331 E visitConstant(Constant node); | 360 E visitConstant(Constant node); |
| 332 E visitConditional(Conditional node); | 361 E visitConditional(Conditional node); |
| 362 E visitLogicalOperator(LogicalOperator node); | |
| 363 E visitNot(Not node); | |
| 333 | 364 |
| 334 S visitStatement(Statement s) => s.accept(this); | 365 S visitStatement(Statement s) => s.accept(this); |
| 335 S visitLabeledStatement(LabeledStatement node); | 366 S visitLabeledStatement(LabeledStatement node); |
| 336 S visitAssign(Assign node); | 367 S visitAssign(Assign node); |
| 337 S visitReturn(Return node); | 368 S visitReturn(Return node); |
| 338 S visitBreak(Break node); | 369 S visitBreak(Break node); |
| 339 S visitIf(If node); | 370 S visitIf(If node); |
| 340 S visitExpressionStatement(ExpressionStatement node); | 371 S visitExpressionStatement(ExpressionStatement node); |
| 341 } | 372 } |
| 342 | 373 |
| (...skipping 238 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 581 compiler.internalError(compiler.currentElement, 'Unexpected IR node.'); | 612 compiler.internalError(compiler.currentElement, 'Unexpected IR node.'); |
| 582 return null; | 613 return null; |
| 583 } | 614 } |
| 584 | 615 |
| 585 Expression visitIsTrue(ir.IsTrue node) { | 616 Expression visitIsTrue(ir.IsTrue node) { |
| 586 return variables[node.value.definition]; | 617 return variables[node.value.definition]; |
| 587 } | 618 } |
| 588 } | 619 } |
| 589 | 620 |
| 590 /** | 621 /** |
| 591 * Performs the following three transformations on the tree: | 622 * Performs the following three transformations on the tree: |
|
Kevin Millikin (Google)
2014/05/19 11:36:41
following three transformations ==> following tran
asgerf
2014/05/19 13:29:07
Done.
| |
| 592 * - Assignment propagation | 623 * - Assignment propagation |
| 593 * - If-to-conditional conversion | 624 * - If-to-conditional conversion |
| 625 * - Flatten nested ifs | |
| 594 * - Break inlining | 626 * - Break inlining |
| 627 * - Redirect breaks | |
| 595 * | 628 * |
| 596 * The above transformations are performed in the same phase because each | 629 * The above transformations are performed in the same phase because each |
| 597 * transformation can introduce redexes of one of the others. | 630 * transformation can introduce redexes of one of the others. |
| 598 * | 631 * |
| 599 * | 632 * |
| 600 * ASSIGNMENT PROPAGATION: | 633 * ASSIGNMENT PROPAGATION: |
| 601 * Single-use definitions are propagated to their use site when possible. | 634 * Single-use definitions are propagated to their use site when possible. |
| 602 * For example: | 635 * For example: |
| 603 * | 636 * |
| 604 * { v0 = foo(); return v0; } | 637 * { v0 = foo(); return v0; } |
| (...skipping 24 matching lines...) Expand all Loading... | |
| 629 * if (v0) { v1 = foo(); break L } else { v1 = bar(); break L } | 662 * if (v0) { v1 = foo(); break L } else { v1 = bar(); break L } |
| 630 * ==> | 663 * ==> |
| 631 * { v1 = v0 ? foo() : bar(); break L } | 664 * { v1 = v0 ? foo() : bar(); break L } |
| 632 * | 665 * |
| 633 * This can lead to inlining of L, which in turn can lead to further propagation | 666 * This can lead to inlining of L, which in turn can lead to further propagation |
| 634 * of the variable v1. | 667 * of the variable v1. |
| 635 * | 668 * |
| 636 * See [visitIf]. | 669 * See [visitIf]. |
| 637 * | 670 * |
| 638 * | 671 * |
| 672 * FLATTEN NESTED IFS: | |
| 673 * An if inside an if is converted to an if with a logical operator. | |
| 674 * For example: | |
| 675 * | |
| 676 * if (E1) { if (E2) {S} else break L } else break L | |
| 677 * ==> | |
| 678 * if (E1 && E2) {S} else break L | |
| 679 * | |
| 680 * This may lead to inlining of L. | |
| 681 * | |
| 682 * | |
| 639 * BREAK INLINING: | 683 * BREAK INLINING: |
| 640 * Single-use labels are inlined at [Break] statements. | 684 * Single-use labels are inlined at [Break] statements. |
| 641 * For example: | 685 * For example: |
| 642 * | 686 * |
| 643 * L0: { v0 = foo(); break L0 }; return v0; | 687 * L0: { v0 = foo(); break L0 }; return v0; |
| 644 * ==> | 688 * ==> |
| 645 * v0 = foo(); return v0; | 689 * v0 = foo(); return v0; |
| 646 * | 690 * |
| 647 * This can lead to propagation of v0. | 691 * This can lead to propagation of v0. |
| 648 * | 692 * |
| 649 * See [visitBreak] and [visitLabeledStatement]. | 693 * See [visitBreak] and [visitLabeledStatement]. |
| 694 * | |
| 695 * | |
| 696 * REDIRECT BREAKS: | |
| 697 * Labeled statements whose next is a break become flattened and all breaks | |
| 698 * to their label are redirected. | |
| 699 * For example: | |
| 700 * | |
| 701 * L0: {... break L0 ...}; break L1 | |
| 702 * ==> | |
| 703 * {... break L1 ...} | |
| 704 * | |
| 705 * This may trigger a flattening of nested ifs in case the eliminated label | |
| 706 * separated two ifs. | |
| 650 */ | 707 */ |
| 651 class TreeRewriter extends Visitor<Statement, Expression> { | 708 class TreeRewriter extends Visitor<Statement, Expression> { |
| 652 // The binding environment. The rightmost element of the list is the nearest | 709 // The binding environment. The rightmost element of the list is the nearest |
| 653 // enclosing binding. | 710 // enclosing binding. |
| 654 // We use null to mark an impure expressions that does not bind a variable. | 711 // We use null to mark an impure expressions that does not bind a variable. |
| 655 List<Assign> environment; | 712 List<Assign> environment; |
| 656 | 713 |
| 657 void apply(FunctionDefinition definition) { | 714 /// Substitution map for labels. Any break to a label L should be substituted |
| 715 /// for a break to L' if L maps to L'. | |
| 716 Map<Label,Label> labelRedirects = <Label,Label>{}; | |
|
Kevin Millikin (Google)
2014/05/19 11:36:41
Space after comma: Map<Label, Label>
asgerf
2014/05/19 13:29:07
Done.
| |
| 717 | |
| 718 /// Returns the redirect target of [label] or [label] itself if it should not | |
| 719 /// be redirected. | |
| 720 Label redirect(Label label) { | |
| 721 Label newTarget = labelRedirects[label]; | |
| 722 return newTarget != null ? newTarget : label; | |
| 723 } | |
| 724 | |
| 725 void rewrite(FunctionDefinition definition) { | |
| 658 environment = <Assign>[]; | 726 environment = <Assign>[]; |
| 659 definition.body = visitStatement(definition.body); | 727 definition.body = visitStatement(definition.body); |
| 660 | 728 |
| 661 // TODO(kmillikin): Allow definitions that are not propagated. Here, | 729 // TODO(kmillikin): Allow definitions that are not propagated. Here, |
| 662 // this means rebuilding the binding with a recursively unnamed definition, | 730 // this means rebuilding the binding with a recursively unnamed definition, |
| 663 // or else introducing a variable definition and an assignment. | 731 // or else introducing a variable definition and an assignment. |
| 664 assert(environment.isEmpty); | 732 assert(environment.isEmpty); |
| 665 } | 733 } |
| 666 | 734 |
| 667 Expression visitExpression(Expression e) => e.processed ? e : e.accept(this); | 735 Expression visitExpression(Expression e) => e.processed ? e : e.accept(this); |
| (...skipping 29 matching lines...) Expand all Loading... | |
| 697 } else if (!environment[i].definition.isPure) { | 765 } else if (!environment[i].definition.isPure) { |
| 698 // Once the first impure definition is seen, impure definitions should | 766 // Once the first impure definition is seen, impure definitions should |
| 699 // no longer be propagated. Continue searching for a pure definition. | 767 // no longer be propagated. Continue searching for a pure definition. |
| 700 seenImpure = true; | 768 seenImpure = true; |
| 701 } | 769 } |
| 702 } | 770 } |
| 703 // If the definition could not be propagated, leave the variable use. | 771 // If the definition could not be propagated, leave the variable use. |
| 704 return node; | 772 return node; |
| 705 } | 773 } |
| 706 | 774 |
| 707 Statement visitLabeledStatement(LabeledStatement node) { | |
| 708 node.body = visitStatement(node.body); | |
| 709 if (node.label.breakCount == 0) { | |
| 710 // If the break was inlined, eliminate the label. | |
| 711 return node.body; | |
| 712 } | |
| 713 node.next = visitStatement(node.next); | |
| 714 return node; | |
| 715 } | |
| 716 | 775 |
| 717 Statement visitAssign(Assign node) { | 776 Statement visitAssign(Assign node) { |
| 718 environment.add(node); | 777 environment.add(node); |
| 719 Statement next = visitStatement(node.next); | 778 Statement next = visitStatement(node.next); |
| 720 | 779 |
| 721 if (!environment.isEmpty && environment.last == node) { | 780 if (!environment.isEmpty && environment.last == node) { |
| 722 // The definition could not be propagated. Residualize the let binding. | 781 // The definition could not be propagated. Residualize the let binding. |
| 723 node.next = next; | 782 node.next = next; |
| 724 environment.removeLast(); | 783 environment.removeLast(); |
| 725 node.definition = visitExpression(node.definition); | 784 node.definition = visitExpression(node.definition); |
| (...skipping 37 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 763 node.condition = visitExpression(node.condition); | 822 node.condition = visitExpression(node.condition); |
| 764 | 823 |
| 765 environment.add(null); // impure expressions may not propagate across branch | 824 environment.add(null); // impure expressions may not propagate across branch |
| 766 node.thenExpression = visitExpression(node.thenExpression); | 825 node.thenExpression = visitExpression(node.thenExpression); |
| 767 node.elseExpression = visitExpression(node.elseExpression); | 826 node.elseExpression = visitExpression(node.elseExpression); |
| 768 environment.removeLast(); | 827 environment.removeLast(); |
| 769 | 828 |
| 770 return node; | 829 return node; |
| 771 } | 830 } |
| 772 | 831 |
| 832 Expression visitLogicalOperator(LogicalOperator node) { | |
| 833 node.left = visitExpression(node.left); | |
| 834 | |
| 835 environment.add(null); // impure expressions may not propagate across branch | |
| 836 node.right = visitExpression(node.right); | |
| 837 environment.removeLast(); | |
| 838 | |
| 839 return node; | |
| 840 } | |
| 841 | |
| 842 Expression visitNot(Not node) { | |
| 843 node.operand = visitExpression(node.operand); | |
| 844 return node; | |
| 845 } | |
| 846 | |
| 773 Statement visitReturn(Return node) { | 847 Statement visitReturn(Return node) { |
| 774 node.value = visitExpression(node.value); | 848 node.value = visitExpression(node.value); |
| 775 return node; | 849 return node; |
| 776 } | 850 } |
| 777 | 851 |
| 778 | 852 |
| 779 Statement visitBreak(Break node) { | 853 Statement visitBreak(Break node) { |
| 854 // Redirect through chain of breaks. | |
| 855 // Note that breakCount was accounted for at visitLabeledStatement. | |
| 856 node.target = redirect(node.target); | |
| 780 if (node.target.breakCount == 1) { | 857 if (node.target.breakCount == 1) { |
| 781 --node.target.breakCount; | 858 --node.target.breakCount; |
| 782 return visitStatement(node.target.binding.next); | 859 return visitStatement(node.target.binding.next); |
| 783 } | 860 } |
| 784 return node; | 861 return node; |
| 785 } | 862 } |
| 786 | 863 |
| 864 Statement visitLabeledStatement(LabeledStatement node) { | |
| 865 if (node.next is Break) { | |
| 866 // Eliminate label if next is just a break statement | |
| 867 // Breaks to this label are redirected to the outer label. | |
| 868 // Note that breakCount for the two labels is updated proactively here | |
| 869 // so breaks can reliably tell if they should inline their target. | |
| 870 Break next = node.next; | |
| 871 Label newTarget = labelRedirects[node.label] = redirect(next.target); | |
|
Kevin Millikin (Google)
2014/05/19 11:36:41
More readable:
Label newTarget = redirect(next.ta
asgerf
2014/05/19 13:29:07
Done.
| |
| 872 newTarget.breakCount += node.label.breakCount; | |
| 873 node.label.breakCount = 0; | |
| 874 Statement result = visitStatement(node.body); | |
| 875 labelRedirects.remove(node.label); // Save some space. | |
|
Kevin Millikin (Google)
2014/05/19 11:36:41
I wouldn't bother doing this here (or at all).
| |
| 876 return result; | |
| 877 } | |
| 878 | |
| 879 node.body = visitStatement(node.body); | |
| 880 | |
| 881 if (node.label.breakCount == 0) { | |
| 882 // Eliminate the label if next was inlined at a break | |
| 883 return node.body; | |
| 884 } | |
| 885 | |
| 886 node.next = visitStatement(node.next); | |
| 887 return node; | |
| 888 } | |
| 889 | |
| 787 Statement visitIf(If node) { | 890 Statement visitIf(If node) { |
| 788 node.condition = visitExpression(node.condition); | 891 node.condition = visitExpression(node.condition); |
| 789 | 892 |
| 790 environment.add(null); // impure expressions may not propagate across branch | 893 environment.add(null); // impure expressions may not propagate across branch |
| 791 node.thenStatement = visitStatement(node.thenStatement); | 894 node.thenStatement = visitStatement(node.thenStatement); |
| 792 node.elseStatement = visitStatement(node.elseStatement); | 895 node.elseStatement = visitStatement(node.elseStatement); |
| 793 environment.removeLast(); | 896 environment.removeLast(); |
| 794 | 897 |
| 898 // Repeatedly try to collapse nested ifs. | |
| 899 // The transformation is shrinking (destroys an if) so it remains linear. | |
| 900 // Here is an example where more than one iteration is required: | |
| 901 // | |
| 902 // if (E1) | |
| 903 // if (E2) break L2 else break L1 | |
| 904 // else | |
| 905 // break L1 | |
| 906 // | |
| 907 // L1.target ::= | |
| 908 // if (E3) S else break L2 | |
| 909 // | |
| 910 // After first collapse: | |
| 911 // | |
| 912 // if (E1 && E2) | |
| 913 // break L2 | |
| 914 // else | |
| 915 // {if (E3) S else break L2} (inlined from break L1) | |
| 916 // | |
| 917 // We can then do another collapse using the inlined nested if. | |
| 918 var changed = true; | |
| 919 while (changed) { | |
| 920 changed = false; | |
| 921 changed = changed || tryCollapseIf(node, true, true); | |
| 922 changed = changed || tryCollapseIf(node, true, false); | |
| 923 changed = changed || tryCollapseIf(node, false, true); | |
| 924 changed = changed || tryCollapseIf(node, false, false); | |
| 925 } | |
| 926 | |
| 795 Statement reduced = combineStatementsWithSubexpressions( | 927 Statement reduced = combineStatementsWithSubexpressions( |
| 796 node.thenStatement, | 928 node.thenStatement, |
| 797 node.elseStatement, | 929 node.elseStatement, |
| 798 (t,f) => new Conditional(node.condition, t, f)..processed = true); | 930 (t,f) => new Conditional(node.condition, t, f)..processed = true); |
| 799 if (reduced != null) { | 931 if (reduced != null) { |
| 800 if (reduced.next is Break) { | 932 if (reduced.next is Break) { |
| 801 // In case the break can now be inlined. | 933 // In case the break can now be inlined. |
| 802 reduced = visitStatement(reduced); | 934 reduced = visitStatement(reduced); |
| 803 } | 935 } |
| 804 return reduced; | 936 return reduced; |
| (...skipping 11 matching lines...) Expand all Loading... | |
| 816 if (!node.expression.isPure) { | 948 if (!node.expression.isPure) { |
| 817 environment.add(null); // insert impurity marker (TODO: refactor) | 949 environment.add(null); // insert impurity marker (TODO: refactor) |
| 818 } | 950 } |
| 819 node.next = visitStatement(node.next); | 951 node.next = visitStatement(node.next); |
| 820 if (!node.expression.isPure) { | 952 if (!node.expression.isPure) { |
| 821 environment.removeLast(); | 953 environment.removeLast(); |
| 822 } | 954 } |
| 823 return node; | 955 return node; |
| 824 } | 956 } |
| 825 | 957 |
| 826 | |
| 827 /// If [s] and [t] are similar statements we extract their subexpressions | 958 /// If [s] and [t] are similar statements we extract their subexpressions |
| 828 /// and returns a new statement of the same type using expressions combined | 959 /// and returns a new statement of the same type using expressions combined |
| 829 /// with the [combine] callback. For example: | 960 /// with the [combine] callback. For example: |
| 830 /// | 961 /// |
| 831 /// combineStatements(Return E1, Return E2) = Return combine(E1, E2) | 962 /// combineStatements(Return E1, Return E2) = Return combine(E1, E2) |
| 832 /// | 963 /// |
| 833 /// If [combine] returns E1 then the unified statement is equivalent to [s], | 964 /// If [combine] returns E1 then the unified statement is equivalent to [s], |
| 834 /// and if [combine] returns E2 the unified statement is equivalence to [t]. | 965 /// and if [combine] returns E2 the unified statement is equivalence to [t]. |
| 835 /// | 966 /// |
| 836 /// It is guaranteed that no side effects occur between the beginning of the | 967 /// It is guaranteed that no side effects occur between the beginning of the |
| (...skipping 51 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 888 if (e1 == e2) { // Detect same variable reference | 1019 if (e1 == e2) { // Detect same variable reference |
| 889 // TODO(asgerf): This might turn the variable into a single-use, | 1020 // TODO(asgerf): This might turn the variable into a single-use, |
| 890 // but we currently don't discover this. | 1021 // but we currently don't discover this. |
| 891 return true; | 1022 return true; |
| 892 } | 1023 } |
| 893 if (e1 is Constant && e2 is Constant) { | 1024 if (e1 is Constant && e2 is Constant) { |
| 894 return e1.value == e2.value; | 1025 return e1.value == e2.value; |
| 895 } | 1026 } |
| 896 return false; | 1027 return false; |
| 897 } | 1028 } |
| 1029 | |
| 1030 /// Try to collapse nested ifs using && and || expressions. | |
| 1031 /// For example: | |
| 1032 /// | |
| 1033 /// if (E1) { if (E2) S else break L } else break L | |
| 1034 /// ==> | |
| 1035 /// if (E1 && E2) S else break L | |
| 1036 /// | |
| 1037 /// [branch1] and [branch2] control the position of the S statement. | |
| 1038 /// | |
| 1039 /// Returns true if another collapse redex might have been introduced. | |
| 1040 bool tryCollapseIf(If outerIf, bool branch1, bool branch2) { | |
|
Kevin Millikin (Google)
2014/05/19 11:36:41
I don't think the caller should have to enumerate
| |
| 1041 // NOTE: We name variables here as if S is in the then-then position. | |
| 1042 Statement outerThen = getBranch(outerIf, branch1); | |
| 1043 Statement outerElse = getBranch(outerIf, !branch1); | |
| 1044 if (outerThen is If && outerElse is Break) { | |
| 1045 If innerIf = outerThen; | |
| 1046 Statement innerThen = getBranch(innerIf, branch2); | |
| 1047 Statement innerElse = getBranch(innerIf, !branch2); | |
| 1048 if (innerElse is Break && innerElse.target == outerElse.target) { | |
| 1049 // We always put S in the then branch of the result, and adjust the | |
| 1050 // condition expression if S was actually found in the else branch(es). | |
| 1051 outerIf.condition = new LogicalOperator.and( | |
| 1052 makeCondition(outerIf.condition, branch1), | |
| 1053 makeCondition(innerIf.condition, branch2)); | |
| 1054 outerIf.thenStatement = innerThen; | |
| 1055 --innerElse.target.breakCount; | |
| 1056 | |
| 1057 // Try to inline the remaining break | |
| 1058 environment.add(null); // Do not propagate impure definitions | |
| 1059 outerIf.elseStatement = visitStatement(outerElse); | |
| 1060 environment.removeLast(); | |
| 1061 | |
| 1062 return outerIf.elseStatement is If && innerThen is Break; | |
| 1063 } | |
| 1064 } | |
| 1065 return false; | |
| 1066 } | |
| 1067 | |
| 1068 Expression makeCondition(Expression e, bool polarity) { | |
| 1069 return polarity ? e : new Not(e); | |
| 1070 } | |
| 1071 | |
| 1072 Statement getBranch(If node, bool polarity) { | |
| 1073 return polarity ? node.thenStatement : node.elseStatement; | |
| 1074 } | |
| 898 } | 1075 } |
| 899 | 1076 |
| 1077 | |
| 1078 | |
| 1079 /// Rewrites logical expressions to be more compact. | |
| 1080 /// | |
| 1081 /// In this class an expression is said to occur in "boolean context" if | |
| 1082 /// its result is immediately applied to boolean conversion. | |
| 1083 /// | |
| 1084 /// IF STATEMENTS: | |
| 1085 /// | |
| 1086 /// We apply the following two rule to [If] statements (see [visitIf]). | |
|
Kevin Millikin (Google)
2014/05/19 11:36:41
two rule ==> rules
| |
| 1087 /// | |
| 1088 /// if (E) {} else S ==> if (!E) S else {} (else can be omitted) | |
| 1089 /// if (!E) S1 else S2 ==> if (E) S2 else S1 (unless previous rule applied) | |
| 1090 /// | |
| 1091 /// NEGATION: | |
| 1092 /// | |
| 1093 /// De Morgan's Laws are used to rewrite negations of logical operators so | |
| 1094 /// negations are closer to the root: | |
| 1095 /// | |
| 1096 /// !x && !y --> !(x || y) | |
| 1097 /// | |
| 1098 /// This is to enable other rewrites, such branch swapping in an if. In some | |
|
Kevin Millikin (Google)
2014/05/19 11:36:41
such branch ==> such as branch
| |
| 1099 /// contexts, the rule is reversed because we do not expect to apply a rewrite | |
| 1100 /// rule to the result. For example: | |
| 1101 /// | |
| 1102 /// z = !(x || y) ==> z = !x && !y; | |
| 1103 /// | |
| 1104 /// CONDITIONALS: | |
| 1105 /// | |
| 1106 /// Conditionals with boolean constant operands occur frequently in the input. | |
| 1107 /// They can often the re-written to logical operators, for instance: | |
| 1108 /// | |
| 1109 /// if (x ? y : false) S1 else S2 | |
| 1110 /// ==> | |
| 1111 /// if (x && y) S1 else S2 | |
| 1112 /// | |
| 1113 /// Conditionals are tricky to rewrite when they occur out of boolean context. | |
| 1114 /// Here we must apply more conservative rules, such as: | |
| 1115 /// | |
| 1116 /// x ? true : false ==> !!x | |
| 1117 /// | |
| 1118 /// If an operand is known to be a boolean, we can introduce a logical operator: | |
| 1119 /// | |
| 1120 /// x ? y : false ==> x && y (if y is known to be a boolean) | |
| 1121 /// | |
| 1122 /// The following sequence of rewrites demonstrates the merit of these rules: | |
| 1123 /// | |
| 1124 /// x ? (y ? true : false) : false | |
| 1125 /// x ? !!y : false (double negation introduced by [toBoolean]) | |
| 1126 /// x && !!y (!!y validated by [isBooleanValued]) | |
| 1127 /// x && y (double negation removed by [putInBooleanContext]) | |
| 1128 /// | |
| 1129 class LogicalRewriter extends Visitor<Statement, Expression> { | |
| 1130 | |
| 1131 /// Statement to be executed next by natural fallthrough. Although fallthrough | |
| 1132 /// is not introduced in this phase, we need to reason about fallthrough when | |
| 1133 /// evaluating the benefit of swapping the branches of an [If]. | |
| 1134 Statement fallthrough; | |
| 1135 | |
| 1136 void rewrite(FunctionDefinition definition) { | |
| 1137 definition.body = visitStatement(definition.body); | |
| 1138 } | |
| 1139 | |
| 1140 Statement visitLabeledStatement(LabeledStatement node) { | |
| 1141 Statement savedFallthrough = fallthrough; | |
| 1142 fallthrough = node.next; | |
| 1143 node.body = visitStatement(node.body); | |
| 1144 fallthrough = savedFallthrough; | |
| 1145 node.next = visitStatement(node.next); | |
| 1146 return node; | |
| 1147 } | |
| 1148 | |
| 1149 Statement visitAssign(Assign node) { | |
| 1150 node.definition = visitExpression(node.definition); | |
| 1151 node.next = visitStatement(node.next); | |
| 1152 return node; | |
| 1153 } | |
| 1154 | |
| 1155 Statement visitReturn(Return node) { | |
| 1156 node.value = visitExpression(node.value); | |
| 1157 return node; | |
| 1158 } | |
| 1159 | |
| 1160 Statement visitBreak(Break node) { | |
| 1161 return node; | |
| 1162 } | |
| 1163 | |
| 1164 bool isFallthroughBreak(Statement node) { | |
| 1165 return node is Break && node.target.binding.next == fallthrough; | |
| 1166 } | |
| 1167 | |
| 1168 Statement visitIf(If node) { | |
| 1169 // If one of the branches is empty (i.e. just a fallthrough), then that | |
| 1170 // branch should preferrably be the 'else' so we won't have to print it. | |
| 1171 // In other words, we wish to perform this rewrite: | |
| 1172 // if (E) {} else {S} | |
| 1173 // ==> | |
| 1174 // if (!E) {S} | |
| 1175 // In the tree language, empty statements do not exist yet, so we must check | |
| 1176 // if one branch contains a break that can be eliminated by fallthrough. | |
| 1177 | |
| 1178 // Swap branches if then is a fallthrough break. | |
| 1179 if (isFallthroughBreak(node.thenStatement)) { | |
| 1180 node.condition = new Not(node.condition); | |
| 1181 Statement tmp = node.thenStatement; | |
| 1182 node.thenStatement = node.elseStatement; | |
| 1183 node.elseStatement = tmp; | |
| 1184 } | |
| 1185 | |
| 1186 // Can the else part be eliminated? | |
| 1187 // (Either due to the above swap or if the break was already there). | |
| 1188 bool emptyElse = isFallthroughBreak(node.elseStatement); | |
| 1189 | |
| 1190 node.condition = makeCondition(node.condition, true, liftNots: !emptyElse); | |
| 1191 node.thenStatement = visitStatement(node.thenStatement); | |
| 1192 node.elseStatement = visitStatement(node.elseStatement); | |
| 1193 | |
| 1194 // If neither branch is empty, eliminate a negation in the condition | |
| 1195 // if (!E) S1 else S2 | |
| 1196 // ==> | |
| 1197 // if (E) S2 else S1 | |
| 1198 if (!emptyElse && node.condition is Not) { | |
| 1199 node.condition = (node.condition as Not).operand; | |
| 1200 Statement tmp = node.thenStatement; | |
| 1201 node.thenStatement = node.elseStatement; | |
| 1202 node.elseStatement = tmp; | |
| 1203 } | |
| 1204 | |
| 1205 return node; | |
| 1206 } | |
| 1207 | |
| 1208 Statement visitExpressionStatement(ExpressionStatement node) { | |
| 1209 // TODO(asgerf): in non-checked mode we can remove Not from the expression. | |
| 1210 node.expression = visitExpression(node.expression); | |
| 1211 node.next = visitStatement(node.next); | |
| 1212 return node; | |
| 1213 } | |
| 1214 | |
| 1215 | |
| 1216 Expression visitVariable(Variable node) { | |
| 1217 return node; | |
| 1218 } | |
| 1219 | |
| 1220 Expression visitInvokeStatic(InvokeStatic node) { | |
| 1221 for (int i=0; i<node.arguments.length; i++) { | |
|
Kevin Millikin (Google)
2014/05/19 11:36:41
Spaces around = and <. Also below.
Maybe we shou
asgerf
2014/05/19 13:29:07
Good idea. I'll look at that in another CL.
| |
| 1222 node.arguments[i] = visitExpression(node.arguments[i]); | |
| 1223 } | |
| 1224 return node; | |
| 1225 } | |
| 1226 | |
| 1227 Expression visitInvokeMethod(InvokeMethod node) { | |
| 1228 node.receiver = visitExpression(node.receiver); | |
| 1229 for (int i=0; i<node.arguments.length; i++) { | |
| 1230 node.arguments[i] = visitExpression(node.arguments[i]); | |
| 1231 } | |
| 1232 return node; | |
| 1233 } | |
| 1234 | |
| 1235 Expression visitInvokeConstructor(InvokeConstructor node) { | |
| 1236 for (int i=0; i<node.arguments.length; i++) { | |
| 1237 node.arguments[i] = visitExpression(node.arguments[i]); | |
| 1238 } | |
| 1239 return node; | |
| 1240 } | |
| 1241 | |
| 1242 Expression visitConcatenateStrings(ConcatenateStrings node) { | |
| 1243 for (int i=0; i<node.arguments.length; i++) { | |
| 1244 node.arguments[i] = visitExpression(node.arguments[i]); | |
| 1245 } | |
| 1246 return node; | |
| 1247 } | |
| 1248 | |
| 1249 Expression visitConstant(Constant node) { | |
| 1250 return node; | |
| 1251 } | |
| 1252 | |
| 1253 Expression visitNot(Not node) { | |
| 1254 return toBoolean(makeCondition(node.operand, false, liftNots: false)); | |
| 1255 } | |
| 1256 | |
| 1257 Expression visitConditional(Conditional node) { | |
| 1258 // node.condition will be visited after the then and else parts, because its | |
| 1259 // polarity depends on what rewrite we use. | |
| 1260 node.thenExpression = visitExpression(node.thenExpression); | |
| 1261 node.elseExpression = visitExpression(node.elseExpression); | |
| 1262 | |
| 1263 // In the following, we must take care not to eliminate or introduce a | |
| 1264 // boolean conversion. | |
| 1265 | |
| 1266 // x ? true : false --> !!x | |
| 1267 if (isTrue(node.thenExpression) && isFalse(node.elseExpression)) { | |
| 1268 return toBoolean(makeCondition(node.condition, true, liftNots: false)); | |
| 1269 } | |
| 1270 // x ? false : true --> !x | |
| 1271 if (isFalse(node.thenExpression) && isTrue(node.elseExpression)) { | |
| 1272 return toBoolean(makeCondition(node.condition, false, liftNots: false)); | |
| 1273 } | |
| 1274 | |
| 1275 // x ? y : false ==> x && y (if y is known to be a boolean) | |
| 1276 if (isBooleanValued(node.thenExpression) && isFalse(node.elseExpression)) { | |
| 1277 return new LogicalOperator.and( | |
| 1278 makeCondition(node.condition, true, liftNots:false), | |
| 1279 putInBooleanContext(node.thenExpression)); | |
| 1280 } | |
| 1281 // x ? y : true ==> !x || y (if y is known to be a boolean) | |
| 1282 if (isBooleanValued(node.thenExpression) && isTrue(node.elseExpression)) { | |
| 1283 return new LogicalOperator.or( | |
| 1284 makeCondition(node.condition, false, liftNots: false), | |
| 1285 putInBooleanContext(node.thenExpression)); | |
| 1286 } | |
| 1287 // x ? true : y ==> x || y (if y if known to be boolean) | |
| 1288 if (isBooleanValued(node.elseExpression) && isTrue(node.thenExpression)) { | |
| 1289 return new LogicalOperator.or( | |
| 1290 makeCondition(node.condition, true, liftNots: false), | |
| 1291 putInBooleanContext(node.elseExpression)); | |
| 1292 } | |
| 1293 // x ? false : y ==> !x && y (if y is known to be a boolean) | |
| 1294 if (isBooleanValued(node.elseExpression) && isTrue(node.thenExpression)) { | |
| 1295 return new LogicalOperator.and( | |
| 1296 makeCondition(node.condition, false, liftNots: false), | |
| 1297 putInBooleanContext(node.elseExpression)); | |
| 1298 } | |
| 1299 | |
| 1300 node.condition = makeCondition(node.condition, true); | |
| 1301 | |
| 1302 // !x ? y : z ==> x ? z : y | |
| 1303 if (node.condition is Not) { | |
| 1304 node.condition = (node.condition as Not).operand; | |
| 1305 Expression tmp = node.thenExpression; | |
| 1306 node.thenExpression = node.elseExpression; | |
| 1307 node.elseExpression = tmp; | |
| 1308 } | |
| 1309 | |
| 1310 return node; | |
| 1311 } | |
| 1312 Expression visitLogicalOperator(LogicalOperator node) { | |
|
Kevin Millikin (Google)
2014/05/19 11:36:41
Blank line between methods.
| |
| 1313 node.left = makeCondition(node.left, true); | |
| 1314 node.right = makeCondition(node.right, true); | |
| 1315 return node; | |
| 1316 } | |
| 1317 | |
| 1318 /// True if the given expression is known to evaluate to a boolean. | |
| 1319 /// This will not recursively traverse [Conditional] expressions, but if | |
| 1320 /// applied to the result of [visitExpression] conditionals will have been | |
| 1321 /// rewritten anyway. | |
| 1322 bool isBooleanValued(Expression e) { | |
| 1323 return isTrue(e) || isFalse(e) || e is Not || e is LogicalOperator; | |
| 1324 } | |
| 1325 | |
| 1326 /// Rewrite an expression that was originally processed in a non-boolean | |
| 1327 /// context. | |
| 1328 Expression putInBooleanContext(Expression e) { | |
| 1329 if (e is Not && e.operand is Not) { | |
| 1330 return (e.operand as Not).operand; | |
| 1331 } else { | |
| 1332 return e; | |
| 1333 } | |
| 1334 } | |
| 1335 | |
| 1336 /// Forces a boolean conversion of the given expression. | |
| 1337 Expression toBoolean(Expression e) { | |
| 1338 if (isBooleanValued(e)) | |
| 1339 return e; | |
| 1340 else | |
| 1341 return new Not(new Not(e)); | |
| 1342 } | |
| 1343 | |
| 1344 /// Creates an equivalent boolean expression. The expression must occur in a | |
|
Kevin Millikin (Google)
2014/05/19 11:36:41
equivalent ==> equivalent simplified, perhaps?
Re
| |
| 1345 /// context where its result is immediately subject to boolean conversion. | |
| 1346 /// If [polarity] if false, the negated condition will be created instead. | |
| 1347 /// If [liftNots] is true (default) then Not expressions will be lifted toward | |
| 1348 /// the root the condition so they can be eliminated by the caller. | |
| 1349 Expression makeCondition(Expression e, bool polarity, {bool liftNots:true}) { | |
| 1350 if (e is Not) { | |
| 1351 // !!E ==> E | |
| 1352 return makeCondition(e.operand, !polarity, liftNots: liftNots); | |
| 1353 } | |
| 1354 if (e is LogicalOperator) { | |
| 1355 // If polarity=false, then apply the rewrite !(x && y) ==> !x || !y | |
| 1356 e.left = makeCondition(e.left, polarity); | |
| 1357 e.right = makeCondition(e.right, polarity); | |
| 1358 if (!polarity) { | |
| 1359 e.isAnd = !e.isAnd; | |
| 1360 } | |
| 1361 // !x && !y ==> !(x || y) (only if lifting nots) | |
| 1362 if (e.left is Not && e.right is Not && liftNots) { | |
| 1363 e.left = (e.left as Not).operand; | |
| 1364 e.right = (e.right as Not).operand; | |
| 1365 e.isAnd = !e.isAnd; | |
| 1366 return new Not(e); | |
| 1367 } | |
| 1368 return e; | |
| 1369 } | |
| 1370 if (e is Conditional) { | |
| 1371 // x ? true : false ==> x | |
| 1372 if (isTrue(e.thenExpression) && isFalse(e.elseExpression)) { | |
| 1373 return makeCondition(e.condition, polarity); | |
| 1374 } | |
| 1375 // x ? false : true ==> !x | |
| 1376 if (isFalse(e.thenExpression) && isTrue(e.elseExpression)) { | |
| 1377 return makeCondition(e.condition, !polarity); | |
| 1378 } | |
| 1379 // x ? true : y ==> x || y | |
|
Kevin Millikin (Google)
2014/05/19 11:36:41
I could well be missing something here, but I thin
| |
| 1380 if (isTrue(e.thenExpression)) { | |
| 1381 return new LogicalOperator.or( | |
| 1382 makeCondition(e.condition, polarity), | |
| 1383 makeCondition(e.elseExpression, polarity)); | |
| 1384 } | |
| 1385 // x ? false : y ==> !x && y | |
|
Kevin Millikin (Google)
2014/05/19 11:36:41
Likewise, if !polarity this should be ||. That is
| |
| 1386 if (isFalse(e.thenExpression)) { | |
| 1387 return new LogicalOperator.and( | |
| 1388 makeCondition(e.condition, !polarity), | |
| 1389 makeCondition(e.elseExpression, polarity)); | |
| 1390 } | |
| 1391 // x ? y : true ==> !x || y | |
| 1392 if (isTrue(e.elseExpression)) { | |
| 1393 return new LogicalOperator.or( | |
| 1394 makeCondition(e.condition, !polarity), | |
| 1395 makeCondition(e.thenExpression, polarity)); | |
| 1396 } | |
| 1397 // x ? y : false ==> x && y | |
| 1398 if (isFalse(e.elseExpression)) { | |
| 1399 return new LogicalOperator.and( | |
| 1400 makeCondition(e.condition, polarity), | |
| 1401 makeCondition(e.thenExpression, polarity)); | |
| 1402 } | |
| 1403 | |
| 1404 // Rewrite individual subconditions | |
| 1405 // Handle polarity by: !(x ? y : z) ==> x ? !y : !z | |
| 1406 e.condition = makeCondition(e.condition, true); | |
| 1407 e.thenExpression = makeCondition(e.thenExpression, polarity); | |
| 1408 e.elseExpression = makeCondition(e.elseExpression, polarity); | |
| 1409 | |
| 1410 // !x ? y : z ==> x ? z : y | |
| 1411 if (e.condition is Not) { | |
| 1412 e.condition = (e.condition as Not).operand; | |
| 1413 Expression tmp = e.thenExpression; | |
| 1414 e.thenExpression = e.elseExpression; | |
| 1415 e.elseExpression = tmp; | |
| 1416 } | |
| 1417 // x ? !y : !z ==> !(x ? y : z) (only if lifting nots) | |
| 1418 if (e.thenExpression is Not && e.elseExpression is Not && liftNots) { | |
| 1419 e.thenExpression = (e.thenExpression as Not).operand; | |
| 1420 e.elseExpression = (e.elseExpression as Not).operand; | |
| 1421 return new Not(e); | |
| 1422 } | |
| 1423 return e; | |
| 1424 } | |
| 1425 if (e is Constant && e.value is dart2js.BoolConstant) { | |
| 1426 // !true ==> false | |
| 1427 if (!polarity) { | |
| 1428 e.value = (e.value as dart2js.BoolConstant).negate(); | |
| 1429 } | |
| 1430 return e; | |
| 1431 } | |
| 1432 e = visitExpression(e); | |
| 1433 return polarity ? e : new Not(e); | |
| 1434 } | |
| 1435 | |
| 1436 bool isTrue(Expression e) { | |
| 1437 return e is Constant && e.value is dart2js.TrueConstant; | |
| 1438 } | |
| 1439 | |
| 1440 bool isFalse(Expression e) { | |
| 1441 return e is Constant && e.value is dart2js.FalseConstant; | |
| 1442 } | |
| 1443 | |
| 1444 } | |
| OLD | NEW |