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

DCE: Deopt impure statements in If.test #386

Merged
merged 4 commits into from Jan 25, 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 @@ -12,7 +12,8 @@
"babel-plugin"
],
"dependencies": {
"babel-helper-evaluate-path": "^0.0.3"
"babel-helper-evaluate-path": "^0.0.3",
"jsesc": "^2.4.0"
},
"devDependencies": {}
}
Expand Up @@ -2421,4 +2421,43 @@ describe("dce-plugin", () => {

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

it("should impure expressions in confidently evaluated if statements", () => {
const source = unpad(`
if (a.b(), true) {
foo();
}
`);
const expected = unpad(`
a.b();

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

it("should extract all necessary things from if statements", () => {
const source = unpad(`
if (a.b(), false) {
var foo = foo1;
foo();
} else if (b.c(), true) {
var bar = bar1;
bar();
} else {
var baz = baz1;
baz();
}
`);
const expected = unpad(`
a.b();
b.c();

var bar = bar1;
bar();
var baz;
var foo;
`);
expect(transform(source)).toBe(expected);
});
});
153 changes: 88 additions & 65 deletions packages/babel-plugin-minify-dead-code-elimination/src/index.js
Expand Up @@ -419,71 +419,6 @@ module.exports = ({ types: t, traverse }) => {
}
},

IfStatement: {
exit(path) {
const consequent = path.get("consequent");
const alternate = path.get("alternate");
const test = path.get("test");

const evaluateTest = test.evaluateTruthy();

// we can check if a test will be truthy 100% and if so then we can inline
// the consequent and completely ignore the alternate
//
// if (true) { foo; } -> { foo; }
// if ("foo") { foo; } -> { foo; }
//
if (evaluateTest === true) {
path.replaceWithMultiple(
[...toStatements(consequent), ...extractVars(alternate)]
);
return;
}

// we can check if a test will be falsy 100% and if so we can inline the
// alternate if there is one and completely remove the consequent
//
// if ("") { bar; } else { foo; } -> { foo; }
// if ("") { bar; } ->
//
if (evaluateTest === false) {
if (alternate.node) {
path.replaceWithMultiple(
[...toStatements(alternate), ...extractVars(consequent)]
);
return;
} else {
path.replaceWithMultiple(extractVars(consequent));
}
}

// remove alternate blocks that are empty
//
// if (foo) { foo; } else {} -> if (foo) { foo; }
//
if (alternate.isBlockStatement() && !alternate.node.body.length) {
alternate.remove();
// For if-statements babel-traverse replaces with an empty block
path.node.alternate = null;
}

// if the consequent block is empty turn alternate blocks into a consequent
// and flip the test
//
// if (foo) {} else { bar; } -> if (!foo) { bar; }
//
if (consequent.isBlockStatement() && !consequent.node.body.length &&
alternate.isBlockStatement() && alternate.node.body.length
) {
consequent.replaceWith(alternate.node);
alternate.remove();
// For if-statements babel-traverse replaces with an empty block
path.node.alternate = null;
test.replaceWith(t.unaryExpression("!", test.node, true));
}
},
},

SwitchStatement: {
exit(path) {
const evaluated = path.get("discriminant").evaluate();
Expand Down Expand Up @@ -699,11 +634,88 @@ module.exports = ({ types: t, traverse }) => {
return {
name: "minify-dead-code-elimination",
visitor: {
IfStatement: {
exit(path) {
const consequent = path.get("consequent");
const alternate = path.get("alternate");
const test = path.get("test");

const evalResult = test.evaluate();
const isPure = test.isPure();

const replacements = [];

if (evalResult.confident && !isPure && test.isSequenceExpression()) {
replacements.push(
t.expressionStatement(extractSequenceImpure(test))
);
}

// we can check if a test will be truthy 100% and if so then we can inline
// the consequent and completely ignore the alternate
//
// if (true) { foo; } -> { foo; }
// if ("foo") { foo; } -> { foo; }
//
if (evalResult.confident && evalResult.value) {
path.replaceWithMultiple([
...replacements, ...toStatements(consequent), ...extractVars(alternate)
]);
return;
}

// we can check if a test will be falsy 100% and if so we can inline the
// alternate if there is one and completely remove the consequent
//
// if ("") { bar; } else { foo; } -> { foo; }
// if ("") { bar; } ->
//
if (evalResult.confident && !evalResult.value) {
if (alternate.node) {
path.replaceWithMultiple([
...replacements, ...toStatements(alternate), ...extractVars(consequent)
]);
return;
} else {
path.replaceWithMultiple([
...replacements, ...extractVars(consequent)
]);
}
}

// remove alternate blocks that are empty
//
// if (foo) { foo; } else {} -> if (foo) { foo; }
//
if (alternate.isBlockStatement() && !alternate.node.body.length) {
alternate.remove();
// For if-statements babel-traverse replaces with an empty block
path.node.alternate = null;
}

// if the consequent block is empty turn alternate blocks into a consequent
// and flip the test
//
// if (foo) {} else { bar; } -> if (!foo) { bar; }
//
if (consequent.isBlockStatement() && !consequent.node.body.length &&
alternate.isBlockStatement() && alternate.node.body.length
) {
consequent.replaceWith(alternate.node);
alternate.remove();
// For if-statements babel-traverse replaces with an empty block
path.node.alternate = null;
test.replaceWith(t.unaryExpression("!", test.node, true));
}
},
},

EmptyStatement(path) {
if (path.parentPath.isBlockStatement() || path.parentPath.isProgram()) {
path.remove();
}
},

Program: {
exit(path, {
opts: {
Expand Down Expand Up @@ -1025,4 +1037,15 @@ module.exports = ({ types: t, traverse }) => {
} while (parent = parent.parentPath);
return false;
}

function extractSequenceImpure(seq) {
const expressions = seq.get("expressions");
const result = [];
for (let i = 0; i < expressions.length; i++) {
if (!expressions[i].isPure()) {
result.push(expressions[i].node);
}
}
return t.sequenceExpression(result);
}
};
14 changes: 14 additions & 0 deletions packages/babel-preset-babili/__tests__/preset-tests.js
Expand Up @@ -61,4 +61,18 @@ describe("preset", () => {
`);
expect(transform(source)).toBe(expected);
});

it("should fix issue#385 - impure if statements with Sequence and DCE", () => {
const source = unpad(`
a = b;
c = d;
if (false) {
const x = y
}
`);
const expected = unpad(`
a = b, c = d;
`);
expect(transform(source)).toBe(expected);
});
});