-
Notifications
You must be signed in to change notification settings - Fork 395
LinkTimeIf - on top of desugaring in BaseLinker #5100
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
Closed
Closed
Conversation
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
I'm glad to see you are digging this up. We need to make sure that whatever comes out of #5096 is compatible with this. |
Previously, the emitters and the optimizer all had to perform the same desugaring for `LinkTimeProperty` nodes. Instead, we now perform the desugaring in the `BaseLinker`, after the reachability analysis. The reachability analysis records whether each method needs desugaring or not. Methods that do not require desugaring are not processed, and so incur no additional cost. Since very few methods need desugaring, we do not cache the results. The machinery is heavy. It definitely outweighs the benefits in terms of duplication for `LinkTimeProperty` alone. However, the same machinery will be used to desugar `NewLambda` nodes. This commit serves as a stepping stone in that direction.
As well as the IR version to 1.19-SNAPSHOT.
Thanks to our optimizer's ability to inline, constant-fold, and then eliminate dead code, we have been able to write link-time conditional branches for a long time. Typical examples include polyfills, as illustrated in the documentation of `LinkingInfo`: if (esVersion >= ESVersion.ES2018 || featureTest()) useES2018Feature() else usePolyfill() which gets folded away to nothing but useES2018Feature() when linking for ES2018+. However, this only works because both branches can *link* during the initial reachability analysis. We cannot use the same technique when one of the branches would refuse to link in the first place. The canonical example is the usage of the JS `**` operator, which does not link under ES2016. The following snippet produces good code when linking for ES2016+, but does not link at all for ES2015: def pow(x: Double, y: Double): Double = { if (esVersion >= ESVersion.ES2016) { (x.asInstanceOf[js.Dynamic] ** y.asInstanceOf[js.Dynamic]).asInstanceOf[Double] } { Math.pow(x, y) } } --- This commit introduces `LinkingInfo.linkTimeIf`, a conditional branch that is guaranteed by spec to be resolved at link-time. Using a `linkTimeIf` instead of the `if` in `def pow`, we can successfully link the fallback branch on ES2015, because the then branch is not even followed by the reachability analysis. In order to provide that guarantee, the corresponding `LinkTimeIf` IR node has strong requirements on its condition. It must be a "link-time expression", which is guaranteed to be resolved at link-time. A link-time expression tree must be of the form: * A `Literal` (of type `int`, `boolean` or `string`, although `string`s are not actually usable here). * A `LinkTimeProperty`. * One of the boolean operators. * One of the int comparison operators. * A nested `LinkTimeIf` (used to encode short-circuiting boolean `&&` and `||`). The `ClassDefChecker` validates the above property, and ensures that link-time expression trees are *well-typed*. Normally that is the job of the IR checker. Here we *can* do in `ClassDefChecker` because we only have the 3 primitive types to deal with; and we *must* do it then, because the reachability analysis itself is only sound if all link-time expression trees are well-typed. The reachability analysis algorithm itself is not affected by `LinkTimeIf`. Instead, we resolve link-time branches when building the `Infos` of methods. We follow only the branch that is taken. This means that `Infos` builders now require the `coreSpec`, but that is the only additional piece of complexity in that area. `LinkTimeIf`s nodes are later removed from the trees during desugaring. --- At the language and compiler level, we introduce `LinkingInfo.linkTimeIf` as a primitive for `LinkTimeIf`. We need a dedicated method to compile link-time expression trees, which does incur some duplication, unfortunately. Other than that, `linkTimeIf` is straightforward, by itself. The problem is that the whole point of `linkTimeIf` is that we can refer to *link-time properties*, and not just literals. However, our link-time properties are all hidden behind regular method calls, such as `LinkInfo.esVersion`. For optimizer-based branching with `if`s, that is fine, as the method is always inlined, and the optimizer can then see the constant. However, for `linkTimeIf`, that does not work, as it does not follow the requirements of a link-time expression tree. If we were on Scala 3 only, we could declare `esVersion` and its friends as an `inline def`, as follows: inline def esVersion: Int = linkTimePropertyInt("core/esVersion") The `inline` keyword is guaranteed by the language to be resolved at *compile*-time. Since the `linkTimePropertyInt` method is itself a primitive replaced by a `LinkTimeProperty`, by the time we reach our backend, we would see the latter, and all would be well. The same cannot be said for the `@inline` optimizer hint, which is all we have. We therefore another language-level feature: `@linkTimeProperty`. This annotation can (currently) only be used in our own library. By contract, it must only be used on a method whose body is the corresponding `linkTimePropertyX` primitive. With it, we can define `esVersion` as: @inline @linkTimeProperty("core/esVersion") def esVersion: Int = linkTimePropertyInt("core/esVersion") That annotation makes the body public, in a way. That means the compiler back-end can now replace *call sites* to `esVersion` by the `LinkTimeProperty`. Semantically, `@linkTimeProperty` does nothing more than guaranteed inlining (with strong restrictions on the shape of body). Co-authored-by: Rikito Taniguchi <[email protected]>
We use a `linkTimeIf` to select a `bigint`-based implementation of `parseFloatDecimalCorrection` when they are supported. We need a `linkTimeIf` in this case because it uses the JS `**` operator, which does not link below ES 2016. The `bigint`-based implementation avoids bringing in the entire `BigInteger` implementation, which is a major code size win if that was the only reason `BigInteger` was needed.
Superseded by #5110. |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
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.
Reboot of #5000