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
47 changes: 43 additions & 4 deletions lib/compress/index.js
Expand Up @@ -1598,6 +1598,49 @@ def_optimize(AST_Switch, function(self, compressor) {
while (i < len) eliminate_branch(self.body[i++], body[body.length - 1]);
self.body = body;

let default_or_exact = default_branch || exact_match;
default_branch = null;
exact_match = null;

// 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, i) =>
(branch === default_or_exact || !branch.expression.has_side_effects(compressor))
&& (branch.body.length === 0 || aborts(branch) || body.length - 1 === i))
) {
for (let i = 0; i < body.length; i++) {
const branch = body[i];
for (let j = i + 1; j < body.length; j++) {
const next = body[j];
if (next.body.length === 0) continue;
const last_branch = j === (body.length - 1);
const equivalentBranch = branches_equivalent(next, branch, false);
if (equivalentBranch || (last_branch && branches_equivalent(next, branch, true))) {
if (!equivalentBranch && last_branch) {
next.body.push(make_node(AST_Break));
}

// let's find previous siblings with inert fallthrough...
let x = j - 1;
let fallthroughDepth = 0;
while (x > i) {
if (is_inert_body(body[x--])) {
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 All @@ -1618,10 +1661,6 @@ def_optimize(AST_Switch, function(self, compressor) {
}
}

let default_or_exact = default_branch || exact_match;
default_branch = null;
exact_match = null;

// Prune any empty branches at the end of the switch statement.
{
let i = body.length - 1;
Expand Down
288 changes: 288 additions & 0 deletions test/compress/switch.js
Expand Up @@ -2447,6 +2447,294 @@ 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
break;
}
}
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_not_in_a_row_even_if_last_case_without_abort: {
options = {
switches: true,
dead_code: true
}
input: {
switch (id(3)) {
case 1:
console.log(1);
break;
case 2:
console.log(2);
break;
case 3:
console.log(1);
}
}
expect: {
switch (id(3)) {
case 1:
case 3:
console.log(1);
break;
case 2:
console.log(2);
}
}
expect_stdout: ["1"]
}


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