Structured advanced compute
TinyFX keeps ordinary parallel for as the race-free default and exposes a
separate resource model for algorithms that need retained in-place state,
integer atomics, dynamic output allocation, or several dependent dispatches.
Statements execute in source order. There is no phase or top-level
compute construct.
Resource types
The Tier 1 element set is int, uint, float, their numeric vectors, and
enums. Atomic storage is restricted to scalar int or uint.
let scratch = workspace<float4>(65_536);
let bins = atomics<uint>(256);
let selected = appendList<uint>(65_536);
let cursor = counter(target: scratch);workspace<T>is retained, in-place GPU storage.atomics<int|uint>is accessed only through atomic methods.appendList<T>owns fixed-capacity data, a count, and an overflow flag.counter(target:)produces reservations valid only for its target workspace.
Advanced storage is affine: assigning the same resource to a second local is rejected. This gives the checker one resource identity on which to record the dispatch access role and GPU-count provenance.
Workspace access roles
Each workspace has one checked role per dispatch:
| Role | Allowed access |
|---|---|
| storage read | arbitrary reads and no writes |
| owned write | writes to the exact loop index or a target-bound slot and no reads |
| owned read/write | reads and writes of the exact loop index only |
The exact index rule includes component and non-repeating swizzle writes. A same-dispatch gather plus write is rejected; split it into two source-ordered statements.
parallel for i in 0..scratch.len() {
scratch[i] = scratch[i] + 1.0; // owned read/modify/write
}Atomics and unordered
Void commutative updates are deterministic when one atomic resource uses one compatible family in a dispatch: add/subtract, min, max, and, or, or xor. Their integer arithmetic wraps at 32 bits.
Operations that expose arbitration order require the contextual prefix
unordered parallel for: fetchAdd, exchange, compare/exchange, a load that
races an update, reservation claims, and append.
counts.clear(0);
parallel for i in 0..values.len() {
counts.add(bucket(values[i]), 1); // exact final histogram
}
selected.reset();
unordered parallel for i in 0..values.len() {
if (values[i] > 0.0) { selected.append(uint(i)); }
}An append past capacity is dropped, sets overflowed, and never writes out of
bounds. selected.overflowed is a synchronized snapshot observation.
Target-bound slots
let storage = workspace<uint>(128);
let cursor = counter(target: storage);
cursor.reset();
unordered parallel for i in 0..items.len() {
const slot = storage.claim(cursor);
if (slot.valid) { storage.write(slot, uint(i)); }
}A slot cannot be mutable, copied, returned, captured, reused, or passed to a different workspace. Its type is an opaque capability, not an integer index.
Deterministic operations
The operations layer currently provides sequential reference semantics in the VM and generated TypeScript core:
let counts = values.histogram(bins: 256, key: (value: float) => bucket(value));
let starts = counts.exclusiveScan();
let alive = values.compact(keep: (value: float) => value > 0.0);
let grouped = categories.groupBy(groups: 12, key: (value: int) => value);
let ordered = priorities.sortBy(key: (value: int) => value);
let total = values.reduce(initial: 0, combine: (a: int, b: int) => a + b);Integer scans, histograms, stable compact/group/sort, and integer reductions are exact. Compact preserves input order. Group-by emits groups in ascending group order and preserves input order within each group. Sort-by is stable. The reference reduction is a left-to-right fold. A GPU-resident physical operation planner, including its floating-point order/tolerance contract, is still required before these methods can be used as a no-readback simulation pipeline.
GPU-resident counts
appendList.count and operation result counts are opaque views tied to one
producer allocation and its known maximum. They can be used where a count
belongs:
parallel for i in 0..alive.count { consume(alive[i]); }
drawAdvanced(shader: particles, mesh: quad, instances: alive.count);The shared Rust wgpu core writes private 12-byte dispatch or 16/20-byte draw argument buffers with a one-invocation fixup and calls WebGPU indirect execution. The main range kernel reads the producer count itself to guard the final partial workgroup. Zero count therefore produces zero work without mapping the producer. Raw indirect buffers and arbitrary atomic-to-count casts are not source-visible.
Execution-target status
| Surface | Tier 1 primitives | Operations | GPU-count consumption |
|---|---|---|---|
| Rust VM reference | sequential | sequential | direct logical count |
| shared Rust wgpu (native and wasm) | GPU | sequential host path today; GPU-owned wasm input is rejected | indirect draw/dispatch |
| generated TypeScript reference | sequential callback | sequential | direct logical count |
| generated TypeScript WebGPU | not yet represented by a physical schema | not yet represented | not yet represented |
Generated WebGPU execution fails directly when an advanced command has no
physical blueprint; it does not silently select a browser CPU fallback. The
remaining generated-WebGPU and operation-planner work is tracked as an
acceptance blocker in SPRINT_4.md.
Inspection and compatibility
tfx build --explain-passes reports advanced access roles, logical operation
subpasses, retained candidates, and indirect plans. Native
tfx run --trace-passes reports the selected workgroup and whether the
observed range or draw used GPU-count indirect lowering.
Advanced programs use .tfxb revision 14. Readers require an exact revision;
revision 13 artifacts fail with the normal rebuild guidance. Existing builtin
wire-key and instruction identities remain unchanged, with new tags appended.
Tier 1 does not expose workgroup-local memory, barriers, subgroups, bind groups, raw WGSL, or a global GPU barrier.