From b27f28d1ea778282fa1337822063b9e1bc3dc809 Mon Sep 17 00:00:00 2001 From: Urjasvi Suthar Date: Tue, 14 Jul 2026 10:26:52 +0530 Subject: [PATCH 1/2] Fix darwin arm64 argument passing abi. Darwin arm64 expects caller to take responsibility of zero/sign extending any arguments < 32 bits. Fixes #6809 and #6816 --- src/llvm_abi.cpp | 37 +++++++++++++++++++------- tests/issues/run.sh | 3 +++ tests/issues/test_issue_6809_6816.c | 26 ++++++++++++++++++ tests/issues/test_issue_6809_6816.odin | 27 +++++++++++++++++++ 4 files changed, 84 insertions(+), 9 deletions(-) create mode 100644 tests/issues/test_issue_6809_6816.c create mode 100644 tests/issues/test_issue_6809_6816.odin diff --git a/src/llvm_abi.cpp b/src/llvm_abi.cpp index 1e3af35ba..f1a23a95c 100644 --- a/src/llvm_abi.cpp +++ b/src/llvm_abi.cpp @@ -1124,7 +1124,7 @@ namespace lbAbiAmd64SysV { namespace lbAbiArm64 { - gb_internal Array compute_arg_types(LLVMContextRef c, LLVMTypeRef *arg_types, unsigned arg_count); + gb_internal Array compute_arg_types(LLVMContextRef c, LLVMTypeRef *arg_types, unsigned arg_count, Type* original_type); gb_internal LB_ABI_COMPUTE_RETURN_TYPE(compute_return_type); gb_internal bool is_homogenous_aggregate(LLVMContextRef c, LLVMTypeRef type, LLVMTypeRef *base_type_, unsigned *member_count_); @@ -1132,7 +1132,7 @@ namespace lbAbiArm64 { LLVMContextRef c = m->ctx; lbFunctionType *ft = permanent_alloc_item(); ft->ctx = c; - ft->args = compute_arg_types(c, arg_types, arg_count); + ft->args = compute_arg_types(c, arg_types, arg_count, original_type); ft->ret = compute_return_type(ft, c, return_type, return_is_defined, return_is_tuple); ft->calling_convention = calling_convention; return ft; @@ -1155,11 +1155,23 @@ namespace lbAbiArm64 { return false; } - gb_internal lbArgType non_struct(LLVMContextRef c, LLVMTypeRef type) { + gb_internal lbArgType non_struct(LLVMContextRef c, LLVMTypeRef type, Type* original_type) { LLVMAttributeRef attr = nullptr; LLVMTypeRef i1 = LLVMInt1TypeInContext(c); - if (type == i1) { - attr = lb_create_enum_attribute(c, "zeroext"); + // https://developer.apple.com/documentation/xcode/writing-arm64-code-for-apple-platforms#Pass-arguments-to-functions-correctly + // Darwin expects caller to take responsibility of zero/sign extending any arguments < 32bits. + if (build_context.metrics.os == TargetOs_darwin && original_type != nullptr) { + if ((is_type_integer_like(original_type) || is_type_enum(original_type)) && lb_sizeof(type) < 4) { + if (is_type_unsigned(original_type) || is_type_boolean(original_type)) { + attr = lb_create_enum_attribute(c, "zeroext"); + } else { + attr = lb_create_enum_attribute(c, "signext"); + } + } + } else { + if (type == i1) { + attr = lb_create_enum_attribute(c, "zeroext"); + } } return lb_arg_type_direct(type, nullptr, nullptr, attr); } @@ -1260,7 +1272,7 @@ namespace lbAbiArm64 { if (!return_is_defined) { return lb_arg_type_direct(LLVMVoidTypeInContext(c)); } else if (is_register(return_type)) { - return non_struct(c, return_type); + return non_struct(c, return_type, nullptr); } else if (is_homogenous_aggregate(c, return_type, &homo_base_type, &homo_member_count)) { if (is_homogenous_aggregate_small_enough(homo_base_type, homo_member_count)) { return lb_arg_type_direct(return_type, llvm_array_type(homo_base_type, homo_member_count), nullptr, nullptr); @@ -1297,17 +1309,24 @@ namespace lbAbiArm64 { } } - gb_internal Array compute_arg_types(LLVMContextRef c, LLVMTypeRef *arg_types, unsigned arg_count) { + gb_internal Array compute_arg_types(LLVMContextRef c, LLVMTypeRef *arg_types, unsigned arg_count, Type* original_type) { auto args = array_make(lb_function_type_args_allocator(), arg_count); - for (unsigned i = 0; i < arg_count; i++) { + GB_ASSERT(original_type->kind == Type_Proc); + auto const ¶ms = original_type->Proc.params->Tuple.variables; + + for (unsigned i = 0, j = 0; i < arg_count; i++, j++) { + while (params[j]->kind != Entity_Variable) { + j++; + } + Type *ptype = params[j]->type; LLVMTypeRef type = arg_types[i]; LLVMTypeRef homo_base_type = {}; unsigned homo_member_count = 0; if (is_register(type)) { - args[i] = non_struct(c, type); + args[i] = non_struct(c, type, ptype); } else if (is_homogenous_aggregate(c, type, &homo_base_type, &homo_member_count)) { if (is_homogenous_aggregate_small_enough(homo_base_type, homo_member_count)) { args[i] = lb_arg_type_direct(type, llvm_array_type(homo_base_type, homo_member_count), nullptr, nullptr); diff --git a/tests/issues/run.sh b/tests/issues/run.sh index b83cf7cf3..c1e231390 100755 --- a/tests/issues/run.sh +++ b/tests/issues/run.sh @@ -85,6 +85,9 @@ $ODIN check ../test_issue_6979.odin -no-entry-point $COMMON clang -c ../test_issue_7010.c -o test_issue_7010_c.o $ODIN test ../test_issue_7010.odin $COMMON +clang -c ../test_issue_6809_6816.c -o test_issue_6809_6816_c.o -O3 +$ODIN test ../test_issue_6809_6816.odin -o:speed $COMMON + set +x popd diff --git a/tests/issues/test_issue_6809_6816.c b/tests/issues/test_issue_6809_6816.c new file mode 100644 index 000000000..36adfcbbd --- /dev/null +++ b/tests/issues/test_issue_6809_6816.c @@ -0,0 +1,26 @@ +#include + #include + + // Clang compiles this to a single 32-bit register comparison: + // cmp w0, #-1 + // If the caller failed to sign-extend the 8-bit -1 (0xFF) to 32-bit (0xFFFFFFFF), + // the comparison fails. + bool test_i8(int8_t val) { + return val == -1; + } + + // Clang compiles this to: + // cmp w0, #200 + // If the caller did not zero-extend 200 (0xC8) to 32-bit, leaving garbage + // in the upper bits of w0, this comparison fails. + bool test_u8(uint8_t val) { + return val == 200; + } + + // Clang compiles this to: + // cmp w0, #1 + // If the caller did not zero-extend the boolean true (1) to 32-bit, + // leaving garbage in the upper bits of w0, this comparison fails. + bool test_bool(bool val) { + return val == true; + } diff --git a/tests/issues/test_issue_6809_6816.odin b/tests/issues/test_issue_6809_6816.odin new file mode 100644 index 000000000..b216f864f --- /dev/null +++ b/tests/issues/test_issue_6809_6816.odin @@ -0,0 +1,27 @@ +// Tests issue: https://github.com/odin-lang/Odin/issues/6809 +// & https://github.com/odin-lang/Odin/issues/6816 + +package test_issues + +import "core:testing" + +foreign import test_lib "build/test_issue_6809_6816_c.o" + +foreign test_lib { + test_i8 :: proc "c" (val: i8) -> bool --- + test_u8 :: proc "c" (val: u8) -> bool --- + test_bool :: proc "c" (val: bool) -> bool --- +} + +@test +test_macos_proc_argument_abi :: proc(t: ^testing.T) { + + res_i8 := test_i8(-1) + testing.expectf(t, res_i8 == true, "test_i8(-1) -> Expected: true, Got: %v\n", res_i8) + + res_u8 := test_u8(200) + testing.expectf(t, res_u8 == true, "test_u8(200) -> Expected: true, Got: %v\n", res_u8) + + res_bool := test_bool(true) + testing.expectf(t, res_bool == true, "test_bool(true) -> Expected: true, Got: %v\n", res_bool) +} From 65591fad4ef4dd80f90c502232fcc5bf544b56b5 Mon Sep 17 00:00:00 2001 From: Urjasvi Suthar Date: Tue, 14 Jul 2026 10:37:20 +0530 Subject: [PATCH 2/2] fmt c test code --- tests/issues/test_issue_6809_6816.c | 44 ++++++++++++++--------------- 1 file changed, 22 insertions(+), 22 deletions(-) diff --git a/tests/issues/test_issue_6809_6816.c b/tests/issues/test_issue_6809_6816.c index 36adfcbbd..a2ad37b96 100644 --- a/tests/issues/test_issue_6809_6816.c +++ b/tests/issues/test_issue_6809_6816.c @@ -1,26 +1,26 @@ #include - #include +#include - // Clang compiles this to a single 32-bit register comparison: - // cmp w0, #-1 - // If the caller failed to sign-extend the 8-bit -1 (0xFF) to 32-bit (0xFFFFFFFF), - // the comparison fails. - bool test_i8(int8_t val) { - return val == -1; - } +// Clang compiles this to a single 32-bit register comparison: +// cmp w0, #-1 +// If the caller failed to sign-extend the 8-bit -1 (0xFF) to 32-bit (0xFFFFFFFF), +// the comparison fails. +bool test_i8(int8_t val) { + return val == -1; +} - // Clang compiles this to: - // cmp w0, #200 - // If the caller did not zero-extend 200 (0xC8) to 32-bit, leaving garbage - // in the upper bits of w0, this comparison fails. - bool test_u8(uint8_t val) { - return val == 200; - } +// Clang compiles this to: +// cmp w0, #200 +// If the caller did not zero-extend 200 (0xC8) to 32-bit, leaving garbage +// in the upper bits of w0, this comparison fails. +bool test_u8(uint8_t val) { + return val == 200; +} - // Clang compiles this to: - // cmp w0, #1 - // If the caller did not zero-extend the boolean true (1) to 32-bit, - // leaving garbage in the upper bits of w0, this comparison fails. - bool test_bool(bool val) { - return val == true; - } +// Clang compiles this to: +// cmp w0, #1 +// If the caller did not zero-extend the boolean true (1) to 32-bit, +// leaving garbage in the upper bits of w0, this comparison fails. +bool test_bool(bool val) { + return val == true; +}