-
Notifications
You must be signed in to change notification settings - Fork 396
Merge several IR nodes into UnaryOp
.
#5088
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
FWIW: IMO |
For |
1e592a4
to
1792413
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.
I'd appreciate separate commits for the review changes to ease re-review.
linker/shared/src/main/scala/org/scalajs/linker/backend/wasmemitter/FunctionEmitter.scala
Show resolved
Hide resolved
|
||
private def genWrapAsThrowable(tree: WrapAsThrowable): Type = { | ||
val WrapAsThrowable(expr) = tree | ||
private def genWrapAsThrowable(tree: UnaryOp): Type = { |
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 to be inlined in genUnaryOp
and unused otherwise. Remove?
} | ||
|
||
tree.tpe | ||
|
||
// scalastyle:on return |
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.
Consider putting this after the early return at the beginning of the function.
IMO it will make it clearer to tell the story:
- attention, this function contains early returns
- code
- from here on, all early returns are done.
- more code
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.
Oh that's new. But it's a good strategy, indeed!
@@ -801,7 +798,7 @@ private final class ClassDefChecker(classDef: ClassDef, | |||
|
|||
checkApplyGeneric(method, args) | |||
|
|||
case UnaryOp(_, lhs) => | |||
case UnaryOp(op, lhs) => |
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.
Change intended?
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. Reverted.
@@ -882,8 +882,7 @@ final class IncOptimizer private[optimizer] (config: CommonPhaseConfig, collOps: | |||
case UnaryOp(UnaryOp.CheckNotNull, expr) => | |||
config.coreSpec.semantics.nullPointers == CheckedBehavior.Unchecked && | |||
isTriviallySideEffectFree(expr) | |||
case GetClass(expr) => // Before 1.17, we used GetClass as CheckNotNull | |||
config.coreSpec.semantics.nullPointers == CheckedBehavior.Unchecked && | |||
case UnaryOp(UnaryOp.GetClass, expr) => // Before 1.17, we used GetClass as CheckNotNull |
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.
If we need to keep this codepath anyways, how about:
case UnaryOp(UnaryOp.GetClass, expr) => // Before 1.17, we used GetClass as CheckNotNull | |
case UnaryOp(op, expr) if UnaryOp.isSideEffectFree(op) => // Before 1.17, we used GetClass as CheckNotNull |
expandLongOps(foldUnaryOp(op, tlhs))(cont) | ||
val folded = foldUnaryOp(op, tlhs) | ||
|
||
folded match { |
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.
Is there an upside to folding the op first and then applying the checks for throwable wrapping? IIUC texpr
below is always tlhs
.
If not consider inverting to avoid confusion:
def folded = foldUnaryOp(op, tlhs)
op match {
case UnaryOp.WrapAsThrowable =>
if (...) else if (...) else folded
// etc.
case _ => expandLongOps(folded)(cont)
}
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 strong opinion here. I changed it to your suggestion.
@@ -6920,7 +6880,6 @@ private[optimizer] object OptimizerCore { | |||
case ApplyStatically(_, receiver, _, _, Nil) => isTrivialArg(receiver) | |||
case ApplyStatic(_, _, _, Nil) => true | |||
|
|||
case ArrayLength(array) => isTrivialArg(array) | |||
case ArraySelect(array, index) => isTrivialArg(array) && isTrivialArg(index) | |||
|
|||
case AsInstanceOf(inner, _) => isSimpleArg(inner) |
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.
Comment should be one line lower (but Github doesn't let me).
IIUC this will consider UnwrapFromThrowable
(and others) a simple arg. Not sure we want that?
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.
Arguably we might not want it for WrapAsThrowable
and UnwrapAsThrowable
, but those won't actually happen in practice. Even then, at call-site there's a good chance that they can be simplified with their expansion, so it might even be better anyway.
For the other ones, IMO we do want that. One I have seen in the diffs are throw
s. If a method does nothing but throw a simple arg, inlining it is probably beneficial.
I did not change this in this round, but I remain open to changing it if you have a stronger opinion.
ArrayLength(Null()), | ||
ArraySelect(Throw(Null()), int(1))(NothingType), | ||
ArrayLength(Throw(Null())) | ||
UnaryOp(UnaryOp.Array_length, UnaryOp(UnaryOp.CheckNotNull, Null())), |
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.
Remove this case? IMO it doesn't test really anything anymore.
I think if you want to add a test case / adjust this, we should check that Array_length
now does not allow null anymore.
@@ -2066,7 +2066,7 @@ object Build { | |||
if (!useMinifySizes) { | |||
Some(ExpectedSizes( | |||
fastLink = 449000 to 450000, | |||
fullLink = 95000 to 96000, | |||
fullLink = 94000 to 95000, |
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.
Heh, nice :) I guess less unnecessary null checks.
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 with one commit addressing the review.
linker/shared/src/main/scala/org/scalajs/linker/backend/wasmemitter/FunctionEmitter.scala
Show resolved
Hide resolved
} | ||
|
||
tree.tpe | ||
|
||
// scalastyle:on return |
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.
Oh that's new. But it's a good strategy, indeed!
expandLongOps(foldUnaryOp(op, tlhs))(cont) | ||
val folded = foldUnaryOp(op, tlhs) | ||
|
||
folded match { |
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 strong opinion here. I changed it to your suggestion.
@@ -6920,7 +6880,6 @@ private[optimizer] object OptimizerCore { | |||
case ApplyStatically(_, receiver, _, _, Nil) => isTrivialArg(receiver) | |||
case ApplyStatic(_, _, _, Nil) => true | |||
|
|||
case ArrayLength(array) => isTrivialArg(array) | |||
case ArraySelect(array, index) => isTrivialArg(array) && isTrivialArg(index) | |||
|
|||
case AsInstanceOf(inner, _) => isSimpleArg(inner) |
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.
Arguably we might not want it for WrapAsThrowable
and UnwrapAsThrowable
, but those won't actually happen in practice. Even then, at call-site there's a good chance that they can be simplified with their expansion, so it might even be better anyway.
For the other ones, IMO we do want that. One I have seen in the diffs are throw
s. If a method does nothing but throw a simple arg, inlining it is probably beneficial.
I did not change this in this round, but I remain open to changing it if you have a stronger opinion.
@@ -801,7 +798,7 @@ private final class ClassDefChecker(classDef: ClassDef, | |||
|
|||
checkApplyGeneric(method, args) | |||
|
|||
case UnaryOp(_, lhs) => | |||
case UnaryOp(op, lhs) => |
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. Reverted.
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.
LGTM (after squashing obviously).
`ArrayLength`, `GetClass`, `Clone`, `IdentityHashCode`, `WrapAsThrowable`, `UnwrapFromThrowable` and `Throw` are now ops of `UnaryOp`, rather than having their own IR nodes. They all follow the general contract of how a `UnaryOp` must behave: first evaluate the argument, then perform an operation that only depends on the value of the argument (and not, for example, on the scope). It makes the JS `FunctionEmitter` a bit messier. However, everything else gets simpler, as we get rid of dedicated paths for those 7 IR nodes. In order to better fit with the other `UnaryOp`s, notably the ones for `jl.Class` operations, we demand non-nullable arguments. An explicit `CheckNotNull` must be used for arguments that are nullable. --- In this commit, we do not update the compiler yet. It still emits the old IR nodes, so that we can test the deserialiation hacks. That also means the case classes still exist for now.
And update the compiler to directly generate the new `UnaryOp`s. We only enable the deserialization hack when reading IR <= 1.17.
(rebased to address conflicts in |
And port the changes that were made to the compiler backend. Most nobably, the changes from the following upstream PRs: * Introduce non-nullable reference types in the IR. scala-js/scala-js#5018 * Opt: Remove useless *Ident indirections in the IR model. scala-js/scala-js#5092 * Merge the IR node This into VarRef, with a magic LocalName. scala-js/scala-js#5090 * Merge several IR nodes into UnaryOp. scala-js/scala-js#5088 * Restrict usages of StoreModule even further. scala-js/scala-js#5059 * Allow Return arg to be a void if the target Labeled is a void. scala-js/scala-js#5074 * Rename NoType to VoidType and print it as "void". scala-js/scala-js#5061
And port the changes that were made to the compiler backend. Most notably, the changes from the following upstream PRs: * Introduce non-nullable reference types in the IR. scala-js/scala-js#5018 * Opt: Remove useless *Ident indirections in the IR model. scala-js/scala-js#5092 * Merge the IR node This into VarRef, with a magic LocalName. scala-js/scala-js#5090 * Merge several IR nodes into UnaryOp. scala-js/scala-js#5088 * Restrict usages of StoreModule even further. scala-js/scala-js#5059 * Allow Return arg to be a void if the target Labeled is a void. scala-js/scala-js#5074 * Rename NoType to VoidType and print it as "void". scala-js/scala-js#5061
And port the changes that were made to the compiler backend. Most notably, the changes from the following upstream PRs: * scala-js/scala-js#5018 * scala-js/scala-js#5092 * scala-js/scala-js#5090 * scala-js/scala-js#5088 * scala-js/scala-js#5059 * scala-js/scala-js#5074 * scala-js/scala-js#5061
ArrayLength
,GetClass
,Clone
,IdentityHashCode
,WrapAsThrowable
,UnwrapFromThrowable
andThrow
are now ops ofUnaryOp
, rather than having their own IR nodes.They all follow the general contract of how a
UnaryOp
must behave: first evaluate the argument, then perform an operation that only depends on the value of the argument (and not, for example, on the scope).It makes the JS
FunctionEmitter
a bit messier. However, everything else gets simpler, as we get rid of dedicated paths for those 7 IR nodes.In order to better fit with the other
UnaryOp
s, notably the ones forjl.Class
operations, we demand non-nullable arguments. An explicitCheckNotNull
must be used for arguments that are nullable.In this commit, we do not update the compiler yet. It still emits the old IR nodes, so that we can test the deserialiation hacks. That also means the case classes still exist for now.