-
Notifications
You must be signed in to change notification settings - Fork 396
Fix #4947: Retain the structure
of PreTransRecordTree
.
#5091
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
Conversation
A higher level comment here, before I dive into the specifics: It seems there are two overall approaches to take to class field / record field mapping:
I was wondering if you have explored the static approach. It seems at least at face value, it would keep the optimizer simpler. |
It's a good question. I have thought a bit about it. I don't think there's a strong case either way. One thing going for the dynamic approach is that it produces less code size in Regarding complexity, I'm not sure the static approach would really be simpler. Overall, I find that keeping the |
b76258f
to
540550e
Compare
Rebased to fix conflicts after #5092 was merged. No other changes. |
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.
Or at least it's clearer to me. It is more obvious that it does the right thing too, especially when merging the results of If/Match/Labeled.
After review, I strongly agree with this. The joining points are much easier to understand with the approach in this PR than the approach before.
linker/shared/src/main/scala/org/scalajs/linker/frontend/optimizer/OptimizerCore.scala
Show resolved
Hide resolved
linker/shared/src/main/scala/org/scalajs/linker/frontend/optimizer/OptimizerCore.scala
Show resolved
Hide resolved
linker/shared/src/main/scala/org/scalajs/linker/frontend/optimizer/OptimizerCore.scala
Show resolved
Hide resolved
linker/shared/src/main/scala/org/scalajs/linker/frontend/optimizer/OptimizerCore.scala
Outdated
Show resolved
Hide resolved
linker/shared/src/main/scala/org/scalajs/linker/frontend/optimizer/OptimizerCore.scala
Outdated
Show resolved
Hide resolved
linker/shared/src/main/scala/org/scalajs/linker/frontend/optimizer/OptimizerCore.scala
Outdated
Show resolved
Hide resolved
@@ -5660,7 +5671,10 @@ private[optimizer] object OptimizerCore { | |||
private val ClassTagApplyMethodName = | |||
MethodName("apply", List(ClassRef(ClassClass)), ClassRef(ClassName("scala.reflect.ClassTag"))) | |||
|
|||
final class InlineableClassStructure(private val allFields: List[FieldDef]) { | |||
final class InlineableClassStructure(val className: ClassName, private val allFields: List[FieldDef]) { | |||
private[OptimizerCore] val refinedType: RefinedType = |
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.
What is this useful for? IIUC this is only used in tpe
of PreTransRecordTree
. And that usage feels very odd (I have commented there).
linker/shared/src/main/scala/org/scalajs/linker/frontend/optimizer/OptimizerCore.scala
Outdated
Show resolved
Hide resolved
linker/shared/src/main/scala/org/scalajs/linker/frontend/optimizer/OptimizerCore.scala
Outdated
Show resolved
Hide resolved
@@ -6286,9 +6309,13 @@ private[optimizer] object OptimizerCore { | |||
* whereas `tree.tpe` is always the lowered `RecordType`. | |||
*/ |
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 specification of tpe
feels odd. From the doc of PreTransform
:
A
PreTransform
has atpe
as precisely refined as if a fulltransformExpr()
had been performed.
So this is technically correct: a full transformExpr will roll-back any PreTransRecord tree (with the exception of RT long).
Do you know when this is useful in practice?
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, right now I'm not sure. I can check later. For now I've addressed all the other comments.
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.
How would you like to proceed with this? It seems this is the last open comment on this PR.
From my second look, it seems like before this PR, we needed the tpe
field to validate if two PreTransRecordTree
s correspond to the same class. In hindsight, it is not much less obvious to me in the original code that the optimizer was correct in that regard, we're just moving form rhsOrigType != lhsOrigType
to lhsStructure.sameClassAs(rhsStructure)
.
This PR introduces a clear distinction between RefinedType
s and (transient) allocated structures. I think this is an improvement we should keep; it makes many decisions the optimizer makes more explicit (notably in label processing this becomes clear).
However, this feels like a strong hint, that requiring a tpe: RefinedType
on an arbitrary pre-transform might not be ideal. So maybe we should try an move it up the hierarchy?
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've had a look at usages of PreTransform#tpe
and IMO it's fairly quickly clear why it is useful. For example, take IsInstanceOf
:
scala-js/linker/shared/src/main/scala/org/scalajs/linker/frontend/optimizer/OptimizerCore.scala
Lines 558 to 576 in f82e59f
case IsInstanceOf(expr, testType) => | |
trampoline { | |
pretransformExpr(expr) { texpr => | |
val result = { | |
if (isSubtype(texpr.tpe.base.toNonNullable, testType)) { | |
if (texpr.tpe.isNullable) | |
BinaryOp(BinaryOp.!==, finishTransformExpr(texpr), Null()) | |
else | |
Block(finishTransformStat(texpr), BooleanLiteral(true)) | |
} else { | |
if (texpr.tpe.isExact) | |
Block(finishTransformStat(texpr), BooleanLiteral(false)) | |
else | |
IsInstanceOf(finishTransformExpr(texpr), testType) | |
} | |
} | |
TailCalls.done(result) | |
} | |
} |
PreTransform#tpe
allows the transformation of IsInstanceOf
to fold the operation if the type is statically know. We want this to happen, if the PreTransform
ends up as a record.
Another usage I've found is to accurately type bindings: I suspect without the tpe
, we'd have to roll-back the binding itself in case it needs var replacement (like this, we only need to roll-back the pretransform).
IMO this is sufficient to move forward with this PR, but PTAL as well.
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.
Ah indeed, the IsInstanceOf
example is pretty obvious. Another one where it is clearly the right thing is in pretansformApply
to resolve targets and decide whether and what to inline:
scala-js/linker/shared/src/main/scala/org/scalajs/linker/frontend/optimizer/OptimizerCore.scala
Line 1966 in f82e59f
val className = boxedClassForType(treceiver.tpe.base) |
Instead of only retaining the `RecordType`. The `structure` contains the complete information required to map `FieldName`s to the record's `SimpleFieldName`s.
540550e
to
24c79c2
Compare
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.
Updated. I haven't addressed the question about the spec of PreTransRecordTree.tpe
.
linker/shared/src/main/scala/org/scalajs/linker/frontend/optimizer/OptimizerCore.scala
Show resolved
Hide resolved
linker/shared/src/main/scala/org/scalajs/linker/frontend/optimizer/OptimizerCore.scala
Show resolved
Hide resolved
linker/shared/src/main/scala/org/scalajs/linker/frontend/optimizer/OptimizerCore.scala
Outdated
Show resolved
Hide resolved
linker/shared/src/main/scala/org/scalajs/linker/frontend/optimizer/OptimizerCore.scala
Outdated
Show resolved
Hide resolved
@@ -6286,9 +6309,13 @@ private[optimizer] object OptimizerCore { | |||
* whereas `tree.tpe` is always the lowered `RecordType`. | |||
*/ |
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, right now I'm not sure. I can check later. For now I've addressed all the other comments.
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 the open comment about tpe
. I might try and fiddle with it a bit today if I find the time.
@@ -6286,9 +6309,13 @@ private[optimizer] object OptimizerCore { | |||
* whereas `tree.tpe` is always the lowered `RecordType`. | |||
*/ |
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.
How would you like to proceed with this? It seems this is the last open comment on this PR.
From my second look, it seems like before this PR, we needed the tpe
field to validate if two PreTransRecordTree
s correspond to the same class. In hindsight, it is not much less obvious to me in the original code that the optimizer was correct in that regard, we're just moving form rhsOrigType != lhsOrigType
to lhsStructure.sameClassAs(rhsStructure)
.
This PR introduces a clear distinction between RefinedType
s and (transient) allocated structures. I think this is an improvement we should keep; it makes many decisions the optimizer makes more explicit (notably in label processing this becomes clear).
However, this feels like a strong hint, that requiring a tpe: RefinedType
on an arbitrary pre-transform might not be ideal. So maybe we should try an move it up the hierarchy?
Instead of only retaining the
RecordType
.The
structure
contains the complete information required to mapFieldName
s to the record'sSimpleFieldName
s.Other than the newly added test, this PR causes 0 changes to the generated JavaScript code.