Merge pull request #7344 from kalsprite/simd_builtin_diagnostics

simd: enforce the `#simd` construction rules and stop two crashes
This commit is contained in:
Jeroen van Rijn
2026-08-16 12:17:39 +02:00
committed by GitHub
6 changed files with 374 additions and 31 deletions

View File

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

View File

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

View File

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

View File

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