[Mlir-commits] [clang] [llvm] [mlir] [X86] Add WinCall Calling Convention and x86_64apx-windows(-gnu/-msvc) triplets (PR #215585)
llvmlistbot at llvm.org
llvmlistbot at llvm.org
Thu Oct 15 23:25:01 PDT 2026
https://github.com/trcrsired updated https://github.com/llvm/llvm-project/pull/215585
>From a8f6c10f2a98af952e6216fcb0b52872a65b2bd1 Mon Sep 17 00:00:00 2001
From: trcrsired <uwgghhbcad at gmail.com>
Date: Sun, 23 Feb 2025 09:25:05 -0500
Subject: [PATCH 01/36] [X86] Initial Attempt to add a new calling convention:
wincall
It is based on the proposal i made before but i want to test myself to
see whether there are some issues
https://developercommunity.visualstudio.com/t/I-present-a-novel-calling-convention-nam/10433601?q=wincall
---
llvm/include/llvm/IR/CallingConv.h | 3 +
llvm/lib/Target/X86/X86CallingConv.td | 84 +++++++++++++++++++++++++++
2 files changed, 87 insertions(+)
diff --git a/llvm/include/llvm/IR/CallingConv.h b/llvm/include/llvm/IR/CallingConv.h
index 55e32028e3ed08..00a175a451cf02 100644
--- a/llvm/include/llvm/IR/CallingConv.h
+++ b/llvm/include/llvm/IR/CallingConv.h
@@ -270,6 +270,9 @@ namespace CallingConv {
/// Preserve X1-X15, X19-X29, SP, Z0-Z31, P0-P15.
AArch64_SME_ABI_Support_Routines_PreserveMost_From_X1 = 111,
+ /// x86 wincall for APX to fix calling convention issues for Windows
+ X86_WinCall = 112,
+
/// The highest possible ID. Must be some 2^k - 1.
MaxID = 1023
};
diff --git a/llvm/lib/Target/X86/X86CallingConv.td b/llvm/lib/Target/X86/X86CallingConv.td
index cf164acba9ec0b..5b34303bfaa550 100644
--- a/llvm/lib/Target/X86/X86CallingConv.td
+++ b/llvm/lib/Target/X86/X86CallingConv.td
@@ -1039,6 +1039,89 @@ def CC_X86_64_Preserve_None : CallingConv<[
CCDelegateTo<CC_X86_64_C>
]>;
+// New Calling convention used on Win64 with Intel APX for Windows 12+
+def CC_X86_Win64_WinCall : CallingConv<[
+ // FIXME: Handle varargs.
+
+ // Byval aggregates are passed by pointer
+ CCIfByVal<CCPassByVal<8, 8>>,
+
+ // Promote i1/v1i1 arguments to i8.
+ CCIfType<[i1, v1i1], CCPromoteToType<i8>>,
+
+ // The 'nest' parameter, if any, is passed in R10.
+ CCIfNest<CCAssignToReg<[R10]>>,
+
+ // A SwiftError is passed in R12.
+ CCIfSwiftError<CCIfType<[i64], CCAssignToReg<[R12]>>>,
+
+ // Pass SwiftSelf in a callee saved register.
+ CCIfSwiftSelf<CCIfType<[i64], CCAssignToReg<[R13]>>>,
+
+ // Pass SwiftAsync in an otherwise callee saved register so that calls to
+ // normal functions don't need to save it somewhere.
+ CCIfSwiftAsync<CCIfType<[i64], CCAssignToReg<[R14]>>>,
+
+ // The 'CFGuardTarget' parameter, if any, is passed in RAX.
+ CCIfCFGuardTarget<CCAssignToReg<[RAX]>>,
+
+ // 256 bit vectors are passed by pointer
+ CCIfType<[v32i8, v16i16, v8i32, v4i64, v16f16, v16bf16, v8f32, v4f64], CCPassIndirect<i64>>,
+
+ // 512 bit vectors are passed by pointer
+ CCIfType<[v64i8, v32i16, v16i32, v32f16, v32bf16, v16f32, v8f64, v8i64], CCPassIndirect<i64>>,
+
+ // Long doubles are passed by pointer
+ CCIfType<[f80], CCPassIndirect<i64>>,
+
+ // If SSE was disabled, pass FP values smaller than 64-bits as integers in
+ // GPRs or on the stack.
+ CCIfType<[f16], CCIfNotSubtarget<"hasSSE1()", CCBitConvertToType<i16>>>,
+ CCIfType<[f32], CCIfNotSubtarget<"hasSSE1()", CCBitConvertToType<i32>>>,
+ CCIfType<[f64], CCIfNotSubtarget<"hasSSE1()", CCBitConvertToType<i64>>>,
+
+ // The first 8 FP/Vector arguments are passed in XMM registers.
+ CCIfType<[f16, f32, f64, v16i8, v8i16, v4i32, v2i64, v8f16, v8bf16, v4f32, v2f64],
+ CCIfSubtarget<"hasSSE1()",
+ CCAssignToRegWithShadow<[XMM0, XMM1, XMM2, XMM3, XMM4, XMM5, XMM6, XMM7],
+ [RCX , RDX , R8 , R9 , R16, R17, R18, R19]>>>,
+ // 256 bit vectors are passed in YMM registers.
+ CCIfType<[v32i8, v16i16, v8i32, v4i64, v16f16, v16bf16, v8f32, v4f64],
+ CCIfSubtarget<"hasAVX()",
+ CCAssignToRegWithShadow<[YMM0, YMM1, YMM2, YMM3, YMM4, YMM5, YMM6, YMM7],
+ [RCX , RDX , R8 , R9 , R16, R17, R18, R19]>>>,
+ // 512 bit vectors are passed in ZMM registers.
+ CCIfType<[v64i8, v32i16, v16i32, v32f16, v32bf16, v16f32, v8f64, v8i64],
+ CCIfSubtarget<"hasAVX512()",
+ CCAssignToRegWithShadow<[ZMM0, ZMM1, ZMM2, ZMM3, ZMM4, ZMM5, ZMM6, ZMM7],
+ [RCX , RDX , R8 , R9 , R16, R17, R18, R19]>>>,
+ // 512 bit vectors are passed by pointer
+ CCIfType<[v64i8, v32i16, v16i32, v32f16, v32bf16, v16f32, v8f64, v8i64], CCPassIndirect<i64>>,
+
+ // The first 4 integer arguments are passed in integer registers.
+ CCIfType<[i8 ], CCAssignToRegWithShadow<[CL , DL , R8B , R9B , R16B, R17B, R18B, R19B],
+ [XMM0, XMM1, XMM2, XMM3, XMM4, XMM5, XMM6, XMM7]>>,
+ CCIfType<[i16], CCAssignToRegWithShadow<[CX , DX , R8W , R9W , R16W, R17W, R18W, R19W],
+ [XMM0, XMM1, XMM2, XMM3, XMM4, XMM5, XMM6, XMM7]>>,
+ CCIfType<[i32], CCAssignToRegWithShadow<[ECX , EDX , R8D , R9D , R16D, R17D, R18D, R19D],
+ [XMM0, XMM1, XMM2, XMM3, XMM4, XMM5, XMM6, XMM7]>>,
+ CCIfType<[i64], CCAssignToRegWithShadow<[RCX , RDX , R8 , R9 , R16, R17, R18, R19],
+ [XMM0, XMM1, XMM2, XMM3, XMM4, XMM5, XMM6, XMM7]>>,
+
+ // Integer/FP values get stored in stack slots that are 8 bytes in size and
+ // 8-byte aligned if there are no more registers to hold them.
+ CCIfType<[i8, i16, i32, i64, f16, f32, f64], CCAssignToStack<8, 8>>,
+
+ // Vectors get 16-byte stack slots that are 16-byte aligned.
+ CCIfType<[v16i8, v8i16, v4i32, v2i64, v8f16, v8bf16, v4f32, v2f64], CCAssignToStack<16, 16>>,
+
+ // 256-bit vectors get 32-byte stack slots that are 32-byte aligned.
+ CCIfType<[v32i8, v16i16, v8i32, v4i64, v16f16, v16bf16, v8f32, v4f64], CCAssignToStack<32, 32>>,
+
+ // 512-bit vectors get 64-byte stack slots that are 64-byte aligned.
+ CCIfType<[v64i8, v32i16, v16i32, v8i64, v32f16, v32bf16, v16f32, v8f64], CCAssignToStack<64, 64>>
+]>;
+
//===----------------------------------------------------------------------===//
// X86 Root Argument Calling Conventions
//===----------------------------------------------------------------------===//
@@ -1049,6 +1132,7 @@ def CC_X86_32 : CallingConv<[
// MCU calling convention. Thus, this should be checked before isTargetMCU().
CCIfCC<"CallingConv::X86_INTR", CCCustom<"CC_X86_Intr">>,
CCIfSubtarget<"isTargetMCU()", CCDelegateTo<CC_X86_32_MCU>>,
+ CCIfCC<"CallingConv::X86_WinCall", CCDelegateTo<CC_X86_Win64_WinCall>>,
CCIfCC<"CallingConv::X86_FastCall", CCDelegateTo<CC_X86_32_FastCall>>,
CCIfCC<"CallingConv::X86_VectorCall", CCDelegateTo<CC_X86_Win32_VectorCall>>,
CCIfCC<"CallingConv::X86_ThisCall", CCDelegateTo<CC_X86_32_ThisCall>>,
>From 938fdc5d279f97e3390a7f9586c62c11c6db4146 Mon Sep 17 00:00:00 2001
From: trcrsired <uwgghhbcad at gmail.com>
Date: Mon, 24 Feb 2025 06:01:09 -0500
Subject: [PATCH 02/36] [X86] Add more random place changes for wincall
---
llvm/include/llvm/AsmParser/LLToken.h | 1 +
llvm/include/llvm/Demangle/MicrosoftDemangleNodes.h | 1 +
llvm/lib/AsmParser/LLLexer.cpp | 1 +
llvm/lib/AsmParser/LLParser.cpp | 1 +
llvm/lib/IR/AsmWriter.cpp | 1 +
llvm/lib/Target/X86/X86Subtarget.h | 1 +
6 files changed, 6 insertions(+)
diff --git a/llvm/include/llvm/AsmParser/LLToken.h b/llvm/include/llvm/AsmParser/LLToken.h
index a53d471f70271b..d8316322a59293 100644
--- a/llvm/include/llvm/AsmParser/LLToken.h
+++ b/llvm/include/llvm/AsmParser/LLToken.h
@@ -141,6 +141,7 @@ enum Kind {
kw_x86_fastcallcc,
kw_x86_thiscallcc,
kw_x86_vectorcallcc,
+ kw_x86_wincallcc,
kw_x86_regcallcc,
kw_arm_apcscc,
kw_arm_aapcscc,
diff --git a/llvm/include/llvm/Demangle/MicrosoftDemangleNodes.h b/llvm/include/llvm/Demangle/MicrosoftDemangleNodes.h
index 09b9d947464ae9..56ccb43172e673 100644
--- a/llvm/include/llvm/Demangle/MicrosoftDemangleNodes.h
+++ b/llvm/include/llvm/Demangle/MicrosoftDemangleNodes.h
@@ -65,6 +65,7 @@ enum class CallingConv : uint8_t {
Eabi,
Vectorcall,
Regcall,
+ Wincall,
Swift, // Clang-only
SwiftAsync, // Clang-only
};
diff --git a/llvm/lib/AsmParser/LLLexer.cpp b/llvm/lib/AsmParser/LLLexer.cpp
index c867a68518e4d7..fda2bac2c0b0b4 100644
--- a/llvm/lib/AsmParser/LLLexer.cpp
+++ b/llvm/lib/AsmParser/LLLexer.cpp
@@ -638,6 +638,7 @@ lltok::Kind LLLexer::LexIdentifier() {
KEYWORD(x86_fastcallcc);
KEYWORD(x86_thiscallcc);
KEYWORD(x86_vectorcallcc);
+ KEYWORD(x86_wincallcc);
KEYWORD(arm_apcscc);
KEYWORD(arm_aapcscc);
KEYWORD(arm_aapcs_vfpcc);
diff --git a/llvm/lib/AsmParser/LLParser.cpp b/llvm/lib/AsmParser/LLParser.cpp
index 37103937c92a7f..ee882135dcf0e8 100644
--- a/llvm/lib/AsmParser/LLParser.cpp
+++ b/llvm/lib/AsmParser/LLParser.cpp
@@ -2228,6 +2228,7 @@ bool LLParser::parseOptionalCallingConv(unsigned &CC) {
case lltok::kw_x86_regcallcc: CC = CallingConv::X86_RegCall; break;
case lltok::kw_x86_thiscallcc: CC = CallingConv::X86_ThisCall; break;
case lltok::kw_x86_vectorcallcc:CC = CallingConv::X86_VectorCall; break;
+ case lltok::kw_x86_wincallcc: CC = CallingConv::X86_WinCall; break;
case lltok::kw_arm_apcscc: CC = CallingConv::ARM_APCS; break;
case lltok::kw_arm_aapcscc: CC = CallingConv::ARM_AAPCS; break;
case lltok::kw_arm_aapcs_vfpcc:CC = CallingConv::ARM_AAPCS_VFP; break;
diff --git a/llvm/lib/IR/AsmWriter.cpp b/llvm/lib/IR/AsmWriter.cpp
index 57e9cccdc0fb6e..f3fb92e7146acd 100644
--- a/llvm/lib/IR/AsmWriter.cpp
+++ b/llvm/lib/IR/AsmWriter.cpp
@@ -310,6 +310,7 @@ static void PrintCallingConv(unsigned cc, raw_ostream &Out) {
case CallingConv::GRAAL: Out << "graalcc"; break;
case CallingConv::CFGuard_Check: Out << "cfguard_checkcc"; break;
case CallingConv::X86_StdCall: Out << "x86_stdcallcc"; break;
+ case CallingConv::X86_WinCall: Out << "x86_wincallcc"; break;
case CallingConv::X86_FastCall: Out << "x86_fastcallcc"; break;
case CallingConv::X86_ThisCall: Out << "x86_thiscallcc"; break;
case CallingConv::X86_RegCall: Out << "x86_regcallcc"; break;
diff --git a/llvm/lib/Target/X86/X86Subtarget.h b/llvm/lib/Target/X86/X86Subtarget.h
index 722076ca88c9c1..25ca66e6889723 100644
--- a/llvm/lib/Target/X86/X86Subtarget.h
+++ b/llvm/lib/Target/X86/X86Subtarget.h
@@ -355,6 +355,7 @@ class X86Subtarget final : public X86GenSubtargetInfo {
case CallingConv::X86_StdCall:
case CallingConv::X86_ThisCall:
case CallingConv::X86_VectorCall:
+ case CallingConv::X86_WinCall:
case CallingConv::Intel_OCL_BI:
return isTargetWin64();
// This convention allows using the Win64 convention on other targets.
>From 9d36dfc92b5cfe33a513f023b07ec18ddc115791 Mon Sep 17 00:00:00 2001
From: trcrsired <uwgghhbcad at gmail.com>
Date: Mon, 5 May 2025 17:21:58 +0800
Subject: [PATCH 03/36] call
---
llvm/lib/Target/X86/X86CallingConv.td | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/llvm/lib/Target/X86/X86CallingConv.td b/llvm/lib/Target/X86/X86CallingConv.td
index 33cc1aceb5503e..f68e7c64233187 100644
--- a/llvm/lib/Target/X86/X86CallingConv.td
+++ b/llvm/lib/Target/X86/X86CallingConv.td
@@ -1138,7 +1138,6 @@ def CC_X86_32 : CallingConv<[
// MCU calling convention. Thus, this should be checked before isTargetMCU().
CCIfCC<"CallingConv::X86_INTR", CCCustom<"CC_X86_Intr">>,
CCIfSubtarget<"isTargetMCU()", CCDelegateTo<CC_X86_32_MCU>>,
- CCIfCC<"CallingConv::X86_WinCall", CCDelegateTo<CC_X86_Win64_WinCall>>,
CCIfCC<"CallingConv::X86_FastCall", CCDelegateTo<CC_X86_32_FastCall>>,
CCIfCC<"CallingConv::X86_VectorCall", CCDelegateTo<CC_X86_Win32_VectorCall>>,
CCIfCC<"CallingConv::X86_ThisCall", CCDelegateTo<CC_X86_32_ThisCall>>,
@@ -1160,6 +1159,7 @@ def CC_X86_64 : CallingConv<[
CCIfCC<"CallingConv::GHC", CCDelegateTo<CC_X86_64_GHC>>,
CCIfCC<"CallingConv::HiPE", CCDelegateTo<CC_X86_64_HiPE>>,
CCIfCC<"CallingConv::AnyReg", CCDelegateTo<CC_X86_64_AnyReg>>,
+ CCIfCC<"CallingConv::X86_WinCall", CCDelegateTo<CC_X86_Win64_WinCall>>,
CCIfCC<"CallingConv::Win64", CCDelegateTo<CC_X86_Win64_C>>,
CCIfCC<"CallingConv::X86_64_SysV", CCDelegateTo<CC_X86_64_C>>,
CCIfCC<"CallingConv::X86_VectorCall", CCDelegateTo<CC_X86_Win64_VectorCall>>,
>From d3dd91b6300d61b33b64d9ee0d405421234b7db3 Mon Sep 17 00:00:00 2001
From: trcrsired <uwgghhbcad at gmail.com>
Date: Sun, 25 May 2025 18:29:56 +0800
Subject: [PATCH 04/36] Add experimental wincall changes to clang frontend
---
clang/include/clang-c/Index.h | 1 +
clang/include/clang/Basic/Attr.td | 7 +++++++
clang/include/clang/Basic/AttrDocs.td | 13 +++++++++++++
clang/include/clang/Basic/LangOptions.h | 3 ++-
clang/include/clang/Basic/Specifiers.h | 2 ++
clang/include/clang/Basic/TokenKinds.def | 2 ++
clang/include/clang/Driver/Options.td | 6 ++++--
clang/lib/AST/ASTContext.cpp | 4 ++++
clang/lib/AST/Expr.cpp | 1 +
clang/lib/AST/ItaniumMangle.cpp | 2 ++
clang/lib/AST/Mangle.cpp | 2 ++
clang/lib/AST/MicrosoftMangle.cpp | 5 +++++
clang/lib/AST/Type.cpp | 2 ++
clang/lib/AST/TypePrinter.cpp | 4 ++++
clang/lib/Basic/Targets.cpp | 2 +-
clang/lib/CodeGen/CGCall.cpp | 2 ++
clang/lib/Driver/ToolChains/Clang.cpp | 5 +++++
clang/lib/Parse/ParseDecl.cpp | 1 +
clang/lib/Parse/ParseTentative.cpp | 3 ++-
clang/lib/Sema/SemaDeclAttr.cpp | 6 ++++++
clang/lib/Sema/SemaExpr.cpp | 3 ++-
clang/lib/Sema/SemaLambda.cpp | 2 +-
clang/lib/Sema/SemaType.cpp | 3 +++
23 files changed, 74 insertions(+), 7 deletions(-)
diff --git a/clang/include/clang-c/Index.h b/clang/include/clang-c/Index.h
index d30d15e53802a1..c9096057f220cd 100644
--- a/clang/include/clang-c/Index.h
+++ b/clang/include/clang-c/Index.h
@@ -3077,6 +3077,7 @@ enum CXCallingConv {
CXCallingConv_RISCVVLSCall_16384 = 31,
CXCallingConv_RISCVVLSCall_32768 = 32,
CXCallingConv_RISCVVLSCall_65536 = 33,
+ CXCAllingConv_WinCall = 34,
CXCallingConv_Invalid = 100,
CXCallingConv_Unexposed = 200
diff --git a/clang/include/clang/Basic/Attr.td b/clang/include/clang/Basic/Attr.td
index 06462b8a26bc0e..dc8f6755583207 100644
--- a/clang/include/clang/Basic/Attr.td
+++ b/clang/include/clang/Basic/Attr.td
@@ -3230,6 +3230,13 @@ def StdCall : DeclOrTypeAttr {
let Documentation = [StdCallDocs];
}
+def WinCall : DeclOrTypeAttr {
+ let Spellings = [GCC<"wincall">, CustomKeyword<"__wincall">,
+ CustomKeyword<"_wincall">];
+// let Subjects = [Function, ObjCMethod];
+ let Documentation = [WinCallDocs];
+}
+
def SwiftCall : DeclOrTypeAttr {
let Spellings = [Clang<"swiftcall">];
// let Subjects = SubjectList<[Function]>;
diff --git a/clang/include/clang/Basic/AttrDocs.td b/clang/include/clang/Basic/AttrDocs.td
index 65d66dd398ad13..48164b43fbc39e 100644
--- a/clang/include/clang/Basic/AttrDocs.td
+++ b/clang/include/clang/Basic/AttrDocs.td
@@ -3394,6 +3394,19 @@ COM applications. See the documentation for `__stdcall`_ on MSDN.
}];
}
+def WinCallDocs : Documentation {
+ let Category = DocCatCallingConvs;
+ let Content = [{
+Wincall introduces a new calling convention for x86_64, designed to support Intel APX.
+This convention addresses calling convention challenges for C++ types, such as `std::span`,
+on the Windows x86_64 platform.
+
+For detailed information, refer to the proposal for `__wincall`_ on Microsoft Developer Community.
+
+.. _`__wincall`: https://developercommunity.visualstudio.com/t/I-present-a-novel-calling-convention-nam/10433601?q=wincall
+ }];
+}
+
def FastCallDocs : Documentation {
let Category = DocCatCallingConvs;
let Content = [{
diff --git a/clang/include/clang/Basic/LangOptions.h b/clang/include/clang/Basic/LangOptions.h
index 491e8bee9fd5c7..6e8e0ea4d411c1 100644
--- a/clang/include/clang/Basic/LangOptions.h
+++ b/clang/include/clang/Basic/LangOptions.h
@@ -126,7 +126,8 @@ class LangOptionsBase {
DCC_StdCall,
DCC_VectorCall,
DCC_RegCall,
- DCC_RtdCall
+ DCC_RtdCall,
+ DCC_WinCall
};
enum AddrSpaceMapMangling { ASMM_Target, ASMM_On, ASMM_Off };
diff --git a/clang/include/clang/Basic/Specifiers.h b/clang/include/clang/Basic/Specifiers.h
index 491badcc804e77..d2c4882d3f970a 100644
--- a/clang/include/clang/Basic/Specifiers.h
+++ b/clang/include/clang/Basic/Specifiers.h
@@ -283,6 +283,7 @@ namespace clang {
CC_X86VectorCall, // __attribute__((vectorcall))
CC_X86Pascal, // __attribute__((pascal))
CC_Win64, // __attribute__((ms_abi))
+ CC_WinCall, // __attribute__((wincall))
CC_X86_64SysV, // __attribute__((sysv_abi))
CC_X86RegCall, // __attribute__((regcall))
CC_AAPCS, // __attribute__((pcs("aapcs")))
@@ -322,6 +323,7 @@ namespace clang {
case CC_X86StdCall:
case CC_X86FastCall:
case CC_X86ThisCall:
+ case CC_WinCall:
case CC_X86RegCall:
case CC_X86Pascal:
case CC_X86VectorCall:
diff --git a/clang/include/clang/Basic/TokenKinds.def b/clang/include/clang/Basic/TokenKinds.def
index 94e72fea56a68d..99193865107669 100644
--- a/clang/include/clang/Basic/TokenKinds.def
+++ b/clang/include/clang/Basic/TokenKinds.def
@@ -619,6 +619,7 @@ KEYWORD(__fastcall , KEYALL)
KEYWORD(__thiscall , KEYALL)
KEYWORD(__regcall , KEYALL)
KEYWORD(__vectorcall , KEYALL)
+KEYWORD(__wincall , KEYALL)
KEYWORD(__forceinline , KEYMS)
KEYWORD(__unaligned , KEYMS)
KEYWORD(__super , KEYMS)
@@ -781,6 +782,7 @@ ALIAS("_ptr64" , __ptr64 , KEYMSCOMPAT)
ALIAS("_restrict" , restrict , KEYMSCOMPAT)
ALIAS("_stdcall" , __stdcall , KEYMS | KEYBORLAND)
ALIAS("_thiscall" , __thiscall , KEYMS)
+ALIAS("_wincall" , __wincall , KEYMS)
ALIAS("_try" , __try , KEYMSCOMPAT)
ALIAS("_vectorcall" , __vectorcall , KEYMS)
ALIAS("_unaligned" , __unaligned , KEYMSCOMPAT)
diff --git a/clang/include/clang/Driver/Options.td b/clang/include/clang/Driver/Options.td
index 22261621df0923..e207574b992da3 100644
--- a/clang/include/clang/Driver/Options.td
+++ b/clang/include/clang/Driver/Options.td
@@ -8397,9 +8397,9 @@ def fnative_half_arguments_and_returns : Flag<["-"], "fnative-half-arguments-and
ImpliedByAnyOf<[open_cl.KeyPath, hlsl.KeyPath, hip.KeyPath]>;
def fdefault_calling_conv_EQ : Joined<["-"], "fdefault-calling-conv=">,
HelpText<"Set default calling convention">,
- Values<"cdecl,fastcall,stdcall,vectorcall,regcall,rtdcall">,
+ Values<"cdecl,fastcall,stdcall,vectorcall,regcall,rtdcall,wincall">,
NormalizedValuesScope<"LangOptions">,
- NormalizedValues<["DCC_CDecl", "DCC_FastCall", "DCC_StdCall", "DCC_VectorCall", "DCC_RegCall", "DCC_RtdCall"]>,
+ NormalizedValues<["DCC_CDecl", "DCC_FastCall", "DCC_StdCall", "DCC_VectorCall", "DCC_RegCall", "DCC_RtdCall", "DCC_WinCall"]>,
MarshallingInfoEnum<LangOpts<"DefaultCallingConv">, "DCC_None">;
// These options cannot be marshalled, because they are used to set up the LangOptions defaults.
@@ -9092,6 +9092,8 @@ def _SLASH_Gz : CLFlag<"Gz">,
HelpText<"Set __stdcall as a default calling convention">;
def _SLASH_Gv : CLFlag<"Gv">,
HelpText<"Set __vectorcall as a default calling convention">;
+def _SLASH_Gwincall : CLFlag<"Gwincall">,
+ HelpText<"Set __wincall as a default calling convention">;
def _SLASH_Gregcall : CLFlag<"Gregcall">,
HelpText<"Set __regcall as a default calling convention">;
def _SLASH_Gregcall4 : CLFlag<"Gregcall4">,
diff --git a/clang/lib/AST/ASTContext.cpp b/clang/lib/AST/ASTContext.cpp
index b5417fcf20ddd8..d04eccbf07439e 100644
--- a/clang/lib/AST/ASTContext.cpp
+++ b/clang/lib/AST/ASTContext.cpp
@@ -13039,6 +13039,10 @@ CallingConv ASTContext::getDefaultCallingConvention(bool IsVariadic,
if (!IsVariadic)
return CC_X86StdCall;
break;
+ case LangOptions::DCC_WinCall
+ if (!IsVariadic)
+ return CC_WinCall;
+ break;
case LangOptions::DCC_VectorCall:
// __vectorcall cannot be applied to variadic functions.
if (!IsVariadic)
diff --git a/clang/lib/AST/Expr.cpp b/clang/lib/AST/Expr.cpp
index fe874ccd7b60fb..40e4c58f359b72 100644
--- a/clang/lib/AST/Expr.cpp
+++ b/clang/lib/AST/Expr.cpp
@@ -796,6 +796,7 @@ std::string PredefinedExpr::ComputeName(PredefinedIdentKind IK,
case CC_X86ThisCall: POut << "__thiscall "; break;
case CC_X86VectorCall: POut << "__vectorcall "; break;
case CC_X86RegCall: POut << "__regcall "; break;
+ case CC_WinCall: POut << "__wincall "; break;
// Only bother printing the conventions that MSVC knows about.
default: break;
}
diff --git a/clang/lib/AST/ItaniumMangle.cpp b/clang/lib/AST/ItaniumMangle.cpp
index 33a87287285745..d6643d34af0e13 100644
--- a/clang/lib/AST/ItaniumMangle.cpp
+++ b/clang/lib/AST/ItaniumMangle.cpp
@@ -3575,6 +3575,8 @@ StringRef CXXNameMangler::getCallingConvQualifierName(CallingConv CC) {
return "swiftcall";
case CC_SwiftAsync:
return "swiftasynccall";
+ case CC_WinCall:
+ return "wincall";
}
llvm_unreachable("bad calling convention");
}
diff --git a/clang/lib/AST/Mangle.cpp b/clang/lib/AST/Mangle.cpp
index 9652fdbc4e125d..b19f38bd3a7f6f 100644
--- a/clang/lib/AST/Mangle.cpp
+++ b/clang/lib/AST/Mangle.cpp
@@ -115,6 +115,8 @@ static CCMangling getCallingConvMangling(const ASTContext &Context,
return CCM_Std;
case CC_X86VectorCall:
return CCM_Vector;
+ case CC_X86WinCall:
+ return CCM_WinCall;
}
}
diff --git a/clang/lib/AST/MicrosoftMangle.cpp b/clang/lib/AST/MicrosoftMangle.cpp
index add737b762ccc7..90d0379d812e79 100644
--- a/clang/lib/AST/MicrosoftMangle.cpp
+++ b/clang/lib/AST/MicrosoftMangle.cpp
@@ -3133,6 +3133,8 @@ void MicrosoftCXXNameMangler::mangleCallingConvention(CallingConv CC,
// ::= H # __export __stdcall
// ::= I # __fastcall
// ::= J # __export __fastcall
+ // ::= K # __wincall
+ // ::= L # __export __wincall
// ::= Q # __vectorcall
// ::= S # __attribute__((__swiftcall__)) // Clang-only
// ::= W # __attribute__((__swiftasynccall__))
@@ -3168,6 +3170,9 @@ void MicrosoftCXXNameMangler::mangleCallingConvention(CallingConv CC,
case CC_X86FastCall:
Out << 'I';
return;
+ case CC_WinCall:
+ Out << 'K';
+ return;
case CC_X86VectorCall:
Out << 'Q';
return;
diff --git a/clang/lib/AST/Type.cpp b/clang/lib/AST/Type.cpp
index df084dd9149a43..3709bd24482fea 100644
--- a/clang/lib/AST/Type.cpp
+++ b/clang/lib/AST/Type.cpp
@@ -3582,6 +3582,8 @@ StringRef FunctionType::getNameForCallConv(CallingConv CC) {
return "fastcall";
case CC_X86ThisCall:
return "thiscall";
+ case CC_WinCall:
+ return "wincall";
case CC_X86Pascal:
return "pascal";
case CC_X86VectorCall:
diff --git a/clang/lib/AST/TypePrinter.cpp b/clang/lib/AST/TypePrinter.cpp
index cba1a2d98d6609..6ef7adc90198ae 100644
--- a/clang/lib/AST/TypePrinter.cpp
+++ b/clang/lib/AST/TypePrinter.cpp
@@ -1084,6 +1084,9 @@ void TypePrinter::printFunctionAfter(const FunctionType::ExtInfo &Info,
case CC_X86Pascal:
OS << " __attribute__((pascal))";
break;
+ case CC_WinCall:
+ OS << " __attribute__((wincall))";
+ break;
case CC_AAPCS:
OS << " __attribute__((pcs(\"aapcs\")))";
break;
@@ -2053,6 +2056,7 @@ void TypePrinter::printAttributedAfter(const AttributedType *T,
case attr::MSABI: OS << "ms_abi"; break;
case attr::SysVABI: OS << "sysv_abi"; break;
case attr::RegCall: OS << "regcall"; break;
+ case attr::WinCall: OS << "wincall"; break;
case attr::Pcs: {
OS << "pcs(";
QualType t = T->getEquivalentType();
diff --git a/clang/lib/Basic/Targets.cpp b/clang/lib/Basic/Targets.cpp
index 9889141ad2085f..07f49b4370dfce 100644
--- a/clang/lib/Basic/Targets.cpp
+++ b/clang/lib/Basic/Targets.cpp
@@ -94,7 +94,7 @@ void addCygMingDefines(const LangOptions &Opts, MacroBuilder &Builder) {
// Provide macros for all the calling convention keywords. Provide both
// single and double underscore prefixed variants. These are available on
// x64 as well as x86, even though they have no effect.
- const char *CCs[] = {"cdecl", "stdcall", "fastcall", "thiscall", "pascal"};
+ const char *CCs[] = {"cdecl", "stdcall", "fastcall", "thiscall", "pascal", "wincall"};
for (const char *CC : CCs) {
std::string GCCSpelling = "__attribute__((__";
GCCSpelling += CC;
diff --git a/clang/lib/CodeGen/CGCall.cpp b/clang/lib/CodeGen/CGCall.cpp
index bd920a2e3f2dd8..5b35b600ff4fd1 100644
--- a/clang/lib/CodeGen/CGCall.cpp
+++ b/clang/lib/CodeGen/CGCall.cpp
@@ -60,6 +60,8 @@ unsigned CodeGenTypes::ClangCallConvToLLVMCallConv(CallingConv CC) {
return llvm::CallingConv::X86_RegCall;
case CC_X86ThisCall:
return llvm::CallingConv::X86_ThisCall;
+ case CC_WinCall:
+ return llvm::CallingConv::X86_WinCall;
case CC_Win64:
return llvm::CallingConv::Win64;
case CC_X86_64SysV:
diff --git a/clang/lib/Driver/ToolChains/Clang.cpp b/clang/lib/Driver/ToolChains/Clang.cpp
index b0042b86ff4219..870d6d06f6f22c 100644
--- a/clang/lib/Driver/ToolChains/Clang.cpp
+++ b/clang/lib/Driver/ToolChains/Clang.cpp
@@ -8540,6 +8540,11 @@ void Clang::AddClangCLArgs(const ArgList &Args, types::ID InputType,
ArchSupported = Arch == llvm::Triple::x86 || Arch == llvm::Triple::x86_64;
DCCFlag = "-fdefault-calling-conv=regcall";
break;
+ break;
+ case options::OPT__SLASH_Gwincall:
+ ArchSupported = Arch == llvm::Triple::x86_64;
+ DCCFlag = "-fdefault-calling-conv=wincall";
+ break;
}
// MSVC doesn't warn if /Gr or /Gz is used on x64, so we don't either.
diff --git a/clang/lib/Parse/ParseDecl.cpp b/clang/lib/Parse/ParseDecl.cpp
index 7a87cd2e340cc3..2f40e5818a9a34 100644
--- a/clang/lib/Parse/ParseDecl.cpp
+++ b/clang/lib/Parse/ParseDecl.cpp
@@ -974,6 +974,7 @@ void Parser::ParseMicrosoftTypeAttributes(ParsedAttributes &attrs) {
case tok::kw___regcall:
case tok::kw___cdecl:
case tok::kw___vectorcall:
+ case tok::kw___wincall:
case tok::kw___ptr64:
case tok::kw___w64:
case tok::kw___ptr32:
diff --git a/clang/lib/Parse/ParseTentative.cpp b/clang/lib/Parse/ParseTentative.cpp
index cc02ee51618aa0..d7b4765769e417 100644
--- a/clang/lib/Parse/ParseTentative.cpp
+++ b/clang/lib/Parse/ParseTentative.cpp
@@ -916,7 +916,7 @@ Parser::TPResult Parser::TryParseDeclarator(bool mayBeAbstract,
// '(' abstract-declarator ')'
if (Tok.isOneOf(tok::kw___attribute, tok::kw___declspec, tok::kw___cdecl,
tok::kw___stdcall, tok::kw___fastcall, tok::kw___thiscall,
- tok::kw___regcall, tok::kw___vectorcall))
+ tok::kw___regcall, tok::kw___vectorcall, tok::kw___wincall))
return TPResult::True; // attributes indicate declaration
TPResult TPR = TryParseDeclarator(mayBeAbstract, mayHaveIdentifier);
if (TPR != TPResult::Ambiguous)
@@ -1238,6 +1238,7 @@ Parser::isCXXDeclarationSpecifier(ImplicitTypenameContext AllowImplicitTypename,
case tok::kw___thiscall:
case tok::kw___regcall:
case tok::kw___vectorcall:
+ case tok::kw___wincall:
case tok::kw___w64:
case tok::kw___sptr:
case tok::kw___uptr:
diff --git a/clang/lib/Sema/SemaDeclAttr.cpp b/clang/lib/Sema/SemaDeclAttr.cpp
index 8ce51cc2882bf6..6341f9ba9dd4fe 100644
--- a/clang/lib/Sema/SemaDeclAttr.cpp
+++ b/clang/lib/Sema/SemaDeclAttr.cpp
@@ -5181,6 +5181,9 @@ static void handleCallConvAttr(Sema &S, Decl *D, const ParsedAttr &AL) {
case ParsedAttr::AT_CDecl:
D->addAttr(::new (S.Context) CDeclAttr(S.Context, AL));
return;
+ case ParsedAttr::AT_WinCall:
+ D->addAttr(::new (S.Context) WinCallAttr(S.Context, AL));
+ return;
case ParsedAttr::AT_Pascal:
D->addAttr(::new (S.Context) PascalAttr(S.Context, AL));
return;
@@ -5415,6 +5418,9 @@ bool Sema::CheckCallingConvAttr(const ParsedAttr &Attrs, CallingConv &CC,
case ParsedAttr::AT_ThisCall:
CC = CC_X86ThisCall;
break;
+ case ParsedAttr::AT_WinCall:
+ CC = CC_WinCall;
+ break;
case ParsedAttr::AT_Pascal:
CC = CC_X86Pascal;
break;
diff --git a/clang/lib/Sema/SemaExpr.cpp b/clang/lib/Sema/SemaExpr.cpp
index 452dbbfe23c5bd..481f9b3d677b1c 100644
--- a/clang/lib/Sema/SemaExpr.cpp
+++ b/clang/lib/Sema/SemaExpr.cpp
@@ -18328,12 +18328,13 @@ static bool funcHasParameterSizeMangling(Sema &S, FunctionDecl *FD) {
if (S.getLangOpts().CPlusPlus && !FD->isExternC())
return false;
- // Stdcall, fastcall, and vectorcall need this special treatment.
+ // Stdcall, fastcall, vectorcall and wincall need this special treatment.
CallingConv CC = FD->getType()->castAs<FunctionType>()->getCallConv();
switch (CC) {
case CC_X86StdCall:
case CC_X86FastCall:
case CC_X86VectorCall:
+ case CC_X86WinCall:
return true;
default:
break;
diff --git a/clang/lib/Sema/SemaLambda.cpp b/clang/lib/Sema/SemaLambda.cpp
index aad16290422f55..c71bd0a7364b82 100644
--- a/clang/lib/Sema/SemaLambda.cpp
+++ b/clang/lib/Sema/SemaLambda.cpp
@@ -1625,7 +1625,7 @@ static void repeatForLambdaConversionFunctionCallingConvs(
if (S.getLangOpts().MSVCCompat) {
CallingConv Convs[] = {
CC_C, CC_X86StdCall, CC_X86FastCall, CC_X86VectorCall,
- DefaultFree, DefaultMember, CallOpCC};
+ CC_WinCall, DefaultFree, DefaultMember, CallOpCC};
llvm::sort(Convs);
llvm::iterator_range<CallingConv *> Range(std::begin(Convs),
llvm::unique(Convs));
diff --git a/clang/lib/Sema/SemaType.cpp b/clang/lib/Sema/SemaType.cpp
index 874e41ac0b90c7..a75615b16d68c6 100644
--- a/clang/lib/Sema/SemaType.cpp
+++ b/clang/lib/Sema/SemaType.cpp
@@ -126,6 +126,7 @@ static void diagnoseBadTypeAttribute(Sema &S, const ParsedAttr &attr,
case ParsedAttr::AT_CDecl: \
case ParsedAttr::AT_FastCall: \
case ParsedAttr::AT_StdCall: \
+ case ParsedAttr::AT_WinCall: \
case ParsedAttr::AT_ThisCall: \
case ParsedAttr::AT_RegCall: \
case ParsedAttr::AT_Pascal: \
@@ -7512,6 +7513,8 @@ static Attr *getCCTypeAttr(ASTContext &Ctx, ParsedAttr &Attr) {
return createSimpleAttr<FastCallAttr>(Ctx, Attr);
case ParsedAttr::AT_StdCall:
return createSimpleAttr<StdCallAttr>(Ctx, Attr);
+ case ParsedAttr::AT_WinCall:
+ return createSimpleAttr<WinCallAttr>(Ctx, Attr);
case ParsedAttr::AT_ThisCall:
return createSimpleAttr<ThisCallAttr>(Ctx, Attr);
case ParsedAttr::AT_RegCall:
>From f9807460c4c15381c8e1d9dcb5a578a2aefc55b7 Mon Sep 17 00:00:00 2001
From: trcrsired <uwgghhbcad at gmail.com>
Date: Sun, 25 May 2025 18:33:57 +0800
Subject: [PATCH 05/36] Fix missing : in ASTContext.cpp
---
clang/lib/AST/ASTContext.cpp | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/clang/lib/AST/ASTContext.cpp b/clang/lib/AST/ASTContext.cpp
index d04eccbf07439e..1a0b892dbe2195 100644
--- a/clang/lib/AST/ASTContext.cpp
+++ b/clang/lib/AST/ASTContext.cpp
@@ -13039,7 +13039,7 @@ CallingConv ASTContext::getDefaultCallingConvention(bool IsVariadic,
if (!IsVariadic)
return CC_X86StdCall;
break;
- case LangOptions::DCC_WinCall
+ case LangOptions::DCC_WinCall:
if (!IsVariadic)
return CC_WinCall;
break;
>From 4a9ef9d40df32cafe842402cbd86d4f6d2339aea Mon Sep 17 00:00:00 2001
From: trcrsired <uwgghhbcad at gmail.com>
Date: Sun, 25 May 2025 18:36:27 +0800
Subject: [PATCH 06/36] CCM_WinCall was not defined. Fix
---
clang/lib/AST/Mangle.cpp | 1 +
1 file changed, 1 insertion(+)
diff --git a/clang/lib/AST/Mangle.cpp b/clang/lib/AST/Mangle.cpp
index b19f38bd3a7f6f..f087fd558cfeeb 100644
--- a/clang/lib/AST/Mangle.cpp
+++ b/clang/lib/AST/Mangle.cpp
@@ -68,6 +68,7 @@ enum CCMangling {
CCM_RegCall,
CCM_Vector,
CCM_Std,
+ CCM_WinCall,
CCM_WasmMainArgcArgv
};
>From b5d348aab21a82338c6701c9bfa5a68fa645768f Mon Sep 17 00:00:00 2001
From: trcrsired <uwgghhbcad at gmail.com>
Date: Sun, 25 May 2025 18:37:26 +0800
Subject: [PATCH 07/36] It is CC_WinCall not CCM_WinCall
---
clang/lib/AST/Mangle.cpp | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/clang/lib/AST/Mangle.cpp b/clang/lib/AST/Mangle.cpp
index f087fd558cfeeb..2fad5a179ab146 100644
--- a/clang/lib/AST/Mangle.cpp
+++ b/clang/lib/AST/Mangle.cpp
@@ -116,7 +116,7 @@ static CCMangling getCallingConvMangling(const ASTContext &Context,
return CCM_Std;
case CC_X86VectorCall:
return CCM_Vector;
- case CC_X86WinCall:
+ case CC_WinCall:
return CCM_WinCall;
}
}
>From 62d000098991b1a671fa784137277d5e102d1022 Mon Sep 17 00:00:00 2001
From: trcrsired <uwgghhbcad at gmail.com>
Date: Sun, 25 May 2025 18:41:29 +0800
Subject: [PATCH 08/36] WinCall, not X86WinCall
---
clang/lib/Sema/SemaExpr.cpp | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/clang/lib/Sema/SemaExpr.cpp b/clang/lib/Sema/SemaExpr.cpp
index 481f9b3d677b1c..9107a1d902865a 100644
--- a/clang/lib/Sema/SemaExpr.cpp
+++ b/clang/lib/Sema/SemaExpr.cpp
@@ -18334,7 +18334,7 @@ static bool funcHasParameterSizeMangling(Sema &S, FunctionDecl *FD) {
case CC_X86StdCall:
case CC_X86FastCall:
case CC_X86VectorCall:
- case CC_X86WinCall:
+ case CC_WinCall:
return true;
default:
break;
>From f0d259dc7ab70ebe3e3c95c9ca7efba0f59d2e3a Mon Sep 17 00:00:00 2001
From: trcrsired <uwgghhbcad at gmail.com>
Date: Sun, 25 May 2025 21:02:03 +0800
Subject: [PATCH 09/36] wincall
---
clang/include/clang-c/Index.h | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/clang/include/clang-c/Index.h b/clang/include/clang-c/Index.h
index c9096057f220cd..96e571b9f79361 100644
--- a/clang/include/clang-c/Index.h
+++ b/clang/include/clang-c/Index.h
@@ -3077,7 +3077,7 @@ enum CXCallingConv {
CXCallingConv_RISCVVLSCall_16384 = 31,
CXCallingConv_RISCVVLSCall_32768 = 32,
CXCallingConv_RISCVVLSCall_65536 = 33,
- CXCAllingConv_WinCall = 34,
+ CXCallingConv_WinCall = 34,
CXCallingConv_Invalid = 100,
CXCallingConv_Unexposed = 200
>From 4d41c627da4e304df33d39528a1de110c21dd7bd Mon Sep 17 00:00:00 2001
From: trcrsired <uwgghhbcad at gmail.com>
Date: Sun, 25 May 2025 23:18:42 +0800
Subject: [PATCH 10/36] remove tail space in attrdocs
---
clang/include/clang/Basic/AttrDocs.td | 6 +++---
1 file changed, 3 insertions(+), 3 deletions(-)
diff --git a/clang/include/clang/Basic/AttrDocs.td b/clang/include/clang/Basic/AttrDocs.td
index 48164b43fbc39e..4e53f745d0f403 100644
--- a/clang/include/clang/Basic/AttrDocs.td
+++ b/clang/include/clang/Basic/AttrDocs.td
@@ -3397,9 +3397,9 @@ COM applications. See the documentation for `__stdcall`_ on MSDN.
def WinCallDocs : Documentation {
let Category = DocCatCallingConvs;
let Content = [{
-Wincall introduces a new calling convention for x86_64, designed to support Intel APX.
-This convention addresses calling convention challenges for C++ types, such as `std::span`,
-on the Windows x86_64 platform.
+Wincall introduces a new calling convention for x86_64, designed to support Intel APX.
+This convention addresses calling convention challenges for C++ types, such as `std::span`,
+on the Windows x86_64 platform.
For detailed information, refer to the proposal for `__wincall`_ on Microsoft Developer Community.
>From 1d58368ca94195606f97abc5c4aafe0e9974909c Mon Sep 17 00:00:00 2001
From: trcrsired <uwgghhbcad at gmail.com>
Date: Sun, 25 May 2025 23:27:58 +0800
Subject: [PATCH 11/36] upload for some changes
---
clang/lib/Parse/ParseDeclCXX.cpp | 1 +
clang/tools/libclang/CXType.cpp | 1 +
2 files changed, 2 insertions(+)
diff --git a/clang/lib/Parse/ParseDeclCXX.cpp b/clang/lib/Parse/ParseDeclCXX.cpp
index 316bc30edf1f04..ab02db9b6a6a9f 100644
--- a/clang/lib/Parse/ParseDeclCXX.cpp
+++ b/clang/lib/Parse/ParseDeclCXX.cpp
@@ -1492,6 +1492,7 @@ bool Parser::isValidAfterTypeSpecifier(bool CouldBeBitfield) {
case tok::kw___stdcall: // struct foo {...} __stdcall x;
case tok::kw___thiscall: // struct foo {...} __thiscall x;
case tok::kw___vectorcall: // struct foo {...} __vectorcall x;
+ case tok::kw___wincall: // struct foo {...} __wincall x;
// We will diagnose these calling-convention specifiers on non-function
// declarations later, so claim they are valid after a type specifier.
return getLangOpts().MicrosoftExt;
diff --git a/clang/tools/libclang/CXType.cpp b/clang/tools/libclang/CXType.cpp
index ffa942d10669c2..0a26799283d439 100644
--- a/clang/tools/libclang/CXType.cpp
+++ b/clang/tools/libclang/CXType.cpp
@@ -707,6 +707,7 @@ CXCallingConv clang_getFunctionTypeCallingConv(CXType X) {
TCALLINGCONV(AArch64VectorCall);
TCALLINGCONV(AArch64SVEPCS);
TCALLINGCONV(Win64);
+ TCALLINGCONV(WinCall);
TCALLINGCONV(X86_64SysV);
TCALLINGCONV(AAPCS);
TCALLINGCONV(AAPCS_VFP);
>From 103cc5deb9746500e37340cd08a75b1fee8e4882 Mon Sep 17 00:00:00 2001
From: trcrsired <uwgghhbcad at gmail.com>
Date: Mon, 26 May 2025 00:00:04 +0800
Subject: [PATCH 12/36] parser should parse wincall
---
clang/lib/Parse/ParseDecl.cpp | 5 +++++
1 file changed, 5 insertions(+)
diff --git a/clang/lib/Parse/ParseDecl.cpp b/clang/lib/Parse/ParseDecl.cpp
index 2f40e5818a9a34..b7454ef23fb076 100644
--- a/clang/lib/Parse/ParseDecl.cpp
+++ b/clang/lib/Parse/ParseDecl.cpp
@@ -1030,6 +1030,7 @@ SourceLocation Parser::SkipExtendedMicrosoftTypeAttributes() {
case tok::kw___thiscall:
case tok::kw___cdecl:
case tok::kw___vectorcall:
+ case tok::kw___wincall:
case tok::kw___ptr32:
case tok::kw___ptr64:
case tok::kw___w64:
@@ -4037,6 +4038,7 @@ void Parser::ParseDeclarationSpecifiers(
case tok::kw___thiscall:
case tok::kw___regcall:
case tok::kw___vectorcall:
+ case tok::kw___wincall:
ParseMicrosoftTypeAttributes(DS.getAttributes());
continue;
@@ -5654,6 +5656,7 @@ bool Parser::isTypeSpecifierQualifier() {
case tok::kw___thiscall:
case tok::kw___regcall:
case tok::kw___vectorcall:
+ case tok::kw___wincall:
case tok::kw___w64:
case tok::kw___ptr64:
case tok::kw___ptr32:
@@ -5934,6 +5937,7 @@ bool Parser::isDeclarationSpecifier(
case tok::kw___thiscall:
case tok::kw___regcall:
case tok::kw___vectorcall:
+ case tok::kw___wincall:
case tok::kw___w64:
case tok::kw___sptr:
case tok::kw___uptr:
@@ -6227,6 +6231,7 @@ void Parser::ParseTypeQualifierListOpt(
case tok::kw___thiscall:
case tok::kw___regcall:
case tok::kw___vectorcall:
+ case tok::kw___wincall:
if (AttrReqs & AR_DeclspecAttributesParsed) {
ParseMicrosoftTypeAttributes(DS.getAttributes());
continue;
>From d31d15fc05b8e9ebe5dfb11534c0b5ea37a992ec Mon Sep 17 00:00:00 2001
From: trcrsired <uwgghhbcad at gmail.com>
Date: Sat, 15 Nov 2025 09:55:21 +0800
Subject: [PATCH 13/36] Missing one case in parsedattr for wincall
---
clang/lib/Sema/SemaDeclAttr.cpp | 1 +
1 file changed, 1 insertion(+)
diff --git a/clang/lib/Sema/SemaDeclAttr.cpp b/clang/lib/Sema/SemaDeclAttr.cpp
index 6d99dd6ec6faf5..23bef3bb1ec7d2 100644
--- a/clang/lib/Sema/SemaDeclAttr.cpp
+++ b/clang/lib/Sema/SemaDeclAttr.cpp
@@ -7513,6 +7513,7 @@ ProcessDeclAttribute(Sema &S, Scope *scope, Decl *D, const ParsedAttr &AL,
case ParsedAttr::AT_CDecl:
case ParsedAttr::AT_FastCall:
case ParsedAttr::AT_ThisCall:
+ case ParsedAttr::AT_WinCall:
case ParsedAttr::AT_Pascal:
case ParsedAttr::AT_RegCall:
case ParsedAttr::AT_SwiftCall:
>From da4e431be48beed2e7ddccc97a9a626b732d8546 Mon Sep 17 00:00:00 2001
From: trcrsired <uwgghhbcad at gmail.com>
Date: Sat, 15 Nov 2025 13:48:26 +0800
Subject: [PATCH 14/36] Add x86.h CC
---
clang/lib/Basic/Targets/X86.h | 1 +
1 file changed, 1 insertion(+)
diff --git a/clang/lib/Basic/Targets/X86.h b/clang/lib/Basic/Targets/X86.h
index e7da2622e78b53..c778bfc1005a73 100644
--- a/clang/lib/Basic/Targets/X86.h
+++ b/clang/lib/Basic/Targets/X86.h
@@ -931,6 +931,7 @@ class LLVM_LIBRARY_VISIBILITY WindowsX86_64TargetInfo
case CC_SwiftAsync:
case CC_X86RegCall:
case CC_DeviceKernel:
+ case CC_WinCall:
return CCCR_OK;
default:
return CCCR_Warning;
>From c08e44600eb13b57f318f057077615d26de4d660 Mon Sep 17 00:00:00 2001
From: trcrsired <oyzawqgcfc at gmail.com>
Date: Tue, 11 Aug 2026 15:42:58 +0800
Subject: [PATCH 15/36] [X86][Clang] Fix wincall calling convention: @win
suffix, independent registers, register returns
Fix several bugs in the wincall (x86_64 APX) calling convention so it
matches the spec in CLAUDE.md:
1. Symbol suffix
Use a uniform '@win' suffix on wincall symbols instead of the stdcall
'@N' parameter-size suffix, so the linker can catch calling convention
mismatches without colliding with stdcall/vectorcall '@0'/'@16'
decorations. Applies to all three ABIs:
- C: foo at win
- C++ MS ABI: ?foo@@YAXXZ at win (mangled as 'A'/cdecl in the calling
convention char, '@win' appended at the
end of the mangled name)
- C++ Itanium: _Z4foov at win
This avoids inventing a 'K' calling convention char that could collide
with MSVC's real encodings.
2. Independent integer/FP register allocation
Replace the Win64-style CCAssignToRegWithShadow pairing (which skipped
e.g. XMM0 when RCX was used for an integer arg) with independent
allocation so all 8 GPRs (RCX,RDX,R8,R9,R16-R19) and all 8 XMM/YMM/ZMM
registers are used independently. This is safe across wincall<->stdcall
calls because each function's convention is fixed at compile time and the
@win suffix catches mismatches.
3. Return convention
Add a dedicated RetCC_X86_Win64_WinCall (wincall previously had no
return CC and silently fell back to Win64). Per the spec:
- scalar <=64 bits -> RAX
- scalar <=128 bits -> RAX (low) + RDX (high)
- non-scalar (float/double/vector) -> XMM0
- user-defined types of length 1,2,4,8,16,32,64,128 bits -> RAX (RDX for
128 bits), and aggregates up to 32 bytes (e.g. 4x size_t, like
std::string/std::vector) returned in RAX, RDX, RCX, R8.
4. Register pass/return of 16/32-byte aggregates
In clang's WinX86_64ABIInfo::classify, pass/return aggregates that fit
in 1,2,4,8,16 or 32 bytes directly in registers instead of by
pointer/sret (MS x64 ABI behavior). __int128 is passed in two integer
registers and returned in RAX+RDX; std::float128_t is handled the same
way.
5. Vector arguments
Remove the 256/512-bit CCPassIndirect rules that shadowed the YMM/ZMM
register rules (dead code) so __m256 passes in YMM0-7 and __m512 in
ZMM0-7 as the spec requires.
---
clang/lib/AST/Mangle.cpp | 7 +++
clang/lib/AST/MicrosoftMangle.cpp | 18 +++++--
clang/lib/CodeGen/Targets/X86.cpp | 32 ++++++++++++
llvm/lib/Target/X86/X86CallingConv.td | 74 ++++++++++++++++++---------
4 files changed, 102 insertions(+), 29 deletions(-)
diff --git a/clang/lib/AST/Mangle.cpp b/clang/lib/AST/Mangle.cpp
index e2a05e59fe7c9a..00b3d08c2179af 100644
--- a/clang/lib/AST/Mangle.cpp
+++ b/clang/lib/AST/Mangle.cpp
@@ -330,6 +330,13 @@ void MangleContext::mangleName(GlobalDecl GD, raw_ostream &Out) {
const FunctionProtoType *Proto = dyn_cast<FunctionProtoType>(FT);
if (CC == CCM_Vector)
Out << '@';
+ if (CC == CCM_WinCall) {
+ // wincall symbols get a @win suffix so the linker can catch calling
+ // convention mismatches, like the @N parameter-size suffix does for
+ // stdcall on i386.
+ Out << "@win";
+ return;
+ }
Out << '@';
if (!Proto) {
Out << '0';
diff --git a/clang/lib/AST/MicrosoftMangle.cpp b/clang/lib/AST/MicrosoftMangle.cpp
index 33559f57bd6710..f62c797c4b35df 100644
--- a/clang/lib/AST/MicrosoftMangle.cpp
+++ b/clang/lib/AST/MicrosoftMangle.cpp
@@ -604,9 +604,14 @@ void MicrosoftCXXNameMangler::mangle(GlobalDecl GD, StringRef Prefix) {
// <mangled-name> ::= ? <name> <type-encoding>
Out << Prefix;
mangleName(GD);
- if (const FunctionDecl *FD = dyn_cast<FunctionDecl>(D))
+ if (const FunctionDecl *FD = dyn_cast<FunctionDecl>(D)) {
mangleFunctionEncoding(GD, Context.shouldMangleDeclName(FD));
- else if (const VarDecl *VD = dyn_cast<VarDecl>(D))
+ // __wincall symbols get a @win suffix so the linker can catch calling
+ // convention mismatches, like the @N parameter-size suffix does for
+ // stdcall on i386.
+ if (FD->getType()->castAs<FunctionType>()->getCallConv() == CC_WinCall)
+ Out << "@win";
+ } else if (const VarDecl *VD = dyn_cast<VarDecl>(D))
mangleVariableEncoding(VD);
else if (isa<MSGuidDecl>(D))
// MSVC appears to mangle GUIDs as if they were variables of type
@@ -3363,6 +3368,11 @@ void MicrosoftCXXNameMangler::mangleCallingConvention(CallingConv CC,
// that keyword. (It didn't actually export them, it just made them so
// that they could be in a DLL and somebody from another module could call
// them.)
+ //
+ // __wincall is mangled as if it were __cdecl ('A') here; the distinct
+ // "@win" suffix is appended to the whole mangled name in
+ // MicrosoftCXXNameMangler::mangle so the linker can catch calling
+ // convention mismatches.
switch (CC) {
default:
@@ -3370,6 +3380,7 @@ void MicrosoftCXXNameMangler::mangleCallingConvention(CallingConv CC,
case CC_Win64:
case CC_X86_64SysV:
case CC_C:
+ case CC_WinCall:
Out << 'A';
return;
case CC_X86Pascal:
@@ -3384,9 +3395,6 @@ void MicrosoftCXXNameMangler::mangleCallingConvention(CallingConv CC,
case CC_X86FastCall:
Out << 'I';
return;
- case CC_WinCall:
- Out << 'K';
- return;
case CC_X86VectorCall:
Out << 'Q';
return;
diff --git a/clang/lib/CodeGen/Targets/X86.cpp b/clang/lib/CodeGen/Targets/X86.cpp
index f0d108f3279fde..7f92099b965757 100644
--- a/clang/lib/CodeGen/Targets/X86.cpp
+++ b/clang/lib/CodeGen/Targets/X86.cpp
@@ -3451,6 +3451,7 @@ ABIArgInfo WinX86_64ABIInfo::classify(QualType Ty, unsigned &FreeSSERegs,
bool IsReturnType, unsigned CC) const {
bool IsVectorCall = CC == llvm::CallingConv::X86_VectorCall;
bool IsRegCall = CC == llvm::CallingConv::X86_RegCall;
+ bool IsWinCall = CC == llvm::CallingConv::X86_WinCall;
if (Ty->isVoidType())
return ABIArgInfo::getIgnore();
@@ -3473,6 +3474,21 @@ ABIArgInfo WinX86_64ABIInfo::classify(QualType Ty, unsigned &FreeSSERegs,
if (RT->getDecl()->getDefinitionOrSelf()->hasFlexibleArrayMember())
return getNaturalAlignIndirect(Ty, getDataLayout().getAllocaAddrSpace(),
/*ByVal=*/false);
+
+ // wincall passes/returns aggregates that fit in 1, 2, 4, 8, 16 or 32 bytes
+ // (e.g. 4x size_t, like std::string/std::vector) directly in registers,
+ // instead of by pointer/sret like the MS x64 ABI.
+ if (IsWinCall && Width <= 256 && !Ty->isAnyComplexType() &&
+ !Ty->isMemberPointerType()) {
+ if (IsReturnType)
+ return ABIArgInfo::getDirect();
+ // Pass as an integer of the aggregate size when it fits in one register,
+ // otherwise expand it into its 8-byte parts.
+ if (Width <= 64)
+ return ABIArgInfo::getDirect(
+ llvm::IntegerType::get(getVMContext(), Width));
+ return ABIArgInfo::getExpand();
+ }
}
const Type *Base = nullptr;
@@ -3548,6 +3564,22 @@ ABIArgInfo WinX86_64ABIInfo::classify(QualType Ty, unsigned &FreeSSERegs,
case BuiltinType::Int128:
case BuiltinType::UInt128:
case BuiltinType::Float128:
+ // wincall passes 128-bit integers in two integer registers and returns
+ // them in RAX (low) and RDX (high), per the spec.
+ if (IsWinCall && BT->getKind() != BuiltinType::Float128) {
+ if (IsReturnType)
+ return ABIArgInfo::getDirect();
+ return ABIArgInfo::getExpand();
+ }
+ if (IsWinCall) {
+ // std::float128_t is passed like __int128: in two integer registers.
+ if (IsReturnType)
+ return ABIArgInfo::getDirect(
+ llvm::FixedVectorType::get(
+ llvm::Type::getInt64Ty(getVMContext()), 2));
+ return ABIArgInfo::getExpand();
+ }
+
// 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.
diff --git a/llvm/lib/Target/X86/X86CallingConv.td b/llvm/lib/Target/X86/X86CallingConv.td
index 0e7986fe34767e..32167bf43cda99 100644
--- a/llvm/lib/Target/X86/X86CallingConv.td
+++ b/llvm/lib/Target/X86/X86CallingConv.td
@@ -389,6 +389,43 @@ def RetCC_X86_Win64_C : CallingConv<[
CCDelegateTo<RetCC_X86_64_C>
]>;
+// X86-Win64 wincall return-value convention.
+//
+// Per the wincall spec:
+// - a scalar return value that fits in 64 bits is returned in RAX,
+// - a scalar return value that fits in 128 bits is returned in RAX (low)
+// and RDX (high),
+// - non-scalar types (float, double, and vector types such as __m128)
+// are returned in XMM0,
+// - user-defined types of length 1, 2, 4, 8, 16, 32, 64 or 128 bits are
+// returned in RAX (RDX for 128 bits).
+def RetCC_X86_Win64_WinCall : CallingConv<[
+ // The first 2 FP/Vector values are returned in XMM0/XMM1 (the spec says
+ // non-scalar types are returned in XMM0).
+ CCIfType<[f16, f32, f64, f128, v16i8, v8i16, v4i32, v2i64, v8f16, v8bf16, v4f32, v2f64],
+ CCIfSubtarget<"hasSSE1()",
+ CCAssignToReg<[XMM0, XMM1]>>>,
+
+ // 256-bit vectors are returned in YMM0.
+ CCIfType<[v32i8, v16i16, v8i32, v4i64, v16f16, v16bf16, v8f32, v4f64],
+ CCIfSubtarget<"hasAVX()",
+ CCAssignToReg<[YMM0]>>>,
+
+ // 512-bit vectors are returned in ZMM0.
+ CCIfType<[v64i8, v32i16, v16i32, v32f16, v32bf16, v16f32, v8f64, v8i64],
+ CCIfSubtarget<"hasAVX512()",
+ CCAssignToReg<[ZMM0]>>>,
+
+ // Scalars and user-defined types of length 1,2,4,8,16,32,64 or 128 bits are
+ // returned in RAX (and RDX for 128-bit values). Aggregates of up to 32 bytes
+ // (e.g. 4x size_t, like std::string/std::vector) are returned in
+ // RAX, RDX, RCX, R8.
+ CCIfType<[i8 ], CCAssignToReg<[AL, DL, CL, R8B]>>,
+ CCIfType<[i16], CCAssignToReg<[AX, DX, CX, R8W]>>,
+ CCIfType<[i32], CCAssignToReg<[EAX, EDX, ECX, R8D]>>,
+ CCIfType<[i64], CCAssignToReg<[RAX, RDX, RCX, R8]>>
+]>;
+
// X86-64 vectorcall return-value convention.
def RetCC_X86_64_Vectorcall : CallingConv<[
// Vectorcall calling convention always returns FP values in XMMs.
@@ -483,6 +520,7 @@ def RetCC_X86_64 : CallingConv<[
// Handle explicit CC selection
CCIfCC<"CallingConv::Win64", CCDelegateTo<RetCC_X86_Win64_C>>,
CCIfCC<"CallingConv::X86_64_SysV", CCDelegateTo<RetCC_X86_64_C>>,
+ CCIfCC<"CallingConv::X86_WinCall", CCDelegateTo<RetCC_X86_Win64_WinCall>>,
// Handle Vectorcall CC
CCIfCC<"CallingConv::X86_VectorCall", CCDelegateTo<RetCC_X86_64_Vectorcall>>,
@@ -1083,12 +1121,6 @@ def CC_X86_Win64_WinCall : CallingConv<[
// The 'CFGuardTarget' parameter, if any, is passed in RAX.
CCIfCFGuardTarget<CCAssignToReg<[RAX]>>,
- // 256 bit vectors are passed by pointer
- CCIfType<[v32i8, v16i16, v8i32, v4i64, v16f16, v16bf16, v8f32, v4f64], CCPassIndirect<i64>>,
-
- // 512 bit vectors are passed by pointer
- CCIfType<[v64i8, v32i16, v16i32, v32f16, v32bf16, v16f32, v8f64, v8i64], CCPassIndirect<i64>>,
-
// Long doubles are passed by pointer
CCIfType<[f80], CCPassIndirect<i64>>,
@@ -1098,33 +1130,27 @@ def CC_X86_Win64_WinCall : CallingConv<[
CCIfType<[f32], CCIfNotSubtarget<"hasSSE1()", CCBitConvertToType<i32>>>,
CCIfType<[f64], CCIfNotSubtarget<"hasSSE1()", CCBitConvertToType<i64>>>,
- // The first 8 FP/Vector arguments are passed in XMM registers.
+ // The first 8 FP/Vector arguments are passed in XMM registers. Integer and
+ // FP arguments are allocated independently (unlike Win64, which pairs each
+ // integer register with an XMM register and skips the partner); this uses
+ // all 8 GPRs and all 8 XMMs of the APX register file.
CCIfType<[f16, f32, f64, v16i8, v8i16, v4i32, v2i64, v8f16, v8bf16, v4f32, v2f64],
CCIfSubtarget<"hasSSE1()",
- CCAssignToRegWithShadow<[XMM0, XMM1, XMM2, XMM3, XMM4, XMM5, XMM6, XMM7],
- [RCX , RDX , R8 , R9 , R16, R17, R18, R19]>>>,
+ CCAssignToReg<[XMM0, XMM1, XMM2, XMM3, XMM4, XMM5, XMM6, XMM7]>>>,
// 256 bit vectors are passed in YMM registers.
CCIfType<[v32i8, v16i16, v8i32, v4i64, v16f16, v16bf16, v8f32, v4f64],
CCIfSubtarget<"hasAVX()",
- CCAssignToRegWithShadow<[YMM0, YMM1, YMM2, YMM3, YMM4, YMM5, YMM6, YMM7],
- [RCX , RDX , R8 , R9 , R16, R17, R18, R19]>>>,
+ CCAssignToReg<[YMM0, YMM1, YMM2, YMM3, YMM4, YMM5, YMM6, YMM7]>>>,
// 512 bit vectors are passed in ZMM registers.
CCIfType<[v64i8, v32i16, v16i32, v32f16, v32bf16, v16f32, v8f64, v8i64],
CCIfSubtarget<"hasAVX512()",
- CCAssignToRegWithShadow<[ZMM0, ZMM1, ZMM2, ZMM3, ZMM4, ZMM5, ZMM6, ZMM7],
- [RCX , RDX , R8 , R9 , R16, R17, R18, R19]>>>,
- // 512 bit vectors are passed by pointer
- CCIfType<[v64i8, v32i16, v16i32, v32f16, v32bf16, v16f32, v8f64, v8i64], CCPassIndirect<i64>>,
+ CCAssignToReg<[ZMM0, ZMM1, ZMM2, ZMM3, ZMM4, ZMM5, ZMM6, ZMM7]>>>,
- // The first 4 integer arguments are passed in integer registers.
- CCIfType<[i8 ], CCAssignToRegWithShadow<[CL , DL , R8B , R9B , R16B, R17B, R18B, R19B],
- [XMM0, XMM1, XMM2, XMM3, XMM4, XMM5, XMM6, XMM7]>>,
- CCIfType<[i16], CCAssignToRegWithShadow<[CX , DX , R8W , R9W , R16W, R17W, R18W, R19W],
- [XMM0, XMM1, XMM2, XMM3, XMM4, XMM5, XMM6, XMM7]>>,
- CCIfType<[i32], CCAssignToRegWithShadow<[ECX , EDX , R8D , R9D , R16D, R17D, R18D, R19D],
- [XMM0, XMM1, XMM2, XMM3, XMM4, XMM5, XMM6, XMM7]>>,
- CCIfType<[i64], CCAssignToRegWithShadow<[RCX , RDX , R8 , R9 , R16, R17, R18, R19],
- [XMM0, XMM1, XMM2, XMM3, XMM4, XMM5, XMM6, XMM7]>>,
+ // The first 8 integer arguments are passed in integer registers.
+ CCIfType<[i8 ], CCAssignToReg<[CL , DL , R8B , R9B , R16B, R17B, R18B, R19B]>>,
+ CCIfType<[i16], CCAssignToReg<[CX , DX , R8W , R9W , R16W, R17W, R18W, R19W]>>,
+ CCIfType<[i32], CCAssignToReg<[ECX , EDX , R8D , R9D , R16D, R17D, R18D, R19D]>>,
+ CCIfType<[i64], CCAssignToReg<[RCX , RDX , R8 , R9 , R16, R17, R18, R19]>>,
// Integer/FP values get stored in stack slots that are 8 bytes in size and
// 8-byte aligned if there are no more registers to hold them.
>From bd737342723e4381d4df5c4a4124e2764e98604d Mon Sep 17 00:00:00 2001
From: trcrsired <oyzawqgcfc at gmail.com>
Date: Tue, 11 Aug 2026 20:48:01 +0800
Subject: [PATCH 16/36] [X86][Clang] Add x86_64apx sub-architecture with
wincall as the Windows default
Treat x86_64apx as a sub-architecture of x86_64 (like arm64ec). The
x86_64apx-windows-msvc/x86_64apx-windows-gnu triples default to the
wincall calling convention and a 64 KiB section alignment passed to the
linker, so that Windows can move to 64 KiB pages (matching the NVIDIA
Grace CPU page size) by adopting this calling convention.
- Triple: add X86_64SubArch_apx, isX86_64APX()/isWindowsAPX(), and
parse/getArchName support for "x86_64apx".
- X86 backend: ParseX86Triple enables the APX features (egpr, push2pop2,
ppx, ndd, ccmp, nf, zu, jmpabs) for the sub-architecture so the
backend works on the triple directly (previously only the clang
frontend enabled them via initFeatureMap, which crashed the LiveVariables
pass when R16-R19 were assigned as wincall argument registers).
- Clang frontend: x86_64apx targets enable the APX features by default.
- getDefaultCallingConv() returns CC_WinCall for isWindowsAPX() targets,
so the wincall calling convention is the default.
- WinX86_64ABIInfo::classify: empty records in wincall take no register
slots (ABIArgInfo::getIgnore()).
- Driver: MSVC and MinGW linkers get /section-alignment:0x10000 (MSVC) /
--section-alignment=0x10000 (MinGW) by default for x86_64apx targets;
a user -Wl section-alignment flag overrides it. The MSVC driver also
passes /driver.
- Tests: Triple unit test, LLVM X86 wincall CC test, clang CodeGen tests
for @win suffix / aggregates / empty structs, clang driver test, clang
Sema test for the wincall attribute.
---
clang/lib/Basic/Targets/X86.cpp | 9 ++
clang/lib/Basic/Targets/X86.h | 3 +
clang/lib/CodeGen/Targets/X86.cpp | 3 +
clang/lib/Driver/ToolChains/MSVC.cpp | 15 +++
clang/lib/Driver/ToolChains/MinGW.cpp | 13 +++
clang/test/CodeGen/X86/wincall-abi.c | 95 +++++++++++++++++++
clang/test/CodeGen/wincall.c | 34 +++++++
clang/test/CodeGenCXX/wincall-mangle.cpp | 34 +++++++
clang/test/Driver/wincall-x86_64apx.c | 23 +++++
clang/test/Sema/callingconv-wincall.c | 10 ++
llvm/include/llvm/TargetParser/Triple.h | 14 +++
.../X86/MCTargetDesc/X86MCTargetDesc.cpp | 4 +
llvm/lib/TargetParser/Triple.cpp | 8 +-
llvm/test/CodeGen/X86/wincall-cconv.ll | 48 ++++++++++
llvm/unittests/TargetParser/TripleTest.cpp | 27 ++++++
15 files changed, 339 insertions(+), 1 deletion(-)
create mode 100644 clang/test/CodeGen/X86/wincall-abi.c
create mode 100644 clang/test/CodeGen/wincall.c
create mode 100644 clang/test/CodeGenCXX/wincall-mangle.cpp
create mode 100644 clang/test/Driver/wincall-x86_64apx.c
create mode 100644 clang/test/Sema/callingconv-wincall.c
create mode 100644 llvm/test/CodeGen/X86/wincall-cconv.ll
diff --git a/clang/lib/Basic/Targets/X86.cpp b/clang/lib/Basic/Targets/X86.cpp
index 8ab39b750dc992..6ed62b205c4b70 100644
--- a/clang/lib/Basic/Targets/X86.cpp
+++ b/clang/lib/Basic/Targets/X86.cpp
@@ -157,6 +157,15 @@ bool X86TargetInfo::initFeatureMap(
if (getTriple().getArch() == llvm::Triple::x86_64)
setFeatureEnabled(Features, "sse2", true);
+ // x86_64apx enables the APX features by default. LLVM's X86 backend models
+ // APX as the individual egpr/push2pop2/ppx/ndd/ccmp/nf/zu/jmpabs features
+ // (there is no single "apxf" feature), so enable each of them.
+ if (getTriple().isX86_64APX()) {
+ for (const char *Sub : {"egpr", "push2pop2", "ppx", "ndd", "ccmp", "nf",
+ "zu", "jmpabs"})
+ setFeatureEnabled(Features, Sub, true);
+ }
+
using namespace llvm::X86;
SmallVector<StringRef, 16> CPUFeatures;
diff --git a/clang/lib/Basic/Targets/X86.h b/clang/lib/Basic/Targets/X86.h
index b37b3a41702703..00e0b93b12d1c2 100644
--- a/clang/lib/Basic/Targets/X86.h
+++ b/clang/lib/Basic/Targets/X86.h
@@ -769,6 +769,9 @@ class LLVM_LIBRARY_VISIBILITY X86_64TargetInfo : public X86TargetInfo {
}
CallingConv getDefaultCallingConv() const override {
+ // x86_64apx targets default to the wincall calling convention.
+ if (getTriple().isWindowsAPX())
+ return CC_WinCall;
return CC_C;
}
diff --git a/clang/lib/CodeGen/Targets/X86.cpp b/clang/lib/CodeGen/Targets/X86.cpp
index 7f92099b965757..8ebcc857bac8a7 100644
--- a/clang/lib/CodeGen/Targets/X86.cpp
+++ b/clang/lib/CodeGen/Targets/X86.cpp
@@ -3480,6 +3480,9 @@ ABIArgInfo WinX86_64ABIInfo::classify(QualType Ty, unsigned &FreeSSERegs,
// instead of by pointer/sret like the MS x64 ABI.
if (IsWinCall && Width <= 256 && !Ty->isAnyComplexType() &&
!Ty->isMemberPointerType()) {
+ // Empty C++ objects take no register slots.
+ if (isEmptyRecord(getContext(), Ty, /*AllowArrays=*/true))
+ return ABIArgInfo::getIgnore();
if (IsReturnType)
return ABIArgInfo::getDirect();
// Pass as an integer of the aggregate size when it fits in one register,
diff --git a/clang/lib/Driver/ToolChains/MSVC.cpp b/clang/lib/Driver/ToolChains/MSVC.cpp
index 84bc2db6d2913f..0eb6637687dccf 100644
--- a/clang/lib/Driver/ToolChains/MSVC.cpp
+++ b/clang/lib/Driver/ToolChains/MSVC.cpp
@@ -372,6 +372,21 @@ void visualstudio::Linker::ConstructJob(Compilation &C, const JobAction &JA,
Output.getFilename() + "_dwo"));
}
+ // x86_64apx targets default to a 64 KiB section alignment (matching the
+ // NVIDIA Grace CPU's 64 KiB page size) so that Windows can move to 64 KiB
+ // pages by adopting this calling convention. The user can still override it
+ // with their own -Wl,/section-alignment: flag.
+ if (TC.getTriple().isX86_64APX()) {
+ bool HasExplicitSectionAlignment = false;
+ for (Arg *A : Args.filtered(options::OPT_Wl_COMMA, options::OPT__SLASH_link))
+ if (StringRef(A->getValue()).contains_insensitive("section-alignment"))
+ HasExplicitSectionAlignment = true;
+ if (!HasExplicitSectionAlignment) {
+ CmdArgs.push_back("/section-alignment:0x10000");
+ CmdArgs.push_back("/driver");
+ }
+ }
+
// Add filenames, libraries, and other linker inputs.
for (const auto &Input : Inputs) {
if (Input.isFilename()) {
diff --git a/clang/lib/Driver/ToolChains/MinGW.cpp b/clang/lib/Driver/ToolChains/MinGW.cpp
index 65120931d81817..28a883b49d402e 100644
--- a/clang/lib/Driver/ToolChains/MinGW.cpp
+++ b/clang/lib/Driver/ToolChains/MinGW.cpp
@@ -168,6 +168,19 @@ void tools::MinGW::Linker::ConstructJob(Compilation &C, const JobAction &JA,
CmdArgs.push_back("console");
}
+ // x86_64apx targets default to a 64 KiB section alignment (matching the
+ // NVIDIA Grace CPU's 64 KiB page size) so that Windows can move to 64 KiB
+ // pages by adopting this calling convention. The user can still override it
+ // with their own -Wl,--section-alignment flag.
+ if (TC.getEffectiveTriple().isX86_64APX()) {
+ bool HasExplicitSectionAlignment = false;
+ for (Arg *A : Args.filtered(options::OPT_Wl_COMMA, options::OPT_Xlinker))
+ if (StringRef(A->getValue()).contains_insensitive("section-alignment"))
+ HasExplicitSectionAlignment = true;
+ if (!HasExplicitSectionAlignment)
+ CmdArgs.push_back("--section-alignment=0x10000");
+ }
+
if (Args.hasArg(options::OPT_mdll))
CmdArgs.push_back("--dll");
else if (Args.hasArg(options::OPT_shared))
diff --git a/clang/test/CodeGen/X86/wincall-abi.c b/clang/test/CodeGen/X86/wincall-abi.c
new file mode 100644
index 00000000000000..8a7327e9136fc5
--- /dev/null
+++ b/clang/test/CodeGen/X86/wincall-abi.c
@@ -0,0 +1,95 @@
+// RUN: %clang_cc1 -triple x86_64apx-unknown-windows-msvc -o - -emit-llvm %s | FileCheck %s
+// RUN: %clang_cc1 -triple x86_64apx-unknown-windows-msvc -o - -S %s | FileCheck -check-prefix=ASM %s
+
+// wincall ABI for x86_64apx-windows targets:
+// - empty objects take no register slots
+// - aggregates <= 32 bytes are passed and returned in registers
+// - larger aggregates use sret / indirect passing
+
+struct empty {};
+
+__attribute__((wincall)) void take_empty(struct empty e, int x);
+
+__attribute__((wincall)) struct empty make_empty(void);
+
+void call_empty(void) {
+ struct empty e;
+ take_empty(e, 42);
+ // CHECK-LABEL: define dso_local x86_wincallcc void @"\01call_empty at win"
+ // CHECK: call x86_wincallcc void @"\01take_empty at win"(i32 noundef 42)
+ make_empty();
+ // CHECK: call x86_wincallcc void @"\01make_empty at win"()
+ // CHECK: declare dso_local x86_wincallcc void @"\01take_empty at win"(i32 noundef)
+ // CHECK: declare dso_local x86_wincallcc void @"\01make_empty at win"()
+}
+
+__attribute__((wincall)) void take_empty2(struct empty e, int x) {}
+// CHECK-LABEL: define dso_local x86_wincallcc void @"\01take_empty2 at win"(i32 noundef %x)
+
+struct span2 {
+ unsigned long long *base;
+ unsigned long long len;
+};
+
+__attribute__((wincall)) struct span2 make_span(void) {
+ struct span2 s = {0, 1};
+ return s;
+}
+// CHECK-LABEL: define dso_local x86_wincallcc %struct.span2 @"\01make_span at win"
+// ASM-LABEL: make_span at win:
+// ASM: movq (%rsp), %rax
+// ASM: movq 8(%rsp), %rdx
+
+struct vec4 {
+ unsigned long long a, b, c, d;
+};
+
+__attribute__((wincall)) struct vec4 make_vec4(void) {
+ struct vec4 v = {1, 2, 3, 4};
+ return v;
+}
+// CHECK-LABEL: define dso_local x86_wincallcc %struct.vec4 @"\01make_vec4 at win"
+// ASM-LABEL: make_vec4 at win:
+// ASM: movq (%rsp), %rax
+// ASM: movq 8(%rsp), %rdx
+// ASM: movq 16(%rsp), %rcx
+// ASM: movq 24(%rsp), %r8
+
+struct big32 {
+ unsigned long long a[5];
+};
+
+__attribute__((wincall)) struct big32 make_big(void) {
+ struct big32 b = {{1, 2, 3, 4, 5}};
+ return b;
+}
+// CHECK-LABEL: define dso_local x86_wincallcc void @"\01make_big at win"(ptr dead_on_unwind noalias writable sret(%struct.big32) align 8 %agg.result)
+// ASM-LABEL: make_big at win:
+
+// FP and integer registers are allocated independently: a double between two
+// ints does not consume or skip an integer register.
+__attribute__((wincall)) void take_mixed(int a, double b, int c, double d,
+ int e, double f) {
+ volatile int sink1 = a + c + e;
+ volatile double sink2 = b + d + f;
+ (void)sink1;
+ (void)sink2;
+}
+// CHECK-LABEL: define dso_local x86_wincallcc void @"\01take_mixed at win"(i32 noundef %a, double noundef %b, i32 noundef %c, double noundef %d, i32 noundef %e, double noundef %f)
+void call_mixed(void) {
+ take_mixed(1, 2.0, 3, 4.0, 5, 6.0);
+ // CHECK-LABEL: define dso_local x86_wincallcc void @"\01call_mixed at win"
+ // CHECK: call x86_wincallcc void @"\01take_mixed at win"(i32 noundef 1, double noundef 2.000000e+00, i32 noundef 3, double noundef 4.000000e+00, i32 noundef 5, double noundef 6.000000e+00)
+}
+// ASM-LABEL: take_mixed at win:
+// ASM: movsd %xmm2, 48(%rsp)
+// ASM: movl %r8d, 44(%rsp)
+// ASM: movsd %xmm1, 32(%rsp)
+// ASM: movl %edx, 28(%rsp)
+// ASM: movsd %xmm0, 16(%rsp)
+// ASM: movl %ecx, 12(%rsp)
+
+__attribute__((wincall)) __int128 make_i128(void) { return (__int128)1 << 64 | 2; }
+// ASM-LABEL: make_i128 at win:
+// ASM: movl $2, %eax
+// ASM: movl $1, %edx
diff --git a/clang/test/CodeGen/wincall.c b/clang/test/CodeGen/wincall.c
new file mode 100644
index 00000000000000..61dbb1c6a8dda8
--- /dev/null
+++ b/clang/test/CodeGen/wincall.c
@@ -0,0 +1,34 @@
+// RUN: %clang_cc1 -triple x86_64apx-unknown-windows-msvc -emit-llvm -o - %s | FileCheck -check-prefix=MSVC %s
+// RUN: %clang_cc1 -triple x86_64apx-unknown-windows-gnu -emit-llvm -o - %s | FileCheck -check-prefix=GNU %s
+
+// The wincall calling convention is the default for x86_64apx-windows targets
+// and appends a @win suffix to the symbol so the linker can catch calling
+// convention mismatches.
+
+void plain(int, int, int);
+
+void __attribute__((wincall)) wc(int, int, int);
+
+void caller(void) {
+ // MSVC-LABEL: define dso_local x86_wincallcc void @"\01caller at win"
+ // GNU-LABEL: define dso_local x86_wincallcc void @"\01caller at win"
+ plain(1, 2, 3);
+ // MSVC: call x86_wincallcc void @"\01plain at win"(i32 noundef 1, i32 noundef 2, i32 noundef 3)
+ // GNU: call x86_wincallcc void @"\01plain at win"(i32 noundef 1, i32 noundef 2, i32 noundef 3)
+ wc(1, 2, 3);
+ // MSVC: call x86_wincallcc void @"\01wc at win"(i32 noundef 1, i32 noundef 2, i32 noundef 3)
+ // GNU: call x86_wincallcc void @"\01wc at win"(i32 noundef 1, i32 noundef 2, i32 noundef 3)
+}
+
+void plain(int a, int b, int c) {
+ // MSVC-LABEL: define dso_local x86_wincallcc void @"\01plain at win"
+ // GNU-LABEL: define dso_local x86_wincallcc void @"\01plain at win"
+ wc(a, b, c);
+ // MSVC: call x86_wincallcc void @"\01wc at win"
+ // GNU: call x86_wincallcc void @"\01wc at win"
+}
+
+void __attribute__((wincall)) wc(int a, int b, int c) {
+ // MSVC-LABEL: define dso_local x86_wincallcc void @"\01wc at win"
+ // GNU-LABEL: define dso_local x86_wincallcc void @"\01wc at win"
+}
diff --git a/clang/test/CodeGenCXX/wincall-mangle.cpp b/clang/test/CodeGenCXX/wincall-mangle.cpp
new file mode 100644
index 00000000000000..8efb01f7296321
--- /dev/null
+++ b/clang/test/CodeGenCXX/wincall-mangle.cpp
@@ -0,0 +1,34 @@
+// RUN: %clang_cc1 -triple x86_64apx-unknown-windows-gnu -o - -emit-llvm %s | FileCheck %s
+
+// wincall is the default calling convention for x86_64apx-windows targets.
+// C++ symbols get a @win suffix (both Itanium and MSVC-style manglings) so
+// that the linker can catch calling-convention mismatches, and function
+// pointer types carry the wincall vendor qualifier in the Itanium mangling.
+
+typedef void(__attribute__((wincall)) *W)(int);
+
+struct C {
+ void __attribute__((wincall)) m(int);
+};
+
+void C::m(int a) {
+ // CHECK-LABEL: define dso_local x86_wincallcc void @"\01_ZN1C1mEi at win"
+ (void)a;
+}
+
+void f(int);
+
+void g() {
+ // CHECK-LABEL: define dso_local x86_wincallcc void @"\01_Z1gv at win"
+ f(1);
+ // CHECK: call x86_wincallcc void @"\01_Z1fi at win"
+}
+
+// Function pointer types get a U7wincall vendor qualifier.
+template <typename T> T func_as_int(T x);
+W w;
+W test() {
+ // CHECK-LABEL: define dso_local x86_wincallcc noundef ptr @"\01_Z4testv at win"
+ // CHECK: call x86_wincallcc noundef ptr @"\01_Z11func_as_intIPU7wincallFviEET_S2_ at win"
+ return func_as_int<W>(w);
+}
diff --git a/clang/test/Driver/wincall-x86_64apx.c b/clang/test/Driver/wincall-x86_64apx.c
new file mode 100644
index 00000000000000..1d1680857917ac
--- /dev/null
+++ b/clang/test/Driver/wincall-x86_64apx.c
@@ -0,0 +1,23 @@
+// x86_64apx-windows targets default to the wincall calling convention and pass
+// a 64 KiB section alignment to the linker.
+
+// RUN: %clang --target=x86_64apx-unknown-windows-msvc -### %s 2>&1 | FileCheck --check-prefix=MSVC-APX %s
+// RUN: %clang --target=x86_64apx-unknown-windows-gnu -### %s 2>&1 | FileCheck --check-prefix=GNU-APX %s
+
+// Non-APX Windows targets do not get the section alignment flag.
+// RUN: %clang --target=x86_64-unknown-windows-msvc -### %s 2>&1 | FileCheck --check-prefix=MSVC-PLAIN %s
+// RUN: %clang --target=x86_64-unknown-windows-gnu -### %s 2>&1 | FileCheck --check-prefix=GNU-PLAIN %s
+
+// MSVC-APX: link.exe"
+// MSVC-APX-SAME: "-nologo" "/section-alignment:0x10000" "/driver"
+// MSVC-PLAIN-NOT: section-alignment
+// GNU-APX: "--section-alignment=0x10000"
+// GNU-PLAIN-NOT: section-alignment
+
+// User-provided -Wl section-alignment overrides the default.
+// RUN: %clang --target=x86_64apx-unknown-windows-msvc -Wl,/section-alignment:0x20000 -### %s 2>&1 | FileCheck --check-prefix=MSVC-OVERRIDE %s
+// RUN: %clang --target=x86_64apx-unknown-windows-gnu -Wl,--section-alignment=0x20000 -### %s 2>&1 | FileCheck --check-prefix=GNU-OVERRIDE %s
+// MSVC-OVERRIDE-NOT: "/section-alignment:0x10000"
+// MSVC-OVERRIDE: "/section-alignment:0x20000"
+// GNU-OVERRIDE-NOT: --section-alignment=0x10000
+// GNU-OVERRIDE: --section-alignment=0x20000
diff --git a/clang/test/Sema/callingconv-wincall.c b/clang/test/Sema/callingconv-wincall.c
new file mode 100644
index 00000000000000..3ae621b0a7e941
--- /dev/null
+++ b/clang/test/Sema/callingconv-wincall.c
@@ -0,0 +1,10 @@
+// RUN: %clang_cc1 -fsyntax-only -verify -triple x86_64apx-pc-windows-msvc %s
+
+// wincall is the default calling convention on x86_64apx-windows targets.
+void __attribute__((wincall)) foo(void);
+void __attribute__((cdecl)) cdeclfoo(void);
+
+void (*pw)(void) = foo; // no error: plain function pointers are wincall by default
+void (*pc)(void) = cdeclfoo; // expected-error{{incompatible function pointer types}}
+
+void (__attribute__((wincall)) *pw2)(void) = foo; // no error: same calling convention
diff --git a/llvm/include/llvm/TargetParser/Triple.h b/llvm/include/llvm/TargetParser/Triple.h
index 18634a8bbe6ff8..9af359c373b308 100644
--- a/llvm/include/llvm/TargetParser/Triple.h
+++ b/llvm/include/llvm/TargetParser/Triple.h
@@ -161,6 +161,7 @@ class Triple {
AArch64SubArch_lfi,
X86_64SubArch_lfi,
+ X86_64SubArch_apx,
KalimbaSubArch_v3,
KalimbaSubArch_v4,
@@ -1009,6 +1010,19 @@ class Triple {
getSubArch() == Triple::X86_64SubArch_lfi);
}
+ /// Tests whether the target is x86-64 with Intel APX (wincall), i.e. the
+ /// x86_64apx architecture.
+ bool isX86_64APX() const {
+ return getArch() == Triple::x86_64 &&
+ getSubArch() == Triple::X86_64SubArch_apx;
+ }
+
+ /// Tests whether the target is the Windows x86_64apx target, which defaults
+ /// to the wincall calling convention.
+ bool isWindowsAPX() const {
+ return isX86_64APX() && isOSWindows();
+ }
+
/// Tests whether the target supports the EHABI exception
/// handling standard.
bool isTargetEHABICompatible() const {
diff --git a/llvm/lib/Target/X86/MCTargetDesc/X86MCTargetDesc.cpp b/llvm/lib/Target/X86/MCTargetDesc/X86MCTargetDesc.cpp
index e6addc00971f5e..6f56386e4f96c5 100644
--- a/llvm/lib/Target/X86/MCTargetDesc/X86MCTargetDesc.cpp
+++ b/llvm/lib/Target/X86/MCTargetDesc/X86MCTargetDesc.cpp
@@ -59,6 +59,10 @@ std::string X86_MC::ParseX86Triple(const Triple &TT) {
if (TT.isX32())
FS += ",+x32";
+ if (TT.getSubArch() == Triple::X86_64SubArch_apx) {
+ FS += ",+egpr,+push2pop2,+ppx,+ndd,+ccmp,+nf,+zu,+jmpabs";
+ }
+
return FS;
}
diff --git a/llvm/lib/TargetParser/Triple.cpp b/llvm/lib/TargetParser/Triple.cpp
index 1e421c428d8b40..5d11c352a01a1f 100644
--- a/llvm/lib/TargetParser/Triple.cpp
+++ b/llvm/lib/TargetParser/Triple.cpp
@@ -198,6 +198,8 @@ StringRef Triple::getArchName(ArchType Kind, SubArchType SubArch) {
case Triple::x86_64:
if (SubArch == X86_64SubArch_lfi)
return "x86_64_lfi";
+ if (SubArch == X86_64SubArch_apx)
+ return "x86_64apx";
break;
case Triple::spirv:
switch (SubArch) {
@@ -598,7 +600,8 @@ Triple::ArchType Triple::parseArch(StringRef ArchName) {
.Cases({"i386", "i486", "i586", "i686"}, Triple::x86)
// FIXME: Do we need to support these?
.Cases({"i786", "i886", "i986"}, Triple::x86)
- .Cases({"amd64", "x86_64", "x86_64h", "x86_64_lfi"}, Triple::x86_64)
+ .Cases({"amd64", "x86_64", "x86_64h", "x86_64_lfi", "x86_64apx"},
+ Triple::x86_64)
.Cases({"powerpc", "powerpcspe", "ppc", "ppc32"}, Triple::ppc)
.Cases({"powerpcle", "ppcle", "ppc32le"}, Triple::ppcle)
.Cases({"powerpc64", "ppu", "ppc64"}, Triple::ppc64)
@@ -750,6 +753,9 @@ Triple::SubArchType Triple::parseSubArch(StringRef SubArchName) {
if (SubArchName == "x86_64_lfi")
return Triple::X86_64SubArch_lfi;
+ if (SubArchName == "x86_64apx")
+ return Triple::X86_64SubArch_apx;
+
if (SubArchName.starts_with("spirv"))
return StringSwitch<Triple::SubArchType>(SubArchName)
.EndsWith("v1.0", Triple::SPIRVSubArch_v10)
diff --git a/llvm/test/CodeGen/X86/wincall-cconv.ll b/llvm/test/CodeGen/X86/wincall-cconv.ll
new file mode 100644
index 00000000000000..f3fe3b40a23f8d
--- /dev/null
+++ b/llvm/test/CodeGen/X86/wincall-cconv.ll
@@ -0,0 +1,48 @@
+; RUN: llc -mtriple=x86_64apx-unknown-windows-msvc < %s | FileCheck %s
+
+; The wincall calling convention (x86_64 APX, default for x86_64apx-windows)
+; passes the first 8 integer args in RCX,RDX,R8,R9,R16,R17,R18,R19 and the
+; first 8 FP/vector args in XMM0-XMM7, allocated independently (no Win64-style
+; register pairing/skipping).
+
+declare x86_wincallcc void @wincall_thunk(i64, i64, i64, i64, i64, i64, i64, i64)
+
+; CHECK-LABEL: call_8_int:
+; CHECK: subq $40, %rsp
+; CHECK-NEXT: movq 80(%rsp), %r16
+; CHECK-NEXT: movq 88(%rsp), %r17
+; CHECK-NEXT: movq 96(%rsp), %r18
+; CHECK-NEXT: movq 104(%rsp), %r19
+; CHECK-NEXT: callq wincall_thunk
+; CHECK-NEXT: addq $40, %rsp
+; CHECK-NEXT: retq
+define void @call_8_int(i64 %a, i64 %b, i64 %c, i64 %d,
+ i64 %e, i64 %f, i64 %g, i64 %h) nounwind {
+entry:
+ call x86_wincallcc void @wincall_thunk(i64 %a, i64 %b, i64 %c, i64 %d,
+ i64 %e, i64 %f, i64 %g, i64 %h)
+ ret void
+}
+
+; Callee side: the first 8 integer args arrive in RCX,RDX,R8,R9,R16,R17,R18,R19.
+; CHECK-LABEL: sum8:
+; CHECK: addq %rdx, %rcx
+; CHECK-NEXT: addq %r9, %r8
+; CHECK-NEXT: addq %r8, %rcx
+; CHECK-NEXT: addq %r17, %r16
+; CHECK-NEXT: addq %r18, %r16
+; CHECK-NEXT: addq %r16, %rcx
+; CHECK-NEXT: addq %r19, %rax
+; CHECK-NEXT: retq
+define x86_wincallcc i64 @sum8(i64 %a, i64 %b, i64 %c, i64 %d,
+ i64 %e, i64 %f, i64 %g, i64 %h) nounwind {
+entry:
+ %s1 = add i64 %a, %b
+ %s2 = add i64 %s1, %c
+ %s3 = add i64 %s2, %d
+ %s4 = add i64 %s3, %e
+ %s5 = add i64 %s4, %f
+ %s6 = add i64 %s5, %g
+ %s7 = add i64 %s6, %h
+ ret i64 %s7
+}
diff --git a/llvm/unittests/TargetParser/TripleTest.cpp b/llvm/unittests/TargetParser/TripleTest.cpp
index 219e3ce9c0baf8..7484cf46beb386 100644
--- a/llvm/unittests/TargetParser/TripleTest.cpp
+++ b/llvm/unittests/TargetParser/TripleTest.cpp
@@ -4027,4 +4027,31 @@ TEST(DataLayoutTest, CheriRISCV32) {
testing::HasSubstr("A200-P200-G200"));
}
+TEST(TripleTest, X86_64APX) {
+ {
+ Triple T = Triple("x86_64apx-pc-windows-msvc");
+ EXPECT_EQ(Triple::x86_64, T.getArch());
+ EXPECT_EQ(Triple::X86_64SubArch_apx, T.getSubArch());
+ EXPECT_TRUE(T.isX86_64APX());
+ EXPECT_TRUE(T.isWindowsAPX());
+ }
+ {
+ Triple T = Triple("x86_64apx-pc-linux-gnu");
+ EXPECT_EQ(Triple::x86_64, T.getArch());
+ EXPECT_EQ(Triple::X86_64SubArch_apx, T.getSubArch());
+ EXPECT_TRUE(T.isX86_64APX());
+ EXPECT_FALSE(T.isWindowsAPX());
+ }
+ {
+ Triple T;
+ T.setArch(Triple::x86_64, Triple::X86_64SubArch_apx);
+ EXPECT_EQ("x86_64apx", T.getArchName());
+ }
+ {
+ Triple T = Triple("x86_64-pc-windows-msvc");
+ EXPECT_FALSE(T.isX86_64APX());
+ EXPECT_FALSE(T.isWindowsAPX());
+ }
+}
+
} // end anonymous namespace
>From a4b2e975b368d738ceb16b510eaca61f705468ea Mon Sep 17 00:00:00 2001
From: trcrsired <oyzawqgcfc at gmail.com>
Date: Tue, 11 Aug 2026 22:26:01 +0800
Subject: [PATCH 17/36] [Docs] Add documentation for the WinCall calling
convention
Add a dedicated WinCall.md page describing the x86-64 wincall calling
convention (x86_wincallcc / CallingConv::X86_WinCall, CC ID 128):
- what WinCall is and when it is used (default for x86_64apx-windows-*
triples, and the wincall attribute)
- the x86_64apx sub-architecture and the APX feature set it enables
- argument passing: 8 GPRs (RCX/RDX/R8/R9/R16-R19), 8 XMM/YMM/ZMM
registers allocated independently of the integer registers, aggregate
and empty-object rules
- return values per RetCC_X86_Win64_WinCall
- callee-saved registers (CSR_Win64_APX)
- the @win symbol decoration and the U7wincall Itanium vendor qualifier
- the 64 KiB section alignment passed by the clang driver
Also cross-reference WinCall from LangRef, CodeGenerator and UserGuides,
and replace the wincall attribute documentation in AttrDocs.td (removing
the unrelated Microsoft Developer Community proposal link) with an
accurate summary that points to the new page.
---
clang/include/clang/Basic/AttrDocs.td | 26 ++-
llvm/docs/CodeGenerator.md | 5 +
llvm/docs/LangRef.md | 4 +
llvm/docs/UserGuides.md | 6 +
llvm/docs/WinCall.md | 239 ++++++++++++++++++++++++++
5 files changed, 273 insertions(+), 7 deletions(-)
create mode 100644 llvm/docs/WinCall.md
diff --git a/clang/include/clang/Basic/AttrDocs.td b/clang/include/clang/Basic/AttrDocs.td
index 6ab891bae59ab3..2ed04e43272b45 100644
--- a/clang/include/clang/Basic/AttrDocs.td
+++ b/clang/include/clang/Basic/AttrDocs.td
@@ -3752,13 +3752,25 @@ COM applications. See the documentation for `__stdcall`_ on MSDN.
def WinCallDocs : Documentation {
let Category = DocCatCallingConvs;
let Content = [{
-Wincall introduces a new calling convention for x86_64, designed to support Intel APX.
-This convention addresses calling convention challenges for C++ types, such as `std::span`,
-on the Windows x86_64 platform.
-
-For detailed information, refer to the proposal for `__wincall`_ on Microsoft Developer Community.
-
-.. _`__wincall`: https://developercommunity.visualstudio.com/t/I-present-a-novel-calling-convention-nam/10433601?q=wincall
+The ``wincall`` attribute applies the WinCall calling convention to a
+function. WinCall is an x86-64-only calling convention for Windows targets
+that makes use of the registers added by Intel APX.
+
+Unlike the Microsoft x64 ABI, WinCall:
+- passes the first eight integer arguments in RCX, RDX, R8, R9, R16, R17,
+ R18, R19 (R16-R19 are APX registers),
+- passes the first eight floating-point/vector arguments in XMM0-XMM7
+ (YMM0-YMM7 for ``__m256``, ZMM0-ZMM7 for ``__m512``), allocated
+ independently of the integer registers,
+- passes/returns aggregates up to 32 bytes directly in registers instead of
+ by pointer, and
+- takes no register slots for empty C++ objects.
+
+On targets where WinCall is not supported the attribute is ignored with a
+warning.
+
+See the `LLVM WinCall documentation <https://llvm.org/docs/WinCall.html>`_ for
+the full specification.
}];
}
diff --git a/llvm/docs/CodeGenerator.md b/llvm/docs/CodeGenerator.md
index 32242b413c2a86..6aefe48b124f06 100644
--- a/llvm/docs/CodeGenerator.md
+++ b/llvm/docs/CodeGenerator.md
@@ -1903,6 +1903,11 @@ The following target-specific calling conventions are known to backend:
others via stack. Callee is responsible for stack cleaning. This convention is
used by MSVC by default for methods in its ABI (CC ID = 70).
+* **x86_WinCall** --- The WinCall calling convention for Windows x86-64 (APX).
+ It is the default calling convention for the ``x86_64apx-windows-*`` triples
+ and passes up to eight integer and eight FP/SIMD arguments in registers,
+ allocated independently. See {doc}`WinCall` for details (CC ID = 128).
+
(X86 addressing mode)=
#### Representing X86 addressing modes in MachineInstrs
diff --git a/llvm/docs/LangRef.md b/llvm/docs/LangRef.md
index 3a616e8a29fcf0..ff9836fd1d33e2 100644
--- a/llvm/docs/LangRef.md
+++ b/llvm/docs/LangRef.md
@@ -535,6 +535,10 @@ More calling conventions can be added/defined on an as-needed basis, to
support Pascal conventions or any other well-known target-independent
convention.
+The target-specific calling conventions are documented in their respective
+target documentation. In particular, the X86 WinCall calling convention
+(spelled ``x86_wincallcc`` in IR) is described in {doc}`WinCall`.
+
(visibilitystyles)=
### Visibility Styles
diff --git a/llvm/docs/UserGuides.md b/llvm/docs/UserGuides.md
index 108601427697cc..0189e64c9efcac 100644
--- a/llvm/docs/UserGuides.md
+++ b/llvm/docs/UserGuides.md
@@ -72,6 +72,7 @@ RISCV/RISCVVCIX
SandboxIR
Telemetry
LFI
+WinCall
AdminTasks
Benchmarking
CMakePrimer
@@ -402,3 +403,8 @@ yaml2obj
- {doc}`LFI <LFI>`
This document describes the Lightweight Fault Isolation (LFI) target in LLVM.
+
+- {doc}`WinCall <WinCall>`
+
+ This document describes the WinCall calling convention for Windows x86-64
+ (APX), including the `x86_64apx` sub-architecture.
diff --git a/llvm/docs/WinCall.md b/llvm/docs/WinCall.md
new file mode 100644
index 00000000000000..2049dcab3d4439
--- /dev/null
+++ b/llvm/docs/WinCall.md
@@ -0,0 +1,239 @@
+# The WinCall Calling Convention
+
+## What WinCall is
+
+WinCall is an x86-64 **calling convention for Windows targets**. It is
+spelled ``x86_wincallcc`` in LLVM IR and corresponds to
+``CallingConv::X86_WinCall`` (CC ID 128).
+
+Its purpose is to exploit the 16 additional general-purpose registers
+(R16-R31) introduced by Intel APX. On a conventional Windows x64 ABI only
+four integer arguments can be passed in registers; WinCall doubles that to
+eight by using R16-R19 as argument registers. It also relaxes the aggregate
+passing and return rules of the Microsoft x64 ABI so that C++ types such as
+``std::span``, ``std::string`` and ``std::vector`` (a ``{pointer, size}``
+pair or a four-word object) can travel in registers.
+
+WinCall does **not** replace any existing ABI. Existing Windows APIs keep
+their ``stdcall``, ``cdecl`` and ``fastcall`` conventions; the convention is
+opt-in and only affects code that is explicitly built for it.
+
+## When WinCall is used
+
+WinCall applies in two situations:
+
+1. It is the **default calling convention** of the ``x86_64apx-windows-msvc``
+ and ``x86_64apx-windows-gnu`` target triples
+ (``X86_64TargetInfo::getDefaultCallingConv`` returns ``CC_WinCall`` for
+ ``getTriple().isWindowsAPX()``).
+
+2. It can be selected per-function with the ``wincall`` attribute
+ (``__attribute__((wincall))``, ``__wincall``, ``_wincall``), which maps to
+ ``CC_WinCall`` in the frontend.
+
+WinCall is **x86-64 only**. On 32-bit x86 or on any non-x86 target the
+attribute is ignored with a warning
+(``X86TargetInfo::checkCallingConvention`` does not accept it there).
+
+## The x86_64apx sub-architecture
+
+The ``x86_64apx`` triple component is a *sub-architecture* of ``x86_64``,
+modelled on how ``arm64ec`` is a sub-architecture of ``aarch64``:
+
+- ``Triple::X86_64SubArch_apx`` records it; ``getArch()`` still returns
+ ``Triple::x86_64``.
+- ``Triple::isX86_64APX()`` is true for any ``x86_64apx-*`` triple.
+- ``Triple::isWindowsAPX()`` is true for ``x86_64apx-*`` triples whose OS is
+ Windows; only these default to WinCall.
+- The name round-trips through ``Triple::getArchName()`` /
+ ``Triple::parseSubArch()``.
+
+The sub-architecture also turns on the APX instruction-set extensions. Both
+the clang frontend (``X86TargetInfo::initFeatureMap``) and the LLVM backend
+(``X86_MC::ParseX86Triple``) enable:
+
+```
++egpr,+push2pop2,+ppx,+ndd,+ccmp,+nf,+zu,+jmpabs
+```
+
+for ``x86_64apx`` targets. There is no single ``apxf`` feature bit; APX is
+modelled as this set of independent features. The backend must enable them
+from the triple itself (not just from the frontend), because backend passes
+size their register tables from ``X86RegisterInfo::getNumSupportedRegs()``,
+which returns the full register count only when ``egpr`` is enabled. If the
+registers were not enabled, WinCall would still assign R16-R19 as argument
+registers, and passes such as ``LiveVariables`` would index out of bounds.
+
+## Argument passing
+
+### Integer and pointer arguments
+
+The first **eight** integer-class arguments are passed in registers:
+
+| arg # | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 |
+|-------|------|------|------|------|------|------|------|------|
+| i8 | CL | DL | R8B | R9B | R16B | R17B | R18B | R19B |
+| i16 | CX | DX | R8W | R9W | R16W | R17W | R18W | R19W |
+| i32 | ECX | EDX | R8D | R9D | R16D | R17D | R18D | R19D |
+| i64 | RCX | RDX | R8 | R9 | R16 | R17 | R18 | R19 |
+
+``i1`` arguments are promoted to ``i8`` first. Remaining integer arguments go
+on the stack in 8-byte, 8-byte-aligned slots.
+
+This list is used by ``bool``, all integer types, pointers, ``__m64``,
+``__int128``/``__uint128_t`` (which is split into two i64s), and
+``std::float128_t``.
+
+### Floating-point, SIMD and vector arguments
+
+The first **eight** FP/SIMD arguments are passed in XMM registers (or
+YMM/ZMM for wider vectors), **independently** of the integer registers:
+
+| arg # | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 |
+|-------|------|------|------|------|------|------|------|------|
+| f16/f32/f64, 128-bit vectors | XMM0 | XMM1 | XMM2 | XMM3 | XMM4 | XMM5 | XMM6 | XMM7 |
+| 256-bit vectors (``__m256``) | YMM0 | YMM1 | YMM2 | YMM3 | YMM4 | YMM5 | YMM6 | YMM7 |
+| 512-bit vectors (``__m512``) | ZMM0 | ZMM1 | ZMM2 | ZMM3 | ZMM4 | ZMM5 | ZMM6 | ZMM7 |
+
+The 256-bit rules require AVX, the 512-bit rules require AVX-512. Stack slots
+are 16 bytes for 128-bit vectors, 32 bytes for 256-bit vectors and 64 bytes
+for 512-bit vectors, aligned to their size.
+
+### Independent integer/FP allocation
+
+Unlike the Microsoft x64 ABI, which pairs each integer register with an XMM
+register and *skips* the partner when the other class is used, WinCall
+allocates the integer and FP register lists **independently**. A function
+``f(int, double, int, double, int, double, ...)`` therefore uses
+ECX, EDX, R8D, ... for its ints and XMM0, XMM1, XMM2, ... for its doubles
+with no skipping.
+
+This is safe across wincall/non-wincall calls because each function's
+convention is fixed at compile time, and the ``@win`` symbol decoration (see
+below) lets the linker catch mismatches.
+
+### Aggregates
+
+Clang's ``WinX86_64ABIInfo::classify`` implements the aggregate rules for
+``CC_WinCall`` (this is a frontend rule layered on top of the IR-level
+convention):
+
+- A record that fits in **1, 2, 4, 8, 16 or 32 bytes** is passed **directly
+ in registers** (not by pointer/sret like the MS x64 ABI). A 4-``size_t``
+ struct — such as ``std::string`` or ``std::vector`` — therefore travels in
+ RCX, RDX, R8, R9.
+- A record of up to 64 bits is coerced to an integer of its size and uses
+ **one** GPR; a larger record (up to 32 bytes) is **expanded** into its
+ 8-byte parts.
+- **Empty records** (``struct empty {}``) consume **no register slots**;
+ ``classify`` returns ``ABIArgInfo::getIgnore()`` for them.
+- Records larger than 32 bytes, records with a flexible array member, and
+ non-trivial C++ records (per ``getRecordArgABI``) are passed by reference.
+- ``f80`` (long double) is passed by pointer.
+- ``__int128`` is split into two GPRs.
+- Complex types and member pointers are handled as in the MS x64 ABI.
+
+## Return values
+
+``RetCC_X86_Win64_WinCall``:
+
+| Return type | Registers |
+|-------------|-----------|
+| Scalar (i8/i16/i32/i64), ``__m64`` | RAX (first value), RDX, RCX, R8 |
+| Scalar up to 128 bits (``__int128``, ``__uint128_t``, ``std::float128_t``) | RAX (low) + RDX (high) |
+| f16/f32/f64 and 128-bit vectors | XMM0 (first value), XMM1 |
+| 256-bit vectors | YMM0 |
+| 512-bit vectors | ZMM0 |
+| Aggregates up to 32 bytes | RAX, RDX, RCX, R8 (expanded) |
+| Aggregates larger than 32 bytes | hidden pointer (sret) |
+
+Empty records consume no return register. The state of unused bits in RAX or
+XMM0 is undefined.
+
+## Callee-saved registers
+
+WinCall keeps the Windows x64 caller-saved model. The callee-saved set is
+``CSR_Win64_APX`` (used whenever ``egpr`` is enabled, since the frontend
+always enables it for ``x86_64apx``):
+
+```
+RBX, RBP, RDI, RSI, R12, R13, R14, R15, R30, R31, XMM6-XMM15
+```
+
+R30 and R31 are APX registers preserved in addition to the standard Windows
+x64 set.
+
+## Symbol decoration: the ``@win`` suffix
+
+Every WinCall function gets a ``@win`` suffix appended to its symbol name, so
+that the linker and loader can detect a caller and callee compiled with
+mismatched calling conventions. This mirrors the ``@N`` parameter-size
+suffix of ``stdcall`` on i386, but uses a fixed tag that cannot collide with
+``stdcall``'s ``@0``/``@16`` decorations.
+
+The decoration is applied uniformly across all three name manglings
+(implemented in ``clang/lib/AST/Mangle.cpp`` for C, and via the suffix check
+in ``clang/lib/AST/MicrosoftMangle.cpp``; Itanium C++ names are decorated by
+the same ``@win`` suffix path):
+
+| Language / ABI | Symbol |
+|----------------|--------|
+| C | ``foo at win`` |
+| C++ MS ABI | ``?foo@@YAXH at Z@win`` |
+| C++ Itanium ABI | ``_Z3fooi at win`` |
+
+Additionally, the Itanium mangling encodes WinCall in function-pointer types
+as the vendor extended qualifier ``U7wincall``:
+
+```cpp
+using W = void (__attribute__((wincall)) *)(int);
+template <typename T> T id(T x);
+W w;
+// _Z2idIPU7wincallFviEET_S2_
+```
+
+## Section alignment
+
+For ``x86_64apx`` targets the clang driver passes a **64 KiB section
+alignment** to the linker by default, so that the OS can use 64 KiB pages
+(matching the page size of the NVIDIA Grace CPU) for code and data built with
+this convention:
+
+| Toolchain | Default flag |
+|-----------|--------------|
+| MSVC ``link.exe`` | ``/section-alignment:0x10000`` and ``/driver`` |
+| MinGW ``ld`` | ``--section-alignment=0x10000`` |
+
+The flags are added by the driver in ``clang/lib/Driver/ToolChains/MSVC.cpp``
+and ``MinGW.cpp``, not by the assembler. If the user passes their own
+section-alignment ``-Wl`` flag, the driver's default is suppressed in favour
+of the user's value.
+
+## Relation to "herbceptions" (deterministic exceptions)
+
+WinCall is designed so that Herb Sutter's proposed zero-overhead
+deterministic exceptions (P0709, "herbceptions") can represent ``std::error``
+as a two-register value passed/returned in RAX (domain pointer) and RDX
+(code), with the carry flag (CF) as the success/failure discriminant. This is
+the design intent of the convention; the discriminant-lowering support in
+LLVM is independent of WinCall and is not part of this calling convention
+itself.
+
+## Implementation notes
+
+- IR calling convention: ``x86_wincallcc`` / ``CallingConv::X86_WinCall``
+ (CC ID 128).
+- TableGen conventions in ``llvm/lib/Target/X86/X86CallingConv.td``:
+ ``CC_X86_Win64_WinCall`` (arguments) and ``RetCC_X86_Win64_WinCall``
+ (returns), dispatched from the root ``CC_X86_64`` convention.
+- Frontend ABI rules: ``WinX86_64ABIInfo::classify`` in
+ ``clang/lib/CodeGen/Targets/X86.cpp``.
+- Default CC: ``X86_64TargetInfo::getDefaultCallingConv`` in
+ ``clang/lib/Basic/Targets/X86.h``.
+- Symbol decoration: ``clang/lib/AST/Mangle.cpp`` and
+ ``clang/lib/AST/MicrosoftMangle.cpp``; vendor qualifier in
+ ``clang/lib/AST/ItaniumMangle.cpp``.
+- Triples: ``Triple::X86_64SubArch_apx`` in ``llvm/lib/TargetParser/Triple.*``.
+- Driver flags: ``clang/lib/Driver/ToolChains/MSVC.cpp`` and ``MinGW.cpp``.
+- Register tables must include the APX registers when ``egpr`` is enabled;
+ see ``X86RegisterInfo::getNumSupportedRegs``.
>From 6d262377d0c0d79b8e4d0705df90472b0cf70e45 Mon Sep 17 00:00:00 2001
From: trcrsired <oyzawqgcfc at gmail.com>
Date: Tue, 11 Aug 2026 22:42:06 +0800
Subject: [PATCH 18/36] [Docs][WinCall] Add FAQ section to the WinCall
documentation
Answer two questions that come up when reading the WinCall spec:
- R30/R31 are callee-saved under the Windows APX ABI (per Microsoft's
x64 calling convention) but can still be used as scratch registers:
the callee preserves them with pushp/popp in the prologue/epilogue,
exactly like RBX/RDI/RSI etc. They are only reserved in functions that
call setjmp/longjmp, where the Windows unwinder cannot restore the APX
extended registers.
- A 4-pointer struct (32 bytes) is split into four GPR slots: returned
in RAX/RDX/RCX/R8 and passed in RCX/RDX/R8/R9, with a following
integer argument going to R16.
---
llvm/docs/WinCall.md | 55 ++++++++++++++++++++++++++++++++++++++++++++
1 file changed, 55 insertions(+)
diff --git a/llvm/docs/WinCall.md b/llvm/docs/WinCall.md
index 2049dcab3d4439..67c02787bdf0ba 100644
--- a/llvm/docs/WinCall.md
+++ b/llvm/docs/WinCall.md
@@ -219,6 +219,61 @@ the design intent of the convention; the discriminant-lowering support in
LLVM is independent of WinCall and is not part of this calling convention
itself.
+## FAQ
+
+### R30 and R31 are callee-saved — can they still be used as scratch registers?
+
+Yes. R30 and R31 are callee-saved registers under the Windows APX ABI, so a
+callee may freely use them as scratch registers as long as it preserves them
+across the call. The compiler does exactly that: when a WinCall function uses
+R30 or R31 it pushes them in the prologue and pops them in the epilogue using
+the APX ``pushp``/``popp`` instructions:
+
+```asm
+use_r31 at win:
+ pushp %r31 # preserve R31
+ movq %rcx, %r31 # use R31 as a scratch register
+ callq g
+ popp %r31 # restore R31
+ retq
+```
+
+This mirrors how the other callee-saved registers (RBX, RDI, RSI, R12-R15,
+XMM6-XMM15) work on the standard Windows x64 ABI. The only restriction is in
+functions that call ``setjmp``/``longjmp``: because the Windows unwinder
+cannot restore the APX extended registers across a jump, clang reserves
+R30/R31 there (and warns on large functions), so they are not allocated.
+
+### Does a 4-pointer struct split into four registers?
+
+Yes. A struct of four pointers (32 bytes) is classified by
+``WinX86_64ABIInfo::classify`` as a direct record: it is returned in
+``RAX, RDX, RCX, R8`` and passed as an argument in ``RCX, RDX, R8, R9`` —
+four separate GPR slots, one per 8-byte field. At ``-O2`` the words move
+directly from the return registers to the argument registers with no stack
+round-trip:
+
+```asm
+caller at win:
+ callq make at win # returns the 4-pointer struct in RAX/RDX/RCX/R8
+ movq %rcx, %r9 # save words 3,4 in scratch regs
+ movq %r8, %r10
+ movq %rax, %rcx # arg 1
+ movq %rdx, %rdx # arg 2
+ movq %r9, %r8 # arg 3
+ movq %r10, %r9 # arg 4
+ callq take at win
+```
+
+(With optimization disabled the four words are spilled to and reloaded from
+the stack frame between the two calls, but the argument registers are still
+RCX, RDX, R8, R9.)
+
+An integer argument following the struct is placed in the next free GPR
+(R16). This is what makes ``std::string`` and ``std::vector`` (four-word
+objects) travel entirely in registers under WinCall, unlike the MS x64 ABI
+which would pass them by pointer.
+
## Implementation notes
- IR calling convention: ``x86_wincallcc`` / ``CallingConv::X86_WinCall``
>From 1303b2f156430deda281876ed5eba1722e593f97 Mon Sep 17 00:00:00 2001
From: trcrsired <oyzawqgcfc at gmail.com>
Date: Tue, 11 Aug 2026 23:11:28 +0800
Subject: [PATCH 19/36] [Docs][WinCall] Add examples of empty objects,
__uint128_t and std::span
Show how common C++ types are passed and returned under WinCall, with
codegen verified against the compiler:
- empty objects consume no register slots; the following argument takes
the first GPR
- __uint128_t / __int128_t / std::float128_t are split into two GPRs
(RCX+RDX in, RAX+RDX out)
- std::span / std::string_view ({pointer, size}) travel entirely in two
registers instead of by pointer as in the MS x64 ABI
---
llvm/docs/WinCall.md | 72 ++++++++++++++++++++++++++++++++++++++++++++
1 file changed, 72 insertions(+)
diff --git a/llvm/docs/WinCall.md b/llvm/docs/WinCall.md
index 67c02787bdf0ba..aed0c8459b6c40 100644
--- a/llvm/docs/WinCall.md
+++ b/llvm/docs/WinCall.md
@@ -219,6 +219,78 @@ the design intent of the convention; the discriminant-lowering support in
LLVM is independent of WinCall and is not part of this calling convention
itself.
+## Examples
+
+The following examples show how common C++ types are passed and returned
+under WinCall (``x86_64apx-windows-msvc``, optimized output).
+
+### Empty objects
+
+An empty object (``struct empty {}``) consumes **no register slots**. The
+``int`` that follows it takes the first GPR (ECX):
+
+```c
+struct empty {};
+
+__attribute__((wincall)) void f(struct empty e, int x) { sink(x); }
+```
+
+```asm
+f at win:
+ callq sink at win # x is forwarded from ECX; e took no register
+```
+
+An empty object as a return type also uses no register. This makes C++ types
+that contain empty base classes or members cheaper to pass and return than
+under the MS x64 ABI.
+
+### ``__uint128_t`` (and ``__int128_t``, ``std::float128_t``)
+
+A 128-bit integer is split into **two GPRs**: passed in RCX (low) + RDX
+(high), returned in RAX (low) + RDX (high).
+
+```c
+__attribute__((wincall)) __uint128_t f(__uint128_t v);
+```
+
+```asm
+f at win:
+ movq %rcx, %rax # low 64 bits: RCX -> RAX
+ retq # high 64 bits already in RDX
+```
+
+The same two-register rule applies to ``__int128_t`` and to
+``std::float128_t``.
+
+### ``std::span`` (two-word aggregates)
+
+A ``{pointer, size}`` aggregate such as ``std::span`` or
+``std::string_view`` is 16 bytes, so it is passed in **two** GPRs: the
+pointer in RCX and the length in RDX, and returned in RAX (pointer) + RDX
+(length).
+
+```c
+struct span { void *base; unsigned long long len; };
+
+__attribute__((wincall)) void f(struct span s);
+__attribute__((wincall)) struct span g(void);
+```
+
+```asm
+ callq g at win # returns {RAX = base, RDX = len}
+ movq %rax, %rcx # pass base in RCX (len already in RDX)
+ callq f at win
+```
+
+Under the MS x64 ABI this same ``std::span`` argument would be passed by
+pointer; WinCall makes it a zero-cost, purely-register argument.
+
+### Four-word aggregates (``std::string`` / ``std::vector``)
+
+A four-word object such as ``std::string`` or ``std::vector`` (32 bytes) is
+passed in **four** GPRs (RCX, RDX, R8, R9) and returned in RAX, RDX, RCX,
+R8. See the FAQ below for the exact code.
+
## FAQ
### R30 and R31 are callee-saved — can they still be used as scratch registers?
>From 661ee7e2a97f23696683f740352528ee5524c138 Mon Sep 17 00:00:00 2001
From: trcrsired <oyzawqgcfc at gmail.com>
Date: Tue, 11 Aug 2026 23:16:58 +0800
Subject: [PATCH 20/36] [Docs][WinCall] Clarify that classes with destructors
are not passed in registers
std::string and std::vector have non-trivial destructors, so getRecordArgABI
makes clang pass them by pointer regardless of size. Remove them from the
register-passing examples and note the exemption explicitly in the
Aggregates section, the four-word aggregate example, and the FAQ.
---
llvm/docs/WinCall.md | 29 ++++++++++++++++++-----------
1 file changed, 18 insertions(+), 11 deletions(-)
diff --git a/llvm/docs/WinCall.md b/llvm/docs/WinCall.md
index aed0c8459b6c40..830b8f007aa27a 100644
--- a/llvm/docs/WinCall.md
+++ b/llvm/docs/WinCall.md
@@ -11,8 +11,8 @@ Its purpose is to exploit the 16 additional general-purpose registers
four integer arguments can be passed in registers; WinCall doubles that to
eight by using R16-R19 as argument registers. It also relaxes the aggregate
passing and return rules of the Microsoft x64 ABI so that C++ types such as
-``std::span``, ``std::string`` and ``std::vector`` (a ``{pointer, size}``
-pair or a four-word object) can travel in registers.
+``std::span`` (a ``{pointer, size}`` pair) and other small aggregates can
+travel in registers.
WinCall does **not** replace any existing ABI. Existing Windows APIs keep
their ``stdcall``, ``cdecl`` and ``fastcall`` conventions; the convention is
@@ -120,8 +120,7 @@ convention):
- A record that fits in **1, 2, 4, 8, 16 or 32 bytes** is passed **directly
in registers** (not by pointer/sret like the MS x64 ABI). A 4-``size_t``
- struct — such as ``std::string`` or ``std::vector`` — therefore travels in
- RCX, RDX, R8, R9.
+ struct therefore travels in RCX, RDX, R8, R9.
- A record of up to 64 bits is coerced to an integer of its size and uses
**one** GPR; a larger record (up to 32 bytes) is **expanded** into its
8-byte parts.
@@ -129,6 +128,10 @@ convention):
``classify`` returns ``ABIArgInfo::getIgnore()`` for them.
- Records larger than 32 bytes, records with a flexible array member, and
non-trivial C++ records (per ``getRecordArgABI``) are passed by reference.
+ Note that this means C++ classes with user-declared or user-provided
+ destructors or copy/move constructors (e.g. ``std::string``,
+ ``std::vector``, ``std::unique_ptr``) are **not** passed in registers —
+ they are passed by pointer regardless of size.
- ``f80`` (long double) is passed by pointer.
- ``__int128`` is split into two GPRs.
- Complex types and member pointers are handled as in the MS x64 ABI.
@@ -285,11 +288,13 @@ __attribute__((wincall)) struct span g(void);
Under the MS x64 ABI this same ``std::span`` argument would be passed by
pointer; WinCall makes it a zero-cost, purely-register argument.
-### Four-word aggregates (``std::string`` / ``std::vector``)
+### Four-word aggregates
-A four-word object such as ``std::string`` or ``std::vector`` (32 bytes) is
-passed in **four** GPRs (RCX, RDX, R8, R9) and returned in RAX, RDX, RCX,
-R8. See the FAQ below for the exact code.
+A plain 4-``size_t`` struct (32 bytes) is passed in **four** GPRs (RCX,
+RDX, R8, R9) and returned in RAX, RDX, RCX, R8. See the FAQ below for the
+exact code. (C++ classes such as ``std::string`` or ``std::vector`` that
+have a non-trivial destructor or copy/move constructor are *not* passed in
+registers — they are passed by pointer; see the Aggregates section above.)
## FAQ
@@ -342,9 +347,11 @@ the stack frame between the two calls, but the argument registers are still
RCX, RDX, R8, R9.)
An integer argument following the struct is placed in the next free GPR
-(R16). This is what makes ``std::string`` and ``std::vector`` (four-word
-objects) travel entirely in registers under WinCall, unlike the MS x64 ABI
-which would pass them by pointer.
+(R16). This is how a four-word aggregate such as a 4-``size_t`` struct
+travels entirely in registers under WinCall, unlike the MS x64 ABI which
+would pass it by pointer. (C++ classes with non-trivial destructors or
+copy/move constructors are exempt from this register passing; see the
+Aggregates section above.)
## Implementation notes
>From 0622d195f89a4997511cc936a79b5c24584fe040 Mon Sep 17 00:00:00 2001
From: trcrsired <oyzawqgcfc at gmail.com>
Date: Tue, 11 Aug 2026 23:21:37 +0800
Subject: [PATCH 21/36] [Docs] add draft to wincall title
---
llvm/docs/WinCall.md | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/llvm/docs/WinCall.md b/llvm/docs/WinCall.md
index 830b8f007aa27a..98f89a9599e5ce 100644
--- a/llvm/docs/WinCall.md
+++ b/llvm/docs/WinCall.md
@@ -1,4 +1,4 @@
-# The WinCall Calling Convention
+# The WinCall Calling Convention Draft
## What WinCall is
>From 5e38e7db07533c7037df35feae73ecc62e60254f Mon Sep 17 00:00:00 2001
From: trcrsired <oyzawqgcfc at gmail.com>
Date: Tue, 11 Aug 2026 23:37:48 +0800
Subject: [PATCH 22/36] [wincall] formatting issues detected by upstream
CI
---
clang/lib/AST/Expr.cpp | 4 +++-
clang/lib/AST/TypePrinter.cpp | 4 +++-
clang/lib/Basic/Targets.cpp | 3 ++-
clang/lib/Basic/Targets/X86.cpp | 4 ++--
clang/lib/CodeGen/Targets/X86.cpp | 5 ++---
clang/lib/Driver/ToolChains/MSVC.cpp | 3 ++-
clang/lib/Parse/ParseTentative.cpp | 3 ++-
clang/lib/Sema/SemaLambda.cpp | 4 ++--
llvm/include/llvm/TargetParser/Triple.h | 4 +---
llvm/lib/AsmParser/LLParser.cpp | 4 +++-
llvm/lib/IR/AsmWriter.cpp | 4 +++-
11 files changed, 25 insertions(+), 17 deletions(-)
diff --git a/clang/lib/AST/Expr.cpp b/clang/lib/AST/Expr.cpp
index 01bc0c3e4b28b7..9396679c1cae60 100644
--- a/clang/lib/AST/Expr.cpp
+++ b/clang/lib/AST/Expr.cpp
@@ -798,7 +798,9 @@ std::string PredefinedExpr::ComputeName(PredefinedIdentKind IK,
case CC_X86ThisCall: POut << "__thiscall "; break;
case CC_X86VectorCall: POut << "__vectorcall "; break;
case CC_X86RegCall: POut << "__regcall "; break;
- case CC_WinCall: POut << "__wincall "; break;
+ case CC_WinCall:
+ POut << "__wincall ";
+ break;
// Only bother printing the conventions that MSVC knows about.
default: break;
}
diff --git a/clang/lib/AST/TypePrinter.cpp b/clang/lib/AST/TypePrinter.cpp
index 079de42e744b40..90a89acb85692c 100644
--- a/clang/lib/AST/TypePrinter.cpp
+++ b/clang/lib/AST/TypePrinter.cpp
@@ -2115,7 +2115,9 @@ void TypePrinter::printAttributedAfter(const AttributedType *T,
case attr::MSABI: OS << "ms_abi"; break;
case attr::SysVABI: OS << "sysv_abi"; break;
case attr::RegCall: OS << "regcall"; break;
- case attr::WinCall: OS << "wincall"; break;
+ case attr::WinCall:
+ OS << "wincall";
+ break;
case attr::Pcs: {
OS << "pcs(";
QualType t = T->getEquivalentType();
diff --git a/clang/lib/Basic/Targets.cpp b/clang/lib/Basic/Targets.cpp
index 572957794d58de..9bb2e642098774 100644
--- a/clang/lib/Basic/Targets.cpp
+++ b/clang/lib/Basic/Targets.cpp
@@ -93,7 +93,8 @@ void addCygMingDefines(const LangOptions &Opts, MacroBuilder &Builder) {
// Provide macros for all the calling convention keywords. Provide both
// single and double underscore prefixed variants. These are available on
// x64 as well as x86, even though they have no effect.
- const char *CCs[] = {"cdecl", "stdcall", "fastcall", "thiscall", "pascal", "wincall"};
+ const char *CCs[] = {"cdecl", "stdcall", "fastcall",
+ "thiscall", "pascal", "wincall"};
for (const char *CC : CCs) {
std::string GCCSpelling = "__attribute__((__";
GCCSpelling += CC;
diff --git a/clang/lib/Basic/Targets/X86.cpp b/clang/lib/Basic/Targets/X86.cpp
index 6ed62b205c4b70..10b63ae3205778 100644
--- a/clang/lib/Basic/Targets/X86.cpp
+++ b/clang/lib/Basic/Targets/X86.cpp
@@ -161,8 +161,8 @@ bool X86TargetInfo::initFeatureMap(
// APX as the individual egpr/push2pop2/ppx/ndd/ccmp/nf/zu/jmpabs features
// (there is no single "apxf" feature), so enable each of them.
if (getTriple().isX86_64APX()) {
- for (const char *Sub : {"egpr", "push2pop2", "ppx", "ndd", "ccmp", "nf",
- "zu", "jmpabs"})
+ for (const char *Sub :
+ {"egpr", "push2pop2", "ppx", "ndd", "ccmp", "nf", "zu", "jmpabs"})
setFeatureEnabled(Features, Sub, true);
}
diff --git a/clang/lib/CodeGen/Targets/X86.cpp b/clang/lib/CodeGen/Targets/X86.cpp
index 8ebcc857bac8a7..35d2494b2eb8c0 100644
--- a/clang/lib/CodeGen/Targets/X86.cpp
+++ b/clang/lib/CodeGen/Targets/X86.cpp
@@ -3577,9 +3577,8 @@ ABIArgInfo WinX86_64ABIInfo::classify(QualType Ty, unsigned &FreeSSERegs,
if (IsWinCall) {
// std::float128_t is passed like __int128: in two integer registers.
if (IsReturnType)
- return ABIArgInfo::getDirect(
- llvm::FixedVectorType::get(
- llvm::Type::getInt64Ty(getVMContext()), 2));
+ return ABIArgInfo::getDirect(llvm::FixedVectorType::get(
+ llvm::Type::getInt64Ty(getVMContext()), 2));
return ABIArgInfo::getExpand();
}
diff --git a/clang/lib/Driver/ToolChains/MSVC.cpp b/clang/lib/Driver/ToolChains/MSVC.cpp
index 0eb6637687dccf..ba844640736ee6 100644
--- a/clang/lib/Driver/ToolChains/MSVC.cpp
+++ b/clang/lib/Driver/ToolChains/MSVC.cpp
@@ -378,7 +378,8 @@ void visualstudio::Linker::ConstructJob(Compilation &C, const JobAction &JA,
// with their own -Wl,/section-alignment: flag.
if (TC.getTriple().isX86_64APX()) {
bool HasExplicitSectionAlignment = false;
- for (Arg *A : Args.filtered(options::OPT_Wl_COMMA, options::OPT__SLASH_link))
+ for (Arg *A :
+ Args.filtered(options::OPT_Wl_COMMA, options::OPT__SLASH_link))
if (StringRef(A->getValue()).contains_insensitive("section-alignment"))
HasExplicitSectionAlignment = true;
if (!HasExplicitSectionAlignment) {
diff --git a/clang/lib/Parse/ParseTentative.cpp b/clang/lib/Parse/ParseTentative.cpp
index 31b802a0580935..1618815f24118a 100644
--- a/clang/lib/Parse/ParseTentative.cpp
+++ b/clang/lib/Parse/ParseTentative.cpp
@@ -937,7 +937,8 @@ Parser::TPResult Parser::TryParseDeclarator(bool mayBeAbstract,
// '(' abstract-declarator ')'
if (Tok.isOneOf(tok::kw___attribute, tok::kw___declspec, tok::kw___cdecl,
tok::kw___stdcall, tok::kw___fastcall, tok::kw___thiscall,
- tok::kw___regcall, tok::kw___vectorcall, tok::kw___wincall))
+ tok::kw___regcall, tok::kw___vectorcall,
+ tok::kw___wincall))
return TPResult::True; // attributes indicate declaration
TPResult TPR = TryParseDeclarator(mayBeAbstract, mayHaveIdentifier);
if (TPR != TPResult::Ambiguous)
diff --git a/clang/lib/Sema/SemaLambda.cpp b/clang/lib/Sema/SemaLambda.cpp
index ab1a3e91222ade..19bc8ef5e11710 100644
--- a/clang/lib/Sema/SemaLambda.cpp
+++ b/clang/lib/Sema/SemaLambda.cpp
@@ -1649,8 +1649,8 @@ static void repeatForLambdaConversionFunctionCallingConvs(
/// detecting the attribute by the time we get here.
if (S.getLangOpts().MSVCCompat) {
CallingConv Convs[] = {
- CC_C, CC_X86StdCall, CC_X86FastCall, CC_X86VectorCall,
- CC_WinCall, DefaultFree, DefaultMember, CallOpCC};
+ CC_C, CC_X86StdCall, CC_X86FastCall, CC_X86VectorCall,
+ CC_WinCall, DefaultFree, DefaultMember, CallOpCC};
llvm::sort(Convs);
llvm::iterator_range<CallingConv *> Range(std::begin(Convs),
llvm::unique(Convs));
diff --git a/llvm/include/llvm/TargetParser/Triple.h b/llvm/include/llvm/TargetParser/Triple.h
index 9af359c373b308..0e53da52f23fbb 100644
--- a/llvm/include/llvm/TargetParser/Triple.h
+++ b/llvm/include/llvm/TargetParser/Triple.h
@@ -1019,9 +1019,7 @@ class Triple {
/// Tests whether the target is the Windows x86_64apx target, which defaults
/// to the wincall calling convention.
- bool isWindowsAPX() const {
- return isX86_64APX() && isOSWindows();
- }
+ bool isWindowsAPX() const { return isX86_64APX() && isOSWindows(); }
/// Tests whether the target supports the EHABI exception
/// handling standard.
diff --git a/llvm/lib/AsmParser/LLParser.cpp b/llvm/lib/AsmParser/LLParser.cpp
index 98a9201528f968..60efaf8c56fefd 100644
--- a/llvm/lib/AsmParser/LLParser.cpp
+++ b/llvm/lib/AsmParser/LLParser.cpp
@@ -2322,7 +2322,9 @@ bool LLParser::parseOptionalCallingConv(unsigned &CC) {
case lltok::kw_x86_regcallcc: CC = CallingConv::X86_RegCall; break;
case lltok::kw_x86_thiscallcc: CC = CallingConv::X86_ThisCall; break;
case lltok::kw_x86_vectorcallcc:CC = CallingConv::X86_VectorCall; break;
- case lltok::kw_x86_wincallcc: CC = CallingConv::X86_WinCall; break;
+ case lltok::kw_x86_wincallcc:
+ CC = CallingConv::X86_WinCall;
+ break;
case lltok::kw_arm_apcscc: CC = CallingConv::ARM_APCS; break;
case lltok::kw_arm_aapcscc: CC = CallingConv::ARM_AAPCS; break;
case lltok::kw_arm_aapcs_vfpcc:CC = CallingConv::ARM_AAPCS_VFP; break;
diff --git a/llvm/lib/IR/AsmWriter.cpp b/llvm/lib/IR/AsmWriter.cpp
index 0a18dfaddc4dda..cd6ca86247a358 100644
--- a/llvm/lib/IR/AsmWriter.cpp
+++ b/llvm/lib/IR/AsmWriter.cpp
@@ -361,7 +361,9 @@ static void printCallingConv(unsigned cc, raw_ostream &Out) {
case CallingConv::GRAAL: Out << "graalcc"; break;
case CallingConv::CFGuard_Check: Out << "cfguard_checkcc"; break;
case CallingConv::X86_StdCall: Out << "x86_stdcallcc"; break;
- case CallingConv::X86_WinCall: Out << "x86_wincallcc"; break;
+ case CallingConv::X86_WinCall:
+ Out << "x86_wincallcc";
+ break;
case CallingConv::X86_FastCall: Out << "x86_fastcallcc"; break;
case CallingConv::X86_ThisCall: Out << "x86_thiscallcc"; break;
case CallingConv::X86_RegCall: Out << "x86_regcallcc"; break;
>From 68052c9fe860961634be96f45a5551ef1f00f0d9 Mon Sep 17 00:00:00 2001
From: trcrsired <oyzawqgcfc at gmail.com>
Date: Wed, 12 Aug 2026 00:21:15 +0800
Subject: [PATCH 23/36] [Docs][clang] indent issue try to fix it
---
clang/include/clang/Basic/AttrDocs.td | 17 +++++++++--------
1 file changed, 9 insertions(+), 8 deletions(-)
diff --git a/clang/include/clang/Basic/AttrDocs.td b/clang/include/clang/Basic/AttrDocs.td
index 2ed04e43272b45..c3628c93076d90 100644
--- a/clang/include/clang/Basic/AttrDocs.td
+++ b/clang/include/clang/Basic/AttrDocs.td
@@ -3756,15 +3756,16 @@ The ``wincall`` attribute applies the WinCall calling convention to a
function. WinCall is an x86-64-only calling convention for Windows targets
that makes use of the registers added by Intel APX.
-Unlike the Microsoft x64 ABI, WinCall:
-- passes the first eight integer arguments in RCX, RDX, R8, R9, R16, R17,
- R18, R19 (R16-R19 are APX registers),
-- passes the first eight floating-point/vector arguments in XMM0-XMM7
+Unlike the Microsoft x64 ABI, WinCall differs in the following ways.
+
+- It passes the first eight integer arguments in RCX, RDX, R8, R9, R16, R17,
+ R18, R19 (R16-R19 are APX registers).
+- It passes the first eight floating-point/vector arguments in XMM0-XMM7
(YMM0-YMM7 for ``__m256``, ZMM0-ZMM7 for ``__m512``), allocated
- independently of the integer registers,
-- passes/returns aggregates up to 32 bytes directly in registers instead of
- by pointer, and
-- takes no register slots for empty C++ objects.
+ independently of the integer registers.
+- It passes/returns aggregates up to 32 bytes directly in registers instead
+ of by pointer.
+- It takes no register slots for empty C++ objects.
On targets where WinCall is not supported the attribute is ignored with a
warning.
>From 5b180c5d993e2d25a3f3bbf2a179678d07356d8e Mon Sep 17 00:00:00 2001
From: trcrsired <oyzawqgcfc at gmail.com>
Date: Wed, 12 Aug 2026 02:24:01 +0800
Subject: [PATCH 24/36] [skip ci] [Docs][WinCall] Document building dual-ABI
DLLs
Explain how a single DLL binary can serve both WinCall and classic
(stdcall/MS x64/MinGW) callers by exposing two entry points per function:
- symbol names: export both foo and foo at win
- calling convention: forward arguments through a thin per-ABI thunk
to one WinCall-compiled implementation
- long double: keep it out of the exported interface, or keep the
classic 16-byte f80 layout in exported functions
Note that the long double size change does not by itself break
dual-ABI DLLs, since the calling-convention and symbol differences
already require the dual-entry-thunk design.
---
llvm/docs/WinCall.md | 65 ++++++++++++++++++++++++++++++++++++++++++++
1 file changed, 65 insertions(+)
diff --git a/llvm/docs/WinCall.md b/llvm/docs/WinCall.md
index 98f89a9599e5ce..906aac9647f43f 100644
--- a/llvm/docs/WinCall.md
+++ b/llvm/docs/WinCall.md
@@ -212,6 +212,71 @@ and ``MinGW.cpp``, not by the assembler. If the user passes their own
section-alignment ``-Wl`` flag, the driver's default is suppressed in favour
of the user's value.
+## Building a DLL that works with both WinCall and the classic ABI
+
+A function's ABI is decided at the *call boundary*, not inside the function.
+A single DLL binary can therefore serve both WinCall callers and classic
+(``stdcall`` / MS x64 / plain MinGW) callers, but only by exposing **two entry
+points per function** — one per ABI — rather than one function with a
+compromise ABI. The three things that differ between the two worlds each need
+their own solution:
+
+| Aspect | WinCall | Classic ABI |
+|---------------|--------------------------------|----------------------------|
+| Symbol | ``foo at win`` | ``foo`` |
+| Calling conv. | 8 GPRs, XMM0-7, aggregates in registers | 4 GPRs, XMM0-3, sret/byval |
+| ``long double`` | f64, 8 bytes | f80, 16 bytes |
+
+### Symbol names
+
+Export **both** names for the same body: ``foo`` and ``foo at win``. On COFF this
+is two export entries pointing at the same RVA (or a one-line asm alias). The
+``@win`` name serves WinCall callers; the plain name serves classic callers.
+
+### Calling convention: forwarding thunks
+
+WinCall and the classic convention genuinely disagree on registers and stack.
+The standard technique is a thin **forwarding thunk** per ABI that converts
+the argument placement and jumps to a single implementation compiled with
+WinCall:
+
+```asm
+foo at win: ; WinCall ABI: args in RCX,RDX,R8,R9,R16-R19, XMM0-7
+ jmp foo_impl
+
+foo: ; classic ABI: args in RCX,RDX,R8,R9, XMM0-3
+ ... ; convert the small subset that differs
+ jmp foo_impl
+```
+
+This is the same machinery as C++ ABI thunks or
+``-fdefault-calling-conv`` plus a per-function calling-convention attribute.
+In practice the thunk is written in assembly, or the exported function is
+marked with the classic attribute (e.g. ``__attribute__((stdcall))``) and the
+compiler emits the conversion.
+
+### ``long double``
+
+This is the one genuine limitation: a single binary stores one
+representation. The options are:
+
+1. **Keep ``long double`` out of the exported interface** (recommended). Most
+ Windows DLL APIs use ``double``/``int``/pointers/structs, so WinCall's f64
+ ``long double`` is invisible to classic callers and the dual-ABI DLL works
+ with both worlds.
+2. **Keep the classic 16-byte f80 ``long double`` in exported functions** that
+ must cross the boundary, while WinCall-internal code uses f64. This needs a
+ per-function (or per-TU) ``long double`` layout switch, so the exported
+ surface matches classic callers.
+3. **Pick f64 everywhere** and accept that only WinCall callers may use
+ ``long double`` in the API. Simple, but classic callers passing f80 will
+ misbehave on those functions.
+
+The ``long double`` size change therefore does **not** by itself break
+dual-ABI DLLs — the calling-convention and symbol differences already require
+the dual-entry-thunk design, and ``long double`` only matters if it is part of
+the exported interface.
+
## Relation to "herbceptions" (deterministic exceptions)
WinCall is designed so that Herb Sutter's proposed zero-overhead
>From 04d022d28a3b2bf8f7d17dac18f6f884ee3ac456 Mon Sep 17 00:00:00 2001
From: trcrsired <oyzawqgcfc at gmail.com>
Date: Wed, 12 Aug 2026 03:06:24 +0800
Subject: [PATCH 25/36] [X86][Clang] WinCall: f64 long double, vector
complex/struct args, AVX-512 default, 64-byte stack
Refine the WinCall (x86_64apx-windows) ABI:
- long double is f64 on x86_64apx-windows-gnu (like the MSVC ABI) so the
WinCall ABI never uses x87. Plain x86_64-windows-gnu keeps the f80/128-bit
layout.
- _Complex float/double scalars are coerced to v2f32/v2f64 and passed and
returned in XMM registers instead of by pointer/sret like the MS x64 ABI.
- A record with a single FP/SIMD member is passed like the scalar it wraps
(in a vector register), as long as the record's size/alignment equals the
member's natural size/alignment; if the user bumped the alignment so the
struct is bigger than its member, it stays an aggregate.
- x86_64apx defaults to AVX-512F (APX/Evex implies AVX-512) in both the
clang frontend and the X86 backend, and vzeroupper is disabled for
x86_64apx so WinCall code stays EVEX-encoded without AVX->SSE transition
penalties.
- x86_64apx-windows keeps a 64-byte stack alignment at every call site
(instead of the classic 16-byte) so AVX-512 ZMM spills can use aligned
moves without dynamic realignment; only the Windows apx triple gets this,
other apx targets keep 16-byte and -mstack-alignment still overrides.
- Fix a missing CC_WinCall case in CGDebugInfo::getDwarfCC (CI -Wswitch
error).
- Tests: new wincall-abi2.c for long double/complex/single-FP struct, update
wincall-cconv.ll for the 64-byte-aligned frame. Update WinCall.md.
---
clang/lib/Basic/Targets/X86.cpp | 3 +
clang/lib/Basic/Targets/X86.h | 8 +++
clang/lib/CodeGen/CGDebugInfo.cpp | 4 ++
clang/lib/CodeGen/Targets/X86.cpp | 61 +++++++++++++++++++
clang/test/CodeGen/X86/wincall-abi2.c | 54 ++++++++++++++++
llvm/docs/WinCall.md | 16 +++++
.../X86/MCTargetDesc/X86MCTargetDesc.cpp | 3 +
llvm/lib/Target/X86/X86Subtarget.cpp | 6 ++
llvm/lib/Target/X86/X86Subtarget.h | 4 ++
llvm/test/CodeGen/X86/wincall-cconv.ll | 12 ++--
10 files changed, 165 insertions(+), 6 deletions(-)
create mode 100644 clang/test/CodeGen/X86/wincall-abi2.c
diff --git a/clang/lib/Basic/Targets/X86.cpp b/clang/lib/Basic/Targets/X86.cpp
index 10b63ae3205778..5d7161f021d633 100644
--- a/clang/lib/Basic/Targets/X86.cpp
+++ b/clang/lib/Basic/Targets/X86.cpp
@@ -164,6 +164,9 @@ bool X86TargetInfo::initFeatureMap(
for (const char *Sub :
{"egpr", "push2pop2", "ppx", "ndd", "ccmp", "nf", "zu", "jmpabs"})
setFeatureEnabled(Features, Sub, true);
+ // APX (EVEX) implies AVX-512, and WinCall expects every instruction to be
+ // EVEX-encoded so that no vzeroupper is needed at WinCall boundaries.
+ setFeatureEnabled(Features, "avx512f", true);
}
using namespace llvm::X86;
diff --git a/clang/lib/Basic/Targets/X86.h b/clang/lib/Basic/Targets/X86.h
index 00e0b93b12d1c2..193dc89934f05c 100644
--- a/clang/lib/Basic/Targets/X86.h
+++ b/clang/lib/Basic/Targets/X86.h
@@ -976,6 +976,14 @@ class LLVM_LIBRARY_VISIBILITY MinGWX86_64TargetInfo
public:
MinGWX86_64TargetInfo(const llvm::Triple &Triple, const TargetOptions &Opts)
: WindowsX86_64TargetInfo(Triple, Opts) {
+ if (Triple.isX86_64APX()) {
+ // WinCall unifies long double to f64 (like the MSVC ABI) so that the
+ // WinCall ABI never needs x87.
+ LongDoubleWidth = LongDoubleAlign = 64;
+ LongDoubleFormat = &llvm::APFloat::IEEEdouble();
+ HasFloat128 = true;
+ return;
+ }
// Mingw64 rounds long double size and alignment up to 16 bytes, but sticks
// with x86 FP ops. Weird.
LongDoubleWidth = LongDoubleAlign = 128;
diff --git a/clang/lib/CodeGen/CGDebugInfo.cpp b/clang/lib/CodeGen/CGDebugInfo.cpp
index 9df5792f69d73f..c458a1f24266ba 100644
--- a/clang/lib/CodeGen/CGDebugInfo.cpp
+++ b/clang/lib/CodeGen/CGDebugInfo.cpp
@@ -1811,6 +1811,10 @@ static unsigned getDwarfCC(CallingConv CC) {
return llvm::dwarf::DW_CC_BORLAND_pascal;
case CC_Win64:
return llvm::dwarf::DW_CC_LLVM_Win64;
+ case CC_WinCall:
+ // WinCall is the wincall convention on Windows x86-64; model it with the
+ // Win64 DWARF calling convention code.
+ return llvm::dwarf::DW_CC_LLVM_Win64;
case CC_X86_64SysV:
return llvm::dwarf::DW_CC_LLVM_X86_64SysV;
case CC_AAPCS:
diff --git a/clang/lib/CodeGen/Targets/X86.cpp b/clang/lib/CodeGen/Targets/X86.cpp
index 35d2494b2eb8c0..e419b26ead099d 100644
--- a/clang/lib/CodeGen/Targets/X86.cpp
+++ b/clang/lib/CodeGen/Targets/X86.cpp
@@ -3475,6 +3475,48 @@ ABIArgInfo WinX86_64ABIInfo::classify(QualType Ty, unsigned &FreeSSERegs,
return getNaturalAlignIndirect(Ty, getDataLayout().getAllocaAddrSpace(),
/*ByVal=*/false);
+ // WinCall passes a record that is a single FP/SIMD member exactly like the
+ // scalar it wraps (in a vector register), as long as the record's size and
+ // alignment match the member's natural size and alignment. If the user
+ // bumped the alignment so that the record is bigger than the member (e.g.
+ // a 16-byte-aligned struct holding one double), fall through to the normal
+ // aggregate rules.
+ if (IsWinCall && !Ty->isAnyComplexType() && !Ty->isMemberPointerType() &&
+ !RT->getDecl()->isUnion()) {
+ unsigned NumFields = 0;
+ const FieldDecl *SingleField = nullptr;
+ for (const FieldDecl *FD : RT->getDecl()->fields()) {
+ if (FD->isUnnamedBitField())
+ continue;
+ if (FD->isBitField()) {
+ NumFields = 0;
+ break;
+ }
+ ++NumFields;
+ SingleField = FD;
+ }
+ if (NumFields == 1 && SingleField) {
+ QualType FieldTy = SingleField->getType();
+ llvm::Type *FieldLLTy = CGT.ConvertType(FieldTy);
+ bool IsScalarFP =
+ FieldTy->isFloatingType() && !FieldTy->isComplexType();
+ bool IsVector = FieldTy->isVectorType();
+ if ((IsScalarFP || IsVector) &&
+ (FieldLLTy->isFloatingPointTy() || FieldLLTy->isVectorTy())) {
+ // The record must be exactly as big as the single member so that no
+ // padding/alignment is being carried by the struct.
+ if (getContext().getTypeSize(Ty) ==
+ getContext().getTypeSize(FieldTy)) {
+ if (IsReturnType)
+ return ABIArgInfo::getDirect(FieldLLTy);
+ if (Width <= 128)
+ return ABIArgInfo::getDirect(FieldLLTy);
+ return ABIArgInfo::getExpand();
+ }
+ }
+ }
+ }
+
// wincall passes/returns aggregates that fit in 1, 2, 4, 8, 16 or 32 bytes
// (e.g. 4x size_t, like std::string/std::vector) directly in registers,
// instead of by pointer/sret like the MS x64 ABI.
@@ -3494,6 +3536,25 @@ ABIArgInfo WinX86_64ABIInfo::classify(QualType Ty, unsigned &FreeSSERegs,
}
}
+ // WinCall passes complex scalars in the vector registers: a complex value is
+ // just two elements of its component type, so coerce _Complex float/double
+ // to v2f32/v2f64 and pass/return them in XMM registers instead of by
+ // pointer/sret like the MS x64 ABI.
+ if (IsWinCall && Ty->isAnyComplexType()) {
+ QualType ElemTy = cast<ComplexType>(Ty)->getElementType();
+ llvm::Type *ElemLLTy = CGT.ConvertType(ElemTy);
+ if (llvm::FixedVectorType::isValidElementType(ElemLLTy)) {
+ auto *V2 = llvm::FixedVectorType::get(ElemLLTy, 2);
+ if (IsReturnType)
+ return ABIArgInfo::getDirect(V2);
+ // A 128-bit complex value fits in one XMM register; a wider one (e.g.
+ // long double complex) is expanded into its 64-bit parts.
+ if (Width <= 128)
+ return ABIArgInfo::getDirect(V2);
+ return ABIArgInfo::getExpand();
+ }
+ }
+
const Type *Base = nullptr;
uint64_t NumElts = 0;
// vectorcall adds the concept of a homogenous vector aggregate, similar to
diff --git a/clang/test/CodeGen/X86/wincall-abi2.c b/clang/test/CodeGen/X86/wincall-abi2.c
new file mode 100644
index 00000000000000..65af11da4a2232
--- /dev/null
+++ b/clang/test/CodeGen/X86/wincall-abi2.c
@@ -0,0 +1,54 @@
+// RUN: %clang_cc1 -triple x86_64apx-unknown-windows-msvc -o - -emit-llvm %s | FileCheck %s
+// RUN: %clang_cc1 -triple x86_64apx-unknown-windows-msvc -o - -S %s | FileCheck -check-prefix=ASM %s
+// RUN: %clang_cc1 -triple x86_64apx-unknown-windows-gnu -o - -emit-llvm %s | FileCheck -check-prefix=GNU-LD %s
+
+// WinCall passes:
+// - long double as f64 (no x87),
+// - complex scalars in vector registers,
+// - a single-FP-member struct in the vector register (only when the struct
+// is exactly as big as its single member).
+
+// long double is f64 on x86_64apx-windows-gnu.
+// GNU-LD: define dso_local x86_wincallcc double @"\01f at win"(double noundef %v)
+__attribute__((wincall)) long double f(long double v) { return v; }
+
+// _Complex double travels in one XMM register.
+// CHECK: define dso_local x86_wincallcc <2 x double> @"\01f_cd at win"(<2 x double> noundef %v.coerce)
+// ASM-LABEL: f_cd at win:
+// ASM: vmovupd %xmm0, (%rsp)
+__attribute__((wincall)) _Complex double f_cd(_Complex double v) { return v; }
+
+// _Complex float travels in one XMM register.
+// CHECK: define dso_local x86_wincallcc <2 x float> @"\01f_cf at win"(<2 x float> noundef %v.coerce)
+// ASM-LABEL: f_cf at win:
+// ASM: vmovlpd %xmm0, (%rsp)
+__attribute__((wincall)) _Complex float f_cf(_Complex float v) { return v; }
+
+// A struct holding one double is passed like a double, in XMM0.
+// CHECK: define dso_local x86_wincallcc double @"\01f_od at win"(double %s.coerce)
+// ASM-LABEL: f_od at win:
+__attribute__((wincall)) double f_od(struct one_double { double d; } s) {
+ return s.d;
+}
+
+// A struct holding one float is passed like a float, in XMM0.
+// CHECK: define dso_local x86_wincallcc float @"\01f_of at win"(float %s.coerce)
+// ASM-LABEL: f_of at win:
+__attribute__((wincall)) float f_of(struct one_float { float f; } s) {
+ return s.f;
+}
+
+// A 16-byte-aligned struct holding one double is NOT treated as a scalar:
+// its size (16) is bigger than the member (8), so it stays an aggregate and
+// is expanded into its parts (the single double field; padding is dropped).
+// CHECK: define dso_local x86_wincallcc double @"\01f_ad at win"(double %s.0)
+__attribute__((wincall)) double f_ad(struct aligned_double { double d; } __attribute__((aligned(16))) s) {
+ return s.d;
+}
+
+// A two-double struct is a normal two-register aggregate.
+// CHECK: define dso_local x86_wincallcc double @"\01f_td at win"(double %s.0, double %s.1)
+// ASM-LABEL: f_td at win:
+__attribute__((wincall)) double f_td(struct two_double { double a, b; } s) {
+ return s.a;
+}
diff --git a/llvm/docs/WinCall.md b/llvm/docs/WinCall.md
index 906aac9647f43f..41d8ef2fb80a62 100644
--- a/llvm/docs/WinCall.md
+++ b/llvm/docs/WinCall.md
@@ -212,6 +212,22 @@ and ``MinGW.cpp``, not by the assembler. If the user passes their own
section-alignment ``-Wl`` flag, the driver's default is suppressed in favour
of the user's value.
+## Stack alignment
+
+On ``x86_64apx-windows`` targets the stack is kept **64-byte aligned** at
+every call site (``X86Subtarget`` sets the stack alignment to 64 for
+``isWindowsAPX()`` targets, instead of the 16-byte alignment of the classic
+Windows ABI). This is a deliberate part of the WinCall ABI: it means the
+backend can use aligned 64-byte moves (``vmovaps``/``vmovdqa64``) for
+AVX-512 ZMM spills and aligned stack slots without dynamic stack realignment.
+
+This matters in practice because the classic Windows x64 ABI only guarantees
+16-byte stack alignment, which is not enough for 64-byte ZMM registers — this
+is why GCC still cannot support AVX-512 on Windows correctly. WinCall's 64-byte
+guarantee removes that limitation. Only ``x86_64apx-windows`` gets the 64-byte
+alignment; other ``x86_64apx`` targets (e.g. ``x86_64apx-linux``) keep the
+16-byte default, and a user-supplied ``-mstack-alignment`` still overrides it.
+
## Building a DLL that works with both WinCall and the classic ABI
A function's ABI is decided at the *call boundary*, not inside the function.
diff --git a/llvm/lib/Target/X86/MCTargetDesc/X86MCTargetDesc.cpp b/llvm/lib/Target/X86/MCTargetDesc/X86MCTargetDesc.cpp
index 6f56386e4f96c5..ec0377b5fb4a0b 100644
--- a/llvm/lib/Target/X86/MCTargetDesc/X86MCTargetDesc.cpp
+++ b/llvm/lib/Target/X86/MCTargetDesc/X86MCTargetDesc.cpp
@@ -61,6 +61,9 @@ std::string X86_MC::ParseX86Triple(const Triple &TT) {
if (TT.getSubArch() == Triple::X86_64SubArch_apx) {
FS += ",+egpr,+push2pop2,+ppx,+ndd,+ccmp,+nf,+zu,+jmpabs";
+ // APX (EVEX) implies AVX-512; WinCall assumes every instruction is
+ // EVEX-encoded so no vzeroupper is needed at WinCall boundaries.
+ FS += ",+avx512f,-vzeroupper";
}
return FS;
diff --git a/llvm/lib/Target/X86/X86Subtarget.cpp b/llvm/lib/Target/X86/X86Subtarget.cpp
index ed2da3128b44af..23e04efaa68b70 100644
--- a/llvm/lib/Target/X86/X86Subtarget.cpp
+++ b/llvm/lib/Target/X86/X86Subtarget.cpp
@@ -292,6 +292,12 @@ void X86Subtarget::initSubtargetFeatures(StringRef CPU, StringRef TuneCPU,
// following the i386 psABI, while on Illumos it is always 16 bytes.
if (StackAlignOverride)
stackAlignment = *StackAlignOverride;
+ else if (isWindowsAPX())
+ // WinCall (x86_64apx-windows) guarantees a 64-byte stack alignment so that
+ // AVX-512 (ZMM) spills and aligned moves can always be used without
+ // dynamic stack realignment (GCC still cannot support AVX-512 on Windows
+ // correctly because the classic ABI only aligns the stack to 16 bytes).
+ stackAlignment = Align(64);
else if (isTargetDarwin() || isTargetLinux() || isTargetKFreeBSD() ||
isTargetHurd() || Is64Bit)
stackAlignment = Align(16);
diff --git a/llvm/lib/Target/X86/X86Subtarget.h b/llvm/lib/Target/X86/X86Subtarget.h
index ed81309f25376c..e6700e1031e7c0 100644
--- a/llvm/lib/Target/X86/X86Subtarget.h
+++ b/llvm/lib/Target/X86/X86Subtarget.h
@@ -290,6 +290,10 @@ class X86Subtarget final : public X86GenSubtargetInfo {
const Triple &getTargetTriple() const { return TargetTriple; }
+ /// Tests whether the target is the Windows x86_64apx target, which defaults
+ /// to the WinCall calling convention.
+ bool isWindowsAPX() const { return TargetTriple.isWindowsAPX(); }
+
bool isTargetDarwin() const { return TargetTriple.isOSDarwin(); }
bool isTargetFreeBSD() const { return TargetTriple.isOSFreeBSD(); }
bool isTargetDragonFly() const { return TargetTriple.isOSDragonFly(); }
diff --git a/llvm/test/CodeGen/X86/wincall-cconv.ll b/llvm/test/CodeGen/X86/wincall-cconv.ll
index f3fe3b40a23f8d..cc2c620cd90ce9 100644
--- a/llvm/test/CodeGen/X86/wincall-cconv.ll
+++ b/llvm/test/CodeGen/X86/wincall-cconv.ll
@@ -8,13 +8,13 @@
declare x86_wincallcc void @wincall_thunk(i64, i64, i64, i64, i64, i64, i64, i64)
; CHECK-LABEL: call_8_int:
-; CHECK: subq $40, %rsp
-; CHECK-NEXT: movq 80(%rsp), %r16
-; CHECK-NEXT: movq 88(%rsp), %r17
-; CHECK-NEXT: movq 96(%rsp), %r18
-; CHECK-NEXT: movq 104(%rsp), %r19
+; CHECK: subq $56, %rsp
+; CHECK-NEXT: movq 96(%rsp), %r16
+; CHECK-NEXT: movq 104(%rsp), %r17
+; CHECK-NEXT: movq 112(%rsp), %r18
+; CHECK-NEXT: movq 120(%rsp), %r19
; CHECK-NEXT: callq wincall_thunk
-; CHECK-NEXT: addq $40, %rsp
+; CHECK-NEXT: addq $56, %rsp
; CHECK-NEXT: retq
define void @call_8_int(i64 %a, i64 %b, i64 %c, i64 %d,
i64 %e, i64 %f, i64 %g, i64 %h) nounwind {
>From 8df837703a99ddc24ae561780547c1f95d21c6e3 Mon Sep 17 00:00:00 2001
From: trcrsired <oyzawqgcfc at gmail.com>
Date: Wed, 12 Aug 2026 03:49:11 +0800
Subject: [PATCH 26/36] [skip ci] [Docs][WinCall] Clarify aggregate register
passing for all sizes up to 32 bytes
The previous wording ('fits in 1, 2, 4, 8, 16 or 32 bytes') wrongly implied
only power-of-two sizes are passed in registers. Any trivial record up to
32 bytes is passed directly in registers: up to 64 bits uses one GPR, larger
records are expanded into their 8-byte parts (so a 3-size_t/24-byte struct
travels in RCX, RDX, R8).
---
llvm/docs/WinCall.md | 12 ++++++------
1 file changed, 6 insertions(+), 6 deletions(-)
diff --git a/llvm/docs/WinCall.md b/llvm/docs/WinCall.md
index 41d8ef2fb80a62..dd980e3adf5aa8 100644
--- a/llvm/docs/WinCall.md
+++ b/llvm/docs/WinCall.md
@@ -118,12 +118,12 @@ Clang's ``WinX86_64ABIInfo::classify`` implements the aggregate rules for
``CC_WinCall`` (this is a frontend rule layered on top of the IR-level
convention):
-- A record that fits in **1, 2, 4, 8, 16 or 32 bytes** is passed **directly
- in registers** (not by pointer/sret like the MS x64 ABI). A 4-``size_t``
- struct therefore travels in RCX, RDX, R8, R9.
-- A record of up to 64 bits is coerced to an integer of its size and uses
- **one** GPR; a larger record (up to 32 bytes) is **expanded** into its
- 8-byte parts.
+- A record of up to **32 bytes** is passed **directly in registers** (not by
+ pointer/sret like the MS x64 ABI): a record of up to 64 bits is coerced to
+ an integer of its size and uses **one** GPR; a larger record (e.g. 16, 24
+ or 32 bytes) is **expanded** into its 8-byte parts. A 4-``size_t`` struct
+ therefore travels in RCX, RDX, R8, R9, and a 3-``size_t`` (24-byte) struct
+ in RCX, RDX, R8.
- **Empty records** (``struct empty {}``) consume **no register slots**;
``classify`` returns ``ABIArgInfo::getIgnore()`` for them.
- Records larger than 32 bytes, records with a flexible array member, and
>From 9696654aa8bc962b4f88cce78a5bbff82d78d4ea Mon Sep 17 00:00:00 2001
From: trcrsired <oyzawqgcfc at gmail.com>
Date: Thu, 13 Aug 2026 09:02:14 +0800
Subject: [PATCH 27/36] [X86][Clang] WinCall: also default to wincall on
Cygwin, MSYS and UEFI PE/COFF targets
---
clang/lib/Basic/Targets/X86.h | 2 +
clang/lib/CodeGen/CodeGenModule.cpp | 2 +-
clang/test/CodeGen/X86/wincall-uefi.c | 31 +++++++++++++++
clang/test/CodeGen/wincall.c | 9 +++--
clang/test/CodeGenCXX/wincall-mangle.cpp | 24 ++++++++----
clang/test/Sema/callingconv-wincall.c | 7 +++-
llvm/docs/WinCall.md | 44 ++++++++++++++--------
llvm/include/llvm/TargetParser/Triple.h | 6 +--
llvm/lib/Target/X86/X86Subtarget.h | 4 +-
llvm/lib/Target/X86/X86TargetMachine.cpp | 5 ++-
llvm/unittests/TargetParser/TripleTest.cpp | 17 +++++++++
11 files changed, 116 insertions(+), 35 deletions(-)
create mode 100644 clang/test/CodeGen/X86/wincall-uefi.c
diff --git a/clang/lib/Basic/Targets/X86.h b/clang/lib/Basic/Targets/X86.h
index 193dc89934f05c..c3d11dd886b968 100644
--- a/clang/lib/Basic/Targets/X86.h
+++ b/clang/lib/Basic/Targets/X86.h
@@ -876,6 +876,7 @@ class LLVM_LIBRARY_VISIBILITY UEFIX86_64TargetInfo
switch (CC) {
case CC_C:
case CC_Win64:
+ case CC_WinCall:
case CC_X86_64SysV:
return CCCR_OK;
default:
@@ -1031,6 +1032,7 @@ class LLVM_LIBRARY_VISIBILITY CygwinX86_64TargetInfo : public X86_64TargetInfo {
case CC_SwiftAsync:
case CC_X86RegCall:
case CC_DeviceKernel:
+ case CC_WinCall:
return CCCR_OK;
default:
return CCCR_Warning;
diff --git a/clang/lib/CodeGen/CodeGenModule.cpp b/clang/lib/CodeGen/CodeGenModule.cpp
index e17bd72c2f0520..c94f214945ad92 100644
--- a/clang/lib/CodeGen/CodeGenModule.cpp
+++ b/clang/lib/CodeGen/CodeGenModule.cpp
@@ -1736,7 +1736,7 @@ void CodeGenModule::Release() {
auto UnwindMode = CodeGenOpts.getWinX64EHUnwind();
if (UnwindMode == llvm::WinX64EHUnwindMode::Default) {
- if (T.isOSWindows() && T.isX86_64() &&
+ if (T.isOSWindowsOrUEFI() && T.isX86_64() &&
Context.getTargetInfo().hasFeature("egpr"))
UnwindMode = llvm::WinX64EHUnwindMode::V3;
else
diff --git a/clang/test/CodeGen/X86/wincall-uefi.c b/clang/test/CodeGen/X86/wincall-uefi.c
new file mode 100644
index 00000000000000..7d6e9a568d316d
--- /dev/null
+++ b/clang/test/CodeGen/X86/wincall-uefi.c
@@ -0,0 +1,31 @@
+// RUN: %clang_cc1 -triple x86_64apx-unknown-uefi -o - -emit-llvm %s | FileCheck %s
+// RUN: %clang_cc1 -triple x86_64apx-unknown-uefi -O2 -mframe-pointer=all -o - -S %s | FileCheck -check-prefix=FRAME %s
+// RUN: %clang_cc1 -triple x86_64apx-unknown-uefi -o - -emit-llvm %s | FileCheck -check-prefix=SEH %s
+
+// UEFI is a PE/COFF target, so x86_64apx-unknown-uefi also defaults to the
+// wincall calling convention (8 integer argument registers incl. R16-R19,
+// @win symbol suffix), and because UEFI uses WinCFI it needs unwind v3 so the
+// push2/pop2 frame (pushp/popp) can describe its epilogues.
+
+int add6(int a, int b, int c, int d, int e, int f) { return a + b + c + d + e + f; }
+// CHECK-LABEL: define dso_local x86_wincallcc i32 @"\01add6 at win"(i32 noundef %a, i32 noundef %b, i32 noundef %c, i32 noundef %d, i32 noundef %e, i32 noundef %f)
+
+// The wincall default is also the ABI used for calls.
+void callee(int, int, int);
+void caller(void) {
+ callee(1, 2, 3);
+ // CHECK-LABEL: define dso_local x86_wincallcc void @"\01caller at win"
+ // CHECK: call x86_wincallcc void @"\01callee at win"(i32 noundef 1, i32 noundef 2, i32 noundef 3)
+}
+
+// With a frame pointer the push2/pop2 frame is used for rbp; the epilogue
+// restore needs unwind v3 (module-wide on UEFI, like Windows).
+int framed(int a) {
+ volatile int x = a;
+ // FRAME-LABEL: framed at win:
+ // FRAME: pushp %rbp
+ // FRAME: popp %rbp
+ return x;
+}
+// SEH: !llvm.module.flags = !{!0}
+// SEH: !0 = !{i32 2, !"winx64-eh-unwind", i32 3}
diff --git a/clang/test/CodeGen/wincall.c b/clang/test/CodeGen/wincall.c
index 61dbb1c6a8dda8..81209addbc324a 100644
--- a/clang/test/CodeGen/wincall.c
+++ b/clang/test/CodeGen/wincall.c
@@ -1,9 +1,12 @@
// RUN: %clang_cc1 -triple x86_64apx-unknown-windows-msvc -emit-llvm -o - %s | FileCheck -check-prefix=MSVC %s
// RUN: %clang_cc1 -triple x86_64apx-unknown-windows-gnu -emit-llvm -o - %s | FileCheck -check-prefix=GNU %s
+// RUN: %clang_cc1 -triple x86_64apx-pc-windows-cygnus -emit-llvm -o - %s | FileCheck -check-prefix=GNU %s
+// RUN: %clang_cc1 -triple x86_64apx-pc-windows-msys -emit-llvm -o - %s | FileCheck -check-prefix=GNU %s
+// RUN: %clang_cc1 -triple x86_64apx-unknown-uefi -emit-llvm -o - %s | FileCheck -check-prefix=GNU %s
-// The wincall calling convention is the default for x86_64apx-windows targets
-// and appends a @win suffix to the symbol so the linker can catch calling
-// convention mismatches.
+// The wincall calling convention is the default for x86_64apx PE/COFF
+// targets (Windows, Cygwin, MSYS and UEFI) and appends a @win suffix to the
+// symbol so the linker can catch calling convention mismatches.
void plain(int, int, int);
diff --git a/clang/test/CodeGenCXX/wincall-mangle.cpp b/clang/test/CodeGenCXX/wincall-mangle.cpp
index 8efb01f7296321..eded62170d25d4 100644
--- a/clang/test/CodeGenCXX/wincall-mangle.cpp
+++ b/clang/test/CodeGenCXX/wincall-mangle.cpp
@@ -1,6 +1,9 @@
-// RUN: %clang_cc1 -triple x86_64apx-unknown-windows-gnu -o - -emit-llvm %s | FileCheck %s
+// RUN: %clang_cc1 -triple x86_64apx-unknown-windows-gnu -o - -emit-llvm %s | FileCheck --check-prefixes=ITANIUM %s
+// RUN: %clang_cc1 -triple x86_64apx-pc-windows-cygnus -o - -emit-llvm %s | FileCheck --check-prefixes=ITANIUM %s
+// RUN: %clang_cc1 -triple x86_64apx-unknown-uefi -o - -emit-llvm %s | FileCheck --check-prefixes=MSVC %s
-// wincall is the default calling convention for x86_64apx-windows targets.
+// wincall is the default calling convention for x86_64apx PE/COFF targets
+// (Windows, Cygwin, MSYS and UEFI).
// C++ symbols get a @win suffix (both Itanium and MSVC-style manglings) so
// that the linker can catch calling-convention mismatches, and function
// pointer types carry the wincall vendor qualifier in the Itanium mangling.
@@ -12,23 +15,28 @@ struct C {
};
void C::m(int a) {
- // CHECK-LABEL: define dso_local x86_wincallcc void @"\01_ZN1C1mEi at win"
+ // ITANIUM-LABEL: define dso_local x86_wincallcc void @"\01_ZN1C1mEi at win"
+ // MSVC-LABEL: define dso_local x86_wincallcc void @"?m at C@@QEAAXH at Z@win"
(void)a;
}
void f(int);
void g() {
- // CHECK-LABEL: define dso_local x86_wincallcc void @"\01_Z1gv at win"
+ // ITANIUM-LABEL: define dso_local x86_wincallcc void @"\01_Z1gv at win"
+ // MSVC-LABEL: define dso_local x86_wincallcc void @"?g@@YAXXZ at win"
f(1);
- // CHECK: call x86_wincallcc void @"\01_Z1fi at win"
+ // ITANIUM: call x86_wincallcc void @"\01_Z1fi at win"
+ // MSVC: call x86_wincallcc void @"?f@@YAXH at Z@win"
}
-// Function pointer types get a U7wincall vendor qualifier.
+// Function pointer types get a U7wincall vendor qualifier (Itanium) or are
+// modelled as a distinct type in the MSVC mangling.
template <typename T> T func_as_int(T x);
W w;
W test() {
- // CHECK-LABEL: define dso_local x86_wincallcc noundef ptr @"\01_Z4testv at win"
- // CHECK: call x86_wincallcc noundef ptr @"\01_Z11func_as_intIPU7wincallFviEET_S2_ at win"
+ // ITANIUM-LABEL: define dso_local x86_wincallcc noundef ptr @"\01_Z4testv at win"
+ // ITANIUM: call x86_wincallcc noundef ptr @"\01_Z11func_as_intIPU7wincallFviEET_S2_ at win"
+ // MSVC-LABEL: define dso_local x86_wincallcc noundef ptr @"?test@@YAP6AXH at ZXZ@win"
return func_as_int<W>(w);
}
diff --git a/clang/test/Sema/callingconv-wincall.c b/clang/test/Sema/callingconv-wincall.c
index 3ae621b0a7e941..d342d5f2f2906c 100644
--- a/clang/test/Sema/callingconv-wincall.c
+++ b/clang/test/Sema/callingconv-wincall.c
@@ -1,6 +1,11 @@
// RUN: %clang_cc1 -fsyntax-only -verify -triple x86_64apx-pc-windows-msvc %s
+// RUN: %clang_cc1 -fsyntax-only -verify -triple x86_64apx-pc-windows-gnu %s
+// RUN: %clang_cc1 -fsyntax-only -verify -triple x86_64apx-pc-windows-cygnus %s
+// RUN: %clang_cc1 -fsyntax-only -verify -triple x86_64apx-pc-windows-msys %s
+// RUN: %clang_cc1 -fsyntax-only -verify -triple x86_64apx-unknown-uefi %s
-// wincall is the default calling convention on x86_64apx-windows targets.
+// wincall is the default calling convention on x86_64apx PE/COFF targets
+// (Windows, Cygwin, MSYS and UEFI).
void __attribute__((wincall)) foo(void);
void __attribute__((cdecl)) cdeclfoo(void);
diff --git a/llvm/docs/WinCall.md b/llvm/docs/WinCall.md
index dd980e3adf5aa8..16c418a2edf309 100644
--- a/llvm/docs/WinCall.md
+++ b/llvm/docs/WinCall.md
@@ -2,9 +2,9 @@
## What WinCall is
-WinCall is an x86-64 **calling convention for Windows targets**. It is
-spelled ``x86_wincallcc`` in LLVM IR and corresponds to
-``CallingConv::X86_WinCall`` (CC ID 128).
+WinCall is an x86-64 **calling convention for Windows and other PE/COFF
+targets** (Windows, Cygwin, MSYS and UEFI). It is spelled ``x86_wincallcc``
+in LLVM IR and corresponds to ``CallingConv::X86_WinCall`` (CC ID 128).
Its purpose is to exploit the 16 additional general-purpose registers
(R16-R31) introduced by Intel APX. On a conventional Windows x64 ABI only
@@ -22,10 +22,14 @@ opt-in and only affects code that is explicitly built for it.
WinCall applies in two situations:
-1. It is the **default calling convention** of the ``x86_64apx-windows-msvc``
- and ``x86_64apx-windows-gnu`` target triples
+1. It is the **default calling convention** of the ``x86_64apx`` **PE/COFF**
+ targets: ``x86_64apx-windows-msvc``, ``x86_64apx-windows-gnu``, and the
+ other PE targets that share the Windows ABI conventions — Cygwin
+ (``x86_64apx-pc-windows-cygnus``), MSYS (``x86_64apx-pc-windows-msys``)
+ and UEFI (``x86_64apx-unknown-uefi``).
(``X86_64TargetInfo::getDefaultCallingConv`` returns ``CC_WinCall`` for
- ``getTriple().isWindowsAPX()``).
+ ``getTriple().isWindowsAPX()``, which is true for any ``x86_64apx`` triple
+ whose OS is Windows or UEFI.)
2. It can be selected per-function with the ``wincall`` attribute
(``__attribute__((wincall))``, ``__wincall``, ``_wincall``), which maps to
@@ -44,7 +48,7 @@ modelled on how ``arm64ec`` is a sub-architecture of ``aarch64``:
``Triple::x86_64``.
- ``Triple::isX86_64APX()`` is true for any ``x86_64apx-*`` triple.
- ``Triple::isWindowsAPX()`` is true for ``x86_64apx-*`` triples whose OS is
- Windows; only these default to WinCall.
+ Windows or UEFI (i.e. the PE/COFF targets); only these default to WinCall.
- The name round-trips through ``Triple::getArchName()`` /
``Triple::parseSubArch()``.
@@ -212,21 +216,31 @@ and ``MinGW.cpp``, not by the assembler. If the user passes their own
section-alignment ``-Wl`` flag, the driver's default is suppressed in favour
of the user's value.
+The 64 KiB section alignment is only meaningful for binaries loaded by an OS
+using 64 KiB pages, so it is **not** added automatically for the Cygwin and
+UEFI toolchains (which use the generic GCC driver and ``lld-link``
+respectively); those are typically firmware or runtime-loader images that want
+a conventional section alignment. The stack alignment guarantee below still
+applies to them, since it is a property of the generated code, not of the
+linked image.
+
## Stack alignment
-On ``x86_64apx-windows`` targets the stack is kept **64-byte aligned** at
-every call site (``X86Subtarget`` sets the stack alignment to 64 for
-``isWindowsAPX()`` targets, instead of the 16-byte alignment of the classic
-Windows ABI). This is a deliberate part of the WinCall ABI: it means the
-backend can use aligned 64-byte moves (``vmovaps``/``vmovdqa64``) for
+On ``isWindowsAPX()`` targets (``x86_64apx-windows``, ``x86_64apx-cygnus``,
+``x86_64apx-msys`` and ``x86_64apx-uefi``) the stack is kept **64-byte
+aligned** at every call site (``X86Subtarget`` sets the stack alignment to 64
+for ``isWindowsAPX()`` targets, instead of the 16-byte alignment of the
+classic Windows ABI). This is a deliberate part of the WinCall ABI: it means
+the backend can use aligned 64-byte moves (``vmovaps``/``vmovdqa64``) for
AVX-512 ZMM spills and aligned stack slots without dynamic stack realignment.
This matters in practice because the classic Windows x64 ABI only guarantees
16-byte stack alignment, which is not enough for 64-byte ZMM registers — this
is why GCC still cannot support AVX-512 on Windows correctly. WinCall's 64-byte
-guarantee removes that limitation. Only ``x86_64apx-windows`` gets the 64-byte
-alignment; other ``x86_64apx`` targets (e.g. ``x86_64apx-linux``) keep the
-16-byte default, and a user-supplied ``-mstack-alignment`` still overrides it.
+guarantee removes that limitation. Only ``isWindowsAPX()`` targets get the
+64-byte alignment; other ``x86_64apx`` targets (e.g. ``x86_64apx-linux``) keep
+the 16-byte default, and a user-supplied ``-mstack-alignment`` still overrides
+it.
## Building a DLL that works with both WinCall and the classic ABI
diff --git a/llvm/include/llvm/TargetParser/Triple.h b/llvm/include/llvm/TargetParser/Triple.h
index 0e53da52f23fbb..17c4b9a54b19cb 100644
--- a/llvm/include/llvm/TargetParser/Triple.h
+++ b/llvm/include/llvm/TargetParser/Triple.h
@@ -1017,9 +1017,9 @@ class Triple {
getSubArch() == Triple::X86_64SubArch_apx;
}
- /// Tests whether the target is the Windows x86_64apx target, which defaults
- /// to the wincall calling convention.
- bool isWindowsAPX() const { return isX86_64APX() && isOSWindows(); }
+ /// Tests whether the target is a Windows or UEFI x86_64apx PE/COFF target,
+ /// which defaults to the wincall calling convention.
+ bool isWindowsAPX() const { return isX86_64APX() && isOSWindowsOrUEFI(); }
/// Tests whether the target supports the EHABI exception
/// handling standard.
diff --git a/llvm/lib/Target/X86/X86Subtarget.h b/llvm/lib/Target/X86/X86Subtarget.h
index e6700e1031e7c0..c40a6e7fadc306 100644
--- a/llvm/lib/Target/X86/X86Subtarget.h
+++ b/llvm/lib/Target/X86/X86Subtarget.h
@@ -290,8 +290,8 @@ class X86Subtarget final : public X86GenSubtargetInfo {
const Triple &getTargetTriple() const { return TargetTriple; }
- /// Tests whether the target is the Windows x86_64apx target, which defaults
- /// to the WinCall calling convention.
+ /// Tests whether the target is a Windows or UEFI x86_64apx PE/COFF target,
+ /// which defaults to the WinCall calling convention.
bool isWindowsAPX() const { return TargetTriple.isWindowsAPX(); }
bool isTargetDarwin() const { return TargetTriple.isOSDarwin(); }
diff --git a/llvm/lib/Target/X86/X86TargetMachine.cpp b/llvm/lib/Target/X86/X86TargetMachine.cpp
index 932669b5cbac68..defdbd4c002e44 100644
--- a/llvm/lib/Target/X86/X86TargetMachine.cpp
+++ b/llvm/lib/Target/X86/X86TargetMachine.cpp
@@ -630,8 +630,9 @@ void X86PassConfig::addPreEmitPass2() {
}));
// Analyzes and emits pseudos to support Win x64 Unwind V2. This pass must run
- // after all real instructions have been added to the epilog.
- if (TT.isOSWindows() && TT.isX86_64()) {
+ // after all real instructions have been added to the epilog. UEFI also uses
+ // WinCFI (.seh_proc), so it needs the same V2/V3 unwind handling as Windows.
+ if (TT.isOSWindowsOrUEFI() && TT.isX86_64()) {
addPass(createX86WinEHUnwindV2LegacyPass());
addPass(createX86WinEHUnwindV3Pass());
}
diff --git a/llvm/unittests/TargetParser/TripleTest.cpp b/llvm/unittests/TargetParser/TripleTest.cpp
index 7484cf46beb386..b986918b533a4b 100644
--- a/llvm/unittests/TargetParser/TripleTest.cpp
+++ b/llvm/unittests/TargetParser/TripleTest.cpp
@@ -4035,6 +4035,17 @@ TEST(TripleTest, X86_64APX) {
EXPECT_TRUE(T.isX86_64APX());
EXPECT_TRUE(T.isWindowsAPX());
}
+ {
+ // PE/COFF targets that share the Windows ABI (Cygwin, MSYS, UEFI) also
+ // default to the wincall calling convention.
+ for (const char *T :
+ {"x86_64apx-pc-windows-cygnus", "x86_64apx-pc-windows-msys",
+ "x86_64apx-unknown-uefi"}) {
+ Triple APX = Triple(T);
+ EXPECT_TRUE(APX.isX86_64APX());
+ EXPECT_TRUE(APX.isWindowsAPX());
+ }
+ }
{
Triple T = Triple("x86_64apx-pc-linux-gnu");
EXPECT_EQ(Triple::x86_64, T.getArch());
@@ -4052,6 +4063,12 @@ TEST(TripleTest, X86_64APX) {
EXPECT_FALSE(T.isX86_64APX());
EXPECT_FALSE(T.isWindowsAPX());
}
+ {
+ // Plain non-APX UEFI is not a wincall target.
+ Triple T = Triple("x86_64-unknown-uefi");
+ EXPECT_FALSE(T.isX86_64APX());
+ EXPECT_FALSE(T.isWindowsAPX());
+ }
}
} // end anonymous namespace
>From 41cb49b9b83878d177166b0c03c812672f214eb0 Mon Sep 17 00:00:00 2001
From: trcrsired <oyzawqgcfc at gmail.com>
Date: Thu, 13 Aug 2026 13:34:09 +0800
Subject: [PATCH 28/36] [skip ci] [X86][Clang] WinCall: msabi implies wincall
on x86_64apx, wincall valid on any x86-64, define __x86_64apx__
---
clang/lib/AST/Type.cpp | 1 +
clang/lib/Basic/Targets/X86.cpp | 3 +++
clang/lib/Basic/Targets/X86.h | 1 +
clang/lib/Sema/SemaDeclAttr.cpp | 8 +++++++-
clang/lib/Sema/SemaType.cpp | 24 +++++++++++++++++-------
clang/test/CodeGen/X86/wincall-msabi.c | 20 ++++++++++++++++++++
clang/test/Preprocessor/init-x86.c | 8 ++++++++
llvm/docs/WinCall.md | 8 ++++++--
8 files changed, 63 insertions(+), 10 deletions(-)
create mode 100644 clang/test/CodeGen/X86/wincall-msabi.c
diff --git a/clang/lib/AST/Type.cpp b/clang/lib/AST/Type.cpp
index 982cd89a82e16c..59d3f9645d7054 100644
--- a/clang/lib/AST/Type.cpp
+++ b/clang/lib/AST/Type.cpp
@@ -4569,6 +4569,7 @@ bool AttributedType::isCallingConv() const {
case attr::PreserveNone:
case attr::RISCVVectorCC:
case attr::RISCVVLSCC:
+ case attr::WinCall:
return true;
}
llvm_unreachable("invalid attr kind");
diff --git a/clang/lib/Basic/Targets/X86.cpp b/clang/lib/Basic/Targets/X86.cpp
index 5d7161f021d633..903bbb71c33c51 100644
--- a/clang/lib/Basic/Targets/X86.cpp
+++ b/clang/lib/Basic/Targets/X86.cpp
@@ -545,6 +545,9 @@ void X86TargetInfo::getTargetDefines(const LangOptions &Opts,
Builder.defineMacro("__x86_64h");
Builder.defineMacro("__x86_64h__");
}
+ if (getTriple().isX86_64APX()) {
+ Builder.defineMacro("__x86_64apx__");
+ }
} else {
DefineStd(Builder, "i386", Opts);
}
diff --git a/clang/lib/Basic/Targets/X86.h b/clang/lib/Basic/Targets/X86.h
index c3d11dd886b968..ffa40b049359a3 100644
--- a/clang/lib/Basic/Targets/X86.h
+++ b/clang/lib/Basic/Targets/X86.h
@@ -756,6 +756,7 @@ class LLVM_LIBRARY_VISIBILITY X86_64TargetInfo : public X86TargetInfo {
case CC_X86VectorCall:
case CC_IntelOclBicc:
case CC_Win64:
+ case CC_WinCall:
case CC_PreserveMost:
case CC_PreserveAll:
case CC_PreserveNone:
diff --git a/clang/lib/Sema/SemaDeclAttr.cpp b/clang/lib/Sema/SemaDeclAttr.cpp
index 7bf6c319ae14f7..e0cb5927de4ff1 100644
--- a/clang/lib/Sema/SemaDeclAttr.cpp
+++ b/clang/lib/Sema/SemaDeclAttr.cpp
@@ -5863,7 +5863,13 @@ bool Sema::CheckCallingConvAttr(const ParsedAttr &Attrs, CallingConv &CC,
CC = CC_X86RegCall;
break;
case ParsedAttr::AT_MSABI:
- CC = IsTargetDefaultMSABI ? CC_C : CC_Win64;
+ // On x86_64apx the MS ABI is WinCall (the APX-aware Microsoft convention),
+ // so msabi implies wincall there. Elsewhere msabi stays the classic MS x64
+ // convention, unless it is combined with an explicit wincall attribute.
+ if (Context.getTargetInfo().getTriple().isX86_64APX())
+ CC = CC_WinCall;
+ else
+ CC = IsTargetDefaultMSABI ? CC_C : CC_Win64;
break;
case ParsedAttr::AT_SysVABI:
CC = IsTargetDefaultMSABI ? CC_X86_64SysV : CC_C;
diff --git a/clang/lib/Sema/SemaType.cpp b/clang/lib/Sema/SemaType.cpp
index 156bebf380e811..39a93f7b99499e 100644
--- a/clang/lib/Sema/SemaType.cpp
+++ b/clang/lib/Sema/SemaType.cpp
@@ -8365,13 +8365,23 @@ static bool handleFunctionTypeAttr(TypeProcessingState &state, ParsedAttr &attr,
if (CCOld != CC) {
// Error out on when there's already an attribute on the type
// and the CCs don't match.
- if (S.getCallingConvAttributedType(type)) {
- S.Diag(attr.getLoc(), diag::err_attributes_are_not_compatible)
- << FunctionType::getNameForCallConv(CC)
- << FunctionType::getNameForCallConv(CCOld)
- << attr.isRegularKeywordAttribute();
- attr.setInvalid();
- return true;
+ const AttributedType *ExistingCC = S.getCallingConvAttributedType(type);
+ if (ExistingCC) {
+ // msabi and wincall may be combined: the MS x64 ABI is the base of
+ // WinCall, so [[msabi, wincall]] (in either order) resolves to wincall.
+ bool IsMSABIPlusWinCall = (ExistingCC->getAttrKind() == attr::MSABI &&
+ attr.getKind() == ParsedAttr::AT_WinCall) ||
+ (ExistingCC->getAttrKind() == attr::WinCall &&
+ attr.getKind() == ParsedAttr::AT_MSABI);
+ if (!IsMSABIPlusWinCall) {
+ S.Diag(attr.getLoc(), diag::err_attributes_are_not_compatible)
+ << FunctionType::getNameForCallConv(CC)
+ << FunctionType::getNameForCallConv(CCOld)
+ << attr.isRegularKeywordAttribute();
+ attr.setInvalid();
+ return true;
+ }
+ CC = CC_WinCall;
}
}
diff --git a/clang/test/CodeGen/X86/wincall-msabi.c b/clang/test/CodeGen/X86/wincall-msabi.c
new file mode 100644
index 00000000000000..76b29d5609507a
--- /dev/null
+++ b/clang/test/CodeGen/X86/wincall-msabi.c
@@ -0,0 +1,20 @@
+// RUN: %clang_cc1 -triple x86_64apx-unknown-linux-gnu -emit-llvm -o - %s | FileCheck --check-prefixes=APX,ALL %s
+// RUN: %clang_cc1 -triple x86_64-unknown-linux-gnu -emit-llvm -o - %s | FileCheck --check-prefixes=PLAIN,ALL %s
+
+// WinCall implies the Microsoft x64 ABI, so the wincall attribute is valid on
+// any x86-64 target (ELF, Mach-O, ...) and does not need to be combined with
+// ms_abi. On x86_64apx, msabi alone also implies wincall; elsewhere msabi
+// stays the classic MS x64 (win64cc) convention.
+
+__attribute__((wincall)) int f_wc(int a) { return a; }
+// ALL-LABEL: define dso_local x86_wincallcc i32 @f_wc
+
+__attribute__((ms_abi, wincall)) int f_mw(int a) { return a; }
+// ALL-LABEL: define dso_local x86_wincallcc i32 @f_mw
+
+__attribute__((wincall, ms_abi)) int f_wm(int a) { return a; }
+// ALL-LABEL: define dso_local x86_wincallcc i32 @f_wm
+
+__attribute__((ms_abi)) int f_msabi(int a) { return a; }
+// APX-LABEL: define dso_local x86_wincallcc i32 @f_msabi
+// PLAIN-LABEL: define dso_local win64cc i32 @f_msabi
diff --git a/clang/test/Preprocessor/init-x86.c b/clang/test/Preprocessor/init-x86.c
index f5fd976b3db737..c298ca4a55acf8 100644
--- a/clang/test/Preprocessor/init-x86.c
+++ b/clang/test/Preprocessor/init-x86.c
@@ -842,6 +842,14 @@
// X86_64H:#define __x86_64h 1
// X86_64H:#define __x86_64h__ 1
+// RUN: %clang_cc1 -E -dM -ffreestanding -triple=x86_64apx-none-none < /dev/null | FileCheck -match-full-lines -check-prefix X86_64APX %s
+//
+// X86_64APX:#define __x86_64 1
+// X86_64APX:#define __x86_64__ 1
+// X86_64APX:#define __x86_64apx__ 1
+// X86_64APX-NOT:#define __x86_64h 1
+// X86_64APX-NOT:#define __x86_64h__ 1
+
// RUN: %clang_cc1 -E -dM -ffreestanding -fgnuc-version=4.2.1 -triple=x86_64-none-none-gnux32 < /dev/null | FileCheck -match-full-lines -check-prefix X32 %s
// RUN: %clang_cc1 -x c++ -E -dM -ffreestanding -fgnuc-version=4.2.1 -triple=x86_64-none-none-gnux32 < /dev/null | FileCheck -match-full-lines -check-prefix X32 -check-prefix X32-CXX %s
//
diff --git a/llvm/docs/WinCall.md b/llvm/docs/WinCall.md
index 16c418a2edf309..e24d0b918b9659 100644
--- a/llvm/docs/WinCall.md
+++ b/llvm/docs/WinCall.md
@@ -32,8 +32,12 @@ WinCall applies in two situations:
whose OS is Windows or UEFI.)
2. It can be selected per-function with the ``wincall`` attribute
- (``__attribute__((wincall))``, ``__wincall``, ``_wincall``), which maps to
- ``CC_WinCall`` in the frontend.
+ (``__attribute__((wincall))``, ``__wincall``, ``_wincall``,
+ ``[[gnu::wincall]]``), which maps to ``CC_WinCall`` in the frontend.
+ WinCall is the APX-aware Microsoft ABI, so ``wincall`` already implies the
+ MS x64 ABI — there is no need to also write ``ms_abi``. (``ms_abi`` alone is
+ still the classic MS x64 convention on non-APX targets, while on
+ ``x86_64apx`` targets ``ms_abi`` alone implies ``wincall``.)
WinCall is **x86-64 only**. On 32-bit x86 or on any non-x86 target the
attribute is ignored with a warning
>From 804ed4e62bafdbb3f70cf45b4e59aa1c8cf51f12 Mon Sep 17 00:00:00 2001
From: trcrsired <oyzawqgcfc at gmail.com>
Date: Tue, 25 Aug 2026 22:10:02 +0800
Subject: [PATCH 29/36] [CIR] Add WinCall calling convention support
Add X86WinCall to the CIR CallingConv enum and wire it through the
full pipeline: constructAttributeList and setCIRFunctionAttributes
now read the calling convention from the function type (where Clang
stores it), and LowerToLLVM maps it to LLVM's X86_WinCall. This
enables __attribute__((wincall)) and the x86_64apx Windows default
to lower correctly through CIR to LLVM IR.
---
clang/include/clang/CIR/Dialect/IR/CIROps.td | 3 ++-
clang/lib/CIR/CodeGen/CIRGenCall.cpp | 14 ++++++++++-
clang/lib/CIR/CodeGen/CIRGenModule.cpp | 22 ++++++++++++----
.../CIR/Lowering/DirectToLLVM/LowerToLLVM.cpp | 2 ++
clang/test/CIR/CodeGen/wincall.c | 25 +++++++++++++++++++
mlir/include/mlir/Dialect/LLVMIR/LLVMEnums.td | 5 +++-
6 files changed, 63 insertions(+), 8 deletions(-)
create mode 100644 clang/test/CIR/CodeGen/wincall.c
diff --git a/clang/include/clang/CIR/Dialect/IR/CIROps.td b/clang/include/clang/CIR/Dialect/IR/CIROps.td
index a52c37e4860ce0..2aaf7af4c4db71 100644
--- a/clang/include/clang/CIR/Dialect/IR/CIROps.td
+++ b/clang/include/clang/CIR/Dialect/IR/CIROps.td
@@ -4166,7 +4166,8 @@ def CIR_CallingConv : CIR_I32EnumAttr<"CallingConv", "calling convention", [
I32EnumAttrCase<"PTXKernel", 1, "ptx_kernel">,
I32EnumAttrCase<"SpirFunction", 2, "spir_function">,
I32EnumAttrCase<"SpirKernel", 3, "spir_kernel">,
- I32EnumAttrCase<"AMDGPUKernel", 4, "amdgpu_kernel">
+ I32EnumAttrCase<"AMDGPUKernel", 4, "amdgpu_kernel">,
+ I32EnumAttrCase<"X86WinCall", 5, "x86_wincall">
]>;
def CIR_FuncOp : CIR_Op<"func", [
diff --git a/clang/lib/CIR/CodeGen/CIRGenCall.cpp b/clang/lib/CIR/CodeGen/CIRGenCall.cpp
index 3e689e031f7ad9..fbb0f8e512d4a1 100644
--- a/clang/lib/CIR/CodeGen/CIRGenCall.cpp
+++ b/clang/lib/CIR/CodeGen/CIRGenCall.cpp
@@ -319,7 +319,19 @@ void CIRGenModule::constructAttributeList(
llvm::MutableArrayRef<mlir::NamedAttrList> argAttrs,
mlir::NamedAttrList &retAttrs, cir::CallingConv &callingConv,
cir::SideEffect &sideEffect, bool attrOnCallSite, bool isThunk) {
- assert(!cir::MissingFeatures::opCallCallConv());
+ // Compute the calling convention based on the function type.
+ // The calling convention is stored on the function type, not the declaration.
+ callingConv = cir::CallingConv::C;
+ if (const Decl *decl = calleeInfo.getCalleeDecl().getDecl()) {
+ if (const FunctionDecl *fd = dyn_cast<FunctionDecl>(decl)) {
+ clang::CallingConv cc =
+ fd->getType()->castAs<clang::FunctionType>()->getCallConv();
+ if (cc == clang::CallingConv::CC_WinCall) {
+ callingConv = cir::CallingConv::X86WinCall;
+ }
+ }
+ }
+
sideEffect = cir::SideEffect::All;
auto addUnitAttr = [&](llvm::StringRef name) {
diff --git a/clang/lib/CIR/CodeGen/CIRGenModule.cpp b/clang/lib/CIR/CodeGen/CIRGenModule.cpp
index 6c49afe2436064..84ab09100d506f 100644
--- a/clang/lib/CIR/CodeGen/CIRGenModule.cpp
+++ b/clang/lib/CIR/CodeGen/CIRGenModule.cpp
@@ -3182,11 +3182,11 @@ void CIRGenModule::setCIRFunctionAttributes(GlobalDecl globalDecl,
// TODO(cir): Check X86_VectorCall incompatibility wiht WinARM64EC
- // TODO(cir): Set the calling convention computed by constructAttributeList
- // on the function. FuncOp supports calling_conv, but target-specific
- // CodeGen is needed to set it correctly (e.g., AMDGPU kernel functions
- // should be marked with AMDGPUKernel).
- assert(!cir::MissingFeatures::opFuncCallingConv());
+ // Set the calling convention on the function if it's not the default C
+ // calling convention. Target-specific CodeGen is needed to set it correctly
+ // (e.g., AMDGPU kernel functions should be marked with AMDGPUKernel).
+ if (callingConv != cir::CallingConv::C)
+ func.setCallingConv(callingConv);
}
void CIRGenModule::setFunctionAttributes(GlobalDecl globalDecl,
@@ -3255,6 +3255,18 @@ void CIRGenModule::setCIRFunctionAttributesForDefinition(
f->setAttr(cir::CIRDialect::getNoThrowAttrName(),
mlir::UnitAttr::get(&getMLIRContext()));
+ // Set calling convention for function definitions.
+ // WinCall is the default for x86_64apx Windows targets or when the wincall
+ // attribute is present. The calling convention is stored on the function
+ // type.
+ if (decl) {
+ clang::CallingConv cc =
+ decl->getType()->castAs<clang::FunctionType>()->getCallConv();
+ if (cc == clang::CallingConv::CC_WinCall) {
+ f.setCallingConv(cir::CallingConv::X86WinCall);
+ }
+ }
+
std::optional<cir::InlineKind> existingInlineKind = f.getInlineKind();
bool isNoInline =
existingInlineKind && *existingInlineKind == cir::InlineKind::NoInline;
diff --git a/clang/lib/CIR/Lowering/DirectToLLVM/LowerToLLVM.cpp b/clang/lib/CIR/Lowering/DirectToLLVM/LowerToLLVM.cpp
index 20db99f11fbef5..c86b4c0ca1f769 100644
--- a/clang/lib/CIR/Lowering/DirectToLLVM/LowerToLLVM.cpp
+++ b/clang/lib/CIR/Lowering/DirectToLLVM/LowerToLLVM.cpp
@@ -336,6 +336,8 @@ static mlir::LLVM::CConv convertCallingConv(cir::CallingConv callingConv) {
return LLVM::PTX_Kernel;
case CIR::AMDGPUKernel:
return LLVM::AMDGPU_KERNEL;
+ case CIR::X86WinCall:
+ return LLVM::X86_WinCall;
}
llvm_unreachable("Unknown calling convention");
}
diff --git a/clang/test/CIR/CodeGen/wincall.c b/clang/test/CIR/CodeGen/wincall.c
new file mode 100644
index 00000000000000..1d74e4a75307e0
--- /dev/null
+++ b/clang/test/CIR/CodeGen/wincall.c
@@ -0,0 +1,25 @@
+// RUN: %clang_cc1 -triple x86_64-unknown-linux-gnu -fclangir -emit-cir %s -o %t.cir
+// RUN: FileCheck --check-prefix=CIR --input-file=%t.cir %s
+// RUN: %clang_cc1 -triple x86_64-unknown-linux-gnu -fclangir -emit-llvm %s -o %t-cir.ll
+// RUN: FileCheck --check-prefix=LLVM-CIR --input-file=%t-cir.ll %s
+// RUN: %clang_cc1 -triple x86_64-unknown-linux-gnu -emit-llvm %s -o %t.ll
+// RUN: FileCheck --check-prefix=LLVM-OGCG --input-file=%t.ll %s
+
+// Test wincall calling convention in CIR
+// WinCall is the default for x86_64apx Windows targets or via the wincall attribute
+
+void __attribute__((wincall)) wc(int a, int b, int c);
+void __attribute__((wincall)) caller(void) { wc(1, 2, 3); }
+void __attribute__((wincall)) plain(int a, int b, int c) { wc(a, b, c); }
+
+// CIR: cir.func no_inline dso_local @caller{{.*}}cc(x86_wincall)
+// CIR: cir.func private @wc{{.*}}cc(x86_wincall)
+// CIR: cir.func no_inline dso_local @plain{{.*}}cc(x86_wincall)
+
+// LLVM-CIR: define dso_local x86_wincallcc void @caller()
+// LLVM-CIR: declare x86_wincallcc void @wc(i32 noundef, i32 noundef, i32 noundef)
+// LLVM-CIR: define dso_local x86_wincallcc void @plain(i32 noundef %0, i32 noundef %1, i32 noundef %2)
+
+// LLVM-OGCG: define dso_local x86_wincallcc void @caller()
+// LLVM-OGCG: declare x86_wincallcc void @wc(i32 noundef, i32 noundef, i32 noundef)
+// LLVM-OGCG: define dso_local x86_wincallcc void @plain(i32 noundef %a, i32 noundef %b, i32 noundef %c)
diff --git a/mlir/include/mlir/Dialect/LLVMIR/LLVMEnums.td b/mlir/include/mlir/Dialect/LLVMIR/LLVMEnums.td
index 4304ec207acf61..e6fbea3469e3f6 100644
--- a/mlir/include/mlir/Dialect/LLVMIR/LLVMEnums.td
+++ b/mlir/include/mlir/Dialect/LLVMIR/LLVMEnums.td
@@ -272,6 +272,8 @@ def CConvAMDGPU_Gfx : LLVM_EnumAttrCase<"AMDGPU_Gfx", "amdgpu_gfxcc",
"AMDGPU_Gfx", 100>;
def CConvM68k_INTR : LLVM_EnumAttrCase<"M68k_INTR", "m68k_intrcc", "M68k_INTR",
101>;
+def CConvX86_WinCall : LLVM_EnumAttrCase<"X86_WinCall", "x86_wincallcc",
+ "X86_WinCall", 128>;
def CConvEnum : LLVM_CEnumAttr<
"CConv",
@@ -288,7 +290,8 @@ def CConvEnum : LLVM_CEnumAttr<
CConvAMDGPU_VS, CConvAMDGPU_GS, CConvAMDGPU_CS, CConvAMDGPU_KERNEL,
CConvX86_RegCall, CConvAMDGPU_HS, CConvMSP430_BUILTIN, CConvAMDGPU_LS,
CConvAMDGPU_ES, CConvAArch64_VectorCall, CConvAArch64_SVE_VectorCall,
- CConvWASM_EmscriptenInvoke, CConvAMDGPU_Gfx, CConvM68k_INTR
+ CConvWASM_EmscriptenInvoke, CConvAMDGPU_Gfx, CConvM68k_INTR,
+ CConvX86_WinCall
]> {
let cppNamespace = "::mlir::LLVM::cconv";
}
>From 78c9f4a7d34962473556b353c5c7163487c5cb4d Mon Sep 17 00:00:00 2001
From: trcrsired <oyzawqgcfc at gmail.com>
Date: Wed, 26 Aug 2026 01:31:08 +0800
Subject: [PATCH 30/36] [mlir][WinCall] x86_wincallcc should be in the
calling convention list
---
mlir/test/Dialect/LLVMIR/func.mlir | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/mlir/test/Dialect/LLVMIR/func.mlir b/mlir/test/Dialect/LLVMIR/func.mlir
index 8af1e524854258..76c9a8afc00662 100644
--- a/mlir/test/Dialect/LLVMIR/func.mlir
+++ b/mlir/test/Dialect/LLVMIR/func.mlir
@@ -523,7 +523,7 @@ module {
module {
"llvm.func"() ({
- // expected-error @below {{expected one of [ccc, fastcc, coldcc, cc_10, cc_11, anyregcc, preserve_mostcc, preserve_allcc, swiftcc, cxx_fast_tlscc, tailcc, cfguard_checkcc, swifttailcc, x86_stdcallcc, x86_fastcallcc, arm_apcscc, arm_aapcscc, arm_aapcs_vfpcc, msp430_intrcc, x86_thiscallcc, ptx_kernelcc, ptx_devicecc, spir_funccc, spir_kernelcc, intel_ocl_bicc, x86_64_sysvcc, win64cc, x86_vectorcallcc, hhvmcc, hhvm_ccc, x86_intrcc, avr_intrcc, avr_builtincc, amdgpu_vscc, amdgpu_gscc, amdgpu_cscc, amdgpu_kernelcc, x86_regcallcc, amdgpu_hscc, msp430_builtincc, amdgpu_lscc, amdgpu_escc, aarch64_vectorcallcc, aarch64_sve_vectorcallcc, wasm_emscripten_invokecc, amdgpu_gfxcc, m68k_intrcc] for Calling Conventions, got: cc_12}}
+ // expected-error @below {{expected one of [ccc, fastcc, coldcc, cc_10, cc_11, anyregcc, preserve_mostcc, preserve_allcc, swiftcc, cxx_fast_tlscc, tailcc, cfguard_checkcc, swifttailcc, x86_stdcallcc, x86_fastcallcc, arm_apcscc, arm_aapcscc, arm_aapcs_vfpcc, msp430_intrcc, x86_thiscallcc, ptx_kernelcc, ptx_devicecc, spir_funccc, spir_kernelcc, intel_ocl_bicc, x86_64_sysvcc, win64cc, x86_vectorcallcc, hhvmcc, hhvm_ccc, x86_intrcc, avr_intrcc, avr_builtincc, amdgpu_vscc, amdgpu_gscc, amdgpu_cscc, amdgpu_kernelcc, x86_regcallcc, amdgpu_hscc, msp430_builtincc, amdgpu_lscc, amdgpu_escc, aarch64_vectorcallcc, aarch64_sve_vectorcallcc, wasm_emscripten_invokecc, amdgpu_gfxcc, m68k_intrcc, x86_wincallcc] for Calling Conventions, got: cc_12}}
// expected-error @below {{failed to parse CConvAttr parameter 'CallingConv' which is to be a `CConv`}}
}) {sym_name = "generic_unknown_calling_convention", CConv = #llvm.cconv<cc_12>, function_type = !llvm.func<i64 (i64, i64)>} : () -> ()
}
>From 85e1fe116eafc598b8877d981fa9a0fcca752849 Mon Sep 17 00:00:00 2001
From: trcrsired <oyzawqgcfc at gmail.com>
Date: Sat, 5 Sep 2026 02:24:36 +0800
Subject: [PATCH 31/36] [WinCall] Merge from upstream code but it now has
formatting issue. resolve it to make CI happy
---
clang/lib/CIR/CodeGen/CIRGenCall.cpp | 5 +++--
clang/lib/CIR/CodeGen/CIRGenModule.cpp | 1 -
2 files changed, 3 insertions(+), 3 deletions(-)
diff --git a/clang/lib/CIR/CodeGen/CIRGenCall.cpp b/clang/lib/CIR/CodeGen/CIRGenCall.cpp
index e2fd52bd13d603..1c0acfb2038bb8 100644
--- a/clang/lib/CIR/CodeGen/CIRGenCall.cpp
+++ b/clang/lib/CIR/CodeGen/CIRGenCall.cpp
@@ -325,8 +325,9 @@ void CIRGenModule::constructAttributeList(
cir::SideEffect &sideEffect, bool attrOnCallSite, bool isThunk) {
// Compute the calling convention based on the function type.
// The calling convention is stored on the function type, not the declaration.
- // WinCall is the only target-specific CC routed here; clangCallConvToCIRCallConv
- // in CIRGenTypes.cpp covers the other cases (C, DeviceKernel, SPIR/SPIR-V).
+ // WinCall is the only target-specific CC routed here;
+ // clangCallConvToCIRCallConv in CIRGenTypes.cpp covers the other cases (C,
+ // DeviceKernel, SPIR/SPIR-V).
callingConv = cir::CallingConv::C;
if (const Decl *decl = calleeInfo.getCalleeDecl().getDecl()) {
if (const FunctionDecl *fd = dyn_cast<FunctionDecl>(decl)) {
diff --git a/clang/lib/CIR/CodeGen/CIRGenModule.cpp b/clang/lib/CIR/CodeGen/CIRGenModule.cpp
index e178affb792e62..2e6a22c951787a 100644
--- a/clang/lib/CIR/CodeGen/CIRGenModule.cpp
+++ b/clang/lib/CIR/CodeGen/CIRGenModule.cpp
@@ -3239,7 +3239,6 @@ void CIRGenModule::setCIRFunctionAttributes(GlobalDecl globalDecl,
// (e.g., AMDGPU kernel functions should be marked with AMDGPUKernel).
if (callingConv != cir::CallingConv::C)
func.setCallingConv(callingConv);
-
}
void CIRGenModule::setFunctionAttributes(GlobalDecl globalDecl,
>From 08e4dddecbe3068318290813594fb1243c62a1fe Mon Sep 17 00:00:00 2001
From: trcrsired <oyzawqgcfc at gmail.com>
Date: Sat, 5 Sep 2026 02:45:21 +0800
Subject: [PATCH 32/36] [CIR] Route WinCall through clangCallConvToCIRCallConv
Now that CIRGenFunctionInfo exposes getCallingConvention() (set via
clangCallConvToCIRCallConv in CIRGenTypes.cpp), add a CC_WinCall case
to that helper so the resulting cir::CallingConv::X86WinCall flows
through the same path as C/DeviceKernel/SPIR.
This lets constructAttributeList drop the FunctionDecl-based wincall
workaround and just read info.getCallingConvention(), matching the
upstream shape.
---
clang/lib/CIR/CodeGen/CIRGenCall.cpp | 16 +---------------
clang/lib/CIR/CodeGen/CIRGenTypes.cpp | 2 ++
2 files changed, 3 insertions(+), 15 deletions(-)
diff --git a/clang/lib/CIR/CodeGen/CIRGenCall.cpp b/clang/lib/CIR/CodeGen/CIRGenCall.cpp
index 1c0acfb2038bb8..d4085a9fdac53d 100644
--- a/clang/lib/CIR/CodeGen/CIRGenCall.cpp
+++ b/clang/lib/CIR/CodeGen/CIRGenCall.cpp
@@ -323,21 +323,7 @@ void CIRGenModule::constructAttributeList(
llvm::MutableArrayRef<mlir::NamedAttrList> argAttrs,
mlir::NamedAttrList &retAttrs, cir::CallingConv &callingConv,
cir::SideEffect &sideEffect, bool attrOnCallSite, bool isThunk) {
- // Compute the calling convention based on the function type.
- // The calling convention is stored on the function type, not the declaration.
- // WinCall is the only target-specific CC routed here;
- // clangCallConvToCIRCallConv in CIRGenTypes.cpp covers the other cases (C,
- // DeviceKernel, SPIR/SPIR-V).
- callingConv = cir::CallingConv::C;
- if (const Decl *decl = calleeInfo.getCalleeDecl().getDecl()) {
- if (const FunctionDecl *fd = dyn_cast<FunctionDecl>(decl)) {
- clang::CallingConv cc =
- fd->getType()->castAs<clang::FunctionType>()->getCallConv();
- if (cc == clang::CallingConv::CC_WinCall) {
- callingConv = cir::CallingConv::X86WinCall;
- }
- }
- }
+ callingConv = info.getCallingConvention();
sideEffect = cir::SideEffect::All;
diff --git a/clang/lib/CIR/CodeGen/CIRGenTypes.cpp b/clang/lib/CIR/CodeGen/CIRGenTypes.cpp
index f2cb875e908a75..9b1acf129211fe 100644
--- a/clang/lib/CIR/CodeGen/CIRGenTypes.cpp
+++ b/clang/lib/CIR/CodeGen/CIRGenTypes.cpp
@@ -767,6 +767,8 @@ CIRGenTypes::clangCallConvToCIRCallConv(clang::CallingConv cc) {
return cir::CallingConv::C;
case CC_DeviceKernel:
return cgm.getTargetCIRGenInfo().getDeviceKernelCallingConv();
+ case CC_WinCall:
+ return cir::CallingConv::X86WinCall;
default:
// TODO(cir): Support the remaining target-specific calling conventions.
return cir::CallingConv::C;
>From b4761ab658f68b6c26b72aef3e2b24c158dbc317 Mon Sep 17 00:00:00 2001
From: trcrsired <oyzawqgcfc at gmail.com>
Date: Sat, 5 Sep 2026 02:55:05 +0800
Subject: [PATCH 33/36] [X86][WinCall] revert the CIRGenCall.cpp changes
to upstream. no reason to have specific code here
---
clang/lib/CIR/CodeGen/CIRGenCall.cpp | 1 -
1 file changed, 1 deletion(-)
diff --git a/clang/lib/CIR/CodeGen/CIRGenCall.cpp b/clang/lib/CIR/CodeGen/CIRGenCall.cpp
index d4085a9fdac53d..752d4d863cb7e8 100644
--- a/clang/lib/CIR/CodeGen/CIRGenCall.cpp
+++ b/clang/lib/CIR/CodeGen/CIRGenCall.cpp
@@ -324,7 +324,6 @@ void CIRGenModule::constructAttributeList(
mlir::NamedAttrList &retAttrs, cir::CallingConv &callingConv,
cir::SideEffect &sideEffect, bool attrOnCallSite, bool isThunk) {
callingConv = info.getCallingConvention();
-
sideEffect = cir::SideEffect::All;
auto addUnitAttr = [&](llvm::StringRef name) {
>From c6501515d041e77f85ef94b07a52e212b9508c81 Mon Sep 17 00:00:00 2001
From: trcrsired <oyzawqgcfc at gmail.com>
Date: Sat, 5 Sep 2026 21:27:18 +0800
Subject: [PATCH 34/36] [clang][CodeGen] Replace IsReturnType parameter
with ClassifyKind::Return
The function parameter from upstream changes, so we
change too
---
clang/lib/CodeGen/Targets/X86.cpp | 10 +++++-----
1 file changed, 5 insertions(+), 5 deletions(-)
diff --git a/clang/lib/CodeGen/Targets/X86.cpp b/clang/lib/CodeGen/Targets/X86.cpp
index 4f25e89a76efd6..172b9f7256c3b6 100644
--- a/clang/lib/CodeGen/Targets/X86.cpp
+++ b/clang/lib/CodeGen/Targets/X86.cpp
@@ -3526,7 +3526,7 @@ ABIArgInfo WinX86_64ABIInfo::classify(QualType Ty, unsigned &FreeSSERegs,
// padding/alignment is being carried by the struct.
if (getContext().getTypeSize(Ty) ==
getContext().getTypeSize(FieldTy)) {
- if (IsReturnType)
+ if (Kind == ClassifyKind::Return)
return ABIArgInfo::getDirect(FieldLLTy);
if (Width <= 128)
return ABIArgInfo::getDirect(FieldLLTy);
@@ -3544,7 +3544,7 @@ ABIArgInfo WinX86_64ABIInfo::classify(QualType Ty, unsigned &FreeSSERegs,
// Empty C++ objects take no register slots.
if (isEmptyRecord(getContext(), Ty, /*AllowArrays=*/true))
return ABIArgInfo::getIgnore();
- if (IsReturnType)
+ if (Kind == ClassifyKind::Return)
return ABIArgInfo::getDirect();
// Pass as an integer of the aggregate size when it fits in one register,
// otherwise expand it into its 8-byte parts.
@@ -3564,7 +3564,7 @@ ABIArgInfo WinX86_64ABIInfo::classify(QualType Ty, unsigned &FreeSSERegs,
llvm::Type *ElemLLTy = CGT.ConvertType(ElemTy);
if (llvm::FixedVectorType::isValidElementType(ElemLLTy)) {
auto *V2 = llvm::FixedVectorType::get(ElemLLTy, 2);
- if (IsReturnType)
+ if (Kind == ClassifyKind::Return)
return ABIArgInfo::getDirect(V2);
// A 128-bit complex value fits in one XMM register; a wider one (e.g.
// long double complex) is expanded into its 64-bit parts.
@@ -3652,13 +3652,13 @@ ABIArgInfo WinX86_64ABIInfo::classify(QualType Ty, unsigned &FreeSSERegs,
// wincall passes 128-bit integers in two integer registers and returns
// them in RAX (low) and RDX (high), per the spec.
if (IsWinCall && BT->getKind() != BuiltinType::Float128) {
- if (IsReturnType)
+ if (Kind == ClassifyKind::Return)
return ABIArgInfo::getDirect();
return ABIArgInfo::getExpand();
}
if (IsWinCall) {
// std::float128_t is passed like __int128: in two integer registers.
- if (IsReturnType)
+ if (Kind == ClassifyKind::Return)
return ABIArgInfo::getDirect(llvm::FixedVectorType::get(
llvm::Type::getInt64Ty(getVMContext()), 2));
return ABIArgInfo::getExpand();
>From 2b539348ba84b38d3f496e55308c9b8216b3d1b1 Mon Sep 17 00:00:00 2001
From: trcrsired <oyzawqgcfc at gmail.com>
Date: Tue, 8 Sep 2026 22:08:04 +0800
Subject: [PATCH 35/36] [CIR] missing a , in CIROps.td for wincall
---
clang/include/clang/CIR/Dialect/IR/CIROps.td | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/clang/include/clang/CIR/Dialect/IR/CIROps.td b/clang/include/clang/CIR/Dialect/IR/CIROps.td
index 975764a9013865..c84682d50dce25 100644
--- a/clang/include/clang/CIR/Dialect/IR/CIROps.td
+++ b/clang/include/clang/CIR/Dialect/IR/CIROps.td
@@ -4172,7 +4172,7 @@ def CIR_CallingConv : CIR_I32Enum<"CallingConv", "calling convention", [
I32EnumCase<"PTXKernel", 1, "ptx_kernel">,
I32EnumCase<"SpirFunction", 2, "spir_function">,
I32EnumCase<"SpirKernel", 3, "spir_kernel">,
- I32EnumCase<"AMDGPUKernel", 4, "amdgpu_kernel">
+ I32EnumCase<"AMDGPUKernel", 4, "amdgpu_kernel">,
I32EnumAttrCase<"X86WinCall", 5, "x86_wincall">
]>;
>From 86f467c5699df5a754d5c1b9a6f25df240e1189c Mon Sep 17 00:00:00 2001
From: trcrsired <oyzawqgcfc at gmail.com>
Date: Fri, 16 Oct 2026 03:54:20 +0800
Subject: [PATCH 36/36] [X86] WinCall: use the Win64 callee-saved sets on every
target
WinCall is the APX-aware Windows x64 convention on every x86_64 target, but
getCalleeSavedRegs() and getCallPreservedMask() chose between the Win64 and
the SysV register set with isCallingConvWin64(X86_WinCall), which is
isTargetWin64(); on a non-Windows target WinCall therefore fell through to
CSR_64 / CSR_64_RegMask. The SysV set keeps XMM6-XMM15 volatile, so a
WinCall call emitted ten movaps spills of XMM6-XMM15, pushes of
RDI/RSI/R30/R31 and a 168-byte frame around every call.
Handle CallingConv::X86_WinCall explicitly and map it to CSR_Win64_APX /
CSR_Win64_APX_RegMask (or the _NoSSE forms when SSE is unavailable), so
WinCall always uses the classic Windows x64 callee-saved set plus R30/R31.
The classic x64 and the WinCall callee-saved sets are now identical, so a
call can change convention without saving or restoring a single callee-saved
register in either direction.
clang: the WinCall calling convention delegates to
WinX86_64ABIInfo::computeInfo on any x86_64 target, not only on the
x86_64apx Windows ones, and its aggregate rules coerce records to eight-byte
integer parts -- one i32 up to four bytes, one i64 up to eight bytes,
otherwise ceil(size/8) i64 parts -- so an aggregate of up to 32 bytes is
passed and returned in registers rather than by pointer. A record holding a
single scalar floating-point member of exactly its own size is passed in an
XMM register, matching the GCC port.
llvm/docs/WinCall.md: the callee-saved section now states that the set is
exactly the classic Windows x64 set plus R30/R31, that the two conventions
share it, and that R16-R19 and XMM0-XMM7 are argument registers as well.
---
clang/lib/CodeGen/Targets/X86.cpp | 88 +++++++++++++-----------
clang/test/CodeGen/X86/wincall-abi.c | 8 ++-
clang/test/CodeGen/X86/wincall-abi2.c | 9 +--
clang/test/CodeGen/X86/wincall-sysv.c | 38 ++++++++++
llvm/docs/WinCall.md | 27 +++++++-
llvm/lib/Target/X86/X86RegisterInfo.cpp | 17 +++++
llvm/lib/Target/X86/X86TargetMachine.cpp | 29 ++++++++
llvm/test/CodeGen/X86/wincall-egpr.ll | 34 +++++++++
8 files changed, 200 insertions(+), 50 deletions(-)
create mode 100644 clang/test/CodeGen/X86/wincall-sysv.c
create mode 100644 llvm/test/CodeGen/X86/wincall-egpr.ll
diff --git a/clang/lib/CodeGen/Targets/X86.cpp b/clang/lib/CodeGen/Targets/X86.cpp
index 1cbba8c950d11f..8df607774291dd 100644
--- a/clang/lib/CodeGen/Targets/X86.cpp
+++ b/clang/lib/CodeGen/Targets/X86.cpp
@@ -3079,10 +3079,16 @@ X86_64ABIInfo::getX86ABIAVXLevel(const FunctionDecl *FD,
void X86_64ABIInfo::computeInfo(CGFunctionInfo &FI) const {
const unsigned CallingConv = FI.getCallingConvention();
- // It is possible to force Win64 calling convention on any x86_64 target by
- // using __attribute__((ms_abi)). In such case to correctly emit Win64
- // compatible code delegate this call to WinX86_64ABIInfo::computeInfo.
- if (CallingConv == llvm::CallingConv::Win64) {
+ // It is possible to force the Win64 calling convention on any x86_64 target
+ // by using __attribute__((ms_abi)), and the WinCall convention
+ // (__attribute__((wincall))) is likewise valid on any x86_64 target rather
+ // than only the x86_64apx Windows ones. In both cases the Windows-compatible
+ // argument and return classification has to be used, so delegate to
+ // WinX86_64ABIInfo::computeInfo. It picks up the extra WinCall rules
+ // (aggregates up to 32 bytes in registers, empty records taking no slot)
+ // from the calling convention itself.
+ if (CallingConv == llvm::CallingConv::Win64 ||
+ CallingConv == llvm::CallingConv::X86_WinCall) {
WinX86_64ABIInfo Win64ABIInfo(CGT, AVXLevel);
Win64ABIInfo.computeInfo(FI);
return;
@@ -3503,12 +3509,13 @@ ABIArgInfo WinX86_64ABIInfo::classify(QualType Ty, unsigned &FreeSSERegs,
return getNaturalAlignIndirect(Ty, getDataLayout().getAllocaAddrSpace(),
/*ByVal=*/false);
- // WinCall passes a record that is a single FP/SIMD member exactly like the
- // scalar it wraps (in a vector register), as long as the record's size and
- // alignment match the member's natural size and alignment. If the user
- // bumped the alignment so that the record is bigger than the member (e.g.
- // a 16-byte-aligned struct holding one double), fall through to the normal
- // aggregate rules.
+ // WinCall passes a record that is a single scalar FP member exactly like
+ // the floating-point scalar it wraps (in an XMM register), as long as the
+ // record is exactly as big as that member so that no padding or alignment
+ // is being carried. This mirrors the GCC wincall port, which passes such a
+ // record in the XMM bank because the record's mode collapses to the
+ // member's scalar mode. Anything else -- including a record wrapping a
+ // vector or a complex -- is an integer-chunked aggregate.
if (IsWinCall && !Ty->isAnyComplexType() && !Ty->isMemberPointerType() &&
!RT->getDecl()->isUnion()) {
unsigned NumFields = 0;
@@ -3528,59 +3535,56 @@ ABIArgInfo WinX86_64ABIInfo::classify(QualType Ty, unsigned &FreeSSERegs,
llvm::Type *FieldLLTy = CGT.ConvertType(FieldTy);
bool IsScalarFP =
FieldTy->isFloatingType() && !FieldTy->isComplexType();
- bool IsVector = FieldTy->isVectorType();
- if ((IsScalarFP || IsVector) &&
- (FieldLLTy->isFloatingPointTy() || FieldLLTy->isVectorTy())) {
- // The record must be exactly as big as the single member so that no
- // padding/alignment is being carried by the struct.
- if (getContext().getTypeSize(Ty) ==
- getContext().getTypeSize(FieldTy)) {
- if (Kind == ClassifyKind::Return)
- return ABIArgInfo::getDirect(FieldLLTy);
- if (Width <= 128)
- return ABIArgInfo::getDirect(FieldLLTy);
- return ABIArgInfo::getExpand();
- }
- }
+ if (IsScalarFP && FieldLLTy->isFloatingPointTy() &&
+ getContext().getTypeSize(Ty) ==
+ getContext().getTypeSize(FieldTy))
+ return ABIArgInfo::getDirect(FieldLLTy);
}
}
- // wincall passes/returns aggregates that fit in 1, 2, 4, 8, 16 or 32 bytes
- // (e.g. 4x size_t, like std::string/std::vector) directly in registers,
- // instead of by pointer/sret like the MS x64 ABI.
+ // WinCall passes/returns aggregates that fit in 1..32 bytes directly in
+ // registers, instead of by pointer/sret like the MS x64 ABI. Like the GCC
+ // wincall port, a record is coerced to eight-byte integer parts: up to four
+ // bytes travel as one i32, up to eight bytes as one i64, and anything
+ // larger (up to 32 bytes) as ceil(size/8) i64 parts. 4x size_t (e.g.
+ // std::string/std::vector) therefore travels in four GPRs.
if (IsWinCall && Width <= 256 && !Ty->isAnyComplexType() &&
!Ty->isMemberPointerType()) {
// Empty C++ objects take no register slots.
if (isEmptyRecord(getContext(), Ty, /*AllowArrays=*/true))
return ABIArgInfo::getIgnore();
- if (Kind == ClassifyKind::Return)
- return ABIArgInfo::getDirect();
- // Pass as an integer of the aggregate size when it fits in one register,
- // otherwise expand it into its 8-byte parts.
+ if (Width <= 32)
+ return ABIArgInfo::getDirect(
+ llvm::IntegerType::get(getVMContext(), 32));
if (Width <= 64)
return ABIArgInfo::getDirect(
- llvm::IntegerType::get(getVMContext(), Width));
- return ABIArgInfo::getExpand();
+ llvm::IntegerType::get(getVMContext(), 64));
+ // Expand the record into its 8-byte parts.
+ unsigned NumParts = (Width + 63) / 64;
+ SmallVector<llvm::Type *, 4> Parts(
+ NumParts, llvm::Type::getInt64Ty(getVMContext()));
+ return ABIArgInfo::getDirect(
+ llvm::StructType::get(getVMContext(), Parts));
}
}
// WinCall passes complex scalars in the vector registers: a complex value is
// just two elements of its component type, so coerce _Complex float/double
- // to v2f32/v2f64 and pass/return them in XMM registers instead of by
+ // to v2f32/v2f64 and pass/return them in a single XMM register instead of by
// pointer/sret like the MS x64 ABI.
if (IsWinCall && Ty->isAnyComplexType()) {
QualType ElemTy = cast<ComplexType>(Ty)->getElementType();
llvm::Type *ElemLLTy = CGT.ConvertType(ElemTy);
- if (llvm::FixedVectorType::isValidElementType(ElemLLTy)) {
+ if (Width <= 128 && llvm::FixedVectorType::isValidElementType(ElemLLTy)) {
auto *V2 = llvm::FixedVectorType::get(ElemLLTy, 2);
- if (Kind == ClassifyKind::Return)
- return ABIArgInfo::getDirect(V2);
- // A 128-bit complex value fits in one XMM register; a wider one (e.g.
- // long double complex) is expanded into its 64-bit parts.
- if (Width <= 128)
- return ABIArgInfo::getDirect(V2);
- return ABIArgInfo::getExpand();
+ return ABIArgInfo::getDirect(V2);
}
+ // A complex whose components do not fit the XMM bank -- only `_Complex
+ // long double' -- is passed and returned by invisible reference, matching
+ // the GCC wincall port: WinCall has no x87 register bank, so x87 values
+ // (`long double' and `_Complex long double') travel by pointer.
+ return getNaturalAlignIndirect(Ty, getDataLayout().getAllocaAddrSpace(),
+ /*ByVal=*/false);
}
const Type *Base = nullptr;
diff --git a/clang/test/CodeGen/X86/wincall-abi.c b/clang/test/CodeGen/X86/wincall-abi.c
index 8a7327e9136fc5..1d7c7e3805bed6 100644
--- a/clang/test/CodeGen/X86/wincall-abi.c
+++ b/clang/test/CodeGen/X86/wincall-abi.c
@@ -35,7 +35,9 @@ __attribute__((wincall)) struct span2 make_span(void) {
struct span2 s = {0, 1};
return s;
}
-// CHECK-LABEL: define dso_local x86_wincallcc %struct.span2 @"\01make_span at win"
+// The 16-byte record is coerced to two eight-byte parts, like the GCC wincall
+// port.
+// CHECK-LABEL: define dso_local x86_wincallcc { i64, i64 } @"\01make_span at win"
// ASM-LABEL: make_span at win:
// ASM: movq (%rsp), %rax
// ASM: movq 8(%rsp), %rdx
@@ -48,7 +50,9 @@ __attribute__((wincall)) struct vec4 make_vec4(void) {
struct vec4 v = {1, 2, 3, 4};
return v;
}
-// CHECK-LABEL: define dso_local x86_wincallcc %struct.vec4 @"\01make_vec4 at win"
+// The 32-byte record is coerced to four eight-byte parts, like the GCC wincall
+// port.
+// CHECK-LABEL: define dso_local x86_wincallcc { i64, i64, i64, i64 } @"\01make_vec4 at win"
// ASM-LABEL: make_vec4 at win:
// ASM: movq (%rsp), %rax
// ASM: movq 8(%rsp), %rdx
diff --git a/clang/test/CodeGen/X86/wincall-abi2.c b/clang/test/CodeGen/X86/wincall-abi2.c
index 65af11da4a2232..9cb5058103a838 100644
--- a/clang/test/CodeGen/X86/wincall-abi2.c
+++ b/clang/test/CodeGen/X86/wincall-abi2.c
@@ -40,14 +40,15 @@ __attribute__((wincall)) float f_of(struct one_float { float f; } s) {
// A 16-byte-aligned struct holding one double is NOT treated as a scalar:
// its size (16) is bigger than the member (8), so it stays an aggregate and
-// is expanded into its parts (the single double field; padding is dropped).
-// CHECK: define dso_local x86_wincallcc double @"\01f_ad at win"(double %s.0)
+// is coerced to its two eight-byte parts, like the GCC wincall port.
+// CHECK: define dso_local x86_wincallcc double @"\01f_ad at win"(i64 %s.coerce0, i64 %s.coerce1)
__attribute__((wincall)) double f_ad(struct aligned_double { double d; } __attribute__((aligned(16))) s) {
return s.d;
}
-// A two-double struct is a normal two-register aggregate.
-// CHECK: define dso_local x86_wincallcc double @"\01f_td at win"(double %s.0, double %s.1)
+// A two-double struct is a normal two-register aggregate: its two eight-byte
+// parts, like the GCC wincall port.
+// CHECK: define dso_local x86_wincallcc double @"\01f_td at win"(i64 %s.coerce0, i64 %s.coerce1)
// ASM-LABEL: f_td at win:
__attribute__((wincall)) double f_td(struct two_double { double a, b; } s) {
return s.a;
diff --git a/clang/test/CodeGen/X86/wincall-sysv.c b/clang/test/CodeGen/X86/wincall-sysv.c
new file mode 100644
index 00000000000000..93633d6ae1aae2
--- /dev/null
+++ b/clang/test/CodeGen/X86/wincall-sysv.c
@@ -0,0 +1,38 @@
+// RUN: %clang_cc1 -triple x86_64-unknown-linux-gnu -O0 -emit-llvm -o - %s | FileCheck %s --check-prefix=IR
+// RUN: %clang_cc1 -triple x86_64-unknown-linux-gnu -O2 -S -o - %s | FileCheck %s --check-prefix=ASM
+
+// The wincall calling convention is accepted on any x86-64 target, not only on
+// the x86_64apx Windows ones. On such a target the Windows argument/return
+// classification has to be used, just as it already is for
+// __attribute__((ms_abi)): X86_64ABIInfo::computeInfo has to delegate to
+// WinX86_64ABIInfo for CC_WinCall as well.
+
+struct c24 {
+ long a, b, c;
+};
+struct c40 {
+ long a, b, c, d, e;
+};
+struct empty {};
+
+__attribute__((wincall)) struct c24 ret24(void) { return (struct c24){1, 2, 3}; }
+// IR-LABEL: define dso_local x86_wincallcc { i64, i64, i64 } @ret24(
+// IR-NOT: sret
+// The 24-byte record is coerced to its three eight-byte parts, like the GCC
+// wincall port, and comes back in RAX, RDX and RCX.
+
+__attribute__((wincall)) struct c40 ret40(void) {
+ return (struct c40){1, 2, 3, 4, 5};
+}
+// IR-LABEL: define dso_local x86_wincallcc void @ret40(ptr {{.*}}sret(%struct.c40)
+
+__attribute__((wincall)) void take_empty(struct empty e, int x) { (void)e; }
+// IR-LABEL: define dso_local x86_wincallcc void @take_empty(i32 noundef %x)
+
+// The convention implies the APX egpr feature, so the fifth integer argument is
+// passed in R16 and the compiler no longer aborts in LiveVariables.
+__attribute__((wincall)) long sum5(long a, long b, long c, long d, long e) {
+ return a + b + c + d + e;
+}
+// ASM-LABEL: sum5:
+// ASM: %r16
diff --git a/llvm/docs/WinCall.md b/llvm/docs/WinCall.md
index e24d0b918b9659..48fe582b698747 100644
--- a/llvm/docs/WinCall.md
+++ b/llvm/docs/WinCall.md
@@ -72,6 +72,14 @@ which returns the full register count only when ``egpr`` is enabled. If the
registers were not enabled, WinCall would still assign R16-R19 as argument
registers, and passes such as ``LiveVariables`` would index out of bounds.
+WinCall is also selectable per-function with the ``wincall`` attribute on any
+x86-64 target, where the triple does not turn APX on. There
+``X86TargetMachine::getSubtargetImpl`` enables ``egpr`` for every function that
+takes part in WinCall - both the callee itself and any function that calls a
+WinCall function, since a caller has to materialise R16-R19 as well - so the
+register tables are sized correctly without requiring ``-mapxf`` on the command
+line.
+
## Argument passing
### Integer and pointer arguments
@@ -171,8 +179,23 @@ always enables it for ``x86_64apx``):
RBX, RBP, RDI, RSI, R12, R13, R14, R15, R30, R31, XMM6-XMM15
```
-R30 and R31 are APX registers preserved in addition to the standard Windows
-x64 set.
+That is exactly the classic Windows x64 set once the APX registers are
+available (only R30 and R31 are added; R16-R29 stay volatile), so the two
+conventions share one callee-saved set and the whole point of calling it the
+"classic Windows x64 caller-saved model" is that a call can be switched from
+one convention to the other without saving or restoring a single
+callee-saved register. The only instructions such a transition needs are
+the ones that move the arguments the two conventions place differently
+(``RCX``/``RDX``/``R8``/``R9`` and ``XMM0``-``XMM3`` are shared, so
+everything below the fifth argument has to be rearranged, and a WinCall
+return may occupy up to four registers).
+
+Because R16-R19 and XMM0-XMM7 are also argument registers, they are not
+preserved for a call that passes an argument in them: the caller has already
+overwritten its own value there, and a callee that wants to use such a
+register as scratch preserves the incoming argument value rather than
+assuming it is dead. R30 and R31 are the APX addition to the classic set;
+on the SysV ABI they remain volatile.
## Symbol decoration: the ``@win`` suffix
diff --git a/llvm/lib/Target/X86/X86RegisterInfo.cpp b/llvm/lib/Target/X86/X86RegisterInfo.cpp
index e67e99ac10cf79..c55bfd64f26c45 100644
--- a/llvm/lib/Target/X86/X86RegisterInfo.cpp
+++ b/llvm/lib/Target/X86/X86RegisterInfo.cpp
@@ -314,6 +314,17 @@ X86RegisterInfo::getCalleeSavedRegs(const MachineFunction *MF) const {
if (HasSSE)
return HasEGPR ? CSR_Win64_APX_SaveList : CSR_Win64_SaveList;
return CSR_Win64_NoSSE_SaveList;
+ case CallingConv::X86_WinCall:
+ // WinCall always uses the Windows x64 callee-saved set -- including
+ // XMM6-XMM15 -- on every target, not just on Windows targets. This
+ // is what makes a transition between WinCall and the classic x64
+ // conventions free of callee-saved register shuffling, and it is
+ // what CSR_Win64_APX (which WinCall.md documents) describes. Note
+ // that getCallPreservedMask() has to agree, since a caller in any
+ // convention must not assume that a WinCall call clobbers these.
+ if (HasSSE)
+ return HasEGPR ? CSR_Win64_APX_SaveList : CSR_Win64_SaveList;
+ return CSR_Win64_NoSSE_SaveList;
case CallingConv::SwiftTail:
if (!Is64Bit)
return CSR_32_SaveList;
@@ -457,6 +468,12 @@ X86RegisterInfo::getCallPreservedMask(const MachineFunction &MF,
break;
case CallingConv::Win64:
return HasEGPR ? CSR_Win64_APX_RegMask : CSR_Win64_RegMask;
+ case CallingConv::X86_WinCall:
+ // Agree with getCalleeSavedRegs(): a WinCall call does not clobber the
+ // Windows x64 callee-saved registers -- including XMM6-XMM15 -- even on
+ // non-Windows targets, so a caller in any convention needs no
+ // callee-saved register shuffling around it.
+ return HasEGPR ? CSR_Win64_APX_RegMask : CSR_Win64_RegMask;
case CallingConv::SwiftTail:
if (!Is64Bit)
return CSR_32_RegMask;
diff --git a/llvm/lib/Target/X86/X86TargetMachine.cpp b/llvm/lib/Target/X86/X86TargetMachine.cpp
index cbd65eebf464ba..d933747b26ce96 100644
--- a/llvm/lib/Target/X86/X86TargetMachine.cpp
+++ b/llvm/lib/Target/X86/X86TargetMachine.cpp
@@ -38,6 +38,7 @@
#include "llvm/IR/Attributes.h"
#include "llvm/IR/DataLayout.h"
#include "llvm/IR/Function.h"
+#include "llvm/IR/Instructions.h"
#include "llvm/MC/MCAsmInfo.h"
#include "llvm/MC/TargetRegistry.h"
#include "llvm/Pass.h"
@@ -231,6 +232,27 @@ X86TargetMachine::getSubtargetImpl(const Function &F) const {
StringRef FS =
FSAttr.isValid() ? FSAttr.getValueAsString() : (StringRef)TargetFS;
+ // The WinCall calling convention (x86_wincallcc) passes the fifth and later
+ // integer arguments in the APX extended GPRs R16-R19. A function therefore
+ // needs the egpr feature when it either uses WinCall itself or calls a WinCall
+ // function, because the backend sizes its physical register tables from
+ // X86RegisterInfo::getNumSupportedRegs(), which only covers R16-R19 when egpr
+ // is enabled. The frontend accepts `wincall` on any x86-64 target, so this
+ // cannot be left to the triple the way the x86_64apx sub-architecture does.
+ bool NeedsWinCallEGPR = F.getCallingConv() == CallingConv::X86_WinCall;
+ if (!NeedsWinCallEGPR)
+ for (const BasicBlock &BB : F) {
+ for (const Instruction &I : BB) {
+ const auto *CB = dyn_cast<CallBase>(&I);
+ if (CB && CB->getCallingConv() == CallingConv::X86_WinCall) {
+ NeedsWinCallEGPR = true;
+ break;
+ }
+ }
+ if (NeedsWinCallEGPR)
+ break;
+ }
+
SmallString<512> Key;
// The additions here are ordered so that the definitely short strings are
// added first so we won't exceed the small size. We append the
@@ -285,6 +307,13 @@ X86TargetMachine::getSubtargetImpl(const Function &F) const {
Key += FS;
+ // Enable egpr for functions that take part in WinCall, see above.
+ if (NeedsWinCallEGPR) {
+ if (Key.size() > FSStart && Key.back() != ',')
+ Key += ',';
+ Key += "+egpr";
+ }
+
// We may have added +soft-float to the features so move the StringRef to
// point to the full string in the Key.
FS = Key.substr(FSStart);
diff --git a/llvm/test/CodeGen/X86/wincall-egpr.ll b/llvm/test/CodeGen/X86/wincall-egpr.ll
new file mode 100644
index 00000000000000..ddf5ccd9a0d5e6
--- /dev/null
+++ b/llvm/test/CodeGen/X86/wincall-egpr.ll
@@ -0,0 +1,34 @@
+; RUN: llc -mtriple=x86_64-unknown-linux-gnu < %s | FileCheck %s
+
+; The WinCall calling convention passes the fifth and later integer arguments
+; in the APX extended GPRs R16-R19. Any function that takes part in WinCall -
+; as the callee itself or as the caller of a WinCall function - therefore has
+; to turn on the egpr feature implicitly, even on a target whose triple does
+; not enable APX. Otherwise the physical register tables sized by
+; X86RegisterInfo::getNumSupportedRegs() would not cover those registers and
+; LiveVariables would abort on the live-in argument registers.
+;
+; This is reachable from ordinary C because the frontend accepts `wincall` on
+; any x86-64 target, see clang/test/CodeGen/X86/wincall-sysv.c.
+
+; The fifth argument is live in R16, which requires egpr to be enabled.
+define x86_wincallcc i64 @callee5(i64 %a, i64 %b, i64 %c, i64 %d, i64 %e) {
+entry:
+ %s1 = add i64 %a, %b
+ %s2 = add i64 %s1, %c
+ %s3 = add i64 %s2, %d
+ %s4 = add i64 %s3, %e
+ ret i64 %s4
+}
+; CHECK-LABEL: callee5:
+; CHECK: addq %rdx, %rcx
+; CHECK: addq %r16
+
+; The caller also has to materialize R16, so it needs egpr as well.
+define i64 @caller5() {
+entry:
+ %r = call x86_wincallcc i64 @callee5(i64 1, i64 2, i64 3, i64 4, i64 5)
+ ret i64 %r
+}
+; CHECK-LABEL: caller5:
+; CHECK: movl $5, %r16d
More information about the Mlir-commits
mailing list