If you can guarantee that your data is a multiple of your CPUs vector length, then yes you’re right. No need for a scalar tail. Routines that take arbitrary sized data need a way to cope with a tail.
On some machines vector loads and stores are fastest when the address is aligned with the vector size, not just the element size. In that case you may end up head-body-tail structure, where the head uses mask to operate only on the parts of the first vector it’s allowed to work on.
great article as usual. I had some issue with understanding step 4 though. The example looks like 8 values that can fit into a u8. Are the codepoints actually u32 but your only interested in the ones that are smaller than 0xF? Or ist the example simplified so that one can comprehend it better? I’m asking becaus the calues could fot into a 64 bit register as well instead of 256.
Pretty sure @Vector lowers to a regular scalar loop if the target doesn’t support SIMD,
which is the whole point of “portable” SIMD I think. Not having to write multiple versions of your code depending on the target, that’s why we use higher level languages such as Zig in the first place
That’s why I was curious if the compiler optimizes the G.A. Maybe by using the simplest scalar assembly, I gave the compiler free reign to find something even faster than I could have described with higher level abstractions. Especially because the address and size are hard-coded and nicely aligned numbers.
Anyway, I’m far from needing anything portable or abstract at this point, 95% of my code is SoC specific MMIO But maybe once I get into SVG rendering it might be useful. eg. I wonder if it could be leveraged for a faster recursive flood fill.