-
Notifications
You must be signed in to change notification settings - Fork 395
Allow for linktime conditional branching #4997
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
Comments
I'm not sure about magically turning an I believe the more Scala.js way to do this would be a dedicated primitive in LinkingInfo.linkTimeIf(LinkingInfo.isWebAssembly) {
ifTrue
} {
ifFalse
} |
Thanks! That looks nicer 👍 Indeed, converting |
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: LinkTimeCondition, thenp: Tree, elsep: Tree)`, that represents link-time dispatching. `LinkTimeCondition` is a condition evaluated at link-time. Currently, we have only a `Binary` class under `LinkTimeCondition`, representing a simple binary operation that evaluates to a boolean value. `Binary` does not allow nesting the condition. `LinkTimeCondition` is defined as a `sealed trait` for future extensibility (maybe we want to define more complex conditions?). `LinkTimeValue` contains three subclasses: IntConst, BooleanConst, and Property. Property contains a key to resolve a value at link-time. `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( LinkTimeCondition( BinaryOp.Int_>=, Property("scala.scalajs.LinkingInfo.esVersion"), IntConst(6), ), thenp, elsep ) ``` **`LinkingInfo.linkTimeIf` and `@LinkTime` 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 `@LinkTime` or int/boolean constants can be used in the condition of `linkTimeIf`. Currently, `@LinkTime` is private to `scalajs` (users cannot define new link-time values), and only a predefined set of link-time values are annotated with `@LinkTime` (`productionMode` and `esVersion` for now). When `@LinkTime` annotated values are used in `linkTimeIf`, they are translated to `LinkTimeValue.Property(name)`, where `name` is the fully qualified name of the symbol. For instance, if `someValue` is annotated with `@LinkTime`, it can be used in `linkTimeIf` like this: ```scala linkTimeIf(someValue > 42) { // code for true branch } { // code for false branch } ``` This will be compiled to an IR node similar to the previous example, with `Property("fully.qualified.name.someValue")` in the condition. **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 `LinkTimeCondition`. **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`.
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: LinkTimeCondition, thenp: Tree, elsep: Tree)`, that represents link-time dispatching. `LinkTimeCondition` is a condition evaluated at link-time. Currently, we have only a `Binary` class under `LinkTimeCondition`, representing a simple binary operation that evaluates to a boolean value. `Binary` does not allow nesting the condition. `LinkTimeCondition` is defined as a `sealed trait` for future extensibility (maybe we want to define more complex conditions?). `LinkTimeValue` contains three subclasses: IntConst, BooleanConst, and Property. Property contains a key to resolve a value at link-time. `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( LinkTimeCondition( BinaryOp.Int_>=, Property("scala.scalajs.LinkingInfo.esVersion"), IntConst(6), ), thenp, elsep ) ``` **`LinkingInfo.linkTimeIf` and `@LinkTime` 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 `@LinkTime` or int/boolean constants can be used in the condition of `linkTimeIf`. Currently, `@LinkTime` is private to `scalajs` (users cannot define new link-time values), and only a predefined set of link-time values are annotated with `@LinkTime` (`productionMode` and `esVersion` for now). When `@LinkTime` annotated values are used in `linkTimeIf`, they are translated to `LinkTimeValue.Property(name)`, where `name` is the fully qualified name of the symbol. For instance, if `someValue` is annotated with `@LinkTime`, it can be used in `linkTimeIf` like this: ```scala linkTimeIf(someValue > 42) { // code for true branch } { // code for false branch } ``` This will be compiled to an IR node similar to the previous example, with `Property("fully.qualified.name.someValue")` in the condition. **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 `LinkTimeCondition`. **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`.
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: LinkTimeCondition, thenp: Tree, elsep: Tree)`, that represents link-time dispatching. `LinkTimeCondition` is a condition evaluated at link-time. Currently, we have only a `Binary` class under `LinkTimeCondition`, representing a simple binary operation that evaluates to a boolean value. `Binary` does not allow nesting the condition. `LinkTimeCondition` is defined as a `sealed trait` for future extensibility (maybe we want to define more complex conditions?). `LinkTimeValue` contains three subclasses: IntConst, BooleanConst, and Property. Property contains a key to resolve a value at link-time. `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( LinkTimeCondition( BinaryOp.Int_>=, Property("scala.scalajs.LinkingInfo.esVersion"), IntConst(6), ), thenp, elsep ) ``` **`LinkingInfo.linkTimeIf` and `@LinkTime` 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 `@LinkTime` or int/boolean constants can be used in the condition of `linkTimeIf`. Currently, `@LinkTime` is private to `scalajs` (users cannot define new link-time values), and only a predefined set of link-time values are annotated with `@LinkTime` (`productionMode` and `esVersion` for now). When `@LinkTime` annotated values are used in `linkTimeIf`, they are translated to `LinkTimeValue.Property(name)`, where `name` is the fully qualified name of the symbol. For instance, if `someValue` is annotated with `@LinkTime`, it can be used in `linkTimeIf` like this: ```scala linkTimeIf(someValue > 42) { // code for true branch } { // code for false branch } ``` This will be compiled to an IR node similar to the previous example, with `Property("fully.qualified.name.someValue")` in the condition. **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 `LinkTimeCondition`. **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`.
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: LinkTimeCondition, thenp: Tree, elsep: Tree)`, that represents link-time dispatching. `LinkTimeCondition` is a condition evaluated at link-time. Currently, we have only a `Binary` class under `LinkTimeCondition`, representing a simple binary operation that evaluates to a boolean value. `Binary` does not allow nesting the condition. `LinkTimeCondition` is defined as a `sealed trait` for future extensibility (maybe we want to define more complex conditions?). `LinkTimeValue` contains three subclasses: IntConst, BooleanConst, and Property. Property contains a key to resolve a value at link-time. `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( LinkTimeCondition( 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 `LinkTimeCondition`. **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`.
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`.
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`.
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`.
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`.
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`.
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`.
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`.
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`.
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`.
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`.
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]>
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]>
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]>
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]>
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]>
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]>
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]>
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]>
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]>
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]>
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]>
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]>
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]>
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]>
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]>
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]>
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 add 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]>
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 below 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 access to the `linkTimeProperties` derived from 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 add 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]>
Part of #4991
Motivation
The motivation behind this feature was to allow switching the implementation based on whether the target is Wasm running in the browser (WasmJS) or a standalone Wasm application. For example, with regular expressions, WasmJS could reuse the JavaScript Regex implementation, while a standalone Wasm application would use a pure-Scala Regex implementation.
While the original motivation is from Wasm use cases, the feature itself is self-contained and can be used for optimization purposes in the JavaScript backend as well.
How to
The main idea is to introducing the new IR node
LInkTimeIf
like:Originally posted by @sjrd in #4991 (comment)
Where the
LinkTimeCondition
should be like same as scala-native's LinktimeConditionHow to: frontend
@LinkTime
(or@resolvedAtLinktime
like SN) annotation, only the annotated values (functions?) can be used as part ofLinkTimeCondition
if
expression will be translated toLinkTimeIf
instead ofIf
, when the condition is only composed of@LinkTime
annotated value,Literal
s, andBinaryOp
s.For example, the
if
will be translated toLinkTimeIf
because the condition is composed of@LinkTime
annotated booleans +BinaryOp
. (if it's mixed with the runtime-value, we should fail linking).The text was updated successfully, but these errors were encountered: