Skip to content

Commit

Permalink
Deopt do..while stmts with break (#433)
Browse files Browse the repository at this point in the history
* Deopt do..while stmts with break - (fix #419)

* Add continue statement
  • Loading branch information
boopathi committed Feb 27, 2017
1 parent 4ef735e commit 19d740c
Show file tree
Hide file tree
Showing 2 changed files with 220 additions and 26 deletions.
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

0 comments on commit 19d740c

Please sign in to comment.