henad-compute 0.3.0

CPU and GPU engines that run Henad models, with their sim threads and parallel primitives.
Documentation
// One level of a multi-level exclusive prefix sum. Each workgroup scans WORKGROUP elements and
// publishes its total to `block_sums`, which the level above scans in turn.

#import henad::dispatch::WORKGROUP

struct ScanParams {
    n: u32,
    groups_x: u32,
    num_blocks: u32,
    _pad: u32,
}

@group(0) @binding(0) var<storage, read> input: array<u32>;
@group(0) @binding(1) var<storage, read_write> output: array<u32>;
@group(0) @binding(2) var<storage, read_write> block_sums: array<u32>;
@group(0) @binding(3) var<uniform> params: ScanParams;

var<workgroup> partials: array<u32, 256>;

@compute
@workgroup_size(256)
fn main(
    @builtin(local_invocation_id) lid: vec3<u32>,
    @builtin(workgroup_id) wid: vec3<u32>,
) {
    let block = wid.y * params.groups_x + wid.x;
    let t = lid.x;
    let i = block * WORKGROUP + t;

    // A lane past the end uses zero rather than returning early, so every lane hits the barriers.
    var value: u32 = 0u;
    if (i < params.n) {
        value = input[i];
    }
    partials[t] = value;
    workgroupBarrier();

    // Hillis-Steele. Both barriers sit outside the branch, to keep control flow uniform.
    for (var offset: u32 = 1u; offset < WORKGROUP; offset = offset << 1u) {
        var addend: u32 = 0u;
        if (t >= offset) {
            addend = partials[t - offset];
        }
        workgroupBarrier();
        partials[t] = partials[t] + addend;
        workgroupBarrier();
    }

    let inclusive = partials[t];
    if (i < params.n) {
        output[i] = inclusive - value;
    }
    if (t == WORKGROUP - 1u && block < params.num_blocks) {
        block_sums[block] = inclusive;
    }
}