From f7b734c82b3ca99bf8b7eea48126dacd082d28aa Mon Sep 17 00:00:00 2001 From: kalsprite Date: Sun, 16 Aug 2026 00:24:09 -0700 Subject: [PATCH] deinterleave -> shuffle --- src/llvm_backend_proc.cpp | 25 ++++++++++++---- tests/internal/test_simd_lane_counts.odin | 35 +++++++++++++++++++++++ 2 files changed, 55 insertions(+), 5 deletions(-) diff --git a/src/llvm_backend_proc.cpp b/src/llvm_backend_proc.cpp index 2ca2594c5..6da9314c5 100644 --- a/src/llvm_backend_proc.cpp +++ b/src/llvm_backend_proc.cpp @@ -1601,7 +1601,6 @@ gb_internal lbValue lb_build_builtin_simd_proc(lbProcedure *p, Ast *expr, TypeAn case BuiltinProc_simd_deinterleave: { lbValue arg0 = lb_build_expr(p, ce->args[0]); - LLVMTypeRef types[1] = {lb_type(m, arg0.type)}; GB_ASSERT(ce->args[1]->tav.value.kind == ExactValue_Integer); int n = cast(int)exact_value_to_i64(ce->args[1]->tav.value); @@ -1611,11 +1610,27 @@ gb_internal lbValue lb_build_builtin_simd_proc(lbProcedure *p, Ast *expr, TypeAn return res; } - gbString name = gb_string_make(heap_allocator(), ""); - name = gb_string_append_fmt(name, "llvm.vector.deinterleave%d", n); - defer (gb_string_free(name)); + // `llvm.vector.deinterleave` for N > 2 cannot be selected or legalized on most + // targets, only arm64 takes it. The split is done with shuffles, same as + // `simd_interleave`. Output `j` is the input strided by N starting at lane `j`. + LLVMTypeRef llvm_u32 = lb_type(m, t_u32); + LLVMTypeRef vector_type = LLVMTypeOf(arg0.value); + LLVMValueRef undef = LLVMGetUndef(vector_type); - res.value = lb_call_intrinsic(p, name, &arg0.value, 1, types, gb_count_of(types)); + unsigned width = LLVMGetVectorSize(vector_type); + unsigned part = width/cast(unsigned)n; + + LLVMValueRef agg = LLVMGetUndef(lb_type(m, tv.type)); + LLVMValueRef *mask = temporary_alloc_array(part); + for (int j = 0; j < n; j++) { + for (unsigned i = 0; i < part; i++) { + mask[i] = LLVMConstInt(llvm_u32, i*cast(unsigned)n + cast(unsigned)j, false); + } + LLVMValueRef lanes = LLVMBuildShuffleVector(p->builder, arg0.value, undef, LLVMConstVector(mask, part), ""); + agg = LLVMBuildInsertValue(p->builder, agg, lanes, cast(unsigned)j, ""); + } + + res.value = agg; return res; } } diff --git a/tests/internal/test_simd_lane_counts.odin b/tests/internal/test_simd_lane_counts.odin index 2d6bea312..3931fc065 100644 --- a/tests/internal/test_simd_lane_counts.odin +++ b/tests/internal/test_simd_lane_counts.odin @@ -47,6 +47,41 @@ simd_interleave_power_of_two_widths :: proc(t: ^testing.T) { testing.expect_value(t, simd_extract_i32(y, 0), 5) } +// `simd_deinterleave` splits by stride, so output `j` is the input taken every N lanes from +// lane `j`. N > 2 lowers to shuffles for the same reason interleave does, only arm64 could +// select the intrinsic. The round trip is asserted, not just the widths. + +@(test) +simd_deinterleave_splits_by_stride :: proc(t: ^testing.T) { + v: #simd[16]i32 + for i in 0..<16 { + v = intrinsics.simd_replace(v, i, i32(i)) + } + + a2, b2 := intrinsics.simd_deinterleave(v, 2) + testing.expect_value(t, len(a2), 8) + testing.expect_value(t, simd_extract_i32(a2, 0), 0) + testing.expect_value(t, simd_extract_i32(a2, 1), 2) + testing.expect_value(t, simd_extract_i32(b2, 0), 1) + testing.expect_value(t, simd_extract_i32(b2, 7), 15) + + a4, b4, c4, d4 := intrinsics.simd_deinterleave(v, 4) + testing.expect_value(t, len(a4), 4) + testing.expect_value(t, simd_extract_i32(a4, 0), 0) + testing.expect_value(t, simd_extract_i32(b4, 0), 1) + testing.expect_value(t, simd_extract_i32(c4, 0), 2) + testing.expect_value(t, simd_extract_i32(d4, 0), 3) + testing.expect_value(t, simd_extract_i32(a4, 3), 12) + testing.expect_value(t, simd_extract_i32(d4, 3), 15) + + // interleave is the inverse, so the pair must round trip + r := intrinsics.simd_interleave(a4, b4, c4, d4) + testing.expect_value(t, len(r), 16) + testing.expect_value(t, simd_extract_i32(r, 0), 0) + testing.expect_value(t, simd_extract_i32(r, 7), 7) + testing.expect_value(t, simd_extract_i32(r, 15), 15) +} + simd_extract_i32 :: #force_inline proc(v: $V/#simd[$N]i32, $I: int) -> i32 { return intrinsics.simd_extract(v, I) }