Sharing code between scalar and vector types

Your assembly outplut looks like it is compiled in debug mode.
You are right that your two functions compile to the same machine code. This gets even more obvious when compiling with ReleaseSafe:

example.multiplyAsScalar:
        push    rbp
        mov     rbp, rsp
        vmulsd  xmm0, xmm0, xmm1
        pop     rbp
        ret

multiplyAsScalar = example.multiplyAsScalar
multiplyAsVec = example.multiplyAsScalar

This is because SIMD vectors in zig are not really special. They do not force the compiler to use SIMD registers. They just keep data in a shape for which the optimizer probably is able to use simd instructions if it sees fit.

Under the right cicumstances scalar zig code can also generate simd instructions. The following will most likely result in vectorized machine code if f is just some scalar math.

fn apply2(f: fn (f64, f64) f64, x: @Vector(8, f64), y: @Vector(8, f64)) @Vector(8, f64) {
    var res: [8]f64 = undefined;
    const x_arr: [8]f64 = x;
    const y_arr: [8]f64 = y;
    for (&res, x_arr, y_arr) |*r, xe, ye| r.* = f(xe, ye);
    return res;
}

Here is a version of apply that works for functions that take arbitrarily many args:

Note that this produces exactly the same assembly if I replace all uses of @Vector(n, T) with [n]T.

4 Likes