Skip to content
This repository has been archived by the owner on Feb 12, 2022. It is now read-only.

Trivial dead code elimination in residual functions #1061

Closed
wants to merge 7 commits into from

Conversation

bakkot
Copy link
Contributor

@bakkot bakkot commented Oct 6, 2017

Progress towards #632.

There's a lot more that can be done here - for example, we could also eliminate if (0) foo() - but presumably that should be done by partial evaluators in a less ad-hoc way.

@facebook-github-bot
Copy link

Thank you for your pull request and welcome to our community. We require contributors to sign our Contributor License Agreement, and we don't seem to have you on file. In order for us to review and merge your code, please sign up at https://code.facebook.com/cla. If you are contributing on behalf of someone else (eg your employer), the individual CLA may not be sufficient and your employer may need the corporate CLA signed.

If you have received this in error or have any questions, please contact us at cla@fb.com. Thanks!

@facebook-github-bot
Copy link

Thank you for signing our Contributor License Agreement. We can now accept your code for this (and any) Facebook open source project. Thanks!

Copy link
Contributor

@NTillmann NTillmann left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks great! I added some comments with suggestions (another one: eliminate while (false) { ... }). There's only one thing I'd really like to see: One more test case for the third case in the handling of IfStatements.

let node = path.node;
if (t.isBooleanLiteral(node.test)) {
if (node.test.value) {
// Strictly speaking this is not safe: Annex B.3.4 allows FunctionDeclarations as the body of IfStatements in sloppy mode,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I like your comment.

IfStatement: {
exit: function(path: BabelTraversePath, state: ClosureRefReplacerState) {
let node = path.node;
if (t.isBooleanLiteral(node.test)) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You could generalize all cases a bit for other kinds of literals that have truthy values.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sure, done. This is getting further and further into the realm of partial evaluation, though - for example, I'm considering [] to be truthy, but not [0] (because I don't want to eliminate [sideEffect()], and don't have a 'isPossiblySideEffecting' function on hand).

let t = true;
global.inspect = function () {
if (f) { console.log("eliminate me"); }
if (t) { } else { console.log("eliminate me"); }
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I see three cases in your handling of the IfStatement above, but only two are getting exercised here.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Added a test. I'm not super happy with these tests, though - there doesn't seem to be a good way of asserting the precise shape of the output. For example, I can assert that [] || 'eliminate' prepacks into something not containing 'eliminate', but not that it actually ends up as [], which is an important property to preserve.

Have you considered adding snapshot tests? For example, the prettier project uses them almost exclusively, to great effect. It makes it easy to see what the impact of a change is over a large range of code.

LogicalExpression: {
exit: function(path: BabelTraversePath, state: ClosureRefReplacerState) {
let node = path.node;
if (node.operator === "&&" && t.isBooleanLiteral(node.left, { value: false })) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not very exciting, but you could also reduce true && x to x, and false || x to x.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I didn't add any tests for this, mind, because I didn't see a good way to fit it into the existing test framework. Same for reducing do { foo } while (false) to { foo }.

Copy link
Contributor

@hermanventer hermanventer left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks good to me.

@@ -56,6 +56,27 @@ function replaceName(path, residualFunctionBinding, name, data) {
}
}

function getLiteralTruthiness(node) {
// Returns { known: boolean, value?: boolean }. 'known' is true only if this is a literal of known truthiness and with no side effects; if 'known' is true, 'value' is its truthiness.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No need to hide type info in comment, you can make it a real type annotation :-)

Copy link
Contributor

@NTillmann NTillmann Oct 7, 2017

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

To avoid costly object allocations, a simpler scheme of void | false | true would be sufficient.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I generally figure the cost of this sort of object allocation is worth the additional readability, at least in modern engines. v8 at least is pretty good at optimizing the creation and use of this sort of object.

if (t.isNullLiteral(node)) {
return { known: true, value: false };
}
return { known: false };
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Another case you could add: If the node is a unary expression with operator void and the argument is a literal, then this node will evaluate to undefined without side effects, and thus results in the known truthiness value of false.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is true. I could also handle !false, +0, [0, true], void void [ +!'a', { foo: 0, get bar(){ return whatever(); } } ], typeof /a/, and so on. But one could go a long, long way down this road, and I think it probably makes sense to do the remainder of that work as part of partial evaluation, rather than in an ad-hoc way here.

Copy link
Contributor

@NTillmann NTillmann left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks great!

All my remaining comments are very optional, so I'll try merge in your changes soon when the CI succeeds.

@bakkot
Copy link
Contributor Author

bakkot commented Oct 7, 2017

Ah, one sec, just realized I overlooked a case...

@ghost
Copy link

ghost commented Oct 7, 2017

help me

@ghost
Copy link

ghost commented Oct 7, 2017

Getting started.pdf

@bakkot
Copy link
Contributor Author

bakkot commented Oct 7, 2017

OK, updated. Should be good to go.

I forgot (class { [foo()](){} } exists, and might be side-effecting, which makes classes with bodies not safe to treat as literals.

There is an absurd number of expression positions in JavaScript.

},
},

DoWhileStatement: {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For completeness/code coverage, please also add a testcase for "do...while".

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not sure how. I don't know what assertion I can make about them, given the current test framework.

(I'd love to have snapshot tests...)

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe just use "does not contain: while" for now.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sure, done.

Copy link

@facebook-github-bot facebook-github-bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@NTillmann is landing this pull request. If you are a Facebook employee, you can view this diff on Phabricator.

@bakkot bakkot deleted the dead-code-elimination branch October 9, 2017 21:48
jwli229 pushed a commit that referenced this pull request Oct 16, 2017
Summary:
Progress towards #632.

There's a lot more that can be done here - for example, we could also eliminate `if (0) foo()` - but presumably that should be done by partial evaluators in a less ad-hoc way.
Closes #1061

Differential Revision: D6012613

Pulled By: NTillmann

fbshipit-source-id: a7dd6b64a42be34dddf1a59f363ed101feefda64
facebook-github-bot pushed a commit that referenced this pull request Nov 11, 2017
Summary:
Followup to #1061; I thought of another case where it was too aggressive.

Do-while loops with always-false conditions can't be replaced by their body because their body might contain a break or continue, which is illegal (or will have different semantics) if the loop becomes a normal block. In principle the body of the loop could be walked to find those, but frankly I doubt it will come up enough to be worth it.
Closes #1145

Differential Revision: D6303312

Pulled By: hermanventer

fbshipit-source-id: 0d677076fea6f225573a36fbbc9df1a0bebd959a
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Projects
None yet
Development

Successfully merging this pull request may close these issues.

5 participants