-
Notifications
You must be signed in to change notification settings - Fork 395
#4997 Allow for link-time dispatching #5000
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
linker/shared/src/test/scala/org/scalajs/linker/OptimizerTest.scala
Outdated
Show resolved
Hide resolved
library/src/main/scala/scala/scalajs/js/annotation/LinkTime.scala
Outdated
Show resolved
Hide resolved
library/src/main/scala/scala/scalajs/js/annotation/LinkTime.scala
Outdated
Show resolved
Hide resolved
linker-interface/shared/src/main/scala/org/scalajs/linker/interface/LinkTimeProperties.scala
Outdated
Show resolved
Hide resolved
linker-interface/shared/src/main/scala/org/scalajs/linker/interface/LinkTimeProperties.scala
Outdated
Show resolved
Hide resolved
linker/shared/src/main/scala/org/scalajs/linker/standard/CoreSpec.scala
Outdated
Show resolved
Hide resolved
linker/shared/src/main/scala/org/scalajs/linker/analyzer/Analyzer.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
Outdated
Show resolved
Hide resolved
514f5f3
to
1308259
Compare
final case class BooleanConst(v: Boolean) extends LinkTimeValue { | ||
val tpe = BooleanType | ||
} | ||
} |
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.
Does it make sense to attempt to re-use trees here? (and maybe have a marker trait?)
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.
You mean reusing BooleanLiteral
s here?
I believe it's more appropriate to have separate trees for this use-case, even if the data these trees hold is same as Literal trees.
The reason is that the "runtime behavior" of Literal
s and these (Boolean|Int)Constant
s are different. The latter appear only in linkTimeIf
statements and are evaluated by the linker, not at runtime."
Ended up like this, and maybe LinkTimeTree
works as a marker trait?
sealed abstract class LinkTimeTree {
val pos: Position
val tpe: Type
}
object LinkTimeTree {
final case class BinaryOp(op: LinkTimeOp.Code, lhs: LinkTimeTree,
rhs: LinkTimeTree)(implicit val pos: Position) extends LinkTimeTree {
val tpe = BooleanType
}
final case class Property(name: String, tpe: Type)(implicit val pos: Position)
extends LinkTimeTree
final case class IntConst(v: Int)(implicit val pos: Position) extends LinkTimeTree {
val tpe = IntType
}
final case class BooleanConst(v: Boolean)(implicit val pos: Position) extends LinkTimeTree {
val tpe = BooleanType
}
}
850c873
to
7a623c8
Compare
linker/shared/src/main/scala/org/scalajs/linker/standard/LinkTimeProperties.scala
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.
Review of compiler/
and ir/
so far.
None | ||
|
||
// if(!foo()) (...) | ||
case Apply(Select(Apply(prop, Nil), nme.UNARY_!), Nil) => |
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 is oddly specific. Can't we generically handle Apply(Select(operand, nme.UNARY_!), Nil)
irrespective of the operand
?
Also, we should check the symbol of the application, to verify that it is indeed scala.Boolean.unary_!
, and not some other method with the same name.
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 is oddly specific. Can't we generically handle Apply(Select(operand, nme.UNARY_!), Nil) irrespective of the operand?
Ah, that's true, and it didn't work for the case that !
is applied to non-link time property like !(esVersion == ESVersion.ES2015)
. fixed in d189e47
Also, we should check the symbol of the application, to verify that it is indeed scala.Boolean.unary_!, and not some other method with the same name.
Oops, it's not yet checked. How can we test if the symbol is a specific symbol? Is there any other way than sym.fullName == "scala.Boolean.unary_$bang"
?
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.
You can check the symbols against the ones defined in definitions
(part of scalac's codebase).
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 pushed commits that addresses the review so it's easy to find diff, but I'll squash them into the first commit before merging |
/** Link-time conditional branching. | ||
* | ||
* The `linkTimeIf` expression will be evaluated at link-time, and only the |
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.
/** Link-time conditional branching. | |
* | |
* The `linkTimeIf` expression will be evaluated at link-time, and only the | |
/** Link-time conditional branching. | |
* | |
* The `linkTimeIf` expression will be evaluated at link-time, and only the |
and the same for the following lines.
* removed during the linking process. | ||
* | ||
* The condition `cond` can be constructed using: | ||
* - Symbols annotated with `@LinkTime` |
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.
* - Symbols annotated with `@LinkTime` | |
* - Symbols annotated with `@linkTimeProperty` |
* | ||
* In this example, if `LinkingInfo.productionMode` is `true`, the first | ||
* branch will be linked, and the second branch will be removed. | ||
* Consequently, the runtime code looks like: |
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.
* Consequently, the runtime code looks like: | |
* Consequently, the run-time code looks like: |
"runtime" vs "run-time" is a subtle thing that confuses even native English speakers. The former is a noun and can refer to a particular VM or something like that. The latter is an adjective. The rule of thumb is: is the sentence still well-formed if I replace it with "compile-time"? If yes, it's "run-time"; if not, it's "runtime".
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.
Thanks for the good explanation! That's what always confused me. 😅
* - Integer or boolean constants | ||
* - Binary operators that return a boolean value | ||
* | ||
* Example usage: |
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 suggest showing an example with esVersion
instead, as it illustrates the usage of a binary operator as well. In addition, we can show a concrete example that relies on using the **
operator of JavaScript on esVersion >= ESVersion.ES2016
. That is a good, concrete motivation for the feature.
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.
private[scalajs] class linkTimeProperty extends scala.annotation.StaticAnnotation { | ||
def this(name: String) = this() |
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.
Let's make the name
non-optional by putting it directly in the primary constructor.
// linkTimeIf(productionMode) { | ||
// this.foo() | ||
// } { | ||
// this.bar() | ||
// } |
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.
// linkTimeIf(productionMode) { | |
// this.foo() | |
// } { | |
// this.bar() | |
// } | |
/* linkTimeIf(productionMode) { | |
* this.foo() | |
* } { | |
* this.bar() | |
* } | |
*/ |
// TODO: it fails at IR cheker | ||
// (1:1:New): Cannot find class Foo |
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 is probably not true anymore?
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.
Thanks. Right, it's no longer the case, because now IRChecker takes linkTimeIf
consideration.
findClass(moduleSet, MainTestClassName.nameString).get | ||
.methods.find(_.name.name == MainMethodName).get | ||
.body.get match { | ||
case t if t == consoleLog(str("prod")) => |
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.
Comparing complex trees for equality is fragile. Trees don't have an ==
we rely on.
Consider using simpler trees (like two distinct IntLiteral
s), then match with case IntLiteral(5) =>
for example. That does not rely on tree equality.
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.
b93d2d9 👍
private val esVersion = | ||
p("core/esVersion", jstpe.IntType) | ||
|
||
private implicit val nopos: Position = Position.NoPosition |
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.
private implicit val nopos: Position = Position.NoPosition | |
private implicit val noPos: Position = Position.NoPosition |
to be consistent with other parts of the codebase.
linkTimeIf(true) { /* ok */ } { fail() } | ||
linkTimeIf(false) { fail() } { /* ok */ } |
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 is weaker than we can hope for because the test will pass if linkTimeIf
is entirely removed along the pipeline.
Consider something like
assertEquals(1, linkTimeIf(true) { 1 } { 2 })
The same applies to the other tests.
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.
Thanks, fixed in aa89d31
283e395
to
d8602d8
Compare
I think I have addressed everything :) |
Where are we (the Scala.js project) in terms of committing to this? IIUC true link time dispatching is not necessary for optimizations: Inlining / folding in the optimizer is sufficient (and used a lot) to optimize behavior at link time and dead code eliminate unneeded things. This is as long as the eliminated code branch also links (which for optimizations is typically the case). My concern here is that, unlike the WASM backend, we're committing to a new public API which we'll have to maintain. (it is true that e.g. #4998 is proposing public API / IR changes motivated by WASM, but the changes themselves - IMO - improve the IR spec either way). I'm sorry this comment comes quite late on this PR. I have been focusing my attention on getting the initial WASM PR merged. |
This has yet to be agreed on, indeed. There are at least two things we can do with link-time tests for JS only, that we cannot do with link-time optimizations. They boil down to the same root cause: things where the alternative wouldn't link in the non-chosen branch.
If we generalize to user-defined link-time properties, I expect people will start using it for linking different code---including different |
Thanks @gzm0 for your comment! You're right that eliminating dead branches is typically handled by the optimizer. However, as Sebastien pointed out, there are use cases where conditional linking is essential, as it ensures that dead branches are eliminated at link-time, even without an optimizer. While this feature was initially introduced for the Wasm backend, I believe that it is self-contained and would also be beneficial for the JS backend, and worth changing the IR. |
d53cbd2
to
239abf0
Compare
I've been thinking about this, and also in the context of #4993 (comment) . I think I've found a more holistic design. It would be composed of two steps:
It could possibly extend The only sub-property of
Its The advantages of this approach are:
For backward compatibility of
@gzm0 @tanishiking How does that sound? |
@sjrd The current approach requires adding all In that sense, I like your idea 👍 That change may mitigate the @gzm0's concern about the IR changes. Regarding No separate structure for link-time trees -> codegen is simplified. The current complexity of the code generation (in Confirmation questions
|
Right, maybe not. It depends how it's done.
Yes, that's right.
Since it is very likely that we'll want custom ones at some point, IMO we can keep the annotation to leave the compiler generic.
Yes.
It's not impossible, but if we do that there is no good reason to introduce the deserialization hack now even for the non-isolated ones. So then it would make more sense to keep the extraction in the optimizer as well. It's not that complicated, TBH: scala-js/linker/shared/src/main/scala/org/scalajs/linker/frontend/optimizer/OptimizerCore.scala Lines 4810 to 4832 in ede4dc1
|
I think we should definitely do that. That
I would drop these semantics. The What isn't clear to me yet, is how we manage the allowed
Regarding that, I'm not sure @sjrd's proposal changes a lot w.r.t. to my concerns about this TBH: Even if the interface becomes simpler (say, because we can re-use trees) than proposed in this PR, we still commit to a significant additional feature. Regarding the use cases for this outside of WASM (collected from comments, please forgive me if I missed one):
Fair, I can see that. For context: Judging from the errors in
Hum, wouldn't the current features deal just fine with this? Basically, define different
Yes, I can see that. And I agree that having a guarantee is better than it "working by happenstance". |
Right ... kind of. But then a codebase that relies on that would only work when the optimizer is enabled. That's not great if you need to troubleshoot an optimizer issue, or if you want to debug your program without the optimizer enabled (for a better one-to-one mapping of the source code to the target, which can notably improve stack traces). |
The use case is to get rid of |
Ooh, right, I forgot about it, thanks 😅
From #4991 point of view, I think we can proceed without So the question for now would be, do we want reliable link-time dispatch ( |
scala-js#4997 This commit introduces linktime dispatching with a new `LinkTimeIf` IR node. The condition of `LinkTimeIf` will be evaluated at link-time and the dead branch be eliminated at link-time by Optimizer or linker backend. For example, ```scala import scala.scalajs.LikningInfo._ val env = linkTimeIf(productionMode) { "prod" } { "dev" } ``` The code above under `.withProductionMode(true)` links to the following at runtime. ```scala val env = "prod" ``` This feature was originally motivated to allow switching the library implementation based on whether it targets browser Wasm or standalone Wasm (see scala-js#4991). However, it should prove useful for further optimization through link-time information-based dispatching. **`LinkTimeIf` IR Node** This change introduces a new IR node `LinkTimeIf(cond: LinkTimeTree, thenp: Tree, elsep: Tree)`, that represents link-time dispatching. `LinkTimeTree` is a small set of IR tree evaluated at link-time. Currently, `LinkTimeTree` contains `BinaryOp`, `IntConst`, `BooleanConst`, and `Property`. - `BinaryOp` representing a simple binary operation that evaluates to a boolean value. - `IntConst` and `BooleanConst` holds a constant of the type. - `Property` contains a key to resolve a value at link-time, where `LinkTimeProperties.scala` is responsible for managing and resolving the link-time value dictionary, which is accessible through `CoreSpec`. For example, the following `LinkTimeIf` looks up the link-time value whose key is "scala.scalajs.LinkingInfo.esVersion" and compares it with the integer constant 6. ```scala LinkTimeIf( BinaryOp( BinaryOp.Int_>=, Property("core/esVersion"), IntConst(6), ), thenp, elsep ) ``` **`LinkingInfo.linkTimeIf` and `@linkTimeProperty` annotation** This commit defines a new API to represent link-time dispatching: `LinkingInfo.linkTimeIf(...) { } { }`, which compiles to the `LinkTimeIf` IR node. For example, `linkTimeIf(esVersion >= ESVersion.ES2015)` compiles to the IR above. Note that only symbols annotated with `@linkTimeProperty` or int/boolean constants can be used in the condition of `linkTimeIf`. Currently, `@linkTimeProperty` is private to `scalajs` (users cannot define new link-time values), and only a predefined set of link-time values are annotated (`productionMode` and `esVersion` for now). When `@linkTimeProperty(name)` annotated values are used in `linkTimeIf`, they are translated to `LinkTimeValue.Property(name)`. **LinkTimeProperties to resolve and evaluate LinkTimeCondition/Value** This commit defines a `LinkTimeProperty` that belongs to the `CoreSpec` (making it accessible from various linker stages). It constructs a link-time value dictionary from `Semantics` and `ESFeatures`, and is responsible for resolving `LinkTimeValue.Property` and evaluating `LinkTimeTree`. **Analyzer doesn't follow the dead branch of linkTimeIf** Now `Analyzer` evaluates the `LinkTimeIf` and follow only the live branch. For example, under `productionMode = true`, `doSomethingDev` won't be marked as reachable by `Analyzer`. ```scala linkTimeIf(productionMode) { doSomethingProd() } { doSomethingDev() } ``` **Eliminate dead branch of LinkTimeIf** Finally, the optimizer and linker-backends (in case the optimizer is turned off) eliminate the dead branch of `LinkTimeIf`.
For reference, I prototyped the idea of |
ref: scala-js#5000 Previously, accessing link-time information via `scala.scalajs.runtime.linkingInfo` required interacting with a JavaScript object. This introduced JS interop in the Wasm backend causing a slowdown and could hinder the optimization pipeline (In fact, we folded the access to `JSLinkingInfo` into `Literal`s in `OptimizerCore`). This commit eliminates the use of JS object access for retrieving link-time information via `scala.scalajs.LinkingInfo` by: - Introducing a new IR node `LinkTimeProperty`, which is guaranteed to be transformed into a `Literal` at the optimizer or backend. Additionally, adding new APIs to `scala.scalajs.LinkingInfo.linkTimePropertyXXX`, defining a new `LinkTimeProperty`. - Updating `scala.scalajs.LinkingInfo` to use `linkTimePropertyXXX` instead of `runtime.linkingInfo`, allowing us to remove the JS object access for link-time information. - Making `js.special.fileLevelThis` primitive so that it no longer refers to `runtime.linkingInfo`. - Deprecating `scala.scalajs.runtime.linkingInfo` in favor of `scala.scalajs.LinkingInfo`.
ref: scala-js#5000 Previously, accessing link-time information via `scala.scalajs.runtime.linkingInfo` required interacting with a JavaScript object. This introduced JS interop in the Wasm backend causing a slowdown and could hinder the optimization pipeline (In fact, we folded the access to `JSLinkingInfo` into `Literal`s in `OptimizerCore`). This commit eliminates the use of JS object access for retrieving link-time information via `scala.scalajs.LinkingInfo` by: - Introducing a new IR node `LinkTimeProperty`, which is guaranteed to be transformed into a `Literal` at the optimizer or backend. Additionally, adding new APIs to `scala.scalajs.LinkingInfo.linkTimePropertyXXX`, defining a new `LinkTimeProperty`. - Updating `scala.scalajs.LinkingInfo` to use `linkTimePropertyXXX` instead of `runtime.linkingInfo`, allowing us to remove the JS object access for link-time information. - Making `js.special.fileLevelThis` primitive so that it no longer refers to `runtime.linkingInfo`. - Deprecating `scala.scalajs.runtime.linkingInfo` in favor of `scala.scalajs.LinkingInfo`.
ref: scala-js#5000 Previously, accessing link-time information via `scala.scalajs.runtime.linkingInfo` required interacting with a JavaScript object. That introduced JS interop in the Wasm backend causing a slowdown, and could hinder the optimization pipeline (In fact, we folded the access to `JSLinkingInfo` into `Literal`s in `OptimizerCore`). `LinkTimeProperty` is a new IR node that is guaranteed to be transformed into a `Literal` at the optimizer or backend stage. We plan to introduce a new primitive, such as `linkTimePropertyXXX`, which will be transformed into a `LinkTimeProperty` in a later commit. Additionally, we will update `scala.scalajs.LinkingInfo` to use `linkTimePropertyXXX` instead of `runtime.linkingInfo`, allowing us to eliminate the JS object when accessing link-time information. This commit also deprecates the `JSLinkingInfo` IR node. For backward compatibility, we introduced a deserialization hack that transforms `JSSelect(JSLinkingInfo(), StringLiteral(...))` into the corresponding `LinkTimeProperty`. An isolated `JSLinkingInfo` will be deserialized into a `JSObjectConstr()` containing the corresponding `LinkTimeProperty` values.
ref: scala-js#5000 Previously, accessing link-time information via `scala.scalajs.runtime.linkingInfo` required interacting with a JavaScript object. That introduced JS interop in the Wasm backend causing a slowdown, and could hinder the optimization pipeline (In fact, we folded the access to `JSLinkingInfo` into `Literal`s in `OptimizerCore`). `LinkTimeProperty` is a new IR node that is guaranteed to be transformed into a `Literal` at the optimizer or backend stage. We plan to introduce a new primitive, such as `linkTimePropertyXXX`, which will be transformed into a `LinkTimeProperty` in a later commit. Additionally, we will update `scala.scalajs.LinkingInfo` to use `linkTimePropertyXXX` instead of `runtime.linkingInfo`, allowing us to eliminate the JS object when accessing link-time information. This commit also deprecates the `JSLinkingInfo` IR node. For backward compatibility, we introduced a deserialization hack that transforms `JSSelect(JSLinkingInfo(), StringLiteral(...))` into the corresponding `LinkTimeProperty`. An isolated `JSLinkingInfo` will be deserialized into a `JSObjectConstr()` containing the corresponding `LinkTimeProperty` values.
ref: scala-js#5000 Previously, accessing link-time information via `scala.scalajs.runtime.linkingInfo` required interacting with a JavaScript object. That introduced JS interop in the Wasm backend causing a slowdown, and could hinder the optimization pipeline (In fact, we folded the access to `JSLinkingInfo` into `Literal`s in `OptimizerCore`). `LinkTimeProperty` is a new IR node that is guaranteed to be transformed into a `Literal` at the optimizer or backend stage. We plan to introduce a new primitive, such as `linkTimePropertyXXX`, which will be transformed into a `LinkTimeProperty` in a later commit. Additionally, we will update `scala.scalajs.LinkingInfo` to use `linkTimePropertyXXX` instead of `runtime.linkingInfo`, allowing us to eliminate the JS object when accessing link-time information. This commit also deprecates the `JSLinkingInfo` IR node. For backward compatibility, we introduced a deserialization hack that transforms `JSSelect(JSLinkingInfo(), StringLiteral(...))` into the corresponding `LinkTimeProperty`. An isolated `JSLinkingInfo` will be deserialized into a `JSObjectConstr()` containing the corresponding `LinkTimeProperty` values.
ref: scala-js#5000 Previously, accessing link-time information via `scala.scalajs.runtime.linkingInfo` required interacting with a JavaScript object. That introduced JS interop in the Wasm backend causing a slowdown, and could hinder the optimization pipeline (In fact, we folded the access to `JSLinkingInfo` into `Literal`s in `OptimizerCore`). `LinkTimeProperty` is a new IR node that is guaranteed to be transformed into a `Literal` at the optimizer or backend stage. We plan to introduce a new primitive, such as `linkTimePropertyXXX`, which will be transformed into a `LinkTimeProperty` in a later commit. Additionally, we will update `scala.scalajs.LinkingInfo` to use `linkTimePropertyXXX` instead of `runtime.linkingInfo`, allowing us to eliminate the JS object when accessing link-time information. This commit also deprecates the `JSLinkingInfo` IR node. For backward compatibility, we introduced a deserialization hack that transforms `JSSelect(JSLinkingInfo(), StringLiteral(...))` into the corresponding `LinkTimeProperty`. An isolated `JSLinkingInfo` will be deserialized into a `JSObjectConstr()` containing the corresponding `LinkTimeProperty` values.
ref: scala-js#5000 Previously, accessing link-time information via `scala.scalajs.runtime.linkingInfo` required interacting with a JavaScript object. That introduced JS interop in the Wasm backend causing a slowdown, and could hinder the optimization pipeline (In fact, we folded the access to `JSLinkingInfo` into `Literal`s in `OptimizerCore`). `LinkTimeProperty` is a new IR node that is guaranteed to be transformed into a `Literal` at the optimizer or backend stage. We plan to introduce a new primitive, such as `linkTimePropertyXXX`, which will be transformed into a `LinkTimeProperty` in a later commit. Additionally, we will update `scala.scalajs.LinkingInfo` to use `linkTimePropertyXXX` instead of `runtime.linkingInfo`, allowing us to eliminate the JS object when accessing link-time information. This commit also deprecates the `JSLinkingInfo` IR node. For backward compatibility, we introduced a deserialization hack that transforms `JSSelect(JSLinkingInfo(), StringLiteral(...))` into the corresponding `LinkTimeProperty`. An isolated `JSLinkingInfo` will be deserialized into a `JSObjectConstr()` containing the corresponding `LinkTimeProperty` values. Also, this commit introduces validation for `LinkTimeProperty` during reachability analysis. The Analyzer now verifies that the `LinkTimeProperty` in the IR has a valid name and type pair, ensuring it can be resolved using the provided link-time information. If an invalid `LinkTimeProperty` is detected, an error will be recorded in the `Analysis`
ref: scala-js#5000 Previously, accessing link-time information via `scala.scalajs.runtime.linkingInfo` required interacting with a JavaScript object. That introduced JS interop in the Wasm backend causing a slowdown, and could hinder the optimization pipeline (In fact, we folded the access to `JSLinkingInfo` into `Literal`s in `OptimizerCore`). `LinkTimeProperty` is a new IR node that is guaranteed to be transformed into a `Literal` at the optimizer or backend stage. We plan to introduce a new primitive, such as `linkTimePropertyXXX`, which will be transformed into a `LinkTimeProperty` in a later commit. Additionally, we will update `scala.scalajs.LinkingInfo` to use `linkTimePropertyXXX` instead of `runtime.linkingInfo`, allowing us to eliminate the JS object when accessing link-time information. This commit also deprecates the `JSLinkingInfo` IR node. For backward compatibility, we introduced a deserialization hack that transforms `JSSelect(JSLinkingInfo(), StringLiteral(...))` into the corresponding `LinkTimeProperty`. An isolated `JSLinkingInfo` will be deserialized into a `JSObjectConstr()` containing the corresponding `LinkTimeProperty` values. Also, this commit introduces validation for `LinkTimeProperty` during reachability analysis. The Analyzer now verifies that the `LinkTimeProperty` in the IR has a valid name and type pair, ensuring it can be resolved using the provided link-time information. If an invalid `LinkTimeProperty` is detected, an error will be recorded in the `Analysis`
ref: scala-js#5000 Previously, accessing link-time information via `scala.scalajs.runtime.linkingInfo` required interacting with a JavaScript object. That introduced JS interop in the Wasm backend causing a slowdown, and could hinder the optimization pipeline (In fact, we folded the access to `JSLinkingInfo` into `Literal`s in `OptimizerCore`). `LinkTimeProperty` is a new IR node that is guaranteed to be transformed into a `Literal` at the optimizer or backend stage. We plan to introduce a new primitive, such as `linkTimePropertyXXX`, which will be transformed into a `LinkTimeProperty` in a later commit. Additionally, we will update `scala.scalajs.LinkingInfo` to use `linkTimePropertyXXX` instead of `runtime.linkingInfo`, allowing us to eliminate the JS object when accessing link-time information. This commit also deprecates the `JSLinkingInfo` IR node. For backward compatibility, we introduced a deserialization hack that transforms `JSSelect(JSLinkingInfo(), StringLiteral(...))` into the corresponding `LinkTimeProperty`. An isolated `JSLinkingInfo` will be deserialized into a `JSObjectConstr()` containing the corresponding `LinkTimeProperty` values. Also, this commit introduces validation for `LinkTimeProperty` during reachability analysis. The Analyzer now verifies that the `LinkTimeProperty` in the IR has a valid name and type pair, ensuring it can be resolved using the provided link-time information. If an invalid `LinkTimeProperty` is detected, an error will be recorded in the `Analysis`
ref: scala-js#5000 Previously, accessing link-time information via `scala.scalajs.runtime.linkingInfo` required interacting with a JavaScript object. That introduced JS interop in the Wasm backend causing a slowdown, and could hinder the optimization pipeline (In fact, we folded the access to `JSLinkingInfo` into `Literal`s in `OptimizerCore`). `LinkTimeProperty` is a new IR node that is guaranteed to be transformed into a `Literal` at the optimizer or backend stage. We plan to introduce a new primitive, such as `linkTimePropertyXXX`, which will be transformed into a `LinkTimeProperty` in a later commit. Additionally, we will update `scala.scalajs.LinkingInfo` to use `linkTimePropertyXXX` instead of `runtime.linkingInfo`, allowing us to eliminate the JS object when accessing link-time information. This commit also deprecates the `JSLinkingInfo` IR node. For backward compatibility, we introduced a deserialization hack that transforms `JSSelect(JSLinkingInfo(), StringLiteral(...))` into the corresponding `LinkTimeProperty`. An isolated `JSLinkingInfo` will be deserialized into a `JSObjectConstr()` containing the corresponding `LinkTimeProperty` values. Also, this commit introduces validation for `LinkTimeProperty` during reachability analysis. The Analyzer now verifies that the `LinkTimeProperty` in the IR has a valid name and type pair, ensuring it can be resolved using the provided link-time information. If an invalid `LinkTimeProperty` is detected, an error will be recorded in the `Analysis`
ref: scala-js#5000 Previously, accessing link-time information via `scala.scalajs.runtime.linkingInfo` required interacting with a JavaScript object. That introduced JS interop in the Wasm backend causing a slowdown, and could hinder the optimization pipeline (In fact, we folded the access to `JSLinkingInfo` into `Literal`s in `OptimizerCore`). `LinkTimeProperty` is a new IR node that is guaranteed to be transformed into a `Literal` at the optimizer or backend stage. We plan to introduce a new primitive, such as `linkTimePropertyXXX`, which will be transformed into a `LinkTimeProperty` in a later commit. Additionally, we will update `scala.scalajs.LinkingInfo` to use `linkTimePropertyXXX` instead of `runtime.linkingInfo`, allowing us to eliminate the JS object when accessing link-time information. This commit also deprecates the `JSLinkingInfo` IR node. For backward compatibility, we introduced a deserialization hack that transforms `JSSelect(JSLinkingInfo(), StringLiteral(...))` into the corresponding `LinkTimeProperty`. An isolated `JSLinkingInfo` will be deserialized into a `JSObjectConstr()` containing the corresponding `LinkTimeProperty` values. Also, this commit introduces validation for `LinkTimeProperty` during reachability analysis. The Analyzer now verifies that the `LinkTimeProperty` in the IR has a valid name and type pair, ensuring it can be resolved using the provided link-time information. If an invalid `LinkTimeProperty` is detected, an error will be recorded in the `Analysis`
ref: scala-js#5000 Previously, accessing link-time information via `scala.scalajs.runtime.linkingInfo` required interacting with a JavaScript object. That introduced JS interop in the Wasm backend causing a slowdown, and could hinder the optimization pipeline (In fact, we folded the access to `JSLinkingInfo` into `Literal`s in `OptimizerCore`). `LinkTimeProperty` is a new IR node that is guaranteed to be transformed into a `Literal` at the optimizer or backend stage. We plan to introduce a new primitive, such as `linkTimePropertyXXX`, which will be transformed into a `LinkTimeProperty` in a later commit. Additionally, we will update `scala.scalajs.LinkingInfo` to use `linkTimePropertyXXX` instead of `runtime.linkingInfo`, allowing us to eliminate the JS object when accessing link-time information. This commit also deprecates the `JSLinkingInfo` IR node. For backward compatibility, we introduced a deserialization hack that transforms `JSSelect(JSLinkingInfo(), StringLiteral(...))` into the corresponding `LinkTimeProperty`. An isolated `JSLinkingInfo` will be deserialized into a `JSObjectConstr()` containing the corresponding `LinkTimeProperty` values. Also, this commit introduces validation for `LinkTimeProperty` during reachability analysis. The Analyzer now verifies that the `LinkTimeProperty` in the IR has a valid name and type pair, ensuring it can be resolved using the provided link-time information. If an invalid `LinkTimeProperty` is detected, an error will be recorded in the `Analysis`
ref: scala-js#5000 Previously, accessing link-time information via `scala.scalajs.runtime.linkingInfo` required interacting with a JavaScript object. That introduced JS interop in the Wasm backend causing a slowdown, and could hinder the optimization pipeline (In fact, we folded the access to `JSLinkingInfo` into `Literal`s in `OptimizerCore`). `LinkTimeProperty` is a new IR node that is guaranteed to be transformed into a `Literal` at the optimizer or backend stage. We plan to introduce a new primitive, such as `linkTimePropertyXXX`, which will be transformed into a `LinkTimeProperty` in a later commit. Additionally, we will update `scala.scalajs.LinkingInfo` to use `linkTimePropertyXXX` instead of `runtime.linkingInfo`, allowing us to eliminate the JS object when accessing link-time information. This commit also deprecates the `JSLinkingInfo` IR node. For backward compatibility, we introduced a deserialization hack that transforms `JSSelect(JSLinkingInfo(), StringLiteral(...))` into the corresponding `LinkTimeProperty`. An isolated `JSLinkingInfo` will be deserialized into a `JSObjectConstr()` containing the corresponding `LinkTimeProperty` values. Also, this commit introduces validation for `LinkTimeProperty` during reachability analysis. The Analyzer now verifies that the `LinkTimeProperty` in the IR has a valid name and type pair, ensuring it can be resolved using the provided link-time information. If an invalid `LinkTimeProperty` is detected, an error will be recorded in the `Analysis`
ref: scala-js#5000 Previously, accessing link-time information via `scala.scalajs.runtime.linkingInfo` required interacting with a JavaScript object. That introduced JS interop in the Wasm backend causing a slowdown, and could hinder the optimization pipeline (In fact, we folded the access to `JSLinkingInfo` into `Literal`s in `OptimizerCore`). `LinkTimeProperty` is a new IR node that is guaranteed to be transformed into a `Literal` at the optimizer or backend stage. We plan to introduce a new primitive, such as `linkTimePropertyXXX`, which will be transformed into a `LinkTimeProperty` in a later commit. Additionally, we will update `scala.scalajs.LinkingInfo` to use `linkTimePropertyXXX` instead of `runtime.linkingInfo`, allowing us to eliminate the JS object when accessing link-time information. This commit also deprecates the `JSLinkingInfo` IR node. For backward compatibility, we introduced a deserialization hack that transforms `JSSelect(JSLinkingInfo(), StringLiteral(...))` into the corresponding `LinkTimeProperty`. An isolated `JSLinkingInfo` will be deserialized into a `JSObjectConstr()` containing the corresponding `LinkTimeProperty` values. Also, this commit introduces validation for `LinkTimeProperty` during reachability analysis. The Analyzer now verifies that the `LinkTimeProperty` in the IR has a valid name and type pair, ensuring it can be resolved using the provided link-time information. If an invalid `LinkTimeProperty` is detected, an error will be recorded in the `Analysis`
ref: scala-js#5000 Previously, accessing link-time information via `scala.scalajs.runtime.linkingInfo` required interacting with a JavaScript object. That introduced JS interop in the Wasm backend causing a slowdown, and could hinder the optimization pipeline (In fact, we folded the access to `JSLinkingInfo` into `Literal`s in `OptimizerCore`). `LinkTimeProperty` is a new IR node that is guaranteed to be transformed into a `Literal` at the optimizer or backend stage. We plan to introduce a new primitive, such as `linkTimePropertyXXX`, which will be transformed into a `LinkTimeProperty` in a later commit. Additionally, we will update `scala.scalajs.LinkingInfo` to use `linkTimePropertyXXX` instead of `runtime.linkingInfo`, allowing us to eliminate the JS object when accessing link-time information. This commit also deprecates the `JSLinkingInfo` IR node. For backward compatibility, we introduced a deserialization hack that transforms `JSSelect(JSLinkingInfo(), StringLiteral(...))` into the corresponding `LinkTimeProperty`. An isolated `JSLinkingInfo` will be deserialized into a `JSObjectConstr()` containing the corresponding `LinkTimeProperty` values. Also, this commit introduces validation for `LinkTimeProperty` during reachability analysis. The Analyzer now verifies that the `LinkTimeProperty` in the IR has a valid name and type pair, ensuring it can be resolved using the provided link-time information. If an invalid `LinkTimeProperty` is detected, an error will be recorded in the `Analysis`
Closing in favor of #5025 for now |
ref: scala-js#5000 Previously, accessing link-time information via `scala.scalajs.runtime.linkingInfo` required interacting with a JavaScript object. That introduced JS interop in the Wasm backend causing a slowdown, and could hinder the optimization pipeline (In fact, we folded the access to `JSLinkingInfo` into `Literal`s in `OptimizerCore`). `LinkTimeProperty` is a new IR node that is guaranteed to be transformed into a `Literal` at the optimizer or backend stage. We plan to introduce a new primitive, such as `linkTimePropertyXXX`, which will be transformed into a `LinkTimeProperty` in a later commit. Additionally, we will update `scala.scalajs.LinkingInfo` to use `linkTimePropertyXXX` instead of `runtime.linkingInfo`, allowing us to eliminate the JS object when accessing link-time information. This commit also deprecates the `JSLinkingInfo` IR node. For backward compatibility, we introduced a deserialization hack that transforms `JSSelect(JSLinkingInfo(), StringLiteral(...))` into the corresponding `LinkTimeProperty`. An isolated `JSLinkingInfo` will be deserialized into a `JSObjectConstr()` containing the corresponding `LinkTimeProperty` values. Also, this commit introduces validation for `LinkTimeProperty` during reachability analysis. The Analyzer now verifies that the `LinkTimeProperty` in the IR has a valid name and type pair, ensuring it can be resolved using the provided link-time information. If an invalid `LinkTimeProperty` is detected, an error will be recorded in the `Analysis`
ref: scala-js#5000 Previously, accessing link-time information via `scala.scalajs.runtime.linkingInfo` required interacting with a JavaScript object. That introduced JS interop in the Wasm backend causing a slowdown, and could hinder the optimization pipeline (In fact, we folded the access to `JSLinkingInfo` into `Literal`s in `OptimizerCore`). `LinkTimeProperty` is a new IR node that is guaranteed to be transformed into a `Literal` at the optimizer or backend stage. We plan to introduce a new primitive, such as `linkTimePropertyXXX`, which will be transformed into a `LinkTimeProperty` in a later commit. Additionally, we will update `scala.scalajs.LinkingInfo` to use `linkTimePropertyXXX` instead of `runtime.linkingInfo`, allowing us to eliminate the JS object when accessing link-time information. This commit also deprecates the `JSLinkingInfo` IR node. For backward compatibility, we introduced a deserialization hack that transforms `JSSelect(JSLinkingInfo(), StringLiteral(...))` into the corresponding `LinkTimeProperty`. An isolated `JSLinkingInfo` will be deserialized into a `JSObjectConstr()` containing the corresponding `LinkTimeProperty` values. Also, this commit introduces validation for `LinkTimeProperty` during reachability analysis. The Analyzer now verifies that the `LinkTimeProperty` in the IR has a valid name and type pair, ensuring it can be resolved using the provided link-time information. If an invalid `LinkTimeProperty` is detected, an error will be recorded in the `Analysis`
ref: scala-js#5000 Previously, accessing link-time information via `scala.scalajs.runtime.linkingInfo` required interacting with a JavaScript object. That introduced JS interop in the Wasm backend causing a slowdown, and could hinder the optimization pipeline (In fact, we folded the access to `JSLinkingInfo` into `Literal`s in `OptimizerCore`). `LinkTimeProperty` is a new IR node that is guaranteed to be transformed into a `Literal` at the optimizer or backend stage. We plan to introduce a new primitive, such as `linkTimePropertyXXX`, which will be transformed into a `LinkTimeProperty` in a later commit. Additionally, we will update `scala.scalajs.LinkingInfo` to use `linkTimePropertyXXX` instead of `runtime.linkingInfo`, allowing us to eliminate the JS object when accessing link-time information. This commit also deprecates the `JSLinkingInfo` IR node. For backward compatibility, we introduced a deserialization hack that transforms `JSSelect(JSLinkingInfo(), StringLiteral(...))` into the corresponding `LinkTimeProperty`. An isolated `JSLinkingInfo` will be deserialized into a `JSObjectConstr()` containing the corresponding `LinkTimeProperty` values. Also, this commit introduces validation for `LinkTimeProperty` during reachability analysis. The Analyzer now verifies that the `LinkTimeProperty` in the IR has a valid name and type pair, ensuring it can be resolved using the provided link-time information. If an invalid `LinkTimeProperty` is detected, an error will be recorded in the `Analysis`
ref: scala-js#5000 Previously, accessing link-time information via `scala.scalajs.runtime.linkingInfo` required interacting with a JavaScript object. That introduced JS interop in the Wasm backend causing a slowdown, and could hinder the optimization pipeline (In fact, we folded the access to `JSLinkingInfo` into `Literal`s in `OptimizerCore`). `LinkTimeProperty` is a new IR node that is guaranteed to be transformed into a `Literal` at the optimizer or backend stage. We plan to introduce a new primitive, such as `linkTimePropertyXXX`, which will be transformed into a `LinkTimeProperty` in a later commit. Additionally, we will update `scala.scalajs.LinkingInfo` to use `linkTimePropertyXXX` instead of `runtime.linkingInfo`, allowing us to eliminate the JS object when accessing link-time information. This commit also deprecates the `JSLinkingInfo` IR node. For backward compatibility, we introduced a deserialization hack that transforms `JSSelect(JSLinkingInfo(), StringLiteral(...))` into the corresponding `LinkTimeProperty`. An isolated `JSLinkingInfo` will be deserialized into a `JSObjectConstr()` containing the corresponding `LinkTimeProperty` values. Also, this commit introduces validation for `LinkTimeProperty` during reachability analysis. The Analyzer now verifies that the `LinkTimeProperty` in the IR has a valid name and type pair, ensuring it can be resolved using the provided link-time information. If an invalid `LinkTimeProperty` is detected, an error will be recorded in the `Analysis`
ref: scala-js#5000 Previously, accessing link-time information via `scala.scalajs.runtime.linkingInfo` required interacting with a JavaScript object. That introduced JS interop in the Wasm backend causing a slowdown, and could hinder the optimization pipeline (In fact, we folded the access to `JSLinkingInfo` into `Literal`s in `OptimizerCore`). `LinkTimeProperty` is a new IR node that is guaranteed to be transformed into a `Literal` at the optimizer or backend stage. We plan to introduce a new primitive, such as `linkTimePropertyXXX`, which will be transformed into a `LinkTimeProperty` in a later commit. Additionally, we will update `scala.scalajs.LinkingInfo` to use `linkTimePropertyXXX` instead of `runtime.linkingInfo`, allowing us to eliminate the JS object when accessing link-time information. This commit also deprecates the `JSLinkingInfo` IR node. For backward compatibility, we introduced a deserialization hack that transforms `JSSelect(JSLinkingInfo(), StringLiteral(...))` into the corresponding `LinkTimeProperty`. An isolated `JSLinkingInfo` will be deserialized into a `JSObjectConstr()` containing the corresponding `LinkTimeProperty` values. Also, this commit introduces validation for `LinkTimeProperty` during reachability analysis. The Analyzer now verifies that the `LinkTimeProperty` in the IR has a valid name and type pair, ensuring it can be resolved using the provided link-time information. If an invalid `LinkTimeProperty` is detected, an error will be recorded in the `Analysis`
based on #4988
and WIP writing tests (link-failed scenario, AnalyzerTest, OptimizerTest, and suites), but the overall design can be reviewedready for review :)closes #4997
This commit introduces linktime dispatching with a new
LinkTimeIf
IR node. The condition ofLinkTimeIf
will be evaluated at link-time and the dead branch be eliminated at link-time by Optimizer or linker backend.For example,
The code above under
.withProductionMode(true)
links to the following at runtime.This feature was originally motivated to allow switching the library implementation based on whether it targets browser Wasm or standalone Wasm (see #4991).
However, it should prove useful for further optimization through link-time information-based dispatching.
LinkTimeIf
IR NodeThis change introduces a new IR node
LinkTimeIf(cond: LinkTimeTree, thenp: Tree, elsep: Tree)
, that represents link-time dispatching.LinkTimeTree
is a small set of IR tree evaluated at link-time.Currently,
LinkTimeTree
containsBinaryOp
,IntConst
,BooleanConst
, andProperty
.BinaryOp
representing a simple binary operation that evaluates to a boolean value.IntConst
andBooleanConst
holds a constant of the type.Property
contains a key to resolve a value at link-time, whereLinkTimeProperties.scala
is responsible for managing and resolving the link-time value dictionary, which is accessible throughCoreSpec
.For example, the following
LinkTimeIf
looks up the link-time value whose key is "scala.scalajs.LinkingInfo.esVersion" and compares it with the integer constant 6.LinkingInfo.linkTimeIf
and@linkTimeProperty
annotationThis commit defines a new API to represent link-time dispatching:
LinkingInfo.linkTimeIf(...) { } { }
, which compiles to theLinkTimeIf
IR node. For example,linkTimeIf(esVersion >= ESVersion.ES2015)
compiles to the IR above.Note that only symbols annotated with
@linkTimeProperty
or int/boolean constants can be used in the condition oflinkTimeIf
. Currently,@linkTimeProperty
is private toscalajs
(users cannot define new link-time values), and only a predefined set of link-time values are annotated (productionMode
andesVersion
for now).When
@linkTimeProperty(name)
annotated values are used inlinkTimeIf
, they are translated toLinkTimeValue.Property(name)
.LinkTimeProperties to resolve and evaluate LinkTimeCondition/Value
This commit defines a
LinkTimeProperty
that belongs to theCoreSpec
(making it accessible from various linker stages). It constructs a link-time value dictionary fromSemantics
andESFeatures
, and is responsible for resolvingLinkTimeValue.Property
and evaluatingLinkTimeCondition
.Analyzer doesn't follow the dead branch of linkTimeIf
Now
Analyzer
evaluates theLinkTimeIf
and follow only the live branch. For example, underproductionMode = true
,doSomethingDev
won't be marked as reachable byAnalyzer
.Eliminate dead branch of LinkTimeIf
Finally, the optimizer and linker-backends (in case the optimizer is turned off) eliminate the dead branch of
LinkTimeIf
.