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
This commit is contained in:
kalsprite
2026-08-14 18:08:11 -07:00
parent 1e2d91c38b
commit 9047c60ec1
3 changed files with 86 additions and 11 deletions

View File

@@ -2079,14 +2079,14 @@ namespace lbAbiWasm {
namespace lbAbiArm32 {
gb_internal Array<lbArgType> 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<lbFunctionType>();
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<lbArgType> compute_arg_types(LLVMContextRef c, LLVMTypeRef *arg_types, unsigned arg_count, ProcCallingConvention calling_convention, Type *original_type) {
auto args = array_make<lbArgType>(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);

View File

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

View File

@@ -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<<i; } }
if(rem)*rem=r; return q; }
u64 shim_udivmod(u64 a, u64 b, u64 *rem){ return udivmod(a,b,rem); }
u64 __udivdi3(u64 a, u64 b){ return udivmod(a,b,0); }
u64 __umoddi3(u64 a, u64 b){ u64 r; udivmod(a,b,&r); return r; }
i64 __divdi3(i64 a, i64 b){ int n=0; u64 ua=a<0?(n^=1,(u64)-a):(u64)a, ub=b<0?(n^=1,(u64)-b):(u64)b;