From 44252f4d352d53afd86d678c0b0a40b3f681c7eb Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Wed, 4 May 2022 22:57:57 -0700 Subject: [PATCH] LLVM: fix C ABI for windows * sret logic needed a check for hasRuntimeBits() * lower f128 on windows targets with the "sse" class rather than "memory". For reference, clang emits a compile error when __float128 is used with the MSVC ABI, saying that this type is not supported. The docs for the x64 calling convention have both of these sentences: - "Any argument that doesn't fit in 8 bytes, or isn't 1, 2, 4, or 8 bytes, must be passed by reference." - "All floating point operations are done using the 16 XMM registers." * For i128, however, it is clear that the Windows calling convention wants such an object to be passed by reference. I fixed the LLVM lowering for function parameters to make this work. --- src/arch/x86_64/abi.zig | 17 +++++++++-------- src/codegen/llvm.zig | 27 ++++++++++++++++++++++++++- 2 files changed, 35 insertions(+), 9 deletions(-) diff --git a/src/arch/x86_64/abi.zig b/src/arch/x86_64/abi.zig index f388c5c93a..da2e3da394 100644 --- a/src/arch/x86_64/abi.zig +++ b/src/arch/x86_64/abi.zig @@ -12,13 +12,10 @@ pub fn classifyWindows(ty: Type, target: Target) Class { // and the registers used for those arguments. Any argument that doesn't fit in 8 // bytes, or isn't 1, 2, 4, or 8 bytes, must be passed by reference. A single argument // is never spread across multiple registers." + // "All floating point operations are done using the 16 XMM registers." // "Structs and unions of size 8, 16, 32, or 64 bits, and __m64 types, are passed // as if they were integers of the same size." - switch (ty.abiSize(target)) { - 1, 2, 4, 8 => {}, - else => return .memory, - } - return switch (ty.zigTypeTag()) { + switch (ty.zigTypeTag()) { .Pointer, .Int, .Bool, @@ -33,9 +30,13 @@ pub fn classifyWindows(ty: Type, target: Target) Class { .ErrorUnion, .AnyFrame, .Frame, - => .integer, + => switch (ty.abiSize(target)) { + 0 => unreachable, + 1, 2, 4, 8 => return .integer, + else => return .memory, + }, - .Float, .Vector => .sse, + .Float, .Vector => return .sse, .Type, .ComptimeFloat, @@ -47,7 +48,7 @@ pub fn classifyWindows(ty: Type, target: Target) Class { .Opaque, .EnumLiteral, => unreachable, - }; + } } /// There are a maximum of 8 possible return slots. Returned values are in diff --git a/src/codegen/llvm.zig b/src/codegen/llvm.zig index c0c576cdf1..426ef7c378 100644 --- a/src/codegen/llvm.zig +++ b/src/codegen/llvm.zig @@ -644,7 +644,17 @@ pub const Object = struct { if (!param_ty.hasRuntimeBitsIgnoreComptime()) continue; const llvm_arg_i = @intCast(c_uint, args.items.len) + param_offset; - try args.append(llvm_func.getParam(llvm_arg_i)); + const param = llvm_func.getParam(llvm_arg_i); + // It is possible for the calling convention to make the argument's by-reference nature + // disagree with our canonical value for it, in which case we must dereference here. + const need_deref = !param_ty.isPtrAtRuntime() and !isByRef(param_ty) and + (param.typeOf().getTypeKind() == .Pointer); + const loaded_param = if (!need_deref) param else l: { + const load_inst = builder.buildLoad(param, ""); + load_inst.setAlignment(param_ty.abiAlignment(target)); + break :l load_inst; + }; + try args.append(loaded_param); } var di_file: ?*llvm.DIFile = null; @@ -3743,6 +3753,19 @@ pub const FuncGen = struct { arg_ptr.setAlignment(alignment); const store_inst = self.builder.buildStore(llvm_arg, arg_ptr); store_inst.setAlignment(alignment); + + if (abi_llvm_ty.getTypeKind() == .Pointer) { + // In this case, the calling convention wants a pointer, but + // we have a value. + if (arg_ptr.typeOf() == abi_llvm_ty) { + try llvm_args.append(arg_ptr); + continue; + } + const casted_ptr = self.builder.buildBitCast(arg_ptr, abi_llvm_ty, ""); + try llvm_args.append(casted_ptr); + continue; + } + break :p self.builder.buildBitCast(arg_ptr, ptr_abi_ty, ""); }; @@ -7931,6 +7954,8 @@ fn llvmFieldIndex( } fn firstParamSRet(fn_info: Type.Payload.Function.Data, target: std.Target) bool { + if (!fn_info.return_type.hasRuntimeBitsIgnoreComptime()) return false; + switch (fn_info.cc) { .Unspecified, .Inline => return isByRef(fn_info.return_type), .C => switch (target.cpu.arch) {