From f3b71ed7ebb8a398fdd925383f0ce17326d28a3d Mon Sep 17 00:00:00 2001 From: Christoffer Lerno Date: Thu, 25 Dec 2025 20:55:11 +0100 Subject: [PATCH] - `$$MASK_TO_INT` and `$$INT_TO_MASK` to create bool masks from integers and back. - Fix bug when creating bool vectors in certain cases. --- .clang-tidy | 28 +++++---- lib/std/core/builtin.c3 | 1 + lib/std/math/vector.c3 | 14 +++++ releasenotes.md | 2 + src/compiler/enums.h | 2 + src/compiler/llvm_codegen_builtins.c | 35 +++++++++++ src/compiler/llvm_codegen_expr.c | 9 ++- src/compiler/sema_builtins.c | 62 +++++++++++++++---- src/compiler/symtab.c | 2 + test/test_suite/builtins/int_to_mask.c3t | 38 ++++++++++++ test/test_suite/builtins/int_to_mask_fail.c3 | 18 ++++++ .../methods/method_resolution_order.c3 | 2 +- test/unit/regression/vector_mask_test.c3 | 10 +++ 13 files changed, 193 insertions(+), 30 deletions(-) create mode 100644 test/test_suite/builtins/int_to_mask.c3t create mode 100644 test/test_suite/builtins/int_to_mask_fail.c3 create mode 100644 test/unit/regression/vector_mask_test.c3 diff --git a/.clang-tidy b/.clang-tidy index 912410670..0a8638235 100644 --- a/.clang-tidy +++ b/.clang-tidy @@ -26,17 +26,19 @@ Checks: > # Turn all the warnings from the checks above into errors. WarningsAsErrors: "*" - CheckOptions: - - { key: readability-function-cognitive-complexity.Threshold, value: 100 } - - { key: readability-identifier-naming.StructCase, value: CamelCase } - - { key: readability-identifier-naming.FunctionCase, value: lower_case } - - { key: readability-identifier-naming.VariableCase, value: lower_case } - - { key: readability-identifier-naming.MacroDefinitionCase, value: UPPER_CASE } - - { key: readability-identifier-naming.EnumConstantCase, value: UPPER_CASE } - - { key: readability-identifier-naming.ConstexprVariableCase, value: CamelCase } - - { key: readability-identifier-naming.ConstexprVariablePrefix, value: k } - - { key: readability-identifier-naming.GlobalConstantCase, value: CamelCase } - - { key: readability-identifier-naming.GlobalConstantPrefix, value: k } - - { key: readability-identifier-naming.StaticConstantCase, value: CamelCase } - - { key: readability-identifier-naming.StaticConstantPrefix, value: k } \ No newline at end of file + - { key: readability-function-cognitive-complexity.Threshold, value: 100 } + - { key: readability-identifier-naming.StructCase, value: CamelCase } + - { key: readability-identifier-naming.FunctionCase, value: lower_case } + - { key: readability-identifier-naming.VariableCase, value: lower_case } + - { key: readability-identifier-naming.MacroDefinitionCase, value: UPPER_CASE } + - { key: readability-identifier-naming.EnumConstantCase, value: UPPER_CASE } + - { key: readability-identifier-naming.ConstexprVariableCase, value: CamelCase } + - { key: readability-identifier-naming.ConstexprVariablePrefix, value: k } + - { key: readability-identifier-naming.GlobalConstantCase, value: CamelCase } + - { key: readability-identifier-naming.GlobalConstantPrefix, value: k } + - { key: readability-identifier-naming.StaticConstantCase, value: CamelCase } + - { key: readability-identifier-naming.StaticConstantPrefix, value: k } + - { key: readability-identifier-naming.MinimumParameterNameLength, value: 0 } + +MinimumParameterNameLength: 0 diff --git a/lib/std/core/builtin.c3 b/lib/std/core/builtin.c3 index b0e05c97f..caa9a7a45 100644 --- a/lib/std/core/builtin.c3 +++ b/lib/std/core/builtin.c3 @@ -462,6 +462,7 @@ macro swizzle(v, ...) @builtin return $$swizzle(v, $vasplat); } + <* Shuffle two vectors by a common index from arranging the vectors sequentially in memory diff --git a/lib/std/math/vector.c3 b/lib/std/math/vector.c3 index ca6c25a4c..add4678a9 100644 --- a/lib/std/math/vector.c3 +++ b/lib/std/math/vector.c3 @@ -3,6 +3,20 @@ module std::math::vector; import std::math; +<* + @require $Type.kindof == VECTOR &&& $kindof(($Type){}[0]) == BOOL : "Expected a bool vector" + @require $kindof(mask).is_int() : "Expected an integer mask" +*> +macro bool[<*>] mask_from_int($Type, mask) +{ + return $$int_to_mask(mask, $Type.len); +} + +macro bool[<*>].mask_to_int(self) +{ + return $$mask_to_int(self); +} + macro double double[<*>].sq_magnitude(self) => self.dot(self); macro float float[<*>].sq_magnitude(self) => self.dot(self); diff --git a/releasenotes.md b/releasenotes.md index cd84bd2ba..0dadd6933 100644 --- a/releasenotes.md +++ b/releasenotes.md @@ -9,6 +9,7 @@ - Fixed bug where constants would get modified when slicing them. #2660 - Support for NetBSD. - Testing for the presence of methods at the top level is prohibited previous to method registration. +- `$$MASK_TO_INT` and `$$INT_TO_MASK` to create bool masks from integers and back. ### Fixes - Regression with npot vector in struct triggering an assert #2219. @@ -30,6 +31,7 @@ - Incorrect rounding for decimals in formatter in some cases. #2657 - Incorrectly using LLVMStructType when emitting dynamic functions on MachO #2666 - FixedThreadPool join did not work correctly. +- Fix bug when creating bool vectors in certain cases. ### Stdlib changes - Add `ThreadPool` join function to wait for all threads to finish in the pool without destroying the threads. diff --git a/src/compiler/enums.h b/src/compiler/enums.h index add7aea74..02b14e83a 100644 --- a/src/compiler/enums.h +++ b/src/compiler/enums.h @@ -464,11 +464,13 @@ typedef enum BUILTIN_FSHR, BUILTIN_GATHER, BUILTIN_GET_ROUNDING_MODE, + BUILTIN_INT_TO_MASK, BUILTIN_LOG, BUILTIN_LOG10, BUILTIN_LOG2, BUILTIN_MATRIX_MUL, BUILTIN_MATRIX_TRANSPOSE, + BUILTIN_MASK_TO_INT, BUILTIN_MASKED_LOAD, BUILTIN_MASKED_STORE, BUILTIN_MAX, diff --git a/src/compiler/llvm_codegen_builtins.c b/src/compiler/llvm_codegen_builtins.c index 460cf3dcb..eedf292fa 100644 --- a/src/compiler/llvm_codegen_builtins.c +++ b/src/compiler/llvm_codegen_builtins.c @@ -595,6 +595,35 @@ static void llvm_emit_gather(GenContext *c, BEValue *be_value, Expr *expr) llvm_value_set(be_value, result, expr->type); } +static void llvm_emit_mask_to_int(GenContext *c, BEValue *be_value, Expr *expr) +{ + Expr **args = expr->call_expr.arguments; + ASSERT(vec_size(args) == 1); + LLVMValueRef val = llvm_emit_expr_to_rvalue(c, args[0]); + LLVMTypeRef mask_type = LLVMTypeOf(val); + unsigned bits = LLVMGetVectorSize(mask_type); + val = LLVMBuildBitCast(c->builder, val, LLVMIntTypeInContext(c->context, bits), ""); + unsigned target_bits = next_highest_power_of_2(bits); + if (target_bits < 8) target_bits = 8; + if (target_bits < bits) val = LLVMBuildZExt(c->builder, val, llvm_get_type(c, expr->type), ""); + llvm_value_set(be_value, val, expr->type); +} + +static void llvm_emit_int_to_mask(GenContext *c, BEValue *be_value, Expr *expr) +{ + Expr **args = expr->call_expr.arguments; + ASSERT(vec_size(args) == 2); + LLVMValueRef val = llvm_emit_expr_to_rvalue(c, args[0]); + unsigned bits = (unsigned)args[1]->const_expr.ixx.i.low; + unsigned int_len = type_bit_size(args[0]->type); + if (int_len > bits) + { + val = LLVMBuildTrunc(c->builder, val, LLVMIntTypeInContext(c->context, bits), ""); + } + val = LLVMBuildBitCast(c->builder, val, LLVMVectorType(c->bool_type, bits), ""); + llvm_value_set(be_value, val, expr->type); +} + static void llvm_emit_masked_store(GenContext *c, BEValue *be_value, Expr *expr) { Expr **args = expr->call_expr.arguments; @@ -915,6 +944,12 @@ void llvm_emit_builtin_call(GenContext *c, BEValue *result_value, Expr *expr) case BUILTIN_SCATTER: llvm_emit_scatter(c, result_value, expr); return; + case BUILTIN_MASK_TO_INT: + llvm_emit_mask_to_int(c, result_value, expr); + return; + case BUILTIN_INT_TO_MASK: + llvm_emit_int_to_mask(c, result_value, expr); + return; case BUILTIN_MASKED_STORE: llvm_emit_masked_store(c, result_value, expr); return; diff --git a/src/compiler/llvm_codegen_expr.c b/src/compiler/llvm_codegen_expr.c index 6064ef03f..447c0596d 100644 --- a/src/compiler/llvm_codegen_expr.c +++ b/src/compiler/llvm_codegen_expr.c @@ -6357,8 +6357,7 @@ static inline void llvm_emit_vector_initializer_list(GenContext *c, BEValue *val FOREACH_IDX(i, Expr *, element, elements) { llvm_emit_expr(c, &val, element); - llvm_value_rvalue(c, &val); - vec_value = llvm_update_vector(c, vec_value, val.value, (ArrayIndex)i); + vec_value = llvm_update_vector(c, vec_value, llvm_load_value_store(c, &val), (ArrayIndex)i); } } else @@ -6382,18 +6381,18 @@ static inline void llvm_emit_vector_initializer_list(GenContext *c, BEValue *val ASSERT(vec_size(designator->designator_expr.path) == 1); DesignatorElement *element = designator->designator_expr.path[0]; llvm_emit_expr(c, &val, designator->designator_expr.value); - llvm_value_rvalue(c, &val); + LLVMValueRef value = llvm_load_value_store(c, &val); switch (element->kind) { case DESIGNATOR_ARRAY: { - vec_value = llvm_update_vector(c, vec_value, val.value, element->index); + vec_value = llvm_update_vector(c, vec_value, value, element->index); break; } case DESIGNATOR_RANGE: for (ArrayIndex idx = element->index; idx <= element->index_end; idx++) { - vec_value = llvm_update_vector(c, vec_value, val.value, idx); + vec_value = llvm_update_vector(c, vec_value, value, idx); } break; case DESIGNATOR_FIELD: diff --git a/src/compiler/sema_builtins.c b/src/compiler/sema_builtins.c index 7d88e1d34..1488c70c5 100644 --- a/src/compiler/sema_builtins.c +++ b/src/compiler/sema_builtins.c @@ -1068,6 +1068,44 @@ bool sema_expr_analyse_builtin_call(SemaContext *context, Expr *expr) rtype = type_void; break; } + case BUILTIN_INT_TO_MASK: + { + ASSERT(arg_count == 2); + if (!sema_check_builtin_args(context, args, (BuiltinArg[]) {BA_INTEGER, BA_INTEGER }, 2)) return false; + Expr *len = args[1]; + if (!sema_cast_const(len) || !expr_is_const_int(len)) + { + RETURN_SEMA_ERROR(len, "Expected constant integer for the vector length."); + } + Type *type = type_flatten(args[0]->type); + int size = (int)type_bit_size(type); + if (int_icomp(len->const_expr.ixx, size, BINARYOP_GT)) + { + RETURN_SEMA_ERROR(args[1], "The vector length (%s) cannot be greater than the bit width of the integer (%d).", + int_to_str(len->const_expr.ixx, 10, false), size); + } + int bits = (int)len->const_expr.ixx.i.low; + if (!bits) + { + RETURN_SEMA_ERROR(args[1], "The vector length cannot be zero"); + } + rtype = type_get_vector(type_bool, TYPE_VECTOR, bits); + break; + } + case BUILTIN_MASK_TO_INT: + { + ASSERT(arg_count == 1); + if (!sema_check_builtin_args(context, args, (BuiltinArg[]) {BA_BOOLVEC }, 1)) return false; + Type *vec = type_flatten(args[0]->type); + ArraySize len = vec->array.len; + if (len > 128) + { + RETURN_SEMA_ERROR(args[0], "Masks must be 128 or fewer bits to convert them to an integer."); + } + if (len < 8) len = 8; + rtype = type_int_unsigned_by_bitsize(next_highest_power_of_2(len)); + break; + } case BUILTIN_MASKED_LOAD: { ASSERT(arg_count == 4); @@ -1376,6 +1414,7 @@ static inline int builtin_expected_args(BuiltinFunction func) case BUILTIN_LOG: case BUILTIN_LRINT: case BUILTIN_LROUND: + case BUILTIN_MASK_TO_INT: case BUILTIN_NEARBYINT: case BUILTIN_POPCOUNT: case BUILTIN_REDUCE_ADD: @@ -1394,15 +1433,16 @@ static inline int builtin_expected_args(BuiltinFunction func) case BUILTIN_SIN: case BUILTIN_SQRT: case BUILTIN_STR_HASH: - case BUILTIN_STR_UPPER: case BUILTIN_STR_LOWER: - case BUILTIN_STR_SNAKECASE: case BUILTIN_STR_PASCALCASE: + case BUILTIN_STR_SNAKECASE: + case BUILTIN_STR_UPPER: case BUILTIN_TRUNC: case BUILTIN_VOLATILE_LOAD: case BUILTIN_WASM_MEMORY_SIZE: return 1; - case BUILTIN_STR_FIND: + case BUILTIN_VOLATILE_STORE: + case BUILTIN_ANY_MAKE: case BUILTIN_COPYSIGN: case BUILTIN_EXACT_ADD: case BUILTIN_EXACT_DIV: @@ -1410,6 +1450,7 @@ static inline int builtin_expected_args(BuiltinFunction func) case BUILTIN_EXACT_MUL: case BUILTIN_EXACT_SUB: case BUILTIN_EXPECT: + case BUILTIN_INT_TO_MASK: case BUILTIN_MAX: case BUILTIN_MIN: case BUILTIN_POW: @@ -1417,19 +1458,18 @@ static inline int builtin_expected_args(BuiltinFunction func) case BUILTIN_REDUCE_FADD: case BUILTIN_REDUCE_FMUL: case BUILTIN_SAT_ADD: + case BUILTIN_SAT_MUL: case BUILTIN_SAT_SHL: case BUILTIN_SAT_SUB: - case BUILTIN_SAT_MUL: - case BUILTIN_VOLATILE_STORE: - case BUILTIN_VECCOMPNE: - case BUILTIN_VECCOMPLT: - case BUILTIN_VECCOMPLE: + case BUILTIN_STR_FIND: + case BUILTIN_UNALIGNED_LOAD: + case BUILTIN_VECCOMPEQ: case BUILTIN_VECCOMPGE: case BUILTIN_VECCOMPGT: - case BUILTIN_VECCOMPEQ: + case BUILTIN_VECCOMPLE: + case BUILTIN_VECCOMPLT: + case BUILTIN_VECCOMPNE: case BUILTIN_WASM_MEMORY_GROW: - case BUILTIN_ANY_MAKE: - case BUILTIN_UNALIGNED_LOAD: return 2; case BUILTIN_EXPECT_WITH_PROBABILITY: case BUILTIN_FMA: diff --git a/src/compiler/symtab.c b/src/compiler/symtab.c index fe8045db1..bcc4bd296 100644 --- a/src/compiler/symtab.c +++ b/src/compiler/symtab.c @@ -235,9 +235,11 @@ void symtab_init(uint32_t capacity) builtin_list[BUILTIN_FSHR] = KW_DEF("fshr"); builtin_list[BUILTIN_GATHER] = KW_DEF("gather"); builtin_list[BUILTIN_GET_ROUNDING_MODE] = KW_DEF("get_rounding_mode"); + builtin_list[BUILTIN_INT_TO_MASK] = KW_DEF("int_to_mask"); builtin_list[BUILTIN_LOG] = KW_DEF("log"); builtin_list[BUILTIN_LOG2] = KW_DEF("log2"); builtin_list[BUILTIN_LOG10] = KW_DEF("log10"); + builtin_list[BUILTIN_MASK_TO_INT] = KW_DEF("mask_to_int"); builtin_list[BUILTIN_MASKED_LOAD] = KW_DEF("masked_load"); builtin_list[BUILTIN_MASKED_STORE] = KW_DEF("masked_store"); builtin_list[BUILTIN_MATRIX_MUL] = KW_DEF("matrix_mul"); diff --git a/test/test_suite/builtins/int_to_mask.c3t b/test/test_suite/builtins/int_to_mask.c3t new file mode 100644 index 000000000..b111e028c --- /dev/null +++ b/test/test_suite/builtins/int_to_mask.c3t @@ -0,0 +1,38 @@ +// #target: macos-x64 +module test; +fn void test() +{ + bool[<8>] x = $$int_to_mask(0b1111000, 8); + bool[<32>] y = $$int_to_mask(0b1111000, 32); +} + +fn void test2() +{ + char x = $$mask_to_int((bool[<8>]){ false, false, false, false, true, true, true, false }); + short x2 = $$mask_to_int((bool[<15>]){}); + uint128 y = $$mask_to_int((bool[<128>]){}); +} + +/* #expect: test.ll + +define void @test.test() #0 { +entry: + %x = alloca <8 x i8>, align 8 + %y = alloca <32 x i8>, align 32 + %0 = sext <8 x i1> bitcast (<1 x i8> splat (i8 120) to <8 x i1>) to <8 x i8> + store <8 x i8> %0, ptr %x, align 8 + %1 = sext <32 x i1> bitcast (<1 x i32> splat (i32 120) to <32 x i1>) to <32 x i8> + store <32 x i8> %1, ptr %y, align 32 + ret void +} + +define void @test.test2() #0 { +entry: + %x = alloca i8, align 1 + %x2 = alloca i16, align 2 + %y = alloca i128, align 16 + store i8 bitcast (<8 x i1> to i8), ptr %x, align 1 + store i16 0, ptr %x2, align 2 + store i128 0, ptr %y, align 16 + ret void +} diff --git a/test/test_suite/builtins/int_to_mask_fail.c3 b/test/test_suite/builtins/int_to_mask_fail.c3 new file mode 100644 index 000000000..63ec4e678 --- /dev/null +++ b/test/test_suite/builtins/int_to_mask_fail.c3 @@ -0,0 +1,18 @@ + +fn void test1() +{ + $$int_to_mask(1u, 32); + $$int_to_mask(1u, 33); // #error: The vector length (33) cannot be greater than the bit width of the integer (32) +} + +fn void test2() +{ + $$int_to_mask(1ULL, 128); + $$int_to_mask(1ULL, 129); // #error: The vector length (129) cannot be greater than the bit width of the integer (128) +} + +fn void test3() +{ + $$mask_to_int((bool[<128>]){}); + $$mask_to_int((bool[<129>]){}); // #error: Masks must be 128 or fewer bits to convert them to an integer +} \ No newline at end of file diff --git a/test/test_suite/methods/method_resolution_order.c3 b/test/test_suite/methods/method_resolution_order.c3 index 6d5088d72..035072a1e 100644 --- a/test/test_suite/methods/method_resolution_order.c3 +++ b/test/test_suite/methods/method_resolution_order.c3 @@ -1,6 +1,6 @@ module test2; import test3; -struct Bar @if($defined(Foo.b)) // #error: "There might be a method 'b' for 'Foo', but methods for the type have not yet been completely registered, so this yields an error. +struct Bar @if($defined(Foo.b)) // #error: There might be a method 'b' for 'Foo', but methods for the type have not yet been completely registered, so this yields an error { int a; } diff --git a/test/unit/regression/vector_mask_test.c3 b/test/unit/regression/vector_mask_test.c3 new file mode 100644 index 000000000..672a3eb89 --- /dev/null +++ b/test/unit/regression/vector_mask_test.c3 @@ -0,0 +1,10 @@ +module vector_mask @test; +import std::io, std::math; + +fn void to_from_mask() +{ + int x = (bool[<9>]){ true, false, true, false, false, false, false, false, true }.mask_to_int(); + test::eq(x, 0b100000101); + bool[<10>] mask = vector::mask_from_int(bool[<10>], x); + test::eq(mask, (bool[<10>]){ true, false, true, false, false, false, false, false, true, false }); +} \ No newline at end of file