zig icon indicating copy to clipboard operation
zig copied to clipboard

A `@swizzle` built-in in addition to the current `@shuffle`

Open expikr opened this issue 2 years ago • 7 comments
trafficstars

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?

expikr avatar Nov 05 '23 13:11 expikr

Might fit in std.simd

squeek502 avatar Nov 05 '23 21:11 squeek502

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)`

Snektron avatar Nov 06 '23 20:11 Snektron

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?

expikr avatar Nov 07 '23 14:11 expikr

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.

tecanec avatar Nov 12 '23 10:11 tecanec

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.

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.

expikr avatar Nov 14 '23 23:11 expikr

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.

InKryption avatar Nov 15 '23 13:11 InKryption

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.

tecanec avatar Dec 15 '23 08:12 tecanec

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);

expikr avatar Mar 29 '24 05:03 expikr