Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

collapsing switch cases with same bodies, when not lined in a row #1070

Merged
52 changes: 51 additions & 1 deletion lib/compress/index.js
Expand Up @@ -145,7 +145,8 @@ import {

_INLINE,
_NOINLINE,
_PURE
_PURE,
AST_Case
elad-yosifon marked this conversation as resolved.
Show resolved Hide resolved
} from "../ast.js";
import {
defaults,
Expand Down Expand Up @@ -1598,6 +1599,52 @@ def_optimize(AST_Switch, function(self, compressor) {
while (i < len) eliminate_branch(self.body[i++], body[body.length - 1]);
self.body = body;

// group equivalent branches so they will be located next to each other,
// that way the next micro-optimization will merge them.
// ** bail micro-optimization if not a simple switch case with breaks
if (body.every(branch =>
(
branch instanceof AST_Default || // default
elad-yosifon marked this conversation as resolved.
Show resolved Hide resolved
!branch.expression.has_side_effects(compressor) // or no_side_effects
)
&& (
branch.body.length === 0 // empty
|| is_break(branch.body[branch.body.length - 1], compressor) // or has a break
|| is_return(branch.body[branch.body.length - 1]) // or has a return
elad-yosifon marked this conversation as resolved.
Show resolved Hide resolved
)
)) {
elad-yosifon marked this conversation as resolved.
Show resolved Hide resolved
for (let i = 0; i < body.length; i++) {
let branch = body[i];
if (branch.body.length === 0) continue;
if (!aborts(branch)) continue;
elad-yosifon marked this conversation as resolved.
Show resolved Hide resolved
for (let j = i + 1; j < body.length; j++) {
let next = body[j];
if (next.body.length === 0) continue;
if (
branches_equivalent(next, branch, false)
|| (j === body.length - 1 && branches_equivalent(next, branch, true))
elad-yosifon marked this conversation as resolved.
Show resolved Hide resolved
) {
// let's find previous siblings with empty fallthrough...
let x = j-1;
elad-yosifon marked this conversation as resolved.
Show resolved Hide resolved
let fallthroughDepth = 0;
while(x > i) {
elad-yosifon marked this conversation as resolved.
Show resolved Hide resolved
let previousSiblingBranch = body[x--];
if(previousSiblingBranch.body.length === 0 && previousSiblingBranch instanceof AST_Case) {
elad-yosifon marked this conversation as resolved.
Show resolved Hide resolved
fallthroughDepth++;
} else {
break;
}
}

const plucked = body.splice(j - fallthroughDepth, 1 + fallthroughDepth);
body.splice(i+1, 0, ...plucked);
i += plucked.length;
}
}
}
}

// merge equivalent branches in a row
for (let i = 0; i < body.length; i++) {
let branch = body[i];
if (branch.body.length === 0) continue;
Expand Down Expand Up @@ -1887,6 +1934,9 @@ def_optimize(AST_Switch, function(self, compressor) {
return node instanceof AST_Break
&& stack.loopcontrol_target(node) === self;
}
function is_return(node) {
return node instanceof AST_Return;
}
function is_inert_body(branch) {
return !aborts(branch) && !make_node(AST_BlockStatement, branch, {
body: branch.body
Expand Down
258 changes: 258 additions & 0 deletions test/compress/switch.js
Expand Up @@ -2447,6 +2447,264 @@ collapse_same_branches_2: {
expect_stdout: ["PASS", "PASS"]
}

collapse_same_branches_not_in_a_row: {
options = {
switches: true,
dead_code: true
}
input: {
switch (id(1)) {
case 1:
console.log("PASS")
break;
case 2:
console.log("FAIL");
break;
case 3:
console.log("PASS");
break;
}
}
expect: {
switch (id(1)) {
case 1:
case 3:
console.log("PASS");
break;
case 2:
console.log("FAIL");
}
}
expect_stdout: ["PASS"]
}

collapse_same_branches_not_in_a_row2: {
options = {
switches: true,
dead_code: true
}
input: {
switch (id(1)) {
case 1:
console.log("PASS");
break;
case 2:
console.log("FAIL");
break;
case 3:
console.log("PASS");
break;
case 4:
console.log("PASS");
break;
}
}
expect: {
switch (id(1)) {
case 1:
case 3:
case 4:
console.log("PASS");
break;
case 2:
console.log("FAIL");
}
}
expect_stdout: ["PASS"]
}

collapse_same_branches_not_in_a_row_including_fallthrough_with_same_body: {
options = {
switches: true,
dead_code: true
}
input: {
switch (id(1)) {
case 1:
console.log("PASS");
break;
case 2:
console.log("FAIL");
break;
case 3:
case 4:
console.log("PASS");
break;
case 9:
console.log("FAIL");
break;
case 5:
case 6:
case 7:
case 8:
console.log("PASS");
break;
}
}
expect: {
switch (id(1)) {
case 1:
case 3:
case 4:
case 5:
case 6:
case 7:
case 8:
console.log("PASS");
break;
case 2:
case 9:
console.log("FAIL");
}
}
expect_stdout: ["PASS"]
}

collapse_same_branches_not_in_a_row_ensure_no_side_effects: {
options = {
switches: true,
dead_code: true
}
input: {
let i = 1;
switch (id(2)) {
case 1:
console.log(1);
break;
case 2:
console.log(2);
break;
case ++i:
console.log(1);
elad-yosifon marked this conversation as resolved.
Show resolved Hide resolved
}
}
expect: {
let i = 1;
switch (id(2)) {
case 1:
console.log(1);
break;
case 2:
console.log(2);
break;
case ++i:
console.log(1);
}
}
expect_stdout: ["2"]
}


collapse_same_branches_as_default_not_in_a_row: {
options = {
switches: true,
dead_code: true
}
input: {
switch (id(1)) {
case 1:
console.log("PASS");
break;
case 2:
console.log("FAIL");
break;
case 3:
console.log("PREVENT_IFS");
break;
case 4:
console.log("PASS");
break;
default:
console.log("PASS");
break;
}
}
expect: {
switch (id(1)) {
default:
console.log("PASS");
break;
case 2:
console.log("FAIL");
break;
case 3:
console.log("PREVENT_IFS");
}
}
expect_stdout: ["PASS"]
}

collapse_same_branches_in_a_row2: {
options = {
switches: true,
dead_code: true
}
input: {
switch (id(1)) {
case 1:
console.log("PASS");
break;
case 2:
console.log("FAIL");
break;
case 3:
console.log("PASS");
break;
case 4:
console.log("FAIL");
break;
}
}
expect: {
switch (id(1)) {
case 1:
case 3:
console.log("PASS");
break;
case 2:
case 4:
console.log("FAIL");
}
}
expect_stdout: ["PASS"]
}

collapse_same_branches_in_a_row_with_return: {
options = {
switches: true,
dead_code: true
}
input: {
function fn() {
switch (id(1)) {
case 1:
return "PASS";
case 2:
return "FAIL";
case 3:
return "PASS";
case 4:
return "FAIL";
}
}
console.log(fn())
}
expect: {
function fn() {
switch (id(1)) {
case 1:
case 3:
return "PASS";
case 2:
case 4:
return "FAIL";
}
}
console.log(fn())
}
expect_stdout: ["PASS"]
}

// Empty branches at the end of the switch get trimmed
trim_empty_last_branches: {
options = {
Expand Down