Skip to content

Commit

Permalink
Collapse inert bodies
Browse files Browse the repository at this point in the history
  • Loading branch information
jridgewell committed Aug 23, 2021
1 parent e9f87c9 commit 804e52f
Show file tree
Hide file tree
Showing 2 changed files with 120 additions and 16 deletions.
37 changes: 32 additions & 5 deletions lib/compress/index.js
Expand Up @@ -1627,7 +1627,7 @@ def_optimize(AST_Switch, function(self, compressor) {
for (; i >= 0; i--) {
let bbody = body[i].body;
if (is_break(bbody[bbody.length - 1], compressor)) bbody.pop();
if (bbody.length > 0) break;
if (!is_inert_body(body[i])) break;
}
// i now points to the index of a branch that contains a body. By incrementing, it's
// pointing to the first branch that's empty.
Expand Down Expand Up @@ -1658,39 +1658,54 @@ def_optimize(AST_Switch, function(self, compressor) {
let default_index = body.indexOf(default_or_exact);
let default_body_index = default_index;
for (; default_body_index < body.length - 1; default_body_index++) {
if (body[default_body_index].body.length > 0) break;
if (!is_inert_body(body[default_body_index])) break;
}
let side_effect_index = body.length - 1;
for (; side_effect_index >= 0; side_effect_index--) {
let branch = body[side_effect_index];
if (branch === default_or_exact) continue;
if (branch.expression.has_side_effects(compressor)) break;
}
// If the default behavior comes after any side-effect case expressions,
// then we can fold all side-effect free cases into the default branch.
// If the side-effect case is after the default, then any side-effect
// free cases could prevent the side-effect from occurring.
if (default_body_index > side_effect_index) {
let prev_body_index = default_index - 1;
for (; prev_body_index >= 0; prev_body_index--) {
if (body[prev_body_index].body.length > 0) break;
if (!is_inert_body(body[prev_body_index])) break;
}
let before = Math.max(side_effect_index, prev_body_index) + 1;
let after = default_index;
if (side_effect_index > default_index) {
// If the default falls into the same body as a side-effect
// case, then we need preserve that case and only prune the
// cases after it.
after = side_effect_index;
body[side_effect_index].body = body[default_body_index].body;
} else {
// The default will be the last branch.
default_or_exact.body = body[default_body_index].body;
}

// Prune everything after the default (or last side-effect case)
// until the next case with a body.
body.splice(after + 1, default_body_index - after);
// Prune everything before the default that falls into it.
body.splice(before, default_index - before);
}
}

// See if we can remove the switch entirely if all cases (the default) fall into the same case body.
DEFAULT: if (default_or_exact) {
let i = body.findIndex(branch => branch.body.length > 0);
let i = body.findIndex(branch => !is_inert_body(branch));
let caseBody;
// `i` is equal to one of the following:
// - `-1`, there is no body in the switch statement.
// - `body.length - 1`, all cases fall into the same body.
// - anything else, there are multiple bodies in the switch.
if (i === body.length - 1) {
// There is only one case body. We can extract the body and place it after the switch.
// All cases fall into the case body.
let branch = body[i];
if (has_nested_break(self)) break DEFAULT;

Expand All @@ -1701,6 +1716,7 @@ def_optimize(AST_Switch, function(self, compressor) {
});
branch.body = [];
} else if (i !== -1) {
// If there are multiple bodies, then we cannot optimize anything.
break DEFAULT;
}

Expand All @@ -1710,6 +1726,7 @@ def_optimize(AST_Switch, function(self, compressor) {
&& branch.expression.has_side_effects(compressor)
);
});
// If no cases cause a side-effect, we can eliminate the switch entirely.
if (!sideEffect) {
return make_node(AST_BlockStatement, self, {
body: decl.concat(
Expand All @@ -1720,9 +1737,14 @@ def_optimize(AST_Switch, function(self, compressor) {
}).optimize(compressor);
}

// If we're this far, either there was no body or all cases fell into the same body.
// If there was no body, then we don't need a default branch (because the default is
// do nothing). If there was a body, we'll extract it to after the switch, so the
// switch's new default is to do nothing and we can still prune it.
const default_index = body.indexOf(default_or_exact);
body.splice(default_index, 1);
default_or_exact = null;

if (caseBody) {
// Recurse into switch statement one more time so that we can append the case body
// outside of the switch. This recursion will only happen once since we've pruned
Expand Down Expand Up @@ -1875,6 +1897,11 @@ def_optimize(AST_Switch, function(self, compressor) {
return node instanceof AST_Break
&& stack.loopcontrol_target(node) === self;
}
function is_inert_body(branch) {
return !aborts(branch) && !make_node(AST_BlockStatement, branch, {
body: branch.body
}).has_side_effects(compressor)
}
});

def_optimize(AST_Try, function(self, compressor) {
Expand Down
99 changes: 88 additions & 11 deletions test/compress/switch.js
Expand Up @@ -2119,17 +2119,50 @@ issue_1680_2: {
}
expect: {
var a = 100, b = 10;
switch (b) {
case a--:
case b:
var c;
case a:
case a--:
}
console.log(a, b);
}
expect_stdout: ["99 10"]
}

issue_1680_4: {
options = {
dead_code: true,
switches: true,
}
input: {
var a = 10, b = 10;
switch (b) {
case a--:
break;
case b:
var c;
break;
case a:
break;
case a--:
break;
}
console.log(a, b);
}
expect_stdout: true
expect: {
var a = 10, b = 10;
switch (b) {
case a--:
case b:
var c;
case a:
case a--:
}
console.log(a, b);
}
expect_stdout: ["9 10"]
}

issue_1690_1: {
Expand Down Expand Up @@ -2404,17 +2437,17 @@ collapse_same_branches: {
case 1:
console.log("PASS");
break
case 2:

case 2:
console.log("PASS");
break

}
}
expect: {
switch (id(1)) {
case 1:
case 2:
case 2:
console.log("PASS");
}
}
Expand All @@ -2431,17 +2464,17 @@ collapse_same_branches_2: {
switch (id(1)) {
case 1:
console.log("PASS");
case 2:

case 2:
console.log("PASS");
}
}
expect: {
switch (id(1)) {
case 1:
console.log("PASS");
case 2:

case 2:
console.log("PASS");
}
}
Expand All @@ -2462,7 +2495,7 @@ trim_empty_last_branches: {
// break should be removed too
break
case 3: {}
case 4:
case 4:
}
}
expect: {
Expand All @@ -2485,7 +2518,7 @@ trim_empty_last_branches_2: {
case 2:
break somewhere_else
case 3: {}
case 4:
case 4:
}
}
}
Expand Down Expand Up @@ -2525,6 +2558,29 @@ trim_side_effect_free_branches_falling_into_default: {
}
}

trim_side_effect_free_branches_falling_into_default_2: {
options = {
switches: true,
dead_code: true
}
input: {
switch (id(1)) {
default:
case 0:
"no side effect"
case 1:
console.log("PASS default")
case 2:
console.log("PASS 2")
}
}
expect: {
if (2 !== id(1))
console.log("PASS default");
console.log("PASS 2")
}
}

gut_entire_switch: {
options = {
switches: true,
Expand All @@ -2545,6 +2601,27 @@ gut_entire_switch: {
expect_stdout: "PASS"
}

gut_entire_switch_2: {
options = {
switches: true,
dead_code: true
}
input: {
switch (id(123)) {
case 1:
"no side effect"
case 1:
// Not here either
default:
console.log("PASS");
}
}
expect: {
id(123); console.log("PASS");
}
expect_stdout: "PASS"
}

turn_into_if: {
options = {
switches: true,
Expand Down

0 comments on commit 804e52f

Please sign in to comment.