WESL Logo

#Conditional Translation

#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 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.

This implementation is similar to the #[cfg(feature = "")] syntax in Rust.

#Usage Example

// global variables and bindings...
@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)
  ray_steps: u32,
}

// function declarations, parameters and statements...
fn main() -> vec4f {
  @if(legacy_implementation || (is_web_version && xyz_not_supported))
  let result = legacy_impl();
  @if(!legacy_implementation && !(is_web_version && xyz_not_supported))
  let result = modern_impl();
}

Quirky examples

// 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.
}

#Definitions

#Location of Translate-time attributes

A translate-time attribute can appear before the following syntax nodes:

[!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.

#Update to the WGSL grammar

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

Refer to the updated grammar appendix 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:

    • function return types
    • the body (part surrounded by curly braces) of:
      • function declarations
      • switch statements
      • switch clauses
      • loop statements
      • for statements
      • while statements
      • if/else statements
      • continuing statements
  2. The grammar is extended to allow translate-time attributes before the following syntax nodes:

    • const value declarations
    • variable declarations
    • directives
    • struct declarations
    • switch clauses
    • assignment statements
    • increment and decrement statements
    • break statements
    • break-if statements
    • continue statements
    • continuing statements
    • return statements
    • discard statements
    • function call statements
    • const assertion statements

#@if attribute family

The @if, @elif and @else translate-time attributes are introduced. The decorated node is only kept if the branch is truthy.

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

@if(feature1 && feature2)   const decl: u32 = 0;

Example:

@if(feature_1 && (!feature_2 || feature_3))
fn f() { ... }
@elif(!feature_1)
fn f() { ... }
@else
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.

  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.
  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.
  5. 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:

If the WESL translator does not support incremental translation, it is a link-time error if any used translate-time feature 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.

#Appendix: Updated grammar

The following non-terminals are added or modified. Global declarations get extended to handle general attributes to support future experiments such as @deprecated. Everything else is extended with the more restricted unambiguous_attribute:

    diagnostic_directive :
      unambiguous_attribute * 'diagnostic' diagnostic_control ';'

    enable_directive :
      unambiguous_attribute * 'enable' enable_extension_list ';'

    requires_directive :
      unambiguous_attribute * 'requires' software_extension_list ';'

    struct_decl :
      attribute * 'struct' ident struct_body_decl
     
    type_alias_decl :
      attribute * 'alias' ident '=' type_specifier

    variable_or_value_statement :
      unambiguous_attribute * variable_decl
    | unambiguous_attribute * variable_decl '=' expression
    | unambiguous_attribute * 'let' optionally_typed_ident '=' expression
    | unambiguous_attribute * 'const' optionally_typed_ident '=' expression

    variable_decl :
      'var' _disambiguate_template template_list ? optionally_typed_ident
     
    global_value_decl :
      attribute * 'const' optionally_typed_ident '=' expression
    | ...

    case_clause :
      attribute * 'case' case_selectors ':' ? compound_statement

    default_alone_clause :
      attribute * 'default' ':' ? compound_statement

    assignment_statement :
      unambiguous_attribute * lhs_expression ( '=' | compound_assignment_operator ) expression
    | unambiguous_attribute * '_' '=' expression

    increment_statement :
      unambiguous_attribute * lhs_expression '++'

    decrement_statement :
      unambiguous_attribute * lhs_expression '--'

    break_statement :
      unambiguous_attribute * 'break'

    break_if_statement :
      unambiguous_attribute * 'break' 'if' expression ';'

    continue_statement :
      unambiguous_attribute * 'continue'
     
    continuing_statement :
      unambiguous_attribute * 'continuing' continuing_compound_statement

    return_statement :
      unambiguous_attribute * 'return' expression ?
    
    discard_statement:
      unambiguous_attribute * 'discard'

    func_call_statement :
      unambiguous_attribute * call_phrase

    global_assert :
      unambiguous_attribute * const_assert

    assert_statement :
      unambiguous_attribute * const_assert

    statement :
      ';'
    | ...
    | discard_statement ';'
    | ...

    unambiguous_attribute:
      '@' ident_pattern_token argument_expression_list
    | '@' 'if' '(' expression ',' ? ')'
    | '@' 'elif' '(' expression ',' ? ')'
    | '@' 'else'
      
    attribute :
      '@' ident_pattern_token
    | unambiguous_attribute
    | align_attr
    | ...

[!NOTE] No alternative of unambiguous_attribute has an optional argument list, so a parser always knows where the attribute ends. attribute lets the argument list be omitted, which is ambiguous before a statement: in @foo (x)++, the (x) could be @foo’s argument or the start of the statement. Using unambiguous_attribute keeps the grammar LR(1) and allows@if(FOO) (x)++.

#Possible extensions