Skip to content

New calling convention preserve_none #76868

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 7 commits into from
Feb 5, 2024

Conversation

weiguozhi
Copy link
Contributor

The new experimental calling convention preserve_none is the opposite side of existing preserve_all. It tries to preserve as few general registers as possible. So all general registers are caller saved registers. It can also uses more general registers to pass arguments. This attribute doesn't impact floating-point registers. Floating-point registers still follow the c calling convention.

Currently preserve_none is supported on X86-64 only. It changes the c calling convention in following fields:

  • RSP and RBP are the only preserved general registers, all other general registers are caller saved registers.
  • We can use [RDI, RSI, RDX, RCX, R8, R9, R11, R12, R13, R14, R15, RAX] to pass arguments.

It can improve the performance of hot tailcall chain, because many callee saved registers' save/restore instructions can be removed if the tail functions are using preserve_none. In my experiment in protocol buffer, the parsing functions are improved by 3% to 10%.

The new calling convention preserve_none is the opposite side of
existing preserve_all. It tries to preserve as few general registers as
possible. So all general registers are caller saved registers. It can
also uses more general registers to pass arguments. This attribute
doesn't impact floating-point registers. Floating-point registers still
follow the c calling convention.

Currently preserve_none is supported on X86-64 only.
@llvmbot llvmbot added clang Clang issues not falling into any other category backend:X86 clang:frontend Language frontend issues, e.g. anything involving "Sema" clang:codegen IR generation bugs: mangling, exceptions, etc. debuginfo llvm:ir llvm:binary-utilities labels Jan 3, 2024
@llvmbot
Copy link
Member

llvmbot commented Jan 3, 2024

@llvm/pr-subscribers-llvm-ir
@llvm/pr-subscribers-backend-x86

@llvm/pr-subscribers-clang

Author: None (weiguozhi)

Changes

The new experimental calling convention preserve_none is the opposite side of existing preserve_all. It tries to preserve as few general registers as possible. So all general registers are caller saved registers. It can also uses more general registers to pass arguments. This attribute doesn't impact floating-point registers. Floating-point registers still follow the c calling convention.

Currently preserve_none is supported on X86-64 only. It changes the c calling convention in following fields:

  • RSP and RBP are the only preserved general registers, all other general registers are caller saved registers.
  • We can use [RDI, RSI, RDX, RCX, R8, R9, R11, R12, R13, R14, R15, RAX] to pass arguments.

It can improve the performance of hot tailcall chain, because many callee saved registers' save/restore instructions can be removed if the tail functions are using preserve_none. In my experiment in protocol buffer, the parsing functions are improved by 3% to 10%.


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

34 Files Affected:

  • (modified) clang/include/clang-c/Index.h (+1)
  • (modified) clang/include/clang/Basic/Attr.td (+5)
  • (modified) clang/include/clang/Basic/AttrDocs.td (+17)
  • (modified) clang/include/clang/Basic/Specifiers.h (+1)
  • (modified) clang/lib/AST/ItaniumMangle.cpp (+1)
  • (modified) clang/lib/AST/Type.cpp (+2)
  • (modified) clang/lib/AST/TypePrinter.cpp (+6)
  • (modified) clang/lib/Basic/Targets/X86.h (+2)
  • (modified) clang/lib/CodeGen/CGCall.cpp (+4)
  • (modified) clang/lib/CodeGen/CGDebugInfo.cpp (+2)
  • (modified) clang/lib/Sema/SemaDeclAttr.cpp (+7)
  • (modified) clang/lib/Sema/SemaType.cpp (+4-1)
  • (modified) clang/test/CodeGen/debug-info-cc.c (+7)
  • (modified) clang/test/CodeGen/preserve-call-conv.c (+7-1)
  • (modified) clang/test/Sema/no_callconv.cpp (+2)
  • (added) clang/test/Sema/preserve-none-call-conv.c (+19)
  • (modified) clang/tools/libclang/CXType.cpp (+1)
  • (modified) llvm/docs/LangRef.rst (+6)
  • (modified) llvm/include/llvm/AsmParser/LLToken.h (+1)
  • (modified) llvm/include/llvm/BinaryFormat/Dwarf.def (+1)
  • (modified) llvm/include/llvm/IR/CallingConv.h (+3)
  • (modified) llvm/lib/AsmParser/LLLexer.cpp (+1)
  • (modified) llvm/lib/AsmParser/LLParser.cpp (+2)
  • (modified) llvm/lib/DebugInfo/DWARF/DWARFTypePrinter.cpp (+3)
  • (modified) llvm/lib/IR/AsmWriter.cpp (+1)
  • (modified) llvm/lib/Target/X86/X86CallingConv.td (+18)
  • (modified) llvm/lib/Target/X86/X86ISelLoweringCall.cpp (+1)
  • (modified) llvm/lib/Target/X86/X86RegisterInfo.cpp (+4)
  • (modified) llvm/test/Bitcode/compatibility.ll (+2)
  • (added) llvm/test/CodeGen/X86/dynamic-regmask-preserve-none.ll (+88)
  • (modified) llvm/test/CodeGen/X86/ipra-reg-usage.ll (+8-1)
  • (modified) llvm/test/CodeGen/X86/ipra-transform.ll (+19)
  • (added) llvm/test/CodeGen/X86/preserve_nonecc64-ret-double.ll (+85)
  • (added) llvm/test/CodeGen/X86/preserve_nonecc64.ll (+86)
diff --git a/clang/include/clang-c/Index.h b/clang/include/clang-c/Index.h
index 64ab3378957c70..c241a8ccc7dfac 100644
--- a/clang/include/clang-c/Index.h
+++ b/clang/include/clang-c/Index.h
@@ -2981,6 +2981,7 @@ enum CXCallingConv {
   CXCallingConv_SwiftAsync = 17,
   CXCallingConv_AArch64SVEPCS = 18,
   CXCallingConv_M68kRTD = 19,
+  CXCallingConv_PreserveNone = 20,
 
   CXCallingConv_Invalid = 100,
   CXCallingConv_Unexposed = 200
diff --git a/clang/include/clang/Basic/Attr.td b/clang/include/clang/Basic/Attr.td
index db17211747b17d..87e2ce91a0afb7 100644
--- a/clang/include/clang/Basic/Attr.td
+++ b/clang/include/clang/Basic/Attr.td
@@ -2868,6 +2868,11 @@ def M68kRTD: DeclOrTypeAttr {
   let Documentation = [M68kRTDDocs];
 }
 
+def PreserveNone : DeclOrTypeAttr {
+  let Spellings = [Clang<"preserve_none">];
+  let Documentation = [PreserveNoneDocs];
+}
+
 def Target : InheritableAttr {
   let Spellings = [GCC<"target">];
   let Args = [StringArgument<"featuresStr">];
diff --git a/clang/include/clang/Basic/AttrDocs.td b/clang/include/clang/Basic/AttrDocs.td
index 98a7ecc7fd7df3..a43e00c07e141d 100644
--- a/clang/include/clang/Basic/AttrDocs.td
+++ b/clang/include/clang/Basic/AttrDocs.td
@@ -5494,6 +5494,23 @@ experimental at this time.
   }];
 }
 
