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
48 changes: 47 additions & 1 deletion lib/compress/index.js
Expand Up @@ -1598,6 +1598,53 @@ 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;

// 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 || branch.body.some(aborts) || body.length - 1 === i))
elad-yosifon marked this conversation as resolved.
Show resolved Hide resolved
) {
for (let i = 0; i < body.length; i++) {
const 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++) {
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))
) {
elad-yosifon marked this conversation as resolved.
Show resolved Hide resolved
if (!equivalentBranch && last_branch) {
if (!is_break(next.body[next.body.length - 1])) {
next.body.push(make_node(AST_Break));
}
elad-yosifon marked this conversation as resolved.
Show resolved Hide resolved
}

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

let default_or_exact = default_branch || exact_match;
default_branch = null;
exact_match = null;
elad-yosifon marked this conversation as resolved.
Show resolved Hide resolved

Expand Down
287 changes: 287 additions & 0 deletions test/compress/switch.js
Expand Up @@ -2447,6 +2447,293 @@ 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_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