-
-
Notifications
You must be signed in to change notification settings - Fork 1.6k
[WIP] handle assignments in dead code #3212
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
[WIP] handle assignments in dead code #3212
Conversation
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.
Just some comments. I know the deoptimization logic is slightly complicated, but it is actually quite efficient. I would not change IfStatement. The initial testValue retrieved during bind will depend on the initial values of the declared variables, but will then be deoptimized automatically later if assignments are added.
@@ -46,6 +41,11 @@ export default class AssignmentExpression extends NodeBase { | |||
return path.length > 0 && this.right.hasEffectsWhenAccessedAtPath(path, context); | |||
} | |||
|
|||
include(context: InclusionContext, includeChildrenRecursively: IncludeChildren) { | |||
this.deoptimize(); | |||
return super.include(context, includeChildrenRecursively); |
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 could actually be inlined to become this.included = true; this.left.include(context, includeChildrenRecursively); this.right.include(context, includeChildrenRecursively);
to save one extra function call.
@@ -69,4 +69,17 @@ export default class AssignmentExpression extends NodeBase { | |||
} | |||
} | |||
} | |||
|
|||
shouldBeIncluded(context: InclusionContext): boolean { | |||
this.deoptimize(); |
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.
Actually on second thought I think there is no reason to do this here: This will only be called for statements (which this one is not) and in some edge cases. Unless we are in a try statement or for declarations, hasEffects
will be called at least once if the expression is included. So shouldBeIncluded
can be removed.
@@ -46,6 +41,11 @@ export default class AssignmentExpression extends NodeBase { | |||
return path.length > 0 && this.right.hasEffectsWhenAccessedAtPath(path, context); | |||
} | |||
|
|||
include(context: InclusionContext, includeChildrenRecursively: IncludeChildren) { | |||
this.deoptimize(); |
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.
By now, everything I told you about this being necessary was guess work. Would be nice to first have a test for the situation where an assignment is included without a hasEffects
check first and only THEN I would add this here. This is all really performance critical code so there should be a tested justification for adding additional checks.
Maybe something like this (but please check if the test is failing without the above deoptimization):
let x = false;
let reassigned = false;
try {
x = true;
} catch (err) {}
if (x) {
reassigned = true;
}
assert.ok(reassigned);
@@ -90,14 +88,22 @@ export default class IfStatement extends StatementBase implements DeoptimizableE | |||
} | |||
} | |||
|
|||
private getTestValue(): LiteralValueOrUnknown { |
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.
The changes in this file make sense but I do not think they actually solve any issue; on the other hand, they add a lot of additional function calls in various places. Usually, the deoptimization logic should fix it if the test value is later reassigned. The reason why it was not working is because there is a bug in MemberExpression
:
deoptimizeCache
rollup/src/ast/nodes/MemberExpression.ts
Lines 114 to 118 in f9b9a7c
deoptimizeCache() { | |
for (const expression of this.expressionsToBeDeoptimized) { | |
expression.deoptimizeCache(); | |
} | |
} |
expressionsToBeDeoptimized
but also set this.propertyKey = UnknownKey
. With that change, I can revert all changes in IfStatement
and still have all tests green.
So how do DeoptimizeableEntity
s work? This is a performance optimization that allows nodes to cache values (such as this.testValue
) reliably while still making sure the value is reset if one of the entities needed to get the cached value is reassigned. To do that, getLiteralValueAtPath
receives the calling Node as parameter. Then when the value that was initially returned is deoptimized, deoptimizeCache
is called on the caller to make it "forget" the cached value. This was missing for MemberExpression
. A similar logic is in place for caching function return 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.
MemberExpression
is fixed now and I think the changes are ok as I think moving logic away from bind
is a good thing.
As I really want this merged because I would love to do some larger refactorings that would include changing some names, I added some tests and slightly adapted the logic. Please have a look! |
Codecov Report
@@ Coverage Diff @@
## master #3212 +/- ##
==========================================
- Coverage 93.17% 93.13% -0.04%
==========================================
Files 172 172
Lines 6049 6062 +13
Branches 1806 1809 +3
==========================================
+ Hits 5636 5646 +10
- Misses 219 221 +2
- Partials 194 195 +1
Continue to review full report at Codecov.
|
|
||
hasEffects(context: HasEffectsContext): boolean { | ||
if (!this.deoptimized) this.applyDeoptimizations(); |
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 extracted the check because hasEffects
and include
are called quite often and the deoptimizations will only be necessary once. This would save the cost of one function call, but the wins are probably small so we could also move the check back if you prefer.
this.included = true; | ||
this.left.include(context, includeChildrenRecursively); | ||
this.right.include(context, includeChildrenRecursively); | ||
} |
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.
Similar reasons, we could also call super.include
, but this is all it would do, though Node.include
is slightly more complicated and generic, so this is more efficient.
@@ -90,14 +88,22 @@ export default class IfStatement extends StatementBase implements DeoptimizableE | |||
} | |||
} | |||
|
|||
private getTestValue(): LiteralValueOrUnknown { |
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.
MemberExpression
is fixed now and I think the changes are ok as I think moving logic away from bind
is a good thing.
13229e0
to
90b242d
Compare
@lukastaegert thanks for finishing this off! I hadn’t realised you pushed some commits a while back |
Sure, I hope it was ok. At the time, I had high hopes of pushing some more stuff based on these refactorings but Christmas and Life got in the way 😜 |
ref #3200
This PR contains:
Are tests included?
Breaking Changes?
List any relevant issue numbers:
Description