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:
181
lib/compress.js
181
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);
|
||||
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;
|
||||
|
||||
@@ -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"
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user