-
Notifications
You must be signed in to change notification settings - Fork 396
Introduce NewLambda to synthesize instances of SAM types. #5003
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
Merged
Merged
Changes from 1 commit
Commits
Show all changes
2 commits
Select commit
Hold shift + click to select a range
File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Prev
Previous commit
Introduce NewLambda to synthesize instances of SAM types.
The `NewLambda` node creates an instance of an anonymous class from a `Descriptor` and a closure `fun`. The `Descriptor` specifies the shape of the anonymous class: a super class, a list of interfaces to implement, and the name of a single non-constructor method to provide. The body of the method calls the `fun` closure. At link time, the Analyzer and BaseLinker synthesize a unique such anonymous class per `Descriptor`. In practice, all the lambdas for a given target type share a common `Descriptor`. This is notably the case for all the Scala functions of arity N. `NewLambda` replaces the need for special `AnonFunctionN` classes in the library. Instead, classes of the right shape are synthesized at link-time. The scheme can also be used for most LambdaMetaFactory-style lambdas, although our `NewLambda` does not support bridge generation. In the common case where no bridges are necessary, we now also generate a `NewLambda`. This generalizes the code size optimization of having only one class per `Descriptor` to non-Scala functions. In order to truly support LMF-style lambdas, the closure `fun` must take parameters that match the (erased) type in their superinterface. Previously, for Scala `FunctionN`, we knew by construction that the parameters and result types were always `any`, and so JS `Closure`s were good enough. Now, we need closures that can accept different types. This is where typed closures come into play (see below). --- When bridges are required, we still generate a custom class from the compiler backend. In that case, we statically inline the closure body in the produced SAM implementation. We have to do this not to expose typed closures across method calls. Moreover, we need the better static types for the parameters to be able to inline the closures without too much hassle. So this change *has* to be done in lockstep with the rest of this commit. --- A typed closure is a `Closure` that does not have any semantics for JS interop. This is stronger than `Char`, which is "merely" opaque to JS. A `Char` can still be passed to JS and has a meaningful `toString()`. A typed closure *cannot* be passed to JS in any way. That is enforced by making their type *not* a subtype of `any` (like record types). Since a typed closure has no JS interop semantics, it is free to strongly, statically type its parameters and result type. Additionally, we can freely choose its representation in the best possible way for the given target. On JS, that remains an arrow function. On Wasm, however, we represent it as a pair of `(capture data pointer, function pointer)`. This allows to compile them in an efficient way that does not require going through a JS bridge closure. The latter has been shown to have a devastating impact on performance when a Scala function is used in a tight loop. The type of a typed closure is a `ClosureType`. It records its parameter types and its result type. Closure types are non-variant: they are only subtypes of themselves. As mentioned, they are not subtypes of `any`. They are however subtypes of `void` and supertypes of `nothing`. Unfortunately, they must also be nullable to have a default value, so they have nullable and non-nullable alternatives. To call a typed closure, we introduce a dedicated application node `ApplyTypedClosure`. IR checking ensures that actual arguments match the expected parameter types. The result type is directly used as the type of the application. There are no changes to the source language. In particular, there is no way to express typed closures or their types at the user level. They are only used for `NewLambda` nodes. In fact, typed closures and `ApplyTypedClosure`s are not first-class at the IR level. Before desugaring, typed closures are only allowed as direct children of `NewLambda` nodes. Desugaring transforms `NewLambda` nodes into `New`s of the synthesized anonymous classes. At that point, the two typed closure nodes become first-class expression trees. --- For Scala functions, these changes have no real impact on the JS output (only marginal naming differences). On Wasm, however, they make Scala functions much, much faster. Before, a Scala function in a tight loop would cause a Wasm implementation to be, in the worst measured case, 20x slower than on JS. After these changes, similar benchmarks become significantly faster on Wasm than on JS.
- Loading branch information
commit 53dc4fe54568acfeb1fd31e89030e98a538bd9c6
There are no files selected for viewing
338 changes: 224 additions & 114 deletions
338
compiler/src/main/scala/org/scalajs/nscplugin/GenJSCode.scala
Large diffs are not rendered by default.
Oops, something went wrong.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.