deinterleave -> shuffle

This commit is contained in:
kalsprite
2026-08-16 00:24:09 -07:00
parent 95c4004239
commit f7b734c82b
2 changed files with 55 additions and 5 deletions

View File

@@ -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<N>` 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<LLVMValueRef>(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;
}
}

View File

@@ -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)
}