Skip to content

[X86][Windows] Return fp128 on the stack#204887

Merged
folkertdev merged 15 commits into
llvm:mainfrom
folkertdev:windows-f128-sret
Jul 1, 2026
Merged

[X86][Windows] Return fp128 on the stack#204887
folkertdev merged 15 commits into
llvm:mainfrom
folkertdev:windows-f128-sret

Conversation

@folkertdev

@folkertdev folkertdev commented Jun 19, 2026

Copy link
Copy Markdown
Contributor

Subsumes #194214

For x86-64 Windows targets, LLVM currently returns fp128 in xmm0. This does match i128 (both Clang and GCC return __int128 in xmm0) but disagrees with GCC's behavior of returning __float128 on the stack.

https://gcc.godbolt.org/z/xnWeGqcbW

Microsoft does not specify a __float128 ABI so any decision is purely an extension. The Windows x64 calling convention 1 does say that user- defined types that do not fit in a register should be returned indirectly, so the GCC behavior seems like a reasonable interpretation of this rule.

Thus, change fp128 to return on the stack for Windows targets. This is done for both MinGW and MSVC targets; if official guidelines are ever published, this can be revisited.

The change is made in LLVM (via sret demotion of f128), but also explicitly in clang. Frontends should emit the sret explicitly, if only for debug info, return value optimization, etc.

Relates to the pass ABI change in 5ee1c0b ("[windows] Always pass fp128 arguments indirectly").

This is in line with mingw64 gcc and follows the win64 CC (at least
more)
Comment thread clang/lib/CodeGen/Targets/X86.cpp Outdated
Comment thread llvm/lib/Target/X86/X86ISelLoweringCall.cpp Outdated
@folkertdev
folkertdev requested review from phoebewang and rnk June 19, 2026 22:08
@folkertdev
folkertdev marked this pull request as ready for review June 19, 2026 22:08
@llvmorg-github-actions llvmorg-github-actions Bot added backend:X86 clang:codegen IR generation bugs: mangling, exceptions, etc. labels Jun 19, 2026
@llvmorg-github-actions

llvmorg-github-actions Bot commented Jun 19, 2026

Copy link
Copy Markdown

@llvm/pr-subscribers-backend-x86

@llvm/pr-subscribers-clang-codegen

Author: Folkert de Vries (folkertdev)

Changes

Subsumes #194214

For x86-64 Windows targets, LLVM currently returns fp128 in xmm0. This does match i128 (both Clang and GCC return __int128 in xmm0) but disagrees with GCC's behavior of returning __float128 on the stack.

https://gcc.godbolt.org/z/xnWeGqcbW

Microsoft does not specify a __float128 ABI so any decision is purely an extension. The Windows x64 calling convention 1 does say that user- defined types that do not fit in a register should be returned indirectly, so the GCC behavior seems like a reasonable interpretation of this rule.

Thus, change fp128 to return on the stack for Windows targets. This is done for both MinGW and MSVC targets; if official guidelines are ever published, this can be revisited.

Relates to the pass ABI change in 5ee1c0b ("[windows] Always pass fp128 arguments indirectly").


Patch is 63.26 KiB, truncated to 20.00 KiB below, full version: https://github.com/llvm/llvm-project/pull/204887.diff

6 Files Affected:

  • (modified) clang/lib/CodeGen/Targets/X86.cpp (+8-6)
  • (modified) clang/test/CodeGen/win-fp128.c (+2-2)
  • (modified) llvm/lib/Target/X86/X86ISelLoweringCall.cpp (+14)
  • (modified) llvm/test/CodeGen/X86/fp128-libcalls-strict.ll (+341-110)
  • (modified) llvm/test/CodeGen/X86/fp128-libcalls.ll (+179-72)
  • (modified) llvm/test/CodeGen/X86/i128-fp128-abi.ll (+80-52)
diff --git a/clang/lib/CodeGen/Targets/X86.cpp b/clang/lib/CodeGen/Targets/X86.cpp
index dbe4d656aabc5..77c912b021604 100644
--- a/clang/lib/CodeGen/Targets/X86.cpp
+++ b/clang/lib/CodeGen/Targets/X86.cpp
@@ -3437,8 +3437,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.
@@ -3449,10 +3447,14 @@ 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. Clang matches that for
+      // compatibility.
+      break;
 
     default:
       break;
