-
Notifications
You must be signed in to change notification settings - Fork 395
Enable the optimizer with WebAssembly. #4993
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
43167ba
to
00c9b29
Compare
019b27c
to
367d63a
Compare
367d63a
to
d2e1a37
Compare
5ba5398
to
13a46b9
Compare
427dd73
to
909e82a
Compare
This is now ready for review. The commits are supposed to be meaningful, i.e., not squashed. Also review by @tanishiking, but I cannot assign you. |
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.
Partial review up to "enable intrinsics" (exclusive).
linker/shared/src/main/scala/org/scalajs/linker/standard/CommonPhaseConfig.scala
Outdated
Show resolved
Hide resolved
linker/shared/src/main/scala/org/scalajs/linker/backend/wasmemitter/FunctionEmitter.scala
Outdated
Show resolved
Hide resolved
linker/shared/src/main/scala/org/scalajs/linker/backend/wasmemitter/FunctionEmitter.scala
Show resolved
Hide resolved
linker/shared/src/main/scala/org/scalajs/linker/backend/wasmemitter/FunctionEmitter.scala
Outdated
Show resolved
Hide resolved
linker/shared/src/main/scala/org/scalajs/linker/backend/wasmemitter/FunctionEmitter.scala
Show resolved
Hide resolved
909e82a
to
4851517
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.
Left several questions, overall it looks good to me :)
genTreeAuto(tree.record) | ||
|
||
markPosition(tree) | ||
|
||
val tempTypes = transformResultType(tree.tpe) | ||
val tempLocals = tempTypes.map(addSyntheticLocal(_)) | ||
|
||
val recordType = tree.record.tpe.asInstanceOf[RecordType] | ||
for (recordField <- recordType.fields.reverseIterator) { | ||
if (recordField.name == tree.field.name) { | ||
// Store this one in our temp locals | ||
for (tempLocal <- tempLocals.reverseIterator) | ||
fb += wa.LocalSet(tempLocal) | ||
} else { | ||
// Discard this field | ||
for (_ <- transformResultType(recordField.tpe)) | ||
fb += wa.Drop | ||
} | ||
} | ||
|
||
// Read back our locals | ||
for (tempLocal <- tempLocals) | ||
fb += wa.LocalGet(tempLocal) | ||
} |
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.
[note]
When the tree looks like
RecordSelect({
"x": ...,
"y": {"a": ..., "b": ...},
"z": {"z1": ..., "z2": ...}
}, "y")
The resulting code looks like
;; tempTypes = [(type of "a"), (type of "b")]
;; tempLocals = [local a, local b]
;; push value for "x"
;; push ... "a"
;; push ... "b"
;; push ... "z1"
;; push ... "z2"
;; drop values for "z" for else branch
drop ;; z2
drop ;; z1
;; field found, local.set values
local.set $b
local.set $a
;; drop values for "x"
drop
;; read back locals
local.get $a
local.get $b
linker/shared/src/main/scala/org/scalajs/linker/frontend/optimizer/OptimizerCore.scala
Show resolved
Hide resolved
linker/shared/src/main/scala/org/scalajs/linker/backend/wasmemitter/FunctionEmitter.scala
Show resolved
Hide resolved
linker/shared/src/main/scala/org/scalajs/linker/backend/wasmemitter/CoreWasmLib.scala
Outdated
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
Show resolved
Hide resolved
4851517
to
a133a94
Compare
linker/shared/src/main/scala/org/scalajs/linker/backend/wasmemitter/WasmTransients.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/backend/wasmemitter/WasmTransients.scala
Show resolved
Hide resolved
} | ||
|
||
case IntegerRotateLeft => | ||
contTree(wasmBinaryOp(WasmBinaryOp.I32Rotl, targs.head, targs.tail.head)) |
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 the rationale to not constant fold here (but above)?
Also, instead of targs.tail.head
, destructure targs
:
contTree(wasmBinaryOp(WasmBinaryOp.I32Rotl, targs.head, targs.tail.head)) | |
val List(arg0, arg1) = targs | |
contTree(wasmBinaryOp(WasmBinaryOp.I32Rotl, arg0, arg1)) |
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.
We should constant-fold, indeed.
genericWasmDivModUnsigned(WasmBinaryOp.I32DivU, BinaryOp.Int_/, | ||
BinaryOp.Int_==, IntLiteral(0)) | ||
case IntegerRemainderUnsigned => | ||
genericWasmDivModUnsigned(WasmBinaryOp.I32RemU, BinaryOp.Int_/, |
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.
genericWasmDivModUnsigned(WasmBinaryOp.I32RemU, BinaryOp.Int_/, | |
genericWasmDivModUnsigned(WasmBinaryOp.I32RemU, BinaryOp.Int_%, |
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/standard/CommonPhaseConfig.scala
Outdated
Show resolved
Hide resolved
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.
Left a comment, otherwise LGTM from my side :)
a133a94
to
8ef4404
Compare
This will be used to provide that information to the optimizer as well as to user-space through linking info. Therefore, it is an observable piece of knowledge that belongs to the core spec.
`StructField` is only used for captures, which are always immutable.
However, it does box `long`s into instances, which means that two boxed longs are not `===`.
We can selectively reintroduce them later.
The WebAssembly backend and the optimizer are now happy to work together, so we can enable the latter.
When compiling to Wasm, the optimized implementation we have for `ArrayBuilder` makes no sense, as it relies on JavaScript arrays. Therefore, we revert to using the original implementations when linking for Wasm.
This includes `arraycopy` and `getClass().getName()`, which produce new `Transient` nodes that we need to handle in the Wasm backend.
The motivation is mainly to get intrinsics for the bit-conversions between integers and floating point numbers, as well as for `numberLeadingZeros`. These are building blocks for many other low-level operations, and their JS-builtin-based implementation is really bad on Wasm for those use cases. Once we have the infrastructure for those as transients in the Wasm backend, we also take the opportunity to add a series of other methods that have a direct Wasm opcode equivalent.
The implementation based on `FloatingPointBits` is poor for a Wasm output, as it uses JS interop. Given that we have an intrinsic for `doubleToLongBits`, we can implement it much more efficiently for Wasm. This is important because this function is called to compute the hash code of all JS `number`s, including those that originate as `Int`s.
When we resolve a dynamic call to a single possible target, but we do not inline it, we now replace the `Apply` by an `ApplyStatically`. This communicates the result of the resolution to the backend, which can emit a static `call` instead of a vtable or itable-based dispatch.
These were one of two remaining sources of `Apply` nodes that the backend could turn into `ApplyStatically` but that the optimizer could not (because they are created after the optimizer).
The optimizer already replaces `Apply` nodes that can be statically resolved by `ApplyStatically`. The `isEffectivelyFinal` analysis is therefore not useful anymore. Technically, it was still applicable to JS property/method *names* that are not string constants, but that is too niche to justify keeping the analysis. Also, arguably the correct fix for that would be to make the optimizer also optimize JS property/method names, since it would also benefit the JS backend.
`ClassInfo` is now completely immutable.
8ef4404
to
828f90b
Compare
I've checked that @tanishiking's pending comment is also addressed, so I'll proceed with merging this. |
No description provided.