aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorAlex Lam S.L <alexlamsl@gmail.com>2017-03-26 05:15:46 +0800
committerGitHub <noreply@github.com>2017-03-26 05:15:46 +0800
commitf83d370f57c49e5112ac5ce74e27a0573265baf6 (patch)
tree4e40bc3039149edc283f32e1e65759f14ab60365
parentb19aa58cff73ea59438346db094de4a54463d669 (diff)
downloadtracifyjs-f83d370f57c49e5112ac5ce74e27a0573265baf6.tar.gz
tracifyjs-f83d370f57c49e5112ac5ce74e27a0573265baf6.zip
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
-rw-r--r--lib/compress.js175
-rw-r--r--test/compress/switch.js146
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"
+}