impl riffle for llvm<=20 fallback on interleave

This commit is contained in:
kalsprite
2026-08-15 23:53:54 -07:00
parent 5f0251fec2
commit d52aff5c4b
2 changed files with 44 additions and 5 deletions

View File

@@ -1563,12 +1563,45 @@ gb_internal lbValue lb_build_builtin_simd_proc(lbProcedure *p, Ast *expr, TypeAn
args[i] = arg.value;
}
gbString name = gb_string_make(heap_allocator(), "");
name = gb_string_append_fmt(name, "llvm.vector.interleave%d", n);
defer (gb_string_free(name));
// `llvm.vector.interleave<N>` for N > 2 is only usable on recent LLVM: it does not
// exist at all on 20, and where it does exist a backend may still have no pattern
// for it (x86 on 20 fails to select). Measured working on 22, so take it there.
#if LLVM_VERSION_MAJOR >= 22
if (n > 2) {
gbString name = gb_string_make(heap_allocator(), "");
name = gb_string_append_fmt(name, "llvm.vector.interleave%d", n);
defer (gb_string_free(name));
LLVMTypeRef types[1] = {lb_type(m, tv.type)};
res.value = lb_call_intrinsic(p, name, args, n, types, gb_count_of(types));
LLVMTypeRef types[1] = {lb_type(m, tv.type)};
res.value = lb_call_intrinsic(p, name, args, n, types, gb_count_of(types));
return res;
}
#endif
// otherwise build the same permutation from interleave2, which every supported LLVM
// has. The operand count is a power of two (the checker requires the lane count to
// be one), and pairing each operand with the one a half-step away is what makes the
// orders agree: interleave4(a,b,c,d) == interleave2(interleave2(a,c), interleave2(b,d)).
LLVMTypeRef elem_type = LLVMGetElementType(LLVMTypeOf(args[0]));
unsigned width = LLVMGetVectorSize(LLVMTypeOf(args[0]));
LLVMValueRef *cur = args;
for (int count = n; count > 1; /**/) {
int half = count/2;
width *= 2;
LLVMTypeRef types[1] = {LLVMVectorType(elem_type, width)};
LLVMValueRef *next = temporary_alloc_array<LLVMValueRef>(half);
for (int i = 0; i < half; i++) {
LLVMValueRef pair[2] = {cur[i], cur[i+half]};
next[i] = lb_call_intrinsic(p, "llvm.vector.interleave2", pair, 2, types, gb_count_of(types));
}
cur = next;
count = half;
}
res.value = cur[0];
return res;
}

View File

@@ -9,6 +9,9 @@ import "core:testing"
// from a test -- it is a compile error -- so what is pinned here is the other side: the widths
// that must keep working, and the values they carry.
// More than two operands take one of two lowerings, `llvm.vector.interleave<N>` on a LLVM 21+,
// a riffle of `interleave2` elsewhere. The lane order and width are both asserted.
@(test)
simd_interleave_power_of_two_widths :: proc(t: ^testing.T) {
a: #simd[4]i32 = {1, 2, 3, 4}
@@ -29,6 +32,9 @@ simd_interleave_power_of_two_widths :: proc(t: ^testing.T) {
testing.expect_value(t, simd_extract_i32(four, 1), 5)
testing.expect_value(t, simd_extract_i32(four, 2), 9)
testing.expect_value(t, simd_extract_i32(four, 3), 13)
testing.expect_value(t, simd_extract_i32(four, 4), 2)
testing.expect_value(t, simd_extract_i32(four, 7), 14)
testing.expect_value(t, simd_extract_i32(four, 15), 16)
// a single argument is a power of two count as well, and must not be caught by the guard
one := intrinsics.simd_interleave(a)