Rake and other languages

Rake's closest established relative is Intel's ISPC, because both let a programmer express parallel lanes directly, whereas Bend 2 expresses parallel tasks.

Ways to write SIMD code

ApproachLeft to the programmer
A C or Rust scalar loopconfirming that autovectorisation happened at the intended width, without helper calls, and still happens after the next change to the source or compiler
SIMD libraries for C++ and Rustportable vector types remove some target-specific syntax, but compiling doesn't require one register per value, no spills, no calls and no scalar replacement
C or Rust intrinsicswriting and dispatching an implementation for each width, while the compiler still allocates registers and may spill vectors
A Rake profilechoosing the profile. The compiler proves the profile's rules or rejects the program

The compiler repository's demo/soa-proof harness writes output_x[i] = position_x[i] + velocity_x[i] * dt over 400 particles in Rake, strict C and Rust, and then takes the sine of every lane. None of the builds has a vector sine, so under the harness's strict flags the C and Rust builds call the scalar sinf once per value, while the Rake build stops with call to 'sin' is not supported by native scratch lowering.

Rake and ISPC

ISPC is a C-like language for writing one program across a group of CPU vector lanes. Like Rake, its source describes a group of lanes instead of leaving an optimiser to find SIMD in a scalar loop, and the target chooses the lane count. Both languages separate values shared by the group from values owned by lanes, run divergent paths under an execution mask, give columnar layouts their own syntax and types, and aim at CPUs and GPUs.

The same update of every particle in a structure of arrays, in ISPC:

export void advance_x(
    uniform float position_x[],
    uniform float velocity_x[],
    uniform float output_x[],
    uniform int count,
    uniform float dt) {
  foreach (i = 0 ... count)
    output_x[i] = position_x[i]
                + velocity_x[i] * dt;
}

and in Rake:

pack Particles {
  f32: position_x, velocity_x;
}

run advance_x(particles: stack Particles, <dt: f32>) -> stack Particles:
  particles with { position_x: particles.position_x + particles.velocity_x * <dt> }

ISPC keeps the familiar imperative loop, index, assignment and pointers, and values in the gang vary by lane unless marked uniform. Rake's version is a pure expression with no loop or index. A run applies it to whole columns of a stack, the compiler splits them into racks, and the shared values are the ones marked, with angle brackets.

ISPCRake
Parallel unita gang of program instances, which may be wider than one CPU vectora rack: one physical CPU vector register or one WebAssembly v128
Columnssoa<n> makes an n-wide structure of arraysa pack describes a record, and a stack holds its columns
Divergenceif, loops and calls become uniform or varying from their operandstines define masks, through blocks compute under them, and a sweep picks each lane's result
Back endLLVM selects instructions and allocates registers, for CPUs and Intel Xe GPUsRake's own selection and allocation for SSE2, AVX2, AVX-512F and NEON, and C intrinsics for WebAssembly
Machine rulesgang semantics are preservedthe compiler also rejects split racks, scalar lane loops, spills and helper calls wherever the profile forbids them
Maturityan established production compiler, used in renderers such as OSPRay and Embreenative stack runs compile on four CPU profiles and WebAssembly, while native general runs and GPU targets are in progress

ISPC's performance guide and its Xe guide explain the cost of irregular accesses that become gathers, scatters or serialised loads, and of register pressure that causes spills. A Rake profile turns those costs into rules, and rejects a program that breaks one with a diagnostic that identifies the operation. The ISPC guide describes the language.

GPU execution: Rake, ISPC and Bend

On an NVIDIA GPU, a warp groups 32 threads that each hold one lane's values and execute each operation together, although divergent paths can leave some lanes inactive. The hardware schedules warps, while the program decides the work on their lanes, as NVIDIA's SIMT description explains.

Rake GPU designISPCBend 2
Parallel workrack lanes mapped onto a specified warp or subgroupSPMD program instances in a gangindependent parallel calls, arranged as binary fork–join tasks
LoweringRake checks the lane mapping, and NVIDIA allocates and schedulesLLVM and the Intel GPU compiler lower gangs for Xethe runtime distributes tasks across CPU or GPU workers
Guaranteerejection of hidden serial lane work and forbidden spills or helpersdefined gang, mask and uniform/varying semanticsnone for balance, which stays the programmer's job
Still needs measurementlane activity, occupancy, memory transactions and elapsed timebranch coherence, spills, memory costs and elapsed timetask balance, divergence, scheduling costs and elapsed time
Availabilitydesign onlyCPU and Intel Xe GPU targetsCPU and GPU parallel-call paths

Bend 2 asks the programmer for independent calls that take roughly the same time. A binary fork–join scheduler distributes that work once, and a ! after a call sends it and its nested calls to the GPU. This comparison uses the Bend 2 guide at revision 75cc360, and the ISPC guides at documentation revision c22f07f.

Other languages for parallel kernels

LanguageProvidesDifference from Rake
Halideimage and stencil pipelines in C++, with the algorithm separate from its schedulea programmer or autoscheduler chooses widths, tiling and placement, and nothing checks them as register rules
Futharka purely functional array language for CUDA, OpenCL or multithreaded CPUsbulk and nested array parallelism, where Rake keeps each rack one profile-sized vector
CUDA, HIP, shader languages and SlangGPU programming over threads, warps, waves and subgroupsRake's proposed GPU profile would also prove its lowering and resource rules or reject the artifact
Taichiparallel kernels for CPUs and GPUs, embedded in Pythonportable parallel iteration and data layout, with no proof of a fixed SIMD representation
Mojoa systems language with parameterised SIMD values and CPU and GPU facilitiesthe width is a SIMD type parameter, whereas a Rake profile fixes it and rejects a rack too wide for one register
Research languagesImpala and AnyDSL, Lift and Shine, Accelerate and Dexstaged compilation, rewrite systems and functional data parallelism