[Pkg-javascript-commits] [uglifyjs] 146/228: improve switch optimisations (#1677)

Jonas Smedegaard dr at jones.dk
Sat Apr 15 14:25:25 UTC 2017


This is an automated email from the git hooks/post-receive script.

js pushed a commit to branch master
in repository uglifyjs.

commit f83d370f57c49e5112ac5ce74e27a0573265baf6
Author: Alex Lam S.L <alexlamsl at gmail.com>
Date:   Sun Mar 26 05:15:46 2017 +0800

    improve switch optimisations (#1677)
    
    - correctly determine reachability of (default) branches
    - gracefully handle multiple default branches
    - optimise branches with duplicate bodies
    
    fixes #376
    fixes #441
    fixes #1674
---
 lib/compress.js         | 175 +++++++++++++++++++++++-------------------------
 test/compress/switch.js | 146 +++++++++++++++++++++++++++++++++++++++-
 2 files changed, 229 insertions(+), 92 deletions(-)

diff --git a/lib/compress.js b/lib/compress.js
index 805a84a..c57287b 100644
--- a/lib/compress.js
+++ b/lib/compress.js
@@ -2504,109 +2504,102 @@ merge(Compressor.prototype, {
     });
 
     OPT(AST_Switch, function(self, compressor){
-        if (self.body.length == 0 && compressor.option("conditionals")) {
-            return make_node(AST_SimpleStatement, self, {
-                body: self.expression
-            }).transform(compressor);
-        }
-        for(;;) {
-            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 && loop_body(compressor.loopcontrol_target(stat.label)) === self)
-                    last_branch.body.pop();
-                if (last_branch instanceof AST_Default && last_branch.body.length == 0) {
-                    self.body.pop();
-                    continue;
-                }
-            }
-            break;
-        }
+        var branch;
         var value = self.expression.evaluate(compressor);
-        out: if (value !== self.expression) try {
-            // constant expression
-            var expression = make_node_from_constant(value, self.expression);
+        if (value !== self.expression) {
+            var expression = make_node_from_constant(value, self.expression).transform(compressor);
             self.expression = best_of_expression(expression, self.expression);
-            if (!compressor.option("dead_code")) break out;
-            var in_if = false;
-            var in_block = false;
-            var started = false;
-            var stopped = false;
-            var ruined = false;
-            var tt = new TreeTransformer(function(node, descend, in_list){
-                if (node instanceof AST_Lambda || node instanceof AST_SimpleStatement) {
-                    // no need to descend these node types
-                    return node;
-                }
-                else if (node === self) {
-                    node = node.clone();
-                    descend(node, this);
-                    return ruined ? node : make_node(AST_BlockStatement, node, {
-                        body: node.body.map(function(stat) {
-                            return stat instanceof AST_SwitchBranch ? make_node(AST_BlockStatement, stat, stat) : stat;
-                        })
-                    }).optimize(compressor);
-                }
-                else if (node instanceof AST_If || node instanceof AST_Try) {
-                    var save = in_if;
-                    in_if = !in_block;
-                    descend(node, this);
-                    in_if = save;
-                    return node;
-                }
-                else if (node instanceof AST_StatementWithBody || node instanceof AST_Switch) {
-                    var save = in_block;
-                    in_block = true;
-                    descend(node, this);
-                    in_block = save;
-                    return node;
-                }
-                else if (node instanceof AST_Break && this.loopcontrol_target(node.label) === self) {
-                    if (in_if) {
-                        ruined = true;
-                        return node;
+        }
+        if (compressor.option("dead_code")) {
+            var blocks = Object.create(null);
+            var decl = [];
+            var body = [];
+            var default_branch;
+            var exact_match;
+            var fallthrough;
+            for (var i = 0, len = self.body.length; i < len && !exact_match; i++) {
+                branch = self.body[i];
+                if (branch instanceof AST_Default) {
+                    if (!default_branch) default_branch = branch;
+                    else if (!fallthrough) {
+                        extract_declarations_from_unreachable_code(compressor, branch, decl);
+                        continue;
                     }
-                    if (in_block) return node;
-                    stopped = true;
-                    return skip(node);
-                }
-                else if (node instanceof AST_SwitchBranch && this.parent() === self) {
-                    if (stopped) return skip(node);
-                    if (node instanceof AST_Case) {
-                        var exp = node.expression.evaluate(compressor);
-                        if (exp === node.expression) {
-                            // got a case with non-constant expression, baling out
-                            throw self;
+                } else if (value !== self.expression) {
+                    var exp = branch.expression.evaluate(compressor);
+                    if (exp === value) {
+                        exact_match = branch;
+                        if (default_branch) {
+                            body.splice(body.indexOf(default_branch), 1);
+                            extract_declarations_from_unreachable_code(compressor, default_branch, decl);
                         }
-                        if (exp === value || started) {
-                            started = true;
-                            if (aborts(node)) stopped = true;
-                        } else return skip(node);
+                    } else if (exp !== branch.expression && !fallthrough) {
+                        extract_declarations_from_unreachable_code(compressor, branch, decl);
+                        continue;
                     }
                 }
-
-                function skip(node) {
-                    var a = [];
-                    extract_declarations_from_unreachable_code(compressor, node, a);
-                    return in_list ? MAP.splice(a) : make_node(AST_BlockStatement, node, {
-                        body: a
-                    });
+                if (aborts(branch)) {
+                    var key = make_node(AST_BlockStatement, branch, branch).print_to_string();
+                    var block;
+                    if (!fallthrough && (block = blocks[key])) {
+                        block.body = [];
+                        body.splice(body.indexOf(block) + 1, 0, branch);
+                    } else {
+                        body.push(branch);
+                    }
+                    blocks[key] = branch;
+                    fallthrough = false;
+                } else {
+                    body.push(branch);
+                    fallthrough = true;
                 }
+            }
+            for (; i < len && fallthrough; i++) {
+                branch = self.body[i];
+                if (branch instanceof AST_Case) {
+                    exact_match.body.push(make_node(AST_SimpleStatement, branch.expression, {
+                        body: branch.expression
+                    }));
+                }
+                exact_match.body = exact_match.body.concat(branch.body);
+                fallthrough = !aborts(exact_match);
+            }
+            while (i < len) extract_declarations_from_unreachable_code(compressor, self.body[i++], decl);
+            if (body.length == 0) return make_node(AST_BlockStatement, self, {
+                body: decl
+            }).optimize(compressor);
+            body[0].body = decl.concat(body[0].body);
+            self.body = body;
+        }
+        while (branch = self.body[self.body.length - 1]) {
+            var stat = branch.body[branch.body.length - 1];
+            if (stat instanceof AST_Break && compressor.loopcontrol_target(stat.label) === self)
+                branch.body.pop();
+            if (branch.body.length
+                || branch instanceof AST_Case
+                    && branch.expression.has_side_effects(compressor)) break;
+            self.body.pop();
+        }
+        if (compressor.option("conditionals") && self.body.length == 0) {
+            return make_node(AST_SimpleStatement, self, {
+                body: self.expression
+            }).optimize(compressor);
+        }
+        if (body && body.length == 1 && (body[0] === exact_match || body[0] === default_branch)) {
+            var has_break = false;
+            var tw = new TreeWalker(function(node) {
+                if (has_break
+                    || node instanceof AST_Lambda
+                    || node instanceof AST_SimpleStatement) return true;
+                if (node instanceof AST_Break && tw.loopcontrol_target(node.label) === self)
+                    has_break = true;
             });
-            // allow transform() to view the whole AST
-            tt.stack = compressor.stack.slice(0, -1);
-            self = self.transform(tt);
-        } catch(ex) {
-            if (ex !== self) throw ex;
+            self.walk(tw);
+            if (!has_break) return make_node(AST_BlockStatement, self, body[0]).optimize(compressor);
         }
         return self;
     });
 
-    OPT(AST_Case, function(self, compressor){
-        self.body = tighten_body(self.body, compressor);
-        return self;
-    });
-
     OPT(AST_Try, function(self, compressor){
         self.body = tighten_body(self.body, compressor);
         return self;
diff --git a/test/compress/switch.js b/test/compress/switch.js
index 01d45f7..7d3d7d1 100644
--- a/test/compress/switch.js
+++ b/test/compress/switch.js
@@ -23,6 +23,7 @@ constant_switch_2: {
     }
     expect: {
         foo();
+        2;
         bar();
     }
 }
@@ -117,6 +118,7 @@ constant_switch_6: {
             x();
             if (foo) break OUT;
             y();
+            2;
             bar();
         }
     }
@@ -155,6 +157,7 @@ constant_switch_7: {
                 console.log(x);
             }
             y();
+            2;
             bar();
         }
     }
@@ -203,6 +206,7 @@ constant_switch_9: {
             x();
             for (;;) if (foo) break OUT;
             y();
+            2;
             bar();
             def();
         }
@@ -281,12 +285,152 @@ issue_1663: {
     expect: {
         var a = 100, b = 10;
         function f() {
+            var b;
             b = a++;
             return ++b;
-            var b;
         }
         f();
         console.log(a, b);
     }
     expect_stdout: true
 }
+
+drop_case: {
+    options = {
+        dead_code: true,
+    }
+    input: {
+        switch (foo) {
+          case 'bar': baz(); break;
+          case 'moo':
+            break;
+        }
+    }
+    expect: {
+        switch (foo) {
+          case 'bar': baz();
+        }
+    }
+}
+
+keep_case: {
+    options = {
+        dead_code: true,
+    }
+    input: {
+        switch (foo) {
+          case 'bar': baz(); break;
+          case moo:
+            break;
+        }
+    }
+    expect: {
+        switch (foo) {
+          case 'bar': baz(); break;
+          case moo:
+        }
+    }
+}
+
+issue_376: {
+    options = {
+        dead_code: true,
+        evaluate: true,
+    }
+    input: {
+        switch (true) {
+          case boolCondition:
+            console.log(1);
+            break;
+          case false:
+            console.log(2);
+            break;
+        }
+    }
+    expect: {
+        switch (true) {
+          case boolCondition:
+            console.log(1);
+        }
+    }
+}
+
+issue_441_1: {
+    options = {
+        dead_code: true,
+    }
+    input: {
+        switch (foo) {
+          case bar:
+            qux();
+            break;
+          case baz:
+            qux();
+            break;
+          default:
+            qux();
+            break;
+        }
+    }
+    expect: {
+        switch (foo) {
+          case bar:
+          case baz:
+          default:
+            qux();
+        }
+    }
+}
+
+issue_441_2: {
+    options = {
+        dead_code: true,
+    }
+    input: {
+        switch (foo) {
+          case bar:
+            // TODO: Fold into the case below
+            qux();
+            break;
+          case fall:
+          case baz:
+            qux();
+            break;
+          default:
+            qux();
+            break;
+        }
+    }
+    expect: {
+        switch (foo) {
+          case bar:
+            qux();
+            break;
+          case fall:
+          case baz:
+          default:
+            qux();
+        }
+    }
+}
+
+issue_1674: {
+    options = {
+        dead_code: true,
+        evaluate: true,
+    }
+    input: {
+        switch (0) {
+          default:
+            console.log("FAIL");
+            break;
+          case 0:
+            console.log("PASS");
+            break;
+        }
+    }
+    expect: {
+        console.log("PASS");
+    }
+    expect_stdout: "PASS"
+}

-- 
Alioth's /usr/local/bin/git-commit-notice on /srv/git.debian.org/git/pkg-javascript/uglifyjs.git



More information about the Pkg-javascript-commits mailing list