@Vector abuse?

I’m running up against two changes in Zig 0.17.0 that I didn’t see in the release notes:

  • @Vector types are no longer allowed in extern structs/unions or as parameters to functions that have callconv(.winapi), due to not having a defined memory layout.
  • Pointers to @Vector fields are now aligned in such a way that you can’t pass them to a function that accepts a *f32 pointer.

These are both very hard for me to change in my project so now I’m looking for excuses to refactor my vector math to use arrays of floats rather than @Vector types. So I’m curious if I am abusing @Vectors in my current implementation of vector math. There’s no need to read the whole file, the first Vector2Type function is enough to understand what I am doing. I originally did this thinking that there would be a performance advantage to using @Vector when doing vector calculations since all elements of the vector can be calculated at the same time, but I’m not convinced that there would be a big difference anymore. If I end up refactoring I can obviously compare the performance, but since that’s a somewhat time consuming task I wanted to get some more opinions on this before I go down that route.

There’s a reasonable chance your previous code with @Vector was already suboptimal compared to an array. When you use @Vector, the compiler does not do any optimizations. It fully trusts you to write optimal code. The problem is that a lot of naive implementations of vector operations are suboptimal compared to what the compiler can do. When you write a loop over an array, the compiler detects the naive implementation and improves it. When you use @Vector, that doesn’t happen.

I had a similiar issue, but after learning about vector - array coercion I think it makes more sense the way they have it than what I did originally, here’s a migration guide, just store [N]T instead of @Vector(N, T) and use coercion when you want to do anything with SIMD.

// Before
fn foo(vec: [*]@Vector(8, u32)) void {
     const v: @Vector(8, u32) = vec[42];
     doSomething(v);
}

// After
fn foo(vec: [*][8]u32) void {
     const v: @Vector(8, u32) = vec[42];
     doSomething(v);
}

I checked the assembly in my project, the only difference is that LLVM will use
vmovdqu instead of vmovdqa, which is basically the same command but with different layout assumptions. AFAIK that’s the same performance.
If you want to force the vmovdqa then you can just use [*]align(vectorAlignment)[8]u32
Compiler explorer example

Edit: TLDR - store your data in arrays instead of vectors and pretend they are vectors using coercion.

This is wrong. Most optimizations act differently on an array vs. a vector (I haven’t looked into this that deep, but I could imagine that e.g. sroa does not work on vectors), but some optimizations work with vectors (e.g. turning vec * splat(2) into vec << splat(1)).

I may be retreading old ground here.

It does feel to me like this is a very hairy part of the language. I personally don’t think declaring something as a vector should tell the compiler which instructions it should use to process the data. It’s saying this data structure should be interpreted as a mathematical vector, just as @Complex() would (if we had one) say treat these values as a complex number or @Matrix would say treat it as a matrix. It’s then up to the compiler to decide how to do the work efficiently. SIMD or not.

I think there’s a proposal to separate SIMD semantics from vector semantics, but I can’t put my finger on it now. I think it’s desperately needed though.

There are many areas for which zig is used, that us the word vector for something (simd vectors, vectors in the mathematical sence, people coming from c++ might think vectors are arraylists). Currently in zig a @Vector is lowered by the llvm backend to the llvm vector type which is very much simd related.