From f83d370f57c49e5112ac5ce74e27a0573265baf6 Mon Sep 17 00:00:00 2001 From: "Alex Lam S.L" Date: Sun, 26 Mar 2017 05:15:46 +0800 Subject: [PATCH] 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 805a84a7..c57287bf 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 01d45f78..7d3d7d13 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" +} -- 2.34.1