Skip to content
Merged
Show file tree
Hide file tree
Changes from 11 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
46 changes: 32 additions & 14 deletions clang/lib/CodeGen/Targets/X86.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1421,12 +1421,12 @@ class WinX86_64ABIInfo : public ABIInfo {
ABIArgInfo classifyArgForArm64ECVarArg(QualType Ty) const override {
unsigned FreeSSERegs = 0;
return classify(Ty, FreeSSERegs, /*IsReturnType=*/false,
/*IsVectorCall=*/false, /*IsRegCall=*/false);
llvm::CallingConv::C);
}

private:
ABIArgInfo classify(QualType Ty, unsigned &FreeSSERegs, bool IsReturnType,
bool IsVectorCall, bool IsRegCall) const;
unsigned CC) const;
ABIArgInfo reclassifyHvaArgForVectorCall(QualType Ty, unsigned &FreeSSERegs,
const ABIArgInfo &current) const;

Expand Down Expand Up @@ -3338,8 +3338,9 @@ ABIArgInfo WinX86_64ABIInfo::reclassifyHvaArgForVectorCall(
}

ABIArgInfo WinX86_64ABIInfo::classify(QualType Ty, unsigned &FreeSSERegs,
bool IsReturnType, bool IsVectorCall,
bool IsRegCall) const {
bool IsReturnType, unsigned CC) const {
bool IsVectorCall = CC == llvm::CallingConv::X86_VectorCall;
bool IsRegCall = CC == llvm::CallingConv::X86_RegCall;

if (Ty->isVoidType())
return ABIArgInfo::getIgnore();
Expand Down Expand Up @@ -3437,8 +3438,6 @@ ABIArgInfo WinX86_64ABIInfo::classify(QualType Ty, unsigned &FreeSSERegs,
case BuiltinType::Int128:
case BuiltinType::UInt128:
case BuiltinType::Float128:
// 128-bit float and integer types share the same ABI.

// If it's a parameter type, the normal ABI rule is that arguments larger
// than 8 bytes are passed indirectly. GCC follows it. We follow it too,
// even though it isn't particularly efficient.
Expand All @@ -3449,10 +3448,31 @@ ABIArgInfo WinX86_64ABIInfo::classify(QualType Ty, unsigned &FreeSSERegs,

// Mingw64 GCC returns i128 in XMM0. Coerce to v2i64 to handle that.
// Clang matches them for compatibility.
// NOTE: GCC actually returns f128 indirectly but will hopefully change.
// See https://gcc.gnu.org/bugzilla/show_bug.cgi?id=115054#c8.
return ABIArgInfo::getDirect(llvm::FixedVectorType::get(
llvm::Type::getInt64Ty(getVMContext()), 2));
if (BT->getKind() == BuiltinType::Int128 ||
BT->getKind() == BuiltinType::UInt128)
return ABIArgInfo::getDirect(llvm::FixedVectorType::get(
llvm::Type::getInt64Ty(getVMContext()), 2));

// Mingw64 GCC returns f128 via sret, and Clang matches that for
// compatibility. This mirrors the X86 backend's CanLowerReturn logic.
if (BT->getKind() == BuiltinType::Float128) {
auto IsWin64F128StackCC = [this](unsigned CC) -> bool {
switch (CC) {
case llvm::CallingConv::Win64:
return true;
case llvm::CallingConv::C:

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

VectorCall is, from what I can find, clang-specific and so I don't think we need to be compatible with anything for it. Leaving it as before is easier.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No, it's this: https://learn.microsoft.com/en-us/cpp/cpp/vectorcall?view=msvc-170

GCC might wish to support it one day, I would consider it to be part of the platform C ABI, even if it does have some quirks that require two iterations over the argument list.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah, sorry yeah I meant that clang is the only compiler that needs to decide what f128 does for that calling convention. And given the name, here it kind of would make sense for GCC to pick passing/returning via vector registers?

In any case, anything we do here is arbitrary, and switching it over is kind of ugly code-wise.

@tgross35 tgross35 Jun 29, 2026

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

And given the name, here it kind of would make sense for GCC to pick passing/returning via vector registers?

I haven't looked at what this code does but my assumption is yes, for vectorcall f128 should ideally be returned in xmm0 (like what LLVM does for the C convention today) and passed that way too (unlike the C convention)

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We currently pass f128 via XMM registers (below xmm0 contains %a, rdx contains %p):

define x86_vectorcallcc void @"\01pass_vectorcall"(fp128 %a, ptr %p) {
; WIN-LABEL: pass_vectorcall:
; WIN:       # %bb.0:
; WIN-NEXT:    movaps %xmm0, (%rdx)
; WIN-NEXT:    retq
;
; LINUX-LABEL: pass_vectorcall:
; LINUX:       # %bb.0:
; LINUX-NEXT:    movaps %xmm0, (%rdx)
; LINUX-NEXT:    retq
  store fp128 %a, ptr %p
  ret void
}

The current code also returns via xmm0 for vectorcall.

So yeah, we can discuss this but it seems quite reasonable? And it can be changed later if there is something we'd want to be compatible with that makes a different choice.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think I agree, if MSVC ever adds f128 support, I strongly suspect they'll choose to return in XMM0, so this is probably the right set of conventions.

return getTarget().getTriple().isOSWindows() ||

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I added Triple::isOSWindowsOrUEFI for this in a recent revision

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Neat, I'm using that now.

getTarget().getTriple().isUEFI();
default:
return false;
}
};

if (IsWin64F128StackCC(CC))
return getNaturalAlignIndirect(
Ty, getDataLayout().getAllocaAddrSpace(), /*ByVal=*/false);
}
break;

default:
break;
Expand Down Expand Up @@ -3498,8 +3518,7 @@ void WinX86_64ABIInfo::computeInfo(CGFunctionInfo &FI) const {
}

if (!getCXXABI().classifyReturnType(FI))
FI.getReturnInfo() = classify(FI.getReturnType(), FreeSSERegs, true,
IsVectorCall, IsRegCall);
FI.getReturnInfo() = classify(FI.getReturnType(), FreeSSERegs, true, CC);

if (IsVectorCall) {
// We can use up to 6 SSE register parameters with vectorcall.
Expand All @@ -3517,8 +3536,7 @@ void WinX86_64ABIInfo::computeInfo(CGFunctionInfo &FI) const {
// registers are left.
unsigned *MaybeFreeSSERegs =
(IsVectorCall && ArgNum >= 6) ? &ZeroSSERegs : &FreeSSERegs;
I.info =
classify(I.type, *MaybeFreeSSERegs, false, IsVectorCall, IsRegCall);
I.info = classify(I.type, *MaybeFreeSSERegs, false, CC);
++ArgNum;
}

Expand Down
15 changes: 13 additions & 2 deletions clang/test/CodeGen/win-fp128.c
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,21 @@
// __float128 is unsupported on MSVC

__float128 fp128_ret(void) { return 0; }
// CHECK-GNU64: define dso_local <2 x i64> @fp128_ret()
// CHECK-GNU64: define dso_local void @fp128_ret(ptr dead_on_unwind noalias writable sret(fp128) align 16 %agg.result)

__float128 fp128_args(__float128 a, __float128 b) { return a * b; }
// CHECK-GNU64: define dso_local <2 x i64> @fp128_args(ptr noundef dead_on_return %0, ptr noundef dead_on_return %1)
// CHECK-GNU64: define dso_local void @fp128_args(ptr dead_on_unwind noalias writable sret(fp128) align 16 %agg.result, ptr noundef dead_on_return %0, ptr noundef dead_on_return %1)

__float128 __attribute__((vectorcall)) fp128_ret_vectorcall(void) { return 0; }
// CHECK-GNU64: define dso_local x86_vectorcallcc fp128 @"\01fp128_ret_vectorcall@@0"()
Comment on lines +11 to +12

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this returns fp128 and then it's up to the backend to figure out what that means. We could force XMM0 with the <2 x i64> trick, I haven't done that yet because it does seem a bit hacky.


__float128 __attribute__((regcall)) fp128_ret_regcall(void) { return 0; }
// CHECK-GNU64: define dso_local x86_regcallcc fp128 @__regcall3__fp128_ret_regcall()

__float128 fp128_callee(void);
__float128 fp128_musttail(void) { [[clang::musttail]] return fp128_callee(); }
// CHECK-GNU64: define dso_local void @fp128_musttail(ptr dead_on_unwind noalias writable sret(fp128) align 16 %agg.result)
// CHECK-GNU64: musttail call void @fp128_callee(ptr dead_on_unwind writable sret(fp128) align 16 %agg.result)

void fp128_vararg(int a, ...) {
// CHECK-GNU64-LABEL: define dso_local void @fp128_vararg
Expand Down
5 changes: 5 additions & 0 deletions llvm/docs/ReleaseNotes.md
Original file line number Diff line number Diff line change
Expand Up @@ -284,6 +284,11 @@ Makes programs 10x faster by doing Special New Thing.
two-register push in Windows x64 V3 unwind info. The directive takes two
register operands: ``.seh_push2regs %r12, %r13``.

* The `fp128` type is now returned via sret instead of XMM0 for some calling
conventions to match GCC. The C and Win64 calling conventions now use
sret, other calling conventions do not need to be compatible with
GCC and still return via XMM0.

### Changes to the OCaml bindings

### Changes to the Python bindings
Expand Down
2 changes: 1 addition & 1 deletion llvm/lib/Target/X86/X86CallingConv.td

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just thinking as somebody who has chased ABI code around - might be worth a note in RetCC_X86_Win64_C that there's an exception for f128 in X86TargetLowering::CanLowerReturn, since that's a bit unconventional.

Original file line number Diff line number Diff line change
Expand Up @@ -391,7 +391,7 @@ def RetCC_X86_Win64_C : CallingConv<[
// X86-64 vectorcall return-value convention.
def RetCC_X86_64_Vectorcall : CallingConv<[
// Vectorcall calling convention always returns FP values in XMMs.
CCIfType<[f32, f64, f128],
CCIfType<[f32, f64, f128],
CCAssignToReg<[XMM0, XMM1, XMM2, XMM3]>>,

// Otherwise, everything is the same as Windows X86-64 C CC.
Expand Down
37 changes: 37 additions & 0 deletions llvm/lib/Target/X86/X86ISelLoweringCall.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -670,6 +670,43 @@ bool X86TargetLowering::CanLowerReturn(
CallingConv::ID CallConv, MachineFunction &MF, bool isVarArg,
const SmallVectorImpl<ISD::OutputArg> &Outs, LLVMContext &Context,
const Type *RetTy) const {
// Mingw64 GCC returns f128 via sret, and LLVM matches it for compatibility.
// This logic exists for libcalls, a frontend should explicitly use sret
// rather than rely on the sret demotion here.
//
// Using sret is a reasonable implementation of the Windows x64 calling
// convention:
//
// https://learn.microsoft.com/en-us/cpp/build/x64-calling-convention?view=msvc-170#return-values
//
// > Otherwise, the caller must allocate memory for the return value and pass
// > a pointer to it as the first argument.
//
// Although it is not the only reasonable interpretation:
//
// > Nonscalar types including floats, doubles, and vector types such as
// > __m128, __m128i, __m128d are returned in XMM0.
//
// For now, we prefer compatibility with GCC. If official guidelines are ever
// published, this can be revisited.
//
// Return false, which will perform sret demotion.
auto IsWin64F128StackCC = [this](CallingConv::ID CC) -> bool {
switch (CC) {
case CallingConv::Win64:
return true;
case CallingConv::C:
return Subtarget.isTargetWin64() || Subtarget.isTargetUEFI64();

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I added a predicate for this just now

default:
return false;
}
};

if (IsWin64F128StackCC(CallConv) &&
llvm::any_of(
Outs, [](const ISD::OutputArg &Out) { return Out.VT == MVT::f128; }))
return false;

SmallVector<CCValAssign, 16> RVLocs;
CCState CCInfo(CallConv, isVarArg, MF, RVLocs, Context);
return CCInfo.CheckReturn(Outs, RetCC_X86);
Expand Down
Loading