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
This commit is contained in:
Alex Lam S.L
2017-03-26 05:15:46 +08:00
committed by GitHub
parent b19aa58cff
commit f83d370f57
2 changed files with 232 additions and 95 deletions

View File

@@ -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);
var branch;
var value = self.expression.evaluate(compressor);
if (value !== self.expression) {
var expression = make_node_from_constant(value, self.expression).transform(compressor);
self.expression = best_of_expression(expression, self.expression);
}
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;
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;
}
} 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);
}
} else if (exp !== branch.expression && !fallthrough) {
extract_declarations_from_unreachable_code(compressor, branch, decl);
continue;
}
}
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;
}
}
break;
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;
}
var value = self.expression.evaluate(compressor);
out: if (value !== self.expression) try {
// constant expression
var expression = make_node_from_constant(value, self.expression);
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 (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;
}
if (exp === value || started) {
started = true;
if (aborts(node)) stopped = true;
} else return skip(node);
}
}
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
});
}
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;

View File

@@ -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"
}