zig
zig copied to clipboard
A `@swizzle` built-in in addition to the current `@shuffle`
I've been wrapping the @shuffle function a lot with the following generic in order to achieve something similar to swizzling syntax in shader languages:
inline fn swiz(vec: anytype, mask: anytype) [mask.len]@TypeOf(vec[0]) {
const T = @TypeOf(vec[0]);
const v: @Vector(vec.len,T) = vec;
const m: @Vector(mask.len,i32) = mask;
return @shuffle(T,v,undefined,m);
}
used like so:
test {
const print = @import("std").debug.print;
const cyan = [_]u8{0,255,255};
const cyan_shade = swiz(cyan,.{0,1,2,2});
print("{any}\n",.{cyan_shade});
const outOfOrder = [_]u8{'o','e','l','w','h','r','d'};
const inOrder = swiz(outOfOrder,.{4,1,2,2,0,3,0,5,2,6});
print("{s}\n",.{@as([10]u8,inOrder)});
}
Personally this boilerplate is good enough for my usage, but I'm wondering if other people might also find it sufficiently useful that it would be beneficial to be adopted into a buillt-in as well?
Might fit in std.simd
I like this interface even more: https://github.com/ziglibs/zlm/blob/master/src/zlm-generic.zig#L139
/// Examples:
/// - `vec4(1,2,3,4).swizzle("wzyx") == vec4(4, 3, 2, 1)`
/// - `vec4(1,2,3,4).swizzle("xyx") == vec3(1,2,1)`
/// - `vec2(1,2).swizzle("xyxy") == vec4(1,2,1,2)`
/// - `vec2(3,4).swizzle("xy01") == vec4(3, 4, 0, 1)`
I like this interface even more: https://github.com/ziglibs/zlm/blob/master/src/zlm-generic.zig#L139
/// Examples: /// - `vec4(1,2,3,4).swizzle("wzyx") == vec4(4, 3, 2, 1)` /// - `vec4(1,2,3,4).swizzle("xyx") == vec3(1,2,1)` /// - `vec2(1,2).swizzle("xyxy") == vec4(1,2,1,2)` /// - `vec2(3,4).swizzle("xy01") == vec4(3, 4, 0, 1)`
what about for longer vectors?
I'd suggest taking this a step further, actually: Replace @shuffle with @swizzle entirely and add a function to std.simd that the compiler can optimize into a shuffle instruction.
In my experience, swizzling is a much more common operation than shuffling, so I think it makes more sense to have easy access to the former. I'm not sure whether I use dual input shuffles significantly more often than other std.simd functions, but I don't think I'd mind if they were moved there.
Some of the functions that I personally wrote for std.simd were actually designed to compile down to a single instruction (at least on SSE/AVX) for some use cases. I'm not sure if they still do that on stage-2, but if they do, I don't think that throwing shuffles into that bunch would be too much of a stretch.
I'd suggest taking this a step further, actually: Replace
@shufflewith@swizzleentirely and add a function tostd.simdthat the compiler can optimize into a shuffle instruction.In my experience, swizzling is a much more common operation than shuffling, so I think it makes more sense to have easy access to the former. I'm not sure whether I use dual input shuffles significantly more often than other
std.simdfunctions, but I don't think I'd mind if they were moved there.Some of the functions that I personally wrote for
std.simdwere actually designed to compile down to a single instruction (at least on SSE/AVX) for some use cases. I'm not sure if they still do that on stage-2, but if they do, I don't think that throwing shuffles into that bunch would be too much of a stretch.
Yeah tbh the @shuffle syntax is rather quite a mouthful to use, especially for being a built-in which is meant to be fast, concise access to commonly used functions; proportionally, replacing @ with std.simd. statistically probably wouldn't move the needle on the percentage of extra characters you'd typically type when using that function.
Reducing the capacity of a SIMD builtin seems explicitly like a step backwards. You can implement swizzling in terms of shuffling without losing performance, but the same is not necessarily true the other way around. The purpose of bultins is not for "concise access", their purpose is as language intrinsics for performing operations that only the compiler can reasonably do without dropping to inline assembly, or to help the compiler optimize code by precisely expressing your intent in its usage.
Reducing the capacity of a SIMD builtin seems explicitly like a step backwards. You can implement swizzling in terms of shuffling without losing performance, but the same is not necessarily true the other way around. The purpose of bultins is not for "concise access", their purpose is as language intrinsics for performing operations that only the compiler can reasonably do without dropping to inline assembly, or to help the compiler optimize code by precisely expressing your intent in its usage.
I'll present two counterarguments:
First, the @shuffle builtin is unable to select the indices in runtime. This is a feature of the swizzle operations on many instruction sets, and it has many applications, such as for small LUTs. However, even if an instruction set supports shuffles with runtime indices, it would be troublesome for a Zig builtin to support it, since different instruction sets may expect the indices to be encoded differently. Swizzles do not have this problem. The "reduced capacity" problem is therefore not unique to the @swizzle route.
Second, I've made this point already, but optimizing compilers are actually really good at this kind of conversion. In fact, many instruction sets, especially CISC, have several instructions and features that can't reasonably be produced by a non-optimizing compiler, such as x86's SIB byte. Trying to create a 1:1 correspondence between source and machine code at the operations level is a lost cause, and one that would only make the programmers' lives difficult, at that. And, again, this is where compiler optimizations excel the most, so adding that abstraction really isn't causing that much harm.
A new idea is to introduce "Builtin methods" to specific builtin types like @Vector or @Complex, to express one-off operations specific to the type, proposed in https://github.com/ziglang/zig/issues/17274#issuecomment-2026541337
How it might look:
var c = @Complex(f32){0,0};
c.@re += 7;
c.@im += 8;
var cs = @Vector(@Complex(f32),3){
.{1,2},
.{3,4},
.{5,6},
};
var ds = cs.@scale(c);
cs *= ds;
const es = cs.@swiz(1,1,2);