Goals

Rake is a language for SIMD programs. It lets a programmer state the vector structure of an algorithm, its registers, masks and data layout, without writing intrinsics or assembly. This page defines the language's compilation rules. When a program can't be compiled under them, the compiler rejects it, and it never keeps a program by weakening a rack, a fused region or a rake into scalar code. Each section ends with what the compiler does today.

Racks are vector values

A rack is one vector register of a physical CPU profile, or one vector value in a virtual machine profile. The profile fixes its width, and the element type gives the lane count: on a 256-bit profile an f32s rack has eight lanes. A rack is never split, narrowed, held in source-visible memory or computed lane by lane. A scalar target is a separate, explicit profile, and a build for another machine chooses its profile instead of taking the host machine's features.

Today: the x86 and AArch64 profiles keep each rack in one physical register, and the object verifier checks it. The wasm-simd128 profile keeps each rack as one v128 value and leaves physical register allocation to the WebAssembly runtime.

Scalars are marked

Plural types, such as f32s, are racks. Angle brackets mark a uniform scalar where it is declared and where it is used, so every broadcast from a scalar into a rack is visible, and a kernel full of brackets prompts the question of whether those values should vary by lane. The type checker rejects an implicit conversion between a scalar and a rack. lanes, the lane count, and @, the lane index, are reserved for later.

Lane choices are masks

Tines describe lane masks, locally or as reusable typed global predicates. A through block computes under one, and without else its result is defined only under that mask. A sweep picks each lane's result in priority order, and the checker proves that its arms cover every lane. A vector backend keeps these choices as vector predication, with masked instructions or benign operands, and never turns them into branches for each lane. An inactive lane raises no floating-point exception, touches no memory and has no other effect.

Today: rakes compile on x86-sse2, x86-avx2, x86-avx512, aarch64-neon and wasm-simd128.

Fused regions are pure data flow

Consecutive | name <| expression bindings describe one pure graph of vector operations, with no calls, spills or reloads. Its identifiers are aliases that impose no evaluation order, storage, instruction or rounding boundary, so the backend may rewrite the whole region to the cheapest graph for the profile, and different profiles may round its intermediates differently. fma(a, b, c) means one rounding, for programs that depend on it.

Today: the compiler substitutes identifiers and forms fused multiply-adds on x86-avx2, x86-avx512 and aarch64-neon, and fused bindings describes the planned rewrites.

A complete vector vocabulary

Rake's vector operations will cover:

Each operation has a type rule, a rule for inactive lanes, a set of supporting profiles and a verified lowering. A profile without a native form of an operation rejects it before generating code.

Today: primitives, operations, and targets lists what each profile compiles.

Data layout is explicit

A pack describes one record. A stack collects those records in structure-of-arrays storage, and a stack run applies calculations to its whole columns while the compiler visits them a rack at a time. Layout conversions and memory operations are written in the source or set by a documented calling convention. A stack run's tail never reads or writes past the count.

Stacks move into the run returning them, so every update is in place and a copy happens only where the source writes copy. A run selects records by masking, with where, or by compaction, with compact, and the kernel report shows the selection method on each target.

Today: stack runs compile on SSE2, AVX2, AVX-512, NEON and wasm-simd128, and general runs over views on wasm-simd128. Packs and runs defines their boundaries and checked tails.

Scalar code stays scalar

A program's scalar work, its records, state, control flow and calls to C, is slow code, marked slow. A run enters it explicitly with slow { ... } and resumes vector mode at }. Slow code can't hold a rack, and reaches vector work only through calls with marked uniform arguments.

Today: whole programs compile on wasm-simd128, as the slow tier describes. The development compiler also builds native objects in which slow code calls register kernels, passing and receiving uniform f32, bool, i32 and u32 values.

Compilation is predictable

On physical CPU profiles the compiler owns rack instruction selection, register allocation, instruction sequencing and assembly, and the assembler only encodes what it's given. On WebAssembly, Rake selects the virtual machine instructions and the shape of its v128 values, while the runtime chooses physical registers. Every compiler stage can be inspected. When the compiler can't prove a profile's properties for a program, it fails with the source construct, profile and obligation involved. A benchmark result is reported with its source, profile, compiler version, command, input size and baseline.

Today: on SSE2, AVX2, AVX-512 and NEON the compiler selects instructions, allocates registers without spills and writes the assembly, and --emit-native-ir and --emit-asm show its work. On wasm-simd128, --emit-c writes each selected instruction as one intrinsic, clang encodes them, and --verify-native checks the result.

GPU work keeps its parallel structure

A GPU rack will map its lanes onto a specified warp, wave or subgroup, so per-invocation arithmetic is the parallel work, and a hidden loop over the rack inside one invocation is forbidden. Uniform values, lane masks, collective participation, memory address patterns and synchronisation scopes will be explicit parts of the profile, and the compiler will verify the declared lane mapping and permitted control flow.

On NVIDIA, the same rule covers the tensor cores. A matrix product runs on tensor-core instructions or compilation fails, and slow { ... } marks one kept on the CUDA cores. A declared number format runs as itself, so a format the profile has no instruction for is rejected, never widened, and declaring a wider format than the values need, where a narrower one runs at the same speed, gets a warning. A fused region compiles to one kernel whose intermediate values stay in registers and shared memory.

The initial NVIDIA PTX route leaves final register allocation and instruction scheduling to NVIDIA, then verifies the exact cubin. Register count, memory address structure and the absence of forbidden lowering can be checked, whereas occupancy, cache behaviour and elapsed time need measurement. Each resource policy has an explicit profile, and the compiler refuses a program that would need a different one.

Today: GPU support is a design. GPU profiles defines the first proposed NVIDIA profile, its artifact verifier and runtime boundary.