mirror of
https://github.com/odin-lang/Odin.git
synced 2026-08-17 19:02:37 +00:00
Merge pull request #7344 from kalsprite/simd_builtin_diagnostics
simd: enforce the `#simd` construction rules and stop two crashes
This commit is contained in:
@@ -2899,7 +2899,7 @@ abs_diff :: #force_inline proc "contextless" (a, b: $T/#simd[$LANES]$E) -> T whe
|
||||
}
|
||||
|
||||
pairwise_add :: intrinsics.simd_pairwise_add
|
||||
pairwise_sub :: intrinsics.simd_pairwise_add
|
||||
pairwise_sub :: intrinsics.simd_pairwise_sub
|
||||
|
||||
interleave :: intrinsics.simd_interleave
|
||||
deinterleave :: intrinsics.simd_deinterleave
|
||||
@@ -949,6 +949,16 @@ gb_internal bool check_builtin_simd_operation(CheckerContext *c, Operand *operan
|
||||
// don't return
|
||||
}
|
||||
|
||||
if (id == BuiltinProc_simd_pairwise_add || id == BuiltinProc_simd_pairwise_sub) {
|
||||
i64 lanes = get_array_type_count(x.type);
|
||||
if (lanes % 2 != 0) {
|
||||
gbString xs = type_to_string(x.type);
|
||||
error(x.expr, "'%.*s' expected a #simd type with an even lane count, got '%s'", LIT(builtin_name), xs);
|
||||
gb_string_free(xs);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
operand->mode = Addressing_Value;
|
||||
operand->type = x.type;
|
||||
return true;
|
||||
@@ -1295,10 +1305,6 @@ gb_internal bool check_builtin_simd_operation(CheckerContext *c, Operand *operan
|
||||
if (!check_index_value(c, x.type, false, ce->args[1], max_count, &value)) {
|
||||
return false;
|
||||
}
|
||||
if (max_count < 0) {
|
||||
error(ce->args[1], "'%.*s' expected a constant integer index, got '%lld'", LIT(builtin_name), cast(long long)value);
|
||||
return false;
|
||||
}
|
||||
|
||||
operand->mode = Addressing_Value;
|
||||
operand->type = elem;
|
||||
@@ -1320,10 +1326,6 @@ gb_internal bool check_builtin_simd_operation(CheckerContext *c, Operand *operan
|
||||
if (!check_index_value(c, x.type, false, ce->args[1], max_count, &value)) {
|
||||
return false;
|
||||
}
|
||||
if (max_count < 0) {
|
||||
error(ce->args[1], "'%.*s' expected a constant integer index, got '%lld'", LIT(builtin_name), cast(long long)value);
|
||||
return false;
|
||||
}
|
||||
|
||||
Operand y = {};
|
||||
check_expr_with_type_hint(c, &y, ce->args[2], elem); if (y.mode == Addressing_Invalid) return false;
|
||||
@@ -1530,6 +1532,12 @@ gb_internal bool check_builtin_simd_operation(CheckerContext *c, Operand *operan
|
||||
return false;
|
||||
}
|
||||
|
||||
// the result is as wide as the index list, which may be twice the operand width
|
||||
if (arg_count > SIMD_ELEMENT_COUNT_MAX) {
|
||||
error(call, "'%.*s' constructs a #simd vector beyond the maximum element count of %d, got %lld", LIT(builtin_name), SIMD_ELEMENT_COUNT_MAX, cast(long long)arg_count);
|
||||
return false;
|
||||
}
|
||||
|
||||
operand->mode = Addressing_Value;
|
||||
operand->type = alloc_type_simd_vector(arg_count, elem);
|
||||
return true;
|
||||
@@ -1785,12 +1793,13 @@ gb_internal bool check_builtin_simd_operation(CheckerContext *c, Operand *operan
|
||||
}
|
||||
Operand offset = {};
|
||||
check_expr(c, &offset, ce->args[1]); if (offset.mode == Addressing_Invalid) return false;
|
||||
convert_to_typed(c, &offset, t_i64);
|
||||
// `base:intrinsics` declares the offset as `int` and does not mark it #any_int
|
||||
convert_to_typed(c, &offset, t_int);
|
||||
if (!is_type_integer(offset.type) || offset.mode != Addressing_Constant) {
|
||||
error(offset.expr, "'%.*s' expected a constant integer offset");
|
||||
error(offset.expr, "'%.*s' expected a constant integer offset", LIT(builtin_name));
|
||||
return false;
|
||||
}
|
||||
check_assignment(c, &offset, t_i64, builtin_name);
|
||||
check_assignment(c, &offset, t_int, builtin_name);
|
||||
|
||||
operand->type = x.type;
|
||||
operand->mode = Addressing_Value;
|
||||
@@ -1915,9 +1924,15 @@ gb_internal bool check_builtin_simd_operation(CheckerContext *c, Operand *operan
|
||||
i64 base_count = get_array_type_count(x.type);
|
||||
i64 count = base_count * cast(i64)ce->args.count;
|
||||
|
||||
i64 max_count = 64;
|
||||
i64 max_count = SIMD_ELEMENT_COUNT_MAX;
|
||||
if (count > max_count) {
|
||||
error(ce->proc, "'%.*s' exceeds the maximum #simd count %lld, got %lld", cast(long long)max_count, cast(long long)count);
|
||||
error(ce->proc, "'%.*s' exceeds the maximum #simd count %lld, got %lld", LIT(builtin_name), cast(long long)max_count, cast(long long)count);
|
||||
return false;
|
||||
}
|
||||
// the lane count is the operand width times the argument count, so it is a power
|
||||
// of two only when the argument count is
|
||||
if (!is_power_of_two(count)) {
|
||||
error(ce->proc, "'%.*s' must produce a power of two #simd count, got %lld", LIT(builtin_name), cast(long long)count);
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -3491,10 +3506,8 @@ gb_internal bool check_builtin_procedure(CheckerContext *c, Operand *operand, As
|
||||
arg_count++;
|
||||
}
|
||||
|
||||
if (false && arg_count > max_count) {
|
||||
error(call, "Too many 'swizzle' indices, %td > %td", arg_count, max_count);
|
||||
return false;
|
||||
} else if (arg_count < 2) {
|
||||
// No upper bound on the index count
|
||||
if (arg_count < 2) {
|
||||
error(call, "Not enough 'swizzle' indices, %td < 2", arg_count);
|
||||
return false;
|
||||
}
|
||||
@@ -3514,6 +3527,11 @@ gb_internal bool check_builtin_procedure(CheckerContext *c, Operand *operand, As
|
||||
return false;
|
||||
}
|
||||
|
||||
if (is_type_simd_vector(type) && arg_count > SIMD_ELEMENT_COUNT_MAX) {
|
||||
error(call, "'swizzle' constructs a #simd vector beyond the maximum element count of %d, got %lld", SIMD_ELEMENT_COUNT_MAX, cast(long long)arg_count);
|
||||
return false;
|
||||
}
|
||||
|
||||
operand->type = determine_swizzle_array_type(original_type, type_hint, arg_count);
|
||||
break;
|
||||
}
|
||||
|
||||
@@ -10690,7 +10690,25 @@ gb_internal ExprKind check_compound_literal(CheckerContext *c, Operand *o, Ast *
|
||||
if (count != nullptr) {
|
||||
if (count->kind == Ast_UnaryExpr &&
|
||||
count->UnaryExpr.op.kind == Token_Question) {
|
||||
type = alloc_type_array(check_type(c, type_expr->ArrayType.elem), -1);
|
||||
Type *elem = check_type(c, type_expr->ArrayType.elem);
|
||||
|
||||
bool is_simd_tag = false;
|
||||
if (type_expr->ArrayType.tag != nullptr) {
|
||||
GB_ASSERT(type_expr->ArrayType.tag->kind == Ast_BasicDirective);
|
||||
is_simd_tag = type_expr->ArrayType.tag->BasicDirective.name.string == "simd";
|
||||
}
|
||||
if (is_simd_tag) {
|
||||
if (!is_type_valid_vector_elem(elem) && !is_type_polymorphic(elem)) {
|
||||
gbString str = type_to_string(elem);
|
||||
error(type_expr->ArrayType.elem, "Invalid element type for #simd, expected an integer, float, boolean, or 'rawptr' with no specific endianness, got '%s'", str);
|
||||
gb_string_free(str);
|
||||
type = alloc_type_array(elem, -1);
|
||||
} else {
|
||||
type = alloc_type_simd_vector(-1, elem);
|
||||
}
|
||||
} else {
|
||||
type = alloc_type_array(elem, -1);
|
||||
}
|
||||
is_to_be_determined_array_count = true;
|
||||
}
|
||||
} else {
|
||||
@@ -10914,7 +10932,9 @@ gb_internal ExprKind check_compound_literal(CheckerContext *c, Operand *o, Ast *
|
||||
} else if (t->kind == Type_SimdVector) {
|
||||
elem_type = t->SimdVector.elem;
|
||||
context_name = str_lit("simd vector literal");
|
||||
max_type_count = t->SimdVector.count;
|
||||
if (!is_to_be_determined_array_count) {
|
||||
max_type_count = t->SimdVector.count;
|
||||
}
|
||||
} else if (t->kind == Type_Matrix) {
|
||||
elem_type = t->Matrix.elem;
|
||||
context_name = str_lit("matrix literal");
|
||||
@@ -11090,6 +11110,16 @@ gb_internal ExprKind check_compound_literal(CheckerContext *c, Operand *o, Ast *
|
||||
error(node, "Expected %lld values for this array literal, got %lld", cast(long long)t->Array.count, cast(long long)max);
|
||||
}
|
||||
}
|
||||
} else if (t->kind == Type_SimdVector) {
|
||||
// the length laws cannot be applied until the literal has supplied the count
|
||||
if (is_to_be_determined_array_count) {
|
||||
t->SimdVector.count = max;
|
||||
if (max < 1 || !is_power_of_two(max)) {
|
||||
error(node, "Invalid length for #simd, expected a power of two length, got '%lld'", cast(long long)max);
|
||||
} else if (max > SIMD_ELEMENT_COUNT_MAX) {
|
||||
error(node, "#simd support a maximum element count of %d, got %lld", SIMD_ELEMENT_COUNT_MAX, cast(long long)max);
|
||||
}
|
||||
}
|
||||
} else if (t->kind == Type_Struct) {
|
||||
GB_ASSERT(t->Struct.soa_kind == StructSoa_Fixed);
|
||||
if (is_to_be_determined_array_count) {
|
||||
|
||||
@@ -3538,9 +3538,12 @@ gb_internal void check_array_type_internal(CheckerContext *ctx, Ast *e, Type **t
|
||||
return;
|
||||
}
|
||||
|
||||
// Track user input and recovery value seperate, since both could be '0'
|
||||
bool count_recovered = false;
|
||||
if (count < 0) {
|
||||
error(at->count, "? can only be used in conjunction with compound literals");
|
||||
count = 0;
|
||||
count_recovered = true;
|
||||
}
|
||||
|
||||
|
||||
@@ -3562,7 +3565,12 @@ gb_internal void check_array_type_internal(CheckerContext *ctx, Ast *e, Type **t
|
||||
// Ignore
|
||||
} else if (count < 1 || !is_power_of_two(count)) {
|
||||
*type = alloc_type_array(elem, count, generic_type);
|
||||
if (ctx->disallow_polymorphic_return_types && count == 0) {
|
||||
if (count_recovered) {
|
||||
return;
|
||||
}
|
||||
// a polymorphic value used as the count is still unresolved while the
|
||||
// signature is checked and reads as 0; only a written count is constant
|
||||
if (ctx->disallow_polymorphic_return_types && o.mode != Addressing_Constant) {
|
||||
return;
|
||||
}
|
||||
error(at->count, "Invalid length for #simd, expected a power of two length, got '%lld'", cast(long long)count);
|
||||
|
||||
@@ -1563,19 +1563,44 @@ 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>` is not usable across the supported targets: N > 2 does
|
||||
// not exist before LLVM 22. Riscv & Darwin AMD64 has no `interleave2` either.
|
||||
// A shuffle is the one primitive every target has, and it expresses a two-way
|
||||
// interleave directly.
|
||||
//
|
||||
// The operand count is a power of two. The result is a riffle: pairing each operand
|
||||
// with the one a half-step away is what makes the order come out right, as
|
||||
// interleave4(a,b,c,d) == interleave2(interleave2(a,c), interleave2(b,d)).
|
||||
LLVMTypeRef llvm_u32 = lb_type(m, t_u32);
|
||||
|
||||
LLVMTypeRef types[1] = {lb_type(m, tv.type)};
|
||||
res.value = lb_call_intrinsic(p, name, args, n, types, gb_count_of(types));
|
||||
LLVMValueRef *cur = args;
|
||||
for (int count = n; count > 1; /**/) {
|
||||
int half = count/2;
|
||||
unsigned width = LLVMGetVectorSize(LLVMTypeOf(cur[0]));
|
||||
|
||||
LLVMValueRef *mask = temporary_alloc_array<LLVMValueRef>(2*width);
|
||||
for (unsigned i = 0; i < width; i++) {
|
||||
mask[2*i + 0] = LLVMConstInt(llvm_u32, i, false);
|
||||
mask[2*i + 1] = LLVMConstInt(llvm_u32, width+i, false);
|
||||
}
|
||||
LLVMValueRef mask_value = LLVMConstVector(mask, 2*width);
|
||||
|
||||
LLVMValueRef *next = temporary_alloc_array<LLVMValueRef>(half);
|
||||
for (int i = 0; i < half; i++) {
|
||||
next[i] = LLVMBuildShuffleVector(p->builder, cur[i], cur[i+half], mask_value, "");
|
||||
}
|
||||
|
||||
cur = next;
|
||||
count = half;
|
||||
}
|
||||
|
||||
res.value = cur[0];
|
||||
return res;
|
||||
}
|
||||
|
||||
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);
|
||||
@@ -1585,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;
|
||||
}
|
||||
}
|
||||
|
||||
246
tests/internal/test_simd_lane_counts.odin
Normal file
246
tests/internal/test_simd_lane_counts.odin
Normal file
@@ -0,0 +1,246 @@
|
||||
package test_internal
|
||||
|
||||
import "base:intrinsics"
|
||||
import "core:simd"
|
||||
import "core:testing"
|
||||
|
||||
// `simd_interleave` derives its lane count as operand width times argument count, so it is a
|
||||
// power of two only when the argument count is. The rejection of the rest cannot be asserted
|
||||
// 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 lower to a riffle of two-way shuffles rather than one intrinsic, so
|
||||
// the lane ORDER is asserted and not just the width.
|
||||
|
||||
@(test)
|
||||
simd_interleave_power_of_two_widths :: proc(t: ^testing.T) {
|
||||
a: #simd[4]i32 = {1, 2, 3, 4}
|
||||
b: #simd[4]i32 = {5, 6, 7, 8}
|
||||
c: #simd[4]i32 = {9, 10, 11, 12}
|
||||
d: #simd[4]i32 = {13, 14, 15, 16}
|
||||
|
||||
two := intrinsics.simd_interleave(a, b)
|
||||
testing.expect_value(t, len(two), 8)
|
||||
testing.expect_value(t, simd_extract_i32(two, 0), 1)
|
||||
testing.expect_value(t, simd_extract_i32(two, 1), 5)
|
||||
testing.expect_value(t, simd_extract_i32(two, 2), 2)
|
||||
testing.expect_value(t, simd_extract_i32(two, 3), 6)
|
||||
|
||||
four := intrinsics.simd_interleave(a, b, c, d)
|
||||
testing.expect_value(t, len(four), 16)
|
||||
testing.expect_value(t, simd_extract_i32(four, 0), 1)
|
||||
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)
|
||||
testing.expect_value(t, len(one), 4)
|
||||
|
||||
x, y := intrinsics.simd_deinterleave(two, 2)
|
||||
testing.expect_value(t, len(x), 4)
|
||||
testing.expect_value(t, len(y), 4)
|
||||
testing.expect_value(t, simd_extract_i32(x, 0), 1)
|
||||
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)
|
||||
}
|
||||
|
||||
// `simd_shuffle` and `swizzle` size their result from the index list, which can be twice the
|
||||
// operand width, so both can construct a `#simd` wider than the syntax accepts. What is pinned
|
||||
// here is the boundary that must keep working: exactly SIMD_ELEMENT_COUNT_MAX lanes, and a
|
||||
// `swizzle` over a plain array, which is not bound by the `#simd` limit at all.
|
||||
|
||||
@(test)
|
||||
simd_construction_at_the_element_count_max :: proc(t: ^testing.T) {
|
||||
a: #simd[32]i32 = 1
|
||||
at_max := intrinsics.simd_shuffle(a, a,
|
||||
0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15,
|
||||
16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31,
|
||||
32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47,
|
||||
48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63)
|
||||
testing.expect_value(t, len(at_max), 64)
|
||||
|
||||
b: #simd[8]i32 = {1, 2, 3, 4, 5, 6, 7, 8}
|
||||
under := intrinsics.simd_shuffle(b, b, 0, 1, 2, 3, 8, 9, 10, 11)
|
||||
testing.expect_value(t, len(under), 8)
|
||||
testing.expect_value(t, simd_extract_i32(under, 4), 1)
|
||||
|
||||
sw := swizzle(b, 7, 6, 5, 4, 3, 2, 1, 0)
|
||||
testing.expect_value(t, len(sw), 8)
|
||||
testing.expect_value(t, simd_extract_i32(sw, 0), 8)
|
||||
|
||||
// a plain array is not a #simd vector, so the element-count limit must not reach it
|
||||
arr: [96]i32
|
||||
arr[95] = 7
|
||||
asw := swizzle(arr, 95, 0, 1)
|
||||
testing.expect_value(t, len(asw), 3)
|
||||
testing.expect_value(t, asw[0], 7)
|
||||
|
||||
d, e: #simd[32]i32
|
||||
ilv := intrinsics.simd_interleave(d, e)
|
||||
testing.expect_value(t, len(ilv), 64)
|
||||
}
|
||||
|
||||
// A swizzle may repeat indices to produce a result wider than its operand. `core:crypto`
|
||||
// depends on it -- chacha20's simd256 path doubles a 4-lane state with eight indices -- so
|
||||
// there is deliberately no upper bound on the index count.
|
||||
|
||||
@(test)
|
||||
swizzle_may_widen_its_operand :: proc(t: ^testing.T) {
|
||||
v: [2]f32 = {1, 2}
|
||||
testing.expect_value(t, swizzle(v, 0, 0, 0, 0), [4]f32{1, 1, 1, 1})
|
||||
testing.expect_value(t, swizzle(v, 0, 1, 0, 1), [4]f32{1, 2, 1, 2})
|
||||
|
||||
a: [3]i32 = {7, 8, 9}
|
||||
testing.expect_value(t, swizzle(a, 2, 2, 2, 2, 2, 2), [6]i32{9, 9, 9, 9, 9, 9})
|
||||
|
||||
// the same shape on a #simd vector: four lanes widened to eight
|
||||
q: #simd[4]u32 = {1, 2, 3, 4}
|
||||
w := swizzle(q, 0, 1, 2, 3, 0, 1, 2, 3)
|
||||
testing.expect_value(t, len(w), 8)
|
||||
testing.expect_value(t, intrinsics.simd_extract(w, 4), u32(1))
|
||||
testing.expect_value(t, intrinsics.simd_extract(w, 7), u32(4))
|
||||
}
|
||||
|
||||
// A pairwise operation folds adjacent lanes within each operand, so it needs an even lane
|
||||
// count -- `base:intrinsics` declares `LANES % 2 == 0`. At one lane it has nothing to pair
|
||||
// with and silently switches to combining the two operands instead, which is why that width
|
||||
// is rejected rather than defined.
|
||||
|
||||
@(test)
|
||||
simd_pairwise_folds_adjacent_lanes :: proc(t: ^testing.T) {
|
||||
a: #simd[2]i32 = {10, 3}
|
||||
b: #simd[2]i32 = {20, 4}
|
||||
|
||||
add := intrinsics.simd_pairwise_add(a, b)
|
||||
testing.expect_value(t, intrinsics.simd_extract(add, 0), i32(13))
|
||||
testing.expect_value(t, intrinsics.simd_extract(add, 1), i32(24))
|
||||
|
||||
sub := intrinsics.simd_pairwise_sub(a, b)
|
||||
testing.expect_value(t, intrinsics.simd_extract(sub, 0), i32(7))
|
||||
testing.expect_value(t, intrinsics.simd_extract(sub, 1), i32(16))
|
||||
|
||||
c: #simd[4]f32 = {1, 2, 3, 4}
|
||||
d: #simd[4]f32 = {5, 6, 7, 8}
|
||||
f := intrinsics.simd_pairwise_add(c, d)
|
||||
testing.expect_value(t, intrinsics.simd_extract(f, 0), f32(3))
|
||||
testing.expect_value(t, intrinsics.simd_extract(f, 1), f32(7))
|
||||
|
||||
// the other six builtins sharing this arm take a single lane, and must stay unaffected
|
||||
o: #simd[1]i32 = 7
|
||||
p: #simd[1]i32 = 2
|
||||
testing.expect_value(t, intrinsics.simd_extract(intrinsics.simd_add(o, p), 0), i32(9))
|
||||
testing.expect_value(t, intrinsics.simd_extract(intrinsics.simd_max(o, p), 0), i32(7))
|
||||
}
|
||||
|
||||
// `#simd[?]T{...}` The inferred form must agree with the explicit one.
|
||||
|
||||
@(test)
|
||||
simd_inferred_length_from_a_compound_literal :: proc(t: ^testing.T) {
|
||||
a := #simd[?]i32{1, 2, 3, 4}
|
||||
testing.expect_value(t, len(a), 4)
|
||||
testing.expect_value(t, intrinsics.type_is_simd_vector(type_of(a)), true)
|
||||
testing.expect_value(t, typeid_of(type_of(a)), typeid_of(#simd[4]i32))
|
||||
testing.expect_value(t, intrinsics.simd_extract(a, 0), i32(1))
|
||||
testing.expect_value(t, intrinsics.simd_extract(a, 3), i32(4))
|
||||
|
||||
b := #simd[?]f32{1.5, 2.5}
|
||||
testing.expect_value(t, len(b), 2)
|
||||
testing.expect_value(t, typeid_of(type_of(b)), typeid_of(#simd[2]f32))
|
||||
testing.expect_value(t, intrinsics.simd_extract(b, 1), f32(2.5))
|
||||
|
||||
// the inferred vector must work with the intrinsics, which is the point of the tag
|
||||
testing.expect_value(t, intrinsics.simd_extract(intrinsics.simd_add(a, a), 3), i32(8))
|
||||
|
||||
// a plain `[?]` array is unaffected
|
||||
c := [?]i32{1, 2, 3}
|
||||
testing.expect_value(t, len(c), 3)
|
||||
testing.expect_value(t, intrinsics.type_is_array(type_of(c)), true)
|
||||
}
|
||||
|
||||
// `core:simd` aliased `pairwise_sub` to `simd_pairwise_add`, so it silently added
|
||||
@(test)
|
||||
simd_pairwise_aliases_are_distinct :: proc(t: ^testing.T) {
|
||||
a: #simd[2]i32 = {10, 3}
|
||||
b: #simd[2]i32 = {20, 4}
|
||||
|
||||
add := simd.pairwise_add(a, b)
|
||||
sub := simd.pairwise_sub(a, b)
|
||||
testing.expect_value(t, intrinsics.simd_extract(add, 0), i32(13))
|
||||
testing.expect_value(t, intrinsics.simd_extract(sub, 0), i32(7))
|
||||
testing.expect_value(t, intrinsics.simd_extract(sub, 1), i32(16))
|
||||
}
|
||||
|
||||
// The rotate offset is declared `$offset: int`; the checker demanded `i64`, so the declared
|
||||
// spelling did not compile. Other integer types stay rejected -- the declaration is not #any_int.
|
||||
|
||||
@(test)
|
||||
simd_lanes_rotate_offset_is_int :: proc(t: ^testing.T) {
|
||||
rot :: proc(v: #simd[4]u32, $offset: int) -> #simd[4]u32 {
|
||||
return intrinsics.simd_lanes_rotate_right(v, offset)
|
||||
}
|
||||
|
||||
v: #simd[4]u32 = {0, 1, 2, 3}
|
||||
r := rot(v, 1)
|
||||
testing.expect_value(t, intrinsics.simd_extract(r, 0), u32(3))
|
||||
testing.expect_value(t, intrinsics.simd_extract(r, 1), u32(0))
|
||||
|
||||
l := intrinsics.simd_lanes_rotate_left(v, 1)
|
||||
testing.expect_value(t, intrinsics.simd_extract(l, 0), u32(1))
|
||||
testing.expect_value(t, intrinsics.simd_extract(l, 3), u32(0))
|
||||
}
|
||||
|
||||
// `simd_extract` / `simd_replace` declare a plain `idx: uint`, so a runtime index is allowed and
|
||||
// lowers to a dynamic extractelement. Bounds are enforced only where the index is constant.
|
||||
|
||||
@(test)
|
||||
simd_extract_accepts_a_runtime_index :: proc(t: ^testing.T) {
|
||||
v: #simd[4]u32 = {10, 20, 30, 40}
|
||||
i := 2
|
||||
testing.expect_value(t, intrinsics.simd_extract(v, i), u32(30))
|
||||
|
||||
w := intrinsics.simd_replace(v, i, u32(99))
|
||||
testing.expect_value(t, intrinsics.simd_extract(w, 2), u32(99))
|
||||
testing.expect_value(t, intrinsics.simd_extract(w, 0), u32(10))
|
||||
}
|
||||
Reference in New Issue
Block a user