| 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 26 matching lines...) Expand all Loading... |
| 37 abstract class Node { | 37 abstract class Node { |
| 38 } | 38 } |
| 39 | 39 |
| 40 /** | 40 /** |
| 41 * The base class of [Expression]s. | 41 * The base class of [Expression]s. |
| 42 */ | 42 */ |
| 43 abstract class Expression extends Node { | 43 abstract class Expression extends Node { |
| 44 bool get isPure; | 44 bool get isPure; |
| 45 accept(Visitor v); | 45 accept(Visitor v); |
| 46 | 46 |
| 47 /// Temporary variable used by [TreeRewriter]. | 47 /// Temporary variable used by [StatementRewriter]. |
| 48 /// If set to true, this expression has already had enclosing assignments | 48 /// If set to true, this expression has already had enclosing assignments |
| 49 /// propagated into its variables, and should not be processed again. | 49 /// propagated into its variables, and should not be processed again. |
| 50 /// It is only set for expressions that are known to be in risk of redundant | 50 /// It is only set for expressions that are known to be in risk of redundant |
| 51 /// processing. | 51 /// processing. |
| 52 bool processed = false; | 52 bool processed = false; |
| 53 } | 53 } |
| 54 | 54 |
| 55 abstract class Statement extends Node { | 55 abstract class Statement extends Node { |
| 56 Statement get next; | 56 Statement get next; |
| 57 void set next(Statement s); | 57 void set next(Statement s); |
| (...skipping 120 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 transformations on the tree: |
| 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 all eliminate statements from the tree, and may |
| 597 * transformation can introduce redexes of one of the others. | 630 * introduce redexes of each other. |
| 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; } |
| 605 * ==> | 638 * ==> |
| 606 * return foo() | 639 * return foo() |
| 607 * | 640 * |
| (...skipping 21 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 StatementRewriter 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>{}; |
| 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 = redirect(next.target); |
| 872 labelRedirects[node.label] = newTarget; |
| 873 newTarget.breakCount += node.label.breakCount; |
| 874 node.label.breakCount = 0; |
| 875 Statement result = visitStatement(node.body); |
| 876 labelRedirects.remove(node.label); // Save some space. |
| 877 return result; |
| 878 } |
| 879 |
| 880 node.body = visitStatement(node.body); |
| 881 |
| 882 if (node.label.breakCount == 0) { |
| 883 // Eliminate the label if next was inlined at a break |
| 884 return node.body; |
| 885 } |
| 886 |
| 887 node.next = visitStatement(node.next); |
| 888 return node; |
| 889 } |
| 890 |
| 787 Statement visitIf(If node) { | 891 Statement visitIf(If node) { |
| 788 node.condition = visitExpression(node.condition); | 892 node.condition = visitExpression(node.condition); |
| 789 | 893 |
| 790 environment.add(null); // impure expressions may not propagate across branch | 894 environment.add(null); // impure expressions may not propagate across branch |
| 791 node.thenStatement = visitStatement(node.thenStatement); | 895 node.thenStatement = visitStatement(node.thenStatement); |
| 792 node.elseStatement = visitStatement(node.elseStatement); | 896 node.elseStatement = visitStatement(node.elseStatement); |
| 793 environment.removeLast(); | 897 environment.removeLast(); |
| 794 | 898 |
| 899 tryCollapseIf(node); |
| 900 |
| 795 Statement reduced = combineStatementsWithSubexpressions( | 901 Statement reduced = combineStatementsWithSubexpressions( |
| 796 node.thenStatement, | 902 node.thenStatement, |
| 797 node.elseStatement, | 903 node.elseStatement, |
| 798 (t,f) => new Conditional(node.condition, t, f)..processed = true); | 904 (t,f) => new Conditional(node.condition, t, f)..processed = true); |
| 799 if (reduced != null) { | 905 if (reduced != null) { |
| 800 if (reduced.next is Break) { | 906 if (reduced.next is Break) { |
| 801 // In case the break can now be inlined. | 907 // In case the break can now be inlined. |
| 802 reduced = visitStatement(reduced); | 908 reduced = visitStatement(reduced); |
| 803 } | 909 } |
| 804 return reduced; | 910 return reduced; |
| (...skipping 11 matching lines...) Expand all Loading... |
| 816 if (!node.expression.isPure) { | 922 if (!node.expression.isPure) { |
| 817 environment.add(null); // insert impurity marker (TODO: refactor) | 923 environment.add(null); // insert impurity marker (TODO: refactor) |
| 818 } | 924 } |
| 819 node.next = visitStatement(node.next); | 925 node.next = visitStatement(node.next); |
| 820 if (!node.expression.isPure) { | 926 if (!node.expression.isPure) { |
| 821 environment.removeLast(); | 927 environment.removeLast(); |
| 822 } | 928 } |
| 823 return node; | 929 return node; |
| 824 } | 930 } |
| 825 | 931 |
| 826 | |
| 827 /// If [s] and [t] are similar statements we extract their subexpressions | 932 /// If [s] and [t] are similar statements we extract their subexpressions |
| 828 /// and returns a new statement of the same type using expressions combined | 933 /// and returns a new statement of the same type using expressions combined |
| 829 /// with the [combine] callback. For example: | 934 /// with the [combine] callback. For example: |
| 830 /// | 935 /// |
| 831 /// combineStatements(Return E1, Return E2) = Return combine(E1, E2) | 936 /// combineStatements(Return E1, Return E2) = Return combine(E1, E2) |
| 832 /// | 937 /// |
| 833 /// If [combine] returns E1 then the unified statement is equivalent to [s], | 938 /// 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]. | 939 /// and if [combine] returns E2 the unified statement is equivalence to [t]. |
| 835 /// | 940 /// |
| 836 /// It is guaranteed that no side effects occur between the beginning of the | 941 /// 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 | 993 if (e1 == e2) { // Detect same variable reference |
| 889 // TODO(asgerf): This might turn the variable into a single-use, | 994 // TODO(asgerf): This might turn the variable into a single-use, |
| 890 // but we currently don't discover this. | 995 // but we currently don't discover this. |
| 891 return true; | 996 return true; |
| 892 } | 997 } |
| 893 if (e1 is Constant && e2 is Constant) { | 998 if (e1 is Constant && e2 is Constant) { |
| 894 return e1.value == e2.value; | 999 return e1.value == e2.value; |
| 895 } | 1000 } |
| 896 return false; | 1001 return false; |
| 897 } | 1002 } |
| 1003 |
| 1004 /// Try to collapse nested ifs using && and || expressions. |
| 1005 /// For example: |
| 1006 /// |
| 1007 /// if (E1) { if (E2) S else break L } else break L |
| 1008 /// ==> |
| 1009 /// if (E1 && E2) S else break L |
| 1010 /// |
| 1011 /// [branch1] and [branch2] control the position of the S statement. |
| 1012 /// |
| 1013 /// Returns true if another collapse redex might have been introduced. |
| 1014 void tryCollapseIf(If node) { |
| 1015 // Repeatedly try to collapse nested ifs. |
| 1016 // The transformation is shrinking (destroys an if) so it remains linear. |
| 1017 // Here is an example where more than one iteration is required: |
| 1018 // |
| 1019 // if (E1) |
| 1020 // if (E2) break L2 else break L1 |
| 1021 // else |
| 1022 // break L1 |
| 1023 // |
| 1024 // L1.target ::= |
| 1025 // if (E3) S else break L2 |
| 1026 // |
| 1027 // After first collapse: |
| 1028 // |
| 1029 // if (E1 && E2) |
| 1030 // break L2 |
| 1031 // else |
| 1032 // {if (E3) S else break L2} (inlined from break L1) |
| 1033 // |
| 1034 // We can then do another collapse using the inlined nested if. |
| 1035 bool changed = true; |
| 1036 while (changed) { |
| 1037 changed = false; |
| 1038 if (tryCollapseIfAux(node, true, true)) { |
| 1039 changed = true; |
| 1040 } |
| 1041 if (tryCollapseIfAux(node, true, false)) { |
| 1042 changed = true; |
| 1043 } |
| 1044 if (tryCollapseIfAux(node, false, true)) { |
| 1045 changed = true; |
| 1046 } |
| 1047 if (tryCollapseIfAux(node, false, false)) { |
| 1048 changed = true; |
| 1049 } |
| 1050 } |
| 1051 } |
| 1052 |
| 1053 bool tryCollapseIfAux(If outerIf, bool branch1, bool branch2) { |
| 1054 // NOTE: We name variables here as if S is in the then-then position. |
| 1055 Statement outerThen = getBranch(outerIf, branch1); |
| 1056 Statement outerElse = getBranch(outerIf, !branch1); |
| 1057 if (outerThen is If && outerElse is Break) { |
| 1058 If innerIf = outerThen; |
| 1059 Statement innerThen = getBranch(innerIf, branch2); |
| 1060 Statement innerElse = getBranch(innerIf, !branch2); |
| 1061 if (innerElse is Break && innerElse.target == outerElse.target) { |
| 1062 // We always put S in the then branch of the result, and adjust the |
| 1063 // condition expression if S was actually found in the else branch(es). |
| 1064 outerIf.condition = new LogicalOperator.and( |
| 1065 makeCondition(outerIf.condition, branch1), |
| 1066 makeCondition(innerIf.condition, branch2)); |
| 1067 outerIf.thenStatement = innerThen; |
| 1068 --innerElse.target.breakCount; |
| 1069 |
| 1070 // Try to inline the remaining break |
| 1071 environment.add(null); // Do not propagate impure definitions |
| 1072 outerIf.elseStatement = visitStatement(outerElse); |
| 1073 environment.removeLast(); |
| 1074 |
| 1075 return outerIf.elseStatement is If && innerThen is Break; |
| 1076 } |
| 1077 } |
| 1078 return false; |
| 1079 } |
| 1080 |
| 1081 Expression makeCondition(Expression e, bool polarity) { |
| 1082 return polarity ? e : new Not(e); |
| 1083 } |
| 1084 |
| 1085 Statement getBranch(If node, bool polarity) { |
| 1086 return polarity ? node.thenStatement : node.elseStatement; |
| 1087 } |
| 898 } | 1088 } |
| 899 | 1089 |
| 1090 |
| 1091 |
| 1092 /// Rewrites logical expressions to be more compact. |
| 1093 /// |
| 1094 /// In this class an expression is said to occur in "boolean context" if |
| 1095 /// its result is immediately applied to boolean conversion. |
| 1096 /// |
| 1097 /// IF STATEMENTS: |
| 1098 /// |
| 1099 /// We apply the following two rules to [If] statements (see [visitIf]). |
| 1100 /// |
| 1101 /// if (E) {} else S ==> if (!E) S else {} (else can be omitted) |
| 1102 /// if (!E) S1 else S2 ==> if (E) S2 else S1 (unless previous rule applied) |
| 1103 /// |
| 1104 /// NEGATION: |
| 1105 /// |
| 1106 /// De Morgan's Laws are used to rewrite negations of logical operators so |
| 1107 /// negations are closer to the root: |
| 1108 /// |
| 1109 /// !x && !y --> !(x || y) |
| 1110 /// |
| 1111 /// This is to enable other rewrites, such as branch swapping in an if. In some |
| 1112 /// contexts, the rule is reversed because we do not expect to apply a rewrite |
| 1113 /// rule to the result. For example: |
| 1114 /// |
| 1115 /// z = !(x || y) ==> z = !x && !y; |
| 1116 /// |
| 1117 /// CONDITIONALS: |
| 1118 /// |
| 1119 /// Conditionals with boolean constant operands occur frequently in the input. |
| 1120 /// They can often the re-written to logical operators, for instance: |
| 1121 /// |
| 1122 /// if (x ? y : false) S1 else S2 |
| 1123 /// ==> |
| 1124 /// if (x && y) S1 else S2 |
| 1125 /// |
| 1126 /// Conditionals are tricky to rewrite when they occur out of boolean context. |
| 1127 /// Here we must apply more conservative rules, such as: |
| 1128 /// |
| 1129 /// x ? true : false ==> !!x |
| 1130 /// |
| 1131 /// If an operand is known to be a boolean, we can introduce a logical operator: |
| 1132 /// |
| 1133 /// x ? y : false ==> x && y (if y is known to be a boolean) |
| 1134 /// |
| 1135 /// The following sequence of rewrites demonstrates the merit of these rules: |
| 1136 /// |
| 1137 /// x ? (y ? true : false) : false |
| 1138 /// x ? !!y : false (double negation introduced by [toBoolean]) |
| 1139 /// x && !!y (!!y validated by [isBooleanValued]) |
| 1140 /// x && y (double negation removed by [putInBooleanContext]) |
| 1141 /// |
| 1142 class LogicalRewriter extends Visitor<Statement, Expression> { |
| 1143 |
| 1144 /// Statement to be executed next by natural fallthrough. Although fallthrough |
| 1145 /// is not introduced in this phase, we need to reason about fallthrough when |
| 1146 /// evaluating the benefit of swapping the branches of an [If]. |
| 1147 Statement fallthrough; |
| 1148 |
| 1149 void rewrite(FunctionDefinition definition) { |
| 1150 definition.body = visitStatement(definition.body); |
| 1151 } |
| 1152 |
| 1153 Statement visitLabeledStatement(LabeledStatement node) { |
| 1154 Statement savedFallthrough = fallthrough; |
| 1155 fallthrough = node.next; |
| 1156 node.body = visitStatement(node.body); |
| 1157 fallthrough = savedFallthrough; |
| 1158 node.next = visitStatement(node.next); |
| 1159 return node; |
| 1160 } |
| 1161 |
| 1162 Statement visitAssign(Assign node) { |
| 1163 node.definition = visitExpression(node.definition); |
| 1164 node.next = visitStatement(node.next); |
| 1165 return node; |
| 1166 } |
| 1167 |
| 1168 Statement visitReturn(Return node) { |
| 1169 node.value = visitExpression(node.value); |
| 1170 return node; |
| 1171 } |
| 1172 |
| 1173 Statement visitBreak(Break node) { |
| 1174 return node; |
| 1175 } |
| 1176 |
| 1177 bool isFallthroughBreak(Statement node) { |
| 1178 return node is Break && node.target.binding.next == fallthrough; |
| 1179 } |
| 1180 |
| 1181 Statement visitIf(If node) { |
| 1182 // If one of the branches is empty (i.e. just a fallthrough), then that |
| 1183 // branch should preferrably be the 'else' so we won't have to print it. |
| 1184 // In other words, we wish to perform this rewrite: |
| 1185 // if (E) {} else {S} |
| 1186 // ==> |
| 1187 // if (!E) {S} |
| 1188 // In the tree language, empty statements do not exist yet, so we must check |
| 1189 // if one branch contains a break that can be eliminated by fallthrough. |
| 1190 |
| 1191 // Swap branches if then is a fallthrough break. |
| 1192 if (isFallthroughBreak(node.thenStatement)) { |
| 1193 node.condition = new Not(node.condition); |
| 1194 Statement tmp = node.thenStatement; |
| 1195 node.thenStatement = node.elseStatement; |
| 1196 node.elseStatement = tmp; |
| 1197 } |
| 1198 |
| 1199 // Can the else part be eliminated? |
| 1200 // (Either due to the above swap or if the break was already there). |
| 1201 bool emptyElse = isFallthroughBreak(node.elseStatement); |
| 1202 |
| 1203 node.condition = makeCondition(node.condition, true, liftNots: !emptyElse); |
| 1204 node.thenStatement = visitStatement(node.thenStatement); |
| 1205 node.elseStatement = visitStatement(node.elseStatement); |
| 1206 |
| 1207 // If neither branch is empty, eliminate a negation in the condition |
| 1208 // if (!E) S1 else S2 |
| 1209 // ==> |
| 1210 // if (E) S2 else S1 |
| 1211 if (!emptyElse && node.condition is Not) { |
| 1212 node.condition = (node.condition as Not).operand; |
| 1213 Statement tmp = node.thenStatement; |
| 1214 node.thenStatement = node.elseStatement; |
| 1215 node.elseStatement = tmp; |
| 1216 } |
| 1217 |
| 1218 return node; |
| 1219 } |
| 1220 |
| 1221 Statement visitExpressionStatement(ExpressionStatement node) { |
| 1222 // TODO(asgerf): in non-checked mode we can remove Not from the expression. |
| 1223 node.expression = visitExpression(node.expression); |
| 1224 node.next = visitStatement(node.next); |
| 1225 return node; |
| 1226 } |
| 1227 |
| 1228 |
| 1229 Expression visitVariable(Variable node) { |
| 1230 return node; |
| 1231 } |
| 1232 |
| 1233 Expression visitInvokeStatic(InvokeStatic node) { |
| 1234 for (int i = 0; i < node.arguments.length; i++) { |
| 1235 node.arguments[i] = visitExpression(node.arguments[i]); |
| 1236 } |
| 1237 return node; |
| 1238 } |
| 1239 |
| 1240 Expression visitInvokeMethod(InvokeMethod node) { |
| 1241 node.receiver = visitExpression(node.receiver); |
| 1242 for (int i = 0; i < node.arguments.length; i++) { |
| 1243 node.arguments[i] = visitExpression(node.arguments[i]); |
| 1244 } |
| 1245 return node; |
| 1246 } |
| 1247 |
| 1248 Expression visitInvokeConstructor(InvokeConstructor node) { |
| 1249 for (int i = 0; i < node.arguments.length; i++) { |
| 1250 node.arguments[i] = visitExpression(node.arguments[i]); |
| 1251 } |
| 1252 return node; |
| 1253 } |
| 1254 |
| 1255 Expression visitConcatenateStrings(ConcatenateStrings node) { |
| 1256 for (int i = 0; i < node.arguments.length; i++) { |
| 1257 node.arguments[i] = visitExpression(node.arguments[i]); |
| 1258 } |
| 1259 return node; |
| 1260 } |
| 1261 |
| 1262 Expression visitConstant(Constant node) { |
| 1263 return node; |
| 1264 } |
| 1265 |
| 1266 Expression visitNot(Not node) { |
| 1267 return toBoolean(makeCondition(node.operand, false, liftNots: false)); |
| 1268 } |
| 1269 |
| 1270 Expression visitConditional(Conditional node) { |
| 1271 // node.condition will be visited after the then and else parts, because its |
| 1272 // polarity depends on what rewrite we use. |
| 1273 node.thenExpression = visitExpression(node.thenExpression); |
| 1274 node.elseExpression = visitExpression(node.elseExpression); |
| 1275 |
| 1276 // In the following, we must take care not to eliminate or introduce a |
| 1277 // boolean conversion. |
| 1278 |
| 1279 // x ? true : false --> !!x |
| 1280 if (isTrue(node.thenExpression) && isFalse(node.elseExpression)) { |
| 1281 return toBoolean(makeCondition(node.condition, true, liftNots: false)); |
| 1282 } |
| 1283 // x ? false : true --> !x |
| 1284 if (isFalse(node.thenExpression) && isTrue(node.elseExpression)) { |
| 1285 return toBoolean(makeCondition(node.condition, false, liftNots: false)); |
| 1286 } |
| 1287 |
| 1288 // x ? y : false ==> x && y (if y is known to be a boolean) |
| 1289 if (isBooleanValued(node.thenExpression) && isFalse(node.elseExpression)) { |
| 1290 return new LogicalOperator.and( |
| 1291 makeCondition(node.condition, true, liftNots:false), |
| 1292 putInBooleanContext(node.thenExpression)); |
| 1293 } |
| 1294 // x ? y : true ==> !x || y (if y is known to be a boolean) |
| 1295 if (isBooleanValued(node.thenExpression) && isTrue(node.elseExpression)) { |
| 1296 return new LogicalOperator.or( |
| 1297 makeCondition(node.condition, false, liftNots: false), |
| 1298 putInBooleanContext(node.thenExpression)); |
| 1299 } |
| 1300 // x ? true : y ==> x || y (if y if known to be boolean) |
| 1301 if (isBooleanValued(node.elseExpression) && isTrue(node.thenExpression)) { |
| 1302 return new LogicalOperator.or( |
| 1303 makeCondition(node.condition, true, liftNots: false), |
| 1304 putInBooleanContext(node.elseExpression)); |
| 1305 } |
| 1306 // x ? false : y ==> !x && y (if y is known to be a boolean) |
| 1307 if (isBooleanValued(node.elseExpression) && isTrue(node.thenExpression)) { |
| 1308 return new LogicalOperator.and( |
| 1309 makeCondition(node.condition, false, liftNots: false), |
| 1310 putInBooleanContext(node.elseExpression)); |
| 1311 } |
| 1312 |
| 1313 node.condition = makeCondition(node.condition, true); |
| 1314 |
| 1315 // !x ? y : z ==> x ? z : y |
| 1316 if (node.condition is Not) { |
| 1317 node.condition = (node.condition as Not).operand; |
| 1318 Expression tmp = node.thenExpression; |
| 1319 node.thenExpression = node.elseExpression; |
| 1320 node.elseExpression = tmp; |
| 1321 } |
| 1322 |
| 1323 return node; |
| 1324 } |
| 1325 |
| 1326 Expression visitLogicalOperator(LogicalOperator node) { |
| 1327 node.left = makeCondition(node.left, true); |
| 1328 node.right = makeCondition(node.right, true); |
| 1329 return node; |
| 1330 } |
| 1331 |
| 1332 /// True if the given expression is known to evaluate to a boolean. |
| 1333 /// This will not recursively traverse [Conditional] expressions, but if |
| 1334 /// applied to the result of [visitExpression] conditionals will have been |
| 1335 /// rewritten anyway. |
| 1336 bool isBooleanValued(Expression e) { |
| 1337 return isTrue(e) || isFalse(e) || e is Not || e is LogicalOperator; |
| 1338 } |
| 1339 |
| 1340 /// Rewrite an expression that was originally processed in a non-boolean |
| 1341 /// context. |
| 1342 Expression putInBooleanContext(Expression e) { |
| 1343 if (e is Not && e.operand is Not) { |
| 1344 return (e.operand as Not).operand; |
| 1345 } else { |
| 1346 return e; |
| 1347 } |
| 1348 } |
| 1349 |
| 1350 /// Forces a boolean conversion of the given expression. |
| 1351 Expression toBoolean(Expression e) { |
| 1352 if (isBooleanValued(e)) |
| 1353 return e; |
| 1354 else |
| 1355 return new Not(new Not(e)); |
| 1356 } |
| 1357 |
| 1358 /// Creates an equivalent boolean expression. The expression must occur in a |
| 1359 /// context where its result is immediately subject to boolean conversion. |
| 1360 /// If [polarity] if false, the negated condition will be created instead. |
| 1361 /// If [liftNots] is true (default) then Not expressions will be lifted toward |
| 1362 /// the root the condition so they can be eliminated by the caller. |
| 1363 Expression makeCondition(Expression e, bool polarity, {bool liftNots:true}) { |
| 1364 if (e is Not) { |
| 1365 // !!E ==> E |
| 1366 return makeCondition(e.operand, !polarity, liftNots: liftNots); |
| 1367 } |
| 1368 if (e is LogicalOperator) { |
| 1369 // If polarity=false, then apply the rewrite !(x && y) ==> !x || !y |
| 1370 e.left = makeCondition(e.left, polarity); |
| 1371 e.right = makeCondition(e.right, polarity); |
| 1372 if (!polarity) { |
| 1373 e.isAnd = !e.isAnd; |
| 1374 } |
| 1375 // !x && !y ==> !(x || y) (only if lifting nots) |
| 1376 if (e.left is Not && e.right is Not && liftNots) { |
| 1377 e.left = (e.left as Not).operand; |
| 1378 e.right = (e.right as Not).operand; |
| 1379 e.isAnd = !e.isAnd; |
| 1380 return new Not(e); |
| 1381 } |
| 1382 return e; |
| 1383 } |
| 1384 if (e is Conditional) { |
| 1385 // Handle polarity by: !(x ? y : z) ==> x ? !y : !z |
| 1386 // Rewrite individual branches now. The condition will be rewritten |
| 1387 // when we know what polarity to use (depends on which rewrite is used). |
| 1388 e.thenExpression = makeCondition(e.thenExpression, polarity); |
| 1389 e.elseExpression = makeCondition(e.elseExpression, polarity); |
| 1390 |
| 1391 // x ? true : false ==> x |
| 1392 if (isTrue(e.thenExpression) && isFalse(e.elseExpression)) { |
| 1393 return makeCondition(e.condition, true, liftNots: liftNots); |
| 1394 } |
| 1395 // x ? false : true ==> !x |
| 1396 if (isFalse(e.thenExpression) && isTrue(e.elseExpression)) { |
| 1397 return makeCondition(e.condition, false, liftNots: liftNots); |
| 1398 } |
| 1399 // x ? true : y ==> x || y |
| 1400 if (isTrue(e.thenExpression)) { |
| 1401 return makeOr(makeCondition(e.condition, true), |
| 1402 e.elseExpression, |
| 1403 liftNots: liftNots); |
| 1404 } |
| 1405 // x ? false : y ==> !x && y |
| 1406 if (isFalse(e.thenExpression)) { |
| 1407 return makeAnd(makeCondition(e.condition, false), |
| 1408 e.elseExpression, |
| 1409 liftNots: liftNots); |
| 1410 } |
| 1411 // x ? y : true ==> !x || y |
| 1412 if (isTrue(e.elseExpression)) { |
| 1413 return makeOr(makeCondition(e.condition, false), |
| 1414 e.thenExpression, |
| 1415 liftNots: liftNots); |
| 1416 } |
| 1417 // x ? y : false ==> x && y |
| 1418 if (isFalse(e.elseExpression)) { |
| 1419 return makeAnd(makeCondition(e.condition, true), |
| 1420 e.thenExpression, |
| 1421 liftNots: liftNots); |
| 1422 } |
| 1423 |
| 1424 e.condition = makeCondition(e.condition, true); |
| 1425 |
| 1426 // !x ? y : z ==> x ? z : y |
| 1427 if (e.condition is Not) { |
| 1428 e.condition = (e.condition as Not).operand; |
| 1429 Expression tmp = e.thenExpression; |
| 1430 e.thenExpression = e.elseExpression; |
| 1431 e.elseExpression = tmp; |
| 1432 } |
| 1433 // x ? !y : !z ==> !(x ? y : z) (only if lifting nots) |
| 1434 if (e.thenExpression is Not && e.elseExpression is Not && liftNots) { |
| 1435 e.thenExpression = (e.thenExpression as Not).operand; |
| 1436 e.elseExpression = (e.elseExpression as Not).operand; |
| 1437 return new Not(e); |
| 1438 } |
| 1439 return e; |
| 1440 } |
| 1441 if (e is Constant && e.value is dart2js.BoolConstant) { |
| 1442 // !true ==> false |
| 1443 if (!polarity) { |
| 1444 e.value = (e.value as dart2js.BoolConstant).negate(); |
| 1445 } |
| 1446 return e; |
| 1447 } |
| 1448 e = visitExpression(e); |
| 1449 return polarity ? e : new Not(e); |
| 1450 } |
| 1451 |
| 1452 bool isTrue(Expression e) { |
| 1453 return e is Constant && e.value is dart2js.TrueConstant; |
| 1454 } |
| 1455 |
| 1456 bool isFalse(Expression e) { |
| 1457 return e is Constant && e.value is dart2js.FalseConstant; |
| 1458 } |
| 1459 |
| 1460 Expression makeAnd(Expression e1, Expression e2, {bool liftNots: true}) { |
| 1461 if (e1 is Not && e2 is Not && liftNots) { |
| 1462 return new Not(new LogicalOperator.or(e1.operand, e2.operand)); |
| 1463 } else { |
| 1464 return new LogicalOperator.and(e1, e2); |
| 1465 } |
| 1466 } |
| 1467 |
| 1468 Expression makeOr(Expression e1, Expression e2, {bool liftNots: true}) { |
| 1469 if (e1 is Not && e2 is Not && liftNots) { |
| 1470 return new Not(new LogicalOperator.and(e1.operand, e2.operand)); |
| 1471 } else { |
| 1472 return new LogicalOperator.or(e1, e2); |
| 1473 } |
| 1474 } |
| 1475 |
| 1476 } |
| OLD | NEW |