diff --git a/src/llvm_backend_proc.cpp b/src/llvm_backend_proc.cpp index be1b3724f..011219c08 100644 --- a/src/llvm_backend_proc.cpp +++ b/src/llvm_backend_proc.cpp @@ -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` 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(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; } diff --git a/tests/internal/test_simd_lane_counts.odin b/tests/internal/test_simd_lane_counts.odin index 5f5ff6e49..c6ad57051 100644 --- a/tests/internal/test_simd_lane_counts.odin +++ b/tests/internal/test_simd_lane_counts.odin @@ -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` 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)