From 8ce492df7c56c6263b8c996e6acb90fe21b5db00 Mon Sep 17 00:00:00 2001 From: Mathis Brossier Date: Tue, 26 Aug 2025 01:06:27 +0200 Subject: [PATCH 1/7] Create ModuleParameters.md --- ModuleParameters.md | 98 +++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 98 insertions(+) create mode 100644 ModuleParameters.md diff --git a/ModuleParameters.md b/ModuleParameters.md new file mode 100644 index 0000000..74150e6 --- /dev/null +++ b/ModuleParameters.md @@ -0,0 +1,98 @@ +# Module Parameters + +Module parameters are module-scope declarations that can be overridden by other modules. constants, functions, structs and type aliases can be overridden. They allow a form of genericity and work hand-in-hand with conditional translation. + +## Syntax + +A module-scope declaration is preceded by the `@param` attribute (TODO or param keyword? choose a syntax) to turn it into a parameter. The initializer (or the function body) is the default value for the parameter, and is mandatory (TODO currently?). + +Examples +``` +@param const MSAA_SAMPLES = 3; +@param const ENABLE_XYZ = false; +@param alias T = f32; +@param struct Point { x: T, y: T } +@param fn binop(lhs: T, rhs: T) { + return lhs + rhs; +} +``` + +The importer module can decide to replace a module parameter with a compatible declaration (see Parameter Compatibility) in the import statement. + +Proposed syntax #1 +``` +import binary_ops::{reduce_buffer as reduce_mul_f32}; +``` + +Proposed syntax #2 +``` +import binary_ops with {binop: mul_f32}::{reduce_buffer as reduce_mul_f32}; +``` + +Proposed syntax #3 +``` +@param(binop: mul_f32) +import binary_ops::{reduce_buffer as reduce_mul_f32}; +``` + +## Parameter Compatibility + +A Module Parameter can only be replaced with a compatible declaration. This ensures that any operations using the original declaration stay valid with the replacement declaration. + +### Type Compatibility + +A type T2 is compatible with a type T1 if one of these conditions is met: + +* T1 equals T2 +* Transitively, T2 is compatible with T1 if U is compatible with T1 and T2 is compatible with U. +* AbstractInt is compatible with AbstractFloat +* f32 and f16 are compatible with AbstractFloat +* i32 and u32 are compatible with AbstractInt +* T1 and T2 are structs, T1’s fields exist in T2, and are each compatible with T1’s field type. T2 may have additional fields. +* T1 and T2 are generated by the same type-generator (vec, mat, array), and the element type is compatible, and the element count is larger or equal. +* TODO: What about texture types? +* TODO: check what are the built-in function limitations with this compatibility thing. I’m sure there are some. + +Examples: TODO + +The compatibility criteria for module parameters replacement are as follows: + +* constants: a `const` declaration of identical type. +* type aliases: a built-in type, `struct` or `alias` declaration with a compatible type. +structs: a built-in type, `struct` or `alias` declaration with a compatible type. +* function declarations: a `fn` declaration with a compatible signature (user-defined or built-in). + A function signature is compatible if: + * It has the same number of parameters + * The parameter types are compatible with the original function’s parameters. (parameter covariance) + * If applicable, the original function’s return type is compatible with the replacement function’s return type (return contravariance) + * If the original function is void, the replacement function may return any type, or void. + +Examples: TODO + +## Behavior + +### Module concretizations + +Modules containing parameter declarations are called generic modules. Each usage of a generic module, with different parameters, forms a concretization of the module. Declarations inside different concretizations of the same module are duplicated. (TODO: what happens with declarations that don’t depend on module parameters? var decls? I think they should be duplicated) + +NOTE: aliases referring to the same declaration do not produce different concretizations. + +NOTE: each module concretization forms a distinct module. Importing multiple concretizations of the same module is allowed. +Root Module: Shader Parameters +Module parameters defined or published (TODO: publish spec) in the Root Module are called Shader Parameters. Shader Parameters can be set from the host code with a linker-specific API. + +NOTE: root module declarations are not reachable from other modules (due to cyclic imports) so shader parameters can only be replaced from the host code. (TODO: should we allow import cycles?) + +### Conditional Translation + +Changes to the Conditional Translation spec: + +* Module parameters of type `bool` can be used as feature flags for conditional translation. (TODO: integer conditionals) +* Conversely, only module parameters can be used for conditional translation. Shader parameters are controlled by the host code. +* The parameter needs to be imported (identifier in scope) to be used in `@if` +* With module concretizations, importer modules can set feature flags of imported modules. +* Typically projects define the feature flags in one module, publish them from the root module, and import them in the modules that need it. (TODO: is importing from the root module allowed?) + +## Implementation + +The same declaration in different concretizations of a generic module need to be mangled with unique names. (TODO) From da21cb05ab7d6d624b1d5b5bb80acdeef47c08ec Mon Sep 17 00:00:00 2001 From: Mathis Brossier Date: Tue, 26 Aug 2025 01:21:44 +0200 Subject: [PATCH 2/7] Update ModuleParameters.md --- ModuleParameters.md | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/ModuleParameters.md b/ModuleParameters.md index 74150e6..86227df 100644 --- a/ModuleParameters.md +++ b/ModuleParameters.md @@ -78,7 +78,9 @@ Modules containing parameter declarations are called generic modules. Each usage NOTE: aliases referring to the same declaration do not produce different concretizations. NOTE: each module concretization forms a distinct module. Importing multiple concretizations of the same module is allowed. -Root Module: Shader Parameters + +### Root Module: Shader Parameters + Module parameters defined or published (TODO: publish spec) in the Root Module are called Shader Parameters. Shader Parameters can be set from the host code with a linker-specific API. NOTE: root module declarations are not reachable from other modules (due to cyclic imports) so shader parameters can only be replaced from the host code. (TODO: should we allow import cycles?) From 8a2ba4211874f61f3e70ba6c536857a5b5350449 Mon Sep 17 00:00:00 2001 From: Mathis Brossier Date: Thu, 13 Nov 2025 21:26:43 +0100 Subject: [PATCH 3/7] Update ConditionalTranslation with param consts --- ConditionalTranslation.md | 167 ++++++++++++++++---------------------- 1 file changed, 71 insertions(+), 96 deletions(-) diff --git a/ConditionalTranslation.md b/ConditionalTranslation.md index 3f034d6..0e8e497 100644 --- a/ConditionalTranslation.md +++ b/ConditionalTranslation.md @@ -1,34 +1,40 @@ -# 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.* +> *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; // 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(); } ``` @@ -37,33 +43,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) @@ -80,14 +78,15 @@ A *translate-time attribute* can appear before the following syntax nodes: * [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. +> *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 @@ -99,7 +98,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 @@ -114,101 +113,77 @@ 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` *condition attribute* marks the decorated node for removal if its *condition expression* evaluates to `false`. +An `@elif` attribute decorates 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`. +An `@else` attribute decorates 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*. - -2. The source file is parsed. +## Error cases -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`. +It is a translate-time error if: +* The *WESL linker* was invoked with a *shader parameter* override that does not exist; +* A *shader parameter* override does not match the type of the declaration; +* -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 From 39d726ec8d7d77b2ea4c5cb032bf697c839313f8 Mon Sep 17 00:00:00 2001 From: Mathis Brossier Date: Thu, 13 Nov 2025 21:30:11 +0100 Subject: [PATCH 4/7] Delete ModuleParameters.md --- ModuleParameters.md | 100 -------------------------------------------- 1 file changed, 100 deletions(-) delete mode 100644 ModuleParameters.md diff --git a/ModuleParameters.md b/ModuleParameters.md deleted file mode 100644 index 86227df..0000000 --- a/ModuleParameters.md +++ /dev/null @@ -1,100 +0,0 @@ -# Module Parameters - -Module parameters are module-scope declarations that can be overridden by other modules. constants, functions, structs and type aliases can be overridden. They allow a form of genericity and work hand-in-hand with conditional translation. - -## Syntax - -A module-scope declaration is preceded by the `@param` attribute (TODO or param keyword? choose a syntax) to turn it into a parameter. The initializer (or the function body) is the default value for the parameter, and is mandatory (TODO currently?). - -Examples -``` -@param const MSAA_SAMPLES = 3; -@param const ENABLE_XYZ = false; -@param alias T = f32; -@param struct Point { x: T, y: T } -@param fn binop(lhs: T, rhs: T) { - return lhs + rhs; -} -``` - -The importer module can decide to replace a module parameter with a compatible declaration (see Parameter Compatibility) in the import statement. - -Proposed syntax #1 -``` -import binary_ops::{reduce_buffer as reduce_mul_f32}; -``` - -Proposed syntax #2 -``` -import binary_ops with {binop: mul_f32}::{reduce_buffer as reduce_mul_f32}; -``` - -Proposed syntax #3 -``` -@param(binop: mul_f32) -import binary_ops::{reduce_buffer as reduce_mul_f32}; -``` - -## Parameter Compatibility - -A Module Parameter can only be replaced with a compatible declaration. This ensures that any operations using the original declaration stay valid with the replacement declaration. - -### Type Compatibility - -A type T2 is compatible with a type T1 if one of these conditions is met: - -* T1 equals T2 -* Transitively, T2 is compatible with T1 if U is compatible with T1 and T2 is compatible with U. -* AbstractInt is compatible with AbstractFloat -* f32 and f16 are compatible with AbstractFloat -* i32 and u32 are compatible with AbstractInt -* T1 and T2 are structs, T1’s fields exist in T2, and are each compatible with T1’s field type. T2 may have additional fields. -* T1 and T2 are generated by the same type-generator (vec, mat, array), and the element type is compatible, and the element count is larger or equal. -* TODO: What about texture types? -* TODO: check what are the built-in function limitations with this compatibility thing. I’m sure there are some. - -Examples: TODO - -The compatibility criteria for module parameters replacement are as follows: - -* constants: a `const` declaration of identical type. -* type aliases: a built-in type, `struct` or `alias` declaration with a compatible type. -structs: a built-in type, `struct` or `alias` declaration with a compatible type. -* function declarations: a `fn` declaration with a compatible signature (user-defined or built-in). - A function signature is compatible if: - * It has the same number of parameters - * The parameter types are compatible with the original function’s parameters. (parameter covariance) - * If applicable, the original function’s return type is compatible with the replacement function’s return type (return contravariance) - * If the original function is void, the replacement function may return any type, or void. - -Examples: TODO - -## Behavior - -### Module concretizations - -Modules containing parameter declarations are called generic modules. Each usage of a generic module, with different parameters, forms a concretization of the module. Declarations inside different concretizations of the same module are duplicated. (TODO: what happens with declarations that don’t depend on module parameters? var decls? I think they should be duplicated) - -NOTE: aliases referring to the same declaration do not produce different concretizations. - -NOTE: each module concretization forms a distinct module. Importing multiple concretizations of the same module is allowed. - -### Root Module: Shader Parameters - -Module parameters defined or published (TODO: publish spec) in the Root Module are called Shader Parameters. Shader Parameters can be set from the host code with a linker-specific API. - -NOTE: root module declarations are not reachable from other modules (due to cyclic imports) so shader parameters can only be replaced from the host code. (TODO: should we allow import cycles?) - -### Conditional Translation - -Changes to the Conditional Translation spec: - -* Module parameters of type `bool` can be used as feature flags for conditional translation. (TODO: integer conditionals) -* Conversely, only module parameters can be used for conditional translation. Shader parameters are controlled by the host code. -* The parameter needs to be imported (identifier in scope) to be used in `@if` -* With module concretizations, importer modules can set feature flags of imported modules. -* Typically projects define the feature flags in one module, publish them from the root module, and import them in the modules that need it. (TODO: is importing from the root module allowed?) - -## Implementation - -The same declaration in different concretizations of a generic module need to be mangled with unique names. (TODO) From 716b902131e993c0ae308b4201baa04b51c1ccff Mon Sep 17 00:00:00 2001 From: Mathis Brossier Date: Thu, 13 Nov 2025 21:50:26 +0100 Subject: [PATCH 5/7] Update ConditionalTranslation.md --- ConditionalTranslation.md | 29 ++++++++++++++++++----------- 1 file changed, 18 insertions(+), 11 deletions(-) diff --git a/ConditionalTranslation.md b/ConditionalTranslation.md index 0e8e497..b7ce3eb 100644 --- a/ConditionalTranslation.md +++ b/ConditionalTranslation.md @@ -7,7 +7,8 @@ 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. -> *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")]`.* +> [!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 @@ -50,16 +51,16 @@ Quirky examples ## Definitions -* 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. +* 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 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 **Condition attribute** (`@if`, `@elif` and `@else`) are eliminated after translation but can also eliminate the syntax node it attached to. +* A **condition attribute** (`@if`, `@elif` and `@else`) are eliminated after translation but can also eliminate the syntax node it attached to. -## Location of *Condition attributes* +## Location of *condition attributes* A *condition attribute* can appear before the following syntax nodes: @@ -77,7 +78,7 @@ A *condition 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] +> [!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 @@ -122,10 +123,15 @@ A module-scope const-declaration can be decorated with the `@param` attribute to ## `@if`, `@elif` and `@else` attributes -The `@if` *condition attribute* marks the decorated node for removal if its *condition expression* evaluates to `false`. -An `@elif` attribute decorates the next sibling of a syntax node decorated by a `@if` or an `@elif`. +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`. -An `@else` attribute decorates the next sibling of a syntax node decorated by a `@if` or an `@elif`. + +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. @@ -146,9 +152,10 @@ fn f() { ... } ## Error cases It is a translate-time error if: + * The *WESL linker* was invoked with a *shader parameter* override that does not exist; -* A *shader parameter* override does not match the type of the declaration; -* +* 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. ## Execution of the conditional translation phase From 0d03b91c4c51d9786f2a79a4e9667e13e1e731ec Mon Sep 17 00:00:00 2001 From: Mathis Brossier Date: Sat, 13 Dec 2025 02:10:18 +0100 Subject: [PATCH 6/7] Apply suggestions and tentative explanation of the overriding process --- ConditionalTranslation.md | 53 +++++++++++++++++++++++++++++++-------- 1 file changed, 43 insertions(+), 10 deletions(-) diff --git a/ConditionalTranslation.md b/ConditionalTranslation.md index b7ce3eb..2ec0a41 100644 --- a/ConditionalTranslation.md +++ b/ConditionalTranslation.md @@ -12,7 +12,7 @@ This specification extends the [*attribute* syntax](https://www.w3.org/TR/WGSL/# ### Usage Example -```wesl +```wgsl // define module parameters with default values... @param const TEXTURED = true; @param const DEBUG = false; @@ -42,7 +42,7 @@ fn main() -> vec4 { Quirky examples -```wesl +```wgsl // attribute order does not matter. @compute @if(FEATURE) fn main() { } // ... is equivalent to this (preferred) @@ -51,14 +51,15 @@ Quirky examples ## Definitions -* A **module parameter** is an module-scope const-declaration decorated with the `@param` attribute. +* A **module parameter** is an module-scope const-declaration decorated with the `@param` attribute. Function-scope const-declarations cannot be module parameters*. * 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 boolean literal value (`true` or `false`). - * a boolean *module parameter* in scope, + * a [literal value](https://www.w3.org/TR/WGSL/#literals) (boolean or numeric literal). + * an [identifier expression](https://www.w3.org/TR/WGSL/#value-identifier-expr) referring to a *module parameter* in scope, * a [logical expression](https://www.w3.org/TR/WGSL/#logical-expr): logical not (`!`), short-circuiting AND (`&&`), short-circuiting OR (`||`), + * a [comparison expression](https://www.w3.org/TR/WGSL/#comparison-expr): `==`, `!=`, `<`, `<=`, `>`, `>=`, * a [parenthesized expression](https://www.w3.org/TR/WGSL/#parenthesized-expressions), -* A **condition attribute** (`@if`, `@elif` and `@else`) are eliminated after translation but can also eliminate the syntax node it attached to. +* **condition attributes** (`@if`, `@elif` and `@else`) are eliminated after translation but can also eliminate the syntax node it attached to. ## Location of *condition attributes* @@ -121,6 +122,24 @@ A module-scope const-declaration can be decorated with the `@param` attribute to * their value can be overriden by the *WESL translator*. * they can be used in *condition expressions* if they are of type boolean. +(TODO) +*Module parameter* overrides for direct dependencies can be set from the `wesl.toml` manifest file. +The manifest file can override *module parameters* by providing either a literal automatically convertible to the *module parameter* type, or the *fully-qualified path* to a *module parameter* in the current package. +Thus, the override in the dependency is "bound" to a new override in the current pacakge. + +(TODO) +*Module parameter* overrides for the root package, whoever, are passed to the *WESL translator* at *shader-translation-time*. +The *WESL translator* refers to a *module parameter* using the *fully-qualified path* of its declaration. +It can override the value by providing a literal automatically convertible to the *module parameter* type. + +(TODO) +> [!NOTE] +> Shader libraries have no means of overriding their own *module parameters*. + +(TODO) +If a dependency appears several times in the dependency graph, overrides several times the same *module parameter* (even if overriden with the same value), that dependency cannot be *unified*. +Otherwise, the dependency can be unified, and all overrides apply to the unified version. + ## `@if`, `@elif` and `@else` attributes The `@if` attribute takes a single *condition expression* parameter. @@ -138,7 +157,7 @@ A syntax node may at most have a single `@if`, `@elif` or `@else` attribute. Che Example: -```wesl +```wgsl @if(PARAM1 && PARAM2) const decl: u32 = 0; @if(PARAM1 && (!PARAM2 || PARAM3)) @@ -156,10 +175,12 @@ It is a translate-time error if: * 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. +* A syntax node has multiple `@param`, `@if`, `@elif` or `@else` attributes. There can be at most one of these four attributes per node. + In particular, a module parameter cannot be have a condition attribute. ## Execution of the conditional translation phase -1. The *WESL translator* is invoked with a list of *module parameters* names and values to override. +1. The *WESL translator* is invoked with a list of *module parameters* names and values to override *for each package*. 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. @@ -178,7 +199,7 @@ In the initial passes, the *WESL translator* is invoked with some *shader parame * 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 + ```wgsl @if(0x106 <= VERSION && VERSION <= 0x320) fn f() { ... } ``` @@ -186,13 +207,25 @@ In the initial passes, the *WESL translator* is invoked with some *shader parame * Decorating other WESL language extensions: import statements could be decorated with *condition attributes* too. Example: - ```wesl + ```wgsl @if(USE_BVH) import accel::bvh_acceleration_structure as scene_struct; @else import accel::default_acceleration_structure as scene_struct; ``` +## Appendix: Summary of constant declarations + +There are three types of so-called "constants" in WGSL. They are evaluated at +different shader stages. + +| syntax | declaration kind | shader stage | module-scope | function-scope | `@if` | array element count | +| ------ | ---------------- | ------------- | ------------ | -------------- | ----- | ------------------- | +| `@param const x = 0;` | module parameter | 1 shader-translation | yes | no | no | yes | +| `const x = 0;` | const-declaration | 2 shader-creation | yes | yes | yes | yes | +| `override x = 0;` | pipeline-overridable constant | 3 pipeline-creation | yes | no | yes | no | + + ## Appendix: Updated grammar The following non-terminals are added or modified: From 5fed5f8d8ea4e263ce47930dd035b50a03bd1e7e Mon Sep 17 00:00:00 2001 From: Mathis Brossier Date: Mon, 22 Dec 2025 12:43:50 +0100 Subject: [PATCH 7/7] Param consts only in root module --- ConditionalTranslation.md | 38 ++++++++++++++++++-------------------- 1 file changed, 18 insertions(+), 20 deletions(-) diff --git a/ConditionalTranslation.md b/ConditionalTranslation.md index 57d8ce2..520a6de 100644 --- a/ConditionalTranslation.md +++ b/ConditionalTranslation.md @@ -9,6 +9,7 @@ This specification extends the [*attribute* syntax](https://www.w3.org/TR/WGSL/# > [!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")]`. +> In JavaScript world, param consts are similar to environment variables, but there is no equivalent for conditional translation. ### Usage Example @@ -51,7 +52,7 @@ Quirky examples ## Definitions -* A **module parameter** is an module-scope const-declaration decorated with the `@param` attribute. Function-scope const-declarations cannot be module parameters*. +* A **module parameter** is an module-scope const-declaration decorated with the `@param` attribute, defined in a root module[^1]. Function-scope const-declarations cannot be module parameters*. * 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 [literal value](https://www.w3.org/TR/WGSL/#literals) (boolean or numeric literal). @@ -121,39 +122,33 @@ Refer to the [updated grammar appendix](#appendix-updated-grammar) for the list ## `@param` attribute -A module-scope const-declaration can be decorated with the `@param` attribute to turn it into a *module parameter*. +A module-scope const-declaration in the root module[^1] 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. +* they can be used in *condition expressions* for conditional translation. -(TODO) -*Module parameter* overrides for direct dependencies can be set from the `wesl.toml` manifest file. -The manifest file can override *module parameters* by providing either a literal automatically convertible to the *module parameter* type, or the *fully-qualified path* to a *module parameter* in the current package. -Thus, the override in the dependency is "bound" to a new override in the current pacakge. +Override values for *module parameters* can be provided in two places: +* For the application's root module: they are passed to the *WESL translator* at *shader-translation-time*. +* For direct dependencies: they are specified in the `wesl.toml` manifest file. + (TODO: this is likely not what we want. We want to provide values from the host code! How do we do that for dependencies, without publish?) -(TODO) -*Module parameter* overrides for the root package, whoever, are passed to the *WESL translator* at *shader-translation-time*. -The *WESL translator* refers to a *module parameter* using the *fully-qualified path* of its declaration. -It can override the value by providing a literal automatically convertible to the *module parameter* type. - -(TODO) > [!NOTE] -> Shader libraries have no means of overriding their own *module parameters*. +> Currently, shader packages have no means of overriding their own *module parameters*. +> Later iterations of this mechanism may allow overriding *module parameters* from shader code. -(TODO) -If a dependency appears several times in the dependency graph, overrides several times the same *module parameter* (even if overriden with the same value), that dependency cannot be *unified*. +If a dependency appears several times in the dependency graph and overrides several times the same *module parameter* (even if overriden with the same value), that dependency cannot be *unified*. Otherwise, the dependency can be unified, and all overrides apply to the unified version. ## `@if`, `@elif` and `@else` attributes -The `@if` attribute takes a single *condition expression* parameter. +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`. +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. +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`. @@ -176,12 +171,15 @@ fn f() { ... } It is a translate-time error if: +* an module parameter was declared outside of the root module[^1]; * 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. * A syntax node has multiple `@param`, `@if`, `@elif` or `@else` attributes. There can be at most one of these four attributes per node. In particular, a module parameter cannot be have a condition attribute. +[^1]: Currently only root module declarations can be parameters. This is due to the lack of publish/re-export mechanism, see #65. This limitation will be lifted soon. + ## Execution of the conditional translation phase 1. The *WESL translator* is invoked with a list of *module parameters* names and values to override *for each package*.