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
This commit is contained in:
Urjasvi Suthar
2026-07-14 10:26:52 +05:30
parent 6953670191
commit b27f28d1ea
4 changed files with 84 additions and 9 deletions

View File

@@ -1124,7 +1124,7 @@ namespace lbAbiAmd64SysV {
namespace lbAbiArm64 {
gb_internal Array<lbArgType> compute_arg_types(LLVMContextRef c, LLVMTypeRef *arg_types, unsigned arg_count);
gb_internal Array<lbArgType> 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<lbFunctionType>();
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<lbArgType> compute_arg_types(LLVMContextRef c, LLVMTypeRef *arg_types, unsigned arg_count) {
gb_internal Array<lbArgType> compute_arg_types(LLVMContextRef c, LLVMTypeRef *arg_types, unsigned arg_count, Type* original_type) {
auto args = array_make<lbArgType>(lb_function_type_args_allocator(), arg_count);
for (unsigned i = 0; i < arg_count; i++) {
GB_ASSERT(original_type->kind == Type_Proc);
auto const &params = 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);

View File

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

View File

@@ -0,0 +1,26 @@
#include <stdint.h>
#include <stdbool.h>
// 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;
}

View File

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