optimization for if/break as first statement in a loop body
authorMihai Bazon <mihai@bazon.net>
Wed, 7 Nov 2012 16:57:51 +0000 (18:57 +0200)
committerMihai Bazon <mihai@bazon.net>
Thu, 8 Nov 2012 09:43:14 +0000 (11:43 +0200)
    for(...; x; ...) if (y) break; → for(...; x&&!y; ...);

similarly for `while` and some combinations (i.e. the `break` appears in the
`else` clause, etc.)

lib/compress.js
test/compress/loops.js [new file with mode: 0644]

index 59c9128..1571173 100644 (file)
@@ -1118,11 +1118,57 @@ merge(Compressor.prototype, {
         return self;
     });
 
+    function if_break_in_loop(self, compressor) {
+        function drop_it(rest) {
+            rest = as_statement_array(rest);
+            if (self.body instanceof AST_BlockStatement) {
+                self.body = self.body.clone();
+                self.body.body = rest.concat(self.body.body.slice(1));
+                self.body = self.body.transform(compressor);
+            } else {
+                self.body = make_node(AST_BlockStatement, self.body, {
+                    body: rest
+                }).transform(compressor);
+            }
+            if_break_in_loop(self, compressor);
+        }
+        var first = self.body instanceof AST_BlockStatement ? self.body.body[0] : self.body;
+        if (first instanceof AST_If) {
+            if (first.body instanceof AST_Break
+                && compressor.loopcontrol_target(first.body.label) === self) {
+                if (self.condition) {
+                    self.condition = make_node(AST_Binary, self.condition, {
+                        left: self.condition,
+                        operator: "&&",
+                        right: first.condition.negate(compressor),
+                    });
+                } else {
+                    self.condition = first.condition.negate(compressor);
+                }
+                drop_it(first.alternative);
+            }
+            else if (first.alternative instanceof AST_Break
+                     && compressor.loopcontrol_target(first.alternative.label) === self) {
+                if (self.condition) {
+                    self.condition = make_node(AST_Binary, self.condition, {
+                        left: self.condition,
+                        operator: "&&",
+                        right: first.condition,
+                    });
+                } else {
+                    self.condition = first.condition;
+                }
+                drop_it(first.body);
+            }
+        }
+    };
+
     OPT(AST_While, function(self, compressor) {
         if (!compressor.option("loops")) return self;
         self = AST_DWLoop.prototype.optimize.call(self, compressor);
         if (self instanceof AST_While) {
-            self = make_node(AST_For, self, self);
+            if_break_in_loop(self, compressor);
+            self = make_node(AST_For, self, self).transform(compressor);
         }
         return self;
     });
@@ -1151,6 +1197,7 @@ merge(Compressor.prototype, {
                 }
             }
         }
+        if_break_in_loop(self, compressor);
         return self;
     });
 
diff --git a/test/compress/loops.js b/test/compress/loops.js
new file mode 100644 (file)
index 0000000..cdf1f04
--- /dev/null
@@ -0,0 +1,123 @@
+while_becomes_for: {
+    options = { loops: true };
+    input: {
+        while (foo()) bar();
+    }
+    expect: {
+        for (; foo(); ) bar();
+    }
+}
+
+drop_if_break_1: {
+    options = { loops: true };
+    input: {
+        for (;;)
+            if (foo()) break;
+    }
+    expect: {
+        for (; !foo(););
+    }
+}
+
+drop_if_break_2: {
+    options = { loops: true };
+    input: {
+        for (;bar();)
+            if (foo()) break;
+    }
+    expect: {
+        for (; bar() && !foo(););
+    }
+}
+
+drop_if_break_3: {
+    options = { loops: true };
+    input: {
+        for (;bar();) {
+            if (foo()) break;
+            stuff1();
+            stuff2();
+        }
+    }
+    expect: {
+        for (; bar() && !foo();) {
+            stuff1();
+            stuff2();
+        }
+    }
+}
+
+drop_if_break_4: {
+    options = { loops: true, sequences: true };
+    input: {
+        for (;bar();) {
+            x();
+            y();
+            if (foo()) break;
+            z();
+            k();
+        }
+    }
+    expect: {
+        for (; bar() && (x(), y(), !foo());) z(), k();
+    }
+}
+
+drop_if_else_break_1: {
+    options = { loops: true };
+    input: {
+        for (;;) if (foo()) bar(); else break;
+    }
+    expect: {
+        for (; foo(); ) bar();
+    }
+}
+
+drop_if_else_break_2: {
+    options = { loops: true };
+    input: {
+        for (;bar();) {
+            if (foo()) baz();
+            else break;
+        }
+    }
+    expect: {
+        for (; bar() && foo();) baz();
+    }
+}
+
+drop_if_else_break_3: {
+    options = { loops: true };
+    input: {
+        for (;bar();) {
+            if (foo()) baz();
+            else break;
+            stuff1();
+            stuff2();
+        }
+    }
+    expect: {
+        for (; bar() && foo();) {
+            baz();
+            stuff1();
+            stuff2();
+        }
+    }
+}
+
+drop_if_else_break_4: {
+    options = { loops: true, sequences: true };
+    input: {
+        for (;bar();) {
+            x();
+            y();
+            if (foo()) baz();
+            else break;
+            z();
+            k();
+        }
+    }
+    expect: {
+        for (; bar() && (x(), y(), foo());) baz(), z(), k();
+    }
+}