-
Notifications
You must be signed in to change notification settings - Fork 1.1k
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
Fix #17549: Unify how Memoize and Constructors decide what fields need storing. #17560
Conversation
@@ -2,6 +2,6 @@ T.f1 | |||
T.f2 | |||
T.f3 | |||
T.f4 | |||
3 2 -3 -4 | |||
3 2 3 -4 |
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.
AFAICT, this is a progression.
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 seems dubious to me because the result leaks before the side-effect runs. Maybe that never matters because leaking a more correct value is always better, given the semantics of initialization. But I had to convince myself.
def isConstExprFinalVal(using Context): Boolean = | ||
atPhaseNoLater(erasurePhase) { | ||
self.is(Final) && self.info.resultType.isInstanceOf[ConstantType] | ||
self.is(Final, butNot = Mutable) && self.info.resultType.isInstanceOf[ConstantType] | ||
} && !self.sjsNeedsField | ||
|
||
def constExprFinalValConstantType(using Context): ConstantType = | ||
atPhaseNoLater(erasurePhase) { | ||
self.info.resultType.asInstanceOf[ConstantType] | ||
} |
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 looks like it could be expressed in a cleaner way using an extractor object?
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 don't think so. There is only 1 call site out of 5 that would benefit from the extractor variant. The other 4 would regress in readability.
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.
ok, can you add a comment to constExprFinalValConstantType
with something like @pre self.isConstExprFinalVal
to make the pre-condition explicit?
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.
…s need storing. The Memoize and Constructors have to work together and agree on which `final val`s actually need storing in a field. Previously, they used slightly different criteria: one on the result type, and one on the rhs (with an additional Scala.js-specific eligibility condition). That discrepancy resulted in the crash/wrong codegen in the issue. We now unify both: we avoid a field if and only if all of the following apply: * it is a `final val`, * its result *type* is a `ConstantType`, and * it is eligible according to Scala.js semantics. In particular, there is no condition on the right-hand-side. We avoid a field even if the right-hand-side has side effects. The side effects are moved to the constructor regardless. --- This introduces both progressions and regressions in the amount of fields we generate. We can avoid fields even for side-effecting rhs'es, as long as the result type is constant. On the other hand, we now create a field for `final val`s with non-constant result type, even if the rhs is a literal. While the latter is a regression for Scala 3, it aligns with the behavior of Scala 2. It also has the nice benefit that whether or not a val has a field is now independent of its *implementation*, and only dependent on its *API*. Overall, I think this is a trade-off worth taking. We could reintroduce that optimization in the future (but in classes only; not in traits), if we really want to, although that would require dedicated code.
dd0121d
to
ac2b53b
Compare
The Memoize and Constructors have to work together and agree on which
final val
s actually need storing in a field. Previously, they used slightly different criteria: one on the result type, and one on the rhs (with an additional Scala.js-specific eligibility condition). That discrepancy resulted in the crash/wrong codegen in the issue.We now unify both: we avoid a field if and only if all of the following apply:
final val
,ConstantType
, andIn particular, there is no condition on the right-hand-side. We avoid a field even if the right-hand-side has side effects. The side effects are moved to the constructor regardless.
This introduces both progressions and regressions in the amount of fields we generate. We can avoid fields even for side-effecting rhs'es, as long as the result type is constant. On the other hand, we now create a field for
final val
s with non-constant result type, even if the rhs is a literal.While the latter is a regression for Scala 3, it aligns with the behavior of Scala 2. It also has the nice benefit that whether or not a val has a field is now independent of its implementation, and only dependent on its API. Overall, I think this is a trade-off worth taking.
We could reintroduce that optimization in the future (but in classes only; not in traits), if we really want to, although that would require dedicated code.