Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Make Scala.js Wasm backend suitable for standalone Wasm VMs (a.k.a. support "server-side Wasm") #4991

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

Open
tanishiking opened this issue May 29, 2024 · 6 comments
Labels
enhancement Feature request (that does not concern language semantics, see "language") language Affects language semantics. wasm Applies to the WebAssembly backend only

Comments

@tanishiking
Copy link
Contributor

tanishiking commented May 29, 2024

Kinda moved from tanishiking/scala-wasm#117

This issue is created as a meta issue to discuss supporting standalone Wasm and the overall approach. For the design and implementation of individual components, separate issues will be created, and the details will be discussed in those respective issues.

The current Scala.js Wasm backend implementation generates a Wasm binary that runs on JavaScript environments such as browsers, Node.js, Deno, and Cloudflare Workers (V8 isolate). However, it does not work on standalone Wasm runtimes such as WasmEdge and wasmtime. Supporting standalone Wasm runtimes would be important to enable Scala to run on Wasm-based cloud/edge computing services, container environments like runwasi, and so on.


To support standalone Wasm runtimes in Scala.js, the following points need to be addressed:

  • Conditional Linking: Introduce a link-time condition or mechanism to allow libraries to switch their implementation based on whether the target is JavaScript Wasm or standalone Wasm.
    • Alternatively, create a separate Intermediate Representation (IR) specifically for standalone Wasm (let's call it SWIR 😄). This would require libraries to build against standalone Wasm in addition to ScalaJVM, Scala.js, and Scala Native (even if there are no changes needed from the Scala.js version). I'm not sure it's a good idea.
  • WASI Support: Implement support for the WASI (at least preview 1 for now) in the scala.scalajs.wasm package.
  • Avoid JavaScript Interop in the Backend: When the target is standalone Wasm, the Wasm backend should avoid using JavaScript interop. This will require changes to various parts of the compiler, such as:
    • String (and some primitive types) implementation needs to be re-implement in pure-wasm, and re-implement box/unbox/typeTest operations. _JSStringOps.
    • Handling of fmod, undefined values, Object.is comparison, makeExportDef function, and closures and so on.
  • Re-implement Libraries: Some parts of the javalib and scalalib need to be re-implemented using pure Scala and Wasm/WASI intrinsics. For example:
    • Math package could be developed using a pure Scala implementation of the fdlibm, similar to how Kotlin/Wasm implemented their math package
    • Regular Expressions: Scala Native has a pure Scala implementation of regular expressions (a port of re2j for Scala), which could be copied or adapted for standalone Wasm in Scala.js?
    • ExecutionContext: Scala.js impl relies on Promise or setTimeout, but we need to re-implement in Wasm.
      • It may not be necessary to support it initially in our stand-alone Wasm backend, until WASI provides more building-blocks in future. ref [Designing an Async Runtime for WASI 0.2 https://blog.yoshuawuyts.com/building-an-async-runtime-for-wasi/]
    • System:
      • PrintStream should be replaced with WASI's fd_write and fd_read functions.
      • currentTimeMillis can be implemented using WASI's lock_time_get function.
      • and so on.
    • There should be much more to consider and re-implement...

Ref


For non-JS hosts, the benefit for users is to target an entirely new ecosystem. In that scenario, we will want to introduce interop features for at least the Component Model of Wasm, and instead remove the JS interop semantics. In that case, we definitely need IR changes. More critically, we will need a link-time way to reliably (i.e., from a reachability point of view) use one path or another. Either we do this with an entirely different ecosystem and IR (SWIR?), or we amend the Scala.js IR. If amending the Scala.js IR is not too disruptive, this would help adoption of Scala-to-Wasm, as the majority of the Scala.js ecosystem of libraries could be reused as is. We would avoid the slow bootstrap problem entirely.

We discussed one possible link-time dispatch mechanism that would fit in the Scala.js IR: a "link-time if-else". Its conditions would be restricted to a few elementary operations, based on a config dictionary of String->Int. For example it might contain "host"->0 for a JS host and "host"->1 for a WASI host. Or a "isWasm"->0/1, which could be used for code paths known to be better for Wasm than for JS (e.g., manipulating Longs more aggressively, or bit-casting between ints and floats, etc.)

#4928 (comment)

@sjrd sjrd added enhancement Feature request (that does not concern language semantics, see "language") language Affects language semantics. wasm Applies to the WebAssembly backend only labels May 29, 2024
@sjrd
Copy link
Member

sjrd commented Jun 7, 2024

I attended the June 2024 hybrid Wasm CG meeting, which happened this week. Among others, we received an update on WASI and the component model. From that update, it seems to me that we should directly target WASI preview 2, and not bother with preview 1. The reason is that we can build our language model of interoperability to be about the Component Model. We would leave WASI out of the compiler and linker altogether.

The very strong added benefit of doing so is that the component model and its WIT interface types model do not, in fact, rely on the Wasm linear memory. Therefore, at the language semantics level and hence the IR level, we need not expose anything related to the notion of linear memory. We can confine the interaction with the linear to the linker backend, where we have to actually compile down to the Canonical ABI.

WASI would then be entirely in a separate library, just like scalajs-dom is.

For some core things like new java.util.Date() or getting an initial random seed, we may need to locally refer to some WASI definitions inside javalib, but that is not something we would expose in an API. It's not ideal, but it is similar to how System.nanoTime() currently works.

@sjrd
Copy link
Member

sjrd commented Jun 7, 2024

Conditional Linking: Introduce a link-time condition or mechanism to allow libraries to switch their implementation based on whether the target is JavaScript Wasm or standalone Wasm.

To put into writing something we discussed offline at some point: I believe the easiest and cleanest way to do this is with a new IR node that would look like

case class LinkTimeIf(cond: LinkTimeCondition, thenp: Tree, elsep: Tree) extends Tree

Then, our 3 main link-time passes process it as follows:

  • the Analyzer, for the reachability analysis, deals with it during the Infos builder. Since the Infos builder walks the IR trees to build Infos, it can resolve the LinkTimeCondition and only recurse in the appropriate branch
  • the OptimizerCore would trivially replace the LinkTimeIf with either thenp or elsep, allowing further optimizations
  • the backends would do the same, if any LinkTimeIf survives until then (normally, only if the optimizer is disabled)

The precise nature of LinkTimeCondition remains to be determined, but it would fairly limited. Definitely not a full Tree.

tanishiking added a commit to tanishiking/scala-js that referenced this issue Jun 20, 2024
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`.
tanishiking added a commit to tanishiking/scala-js that referenced this issue Jun 20, 2024
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`.
tanishiking added a commit to tanishiking/scala-js that referenced this issue Jun 21, 2024
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`.
tanishiking added a commit to tanishiking/scala-js that referenced this issue Jun 21, 2024
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`.
tanishiking added a commit to tanishiking/scala-js that referenced this issue Jun 25, 2024
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`.
tanishiking added a commit to tanishiking/scala-js that referenced this issue Jun 25, 2024
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`.
tanishiking added a commit to tanishiking/scala-js that referenced this issue Jun 25, 2024
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`.
tanishiking added a commit to tanishiking/scala-js that referenced this issue Jun 26, 2024
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`.
tanishiking added a commit to tanishiking/scala-js that referenced this issue Jul 15, 2024
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`.
tanishiking added a commit to tanishiking/scala-js that referenced this issue Jul 18, 2024
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`.
tanishiking added a commit to tanishiking/scala-js that referenced this issue Aug 20, 2024
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`.
tanishiking added a commit to tanishiking/scala-js that referenced this issue Aug 20, 2024
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`.
tanishiking added a commit to scala-wasm/scala-wasm that referenced this issue Aug 23, 2024
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`.
tanishiking added a commit to tanishiking/scala-js that referenced this issue Aug 29, 2024
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`.
@tanishiking
Copy link
Contributor Author

tanishiking commented Oct 10, 2024

Thanks to #4998 and #5025, we’ve removed the JS dependency at the core of the Wasm linker backend.

I’ve now assembled my earlier prototypes (wasm-native-strings and experimental WASI), and successfully ran println("hello world") on WasmEdge! 🎉 See scala-wasm#4.

$ wasmedge --version
wasmedge version 0.14.1
 (plugin "wasi_logging") version 0.1.0.0

$ wasmedge --enable-gc --enable-exception-handling --enable-tail-call examples/helloworld/.2.12/target/scala-2.12/helloworld-fastopt/main.wasm
[2024-10-09 22:41:04.018] [warning] GC proposal is enabled, this is experimental.
Hello world!

This prototype is still very faaaar from perfect. However, this has made it clear what needs to be done to support standalone Wasm runtimes. I’m planning to list the necessary tasks to do.

  • The standalone Wasm backend requires the Scala.js optimizer to be enabled, for (1) dead branch elimination needs to be performed, and (2) we currently define WASI-related IR as a Transients. We may want to support link-time dispatch (optional), and we need to design proper IR nodes for WASI-related features.
  • We're currently using WASI preview1, with the fd_write API hardcoded into the backend. Maybe we would like to support WASI preview2(?), which requires new interop semantics for the Wasm component model (at least consuming component), and Scala port of wit-bindgen based on those APIs.
  • Only the libraries required for println("hello world") have been ported to pure Scala/WASI so far, more javalib/scalalib have to be ported.
  • We're using (array (mut i16)) for string representation instead of JS strings. Currently, concat is very slow as it allocates a new array and copies data. We need to optimize this by making concat simply update references to the predecessor string, similar to Kotlin/Wasm’s approach.
  • Some JS interop in the linker backend, like intToString, is still unsupported. We need to re-implement them in pure Wasm. This shouldn't be too hard, at least for int (https://gist.github.com/tanishiking/79adbf29a57e1f19b3dca5cf4b569353).
  • The current fmod implementation is naive one. We need to emulate the behavior described in the ECMAScript spec, as noted in the comments.
  • identityHashCode is currently a stub, since we can't rely on JS interop for object equality. We may want to store a globally unique ID or a random value in the vtable or elsewhere so the same object consistently returns the same hash code.
  • I feel that adding conditional branching in FunctionEmitter + CoreWasmLib here and there depending on whether WASI is used will make the linker backend very complex. It would be ideal to find a better design.
  • We'd like to add a new option/LinkingInfo to indicate if the target is a WASI host.

deps

graph TB
    A(Design Wasm Component based interop) --> WIT(wit-bindgen for Scala)
    WIT --> WASIp2(WASI preview2 support)
    WASIp2 --> SJS_WASI(Port Scala.js libs depends on WASI)
    WASIp1(WASI preview1 support) --> SJS_WASI
    SJS_WASI -.->|No need to port everything at once| NOJS(Do not link JS interop under WASI config)
    SJS_SCALA(Port Scala.js libs to pure Scala) -.->|No need to port everything at once| NOJS
    NOJS -.->|It is not strict dependency, but unless JS interop is removed, conversion between Wasm native strings and JS strings will be necessary.| String(Wasm Native String)
    String --> Goal(Support standalone Wasm runtime)
    NOJS --> Goal
    intToString --> Goal
    fmod --> Goal
    identityHashCode --> Goal
Loading

@ekrich
Copy link
Contributor

ekrich commented Oct 10, 2024

Only the libraries required for println("hello world") have been ported to pure Scala/WASI so far, more javalib/scalalib have to be ported.

Gosh, this sounds almost like a repeat of Scala Native at this point.

@tanishiking
Copy link
Contributor Author

tanishiking commented Oct 10, 2024

Gosh, this sounds almost like a repeat of Scala Native at this point.

If "repeat of Scala Native" mean re-implementing parts of javalib/scalalib and third-party libraries in pure Scala and/or using WASI instead of libc/js-interop, yes, unfortunately. This is unavoidable for supporting standalone Wasm based on Scala.js. (We should be able to share certain library code, such as the re2j port for Scala, between Native and Wasm though)

By using ScalaNative with boehmGC's Emscripten port and wasi-libc, it’s possible to generate Wasm code while reusing existing Scala code. However, the lack of WasmGC support means that a shadow stack would need to be introduced, resulting in performance disadvantages, and it could also lead to the cycle collection problem (though it might not be a problem in practice?). While not all C libraries are freely usable, we can certainly benefit from the library ports (for wasi-libc) provided by the C/C++/Emscripten/LLVM communities. This is a trade-off.

@ekrich
Copy link
Contributor

ekrich commented Oct 11, 2024

Thanks for the clarification. Definitely, a bunch of work.

@tanishiking tanishiking changed the title Make Scala.js Wasm backend suitable for standalone Wasm VMs Make Scala.js Wasm backend suitable for standalone Wasm VMs (a.k.a. support "server-side Wasm") Jan 27, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
enhancement Feature request (that does not concern language semantics, see "language") language Affects language semantics. wasm Applies to the WebAssembly backend only
Projects
None yet
Development

No branches or pull requests

3 participants