From eb72d8ffbef5e325b0aaf0aeb58e22b2bca0a247 Mon Sep 17 00:00:00 2001 From: kalsprite Date: Fri, 14 Aug 2026 18:42:23 -0700 Subject: [PATCH] arm32: pass homogeneous vector aggregates in the VFP registers arm32: coerce narrow bare vectors, and return wide ones through a hidden pointer --- src/llvm_abi.cpp | 32 ++++++++++++++++++++++++++++++++ 1 file changed, 32 insertions(+) diff --git a/src/llvm_abi.cpp b/src/llvm_abi.cpp index 6d48272e9..eaafdae24 100644 --- a/src/llvm_abi.cpp +++ b/src/llvm_abi.cpp @@ -2111,6 +2111,20 @@ namespace lbAbiArm32 { } gb_internal lbArgType non_struct(LLVMContextRef c, LLVMTypeRef type, bool is_return, Type *source_type) { + // A bare vector narrower than a word has no register of its own to sit in, clang coerces + // it to `i32` as an argument whatever its element is. The return keeps the vector + // type, same as x86, except: a half vector is not a legal type at this microarchitecture + // (`arm1176jzf-s` has VFP2 but no fp16), so clang coerces that one in both directions. + // + // <4 x i8> <2 x i16> <2 x half> as an argument -> i32 + // <4 x i8> <2 x i16> as a return -> unchanged + // <2 x half> as a return -> i32 + if (LLVMGetTypeKind(type) == LLVMVectorTypeKind && lb_sizeof(type) == 4) { + bool is_half = LLVMGetTypeKind(LLVMGetElementType(type)) == LLVMHalfTypeKind; + if (!is_return || is_half) { + return lb_arg_type_direct(type, LLVMIntTypeInContext(c, 32), nullptr, nullptr); + } + } LLVMAttributeRef attr = lb_integer_extension_attribute(c, type, source_type); return lb_arg_type_direct(type, nullptr, nullptr, attr); } @@ -2147,6 +2161,17 @@ namespace lbAbiArm32 { case LLVMFloatTypeKind: case LLVMDoubleTypeKind: break; + case LLVMVectorTypeKind: + // AAPCS32's short vectors are the 64-bit and 128-bit ones. An aggregate of up to + // four of them is a Homogeneous Vector Aggregate, which rides in the VFP registers + // exactly as an HFA does. Any other width is not a short vector and does not qualify. + { + i64 vec_size = lb_sizeof(base_type); + if (vec_size != 8 && vec_size != 16) { + return false; + } + } + break; default: return false; } @@ -2190,6 +2215,13 @@ namespace lbAbiArm32 { 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 (LLVMGetTypeKind(return_type) == LLVMVectorTypeKind && lb_sizeof(return_type) > 16) { + // A bare vector wider than a short vector has no register file to come back in. It + // is returned through a hidden pointer. `is_register` answers true for every vector, + // without this the caller returns it directly while the C callee stores + // through an `sret` pointer that was never passed (segfault) + LLVMAttributeRef attr = lb_create_enum_attribute_with_type(c, "sret", return_type); + return lb_arg_type_indirect(return_type, attr); } else if (!is_register(return_type, true)) { if (calling_convention == ProcCC_CDecl && selected_subtarget == Subtarget_Playdate) { return lb_arg_type_direct(return_type);