more optimizations for some break/continue cases
authorMihai Bazon <mihai@bazon.net>
Thu, 18 Oct 2012 12:14:57 +0000 (15:14 +0300)
committerMihai Bazon <mihai@bazon.net>
Thu, 18 Oct 2012 12:14:57 +0000 (15:14 +0300)
lib/ast.js
lib/compress.js
lib/utils.js
test/compress/labels.js [new file with mode: 0644]
test/run-tests.js

index 177bd1d..44cbed1 100644 (file)
@@ -929,10 +929,10 @@ TreeWalker.prototype = {
         } else {
             for (var i = stack.length; --i >= 0;) {
                 var x = stack[i];
-                if (x instanceof AST_Switch) return x;
-                if (x instanceof AST_For || x instanceof AST_ForIn || x instanceof AST_DWLoop) {
-                    return (x.body instanceof AST_BlockStatement ? x.body : x);
-                }
+                if (x instanceof AST_Switch
+                    || x instanceof AST_For
+                    || x instanceof AST_ForIn
+                    || x instanceof AST_DWLoop) return x;
             }
         }
     }
index 1af4a2d..cde4b6f 100644 (file)
@@ -186,6 +186,14 @@ merge(Compressor.prototype, {
         return false;
     };
 
+    function loop_body(x) {
+        if (x instanceof AST_Switch) return x;
+        if (x instanceof AST_For || x instanceof AST_ForIn || x instanceof AST_DWLoop) {
+            return (x.body instanceof AST_BlockStatement ? x.body : x);
+        }
+        return x;
+    };
+
     function tighten_body(statements, compressor) {
         var CHANGED;
         do {
@@ -303,8 +311,13 @@ merge(Compressor.prototype, {
                     }
 
                     var ab = aborts(stat.body);
+                    var lct = ab instanceof AST_LoopControl ? compressor.loopcontrol_target(ab.label) : null;
                     if (ab && ((ab instanceof AST_Return && !ab.value && in_lambda)
-                               || (ab instanceof AST_Continue && self === compressor.loopcontrol_target(ab.label)))) {
+                               || (ab instanceof AST_Continue && self === loop_body(lct))
+                               || (ab instanceof AST_Break && lct instanceof AST_BlockStatement && self === lct))) {
+                        if (ab.label) {
+                            remove(ab.label.thedef.references, ab.label);
+                        }
                         CHANGED = true;
                         var body = as_statement_array(stat.body).slice(0, -1);
                         stat = stat.clone();
@@ -320,8 +333,13 @@ merge(Compressor.prototype, {
                     }
 
                     var ab = aborts(stat.alternative);
+                    var lct = ab instanceof AST_LoopControl ? compressor.loopcontrol_target(ab.label) : null;
                     if (ab && ((ab instanceof AST_Return && !ab.value && in_lambda)
-                               || (ab instanceof AST_Continue && self === compressor.loopcontrol_target(ab.label)))) {
+                               || (ab instanceof AST_Continue && self === loop_body(lct))
+                               || (ab instanceof AST_Break && lct instanceof AST_BlockStatement && self === lct))) {
+                        if (ab.label) {
+                            remove(ab.label.thedef.references, ab.label);
+                        }
                         CHANGED = true;
                         stat = stat.clone();
                         stat.body = make_node(AST_BlockStatement, stat.body, {
@@ -347,11 +365,26 @@ merge(Compressor.prototype, {
         function eliminate_dead_code(statements, compressor) {
             var has_quit = false;
             var orig = statements.length;
+            var self = compressor.self();
             statements = statements.reduce(function(a, stat){
                 if (has_quit) {
                     extract_declarations_from_unreachable_code(compressor, stat, a);
                 } else {
-                    a.push(stat);
+                    if (stat instanceof AST_LoopControl) {
+                        var lct = compressor.loopcontrol_target(stat.label);
+                        if ((stat instanceof AST_Break
+                             && lct instanceof AST_BlockStatement
+                             && loop_body(lct) === self) || (stat instanceof AST_Continue
+                                                             && loop_body(lct) === self)) {
+                            if (stat.label) {
+                                remove(stat.label.thedef.references, stat.label);
+                            }
+                        } else {
+                            a.push(stat);
+                        }
+                    } else {
+                        a.push(stat);
+                    }
                     if (aborts(stat)) has_quit = true;
                 }
                 return a;
@@ -795,6 +828,10 @@ merge(Compressor.prototype, {
     });
 
     OPT(AST_LabeledStatement, function(self, compressor){
+        if (self.body instanceof AST_Break
+            && compressor.loopcontrol_target(self.body.label) === self.body) {
+            return make_node(AST_EmptyStatement, self);
+        }
         return self.label.references.length == 0 ? self.body : self;
     });
 
@@ -1227,7 +1264,7 @@ merge(Compressor.prototype, {
         var last_branch = self.body[self.body.length - 1];
         if (last_branch) {
             var stat = last_branch.body[last_branch.body.length - 1]; // last statement
-            if (stat instanceof AST_Break && compressor.loopcontrol_target(stat.label) === self)
+            if (stat instanceof AST_Break && loop_body(compressor.loopcontrol_target(stat.label)) === self)
                 last_branch.body.pop();
         }
         return self;
index 7903966..4d3d60f 100644 (file)
@@ -166,6 +166,12 @@ function string_template(text, props) {
     });
 };
 
+function remove(array, el) {
+    for (var i = array.length; --i >= 0;) {
+        if (array[i] === el) array.splice(i, 1);
+    }
+};
+
 function mergeSort(array, cmp) {
     if (array.length < 2) return array.slice();
     function merge(a, b) {
diff --git a/test/compress/labels.js b/test/compress/labels.js
new file mode 100644 (file)
index 0000000..044b7a7
--- /dev/null
@@ -0,0 +1,163 @@
+labels_1: {
+    options = { if_return: true, conditionals: true, dead_code: true };
+    input: {
+        out: {
+            if (foo) break out;
+            console.log("bar");
+        }
+    };
+    expect: {
+        foo || console.log("bar");
+    }
+}
+
+labels_2: {
+    options = { if_return: true, conditionals: true, dead_code: true };
+    input: {
+        out: {
+            if (foo) print("stuff");
+            else break out;
+            console.log("here");
+        }
+    };
+    expect: {
+        if (foo) {
+            print("stuff");
+            console.log("here");
+        }
+    }
+}
+
+labels_3: {
+    options = { if_return: true, conditionals: true, dead_code: true };
+    input: {
+        for (var i = 0; i < 5; ++i) {
+            if (i < 3) continue;
+            console.log(i);
+        }
+    };
+    expect: {
+        for (var i = 0; i < 5; ++i)
+            i < 3 || console.log(i);
+    }
+}
+
+labels_4: {
+    options = { if_return: true, conditionals: true, dead_code: true };
+    input: {
+        out: for (var i = 0; i < 5; ++i) {
+            if (i < 3) continue out;
+            console.log(i);
+        }
+    };
+    expect: {
+        for (var i = 0; i < 5; ++i)
+            i < 3 || console.log(i);
+    }
+}
+
+labels_5: {
+    options = { if_return: true, conditionals: true, dead_code: true };
+    // should keep the break-s in the following
+    input: {
+        while (foo) {
+            if (bar) break;
+            console.log("foo");
+        }
+        out: while (foo) {
+            if (bar) break out;
+            console.log("foo");
+        }
+    };
+    expect: {
+        while (foo) {
+            if (bar) break;
+            console.log("foo");
+        }
+        out: while (foo) {
+            if (bar) break out;
+            console.log("foo");
+        }
+    }
+}
+
+labels_6: {
+    input: {
+        out: break out;
+    };
+    expect: {}
+}
+
+labels_7: {
+    options = { if_return: true, conditionals: true, dead_code: true };
+    input: {
+        while (foo) {
+            x();
+            y();
+            continue;
+        }
+    };
+    expect: {
+        while (foo) {
+            x();
+            y();
+        }
+    }
+}
+
+labels_8: {
+    options = { if_return: true, conditionals: true, dead_code: true };
+    input: {
+        while (foo) {
+            x();
+            y();
+            break;
+        }
+    };
+    expect: {
+        while (foo) {
+            x();
+            y();
+            break;
+        }
+    }
+}
+
+labels_9: {
+    options = { if_return: true, conditionals: true, dead_code: true };
+    input: {
+        out: while (foo) {
+            x();
+            y();
+            continue out;
+            z();
+            k();
+        }
+    };
+    expect: {
+        while (foo) {
+            x();
+            y();
+        }
+    }
+}
+
+labels_10: {
+    options = { if_return: true, conditionals: true, dead_code: true };
+    input: {
+        out: while (foo) {
+            x();
+            y();
+            break out;
+            z();
+            k();
+        }
+    };
+    expect: {
+        out: while (foo) {
+            x();
+            y();
+            break out;
+        }
+    }
+}
index 001140f..0568c6a 100755 (executable)
@@ -73,12 +73,13 @@ function run_compress_tests() {
             var cmp = new U.Compressor(options, true);
             var expect = make_code(as_toplevel(test.expect), false);
             var input = as_toplevel(test.input);
+            var input_code = make_code(test.input);
             var output = input.transform(cmp);
             output.figure_out_scope();
             output = make_code(output, false);
             if (expect != output) {
                 log("!!! failed\n---INPUT---\n{input}\n---OUTPUT---\n{output}\n---EXPECTED---\n{expected}\n\n", {
-                    input: make_code(test.input),
+                    input: input_code,
                     output: output,
                     expected: expect
                 });