+def PreserveNoneDocs : Documentation {
+  let Category = DocCatCallingConvs;
+  let Content = [{
+On X86-64 target, this attribute changes the calling convention of a function.
+The ``preserve_none`` calling convention tries to preserve as few general
+registers as possible. So all general registers are caller saved registers. It
+also uses more general registers to pass arguments. This attribute doesn't
+impact floating-point registers (XMMs/YMMs). Floating-point registers still
+follow the c calling convention.
+
+- Only RSP and RBP are preserved by callee.
+
+- Register RDI, RSI, RDX, RCX, R8, R9, R11, R12, R13, R14, R15 and RAX now can
+  be used to pass function arguments.
+  }];
+}
+
 def DeprecatedDocs : Documentation {
   let Category = DocCatDecl;
   let Content = [{
diff --git a/clang/include/clang/Basic/Specifiers.h b/clang/include/clang/Basic/Specifiers.h
index 87f29c8ae10bd9..410be857dee713 100644
--- a/clang/include/clang/Basic/Specifiers.h
+++ b/clang/include/clang/Basic/Specifiers.h
@@ -293,6 +293,7 @@ namespace clang {
     CC_AArch64SVEPCS, // __attribute__((aarch64_sve_pcs))
     CC_AMDGPUKernelCall, // __attribute__((amdgpu_kernel))
     CC_M68kRTD,       // __attribute__((m68k_rtd))
+    CC_PreserveNone,  // __attribute__((preserve_none))
   };
 
   /// Checks whether the given calling convention supports variadic
diff --git a/clang/lib/AST/ItaniumMangle.cpp b/clang/lib/AST/ItaniumMangle.cpp
index b1678479888eb7..e692ef3412d01c 100644
--- a/clang/lib/AST/ItaniumMangle.cpp
+++ b/clang/lib/AST/ItaniumMangle.cpp
@@ -3442,6 +3442,7 @@ StringRef CXXNameMangler::getCallingConvQualifierName(CallingConv CC) {
   case CC_PreserveMost:
   case CC_PreserveAll:
   case CC_M68kRTD:
+  case CC_PreserveNone:
     // FIXME: we should be mangling all of the above.
     return "";
 
diff --git a/clang/lib/AST/Type.cpp b/clang/lib/AST/Type.cpp
index 160a725939ccd4..aae8834b97bce9 100644
--- a/clang/lib/AST/Type.cpp
+++ b/clang/lib/AST/Type.cpp
@@ -3409,6 +3409,7 @@ StringRef FunctionType::getNameForCallConv(CallingConv CC) {
   case CC_PreserveMost: return "preserve_most";
   case CC_PreserveAll: return "preserve_all";
   case CC_M68kRTD: return "m68k_rtd";
+  case CC_PreserveNone: return "preserve_none";
   }
 
   llvm_unreachable("Invalid calling convention.");
@@ -3889,6 +3890,7 @@ bool AttributedType::isCallingConv() const {
   case attr::PreserveMost:
   case attr::PreserveAll:
   case attr::M68kRTD:
+  case attr::PreserveNone:
     return true;
   }
   llvm_unreachable("invalid attr kind");
diff --git a/clang/lib/AST/TypePrinter.cpp b/clang/lib/AST/TypePrinter.cpp
index f6941242927367..7bc4499efc13a2 100644
--- a/clang/lib/AST/TypePrinter.cpp
+++ b/clang/lib/AST/TypePrinter.cpp
@@ -1047,6 +1047,9 @@ void TypePrinter::printFunctionAfter(const FunctionType::ExtInfo &Info,
     case CC_M68kRTD:
       OS << " __attribute__((m68k_rtd))";
       break;
+    case CC_PreserveNone:
+      OS << " __attribute__((preserve_none))";
+      break;
     }
   }
 
@@ -1885,6 +1888,9 @@ void TypePrinter::printAttributedAfter(const AttributedType *T,
   case attr::M68kRTD:
     OS << "m68k_rtd";
     break;
+  case attr::PreserveNone:
+    OS << "preserve_none";
+    break;
   case attr::NoDeref:
     OS << "noderef";
     break;
diff --git a/clang/lib/Basic/Targets/X86.h b/clang/lib/Basic/Targets/X86.h
index 0ab1c10833db26..cdb4b23bc5b430 100644
--- a/clang/lib/Basic/Targets/X86.h
+++ b/clang/lib/Basic/Targets/X86.h
@@ -772,6 +772,7 @@ class LLVM_LIBRARY_VISIBILITY X86_64TargetInfo : public X86TargetInfo {
     case CC_Win64:
     case CC_PreserveMost:
     case CC_PreserveAll:
+    case CC_PreserveNone:
     case CC_X86RegCall:
     case CC_OpenCLKernel:
       return CCCR_OK;
@@ -849,6 +850,7 @@ class LLVM_LIBRARY_VISIBILITY WindowsX86_64TargetInfo
     case CC_IntelOclBicc:
     case CC_PreserveMost:
     case CC_PreserveAll:
+    case CC_PreserveNone:
     case CC_X86_64SysV:
     case CC_Swift:
     case CC_SwiftAsync:
diff --git a/clang/lib/CodeGen/CGCall.cpp b/clang/lib/CodeGen/CGCall.cpp
index 51a43b5f85b3cc..e7773076c203e6 100644
--- a/clang/lib/CodeGen/CGCall.cpp
+++ b/clang/lib/CodeGen/CGCall.cpp
@@ -73,6 +73,7 @@ unsigned CodeGenTypes::ClangCallConvToLLVMCallConv(CallingConv CC) {
   case CC_Swift: return llvm::CallingConv::Swift;
   case CC_SwiftAsync: return llvm::CallingConv::SwiftTail;
   case CC_M68kRTD: return llvm::CallingConv::M68k_RTD;
+  case CC_PreserveNone: return llvm::CallingConv::PreserveNone;
   }
 }
 
@@ -256,6 +257,9 @@ static CallingConv getCallingConventionForDecl(const ObjCMethodDecl *D,
   if (D->hasAttr<M68kRTDAttr>())
     return CC_M68kRTD;
 
+  if (D->hasAttr<PreserveNoneAttr>())
+    return CC_PreserveNone;
+
   return CC_C;
 }
 
diff --git a/clang/lib/CodeGen/CGDebugInfo.cpp b/clang/lib/CodeGen/CGDebugInfo.cpp
index 236d53bee4e8f1..78970e784f6f60 100644
--- a/clang/lib/CodeGen/CGDebugInfo.cpp
+++ b/clang/lib/CodeGen/CGDebugInfo.cpp
@@ -1450,6 +1450,8 @@ static unsigned getDwarfCC(CallingConv CC) {
     return llvm::dwarf::DW_CC_LLVM_X86RegCall;
   case CC_M68kRTD:
     return llvm::dwarf::DW_CC_LLVM_M68kRTD;
+  case CC_PreserveNone:
+    return llvm::dwarf::DW_CC_LLVM_PreserveNone;
   }
   return 0;
 }
diff --git a/clang/lib/Sema/SemaDeclAttr.cpp b/clang/lib/Sema/SemaDeclAttr.cpp
index af8b90ecfed973..6f124c9045f40d 100644
--- a/clang/lib/Sema/SemaDeclAttr.cpp
+++ b/clang/lib/Sema/SemaDeclAttr.cpp
@@ -5219,6 +5219,9 @@ static void handleCallConvAttr(Sema &S, Decl *D, const ParsedAttr &AL) {
   case ParsedAttr::AT_M68kRTD:
     D->addAttr(::new (S.Context) M68kRTDAttr(S.Context, AL));
     return;
+  case ParsedAttr::AT_PreserveNone:
+    D->addAttr(::new (S.Context) PreserveNoneAttr(S.Context, AL));
+    return;
   default:
     llvm_unreachable("unexpected attribute kind");
   }
@@ -5425,6 +5428,9 @@ bool Sema::CheckCallingConvAttr(const ParsedAttr &Attrs, CallingConv &CC,
   case ParsedAttr::AT_M68kRTD:
     CC = CC_M68kRTD;
     break;
+  case ParsedAttr::AT_PreserveNone:
+    CC = CC_PreserveNone;
+    break;
   default: llvm_unreachable("unexpected attribute kind");
   }
 
@@ -9355,6 +9361,7 @@ ProcessDeclAttribute(Sema &S, Scope *scope, Decl *D, const ParsedAttr &AL,
   case ParsedAttr::AT_AArch64SVEPcs:
   case ParsedAttr::AT_AMDGPUKernelCall:
   case ParsedAttr::AT_M68kRTD:
+  case ParsedAttr::AT_PreserveNone:
     handleCallConvAttr(S, D, AL);
     break;
   case ParsedAttr::AT_Suppress:
diff --git a/clang/lib/Sema/SemaType.cpp b/clang/lib/Sema/SemaType.cpp
index a376f20fa4f4e0..68b9e37f3a9281 100644
--- a/clang/lib/Sema/SemaType.cpp
+++ b/clang/lib/Sema/SemaType.cpp
@@ -137,7 +137,8 @@ static void diagnoseBadTypeAttribute(Sema &S, const ParsedAttr &attr,
   case ParsedAttr::AT_IntelOclBicc:                                            \
   case ParsedAttr::AT_PreserveMost:                                            \
   case ParsedAttr::AT_PreserveAll:                                             \
-  case ParsedAttr::AT_M68kRTD
+  case ParsedAttr::AT_M68kRTD:                                                 \
+  case ParsedAttr::AT_PreserveNone
 
 // Function type attributes.
 #define FUNCTION_TYPE_ATTRS_CASELIST                                           \
@@ -7852,6 +7853,8 @@ static Attr *getCCTypeAttr(ASTContext &Ctx, ParsedAttr &Attr) {
     return createSimpleAttr<PreserveAllAttr>(Ctx, Attr);
   case ParsedAttr::AT_M68kRTD:
     return createSimpleAttr<M68kRTDAttr>(Ctx, Attr);
+  case ParsedAttr::AT_PreserveNone:
+    return createSimpleAttr<PreserveNoneAttr>(Ctx, Attr);
   }
   llvm_unreachable("unexpected attribute kind!");
 }
diff --git a/clang/test/CodeGen/debug-info-cc.c b/clang/test/CodeGen/debug-info-cc.c
index a64515e31d1ae3..2664bcd4cb6b2d 100644
--- a/clang/test/CodeGen/debug-info-cc.c
+++ b/clang/test/CodeGen/debug-info-cc.c
@@ -22,6 +22,7 @@
 //    CC_SwiftAsync,   // __attribute__((swiftasynccall))
 //    CC_PreserveMost, // __attribute__((preserve_most))
 //    CC_PreserveAll,  // __attribute__((preserve_all))
+//    CC_PreserveNone,  // __attribute__((preserve_none))
 //  };
 
 #ifdef __x86_64__
@@ -51,6 +52,12 @@ __attribute__((preserve_all)) int add_preserve_all(int a, int b) {
   return a+b;
 }
 
+// LINUX: !DISubprogram({{.*}}"add_preserve_none", {{.*}}type: ![[FTY:[0-9]+]]
+// LINUX: ![[FTY]] = !DISubroutineType({{.*}}cc: DW_CC_LLVM_PreserveNone,
+__attribute__((preserve_none)) int add_preserve_none(int a, int b) {
+  return a+b;
+}
+
 // LINUX: !DISubprogram({{.*}}"add_swiftcall", {{.*}}type: ![[FTY:[0-9]+]]
 // LINUX: ![[FTY]] = !DISubroutineType({{.*}}cc: DW_CC_LLVM_Swift,
 __attribute__((swiftcall)) int add_swiftcall(int a, int b) {
diff --git a/clang/test/CodeGen/preserve-call-conv.c b/clang/test/CodeGen/preserve-call-conv.c
index e700c5cf12f539..74bf695e6f331d 100644
--- a/clang/test/CodeGen/preserve-call-conv.c
+++ b/clang/test/CodeGen/preserve-call-conv.c
@@ -1,4 +1,4 @@
-// RUN: %clang_cc1 -triple x86_64-unknown-unknown -emit-llvm < %s | FileCheck %s
+// RUN: %clang_cc1 -triple x86_64-unknown-unknown -emit-llvm < %s | FileCheck %s --check-prefixes=CHECK,X86-LINUX
 // RUN: %clang_cc1 -triple arm64-unknown-unknown -emit-llvm < %s | FileCheck %s
 
 // RUN: %clang_cc1 -triple x86_64-unknown-windows-msvc -emit-llvm %s -o - | FileCheck %s
@@ -19,3 +19,9 @@ void boo(void) __attribute__((preserve_all)) {
   // CHECK-LABEL: define {{(dso_local )?}}preserve_allcc void @boo()
 }
 
+// Check that the preserve_none calling convention attribute at the source level
+// is lowered to the corresponding calling convention attrribute at the LLVM IR
+// level.
+void bar(void) __attribute__((preserve_none)) {
+  // X86-LINUX-LABEL: define {{(dso_local )?}}preserve_nonecc void @bar()
+}
diff --git a/clang/test/Sema/no_callconv.cpp b/clang/test/Sema/no_callconv.cpp
index a8b3c91e0e3f60..c00930919306a3 100644
--- a/clang/test/Sema/no_callconv.cpp
+++ b/clang/test/Sema/no_callconv.cpp
@@ -15,6 +15,7 @@ void __attribute__((swiftasynccall)) funcKK() {} // expected-error {{'swiftasync
 void __attribute__((pascal)) funcG() {} // expected-error {{'pascal' calling convention is not supported for this target}}
 void __attribute__((preserve_most)) funcL() {} // expected-error {{'preserve_most' calling convention is not supported for this target}}
 void __attribute__((preserve_all)) funcM() {} // expected-error {{'preserve_all' calling convention is not supported for this target}}
+void __attribute__((preserve_none)) funcN() {} // expected-error {{'preserve_none' calling convention is not supported for this target}}
 void __attribute__((stdcall)) funcD() {} // expected-error {{'stdcall' calling convention is not supported for this target}}
 void __attribute__((fastcall)) funcE() {} // expected-error {{'fastcall' calling convention is not supported for this target}}
 void __attribute__((thiscall)) funcF() {} // expected-error {{'thiscall' calling convention is not supported for this target}}
@@ -30,6 +31,7 @@ void __attribute__((swiftcall)) funcK() {}
 void __attribute__((swiftasynccall)) funcKK() {}
 void __attribute__((preserve_most)) funcL() {}
 void __attribute__((preserve_all)) funcM() {}
+void __attribute__((preserve_none)) funcN() {}
 
 // Same function with different calling conventions. Error with a note pointing to the last decl.
 void __attribute__((preserve_all)) funcR(); // expected-note {{previous declaration is here}}
diff --git a/clang/test/Sema/preserve-none-call-conv.c b/clang/test/Sema/preserve-none-call-conv.c
new file mode 100644
index 00000000000000..2f2fed1765dc15
--- /dev/null
+++ b/clang/test/Sema/preserve-none-call-conv.c
@@ -0,0 +1,19 @@
+// RUN: %clang_cc1 %s -fsyntax-only -triple x86_64-unknown-unknown -verify
+
+typedef void typedef_fun_t(int);
+
+void __attribute__((preserve_none)) boo(void *ptr) {
+}
+
+void __attribute__((preserve_none(1))) boo1(void *ptr) { // expected-error {{'preserve_none' attribute takes no arguments}}
+}
+
+void (__attribute__((preserve_none)) *pboo1)(void *) = boo;
+
+void (__attribute__((cdecl)) *pboo2)(void *) = boo; // expected-error {{incompatible function pointer types initializing 'void (*)(void *) __attribute__((cdecl))' with an expression of type 'void (void *) __attribute__((preserve_none))'}}
+void (*pboo3)(void *) = boo; // expected-error {{incompatible function pointer types initializing 'void (*)(void *)' with an expression of type 'void (void *) __attribute__((preserve_none))'}}
+
+typedef_fun_t typedef_fun_boo; // expected-note {{previous declaration is here}}
+void __attribute__((preserve_none)) typedef_fun_boo(int x) { } // expected-error {{function declared 'preserve_none' here was previously declared without calling convention}}
+
+struct type_test_boo {} __attribute__((preserve_none));  // expected-warning {{'preserve_none' attribute only applies to functions and methods}}
diff --git a/clang/tools/libclang/CXType.cpp b/clang/tools/libclang/CXType.cpp
index 3d620d3bfb2602..292d524f00abd6 100644
--- a/clang/tools/libclang/CXType.cpp
+++ b/clang/tools/libclang/CXType.cpp
@@ -679,6 +679,7 @@ CXCallingConv clang_getFunctionTypeCallingConv(CXType X) {
       TCALLINGCONV(PreserveMost);
       TCALLINGCONV(PreserveAll);
       TCALLINGCONV(M68kRTD);
+      TCALLINGCONV(PreserveNone);
     case CC_SpirFunction: return CXCallingConv_Unexposed;
     case CC_AMDGPUKernelCall: return CXCallingConv_Unexposed;
     case CC_OpenCLKernel: return CXCallingConv_Unexposed;
diff --git a/llvm/docs/LangRef.rst b/llvm/docs/LangRef.rst
index 7f4a316a21acee..9344acb5d8301d 100644
--- a/llvm/docs/LangRef.rst
+++ b/llvm/docs/LangRef.rst
@@ -416,6 +416,12 @@ added in the future:
     This calling convention, like the `PreserveMost` calling convention, will be
     used by a future version of the ObjectiveC runtime and should be considered
     experimental at this time.
+"``preserve_nonecc``" - The `PreserveNone` calling convention
+    This calling convention doesn't preserve any general registers. So all
+    general registers are caller saved registers. It also uses all general
+    registers to pass arguments. This attribute doesn't impact floating-point
+    registers (XMMs/YMMs). Floating-point registers still follow the c calling
+    convention.
 "``cxx_fast_tlscc``" - The `CXX_FAST_TLS` calling convention for access functions
     Clang generates an access function to access C++-style TLS. The access
     function generally has an entry block, an exit block and an initialization
diff --git a/llvm/include/llvm/AsmParser/LLToken.h b/llvm/include/llvm/AsmParser/LLToken.h
index 147cf56c821aa1..3c34706ee03e82 100644
--- a/llvm/include/llvm/AsmParser/LLToken.h
+++ b/llvm/include/llvm/AsmParser/LLToken.h
@@ -160,6 +160,7 @@ enum Kind {
   kw_swifttailcc,
   kw_preserve_mostcc,
   kw_preserve_allcc,
+  kw_preserve_nonecc,
   kw_ghccc,
   kw_x86_intrcc,
   kw_hhvmcc,
diff --git a/llvm/include/llvm/BinaryFormat/Dwarf.def b/llvm/include/llvm/BinaryFormat/Dwarf.def
index d1abb1f361d3ed..3a08eeaa791aa6 100644
--- a/llvm/include/llvm/BinaryFormat/Dwarf.def
+++ b/llvm/include/llvm/BinaryFormat/Dwarf.def
@@ -1038,6 +1038,7 @@ HANDLE_DW_CC(0xc9, LLVM_PreserveMost)
 HANDLE_DW_CC(0xca, LLVM_PreserveAll)
 HANDLE_DW_CC(0xcb, LLVM_X86RegCall)
 HANDLE_DW_CC(0xcc, LLVM_M68kRTD)
+HANDLE_DW_CC(0xcd, LLVM_PreserveNone)
 // From GCC source code (include/dwarf2.h): This DW_CC_ value is not currently
 // generated by any toolchain.  It is used internally to GDB to indicate OpenCL
 // C functions that have been compiled with the IBM XL C for OpenCL compiler and
diff --git a/llvm/include/llvm/IR/CallingConv.h b/llvm/include/llvm/IR/CallingConv.h
index 3a522c239ad59e..ab553da5a2461c 100644
--- a/llvm/include/llvm/IR/CallingConv.h
+++ b/llvm/include/llvm/IR/CallingConv.h
@@ -86,6 +86,9 @@ namespace CallingConv {
     /// their stack.
     SwiftTail = 20,
 
+    /// Used for runtime calls that preserves none general registers.
+    PreserveNone = 21,
+
     /// This is the start of the target-specific calling conventions, e.g.
     /// fastcall and thiscall on X86.
     FirstTargetCC = 64,
diff --git a/llvm/lib/AsmParser/LLLexer.cpp b/llvm/lib/AsmParser/LLLexer.cpp
index c8da3efbb68aff..5d8a50eee13068 100644
--- a/llvm/lib/AsmParser/LLLexer.cpp
+++ b/llvm/lib/AsmParser/LLLexer.cpp
@@ -617,6 +617,7 @@ lltok::Kind LLLexer::LexIdentifier() {
   KEYWORD(anyregcc);
   KEYWORD(preserve_mostcc);
   KEYWORD(preserve_allcc);
+  KEYWORD(preserve_nonecc);
   KEYWORD(ghccc);
   KEYWORD(x86_intrcc);
   KEYWORD(hhvmcc);
diff --git a/llvm/lib/AsmParser/LLParser.cpp b/llvm/lib/AsmParser/LLParser.cpp
index fb9e1ba875e1fa..382c9799a4afcc 100644
--- a/llvm/lib/AsmParser/LLParser.cpp
+++ b/llvm/lib/AsmParser/LLParser.cpp
@@ -1985,6 +1985,7 @@ void LLParser::parseOptionalDLLStorageClass(unsigned &Res) {
 ///   ::= 'anyregcc'
 ///   ::= 'preserve_mostcc'
 ///   ::= 'preserve_allcc'
+///   ::= 'preserve_nonecc'
 ///   ::= 'ghccc'
 ///   ::= 'swiftcc'
 ///   ::= 'swifttailcc'
@@ -2045,6 +2046,7 @@ bool LLParser::parseOptionalCallingConv(unsigned &CC) {
   case lltok::kw_anyregcc:       CC = CallingConv::AnyReg; break;
   case lltok::kw_preserve_mostcc:CC = CallingConv::PreserveMost; break;
   case lltok::kw_preserve_allcc: CC = CallingConv::PreserveAll; break;
+  case lltok::kw_preserve_nonecc:CC = CallingConv::PreserveNone; break;
   case lltok::kw_ghccc:          CC = CallingConv::GHC; break;
   case lltok::kw_swiftcc:        CC = CallingConv::Swift; break;
   case lltok::kw_swifttailcc:    CC = CallingConv::SwiftTail; break;
diff --git a/llvm/lib/DebugInfo/DWARF/DWARFTypePrinter.cpp b/llvm/lib/DebugInfo/DWARF/DWARFTypePrinter.cpp
index 20242d958b6b42..d97927b92a6c61 100644
--- a/llvm/lib/DebugInfo/DWARF/DWARFTypePrinter.cpp
+++ b/llvm/lib/DebugInfo/DWARF/DWARFTypePrinter.cpp
@@ -617,6 +617,9 @@ void DWARFTypePrinter::appendSubroutineNameAfter(
     case CallingConvention::DW_CC_LLVM_PreserveAll:
       OS << " __attribute__((preserve_all))";
       break;
+    case CallingConvention::DW_CC_LLVM_PreserveNone:
+      OS << " __attribute__((preserve_none))";
+      break;
     case CallingConvention::DW_CC_LLVM_X86RegCall:
       OS << " __attribute__((regcall))";
       break;
diff --git a/llvm/lib/IR/AsmWriter.cpp b/llvm/lib/IR/AsmWriter.cpp
index 95cdec722062e3..afd2845133c090 100644
--- a/llvm/lib/IR/AsmWriter.cpp
+++ b/llvm/lib/IR/AsmWriter.cpp
@@ -304,6 +304,7 @@ static void PrintCallingConv(unsigned cc, raw_ostream &Out) {
   case CallingConv::AnyReg:        Out << "anyregcc"; break;
   case CallingConv::PreserveMost:  Out << "preserve_mostcc"; break;
   case CallingConv::PreserveAll:   Out << "preserve_allcc"; break;
+  case CallingConv::PreserveNone:  Out << "preserve_nonecc"; break;
   case CallingConv::CXX_FAST_TLS:  Out << "cxx_fast_tlscc"; break;
   case CallingConv::GHC:           Out << "ghccc"; break;
   case CallingConv::Tail:          Out << "tailcc"; break;
diff --git a/llvm/lib/Target/X86/X86CallingConv.td b/llvm/lib/Target/X86/X86CallingConv.td
index 16014d6a2f6024..9d2984e4c12a7a 100644
--- a/llvm/lib/Target/X86/X86CallingConv.td
+++ b/llvm/lib/Target/X86/X86CallingConv.td
@@ -1056,6 +1056,22 @@ def CC_Intel_OCL_BI : CallingConv<[
   CCDelegateTo<CC_X86_32_C>
 ]>;
 
+def CC_X86_...
[truncated]

@llvmbot
Copy link
Member

llvmbot commented Jan 3, 2024

@llvm/pr-subscribers-clang-codegen

Author: None (weiguozhi)

Changes

The new experimental calling convention preserve_none is the opposite side of existing preserve_all. It tries to preserve as few general registers as possible. So all general registers are caller saved registers. It can also uses more general registers to pass arguments. This attribute doesn't impact floating-point registers. Floating-point registers still follow the c calling convention.

Currently preserve_none is supported on X86-64 only. It changes the c calling convention in following fields:

  • RSP and RBP are the only preserved general registers, all other general registers are caller saved registers.
  • We can use [RDI, RSI, RDX, RCX, R8, R9, R11, R12, R13, R14, R15, RAX] to pass arguments.

It can improve the performance of hot tailcall chain, because many callee saved registers' save/restore instructions can be removed if the tail functions are using preserve_none. In my experiment in protocol buffer, the parsing functions are improved by 3% to 10%.


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

34 Files Affected:

  • (modified) clang/include/clang-c/Index.h (+1)
  • (modified) clang/include/clang/Basic/Attr.td (+5)
  • (modified) clang/include/clang/Basic/AttrDocs.td (+17)
  • (modified) clang/include/clang/Basic/Specifiers.h (+1)
  • (modified) clang/lib/AST/ItaniumMangle.cpp (+1)
  • (modified) clang/lib/AST/Type.cpp (+2)
  • (modified) clang/lib/AST/TypePrinter.cpp (+6)
  • (modified) clang/lib/Basic/Targets/X86.h (+2)
  • (modified) clang/lib/CodeGen/CGCall.cpp (+4)
  • (modified) clang/lib/CodeGen/CGDebugInfo.cpp (+2)
  • (modified) clang/lib/Sema/SemaDeclAttr.cpp (+7)
  • (modified) clang/lib/Sema/SemaType.cpp (+4-1)
  • (modified) clang/test/CodeGen/debug-info-cc.c (+7)
  • (modified) clang/test/CodeGen/preserve-call-conv.c (+7-1)
  • (modified) clang/test/Sema/no_callconv.cpp (+2)
  • (added) clang/test/Sema/preserve-none-call-conv.c (+19)
  • (modified) clang/tools/libclang/CXType.cpp (+1)
  • (modified) llvm/docs/LangRef.rst (+6)
  • (modified) llvm/include/llvm/AsmParser/LLToken.h (+1)
  • (modified) llvm/include/llvm/BinaryFormat/Dwarf.def (+1)
  • (modified) llvm/include/llvm/IR/CallingConv.h (+3)
  • (modified) llvm/lib/AsmParser/LLLexer.cpp (+1)
  • (modified) llvm/lib/AsmParser/LLParser.cpp (+2)
  • (modified) llvm/lib/DebugInfo/DWARF/DWARFTypePrinter.cpp (+3)
  • (modified) llvm/lib/IR/AsmWriter.cpp (+1)
  • (modified) llvm/lib/Target/X86/X86CallingConv.td (+18)
  • (modified) llvm/lib/Target/X86/X86ISelLoweringCall.cpp (+1)
  • (modified) llvm/lib/Target/X86/X86RegisterInfo.cpp (+4)
  • (modified) llvm/test/Bitcode/compatibility.ll (+2)
  • (added) llvm/test/CodeGen/X86/dynamic-regmask-preserve-none.ll (+88)
  • (modified) llvm/test/CodeGen/X86/ipra-reg-usage.ll (+8-1)
  • (modified) llvm/test/CodeGen/X86/ipra-transform.ll (+19)
  • (added) llvm/test/CodeGen/X86/preserve_nonecc64-ret-double.ll (+85)
  • (added) llvm/test/CodeGen/X86/preserve_nonecc64.ll (+86)
diff --git a/clang/include/clang-c/Index.h b/clang/include/clang-c/Index.h
index 64ab3378957c70..c241a8ccc7dfac 100644
--- a/clang/include/clang-c/Index.h
+++ b/clang/include/clang-c/Index.h
@@ -2981,6 +2981,7 @@ enum CXCallingConv {
   CXCallingConv_SwiftAsync = 17,
   CXCallingConv_AArch64SVEPCS = 18,
   CXCallingConv_M68kRTD = 19,
+  CXCallingConv_PreserveNone = 20,
 
   CXCallingConv_Invalid = 100,
   CXCallingConv_Unexposed = 200
diff --git a/clang/include/clang/Basic/Attr.td b/clang/include/clang/Basic/Attr.td
index db17211747b17d..87e2ce91a0afb7 100644
--- a/clang/include/clang/Basic/Attr.td
+++ b/clang/include/clang/Basic/Attr.td
@@ -2868,6 +2868,11 @@ def M68kRTD: DeclOrTypeAttr {
   let Documentation = [M68kRTDDocs];
 }
 
+def PreserveNone : DeclOrTypeAttr {
+  let Spellings = [Clang<"preserve_none">];
+  let Documentation = [PreserveNoneDocs];
+}
+
 def Target : InheritableAttr {
   let Spellings = [GCC<"target">];
   let Args = [StringArgument<"featuresStr">];
diff --git a/clang/include/clang/Basic/AttrDocs.td b/clang/include/clang/Basic/AttrDocs.td
index 98a7ecc7fd7df3..a43e00c07e141d 100644
--- a/clang/include/clang/Basic/AttrDocs.td
+++ b/clang/include/clang/Basic/AttrDocs.td
@@ -5494,6 +5494,23 @@ experimental at this time.
   }];
 }
 
+def PreserveNoneDocs : Documentation {
+  let Category = DocCatCallingConvs;
+  let Content = [{
+On X86-64 target, this attribute changes the calling convention of a function.
+The ``preserve_none`` calling convention tries to preserve as few general
+registers as possible. So all general registers are caller saved registers. It
+also uses more general registers to pass arguments. This attribute doesn't
+impact floating-point registers (XMMs/YMMs). Floating-point registers still
+follow the c calling convention.
+
+- Only RSP and RBP are preserved by callee.
+
+- Register RDI, RSI, RDX, RCX, R8, R9, R11, R12, R13, R14, R15 and RAX now can
+  be used to pass function arguments.
+  }];
+}
+
 def DeprecatedDocs : Documentation {
   let Category = DocCatDecl;
   let Content = [{
diff --git a/clang/include/clang/Basic/Specifiers.h b/clang/include/clang/Basic/Specifiers.h
index 87f29c8ae10bd9..410be857dee713 100644
--- a/clang/include/clang/Basic/Specifiers.h
+++ b/clang/include/clang/Basic/Specifiers.h
@@ -293,6 +293,7 @@ namespace clang {
     CC_AArch64SVEPCS, // __attribute__((aarch64_sve_pcs))
     CC_AMDGPUKernelCall, // __attribute__((amdgpu_kernel))
     CC_M68kRTD,       // __attribute__((m68k_rtd))
+    CC_PreserveNone,  // __attribute__((preserve_none))
   };
 
   /// Checks whether the given calling convention supports variadic
diff --git a/clang/lib/AST/ItaniumMangle.cpp b/clang/lib/AST/ItaniumMangle.cpp
index b1678479888eb7..e692ef3412d01c 100644
--- a/clang/lib/AST/ItaniumMangle.cpp
+++ b/clang/lib/AST/ItaniumMangle.cpp
@@ -3442,6 +3442,7 @@ StringRef CXXNameMangler::getCallingConvQualifierName(CallingConv CC) {
   case CC_PreserveMost:
   case CC_PreserveAll:
   case CC_M68kRTD:
+  case CC_PreserveNone:
     // FIXME: we should be mangling all of the above.
     return "";
 
diff --git a/clang/lib/AST/Type.cpp b/clang/lib/AST/Type.cpp
index 160a725939ccd4..aae8834b97bce9 100644
--- a/clang/lib/AST/Type.cpp
+++ b/clang/lib/AST/Type.cpp
@@ -3409,6 +3409,7 @@ StringRef FunctionType::getNameForCallConv(CallingConv CC) {
   case CC_PreserveMost: return "preserve_most";
   case CC_PreserveAll: return "preserve_all";
   case CC_M68kRTD: return "m68k_rtd";
+  case CC_PreserveNone: return "preserve_none";
   }
 
   llvm_unreachable("Invalid calling convention.");
@@ -3889,6 +3890,7 @@ bool AttributedType::isCallingConv() const {
   case attr::PreserveMost:
   case attr::PreserveAll:
   case attr::M68kRTD:
+  case attr::PreserveNone:
     return true;
   }
   llvm_unreachable("invalid attr kind");
diff --git a/clang/lib/AST/TypePrinter.cpp b/clang/lib/AST/TypePrinter.cpp
index f6941242927367..7bc4499efc13a2 100644
--- a/clang/lib/AST/TypePrinter.cpp
+++ b/clang/lib/AST/TypePrinter.cpp
@@ -1047,6 +1047,9 @@ void TypePrinter::printFunctionAfter(const FunctionType::ExtInfo &Info,
     case CC_M68kRTD:
       OS << " __attribute__((m68k_rtd))";
       break;
+    case CC_PreserveNone:
+      OS << " __attribute__((preserve_none))";
+      break;
     }
   }
 
@@ -1885,6 +1888,9 @@ void TypePrinter::printAttributedAfter(const AttributedType *T,
   case attr::M68kRTD:
     OS << "m68k_rtd";
     break;
+  case attr::PreserveNone:
+    OS << "preserve_none";
+    break;
   case attr::NoDeref:
     OS << "noderef";
     break;
diff --git a/clang/lib/Basic/Targets/X86.h b/clang/lib/Basic/Targets/X86.h
index 0ab1c10833db26..cdb4b23bc5b430 100644
--- a/clang/lib/Basic/Targets/X86.h
+++ b/clang/lib/Basic/Targets/X86.h
@@ -772,6 +772,7 @@ class LLVM_LIBRARY_VISIBILITY X86_64TargetInfo : public X86TargetInfo {
     case CC_Win64:
     case CC_PreserveMost:
     case CC_PreserveAll:
+    case CC_PreserveNone:
     case CC_X86RegCall:
     case CC_OpenCLKernel:
       return CCCR_OK;
@@ -849,6 +850,7 @@ class LLVM_LIBRARY_VISIBILITY WindowsX86_64TargetInfo
     case CC_IntelOclBicc:
     case CC_PreserveMost:
     case CC_PreserveAll:
+    case CC_PreserveNone:
     case CC_X86_64SysV:
     case CC_Swift:
     case CC_SwiftAsync:
diff --git a/clang/lib/CodeGen/CGCall.cpp b/clang/lib/CodeGen/CGCall.cpp
index 51a43b5f85b3cc..e7773076c203e6 100644
--- a/clang/lib/CodeGen/CGCall.cpp
+++ b/clang/lib/CodeGen/CGCall.cpp
@@ -73,6 +73,7 @@ unsigned CodeGenTypes::ClangCallConvToLLVMCallConv(CallingConv CC) {
   case CC_Swift: return llvm::CallingConv::Swift;
   case CC_SwiftAsync: return llvm::CallingConv::SwiftTail;
   case CC_M68kRTD: return llvm::CallingConv::M68k_RTD;
+  case CC_PreserveNone: return llvm::CallingConv::PreserveNone;
   }
 }
 
@@ -256,6 +257,9 @@ static CallingConv getCallingConventionForDecl(const ObjCMethodDecl *D,
   if (D->hasAttr<M68kRTDAttr>())
     return CC_M68kRTD;
 
+  if (D->hasAttr<PreserveNoneAttr>())
+    return CC_PreserveNone;
+
   return CC_C;
 }
 
diff --git a/clang/lib/CodeGen/CGDebugInfo.cpp b/clang/lib/CodeGen/CGDebugInfo.cpp
index 236d53bee4e8f1..78970e784f6f60 100644
--- a/clang/lib/CodeGen/CGDebugInfo.cpp
+++ b/clang/lib/CodeGen/CGDebugInfo.cpp
@@ -1450,6 +1450,8 @@ static unsigned getDwarfCC(CallingConv CC) {
     return llvm::dwarf::DW_CC_LLVM_X86RegCall;
   case CC_M68kRTD:
     return llvm::dwarf::DW_CC_LLVM_M68kRTD;
+  case CC_PreserveNone:
+    return llvm::dwarf::DW_CC_LLVM_PreserveNone;
   }
   return 0;
 }
diff --git a/clang/lib/Sema/SemaDeclAttr.cpp b/clang/lib/Sema/SemaDeclAttr.cpp
index af8b90ecfed973..6f124c9045f40d 100644
--- a/clang/lib/Sema/SemaDeclAttr.cpp
+++ b/clang/lib/Sema/SemaDeclAttr.cpp
@@ -5219,6 +5219,9 @@ static void handleCallConvAttr(Sema &S, Decl *D, const ParsedAttr &AL) {
   case ParsedAttr::AT_M68kRTD:
     D->addAttr(::new (S.Context) M68kRTDAttr(S.Context, AL));
     return;
+  case ParsedAttr::AT_PreserveNone:
+    D->addAttr(::new (S.Context) PreserveNoneAttr(S.Context, AL));
+    return;
   default:
     llvm_unreachable("unexpected attribute kind");
   }
@@ -5425,6 +5428,9 @@ bool Sema::CheckCallingConvAttr(const ParsedAttr &Attrs, CallingConv &CC,
   case ParsedAttr::AT_M68kRTD:
     CC = CC_M68kRTD;
     break;
+  case ParsedAttr::AT_PreserveNone:
+    CC = CC_PreserveNone;
+    break;
   default: llvm_unreachable("unexpected attribute kind");
   }
 
@@ -9355,6 +9361,7 @@ ProcessDeclAttribute(Sema &S, Scope *scope, Decl *D, const ParsedAttr &AL,
   case ParsedAttr::AT_AArch64SVEPcs:
   case ParsedAttr::AT_AMDGPUKernelCall:
   case ParsedAttr::AT_M68kRTD:
+  case ParsedAttr::AT_PreserveNone:
     handleCallConvAttr(S, D, AL);
     break;
   case ParsedAttr::AT_Suppress:
diff --git a/clang/lib/Sema/SemaType.cpp b/clang/lib/Sema/SemaType.cpp
index a376f20fa4f4e0..68b9e37f3a9281 100644
--- a/clang/lib/Sema/SemaType.cpp
+++ b/clang/lib/Sema/SemaType.cpp
@@ -137,7 +137,8 @@ static void diagnoseBadTypeAttribute(Sema &S, const ParsedAttr &attr,
   case ParsedAttr::AT_IntelOclBicc:                                            \
   case ParsedAttr::AT_PreserveMost:                                            \
   case ParsedAttr::AT_PreserveAll:                                             \
-  case ParsedAttr::AT_M68kRTD
+  case ParsedAttr::AT_M68kRTD:                                                 \
+  case ParsedAttr::AT_PreserveNone
 
 // Function type attributes.
 #define FUNCTION_TYPE_ATTRS_CASELIST                                           \
@@ -7852,6 +7853,8 @@ static Attr *getCCTypeAttr(ASTContext &Ctx, ParsedAttr &Attr) {
     return createSimpleAttr<PreserveAllAttr>(Ctx, Attr);
   case ParsedAttr::AT_M68kRTD:
     return createSimpleAttr<M68kRTDAttr>(Ctx, Attr);
+  case ParsedAttr::AT_PreserveNone:
+    return createSimpleAttr<PreserveNoneAttr>(Ctx, Attr);
   }
   llvm_unreachable("unexpected attribute kind!");
 }
diff --git a/clang/test/CodeGen/debug-info-cc.c b/clang/test/CodeGen/debug-info-cc.c
index a64515e31d1ae3..2664bcd4cb6b2d 100644
--- a/clang/test/CodeGen/debug-info-cc.c
+++ b/clang/test/CodeGen/debug-info-cc.c
@@ -22,6 +22,7 @@
 //    CC_SwiftAsync,   // __attribute__((swiftasynccall))
 //    CC_PreserveMost, // __attribute__((preserve_most))
 //    CC_PreserveAll,  // __attribute__((preserve_all))
+//    CC_PreserveNone,  // __attribute__((preserve_none))
 //  };
 
 #ifdef __x86_64__
@@ -51,6 +52,12 @@ __attribute__((preserve_all)) int add_preserve_all(int a, int b) {
   return a+b;
 }
 
+// LINUX: !DISubprogram({{.*}}"add_preserve_none", {{.*}}type: ![[FTY:[0-9]+]]
+// LINUX: ![[FTY]] = !DISubroutineType({{.*}}cc: DW_CC_LLVM_PreserveNone,
+__attribute__((preserve_none)) int add_preserve_none(int a, int b) {
+  return a+b;
+}
+
 // LINUX: !DISubprogram({{.*}}"add_swiftcall", {{.*}}type: ![[FTY:[0-9]+]]
 // LINUX: ![[FTY]] = !DISubroutineType({{.*}}cc: DW_CC_LLVM_Swift,
 __attribute__((swiftcall)) int add_swiftcall(int a, int b) {
diff --git a/clang/test/CodeGen/preserve-call-conv.c b/clang/test/CodeGen/preserve-call-conv.c
index e700c5cf12f539..74bf695e6f331d 100644
--- a/clang/test/CodeGen/preserve-call-conv.c
+++ b/clang/test/CodeGen/preserve-call-conv.c
@@ -1,4 +1,4 @@
-// RUN: %clang_cc1 -triple x86_64-unknown-unknown -emit-llvm < %s | FileCheck %s
+// RUN: %clang_cc1 -triple x86_64-unknown-unknown -emit-llvm < %s | FileCheck %s --check-prefixes=CHECK,X86-LINUX
 // RUN: %clang_cc1 -triple arm64-unknown-unknown -emit-llvm < %s | FileCheck %s
 
 // RUN: %clang_cc1 -triple x86_64-unknown-windows-msvc -emit-llvm %s -o - | FileCheck %s
@@ -19,3 +19,9 @@ void boo(void) __attribute__((preserve_all)) {
   // CHECK-LABEL: define {{(dso_local )?}}preserve_allcc void @boo()
 }
 
+// Check that the preserve_none calling convention attribute at the source level
+// is lowered to the corresponding calling convention attrribute at the LLVM IR
+// level.
+void bar(void) __attribute__((preserve_none)) {
+  // X86-LINUX-LABEL: define {{(dso_local )?}}preserve_nonecc void @bar()
+}
diff --git a/clang/test/Sema/no_callconv.cpp b/clang/test/Sema/no_callconv.cpp
index a8b3c91e0e3f60..c00930919306a3 100644
--- a/clang/test/Sema/no_callconv.cpp
+++ b/clang/test/Sema/no_callconv.cpp
@@ -15,6 +15,7 @@ void __attribute__((swiftasynccall)) funcKK() {} // expected-error {{'swiftasync
 void __attribute__((pascal)) funcG() {} // expected-error {{'pascal' calling convention is not supported for this target}}
 void __attribute__((preserve_most)) funcL() {} // expected-error {{'preserve_most' calling convention is not supported for this target}}
 void __attribute__((preserve_all)) funcM() {} // expected-error {{'preserve_all' calling convention is not supported for this target}}
+void __attribute__((preserve_none)) funcN() {} // expected-error {{'preserve_none' calling convention is not supported for this target}}
 void __attribute__((stdcall)) funcD() {} // expected-error {{'stdcall' calling convention is not supported for this target}}
 void __attribute__((fastcall)) funcE() {} // expected-error {{'fastcall' calling convention is not supported for this target}}
 void __attribute__((thiscall)) funcF() {} // expected-error {{'thiscall' calling convention is not supported for this target}}
@@ -30,6 +31,7 @@ void __attribute__((swiftcall)) funcK() {}
 void __attribute__((swiftasynccall)) funcKK() {}
 void __attribute__((preserve_most)) funcL() {}
 void __attribute__((preserve_all)) funcM() {}
+void __attribute__((preserve_none)) funcN() {}
 
 // Same function with different calling conventions. Error with a note pointing to the last decl.
 void __attribute__((preserve_all)) funcR(); // expected-note {{previous declaration is here}}
diff --git a/clang/test/Sema/preserve-none-call-conv.c b/clang/test/Sema/preserve-none-call-conv.c
new file mode 100644
index 00000000000000..2f2fed1765dc15
--- /dev/null
+++ b/clang/test/Sema/preserve-none-call-conv.c
@@ -0,0 +1,19 @@
+// RUN: %clang_cc1 %s -fsyntax-only -triple x86_64-unknown-unknown -verify
+
+typedef void typedef_fun_t(int);
+
+void __attribute__((preserve_none)) boo(void *ptr) {
+}
+
+void __attribute__((preserve_none(1))) boo1(void *ptr) { // expected-error {{'preserve_none' attribute takes no arguments}}
+}
+
+void (__attribute__((preserve_none)) *pboo1)(void *) = boo;
+
+void (__attribute__((cdecl)) *pboo2)(void *) = boo; // expected-error {{incompatible function pointer types initializing 'void (*)(void *) __attribute__((cdecl))' with an expression of type 'void (void *) __attribute__((preserve_none))'}}
+void (*pboo3)(void *) = boo; // expected-error {{incompatible function pointer types initializing 'void (*)(void *)' with an expression of type 'void (void *) __attribute__((preserve_none))'}}
+
+typedef_fun_t typedef_fun_boo; // expected-note {{previous declaration is here}}
+void __attribute__((preserve_none)) typedef_fun_boo(int x) { } // expected-error {{function declared 'preserve_none' here was previously declared without calling convention}}
+
+struct type_test_boo {} __attribute__((preserve_none));  // expected-warning {{'preserve_none' attribute only applies to functions and methods}}
diff --git a/clang/tools/libclang/CXType.cpp b/clang/tools/libclang/CXType.cpp
index 3d620d3bfb2602..292d524f00abd6 100644
--- a/clang/tools/libclang/CXType.cpp
+++ b/clang/tools/libclang/CXType.cpp
@@ -679,6 +679,7 @@ CXCallingConv clang_getFunctionTypeCallingConv(CXType X) {
       TCALLINGCONV(PreserveMost);
       TCALLINGCONV(PreserveAll);
       TCALLINGCONV(M68kRTD);
+      TCALLINGCONV(PreserveNone);
     case CC_SpirFunction: return CXCallingConv_Unexposed;
     case CC_AMDGPUKernelCall: return CXCallingConv_Unexposed;
     case CC_OpenCLKernel: return CXCallingConv_Unexposed;
diff --git a/llvm/docs/LangRef.rst b/llvm/docs/LangRef.rst
index 7f4a316a21acee..9344acb5d8301d 100644
--- a/llvm/docs/LangRef.rst
+++ b/llvm/docs/LangRef.rst
@@ -416,6 +416,12 @@ added in the future:
     This calling convention, like the `PreserveMost` calling convention, will be
     used by a future version of the ObjectiveC runtime and should be considered
     experimental at this time.
+"``preserve_nonecc``" - The `PreserveNone` calling convention
+    This calling convention doesn't preserve any general registers. So all
+    general registers are caller saved registers. It also uses all general
+    registers to pass arguments. This attribute doesn't impact floating-point
+    registers (XMMs/YMMs). Floating-point registers still follow the c calling
+    convention.
 "``cxx_fast_tlscc``" - The `CXX_FAST_TLS` calling convention for access functions
     Clang generates an access function to access C++-style TLS. The access
     function generally has an entry block, an exit block and an initialization
diff --git a/llvm/include/llvm/AsmParser/LLToken.h b/llvm/include/llvm/AsmParser/LLToken.h
index 147cf56c821aa1..3c34706ee03e82 100644
--- a/llvm/include/llvm/AsmParser/LLToken.h
+++ b/llvm/include/llvm/AsmParser/LLToken.h
@@ -160,6 +160,7 @@ enum Kind {
   kw_swifttailcc,
   kw_preserve_mostcc,
   kw_preserve_allcc,
+  kw_preserve_nonecc,
   kw_ghccc,
   kw_x86_intrcc,
   kw_hhvmcc,
diff --git a/llvm/include/llvm/BinaryFormat/Dwarf.def b/llvm/include/llvm/BinaryFormat/Dwarf.def
index d1abb1f361d3ed..3a08eeaa791aa6 100644
--- a/llvm/include/llvm/BinaryFormat/Dwarf.def
+++ b/llvm/include/llvm/BinaryFormat/Dwarf.def
@@ -1038,6 +1038,7 @@ HANDLE_DW_CC(0xc9, LLVM_PreserveMost)
 HANDLE_DW_CC(0xca, LLVM_PreserveAll)
 HANDLE_DW_CC(0xcb, LLVM_X86RegCall)
 HANDLE_DW_CC(0xcc, LLVM_M68kRTD)
+HANDLE_DW_CC(0xcd, LLVM_PreserveNone)
 // From GCC source code (include/dwarf2.h): This DW_CC_ value is not currently
 // generated by any toolchain.  It is used internally to GDB to indicate OpenCL
 // C functions that have been compiled with the IBM XL C for OpenCL compiler and
diff --git a/llvm/include/llvm/IR/CallingConv.h b/llvm/include/llvm/IR/CallingConv.h
index 3a522c239ad59e..ab553da5a2461c 100644
--- a/llvm/include/llvm/IR/CallingConv.h
+++ b/llvm/include/llvm/IR/CallingConv.h
@@ -86,6 +86,9 @@ namespace CallingConv {
     /// their stack.
     SwiftTail = 20,
 
+    /// Used for runtime calls that preserves none general registers.
+    PreserveNone = 21,
+
     /// This is the start of the target-specific calling conventions, e.g.
     /// fastcall and thiscall on X86.
     FirstTargetCC = 64,
diff --git a/llvm/lib/AsmParser/LLLexer.cpp b/llvm/lib/AsmParser/LLLexer.cpp
index c8da3efbb68aff..5d8a50eee13068 100644
--- a/llvm/lib/AsmParser/LLLexer.cpp
+++ b/llvm/lib/AsmParser/LLLexer.cpp
@@ -617,6 +617,7 @@ lltok::Kind LLLexer::LexIdentifier() {
   KEYWORD(anyregcc);
   KEYWORD(preserve_mostcc);
   KEYWORD(preserve_allcc);
+  KEYWORD(preserve_nonecc);
   KEYWORD(ghccc);
   KEYWORD(x86_intrcc);
   KEYWORD(hhvmcc);
diff --git a/llvm/lib/AsmParser/LLParser.cpp b/llvm/lib/AsmParser/LLParser.cpp
index fb9e1ba875e1fa..382c9799a4afcc 100644
--- a/llvm/lib/AsmParser/LLParser.cpp
+++ b/llvm/lib/AsmParser/LLParser.cpp
@@ -1985,6 +1985,7 @@ void LLParser::parseOptionalDLLStorageClass(unsigned &Res) {
 ///   ::= 'anyregcc'
 ///   ::= 'preserve_mostcc'
 ///   ::= 'preserve_allcc'
+///   ::= 'preserve_nonecc'
 ///   ::= 'ghccc'
 ///   ::= 'swiftcc'
 ///   ::= 'swifttailcc'
@@ -2045,6 +2046,7 @@ bool LLParser::parseOptionalCallingConv(unsigned &CC) {
   case lltok::kw_anyregcc:       CC = CallingConv::AnyReg; break;
   case lltok::kw_preserve_mostcc:CC = CallingConv::PreserveMost; break;
   case lltok::kw_preserve_allcc: CC = CallingConv::PreserveAll; break;
+  case lltok::kw_preserve_nonecc:CC = CallingConv::PreserveNone; break;
   case lltok::kw_ghccc:          CC = CallingConv::GHC; break;
   case lltok::kw_swiftcc:        CC = CallingConv::Swift; break;
   case lltok::kw_swifttailcc:    CC = CallingConv::SwiftTail; break;
diff --git a/llvm/lib/DebugInfo/DWARF/DWARFTypePrinter.cpp b/llvm/lib/DebugInfo/DWARF/DWARFTypePrinter.cpp
index 20242d958b6b42..d97927b92a6c61 100644
--- a/llvm/lib/DebugInfo/DWARF/DWARFTypePrinter.cpp
+++ b/llvm/lib/DebugInfo/DWARF/DWARFTypePrinter.cpp
@@ -617,6 +617,9 @@ void DWARFTypePrinter::appendSubroutineNameAfter(
     case CallingConvention::DW_CC_LLVM_PreserveAll:
       OS << " __attribute__((preserve_all))";
       break;
+    case CallingConvention::DW_CC_LLVM_PreserveNone:
+      OS << " __attribute__((preserve_none))";
+      break;
     case CallingConvention::DW_CC_LLVM_X86RegCall:
       OS << " __attribute__((regcall))";
       break;
diff --git a/llvm/lib/IR/AsmWriter.cpp b/llvm/lib/IR/AsmWriter.cpp
index 95cdec722062e3..afd2845133c090 100644
--- a/llvm/lib/IR/AsmWriter.cpp
+++ b/llvm/lib/IR/AsmWriter.cpp
@@ -304,6 +304,7 @@ static void PrintCallingConv(unsigned cc, raw_ostream &Out) {
   case CallingConv::AnyReg:        Out << "anyregcc"; break;
   case CallingConv::PreserveMost:  Out << "preserve_mostcc"; break;
   case CallingConv::PreserveAll:   Out << "preserve_allcc"; break;
+  case CallingConv::PreserveNone:  Out << "preserve_nonecc"; break;
   case CallingConv::CXX_FAST_TLS:  Out << "cxx_fast_tlscc"; break;
   case CallingConv::GHC:           Out << "ghccc"; break;
   case CallingConv::Tail:          Out << "tailcc"; break;
diff --git a/llvm/lib/Target/X86/X86CallingConv.td b/llvm/lib/Target/X86/X86CallingConv.td
index 16014d6a2f6024..9d2984e4c12a7a 100644
--- a/llvm/lib/Target/X86/X86CallingConv.td
+++ b/llvm/lib/Target/X86/X86CallingConv.td
@@ -1056,6 +1056,22 @@ def CC_Intel_OCL_BI : CallingConv<[
   CCDelegateTo<CC_X86_32_C>
 ]>;
 
+def CC_X86_...
[truncated]

Copy link

github-actions bot commented Jan 3, 2024

⚠️ C/C++ code formatter, clang-format found issues in your code. ⚠️

You can test this locally with the following command:
git-clang-format --diff 8fdc3b98b894bbbe301b13cf8fc89663e1cbac1a 6cf05164620c32ff55caff4639a58b8b29b537d5 -- clang/test/Sema/preserve-none-call-conv.c clang/include/clang-c/Index.h clang/include/clang/Basic/Specifiers.h clang/lib/AST/ItaniumMangle.cpp clang/lib/AST/Type.cpp clang/lib/AST/TypePrinter.cpp clang/lib/Basic/Targets/X86.h clang/lib/CodeGen/CGCall.cpp clang/lib/CodeGen/CGDebugInfo.cpp clang/lib/Sema/SemaDeclAttr.cpp clang/lib/Sema/SemaType.cpp clang/test/CodeGen/debug-info-cc.c clang/test/CodeGen/preserve-call-conv.c clang/test/Sema/no_callconv.cpp clang/tools/libclang/CXType.cpp llvm/include/llvm/AsmParser/LLToken.h llvm/include/llvm/IR/CallingConv.h llvm/lib/AsmParser/LLLexer.cpp llvm/lib/AsmParser/LLParser.cpp llvm/lib/DebugInfo/DWARF/DWARFTypePrinter.cpp llvm/lib/IR/AsmWriter.cpp llvm/lib/Target/X86/X86ISelLoweringCall.cpp llvm/lib/Target/X86/X86RegisterInfo.cpp
View the diff from clang-format here.
diff --git a/clang/include/clang/Basic/Specifiers.h b/clang/include/clang/Basic/Specifiers.h
index 410be857de..305a801c6b 100644
--- a/clang/include/clang/Basic/Specifiers.h
+++ b/clang/include/clang/Basic/Specifiers.h
@@ -271,29 +271,29 @@ namespace clang {
 
   /// CallingConv - Specifies the calling convention that a function uses.
   enum CallingConv {
-    CC_C,           // __attribute__((cdecl))
-    CC_X86StdCall,  // __attribute__((stdcall))
-    CC_X86FastCall, // __attribute__((fastcall))
-    CC_X86ThisCall, // __attribute__((thiscall))
-    CC_X86VectorCall, // __attribute__((vectorcall))
-    CC_X86Pascal,   // __attribute__((pascal))
-    CC_Win64,       // __attribute__((ms_abi))
-    CC_X86_64SysV,  // __attribute__((sysv_abi))
-    CC_X86RegCall, // __attribute__((regcall))
-    CC_AAPCS,       // __attribute__((pcs("aapcs")))
-    CC_AAPCS_VFP,   // __attribute__((pcs("aapcs-vfp")))
-    CC_IntelOclBicc, // __attribute__((intel_ocl_bicc))
-    CC_SpirFunction, // default for OpenCL functions on SPIR target
-    CC_OpenCLKernel, // inferred for OpenCL kernels
-    CC_Swift,        // __attribute__((swiftcall))
+    CC_C,                 // __attribute__((cdecl))
+    CC_X86StdCall,        // __attribute__((stdcall))
+    CC_X86FastCall,       // __attribute__((fastcall))
+    CC_X86ThisCall,       // __attribute__((thiscall))
+    CC_X86VectorCall,     // __attribute__((vectorcall))
+    CC_X86Pascal,         // __attribute__((pascal))
+    CC_Win64,             // __attribute__((ms_abi))
+    CC_X86_64SysV,        // __attribute__((sysv_abi))
+    CC_X86RegCall,        // __attribute__((regcall))
+    CC_AAPCS,             // __attribute__((pcs("aapcs")))
+    CC_AAPCS_VFP,         // __attribute__((pcs("aapcs-vfp")))
+    CC_IntelOclBicc,      // __attribute__((intel_ocl_bicc))
+    CC_SpirFunction,      // default for OpenCL functions on SPIR target
+    CC_OpenCLKernel,      // inferred for OpenCL kernels
+    CC_Swift,             // __attribute__((swiftcall))
     CC_SwiftAsync,        // __attribute__((swiftasynccall))
-    CC_PreserveMost, // __attribute__((preserve_most))
-    CC_PreserveAll,  // __attribute__((preserve_all))
+    CC_PreserveMost,      // __attribute__((preserve_most))
+    CC_PreserveAll,       // __attribute__((preserve_all))
     CC_AArch64VectorCall, // __attribute__((aarch64_vector_pcs))
-    CC_AArch64SVEPCS, // __attribute__((aarch64_sve_pcs))
-    CC_AMDGPUKernelCall, // __attribute__((amdgpu_kernel))
-    CC_M68kRTD,       // __attribute__((m68k_rtd))
-    CC_PreserveNone,  // __attribute__((preserve_none))
+    CC_AArch64SVEPCS,     // __attribute__((aarch64_sve_pcs))
+    CC_AMDGPUKernelCall,  // __attribute__((amdgpu_kernel))
+    CC_M68kRTD,           // __attribute__((m68k_rtd))
+    CC_PreserveNone,      // __attribute__((preserve_none))
   };
 
   /// Checks whether the given calling convention supports variadic
diff --git a/clang/lib/AST/Type.cpp b/clang/lib/AST/Type.cpp
index aae8834b97..e1ccc11d6b 100644
--- a/clang/lib/AST/Type.cpp
+++ b/clang/lib/AST/Type.cpp
@@ -3409,7 +3409,8 @@ StringRef FunctionType::getNameForCallConv(CallingConv CC) {
   case CC_PreserveMost: return "preserve_most";
   case CC_PreserveAll: return "preserve_all";
   case CC_M68kRTD: return "m68k_rtd";
-  case CC_PreserveNone: return "preserve_none";
+  case CC_PreserveNone:
+    return "preserve_none";
   }
 
   llvm_unreachable("Invalid calling convention.");
diff --git a/clang/lib/CodeGen/CGCall.cpp b/clang/lib/CodeGen/CGCall.cpp
index e7773076c2..032cc83fda 100644
--- a/clang/lib/CodeGen/CGCall.cpp
+++ b/clang/lib/CodeGen/CGCall.cpp
@@ -73,7 +73,8 @@ unsigned CodeGenTypes::ClangCallConvToLLVMCallConv(CallingConv CC) {
   case CC_Swift: return llvm::CallingConv::Swift;
   case CC_SwiftAsync: return llvm::CallingConv::SwiftTail;
   case CC_M68kRTD: return llvm::CallingConv::M68k_RTD;
-  case CC_PreserveNone: return llvm::CallingConv::PreserveNone;
+  case CC_PreserveNone:
+    return llvm::CallingConv::PreserveNone;
   }
 }
 
diff --git a/llvm/lib/AsmParser/LLParser.cpp b/llvm/lib/AsmParser/LLParser.cpp
index 382c9799a4..e6aa305be8 100644
--- a/llvm/lib/AsmParser/LLParser.cpp
+++ b/llvm/lib/AsmParser/LLParser.cpp
@@ -2046,7 +2046,9 @@ bool LLParser::parseOptionalCallingConv(unsigned &CC) {
   case lltok::kw_anyregcc:       CC = CallingConv::AnyReg; break;
   case lltok::kw_preserve_mostcc:CC = CallingConv::PreserveMost; break;
   case lltok::kw_preserve_allcc: CC = CallingConv::PreserveAll; break;
-  case lltok::kw_preserve_nonecc:CC = CallingConv::PreserveNone; break;
+  case lltok::kw_preserve_nonecc:
+    CC = CallingConv::PreserveNone;
+    break;
   case lltok::kw_ghccc:          CC = CallingConv::GHC; break;
   case lltok::kw_swiftcc:        CC = CallingConv::Swift; break;
   case lltok::kw_swifttailcc:    CC = CallingConv::SwiftTail; break;
diff --git a/llvm/lib/IR/AsmWriter.cpp b/llvm/lib/IR/AsmWriter.cpp
index afd2845133..98e2a0e215 100644
--- a/llvm/lib/IR/AsmWriter.cpp
+++ b/llvm/lib/IR/AsmWriter.cpp
@@ -304,7 +304,9 @@ static void PrintCallingConv(unsigned cc, raw_ostream &Out) {
   case CallingConv::AnyReg:        Out << "anyregcc"; break;
   case CallingConv::PreserveMost:  Out << "preserve_mostcc"; break;
   case CallingConv::PreserveAll:   Out << "preserve_allcc"; break;
-  case CallingConv::PreserveNone:  Out << "preserve_nonecc"; break;
+  case CallingConv::PreserveNone:
+    Out << "preserve_nonecc";
+    break;
   case CallingConv::CXX_FAST_TLS:  Out << "cxx_fast_tlscc"; break;
   case CallingConv::GHC:           Out << "ghccc"; break;
   case CallingConv::Tail:          Out << "tailcc"; break;

"``preserve_nonecc``" - The `PreserveNone` calling convention
This calling convention doesn't preserve any general registers. So all
general registers are caller saved registers. It also uses all general
registers to pass arguments. This attribute doesn't impact floating-point
Copy link

Choose a reason for hiding this comment

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

I suspect this should say any non-general purpose registers (e.g. floating point registers, on x86 XMMs/YMMs). Rather than floating-point registers.
Also isn't this just a hack to increase the number of registers used to pass arguments? If so there has to be a better way of doing this. Maybe a non-exposed attribute which is used only for non-exposed functions?
e.g. on x86 (not 64bit), regparm could be used internally there.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I suspect this should say any non-general purpose registers (e.g. floating point registers, on x86 XMMs/YMMs). Rather than floating-point registers.

Thanks for the correction! Will change it.

Also isn't this just a hack to increase the number of registers used to pass arguments? If so there has to be a better way of doing this. Maybe a non-exposed attribute which is used only for non-exposed functions? e.g. on x86 (not 64bit), regparm could be used internally there.

It's not a hack, it's a natural extension. Because we don't preserve general registers, we can use all of them to pass arguments without the extra cost to save/restore those registers, they are clobbered by the function call anyway.

def CC_X86_64_Preserve_None : CallingConv<[
// We don't preserve general registers, so all of them can be used to pass
// arguments except
// - RBP frame pointer
Copy link
Contributor

Choose a reason for hiding this comment

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

It does not mention R16-R31

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Added a comment to mention that R16-R31 are not used to pass arguments because they are not universally available.

@aeubanks
Copy link
Contributor

aeubanks commented Jan 8, 2024

the clang changes should be split into a followup patch

Copy link
Collaborator

@erichkeane erichkeane left a comment

Choose a reason for hiding this comment

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

CFE changes aren't bad, just a few comments on how the attribute is defined in attr.td

@@ -2868,6 +2868,11 @@ def M68kRTD: DeclOrTypeAttr {
let Documentation = [M68kRTDDocs];
}

def PreserveNone : DeclOrTypeAttr {
let Spellings = [Clang<"preserve_none">];
Copy link
Collaborator

Choose a reason for hiding this comment

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

This should have a subjectlist set, since this is on functions, it should probably have a function subject. Since it is target specific, it should ALSO be a target specific attribute.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I tried to add

  let Subjects = SubjectList<[Function]>;

But it doesn't work for the code

  void __attribute__((preserve_none)) boo(void *ptr) {
  }

  void (__attribute__((preserve_none)) *pboo1)(void *) = boo;

I got the error message

'preserve_none' attribute only applies to functions

Copy link
Collaborator

Choose a reason for hiding this comment

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

Calling conventions are somewhat odd in the attribute system, so I would do:

// let Subjects = [Function, ObjCMethod];

so there's some visual indication of what we meant but it doesn't have any effect. Many of the other calling conventions do this.

Copy link
Collaborator

Choose a reason for hiding this comment

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

The second example there is a function pointer, not a function you're trying to apply it to. If you want it to work on Function Pointers as well, you need to use FunctionLike.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

FunctionLike works for me. Thanks!

R11, R12, R13, R14, R15, RAX]>>,

// Otherwise it's the same as the regular C calling convention.
CCDelegateTo<CC_X86_64_C>
Copy link
Member

Choose a reason for hiding this comment

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

This delegation seems questionable -- what about the interaction with the swift attributes which use dedicated registers in CC_X86_64_C, which you're now also using for normal parameters?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I have never thought of swift attributes. How can I disable swift attributes when using preserve_none calling convention?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I added code to check if there are any swift attributes used with preserve_none at the same time, and report error if it is detected.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

@jyknight could you help to review the code that checks the combination of swift attributes and preserve_none in dc1bc55

// - RBP frame pointer
// - R10 'nest' parameter
// - RBX base pointer
// - R16 - R31 these are not available everywhere
Copy link
Contributor

Choose a reason for hiding this comment

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

I won't expect this from the name preserve_none. R16-R31 should be used to pas arguments when they're available.

Copy link
Member

Choose a reason for hiding this comment

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

They should not be used to pass arguments conditionally based on the current subtarget, since that creates two incompatible calling conventions.

There's no reason "preserve none" has to be read to imply "uses all possible registers to pass arguments.", so I don't see an issue with leaving it like it is.

Specify it has a FunctionLike subject.
Specify it as a target specific attribute.
Report error when swift attribute is used with preserve_none at the same
time.
@weiguozhi weiguozhi requested a review from rnk January 19, 2024 02:27
Copy link
Collaborator

@rnk rnk left a comment

Choose a reason for hiding this comment

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

This looks complete to the best of my knowledge, but I know others have specific concerns, like James identified the swift attribute conflict. To other reviewers, do you have any outstanding concerns to address?

I think it would be nice to quickly generalize this to AArch64 if possible, since all the same use cases apply there.

Copy link
Contributor

@david-xl david-xl left a comment

Choose a reason for hiding this comment

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

Can you increase the test coverage:

  1. preserve_none caller TAIL-calls preserve_none callee -- the tail call is preserved
  2. regular function TAIL-calls preserve_none callee -- the tail call should be disabled and all CSRs should be saved/restored around the call.
  3. preserve-none caller calls preserve_none callee -- no registers are saved/restored around the call
  4. preserve_none caller calls preserve_none callee with long argument list -- CSRs used for arg passing.

Modify the test case to avoid possible future re-association impact.
@weiguozhi weiguozhi merged commit c166a43 into llvm:main Feb 5, 2024
@brandtbucher
Copy link
Contributor

brandtbucher commented Feb 12, 2024

Thanks for working on this! It's really appreciated.

Two follow-up questions:

  • Is AArch64 support planned soon?
  • Am I correct in understanding that this missed the LLVM 18 cut, and won't ship until LLVM 19 in mid-September-ish?

@calebh
Copy link

calebh commented Mar 10, 2024

We have another use case for this calling convention - micropatching. See my coworker's blog post on how this would work: https://www.philipzucker.com/permutation_compile/

Is there a reason why r10 isn't included in the list of registers for x64?

Having support for additional architectures (ARM, PPC) would be great for our use case.

@weiguozhi
Copy link
Contributor Author

weiguozhi commented Mar 12, 2024

We have another use case for this calling convention - micropatching. See my coworker's blog post on how this would work: https://www.philipzucker.com/permutation_compile/

I'm happy to see it's useful to you.

Is there a reason why r10 isn't included in the list of registers for x64?

In the standard C calling convention R10 is used as nest parameter, I don't know its exact use case (maybe for nest function?). Excluding R10 will avoid problem in those cases.

Having support for additional architectures (ARM, PPC) would be great for our use case.

We are also interested in AArch64 support, but currently it's not in our high priority.

@brandtbucher
Copy link
Contributor

brandtbucher commented Apr 4, 2024

After playing around with this a bit, I'm wondering if the registers used for argument-passing should be reordered.

The motivating use-case for this calling convention is to pin registers across chained tail calls, but much of the pinning effect is lost if any other calls are made in the body of the function. This is because the first 7 parameters are all being pinned in volatile registers, so any call results in a cascade of shuffles and spills. You can see a simple example of this here: https://godbolt.org/z/rzsG9fb4W

If I understand correctly, we can reduce this effect by choosing registers that are normally callee-saved (r12, r13, r14, and r15) before using normally-clobbered registers (rdi, rsi, rdx, rcx, r8, r9, r11, and rax). This is what the similar GHC calling convention does.

What do you think, @weiguozhi?

@david-xl
Copy link
Contributor

david-xl commented Apr 4, 2024

After playing around with this a bit, I'm wondering if the registers used for argument-passing should be reordered.

The motivating use-case for this calling convention is to pin registers across chained tail calls, but much of the pinning effect is lost if any other calls are made in the body of the function. This is because the first 7 parameters are all being pinned in volatile registers, so any call results in a cascade of shuffles and spills. You can see a simple example of this here: https://godbolt.org/z/rzsG9fb4W

If I understand correctly, we can reduce this effect by choosing registers that are normally callee-saved (r12, r13, r14, and r15) before using normally-clobbered registers (rdi, rsi, rdx, rcx, r8, r9, r11, and rax). This is what the similar GHC calling convention does.

What do you think, @weiguozhi?

Makes sense. @weiguozhi can you also help measure the performance impact with the order reverse change with preserve_none? I wonder if a new calling convention is needed or just bake this into preserve_none.

@brandtbucher
Copy link
Contributor

Draft PR at #88333.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
backend:X86 clang:codegen IR generation bugs: mangling, exceptions, etc. clang:frontend Language frontend issues, e.g. anything involving "Sema" clang Clang issues not falling into any other category debuginfo llvm:binary-utilities llvm:ir
Projects
None yet
Development

Successfully merging this pull request may close these issues.