diff --git a/clang/test/CodeGen/win-fp128.c b/clang/test/CodeGen/win-fp128.c
index 58e203d4fc8ed..dc144f899fa4f 100644
--- a/clang/test/CodeGen/win-fp128.c
+++ b/clang/test/CodeGen/win-fp128.c
@@ -3,10 +3,10 @@
 // __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 fp128 @fp128_ret()
 
 __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 fp128 @fp128_args(ptr noundef dead_on_return %0, ptr noundef dead_on_return %1)
 
 void fp128_vararg(int a, ...) {
   // CHECK-GNU64-LABEL: define dso_local void @fp128_vararg
diff --git a/llvm/lib/Target/X86/X86ISelLoweringCall.cpp b/llvm/lib/Target/X86/X86ISelLoweringCall.cpp
index 7c068115df481..bce581ad7a48b 100644
--- a/llvm/lib/Target/X86/X86ISelLoweringCall.cpp
+++ b/llvm/lib/Target/X86/X86ISelLoweringCall.cpp
@@ -670,6 +670,20 @@ 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, which matches the documentation 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.
+  //
+  // Return false, which will perform sret demotion.
+  if (Subtarget.isCallingConvWin64(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);
diff --git a/llvm/test/CodeGen/X86/fp128-libcalls-strict.ll b/llvm/test/CodeGen/X86/fp128-libcalls-strict.ll
index ad2d690fd7ed0..dfff88d30bcd4 100644
--- a/llvm/test/CodeGen/X86/fp128-libcalls-strict.ll
+++ b/llvm/test/CodeGen/X86/fp128-libcalls-strict.ll
@@ -79,15 +79,22 @@ define fp128 @add(fp128 %x, fp128 %y) nounwind strictfp {
 ;
 ; WIN-LABEL: add:
 ; WIN:       # %bb.0: # %entry
-; WIN-NEXT:    subq $72, %rsp
-; WIN-NEXT:    movaps (%rcx), %xmm0
-; WIN-NEXT:    movaps (%rdx), %xmm1
+; WIN-NEXT:    pushq %rsi
+; WIN-NEXT:    subq $80, %rsp
+; WIN-NEXT:    movq %rcx, %rsi
+; WIN-NEXT:    movaps (%rdx), %xmm0
+; WIN-NEXT:    movaps (%r8), %xmm1
 ; WIN-NEXT:    movaps %xmm1, {{[0-9]+}}(%rsp)
 ; WIN-NEXT:    movaps %xmm0, {{[0-9]+}}(%rsp)
 ; WIN-NEXT:    leaq {{[0-9]+}}(%rsp), %rcx
 ; WIN-NEXT:    leaq {{[0-9]+}}(%rsp), %rdx
+; WIN-NEXT:    leaq {{[0-9]+}}(%rsp), %r8
 ; WIN-NEXT:    callq __addtf3
-; WIN-NEXT:    addq $72, %rsp
+; WIN-NEXT:    movaps {{[0-9]+}}(%rsp), %xmm0
+; WIN-NEXT:    movaps %xmm0, (%rsi)
+; WIN-NEXT:    movq %rsi, %rax
+; WIN-NEXT:    addq $80, %rsp
+; WIN-NEXT:    popq %rsi
 ; WIN-NEXT:    retq
 ;
 ; WIN-X86-LABEL: add:
@@ -201,15 +208,22 @@ define fp128 @sub(fp128 %x, fp128 %y) nounwind strictfp {
 ;
 ; WIN-LABEL: sub:
 ; WIN:       # %bb.0: # %entry
-; WIN-NEXT:    subq $72, %rsp
-; WIN-NEXT:    movaps (%rcx), %xmm0
-; WIN-NEXT:    movaps (%rdx), %xmm1
+; WIN-NEXT:    pushq %rsi
+; WIN-NEXT:    subq $80, %rsp
+; WIN-NEXT:    movq %rcx, %rsi
+; WIN-NEXT:    movaps (%rdx), %xmm0
+; WIN-NEXT:    movaps (%r8), %xmm1
 ; WIN-NEXT:    movaps %xmm1, {{[0-9]+}}(%rsp)
 ; WIN-NEXT:    movaps %xmm0, {{[0-9]+}}(%rsp)
 ; WIN-NEXT:    leaq {{[0-9]+}}(%rsp), %rcx
 ; WIN-NEXT:    leaq {{[0-9]+}}(%rsp), %rdx
+; WIN-NEXT:    leaq {{[0-9]+}}(%rsp), %r8
 ; WIN-NEXT:    callq __subtf3
-; WIN-NEXT:    addq $72, %rsp
+; WIN-NEXT:    movaps {{[0-9]+}}(%rsp), %xmm0
+; WIN-NEXT:    movaps %xmm0, (%rsi)
+; WIN-NEXT:    movq %rsi, %rax
+; WIN-NEXT:    addq $80, %rsp
+; WIN-NEXT:    popq %rsi
 ; WIN-NEXT:    retq
 ;
 ; WIN-X86-LABEL: sub:
@@ -323,15 +337,22 @@ define fp128 @mul(fp128 %x, fp128 %y) nounwind strictfp {
 ;
 ; WIN-LABEL: mul:
 ; WIN:       # %bb.0: # %entry
-; WIN-NEXT:    subq $72, %rsp
-; WIN-NEXT:    movaps (%rcx), %xmm0
-; WIN-NEXT:    movaps (%rdx), %xmm1
+; WIN-NEXT:    pushq %rsi
+; WIN-NEXT:    subq $80, %rsp
+; WIN-NEXT:    movq %rcx, %rsi
+; WIN-NEXT:    movaps (%rdx), %xmm0
+; WIN-NEXT:    movaps (%r8), %xmm1
 ; WIN-NEXT:    movaps %xmm1, {{[0-9]+}}(%rsp)
 ; WIN-NEXT:    movaps %xmm0, {{[0-9]+}}(%rsp)
 ; WIN-NEXT:    leaq {{[0-9]+}}(%rsp), %rcx
 ; WIN-NEXT:    leaq {{[0-9]+}}(%rsp), %rdx
+; WIN-NEXT:    leaq {{[0-9]+}}(%rsp), %r8
 ; WIN-NEXT:    callq __multf3
-; WIN-NEXT:    addq $72, %rsp
+; WIN-NEXT:    movaps {{[0-9]+}}(%rsp), %xmm0
+; WIN-NEXT:    movaps %xmm0, (%rsi)
+; WIN-NEXT:    movq %rsi, %rax
+; WIN-NEXT:    addq $80, %rsp
+; WIN-NEXT:    popq %rsi
 ; WIN-NEXT:    retq
 ;
 ; WIN-X86-LABEL: mul:
@@ -445,15 +466,22 @@ define fp128 @div(fp128 %x, fp128 %y) nounwind strictfp {
 ;
 ; WIN-LABEL: div:
 ; WIN:       # %bb.0: # %entry
-; WIN-NEXT:    subq $72, %rsp
-; WIN-NEXT:    movaps (%rcx), %xmm0
-; WIN-NEXT:    movaps (%rdx), %xmm1
+; WIN-NEXT:    pushq %rsi
+; WIN-NEXT:    subq $80, %rsp
+; WIN-NEXT:    movq %rcx, %rsi
+; WIN-NEXT:    movaps (%rdx), %xmm0
+; WIN-NEXT:    movaps (%r8), %xmm1
 ; WIN-NEXT:    movaps %xmm1, {{[0-9]+}}(%rsp)
 ; WIN-NEXT:    movaps %xmm0, {{[0-9]+}}(%rsp)
 ; WIN-NEXT:    leaq {{[0-9]+}}(%rsp), %rcx
 ; WIN-NEXT:    leaq {{[0-9]+}}(%rsp), %rdx
+; WIN-NEXT:    leaq {{[0-9]+}}(%rsp), %r8
 ; WIN-NEXT:    callq __divtf3
-; WIN-NEXT:    addq $72, %rsp
+; WIN-NEXT:    movaps {{[0-9]+}}(%rsp), %xmm0
+; WIN-NEXT:    movaps %xmm0, (%rsi)
+; WIN-NEXT:    movq %rsi, %rax
+; WIN-NEXT:    addq $80, %rsp
+; WIN-NEXT:    popq %rsi
 ; WIN-NEXT:    retq
 ;
 ; WIN-X86-LABEL: div:
@@ -568,18 +596,25 @@ define fp128 @fma(fp128 %x, fp128 %y, fp128 %z) nounwind strictfp {
 ;
 ; WIN-LABEL: fma:
 ; WIN:       # %bb.0: # %entry
-; WIN-NEXT:    subq $88, %rsp
-; WIN-NEXT:    movaps (%r8), %xmm0
-; WIN-NEXT:    movaps (%rcx), %xmm1
-; WIN-NEXT:    movaps (%rdx), %xmm2
+; WIN-NEXT:    pushq %rsi
+; WIN-NEXT:    subq $96, %rsp
+; WIN-NEXT:    movq %rcx, %rsi
+; WIN-NEXT:    movaps (%r9), %xmm0
+; WIN-NEXT:    movaps (%rdx), %xmm1
+; WIN-NEXT:    movaps (%r8), %xmm2
 ; WIN-NEXT:    movaps %xmm2, {{[0-9]+}}(%rsp)
 ; WIN-NEXT:    movaps %xmm1, {{[0-9]+}}(%rsp)
 ; WIN-NEXT:    movaps %xmm0, {{[0-9]+}}(%rsp)
 ; WIN-NEXT:    leaq {{[0-9]+}}(%rsp), %rcx
 ; WIN-NEXT:    leaq {{[0-9]+}}(%rsp), %rdx
 ; WIN-NEXT:    leaq {{[0-9]+}}(%rsp), %r8
+; WIN-NEXT:    leaq {{[0-9]+}}(%rsp), %r9
 ; WIN-NEXT:    callq fmal
-; WIN-NEXT:    addq $88, %rsp
+; WIN-NEXT:    movaps {{[0-9]+}}(%rsp), %xmm0
+; WIN-NEXT:    movaps %xmm0, (%rsi)
+; WIN-NEXT:    movq %rsi, %rax
+; WIN-NEXT:    addq $96, %rsp
+; WIN-NEXT:    popq %rsi
 ; WIN-NEXT:    retq
 ;
 ; WIN-X86-LABEL: fma:
@@ -694,15 +729,22 @@ define fp128 @frem(fp128 %x, fp128 %y) nounwind strictfp {
 ;
 ; WIN-LABEL: frem:
 ; WIN:       # %bb.0: # %entry
-; WIN-NEXT:    subq $72, %rsp
-; WIN-NEXT:    movaps (%rcx), %xmm0
-; WIN-NEXT:    movaps (%rdx), %xmm1
+; WIN-NEXT:    pushq %rsi
+; WIN-NEXT:    subq $80, %rsp
+; WIN-NEXT:    movq %rcx, %rsi
+; WIN-NEXT:    movaps (%rdx), %xmm0
+; WIN-NEXT:    movaps (%r8), %xmm1
 ; WIN-NEXT:    movaps %xmm1, {{[0-9]+}}(%rsp)
 ; WIN-NEXT:    movaps %xmm0, {{[0-9]+}}(%rsp)
 ; WIN-NEXT:    leaq {{[0-9]+}}(%rsp), %rcx
 ; WIN-NEXT:    leaq {{[0-9]+}}(%rsp), %rdx
+; WIN-NEXT:    leaq {{[0-9]+}}(%rsp), %r8
 ; WIN-NEXT:    callq fmodl
-; WIN-NEXT:    addq $72, %rsp
+; WIN-NEXT:    movaps {{[0-9]+}}(%rsp), %xmm0
+; WIN-NEXT:    movaps %xmm0, (%rsi)
+; WIN-NEXT:    movq %rsi, %rax
+; WIN-NEXT:    addq $80, %rsp
+; WIN-NEXT:    popq %rsi
 ; WIN-NEXT:    retq
 ;
 ; WIN-X86-LABEL: frem:
@@ -797,12 +839,19 @@ define fp128 @ceil(fp128 %x) nounwind strictfp {
 ;
 ; WIN-LABEL: ceil:
 ; WIN:       # %bb.0: # %entry
-; WIN-NEXT:    subq $56, %rsp
-; WIN-NEXT:    movaps (%rcx), %xmm0
+; WIN-NEXT:    pushq %rsi
+; WIN-NEXT:    subq $64, %rsp
+; WIN-NEXT:    movq %rcx, %rsi
+; WIN-NEXT:    movaps (%rdx), %xmm0
 ; WIN-NEXT:    movaps %xmm0, {{[0-9]+}}(%rsp)
 ; WIN-NEXT:    leaq {{[0-9]+}}(%rsp), %rcx
+; WIN-NEXT:    leaq {{[0-9]+}}(%rsp), %rdx
 ; WIN-NEXT:    callq ceill
-; WIN-NEXT:    addq $56, %rsp
+; WIN-NEXT:    movaps {{[0-9]+}}(%rsp), %xmm0
+; WIN-NEXT:    movaps %xmm0, (%rsi)
+; WIN-NEXT:    movq %rsi, %rax
+; WIN-NEXT:    addq $64, %rsp
+; WIN-NEXT:    popq %rsi
 ; WIN-NEXT:    retq
 ;
 ; WIN-X86-LABEL: ceil:
@@ -887,12 +936,19 @@ define fp128 @acos(fp128 %x) nounwind strictfp {
 ;
 ; WIN-LABEL: acos:
 ; WIN:       # %bb.0: # %entry
-; WIN-NEXT:    subq $56, %rsp
-; WIN-NEXT:    movaps (%rcx), %xmm0
+; WIN-NEXT:    pushq %rsi
+; WIN-NEXT:    subq $64, %rsp
+; WIN-NEXT:    movq %rcx, %rsi
+; WIN-NEXT:    movaps (%rdx), %xmm0
 ; WIN-NEXT:    movaps %xmm0, {{[0-9]+}}(%rsp)
 ; WIN-NEXT:    leaq {{[0-9]+}}(%rsp), %rcx
+; WIN-NEXT:    leaq {{[0-9]+}}(%rsp), %rdx
 ; WIN-NEXT:    callq acosl
-; WIN-NEXT:    addq $56, %rsp
+; WIN-NEXT:    movaps {{[0-9]+}}(%rsp), %xmm0
+; WIN-NEXT:    movaps %xmm0, (%rsi)
+; WIN-NEXT:    movq %rsi, %rax
+; WIN-NEXT:    addq $64, %rsp
+; WIN-NEXT:    popq %rsi
 ; WIN-NEXT:    retq
 ;
 ; WIN-X86-LABEL: acos:
@@ -977,12 +1033,19 @@ define fp128 @cos(fp128 %x) nounwind strictfp {
 ;
 ; WIN-LABEL: cos:
 ; WIN:       # %bb.0: # %entry
-; WIN-NEXT:    subq $56, %rsp
-; WIN-NEXT:    movaps (%rcx), %xmm0
+; WIN-NEXT:    pushq %rsi
+; WIN-NEXT:    subq $64, %rsp
+; WIN-NEXT:    movq %rcx, %rsi
+; WIN-NEXT:    movaps (%rdx), %xmm0
 ; WIN-NEXT:    movaps %xmm0, {{[0-9]+}}(%rsp)
 ; WIN-NEXT:    leaq {{[0-9]+}}(%rsp), %rcx
+; WIN-NEXT:    leaq {{[0-9]+}}(%rsp), %rdx
 ; WIN-NEXT:    callq cosl
-; WIN-NEXT:    addq $56, %rsp
+; WIN-NEXT:    movaps {{[0-9]+}}(%rsp), %xmm0
+; WIN-NEXT:    movaps %xmm0, (%rsi)
+; WIN-NEXT:    movq %rsi, %rax
+; WIN-NEXT:    addq $64, %rsp
+; WIN-NEXT:    popq %rsi
 ; WIN-NEXT:    retq
 ;
 ; WIN-X86-LABEL: cos:
@@ -1067,12 +1130,19 @@ define fp128 @cosh(fp128 %x) nounwind strictfp {
 ;
 ; WIN-LABEL: cosh:
 ; WIN:       # %bb.0: # %entry
-; WIN-NEXT:    subq $56, %rsp
-; WIN-NEXT:    movaps (%rcx), %xmm0
+; WIN-NEXT:    pushq %rsi
+; WIN-NEXT:    subq $64, %rsp
+; WIN-NEXT:    movq %rcx, %rsi
+; WIN-NEXT:    movaps (%rdx), %xmm0
 ; WIN-NEXT:    movaps %xmm0, {{[0-9]+}}(%rsp)
 ; WIN-NEXT:    leaq {{[0-9]+}}(%rsp), %rcx
+; WIN-NEXT:    leaq {{[0-9]+}}(%rsp), %rdx
 ; WIN-NEXT:    callq coshl
-; WIN-NEXT:    addq $56, %rsp
+; WIN-NEXT:    movaps {{[0-9]+}}(%rsp), %xmm0
+; WIN-NEXT:    movaps %xmm0, (%rsi)
+; WIN-NEXT:    movq %rsi, %rax
+; WIN-NEXT:    addq $64, %rsp
+; WIN-NEXT:    popq %rsi
 ; WIN-NEXT:    retq
 ;
 ; WIN-X86-LABEL: cosh:
@@ -1157,12 +1227,19 @@ define fp128 @exp(fp128 %x) nounwind strictfp {
 ;
 ; WIN-LABEL: exp:
 ; WIN:       # %bb.0: # %entry
-; WIN-NEXT:    subq $56, %rsp
-; WIN-NEXT:    movaps (%rcx), %xmm0
+; WIN-NEXT:    pushq %rsi
+; WIN-NEXT:    subq $64, %rsp
+; WIN-NEXT:    movq %rcx, %rsi
+; WIN-NEXT:    movaps (%rdx), %xmm0
 ; WIN-NEXT:    movaps %xmm0, {{[0-9]+}}(%rsp)
 ; WIN-NEXT:    leaq {{[0-9]+}}(%rsp), %rcx
+; WIN-NEXT:    leaq {{[0-9]+}}(%rsp), %rdx
 ; WIN-NEXT:    callq expl
-; WIN-NEXT:    addq $56, %rsp
+; WIN-NEXT:    movaps {{[0-9]+}}(%rsp), %xmm0
+; WIN-NEXT:    movaps %xmm0, (%rsi)
+; WIN-NEXT:    movq %rsi, %rax
+; WIN-NEXT:    addq $64, %rsp
+; WIN-NEXT:    popq %rsi
 ; WIN-NEXT:    retq
 ;
 ; WIN-X86-LABEL: exp:
@@ -1247,12 +1324,19 @@ define fp128 @exp2(fp128 %x) nounwind strictfp {
 ;
 ; WIN-LABEL: exp2:
 ; WIN:       # %bb.0: # %entry
-; WIN-NEXT:    subq $56, %rsp
-; WIN-NEXT:    movaps (%rcx), %xmm0
+; WIN-NEXT:    pushq %rsi
+; WIN-NEXT:    subq $64, %rsp
+; WIN-NEXT:    movq %rcx, %rsi
+; WIN-NEXT:    movaps (%rdx), %xmm0
 ; WIN-NEXT:    movaps %xmm0, {{[0-9]+}}(%rsp)
 ; WIN-NEXT:    leaq {{[0-9]+}}(%rsp), %rcx
+; WIN-NEXT:    leaq {{[0-9]+}}(%rsp), %rdx
 ; WIN-NEXT:    callq exp2l
-; WIN-NEXT:    addq $56, %rsp
+; WIN-NEXT:    movaps {{[0-9]+}}(%rsp), %xmm0
+; WIN-NEXT:    movaps %xmm0, (%rsi)
+; WIN-NEXT:    movq %rsi, %rax
+; WIN-NEXT:    addq $64, %rsp
+; WIN-NEXT:    popq %rsi
 ; WIN-NEXT:    retq
 ;
 ; WIN-X86-LABEL: exp2:
@@ -1337,12 +1421,19 @@ define fp128 @floor(fp128 %x) nounwind strictfp {
 ;
 ; WIN-LABEL: floor:
 ; WIN:       # %bb.0: # %entry
-; WIN-NEXT:    subq $56, %rsp
-; WIN-NEXT:    movaps (%rcx), %xmm0
+; WIN-NEXT:    pushq %rsi
+; WIN-NEXT:    subq $64, %rsp
+; WIN-NEXT:    movq %rcx, %rsi
+; WIN-NEXT:    movaps (%rdx), %xmm0
 ; WIN-NEXT:    movaps %xmm0, {{[0-9]+}}(%rsp)
 ; WIN-NEXT:    leaq {{[0-9]+}}(%rsp), %rcx
+; WIN-NEXT:    leaq {{[0-9]+}}(%rsp), %rdx
 ; WIN-NEXT:    callq floorl
-; WIN-NEXT:    addq $56, %rsp
+; WIN-NEXT:    movaps {{[0-9]+}}(%rsp), %xmm0
+; WIN-NEXT:    movaps %xmm0, (%rsi)
+; WIN-NEXT:    movq %rsi, %rax
+; WIN-NEXT:    addq $64, %rsp
+; WIN-NEXT:    popq %rsi
 ; WIN-NEXT:    retq
 ;
 ; WIN-X86-LABEL: floor:
@@ -1427,12 +1518,19 @@ define fp128 @log(fp128 %x) nounwind strictfp {
 ;
 ; WIN-LABEL: log:
 ; WIN:       # %bb.0: # %entry
-; WIN-NEXT:    subq $56, %rsp
-; WIN-NEXT:    movaps (%rcx), %xmm0
+; WIN-NEXT:    pushq %rsi
+; WIN-NEXT:    subq $64, %rsp
+; WIN-NEXT:    movq %rcx, %rsi
+; WIN-NEXT:    movaps (%rdx), %xmm0
 ; WIN-NEXT:    movaps %xmm0, {{[0-9]+}}(%rsp)
 ; WIN-NEXT:    leaq {{[0-9]+}}(%rsp), %rcx
+; WIN-NEXT:    leaq {{[0-9]+}}(%rsp), %rdx
 ; WIN-NEXT:    callq logl
-; WIN-NEXT:    addq $56, %rsp
+; WIN-NEXT:    movaps {{[0-9]+}}(%rsp), %xmm0
+; WIN-NEXT:    movaps %xmm0, (%rsi)
+; WIN-NEXT:    movq %rsi, %rax
+; WIN-NEXT:    addq $64, %rsp
+; WIN-NEXT:    popq %rsi
 ; WIN-NEXT:    retq
 ;
 ; WIN-X86-LABEL: log:
@@ -1517,12 +1615,19 @@ define fp128 @log10(fp128 %x) nounwind strictfp {
 ;
 ; WIN-LABEL: log10:
 ; WIN:       # %bb.0: # %entry
-; WIN-NEXT:    subq $56, %rsp
-; WIN-NEXT:    movaps (%rcx), %xmm0
+; WIN-NEXT:    pushq %rsi
+; WIN-NEXT:    subq $64, %rsp
+; WIN-NEXT:    movq %rcx, %rsi
+; WIN-NEXT:    movaps (%rdx), %xmm0
 ; WIN-NEXT:    movaps %xmm0, {{[0-9]+}}(%rsp)
 ; WIN-NEXT:    leaq {{[0-9]+}}(%rsp), %rcx
+; WIN-NEXT:    leaq {{[0-9]+}}(%rsp), %rdx
 ; WIN-NEXT:    callq log10l
-; WIN-NEXT:    addq $56, %rsp
+; WIN-NEXT:    movaps {{[0-9]+}}(%rsp), %xmm0
+; WIN-NEXT:    movaps %xmm0, (%rsi)
+; WIN-NEXT:    movq %rsi, %rax
+; WIN-NEXT:    addq $64, %rsp
+; WIN-NEXT:    popq %rsi
 ; WIN-NEXT:    retq
 ;
 ; WIN-X86-LABEL: log10:
@@ -1607,12 +1712,19 @@ define fp128 @log2(fp128 %x) nounwind strictfp {
 ;
 ; WIN-LABEL: log2:
 ; WIN:       # %bb.0: # %entry
-; WIN-NEXT:    subq $56, %rsp
-; WIN-NEXT:    movaps (%rcx), %xmm0
+; WIN-NEXT:    pushq %rsi
+; WIN-NEXT:    subq $64, %rsp
+; WIN-NEXT:    movq %rcx, %rsi
+; WIN-NEXT:    movaps (%rdx), %xmm0
 ; WIN-NEXT:    movaps %xmm0, {{[0-9]+}}(%rsp)
 ; WIN-NEXT:    leaq {{[0-9]+}}(%rsp), %rcx
+; WIN-NEXT:    leaq {{[0-9]+}}(%rsp), %rdx
 ; WIN-NEXT:    callq log2l
-; WIN-NEXT:    addq $56, %rsp
+; WIN-NEXT:    movaps {{[0-9]+}}(%rsp), %xmm0
+; WIN-NEXT:    movaps %xmm0, (%rsi)
+; WIN-NEXT:    movq %rsi, %rax
+; WIN-NEXT:    addq $64, %rsp
+; WIN-NEXT:    popq %rsi
 ; WIN-NEXT:    retq
 ;
 ; WIN-X86-LABEL: log2:
@@ -1709,15 +1821,22 @@ define fp128 @maxnum(fp128 %x, fp128 %y) nounwind strictfp {
 ;
 ; WIN-LABEL: maxnum:
 ; WIN:       # %bb.0: # %entry
-; WIN-NEXT:    subq $72, %rsp
-; WIN-NEXT:    movaps (%rcx), %xmm0
-; WIN-NEXT:    movaps (%rdx), %xmm1
+; WIN-NEXT:    pushq %rsi
+; WIN-NEXT:    subq $80, %rsp
+; WIN-NEXT:    movq %rcx, %rsi
+; WIN-NEXT:    movaps (%rdx), %xmm0
+; WIN-NEXT:    movaps (%r8), %xmm1
 ; WIN-NEXT:    movaps %xmm1, {{[0-9]+}}(%rsp)
 ; WIN-NEXT:    movaps %xmm0, {{[0-9]+}}(%rsp)
 ; WIN-NEXT:    leaq {{[0-9]+}}(%rsp), %rcx
 ; WIN-NEXT:    leaq {{[0-9]+}}(%rsp), %rdx
+; WIN-NEXT:    leaq {{[0-9]+}}(%rsp), %r8
 ; WIN-NEXT:    callq fmaxl
-; WIN-NEXT:    addq $72, %rsp
+; WIN-NEXT:    movaps {{[0-9]+}}(%rsp), %xmm0
+; WIN-NEXT:    movaps %xmm0, (%rsi)
+; WIN-NEXT:    movq %rsi, %rax
+; WIN-NEXT:    addq $80, %rsp
+; WIN-NEXT:    popq %rsi
 ; WIN-NEXT:    retq
 ;
 ; WIN-X86-LABEL: maxnum:
@@ -1824,15 +1943,22 @@ define fp128 @minnum(fp128 %x, fp128 %y) nounwind strictfp {
 ;
 ; WIN-LABEL: minnum:
 ; WIN:       # %bb.0: # %entry
-; WIN-NEXT:    subq $72, %rsp
-; WIN-NEXT:    movaps (%rcx), %xmm0
-; WIN-NEXT:    movaps (%rdx), %xmm1
+; WIN-NEXT:    pushq %rsi
+; WIN-NEXT:    subq $80, %rsp
+; WIN-NEXT:    movq %rcx, %rsi
+; WIN-NEXT:    movaps (%rdx), %xmm0
+; WIN-NEXT:    movaps (%r8), %xmm1
 ; WIN-NEXT:    movaps %xmm1, {{[0-9]+}}(%rsp)
 ; WIN-NEXT:    movaps %xmm0, {{[0-9]+}}(%rsp)
 ; WIN-NEXT:    leaq {{[0-9]+}}(%rsp), %rcx
 ; WIN-NEXT:    leaq {{[0-9]+}}(%rsp), %rdx
+; WIN-NEXT:    leaq {{[0-9]+}}(%rsp), %r8
 ; WIN-NEXT:    callq fminl
-; WIN-NEXT:    addq $72, %rsp
+; WIN-NEXT:    movaps {{[0-9]+}}(%rsp), %xmm0
+; WIN-NEXT:    movaps %xmm0, (%rsi)
+; WIN-NEXT:    movq %rsi, %rax
+; WIN-NEXT:    addq $80, %rsp
+; WIN-NEXT:    popq %rsi
 ; WIN-NEXT:    retq
 ;
 ; WIN-X86-LABEL: minnum:
@@ -1927,12 +2053,19 @@ define fp128 @nearbyint(fp128 %x) nounwind strictfp {
 ;
 ; WIN-LABEL: nearbyint:
 ; WIN:       # %bb.0: # %entry
-; WIN-NEXT:    subq $56, %rsp
-; WIN-NEXT:    movaps (%rcx), %xmm0
+; WIN-NEXT:    pushq %rsi
+; WIN-NEXT:    subq $64, %rsp
+; WIN-NEXT:    movq %rcx, %rsi
+; WIN-NEXT:    movaps (%rdx), %xmm0
 ; WIN-NEXT:    movaps %xmm0, {{[0-9]+}}(%rsp)
 ; WIN-NEXT:    leaq {{[0-9]+}}(%rsp), %rcx
+; WIN-NEXT:    leaq {{[0-9]+}}(%rsp), %rdx
 ; WIN-NEXT:    callq nearbyintl
-; WIN-NEXT:    addq $56, %rsp
+; WIN-NEXT:    movaps {{[0-9]+}}(%rsp), %xmm0
+; WIN-NEXT:    movaps %xmm0, (%rsi)
+; WIN-NEXT:    movq %rsi, %rax
+; WIN-NEXT:    addq $64, %rsp
+; WIN-NEXT:    popq %rsi
 ; WIN-NEXT:    retq
 ;
 ; WIN-X86-LABEL: nearbyint:
@@ -2029,15 +2162,22 @@ define fp128 @pow(fp128 %x, fp128 %y) nounwind strictfp {
 ;
 ; WIN-LABEL: pow:
 ; WIN:       # %bb.0: # %entry
-; WIN-NEXT:    subq $72, %rsp
-; WIN-NEXT:    movaps (%rcx), %xmm0
-; WIN-NEXT:    movaps (%rdx), %xmm1
+; WIN-NEXT:    pushq %rsi
+; WIN-NEXT:    subq $80, %rsp
+; WIN-NEXT:    movq %rcx, %rsi
+; WIN-NEXT:    movaps (%rdx), %xmm0
+; WIN-NEXT:    movaps (%r8), %xmm1
 ; WIN-NEXT:    movaps %xmm1, {{[0-9]+}}(%rsp)
 ; WIN-NEXT:    movaps %xmm0, {{[0-9]+}}(%rsp)
 ; WIN-NEXT:    leaq {{[0-9]+}}(%rsp), %rcx
 ; WIN-NEXT:    leaq {{[0-9]+}}(%rsp), %rdx
+; WIN-NEXT:    leaq {{[0-9]+}}(%rsp), %r8
 ; WIN-NEXT:    callq powl
-; WIN-NEXT:    addq $72, %rsp
+; WIN-NEXT:    movaps {{[0-9]+}}(%rsp), %xmm0
+; WIN-NEXT:    movaps %xmm0, (%rsi)
+; WIN-NEXT:    movq %rsi, %rax
+; WIN-NEXT:    addq $80, %rsp
+; WIN-NEXT:    popq %rsi
 ; WIN-NEXT:    retq
 ;
 ; WIN-X86-LABEL: pow:
@@ -2143,12 +2283,19 @@ define fp128 @powi(fp128 %x, i32 %y) nounwind strictfp {
 ;
 ; WIN-LABEL: powi:
 ; WIN:       # %bb.0: # %entry
-; WIN-NEXT:    subq $56, %rsp
-; WIN-NEXT:   ...
[truncated]

@tgross35

Copy link
Copy Markdown
Contributor

Copying my reply to @Andarwinux on #19214 for some more context on reasoning:

Since ABI doesn’t specify this, why choose the less efficient approach? Why can’t make gcc match clang’s behavior?

That was my initial plan for a long time, which I noted in the linked commit. But I talked with a handful of people and got the impression that the proposed behavior here (return on the stack) is likely a closer interpretation of the MSVC ABI's handling of fundamental types than returning in xmm0. This also applies to i128 but it is much more set in stone (based on some light GCC archaeology I got the feeling that returning __int128 in xmm0 is not really intentional, but rather a carryover of __m128).

If that's the case then I don't think the performance difference is especially concerning. The type already needs to be passed in memory and even a simple f128 addition is ~300 instructions (~50 for the shortest nontrivial fast path), an extra movups won't matter much.

I'll close https://gcc.gnu.org/bugzilla/show_bug.cgi?id=115054 once this merges.

@tgross35 tgross35 left a comment

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.

Codegen changes look correct to me

Comment thread clang/lib/CodeGen/Targets/X86.cpp Outdated
Comment thread llvm/lib/Target/X86/X86ISelLoweringCall.cpp Outdated
Comment thread llvm/lib/Target/X86/X86ISelLoweringCall.cpp Outdated

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.

MSVC and MinGW should be matching for these types, so I believe you could merge CHECK-MSVC64-F128 and CHECK-MINGW-F128 into CHECK-WIN64-F128 (same for i128).

Could also be separate since I think this was true before this PR

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.

I'll leave it for now, that would make this harder to review

@tgross35 tgross35 left a comment

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.

Obviously needs review from somebody else but this all LGTM, assuming CanLowerReturn is the way to get around tablegen not working.

@rnk rnk left a comment

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.

We need to think about how to manage the ABI change. At a minimum, we should announce the change with a release note, but I'll elaborate.

There are two interrelated changes here:

  1. Clang-side
  2. LLVM-side

Part of me wants to say: let's make this a clang-only change. Then we can respect the -fclang-abi=N flag, and clients who are affected by the ABI break can pin back to the old version. This requires LLVM to retain the behavior of passing f128 in XMM0.

Then we turn our attention to libclang_rt.builtins and the backend library call emission. Returning false for CanLowerReturn activates the sret demotion path, but I think that codepath is generally understood to be non-loadbearing for ABI compatibility purposes, i.e. once we get on that codepath, the compiler can do anything, so long as it handshakes with LLVM itself.

A different route would be to try to generalize the compiler runtime call so that it directly emits an sret call, rather than relying on the demotion.

To conclude, if you go this way, where you keep the change in the frontend and runtime call emission, you give the IR generator maximum control. They get to pick between implicit sret and XMM0, and they get to manage their ABI transition.

WDYT?

Comment thread llvm/lib/Target/X86/X86ISelLoweringCall.cpp Outdated
@rnk

rnk commented Jun 22, 2026

Copy link
Copy Markdown
Contributor

To conclude, if you go this way, where you keep the change in the frontend and runtime call emission, you give the IR generator maximum control. They get to pick between implicit sret and XMM0, and they get to manage their ABI transition.

The obvious tradeoff here is that when you give frontends control, ABI compatibility becomes their problem, and people really hate this. Rust developers really love rediscovering all the ugly ABI lowering corner cases we've run into over the years. :p

@tgross35

Copy link
Copy Markdown
Contributor

Part of me wants to say: let's make this a clang-only change. Then we can respect the -fclang-abi=N flag, and clients who are affected by the ABI break can pin back to the old version. This requires LLVM to retain the behavior of passing f128 in XMM0.

I don't think this will work on its own because libcalls use LLVM's ABI. It's also doesn't seem worth any effort to do a gentle migration; because of the ABI mismatch, f128 is effectively broken if you're linking with libgcc so I'm a bit skeptical there are many people relying on this.

(+1 to relnotes anyway)

Rust developers really love rediscovering all the ugly ABI lowering corner cases we've run into over the years. :p

Not sure if you're already aware or not, but Folkert and I are the Rust developers trying to figure out all the f128 ABI problems :) hence this PR, Windows is one of the few remaining popular platforms that's currently unusable.

Comment thread llvm/lib/Target/X86/X86ISelLoweringCall.cpp Outdated
Comment thread llvm/lib/Target/X86/X86CallingConv.td Outdated
Comment thread llvm/lib/Target/X86/X86CallingConv.td
@folkertdev
folkertdev requested a review from rnk June 23, 2026 08:54
@rnk

rnk commented Jun 27, 2026

Copy link
Copy Markdown
Contributor

I don't think this will work on its own because libcalls use LLVM's ABI.

Well, we have lots examples of custom lowered library calls, like X86TargetLowering::LowerI128_TO_FP (sp).

I think my hesitance was founded on a a really hazy recollection of this 2009 commit: 9f34406a9048

That's where this codepath comes from, and the test case provided suggests that the use case is for returning large structs from fastcc functions. I inferred that this code was never intended to match any other compiler's ABI, and it should not be trusted. Up until that point, frontends always had to explicitly lower structs too large to be returned in registers with sret pointers, or the backend would abort, and that is still the safest way to be sure everything matches.

However, over the years, research shows that it has become ABI load-bearing. You can read the GPT research report here: https://gist.github.com/rnk/9a63ccc03bd3d0188d653dababbe8889

Long story short, I had a long conversation with a computer and convinced myself we should just use this codepath.

It's also doesn't seem worth any effort to do a gentle migration; because of the ABI mismatch, f128 is effectively broken if you're linking with libgcc so I'm a bit skeptical there are many people relying on this.

(+1 to relnotes anyway)

What I'm worried about going wrong is clang-cl calling compiler-rt builtins in libclang_rt.builtins, if there is skew between the compiler and the runtime. Chromium and all its derivatives ship that, but they don't have a stable ABI, so that probably doesn't matter.

Rust developers really love rediscovering all the ugly ABI lowering corner cases we've run into over the years. :p

Not sure if you're already aware or not, but Folkert and I are the Rust developers trying to figure out all the f128 ABI problems :) hence this PR, Windows is one of the few remaining popular platforms that's currently unusable.

Maybe one day 128 FP bits won't be enough, and some poor soul will come along and ask, "why didn't these compiler engineers think about the fp256 ABI!? so short-sighted!"

Comment thread clang/test/CodeGen/win-fp128.c Outdated
Comment thread llvm/lib/Target/X86/X86ISelLoweringCall.cpp Outdated
actually vectorcall is clang-specific so we don't need to be compatible there
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.

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

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.

@folkertdev
folkertdev requested a review from rnk June 27, 2026 17:06
Comment thread llvm/docs/ReleaseNotes.md Outdated
Comment thread llvm/test/CodeGen/X86/fp128-return-calling-conv.ll Outdated
Comment thread llvm/test/CodeGen/X86/fp128-return-calling-conv.ll Outdated

@rnk rnk left a comment

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.

Minor code golf fix, otherwise, let's ship it.

Comment thread clang/lib/CodeGen/Targets/X86.cpp Outdated
case llvm::CallingConv::Win64:
return true;
case llvm::CallingConv::C:
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.

switch (CC) {
case llvm::CallingConv::Win64:
return true;
case llvm::CallingConv::C:

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.

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

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.

@folkertdev
folkertdev merged commit aa47e59 into llvm:main Jul 1, 2026
12 checks passed
maarcosrmz pushed a commit to maarcosrmz/llvm-project that referenced this pull request Jul 1, 2026
Subsumes llvm#194214

For x86-64 Windows targets, LLVM currently returns `fp128` in xmm0. This
does match `i128` (both Clang and GCC return `__int128` in xmm0) but
disagrees with GCC's behavior of returning `__float128` on the stack.

https://gcc.godbolt.org/z/xnWeGqcbW

Microsoft does not specify a `__float128` ABI so any decision is purely
an extension. The Windows x64 calling convention [1] does say that user-
defined types that do not fit in a register should be returned
indirectly, so the GCC behavior seems like a reasonable interpretation
of this rule.

Thus, change `fp128` to return on the stack for Windows targets. This is
done for both MinGW and MSVC targets; if official guidelines are ever
published, this can be revisited.

The change is made in LLVM (via sret demotion of f128), but also
explicitly in `clang`. Frontends should emit the `sret` explicitly, if
only for debug info, return value optimization, etc.

Relates to the pass ABI change in 5ee1c0b ("[windows] Always pass
fp128 arguments indirectly").

[1]:
https://learn.microsoft.com/en-us/cpp/build/x64-calling-convention?view=msvc-170#return-values
folkertdev added a commit that referenced this pull request Jul 7, 2026
…version (#207285)

This change fixes an ABI issue where the signed conversion passes a
`i128` different from a `u128`, and crucially different from GCC.

https://godbolt.org/z/x3daY3rej

```diff
--- <unnamed>
+++ <unnamed>
@@ -1,15 +1,15 @@
-conv_from_i128:
+conv_from_u128:
         push    rsi
-        sub     rsp, 64
+        sub     rsp, 48
         mov     rsi, rcx
-        movaps  xmm0, xmmword ptr [rdx]
-        movaps  xmmword ptr [rsp + 48], xmm0
+        mov     rax, qword ptr [rdx]
+        mov     r8, qword ptr [rdx + 8]
         lea     rcx, [rsp + 32]
-        lea     rdx, [rsp + 48]
-        call    __floattitf
+        mov     rdx, rax
+        call    __floatuntitf
         movaps  xmm0, xmmword ptr [rsp + 32]
         movaps  xmmword ptr [rsi], xmm0
         mov     rax, rsi
-        add     rsp, 64
+        add     rsp, 48
         pop     rsi
         ret
```

The signed case being correct is apparently due to a special
windows-specific path being chosen. The unsigned version bailed out too
early. So, I've changed `LowerUINT_TO_FP` to be more like
`LowerSINT_TO_FP`. In particular this moves the "Bail out when we don't
have native conversion instructions." down a bit, so that
`LowerWin64_INT128_TO_FP` is actually used.

I've then also added some testing for win64 SSE. I'm not sure if the
other target features are as relevant but I can add those too if
helpful.

This was found by running the `rust-lang/compiler-builtins` tests with a
build that includes #204887.
llvm-upstreamsync Bot pushed a commit to qualcomm/cpullvm-toolchain that referenced this pull request Jul 7, 2026
… `f128` conversion (#207285)

This change fixes an ABI issue where the signed conversion passes a
`i128` different from a `u128`, and crucially different from GCC.

https://godbolt.org/z/x3daY3rej

```diff
--- <unnamed>
+++ <unnamed>
@@ -1,15 +1,15 @@
-conv_from_i128:
+conv_from_u128:
         push    rsi
-        sub     rsp, 64
+        sub     rsp, 48
         mov     rsi, rcx
-        movaps  xmm0, xmmword ptr [rdx]
-        movaps  xmmword ptr [rsp + 48], xmm0
+        mov     rax, qword ptr [rdx]
+        mov     r8, qword ptr [rdx + 8]
         lea     rcx, [rsp + 32]
-        lea     rdx, [rsp + 48]
-        call    __floattitf
+        mov     rdx, rax
+        call    __floatuntitf
         movaps  xmm0, xmmword ptr [rsp + 32]
         movaps  xmmword ptr [rsi], xmm0
         mov     rax, rsi
-        add     rsp, 64
+        add     rsp, 48
         pop     rsi
         ret
```

The signed case being correct is apparently due to a special
windows-specific path being chosen. The unsigned version bailed out too
early. So, I've changed `LowerUINT_TO_FP` to be more like
`LowerSINT_TO_FP`. In particular this moves the "Bail out when we don't
have native conversion instructions." down a bit, so that
`LowerWin64_INT128_TO_FP` is actually used.

I've then also added some testing for win64 SSE. I'm not sure if the
other target features are as relevant but I can add those too if
helpful.

This was found by running the `rust-lang/compiler-builtins` tests with a
build that includes llvm/llvm-project#204887.
llvm-sync Bot pushed a commit to arm/arm-toolchain that referenced this pull request Jul 7, 2026
… `f128` conversion (#207285)

This change fixes an ABI issue where the signed conversion passes a
`i128` different from a `u128`, and crucially different from GCC.

https://godbolt.org/z/x3daY3rej

```diff
--- <unnamed>
+++ <unnamed>
@@ -1,15 +1,15 @@
-conv_from_i128:
+conv_from_u128:
         push    rsi
-        sub     rsp, 64
+        sub     rsp, 48
         mov     rsi, rcx
-        movaps  xmm0, xmmword ptr [rdx]
-        movaps  xmmword ptr [rsp + 48], xmm0
+        mov     rax, qword ptr [rdx]
+        mov     r8, qword ptr [rdx + 8]
         lea     rcx, [rsp + 32]
-        lea     rdx, [rsp + 48]
-        call    __floattitf
+        mov     rdx, rax
+        call    __floatuntitf
         movaps  xmm0, xmmword ptr [rsp + 32]
         movaps  xmmword ptr [rsi], xmm0
         mov     rax, rsi
-        add     rsp, 64
+        add     rsp, 48
         pop     rsi
         ret
```

The signed case being correct is apparently due to a special
windows-specific path being chosen. The unsigned version bailed out too
early. So, I've changed `LowerUINT_TO_FP` to be more like
`LowerSINT_TO_FP`. In particular this moves the "Bail out when we don't
have native conversion instructions." down a bit, so that
`LowerWin64_INT128_TO_FP` is actually used.

I've then also added some testing for win64 SSE. I'm not sure if the
other target features are as relevant but I can add those too if
helpful.

This was found by running the `rust-lang/compiler-builtins` tests with a
build that includes llvm/llvm-project#204887.
llvm-upstream-sync Bot pushed a commit to sriyalamar/cpullvm-toolchain that referenced this pull request Jul 7, 2026
… `f128` conversion (#207285)

This change fixes an ABI issue where the signed conversion passes a
`i128` different from a `u128`, and crucially different from GCC.

https://godbolt.org/z/x3daY3rej

```diff
--- <unnamed>
+++ <unnamed>
@@ -1,15 +1,15 @@
-conv_from_i128:
+conv_from_u128:
         push    rsi
-        sub     rsp, 64
+        sub     rsp, 48
         mov     rsi, rcx
-        movaps  xmm0, xmmword ptr [rdx]
-        movaps  xmmword ptr [rsp + 48], xmm0
+        mov     rax, qword ptr [rdx]
+        mov     r8, qword ptr [rdx + 8]
         lea     rcx, [rsp + 32]
-        lea     rdx, [rsp + 48]
-        call    __floattitf
+        mov     rdx, rax
+        call    __floatuntitf
         movaps  xmm0, xmmword ptr [rsp + 32]
         movaps  xmmword ptr [rsi], xmm0
         mov     rax, rsi
-        add     rsp, 64
+        add     rsp, 48
         pop     rsi
         ret
```

The signed case being correct is apparently due to a special
windows-specific path being chosen. The unsigned version bailed out too
early. So, I've changed `LowerUINT_TO_FP` to be more like
`LowerSINT_TO_FP`. In particular this moves the "Bail out when we don't
have native conversion instructions." down a bit, so that
`LowerWin64_INT128_TO_FP` is actually used.

I've then also added some testing for win64 SSE. I'm not sure if the
other target features are as relevant but I can add those too if
helpful.

This was found by running the `rust-lang/compiler-builtins` tests with a
build that includes llvm/llvm-project#204887.
aobolensk pushed a commit to aobolensk/llvm-project that referenced this pull request Jul 8, 2026
…version (llvm#207285)

This change fixes an ABI issue where the signed conversion passes a
`i128` different from a `u128`, and crucially different from GCC.

https://godbolt.org/z/x3daY3rej

```diff
--- <unnamed>
+++ <unnamed>
@@ -1,15 +1,15 @@
-conv_from_i128:
+conv_from_u128:
         push    rsi
-        sub     rsp, 64
+        sub     rsp, 48
         mov     rsi, rcx
-        movaps  xmm0, xmmword ptr [rdx]
-        movaps  xmmword ptr [rsp + 48], xmm0
+        mov     rax, qword ptr [rdx]
+        mov     r8, qword ptr [rdx + 8]
         lea     rcx, [rsp + 32]
-        lea     rdx, [rsp + 48]
-        call    __floattitf
+        mov     rdx, rax
+        call    __floatuntitf
         movaps  xmm0, xmmword ptr [rsp + 32]
         movaps  xmmword ptr [rsi], xmm0
         mov     rax, rsi
-        add     rsp, 64
+        add     rsp, 48
         pop     rsi
         ret
```

The signed case being correct is apparently due to a special
windows-specific path being chosen. The unsigned version bailed out too
early. So, I've changed `LowerUINT_TO_FP` to be more like
`LowerSINT_TO_FP`. In particular this moves the "Bail out when we don't
have native conversion instructions." down a bit, so that
`LowerWin64_INT128_TO_FP` is actually used.

I've then also added some testing for win64 SSE. I'm not sure if the
other target features are as relevant but I can add those too if
helpful.

This was found by running the `rust-lang/compiler-builtins` tests with a
build that includes llvm#204887.
gandhi56 pushed a commit that referenced this pull request Jul 9, 2026
…version (#207285)

This change fixes an ABI issue where the signed conversion passes a
`i128` different from a `u128`, and crucially different from GCC.

https://godbolt.org/z/x3daY3rej

```diff
--- <unnamed>
+++ <unnamed>
@@ -1,15 +1,15 @@
-conv_from_i128:
+conv_from_u128:
         push    rsi
-        sub     rsp, 64
+        sub     rsp, 48
         mov     rsi, rcx
-        movaps  xmm0, xmmword ptr [rdx]
-        movaps  xmmword ptr [rsp + 48], xmm0
+        mov     rax, qword ptr [rdx]
+        mov     r8, qword ptr [rdx + 8]
         lea     rcx, [rsp + 32]
-        lea     rdx, [rsp + 48]
-        call    __floattitf
+        mov     rdx, rax
+        call    __floatuntitf
         movaps  xmm0, xmmword ptr [rsp + 32]
         movaps  xmmword ptr [rsi], xmm0
         mov     rax, rsi
-        add     rsp, 64
+        add     rsp, 48
         pop     rsi
         ret
```

The signed case being correct is apparently due to a special
windows-specific path being chosen. The unsigned version bailed out too
early. So, I've changed `LowerUINT_TO_FP` to be more like
`LowerSINT_TO_FP`. In particular this moves the "Bail out when we don't
have native conversion instructions." down a bit, so that
`LowerWin64_INT128_TO_FP` is actually used.

I've then also added some testing for win64 SSE. I'm not sure if the
other target features are as relevant but I can add those too if
helpful.

This was found by running the `rust-lang/compiler-builtins` tests with a
build that includes #204887.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

backend:X86 clang:codegen IR generation bugs: mangling, exceptions, etc.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants