Control flow: familiar syntax, explicit GPU execution
TypeShade uses familiar TypeScript conditional and loop syntax, but the code executes on the GPU. Understand control flow in terms of compilable computation, not the full dynamic behavior of the JavaScript runtime.
1. Start from the TypeScript control-flow model
Syntax such as if/else and for is familiar, but every condition and loop in TypeShade must be lowerable to GPU code. Matching syntax does not imply that all JavaScript runtime semantics are available.
2. Branching
Use if/else to express calculation paths. Inside a branch, keep values and resources within the TypeShade GPU model.
@compute([64, 1, 1])export function paint(@builtin("global_invocation_id") gid: vec3u) { if (camera.pos.x > 0) { pixels[gid.x] = 1 } else { pixels[gid.x] = 0 }}3. Loops
Write a for loop, a while loop, or for...of over an array. A for loop may count to a value known only at run time, such as a uniform field or the length of an array. An array's map, forEach, some, every and reduce compile too, each to a counted loop, and map takes an array with a fixed length.
for (let i = 0; i < 4; i++) { pixels[i] = pixels[i] + camera.pos.x}4. A loop that runs as a kernel
An exported function that is not an entry point and takes an array with no size, array<T>, is a kernel function (Rule 8.22). Its array is the caller's, read and written in place. Each for at the top of its body is a loop the compiler may run on the GPU, one invocation per iteration, and a host file awaits the function like any import. No @compute, binding or global_invocation_id is written.
export function render(k: vec4, size: u32, out: array<f32>) { for (let i: u32 = 0; i < size * size; i++) { const p = vec2(f32(i % size), f32(i / size)) / f32(size) out[i] = k.x * sin(p.x * k.y) + k.z * cos(p.y * k.w) }}The compiler runs a loop on the GPU when it proves that no iteration touches what another one does. The loop is a counted for, or a for...of, that steps by a constant and that nothing returns from or breaks out of. Each write lands on a name declared inside the loop, on an outer array or a texture at an index made from the loop's own, on an integer array the loop combines into at any index, or on a variable the loop combines with +=, *=, min, max, &, | or ^, which the GPU and the CPU fold in one tree order. The loop reads an array it writes only where it writes it and never reads a variable it combines, calls no function that writes a module variable or a binding, and has no barrier, no workgroup memory and no console call.
A loop the proof refuses runs on the CPU, and the program is correct either way. The proof never looks at a loop in an entry, a helper or a fragment shader, which stays per-invocation code. The compiler marks a refused loop with the warning TS8070: its first sentence names the line and the names in your code, and its second gives the remedy. At the pinned compiler the warnings read:
| Rule | Warning |
|---|---|
R1 | This loop runs on the CPU because it is a while loop, whose trip count is known only when it ends. A for loop over a count runs on the GPU. |
R1 | This loop runs on the CPU because "stride /= 2" does not step through a range of indices. Step by adding a constant. |
R2 | This loop runs on the CPU because line 9 returns from inside it, so whether an iteration runs depends on the ones before it. Record the result in an array and read it after the loop. |
R3 | This loop runs on the CPU because line 49 writes "nearest", which the next iteration reads. Declare it inside the loop, or combine it with one of += *= min max & | ^. |
R3 | This loop runs on the CPU because line 4 writes "b[idx[i]]", an element two iterations can share. Write at an index made from "i". |
R4 | This loop runs on the CPU because line 9 reads "out[i - 1]", which another iteration writes. Read from an array the loop does not write. |
R5 | This loop runs on the CPU because line 7 calls "tally", which writes "calls". Return the value from "tally" and combine it in the loop instead. |
R6 | This loop runs on the CPU because line 5 calls console.log, whose lines would print in another order on the GPU. Log after the loop. |
5. Where JavaScript control flow stops
- Write filter, find and the other array methods that change a length or search for an element as a loop.
- Do not rely on general runtime objects.
- Keep conditions and loop ranges based on GPU-compilable values.
- A control-flow pattern valid in TypeScript is not automatically valid under TypeShade shader semantics.