@Vector, SIMD, and SPIR-V

There’s something that’s been bugging me about @Vector.

It’s unfortunate that both the math vector and the simd vector have reasonable claim on the name ‘vector’. I hope zig can serve to clarify that distinction.

In my code, I do a lot of computations with math vectors and simd vectors. There is a very fundamental construct in my repo, as follows (simplified slightly for this post):

pub const Width = enum(u8) {
    scalar = 0,
    _,

    fn lanes(comptime width: Width) comptime_int {
        return @max(1, @backingInt(width));
    }
};

pub fn Vector(T: type, n: usize, width: Width) type {
    return extern struct {
        values: [n]Stored align(@alignOf(Lane)),

        const Stored = if (width == .scalar) T else [width.lanes()]T;
        const Lane = if (width == .scalar) T else @Vector(width.lanes(), T);

        // ...a bunch of decls and methods, eg
        pub fn x(vector: @This()) Lane {
            return vector.values[0];
        }
    };
}

There are then generic functions like add() and mul() that dispatch by type based on a comptime function map. It works really well and is easy for me to understand. There’s no real need for operator overloading (it’d help with readability, but the ergonomics are fine as-is) for me.

The thing to note here is that there are two dimensions of data: the [n] which is the mathematical dimension and the [width] which is the simd width. These axes are orthogonal. I would rarely want to load (x,y,z,z) as a math vector into a simd register, for example. Instead I want to load (x1, x2, x3, x4) into a simd register. For some evidence, see this (very good presentation!) set of slides: https://deplinenoise.wordpress.com/wp-content/uploads/2015/03/gdc2015_afredriksson_simd.pdf, slide 37-ish, but the entire thing is worth reading.

Zig currently uses the K in @Vector(K, f32) to mean the simd dimension in “normal” cpu code. But, when you compile to spir-v, suddenly that same K is taken to be the math dimension (zig lowers [3]f32 to OpTypeArray and only @Vector to OpTypeVector). Then the gpu driver black box may group multiple shader invocations into a warp (SIMT) if I understand correctly.

Most of these ideas were raised in https://codeberg.org/ziglang/zig/issues/35376, but they weren’t ever responded to there by the zig core team (except for one now-deleted comment iirc). I think that issue is solving a valuable problem (sharing code and types between cpu and gpu code). But it is building on the pre-existing inconsistent dimension-usage of @Vector that people there tried to verbalise, as I am trying here.

What do you think? I would like to see the two vectors disentangled: one construct that uses cpu-simd where available, and one construct for vectors that is [3]f32 on the cpu and OpTypeVector in spirv.