Skip to content

No Aliasing in Nested Calls

In complex shaders, functions often delegate work to other helper functions, passing their pointer parameters deeper down the call stack. To guarantee zero-aliasing optimization safety across the entire program, WGSL's compiler does not just look at the immediate function you call—it recursively evaluates every nested function call triggered by that invocation.

This transitive, multi-layered tracking is known as Deep Call-Stack Alias Analysis.


How Transitive Aliasing Validation Works

When a shader is compiled, the compiler constructs a Call Graph of the program and performs a bottom-up analysis of memory access views:

  1. Leaf Analysis: The compiler starts at the bottom of the call stack (leaf functions that call no other user functions). It identifies all reads and writes performed via pointer parameters and references.
  2. Access Propagation: When a parent function calls a helper, the compiler maps the helper's pointer accesses back to the arguments passed by the parent.
  3. Root Trace Back: Finally, at the entry-point call site, the compiler traces all argument paths back to their original root identifiers (the base variables declared via var at local or module scope).
  4. Overlap Verification: If two distinct argument paths map back to the same root identifier, and any downstream nested function in the call tree contains a potential write along one of those paths, the entire compilation fails.

Visualizing Call Stack Access Conflicts

The diagram below illustrates how a conflict in a deeply nested helper propagates back up to fail the caller.

Nested Call Stack Aliasing Error

1. f3() (Caller Site) ROOT DECLARATION

Declares a local variable `var a: i32` and calls `f2(&a, &a)`. Both pointer arguments share the same physical root block in the thread's stack.

f2(p1: &a, p2: &a) → Conflict Triggered
2. f2(p1, p2) (Intermediate) DELEGATION FRAME

Receives two pointer parameters and forwards them directly to the leaf function: `f1(p1, p2)`. It performs no accesses itself, but passes the aliased views.

3. f1(p1, p2) (Leaf Conflict) HAZARD LOCATED

Executes code: *p1 = *p2;

The compiler detects a read from `p2` and a **write** to `p1`. Since both trace back to the same root variable `a`, an illegal write-alias conflict is registered.


Under the Hood: Resolving to Backend Shaders

Why is this level of scrutiny necessary? When the GPU driver compiles your WGSL shader into native backend instructions (such as HLSL for Direct3D, MSL for Metal, or GLSL/SPIR-V for Vulkan), it often expands WGSL pointer parameters into the backend's native parameter representation.

In Vulkan (SPIR-V) or Metal (MSL), pointers are compiled as physical memory spaces or restricted register references. If they aliased with writes, hardware execution would cause memory ordering undefined behaviors, or trigger severe pipeline stalls while waiting for cache flushes.

In DX12 (HLSL), user-declared functions with inout pointer parameters are often compiled as a Copy-In / Copy-Out register operations. If you alias the same variable across two parameters, the value written back to the root variable when the function returns would be non-deterministic, as each parameter copies its local register back to the variable in an arbitrary sequence.

WGSL prevents all of these hardware-specific compilation and concurrency disasters by stopping compilation at the high-level stage!

Valid Disjoint Accesses

If you declare two separate variables var a: i32; and var b: i32;, you can freely call f2(&a, &b). Because a and b represent completely independent allocations with disjoint roots, there is no overlap hazard. The compiler can promote both to registers, run instructions in parallel, and execute the helper functions at maximum hardware speed.