mirror of
https://github.com/odin-lang/Odin.git
synced 2026-08-23 13:31:35 +00:00
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:
@@ -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);
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
Reference in New Issue
Block a user