From 9047c60ec137f4a0c77ab2ea0cc57c08fe28c0dd Mon Sep 17 00:00:00 2001 From: kalsprite Date: Fri, 14 Aug 2026 18:08:11 -0700 Subject: [PATCH] arm32: default to arm1176jzf-s so the gnueabihf triple actually has an FPU, arm32: implement AAPCS32 homogeneous float aggregates incl unions, arm32: fix reversed arguments in the small-struct return coercion,abi test: __aeabi_uldivmod so the arm32 corpus links --- src/llvm_abi.cpp | 62 +++++++++++++++++++++++++++++++++++++++----- src/llvm_backend.cpp | 7 +++++ tests/abi/cross.sh | 28 ++++++++++++++++---- 3 files changed, 86 insertions(+), 11 deletions(-) diff --git a/src/llvm_abi.cpp b/src/llvm_abi.cpp index 08d6adf69..6d48272e9 100644 --- a/src/llvm_abi.cpp +++ b/src/llvm_abi.cpp @@ -2079,14 +2079,14 @@ namespace lbAbiWasm { namespace lbAbiArm32 { gb_internal Array compute_arg_types(LLVMContextRef c, LLVMTypeRef *arg_types, unsigned arg_count, ProcCallingConvention calling_convention, Type *original_type); - gb_internal lbArgType compute_return_type(LLVMContextRef c, LLVMTypeRef return_type, bool return_is_defined, ProcCallingConvention calling_convention); + gb_internal lbArgType compute_return_type(LLVMContextRef c, LLVMTypeRef return_type, bool return_is_defined, ProcCallingConvention calling_convention, Type *return_source); gb_internal LB_ABI_INFO(abi_info) { LLVMContextRef c = m->ctx; lbFunctionType *ft = permanent_alloc_item(); ft->ctx = c; ft->args = compute_arg_types(c, arg_types, arg_count, calling_convention, original_type); - ft->ret = compute_return_type(c, return_type, return_is_defined, calling_convention); + ft->ret = compute_return_type(c, return_type, return_is_defined, calling_convention, lb_abi_single_result_type(original_type)); ft->calling_convention = calling_convention; return ft; } @@ -2115,6 +2115,47 @@ namespace lbAbiArm32 { return lb_arg_type_direct(type, nullptr, nullptr, attr); } + // AAPCS32 ยง5.5, the VFP variant that the `gnueabihf` triple selects: an aggregate of at most + // four members that are all the same fp type is a Homogeneous fp Aggregate, and travels + // in s0-s3 / d0-d3 rather than in the core registers. Everything below coerces aggregates to + // `[N x i32]`, which puts an HFA in r0-r3 where the C side reads s0-s3. + // + // The detector is arm64's: AAPCS64 states the same rule over the same shapes. + // `coerce_` is set when the lowered type cannot express the HFA and LLVM has to be handed an + // `[N x base]` instead of the type itself. That happens for a `#raw_union`, which has become + // an opaque integer by now and AAPCS32 DOES count a union of floats as homogeneous + gb_internal bool is_hfa(LLVMContextRef c, LLVMTypeRef type, Type *source_type, + ProcCallingConvention calling_convention, LLVMTypeRef *coerce_) { + if (is_calling_convention_odin(calling_convention)) { + // Both sides are Odin, so the existing lowering is self-consistent; leave it alone. + return false; + } + LLVMTypeRef base_type = nullptr; + unsigned member_count = 0; + bool needs_coerce = false; + if (!lbAbiArm64::is_homogenous_aggregate(c, type, &base_type, &member_count)) { + if (source_type == nullptr || + !lbAbiArm64::is_homogenous_aggregate_source(c, source_type, &base_type, &member_count)) { + return false; + } + needs_coerce = true; + } + if (member_count == 0 || member_count > 4) { + return false; + } + switch (LLVMGetTypeKind(base_type)) { + case LLVMFloatTypeKind: + case LLVMDoubleTypeKind: + break; + default: + return false; + } + if (coerce_) { + *coerce_ = needs_coerce ? llvm_array_type(base_type, member_count) : nullptr; + } + return true; + } + gb_internal Array compute_arg_types(LLVMContextRef c, LLVMTypeRef *arg_types, unsigned arg_count, ProcCallingConvention calling_convention, Type *original_type) { auto args = array_make(lb_function_type_args_allocator(), arg_count); auto srcs = lb_abi_param_source_types(original_type, arg_count); @@ -2129,6 +2170,8 @@ namespace lbAbiArm32 { // Added to support hard floats included in the playdates cortex-m7. if (calling_convention == ProcCC_CDecl && selected_subtarget == Subtarget_Playdate) { args[i] = lb_arg_type_direct(t); + } else if (LLVMTypeRef hfa_coerce = nullptr; is_hfa(c, t, srcs[i], calling_convention, &hfa_coerce)) { + args[i] = lb_arg_type_direct(t, hfa_coerce, nullptr, nullptr); } else if (is_calling_convention_odin(calling_convention) && sz > 8) { // Minor change to improve performance using the Odin calling conventions args[i] = lb_arg_type_indirect(t, nullptr); @@ -2144,17 +2187,24 @@ namespace lbAbiArm32 { return args; } - gb_internal lbArgType compute_return_type(LLVMContextRef c, LLVMTypeRef return_type, bool return_is_defined, ProcCallingConvention calling_convention) { + gb_internal lbArgType compute_return_type(LLVMContextRef c, LLVMTypeRef return_type, bool return_is_defined, ProcCallingConvention calling_convention, Type *return_source) { if (!return_is_defined) { return lb_arg_type_direct(LLVMVoidTypeInContext(c)); } else if (!is_register(return_type, true)) { if (calling_convention == ProcCC_CDecl && selected_subtarget == Subtarget_Playdate) { return lb_arg_type_direct(return_type); } + // An HFA is returned in s0-s3 / d0-d3 too. It must not fall through to the + // integer coercions or to `sret`. + LLVMTypeRef hfa_coerce = nullptr; + if (is_hfa(c, return_type, return_source, calling_convention, &hfa_coerce)) { + return lb_arg_type_direct(return_type, hfa_coerce, nullptr, nullptr); + } + // `lb_arg_type_direct` takes (type, cast_type), and the cast type is what the function actually returns. switch (lb_sizeof(return_type)) { - case 1: return lb_arg_type_direct(LLVMIntTypeInContext(c, 8), return_type, nullptr, nullptr); - case 2: return lb_arg_type_direct(LLVMIntTypeInContext(c, 16), return_type, nullptr, nullptr); - case 3: case 4: return lb_arg_type_direct(LLVMIntTypeInContext(c, 32), return_type, nullptr, nullptr); + case 1: return lb_arg_type_direct(return_type, LLVMIntTypeInContext(c, 8), nullptr, nullptr); + case 2: return lb_arg_type_direct(return_type, LLVMIntTypeInContext(c, 16), nullptr, nullptr); + case 3: case 4: return lb_arg_type_direct(return_type, LLVMIntTypeInContext(c, 32), nullptr, nullptr); } LLVMAttributeRef attr = lb_create_enum_attribute_with_type(c, "sret", return_type); return lb_arg_type_indirect(return_type, attr); diff --git a/src/llvm_backend.cpp b/src/llvm_backend.cpp index 95df563fd..1f71257f8 100644 --- a/src/llvm_backend.cpp +++ b/src/llvm_backend.cpp @@ -46,6 +46,13 @@ gb_internal String get_default_microarchitecture() { } } else if (build_context.metrics.arch == TargetArch_riscv64) { default_march = str_lit("generic-rv64"); + } else if (build_context.metrics.arch == TargetArch_arm32) { + // The arm32 triple is `gnueabihf`, and the hard-float ABI passes floating point in the + // VFP registers. `generic` has no FPU at all. LLVM cannot honor the ABI its own + // triple asks for and quietly falls back to the soft-float convention. + // + // `arm1176jzf-s` is what clang picks by default for this same triple. + default_march = str_lit("arm1176jzf-s"); } return default_march; diff --git a/tests/abi/cross.sh b/tests/abi/cross.sh index 1a700114c..64ad01526 100755 --- a/tests/abi/cross.sh +++ b/tests/abi/cross.sh @@ -20,10 +20,12 @@ set -eu # `#simd[2]f16` aborts the compiler with "LLVM ERROR: Do not know how to split # the result of this operator!". # -# `pentium4` is what clang's own default for `i386-linux-gnu` is, `haswell` -# additionally has F16C, which isolates one non-ABI failure: `hfa4_f16` -# diverges at pentium4 and agrees at haswell. It is `_Float16` conversion -# codegen rather than a calling convention +# `pentium4` is what clang's own default for `i386-linux-gnu` is, so it is the +# baseline to compare against. BOTH SIDES must agree on it: the C side follows +# clang's default unless ABI_CFLAGS says otherwise, so `-microarch:haswell` +# alone makes the two disagree about where a 32-byte vector lives and reports +# phantom vector failures. Match them (`ABI_CFLAGS=-march=haswell`) or use +# pentium4 on both. TARGET=${1:?odin target, e.g. linux_arm64} TRIPLE=${2:?clang triple, e.g. aarch64-linux-gnu} @@ -54,7 +56,22 @@ _start: _start: bl probe_main mov r7, #1 - svc #0' ;; + svc #0 + +@ The runtime does 64-bit division. On Arm, the compiler emits `__aeabi_uldivmod` +@ instead of `__udivdi3`. It returns the quotient in r0:r1 and the remainder in +@ r2:r3, which C cannot express. It is written here and forwards to the shim. + .globl __aeabi_uldivmod +__aeabi_uldivmod: + push {lr} + sub sp, sp, #12 + add r12, sp, #4 + str r12, [sp] + bl shim_udivmod + ldr r2, [sp, #4] + ldr r3, [sp, #8] + add sp, sp, #12 + pop {pc}' ;; *riscv64*) START='.text .globl _start _start: @@ -102,6 +119,7 @@ static u64 udivmod(u64 a, u64 b, u64 *rem){ u64 q=0,r=0; if(b==0){ if(rem)*rem=0; return 0; } for(int i=63;i>=0;i--){ r=(r<<1)|((a>>i)&1); if(r>=b){ r-=b; q|=(u64)1<