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

Deopt do..while stmts with break #433

Merged
merged 2 commits into from Feb 27, 2017
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
Expand Up @@ -2522,4 +2522,157 @@ describe("dce-plugin", () => {

expect(transform(source)).toBe(expected);
});

it("should deopt for impure tests", () => {
const source = unpad(`
function foo() {
do {
bar();
} while ((bar(), false));
for (; bar(), false;) {
bar();
}
while (bar(), false) {
bar();
}
}
`);
expect(transform(source)).toBe(source);
});

it("should handle confident do..while with break statements", () => {
const source = unpad(`
function foo() {
do {
if (x) break;
} while (false);

do break; while (false);

bar0: do break; while (false);

bar1: do break bar1; while (false);

bar2: do {
if (y) break;
} while (false);

bar3: do {
if (y) break bar3;
} while (false);

bar4: do {
while (baz()) {
if (x) break;
}
} while (false);

bar5: do {
while (baz()) {
if (x) break bar5;
}
} while (false);
}
`);
const expected = unpad(`
function foo() {
do {
if (x) break;
} while (false);

bar1: do break bar1; while (false);

bar2: do {
if (y) break;
} while (false);

bar3: do {
if (y) break bar3;
} while (false);

bar4: {
while (baz()) {
if (x) break;
}
}

bar5: do {
while (baz()) {
if (x) break bar5;
}
} while (false);
}
`);
expect(transform(source)).toBe(expected);
});

it("should handle confident do..while with continue statements", () => {
const source = unpad(`
function foo() {
do {
if (x) continue;
} while (false);

do continue; while (false);

bar0: do continue; while (false);

bar1: do continue bar1; while (false);

bar2: do {
if (y) continue;
} while (false);

bar3: do {
if (y) continue bar3;
} while (false);

bar4: do {
while (baz()) {
if (x) continue;
}
} while (false);

bar5: do {
while (baz()) {
if (x) continue bar5;
}
} while (false);
}
`);
const expected = unpad(`
function foo() {
do {
if (x) continue;
} while (false);

do continue; while (false);

bar0: do continue; while (false);

bar1: do continue bar1; while (false);

bar2: do {
if (y) continue;
} while (false);

bar3: do {
if (y) continue bar3;
} while (false);

bar4: {
while (baz()) {
if (x) continue;
}
}

bar5: do {
while (baz()) {
if (x) continue bar5;
}
} while (false);
}
`);
expect(transform(source)).toBe(expected);
});
});
93 changes: 67 additions & 26 deletions packages/babel-plugin-minify-dead-code-elimination/src/index.js
Expand Up @@ -571,13 +571,15 @@ module.exports = ({ types: t, traverse }) => {
WhileStatement(path) {
const test = path.get("test");
const result = test.evaluate();
if (result.confident && !result.value) {
if (result.confident && test.isPure() && !result.value) {
path.remove();
}
},

ForStatement(path) {
const test = path.get("test");
if (!test.isPure()) return;

const result = test.evaluate();
if (result.confident) {
if (result.value) {
Expand All @@ -591,8 +593,28 @@ module.exports = ({ types: t, traverse }) => {
DoWhileStatement(path) {
const test = path.get("test");
const result = test.evaluate();
if (result.confident && !result.value) {
path.replaceWith(path.get("body").node);
if (result.confident && test.isPure() && !result.value) {
const body = path.get("body");

if (body.isBlockStatement()) {
const stmts = body.get("body");
for (const stmt of stmts) {
const _isBreaking = isBreaking(stmt, path);
if (_isBreaking.bail || _isBreaking.break) return;
const _isContinuing = isContinuing(stmt, path);
if (_isContinuing.bail || isContinuing.continue) return;
}
path.replaceWith(body.node);
} else if (body.isBreakStatement()) {
const _isBreaking = isBreaking(body, path);
if (_isBreaking.bail) return;
if (_isBreaking.break) path.remove();

} else if (body.isContinueStatement()) {
return;
} else {
path.replaceWith(body.node);
}
}
},

Expand Down Expand Up @@ -946,40 +968,59 @@ module.exports = ({ types: t, traverse }) => {
return path1.scope.getFunctionParent() === path2.scope.getFunctionParent();
}

// tells if a "stmt" is a break statement that would break the "path"
function isBreaking(stmt, path) {
if (stmt.isBreakStatement()) {
return _isBreaking(stmt, path);
return isControlTransfer(stmt, path, "break");
}

function isContinuing(stmt, path) {
return isControlTransfer(stmt, path, "continue");
}

// tells if a "stmt" is a break/continue statement
function isControlTransfer(stmt, path, control = "break") {
const {
[control]: type
} = {
break: "BreakStatement",
continue: "ContinueStatement"
};
if (!type) {
throw new Error("Can only handle break and continue statements");
}
const checker = `is${type}`;

if (stmt[checker]()) {
return _isControlTransfer(stmt, path);
}

let isBroken = false;
let isTransferred = false;
let result = {
break: false,
[control]: false,
bail: false
};

stmt.traverse({
BreakStatement(breakPath) {
// if we already detected a break statement,
if (isBroken) return;
[type](cPath) {
// if we already detected a break/continue statement,
if (isTransferred) return;

result = _isBreaking(breakPath, path);
result = _isControlTransfer(cPath, path);

if (result.bail || result.break) {
isBroken = true;
if (result.bail || result[control]) {
isTransferred = true;
}
}
});

return result;

function _isBreaking(breakPath, path) {
const label = breakPath.get("label");
function _isControlTransfer(cPath, path) {
const label = cPath.get("label");

if (label.node !== null) {
// labels are fn scoped and not accessible by inner functions
// path is the switch statement
if (!isSameFunctionScope(path, breakPath)) {
if (!isSameFunctionScope(path, cPath)) {
// we don't have to worry about this break statement
return {
break: false,
Expand All @@ -1000,29 +1041,29 @@ module.exports = ({ types: t, traverse }) => {

return {
bail: _isAncestor,
break: _isAncestor
[control]: _isAncestor
};
}

// set the flag that it is indeed breaking
let isBreak = true;
let isCTransfer = true;

// this flag is to capture
// switch(0) { case 0: while(1) if (x) break; }
let possibleRunTimeBreak = false;
let possibleRunTimeControlTransfer = false;

// and compute if it's breaking the correct thing
let parent = breakPath.parentPath;
let parent = cPath.parentPath;

while (parent !== stmt.parentPath) {
// loops and nested switch cases
if (parent.isLoop() || parent.isSwitchCase()) {
// invalidate all the possible runtime breaks captured
// while (1) { if (x) break; }
possibleRunTimeBreak = false;
possibleRunTimeControlTransfer = false;

// and set that it's not breaking our switch statement
isBreak = false;
isCTransfer = false;
break;
}
//
Expand All @@ -1041,14 +1082,14 @@ module.exports = ({ types: t, traverse }) => {
// IfStatement if it was a compile time determined
//
if (parent.isIfStatement()) {
possibleRunTimeBreak = true;
possibleRunTimeControlTransfer = true;
}
parent = parent.parentPath;
}

return {
break: possibleRunTimeBreak || isBreak,
bail: possibleRunTimeBreak
[control]: possibleRunTimeControlTransfer || isCTransfer,
bail: possibleRunTimeControlTransfer
};
}
}
Expand Down