-
Notifications
You must be signed in to change notification settings - Fork 425
Trivial dead code elimination in residual functions #1061
Conversation
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! |
Thank you for signing our Contributor License Agreement. We can now accept your code for this (and any) Facebook open source project. Thanks! |
There was a problem hiding this 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.
src/serializer/visitors.js
Outdated
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, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I like your comment.
src/serializer/visitors.js
Outdated
IfStatement: { | ||
exit: function(path: BabelTraversePath, state: ClosureRefReplacerState) { | ||
let node = path.node; | ||
if (t.isBooleanLiteral(node.test)) { |
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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"); } |
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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.
src/serializer/visitors.js
Outdated
LogicalExpression: { | ||
exit: function(path: BabelTraversePath, state: ClosureRefReplacerState) { | ||
let node = path.node; | ||
if (node.operator === "&&" && t.isBooleanLiteral(node.left, { value: false })) { |
There was a problem hiding this comment.
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
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done.
There was a problem hiding this comment.
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 }
.
There was a problem hiding this 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.
a7567aa
to
f3643ce
Compare
src/serializer/visitors.js
Outdated
@@ -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. |
There was a problem hiding this comment.
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 :-)
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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 }; |
There was a problem hiding this comment.
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
.
There was a problem hiding this comment.
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.
There was a problem hiding this 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.
Ah, one sec, just realized I overlooked a case... |
help me |
OK, updated. Should be good to go. I forgot There is an absurd number of expression positions in JavaScript. |
}, | ||
}, | ||
|
||
DoWhileStatement: { |
There was a problem hiding this comment.
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".
There was a problem hiding this comment.
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...)
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Sure, done.
There was a problem hiding this 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.
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
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
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.