Commit 1462c31
WGSL: emit runtime-indexable static const arrays as var<private> (#11628)
## Motivation
When targeting WGSL, a module-scope `static const` array is emitted as a
WGSL `const`. But a WGSL `const` is a compile-time value (closer to C++
`constexpr` than to `const`): the WGSL spec permits a *value* of array
type to be indexed only by a const-expression. So indexing such a global
by a runtime value is rejected by the WGSL validator.
Motivating case (from the issue):
```hlsl
static const float2 positions[] = {
float2(-1.0f, -1.0f), float2(1.0f, -1.0f), float2(1.0f, 1.0f), float2(-1.0f, 1.0f)
};
[shader("vertex")]
VertexOutput vertexMain(uint vertexID : SV_VertexID)
{
VertexOutput o;
o.position = float4(positions[vertexID], 0.0f, 1.0f); // runtime index
return o;
}
```
emits `const positions_0 : array<vec2<f32>, i32(4)> = ...;` then
`positions_0[vertexID_0]`, which naga/tint reject: *"The expression may
only be indexed by a constant."*
## Proposed solution
Emit module-scope `static const` globals of **array** type as WGSL
`var<private>` (a private module-scope variable), keeping their inline
const-expression initializer, instead of `const`. A `var<private>` holds
the same value but is addressable, so it is runtime-indexable. After the
fix the array emits `var<private> positions_0 : array<vec2<f32>, i32(4)>
= array<vec2<f32>, i32(4)>( ... );` and `positions_0[vertexID_0]` is
valid.
Only arrays are converted. A WGSL matrix *value* is itself
runtime-indexable, so a `const` matrix needs no conversion;
scalars/vectors stay `const` (a vector value is also dynamically
indexable), and function-local constants are unaffected.
Nested aggregates need one more step. A nested `static const` (e.g. `int
grid[2][3]`) lowers to nested `MakeArray` insts, each a module-scope
array constant emitted as a *separate named* declaration. Converting all
of them to `var<private>` would make the outer initializer reference
inner `var<private>`s — illegal WGSL (a `var<private>` initializer must
be a const-expression and cannot read another `var`). The fix folds the
inner constituents inline so the outermost runtime-indexed array becomes
a single `var<private>` with a self-contained initializer:
```wgsl
var<private> grid_0 : array<array<i32, i32(3)>, i32(2)> = array<array<i32, i32(3)>, i32(2)>( array<i32, i32(3)>( i32(1), i32(2), i32(3) ), array<i32, i32(3)>( i32(4), i32(5), i32(6) ) );
```
Alternatives ruled out: a `static`-only (non-`const`) global lowers its
initializer into an init function, producing the broken `const _S3 =
...;` statements seen in the issue thread; keeping the initializer
inline avoids that. A precise "is this global ever dynamically indexed"
use-analysis is unnecessary — constant-indexed reads are already
constant-folded before emit (see Process report).
## Change summary
| File | Change |
| --- | --- |
| `source/slang/slang-emit-wgsl.cpp` | `emitVarKeywordImpl`: a
module-scope **array** constant emits `var` + `<private>` instead of
`const`. `shouldFoldInstIntoUseSites`: for WGSL, fold a module-scope
`MakeArray`/`MakeStruct`/`MakeArrayFromElement` inline when it is only a
nested constituent of another aggregate, so the outermost array's
initializer stays self-contained. |
| `tests/wgsl/static-const-array-indexing.slang` | Runtime-indexed array
→ `var<private>`; `+ wgsl-spirv-asm` (Tint) validation directive. |
| `tests/wgsl/static-const-array-nested.slang` | New: nested 2D-array
regression — one inline `var<private>`, no separate inner-array decls;
`+ wgsl-spirv-asm` directive. |
| `tests/wgsl/static-const-matrix.slang` | New: a runtime-indexed
`static const` matrix stays an inline `const` value (not
`var<private>`); `+ wgsl-spirv-asm` directive validates it through Tint
on CI. |
| `tests/wgsl/static-const-array-const-index.slang` | A constant-indexed
element folds away and leaves no `const`/`var` reading another variable;
tightened anchors + negative guard. |
| `docs/user-guide/a2-03-wgsl-target-specific.md` | Document the
module-scope `const` (scalar/vector/matrix) vs `var<private>` (array)
split. |
## Concepts and vocabulary
- **WGSL `const` vs `var<private>`** — a WGSL `const` is a compile-time
value; a `const` value of array type may only be indexed by a
const-expression. A `var<private>` is an addressable module-scope
variable, indexable by a runtime value, whose initializer must still be
a const-expression.
- **`GlobalConstant` / `MakeArray`** — a `static const` array lowers to
an `IRGlobalConstant` wrapping an `IRMakeArray`; nested arrays nest
`MakeArray`s.
- **`replaceGlobalConstants` / peephole fold** —
`replaceGlobalConstants` inlines a global constant's value into its
uses; the peephole pass folds `GetElement(MakeArray, constIndex)` to the
element, leaving a runtime index intact.
- **`shouldFoldInstIntoUseSites`** — decides whether an inst is emitted
inline at its use or as its own declaration. WGSL can fold
`MakeArray`/`MakeStruct` (constructor expressions, valid in any
expression context) where the base class cannot (C/HLSL initializer
lists).
## Process report
**`emitVarKeywordImpl` (`source/slang/slang-emit-wgsl.cpp`).** The
declared inst for a module-scope `static const` array is an ordinary
module-scope instruction; `emitInstResultDecl` (`slang-emit-c-like.cpp`)
emits `<keyword> <name> : <type> = <initializer>`, with the keyword from
`emitVarKeywordImpl`. Previously the `default:` arm emitted `const` for
any module-scope constant. The predicate
`emitModuleScopeArrayConstAsPrivateVar` reuses the existing
`isStaticConst` helper and is true when the inst is a module-scope
constant (`isStaticConst(varDecl)`), is not a `GlobalParam`, and has
type `kIROp_ArrayType`; it then emits `var` and `<private>` (joining the
existing `kIROp_GlobalVar` storage-space branch, after the
array-of-ConstantBuffer/structured-buffer branches, so resource arrays
are unaffected). The `!= kIROp_GlobalParam` guard is load-bearing: the
predicate is also read in the address-space chain (which runs for all
ops), where a module-scope `GlobalParam` array — e.g. a descriptor array
like `Texture2D t[8]`, whose element type isn't
ConstantBuffer/structured so it falls through to the final branch —
would otherwise wrongly get `<private>` instead of its handle address
space. (`isStaticConst` is true for any module child, including
`GlobalParam`/`GlobalVar`, so the guard is required; a local `Var` is
not module-scope and is already excluded.)
**Why a type-based conversion is safe (no const-expression chain
breaks).** A constant index into a static-const array is constant-folded
before emit, so no `const` is left whose initializer reads the converted
`var`: `replaceGlobalConstants` inlines `GlobalConstant` values into
uses before `simplifyIR`, then the peephole `kIROp_GetElement` case
folds `GetElement(MakeArray, IRIntLit)` to the element while a
non-constant (runtime) index hits `if (!index) break;` and is left
intact. `tests/wgsl/static-const-array-const-index.slang` guards this.
**Nested aggregates — `shouldFoldInstIntoUseSites`.** The base class
never-folds `MakeArray`/`MakeStruct`/`MakeArrayFromElement` because in
C/HLSL they lower to initializer lists, invalid in a general expression
context. WGSL instead emits them as constructor expressions
(`type(args...)`, `tryEmitInstExprImpl`), valid anywhere. So a nested
`static const` otherwise emits each inner `MakeArray` as a separate
named module-scope decl, and converting them all to `var<private>`
yields an outer initializer that references inner `var<private>`s —
illegal. The override folds a module-scope constituent inline when every
use of it is an operand of another
`MakeArray`/`MakeStruct`/`MakeArrayFromElement` (checked by a small
inlined loop), so the outermost aggregate — used directly, e.g. by a
runtime `GetElement` — stays a declaration eligible for `var<private>`
while its constituents inline. Function-local aggregates are left to the
base policy; resource/global-parameter arrays keep their existing
declaration paths. `tests/wgsl/static-const-array-nested.slang` covers a
runtime-indexed 2D array.
*Known limitation:* an aggregate that is both a nested constituent and
used directly — a named `static const` array shared as an element of
another `static const` array *and* independently runtime-indexed — is
not folded, so it stays a separate `var<private>` declaration that the
enclosing converted array's initializer references by name (invalid
WGSL). This is not constructible from typical anonymous nested literals,
and is not a regression for accepted WGSL output (such a constant was a
rejected runtime-indexed `const` before this change too, though the
failure now takes a different form); fully handling it would require
inlining a duplicate copy of the constituent into the enclosing
initializer.
**Input-shape check (right layer?).** Yes. The IR for a `static const`
array is correct (`IRGlobalConstant` wrapping `IRMakeArray`); the
defects are WGSL keyword selection and WGSL fold policy, both
target-specific. The fix belongs in the WGSL emitter.
**Verification.** Built a debug `slangc`: the flat and nested cases emit
single self-contained `var<private>` declarations indexed by name; a
`static const` matrix is emitted as an inline matrix value indexed in
place (`mat2x2<…>(…)[i]`), not promoted to `var<private>`. The full
`tests/wgsl/` suite passes (49/49). Note: `slangc` emitting WGSL is not
the same as a validator accepting it — Tint is not available in the
build environment, so the `wgsl-spirv-asm` directives (which round-trip
the emitted WGSL through Tint) are ignored locally and run on CI. The
matrix test's directive is what would confirm or refute that a
runtime-indexed `const` matrix is valid WGSL.
**Known limitation — arrays nested inside a `static const` struct.** A
`static const` struct with an array field, runtime-indexed via
`g.arr[i]`, is *not* addressed: the struct is not `ArrayType`, so it
stays `const`, and it lowers through a constructor function (`g.arr[i]`
→ `Foo_init(...).arr[i]`) whose result is an array value runtime-indexed
— the same #6747 rejection. This is a realistic lookup-table shape but a
distinct (struct-valued) problem from the top-level arrays #6747
reports, and is left as a follow-up.
Fixes #6747.
---------
Co-authored-by: nv-slang-bot[bot] <274397474+nv-slang-bot[bot]@users.noreply.github.com>
Co-authored-by: Harsh Aggarwal <haaggarwal@nvidia.com>1 parent 1286697 commit 1462c31
6 files changed
Lines changed: 208 additions & 3 deletions
File tree
- docs/user-guide
- source/slang
- tests/wgsl
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
158 | 158 | | |
159 | 159 | | |
160 | 160 | | |
| 161 | + | |
| 162 | + | |
| 163 | + | |
161 | 164 | | |
162 | 165 | | |
163 | 166 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
808 | 808 | | |
809 | 809 | | |
810 | 810 | | |
| 811 | + | |
| 812 | + | |
| 813 | + | |
| 814 | + | |
| 815 | + | |
| 816 | + | |
| 817 | + | |
| 818 | + | |
| 819 | + | |
| 820 | + | |
| 821 | + | |
| 822 | + | |
| 823 | + | |
| 824 | + | |
811 | 825 | | |
812 | 826 | | |
813 | 827 | | |
| |||
824 | 838 | | |
825 | 839 | | |
826 | 840 | | |
827 | | - | |
| 841 | + | |
| 842 | + | |
| 843 | + | |
| 844 | + | |
| 845 | + | |
| 846 | + | |
828 | 847 | | |
829 | 848 | | |
830 | 849 | | |
| |||
872 | 891 | | |
873 | 892 | | |
874 | 893 | | |
875 | | - | |
| 894 | + | |
876 | 895 | | |
877 | | - | |
| 896 | + | |
| 897 | + | |
| 898 | + | |
878 | 899 | | |
879 | 900 | | |
880 | 901 | | |
| |||
1386 | 1407 | | |
1387 | 1408 | | |
1388 | 1409 | | |
| 1410 | + | |
| 1411 | + | |
| 1412 | + | |
| 1413 | + | |
| 1414 | + | |
| 1415 | + | |
| 1416 | + | |
| 1417 | + | |
| 1418 | + | |
| 1419 | + | |
| 1420 | + | |
| 1421 | + | |
| 1422 | + | |
| 1423 | + | |
| 1424 | + | |
| 1425 | + | |
| 1426 | + | |
| 1427 | + | |
| 1428 | + | |
| 1429 | + | |
| 1430 | + | |
| 1431 | + | |
| 1432 | + | |
| 1433 | + | |
| 1434 | + | |
| 1435 | + | |
| 1436 | + | |
| 1437 | + | |
1389 | 1438 | | |
1390 | 1439 | | |
1391 | 1440 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
| 1 | + | |
| 2 | + | |
| 3 | + | |
| 4 | + | |
| 5 | + | |
| 6 | + | |
| 7 | + | |
| 8 | + | |
| 9 | + | |
| 10 | + | |
| 11 | + | |
| 12 | + | |
| 13 | + | |
| 14 | + | |
| 15 | + | |
| 16 | + | |
| 17 | + | |
| 18 | + | |
| 19 | + | |
| 20 | + | |
| 21 | + | |
| 22 | + | |
| 23 | + | |
| 24 | + | |
| 25 | + | |
| 26 | + | |
| 27 | + | |
| 28 | + | |
| 29 | + | |
| 30 | + | |
| 31 | + | |
| 32 | + | |
| 33 | + | |
| 34 | + | |
| 35 | + | |
| 36 | + | |
| 37 | + | |
| 38 | + | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
| 1 | + | |
| 2 | + | |
| 3 | + | |
| 4 | + | |
| 5 | + | |
| 6 | + | |
| 7 | + | |
| 8 | + | |
| 9 | + | |
| 10 | + | |
| 11 | + | |
| 12 | + | |
| 13 | + | |
| 14 | + | |
| 15 | + | |
| 16 | + | |
| 17 | + | |
| 18 | + | |
| 19 | + | |
| 20 | + | |
| 21 | + | |
| 22 | + | |
| 23 | + | |
| 24 | + | |
| 25 | + | |
| 26 | + | |
| 27 | + | |
| 28 | + | |
| 29 | + | |
| 30 | + | |
| 31 | + | |
| 32 | + | |
| 33 | + | |
| 34 | + | |
| 35 | + | |
| 36 | + | |
| 37 | + | |
| 38 | + | |
| 39 | + | |
| 40 | + | |
| 41 | + | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
| 1 | + | |
| 2 | + | |
| 3 | + | |
| 4 | + | |
| 5 | + | |
| 6 | + | |
| 7 | + | |
| 8 | + | |
| 9 | + | |
| 10 | + | |
| 11 | + | |
| 12 | + | |
| 13 | + | |
| 14 | + | |
| 15 | + | |
| 16 | + | |
| 17 | + | |
| 18 | + | |
| 19 | + | |
| 20 | + | |
| 21 | + | |
| 22 | + | |
| 23 | + | |
| 24 | + | |
| 25 | + | |
| 26 | + | |
| 27 | + | |
| 28 | + | |
| 29 | + | |
| 30 | + | |
| 31 | + | |
| 32 | + | |
| 33 | + | |
| 34 | + | |
| 35 | + | |
| 36 | + | |
| 37 | + | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
| 1 | + | |
| 2 | + | |
| 3 | + | |
| 4 | + | |
| 5 | + | |
| 6 | + | |
| 7 | + | |
| 8 | + | |
| 9 | + | |
| 10 | + | |
| 11 | + | |
| 12 | + | |
| 13 | + | |
| 14 | + | |
| 15 | + | |
| 16 | + | |
| 17 | + | |
| 18 | + | |
| 19 | + | |
| 20 | + | |
| 21 | + | |
| 22 | + | |
| 23 | + | |
| 24 | + | |
| 25 | + | |
| 26 | + | |
| 27 | + | |
| 28 | + | |
| 29 | + | |
| 30 | + | |
| 31 | + | |
| 32 | + | |
| 33 | + | |
| 34 | + | |
| 35 | + | |
| 36 | + | |
| 37 | + | |
0 commit comments