Skip to content
Draft
Changes from 5 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
174 changes: 78 additions & 96 deletions ConditionalTranslation.md
Original file line number Diff line number Diff line change
@@ -1,34 +1,41 @@
# Conditional Translation
# Conditional Translation and Shader Specialization

## Overview

> *This section is non-normative*

Conditional translation is a mechanism to modify the output source code based on parameters passed to the *WESL translator*.
This specification extends the [*attribute* syntax](https://www.w3.org/TR/WGSL/#attributes) with a new `@if` attribute.
This attribute indicates that the syntax node it decorates can be removed by the *WESL translator* based on feature flags.
Conditional translation and shader specializaton are two mechanisms to control the WGSL code produced based on parameters passed to the *WESL translator*.
This specification extends the [*attribute* syntax](https://www.w3.org/TR/WGSL/#attributes) with new attributes: `@param` defines *module parameters* for shader specializaton via constant injection, while `@if`/`@elif`/`@else` control conditional translation.

> *This implementation is similar to the `#[cfg(feature = "")]` syntax in Rust.*
> [!NOTE]
> Compared to Rust, `@param const my_param = true` is analogous to a feature flag, while `@if(my_param)` is analogous to `#[cfg(feature = "my_param")]`.

### Usage Example

```wesl
// define module parameters with default values...
@param const TEXTURED = true;
@param const DEBUG = false;
@param const RAYTRACING_ENABLED = false;
@param const LEGACY_COMPAT = false;

// global variables and bindings...
@if(textured)
@if(TEXTURED)
@group(0) @binding(0) var my_texture: texture_2d<f32>;

// structs declarations and struct members...
struct Ray {
position: vec4f,
direction: vec4f,
@if(debug_mode && raytracing_enabled)
@if(DEBUG && RAYTRACING_ENABLED)
ray_steps: u32,
}

// function declarations, parameters and statements...
fn main() -> vec4 {
@if(legacy_implementation || (is_web_version && xyz_not_supported))
@if(LEGACY_COMPAT)
let result = legacy_impl();
@if(!legacy_implementation && !(is_web_version && xyz_not_supported))
@else
let result = modern_impl();
}
```
Expand All @@ -37,33 +44,25 @@ Quirky examples

```wesl
// attribute order does not matter.
@compute @if(feature) fn main() { }
// ... is equivalent to
@if(feature) @compute fn main() { }

// feature names live in their own namespace, i.e. they cannot shadow, or be shadowed by declarations.
const feature1 = 10;
@if(feature1) fn main() -> u32 { // 'feature1' in @if does not refer to the const-declaration.
return feature1*2; // 'feature1' in return statement does not refer to the feature flag.
}
@compute @if(FEATURE) fn main() { }
// ... is equivalent to this (preferred)
@if(FEATURE) @compute fn main() { }
```

## Definitions
* **Translate-time expression**: A *translate-time expression* is evaluated by the *WESL translator* and eliminated after translation.

* A **module parameter** is an module-scope const-declaration decorated with the `@param` attribute.
* A **condition expression** is evaluated by the *WESL translator* and eliminated after translation.
Its grammar is a subset of normal WGSL [expressions](https://www.w3.org/TR/WGSL/#expressions). it must be one of:
* a *translate-time feature*,
* a boolean literal value (`true` or `false`).
* a boolean *module parameter* in scope,
* a [logical expression](https://www.w3.org/TR/WGSL/#logical-expr): logical not (`!`), short-circuiting AND (`&&`), short-circuiting OR (`||`),
* a [parenthesized expression](https://www.w3.org/TR/WGSL/#parenthesized-expressions),
* a boolean literal value (`true` or `false`).

* **Translate-time scope**: The *translate-time scope* this is an independent scope from the [*module scope*](https://www.w3.org/TR/WGSL/#module-scope), meaning it cannot see any declarations from the source code, and its identifiers are independent.

* **Translate-time feature**: A *translate-time feature* is an identifier that evaluates to a boolean. It is set to `true` if the feature is *enabled* during the translation phase and `false` if the feature is *disabled*. It lives in the *Translate-time scope*.
* A **condition attribute** (`@if`, `@elif` and `@else`) are eliminated after translation but can also eliminate the syntax node it attached to.

* **Translate-time attribute**: A *translate-time attribute* is parametrized by a *translate-time expression*. It is eliminated after translation but can affect the syntax node it decorates.
## Location of *condition attributes*

## Location of *Translate-time attributes*
A *translate-time attribute* can appear before the following syntax nodes:
A *condition attribute* can appear before the following syntax nodes:

* [directives](https://www.w3.org/TR/WGSL/#directives)
* [variable and value declarations](https://www.w3.org/TR/WGSL/#var-and-value)
Expand All @@ -79,15 +78,16 @@ A *translate-time attribute* can appear before the following syntax nodes:
* [const assertion statements](https://www.w3.org/TR/WGSL/#const-assert-statement)
* [switch clauses](https://www.w3.org/TR/WGSL/#switch-statement)

> [!TIP]
> *Translate-time attributes* are not allowed in places where removal of the syntax node would lead to syntactically incorrect code. The current set of *translate-time attribute* locations guarantees that the code is syntactically correct after specialization. This is why *translate-time attributes* are not allowed before expressions.
> [!NOTE]
> *Condition attributes* are not allowed in places where removal of the syntax node would lead to syntactically incorrect code. The current set of *condition attribute* locations guarantees that the code is syntactically correct after specialization. This is why *condition attributes* are not allowed before expressions.

### Update to the WGSL grammar
The WGSL grammar allows attributes in several locations where *translate-time attribute* are not allowed (1). Conversely, the WGSL grammar does not allow attributes in several locations where *translate-time attribute* are allowed (2).

The WGSL grammar allows attributes in several locations where *condition attributes* are not allowed (1). Conversely, the WGSL grammar does not allow attributes in several locations where *condition attribute* are allowed (2).

Refer to the [updated grammar appendix](#appendix-updated-grammar) for the list of updated grammar non-terminals.

1. A *translate-time attribute* CANNOT decorate the following syntax nodes, even if the WGSL grammar allows attributes before these syntax nodes:
1. A *condition attribute* CANNOT decorate the following syntax nodes, even if the WGSL grammar allows attributes before these syntax nodes:
* function return types
* the body (part surrounded by curly braces) of:
* function declarations
Expand All @@ -99,7 +99,7 @@ Refer to the [updated grammar appendix](#appendix-updated-grammar) for the list
* if/else statements
* continuing statements

2. The grammar is extended to allow *translate-time attributes* before the following syntax nodes:
2. The grammar is extended to allow *condition attributes* before the following syntax nodes:
* const value declarations
* variable declarations
* directives
Expand All @@ -114,101 +114,83 @@ Refer to the [updated grammar appendix](#appendix-updated-grammar) for the list
* function call statements
* const assertion statements

## `@if` attribute
The `@if` *translate-time attribute* is introduced. It takes a single parameter. It marks the decorated node for removal if the parameter evaluates to `false`.
## `@param` attribute

A syntax node may at most have a single `@if` attributes. This keeps the way open for a `@else` attribute introduction in the future.
Checking for multiple features is done with an `&&`
A module-scope const-declaration can be decorated with the `@param` attribute to turn it into a *module parameter*.
*Module parameters* work like any other const declarations, but also provide two advantages:
* their value can be overriden by the *WESL translator*.
* they can be used in *condition expressions* if they are of type boolean.

```wesl
@if(feature1 && feature2) const decl: u32 = 0;
```
## `@if`, `@elif` and `@else` attributes

> *See the [possible future extensions](#possible-future-extensions) for the attributes `@elif` and `@else`.
> They may be introduced in the specification in a future version if deemed useful.*
The `@if` attribute takes a single *condition expression* parameter.
It marks the decorated node for removal if its *condition expression* evaluates to `false`.

The `@elif` attribute takes a single *condition expression* parameter.
It can only be attached to the next sibling of a syntax node decorated by a `@if` or an `@elif`.
It marks the decorated node for removal if its *condition expression* evaluates to `false` OR any of if the previous `@if` and `@elif` attribute parameters in the chain evaluate to `true`.

The `@else` attribute takes no parameter.
It can only be attached to the next sibling of a syntax node decorated by a `@if` or an `@elif`.
It marks the decorated node for removal if any of the previous `@if` and `@elif` attribute *condition expressions* in the chain evaluate to `true`.

A syntax node may at most have a single `@if`, `@elif` or `@else` attribute. Checking for multiple features is done with an AND (`&&`) expression.

Example:

```wesl
@if(feature_1 && (!feature_2 || feature_3))
@if(PARAM1 && PARAM2) const decl: u32 = 0;

@if(PARAM1 && (!PARAM2 || PARAM3))
fn f() { ... }
@if(!feature_1) // corresponds to @elif(!feature_1)
@elif(!PARAM1) // identical to @if(!PARAM1)
fn f() { ... }
@if(feature_1 && !(!feature_2 || feature_3)) // corresponds to @else
@else // identical to @if(!(!PARAM2 || PARAM3))
fn f() { ... }
```

## Execution of the conditional translation phase
1. The *WESL translator* is invoked with the list of features to *enable* or *disable*.
## Error cases

2. The source file is parsed.
It is a translate-time error if:

3. The *translate-time features* in *translate-time expressions* are resolved:
* If the feature is *enabled*, the identifier is replaced with `true`.
* If the feature is *disabled*, the identifier is replaced with `false`.
* The *WESL linker* was invoked with a *shader parameter* override that does not exist;
* The type of a *shader parameter* override does not match the type of the declaration;
* An identifier used in a *condition expression* does not reference a boolean shader parameter.

4. *Translate-time attributes* are evaluated:
* If the decorated syntax node is marked for removal: it is eliminated from the source code along with the attribute.
* Otherwise, only the attribute is eliminated from the source code.
## Execution of the conditional translation phase

5. The updated source code is passed to the next translation phase. (e.g. import resolution)
1. The *WESL translator* is invoked with a list of *module parameters* names and values to override.
2. The source file is parsed.
3. For each overriden *module parameter*, the const-declaration initializer are replaced with the overridden value and the `@param` attribute is removed.
4. *condition expressions* are evaluated in order.
* If it evaluates to `false`, the decorated syntax node is eliminated from the source code along with the attribute.
* If it evaluates to `true`, the attribute is removed and subsequent nodes decorated with `@elif` or `@else` are eliminated.
6. The updated source code is passed to the next translation phase. (e.g. import resolution)

### Incremental translation
In case some features can only be resolved at runtime, a *WESL translator* can *optionally* support feature specialization in multiple passes:

* In the initial passes, the *WESL translator* is invoked with some of the feature flags. It replaces their occurrences in *translate-time attributes* with either `true` or `false`.
These passes return a partially-translated WESL code.
* After the final pass, the resulting code must be valid WGSL. it is an *link-time error* if any used *Translate-time features* was not provided to the linker.

If the *WESL translator* does not support incremental translation, it is an *link-time error* if any used *Translate-time features* was not provided to the linker.

> *It is not an error to provide unused feature flags to the linker. However, an implementation may choose to display a warning in that case.*
In case some features can only be resolved at runtime, a *WESL translator* can *optionally* support specialization in multiple passes.
In the initial passes, the *WESL translator* is invoked with some *shader parameters* marked explicitly to be preserved.

## Possible future extensions
> *This section is non-normative*

* `@else` and `@elif` attributes:
* An `@elif` attribute decorates the next sibling of a syntax node decorated by a `@if` or an `@elif`. It takes one parameter.
It marks the decorated node for removal if its parameter evaluates to `false` OR any of if the previous `@if` and `@elif` attribute parameters evaluate to `true`.
* An `@else` attribute decorates the next sibling of a syntax node decorated by a `@if` or an `@elif`. It does not take any parameter.
It marks the decorated node for removal if any of the previous `@if` and `@elif` attribute parameters evaluate to `true`.

The `@else` attribute has the nice property that all cases lead to generating a node, and *could* therefore be used in places where the node is required (e.g. expressions)
* Arithmetic *condition expressions*: currently *condition expressions* only accept *shader parameters* of type boolean. In the future we may accept other types of constants and arithmetic expressions.

Example:

```wesl
@if(feature_1 && (!feature_2 || feature_3))
fn f() { ... }
@elif(!feature_1)
fn f() { ... }
@else
@if(0x106 <= VERSION && VERSION <= 0x320)
fn f() { ... }
```

* High-complexity *translate-time expressions*: if we end up implementing other *translate-time attributes*, such as loops (e.g. `@for`, `@repeat`), or [translate-time-evaluable](https://zig.guide/language-basics/comptime/) expressions, then we would need to extend the grammar of *translate-time expressions*. It would also affect this proposal.

*Example*

```wesl
@comptime
fn response_to_the_ultimate_question() -> u32 {
return 42;
}

@if(response_to_the_ultimate_question() == 42)
fn f() { ... }
```

* Decorating other WESL language extensions: import statements could be decorated with *translate-time attributes* too.

*Example*
* Decorating other WESL language extensions: import statements could be decorated with *condition attributes* too.

Example:
```wesl
@if(use_bvh)
import accel/bvh_acceleration_structure as scene_struct;
@if(USE_BVH)
import accel::bvh_acceleration_structure as scene_struct;
@else
import accel/default_acceleration_structure as scene_struct;
import accel::default_acceleration_structure as scene_struct;
```

## Appendix: Updated grammar
Expand Down
Loading