https://github.com/adams381 created 
https://github.com/llvm/llvm-project/pull/211335

The x86_64 System V classifier and the CallConvLowering pass that drives it are 
already in the tree, but nothing runs them: the -fclangir pipeline never adds 
the pass, so CIR still emits uncoerced aggregate signatures on x86_64 and its 
LLVM output diverges from classic CodeGen for anything passed or returned in 
registers.

This adds the cc1 flag -clangir-enable-call-conv-lowering, off by default like 
-clangir-enable-idiom-recognizer, and runs the pass in runCIRToCIRPasses right 
after CXXABILowering, which has already reduced C++ ABI types to plain records 
the classifier can handle.  The flag is gated on x86_64.  On other targets it 
is a silent no-op, so a driver can set it unconditionally without breaking 
non-x86_64 builds.  The AVX level is fixed at None because the classifier's 
current supported subset has no >128-bit vector aggregate where the level would 
change the result.

With the flag on, supported scalars and small aggregates are coerced to match 
classic CodeGen: sign/zero-extended narrow integers, SSE scalars, one- and 
two-eightbyte INTEGER/SSE struct coercion, ignored empty structs, sret returns, 
and byval arguments.  Types the classifier does not yet handle — unions, long 
double, and float aggregates that classify to an SSE vector — still errorNYI 
when the flag is on.  CIR does emit noalias on byval parameters where classic 
CodeGen does not, which the test documents with split prefixes.


>From 6ce3427b8348be653547e89875dfd5ab11462c1f Mon Sep 17 00:00:00 2001
From: Adam Smith <[email protected]>
Date: Wed, 22 Jul 2026 10:52:44 -0700
Subject: [PATCH] [CIR] Add opt-in flag to run x86_64 call-conv lowering

The CallConvLowering pass and its x86_64 System V classifier are in the
tree but nothing runs them in the -fclangir pipeline, so CIR still emits
uncoerced aggregate signatures for x86_64.

Add the cc1 flag -clangir-enable-call-conv-lowering (off by default,
mirroring -clangir-enable-idiom-recognizer) and run the pass in
runCIRToCIRPasses right after CXXABILowering, which has already reduced
C++ ABI types to plain records the classifier understands.  The pass is
gated on x86_64.  On other targets the flag is a silent no-op so it can be
set unconditionally without breaking non-x86_64 compilations.

AVX level is fixed at None: the classifier's supported type subset has no
>128-bit vector aggregate where the level matters, so deriving it now
would be untested.  Types the classifier does not yet handle (e.g.
unions) still errorNYI when the flag is on.
---
 clang/include/clang/CIR/CIRToCIRPasses.h      |  3 +-
 .../include/clang/Frontend/FrontendOptions.h  |  8 +-
 clang/include/clang/Options/Options.td        |  5 ++
 clang/lib/CIR/FrontendAction/CIRGenAction.cpp |  3 +-
 clang/lib/CIR/Lowering/CIRPasses.cpp          | 28 ++++++-
 .../CIR/CodeGen/call-conv-lowering-x86_64.c   | 81 +++++++++++++++++++
 6 files changed, 122 insertions(+), 6 deletions(-)
 create mode 100644 clang/test/CIR/CodeGen/call-conv-lowering-x86_64.c

diff --git a/clang/include/clang/CIR/CIRToCIRPasses.h 
b/clang/include/clang/CIR/CIRToCIRPasses.h
index de0d4fab8597d..6e60614f8e9de 100644
--- a/clang/include/clang/CIR/CIRToCIRPasses.h
+++ b/clang/include/clang/CIR/CIRToCIRPasses.h
@@ -33,7 +33,8 @@ mlir::LogicalResult
 runCIRToCIRPasses(mlir::ModuleOp theModule, mlir::MLIRContext &mlirCtx,
                   clang::ASTContext &astCtx, bool enableVerifier,
                   bool enableIdiomRecognizer, bool enableCIRSimplify,
-                  bool enableLibOpt, llvm::StringRef libOptOptions);
+                  bool enableLibOpt, llvm::StringRef libOptOptions,
+                  bool enableCallConvLowering);
 
 } // namespace cir
 
diff --git a/clang/include/clang/Frontend/FrontendOptions.h 
b/clang/include/clang/Frontend/FrontendOptions.h
index 73c422998a217..3d410c0d7deef 100644
--- a/clang/include/clang/Frontend/FrontendOptions.h
+++ b/clang/include/clang/Frontend/FrontendOptions.h
@@ -423,6 +423,10 @@ class FrontendOptions {
   LLVM_PREFERRED_TYPE(bool)
   unsigned ClangIREnableIdiomRecognizer : 1;
 
+  /// Enable Clang IR (CIR) calling-convention lowering
+  LLVM_PREFERRED_TYPE(bool)
+  unsigned ClangIREnableCallConvLowering : 1;
+
   /// Enable ClangIR library optimization.
   /// Set when -fclangir-lib-opt or -fclangir-lib-opt= was passed.
   LLVM_PREFERRED_TYPE(bool)
@@ -565,8 +569,8 @@ class FrontendOptions {
         EmitPrettySymbolGraphs(false), GenReducedBMI(false),
         UseClangIRPipeline(false), ClangIRDisablePasses(false),
         ClangIRDisableCIRVerifier(false), ClangIREnableIdiomRecognizer(false),
-        ClangIRLibOptEnabled(false), TimeTraceGranularity(500),
-        TimeTraceVerbose(false) {}
+        ClangIREnableCallConvLowering(false), ClangIRLibOptEnabled(false),
+        TimeTraceGranularity(500), TimeTraceVerbose(false) {}
 
   /// getInputKindForExtension - Return the appropriate input kind for a file
   /// extension. For example, "c" would return Language::C.
diff --git a/clang/include/clang/Options/Options.td 
b/clang/include/clang/Options/Options.td
index 669b6e64725c9..f5264d5ea12fa 100644
--- a/clang/include/clang/Options/Options.td
+++ b/clang/include/clang/Options/Options.td
@@ -3459,6 +3459,11 @@ def clangir_enable_idiom_recognizer : Flag<["-"], 
"clangir-enable-idiom-recogniz
   HelpText<"ClangIR: Enable Idiom Recognizer pass">,
   MarshallingInfoFlag<FrontendOpts<"ClangIREnableIdiomRecognizer">>;
 
+def clangir_enable_call_conv_lowering : Flag<["-"], 
"clangir-enable-call-conv-lowering">,
+  Visibility<[ClangOption, CC1Option]>,
+  HelpText<"ClangIR: Enable calling-convention lowering pass (x86_64 only)">,
+  MarshallingInfoFlag<FrontendOpts<"ClangIREnableCallConvLowering">>;
+
 def clangir_lib_opt_EQ : Joined<["-"], "clangir-lib-opt=">,
   Visibility<[ClangOption, CC1Option]>, Group<f_Group>, Values<"all">,
   HelpText<"Enable C/C++ library based optimizations (with options)">,
diff --git a/clang/lib/CIR/FrontendAction/CIRGenAction.cpp 
b/clang/lib/CIR/FrontendAction/CIRGenAction.cpp
index 28229ac8c58fd..42baa948d9698 100644
--- a/clang/lib/CIR/FrontendAction/CIRGenAction.cpp
+++ b/clang/lib/CIR/FrontendAction/CIRGenAction.cpp
@@ -144,7 +144,8 @@ class CIRGenConsumer : public clang::ASTConsumer {
       if (runCIRToCIRPasses(
               MlirModule, MlirCtx, C, !FEOptions.ClangIRDisableCIRVerifier,
               FEOptions.ClangIREnableIdiomRecognizer, CGO.OptimizationLevel > 
0,
-              EnableLibOpt, LibOptOptions)
+              EnableLibOpt, LibOptOptions,
+              FEOptions.ClangIREnableCallConvLowering)
               .failed()) {
         CI.getDiagnostics().Report(diag::err_cir_to_cir_transform_failed);
         return;
diff --git a/clang/lib/CIR/Lowering/CIRPasses.cpp 
b/clang/lib/CIR/Lowering/CIRPasses.cpp
index 2866e5f15379c..0a683b8c8a498 100644
--- a/clang/lib/CIR/Lowering/CIRPasses.cpp
+++ b/clang/lib/CIR/Lowering/CIRPasses.cpp
@@ -10,19 +10,30 @@
 //
 
//===----------------------------------------------------------------------===//
 
-// #include "clang/AST/ASTContext.h"
 #include "mlir/IR/BuiltinOps.h"
 #include "mlir/Pass/PassManager.h"
+#include "clang/AST/ASTContext.h"
+#include "clang/Basic/TargetInfo.h"
 #include "clang/CIR/Dialect/Passes.h"
 #include "llvm/Support/TimeProfiler.h"
+#include "llvm/TargetParser/Triple.h"
 
 namespace cir {
 
+/// Map a target triple to the ABI target that drives CallConvLowering.
+/// Returns None for targets whose calling convention is not yet implemented.
+static CallConvTarget getCallConvTarget(const llvm::Triple &triple) {
+  if (triple.getArch() == llvm::Triple::x86_64)
+    return CallConvTarget::X86_64;
+  return CallConvTarget::None;
+}
+
 mlir::LogicalResult
 runCIRToCIRPasses(mlir::ModuleOp theModule, mlir::MLIRContext &mlirContext,
                   clang::ASTContext &astContext, bool enableVerifier,
                   bool enableIdiomRecognizer, bool enableCIRSimplify,
-                  bool enableLibOpt, llvm::StringRef libOptOptions) {
+                  bool enableLibOpt, llvm::StringRef libOptOptions,
+                  bool enableCallConvLowering) {
 
   llvm::TimeTraceScope scope("CIR To CIR Passes");
 
@@ -49,6 +60,19 @@ runCIRToCIRPasses(mlir::ModuleOp theModule, 
mlir::MLIRContext &mlirContext,
 
   pm.addPass(mlir::createTargetLoweringPass());
   pm.addPass(mlir::createCXXABILoweringPass());
+
+  if (enableCallConvLowering) {
+    // CallConvLowering rewrites signatures and call sites using the 
classifier,
+    // so it must run after CXXABILowering has lowered C++ ABI types to plain
+    // records the classifier can handle.  Only the x86_64 System V classifier
+    // is implemented; other targets are left unchanged.
+    CallConvTarget target =
+        getCallConvTarget(astContext.getTargetInfo().getTriple());
+    if (target != CallConvTarget::None)
+      pm.addPass(mlir::createCallConvLoweringPass(
+          target, llvm::abi::X86AVXABILevel::None));
+  }
+
   pm.addPass(mlir::createLoweringPreparePass(&astContext));
 
   pm.enableVerifier(enableVerifier);
diff --git a/clang/test/CIR/CodeGen/call-conv-lowering-x86_64.c 
b/clang/test/CIR/CodeGen/call-conv-lowering-x86_64.c
new file mode 100644
index 0000000000000..6931198a90c3d
--- /dev/null
+++ b/clang/test/CIR/CodeGen/call-conv-lowering-x86_64.c
@@ -0,0 +1,81 @@
+// RUN: %clang_cc1 -triple x86_64-unknown-linux-gnu -fclangir 
-clangir-enable-call-conv-lowering -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 
-clangir-enable-call-conv-lowering -emit-llvm %s -o %t-cir.ll
+// RUN: FileCheck --check-prefixes=LLVM,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-prefixes=LLVM,LLVM-OGCG --input-file=%t.ll %s
+
+typedef struct { int x; int y; } Pair2;
+typedef struct { long a; long b; } Pair16;
+typedef struct { long a, b, c, d; } Big;
+typedef struct { long a; double b; } IntSSE;
+typedef struct { double a; double b; } SSE2;
+typedef struct { } Empty;
+
+// Narrow signed integer sign-extended in a register.
+signed char ext_schar(signed char c) { return c; }
+
+// CIR: cir.func {{.*}}@ext_schar(%arg0: !s8i {{.*}}llvm.signext{{.*}}) -> 
(!s8i {{.*}}llvm.signext
+// LLVM: define dso_local signext i8 @ext_schar(i8 noundef signext %{{.+}})
+
+// Narrow unsigned integer zero-extended in a register.
+unsigned char ext_uchar(unsigned char c) { return c; }
+
+// CIR: cir.func {{.*}}@ext_uchar(%arg0: !u8i {{.*}}llvm.zeroext{{.*}}) -> 
(!u8i {{.*}}llvm.zeroext
+// LLVM: define dso_local zeroext i8 @ext_uchar(i8 noundef zeroext %{{.+}})
+
+// Floating-point scalar passed/returned in an SSE register.
+double sse_double(double d) { return d; }
+
+// CIR: cir.func {{.*}}@sse_double(%arg0: !cir.double {{.*}}) -> !cir.double
+// LLVM: define dso_local double @sse_double(double noundef %{{.+}})
+
+// Two-int struct returned in a single INTEGER eightbyte -> i64.
+Pair2 ret_pair2(int a) { Pair2 p = {a, a}; return p; }
+
+// CIR: cir.func {{.*}}@ret_pair2(%arg0: !s32i {{.*}}) -> !u64i
+// LLVM: define dso_local i64 @ret_pair2(i32 noundef %{{.+}})
+
+// 16-byte struct flattened into two integer registers.
+void take_pair16(Pair16 p) { (void)p; }
+
+// CIR: cir.func {{.*}}@take_pair16(%arg0: !s64i{{.*}}, %arg1: !s64i{{.*}})
+// LLVM: define dso_local void @take_pair16(i64 %{{.+}}, i64 %{{.+}})
+
+// Struct split into one INTEGER and one SSE eightbyte.
+void take_int_sse(IntSSE s) { (void)s; }
+
+// CIR: cir.func {{.*}}@take_int_sse(%arg0: !s64i{{.*}}, %arg1: 
!cir.double{{.*}})
+// LLVM: define dso_local void @take_int_sse(i64 %{{.+}}, double %{{.+}})
+
+// Struct split into two SSE eightbytes.
+void take_sse2(SSE2 s) { (void)s; }
+
+// CIR: cir.func {{.*}}@take_sse2(%arg0: !cir.double{{.*}}, %arg1: 
!cir.double{{.*}})
+// LLVM: define dso_local void @take_sse2(double %{{.+}}, double %{{.+}})
+
+// Empty struct argument is ignored -- dropped from the signature entirely.
+void take_empty(Empty e) { (void)e; }
+
+// CIR: cir.func {{.*}}@take_empty()
+// LLVM: define dso_local void @take_empty()
+
+// Empty struct return is ignored -- the function returns void.
+Empty ret_empty(void) { Empty e; return e; }
+
+// CIR: cir.func {{.*}}@ret_empty()
+// LLVM: define dso_local void @ret_empty()
+
+// Large struct returned indirectly via sret.
+Big ret_big(void) { Big b = {1, 2, 3, 4}; return b; }
+
+// CIR: cir.func {{.*}}@ret_big(%arg0: !cir.ptr<!rec_Big> {{.*}}llvm.sret = 
!rec_Big{{.*}})
+// LLVM: define dso_local void @ret_big(ptr dead_on_unwind noalias writable 
sret(%struct.Big) align 8 %{{.+}})
+
+// Large struct passed byval.  CIR also emits noalias on byval; OGCG only does
+// so under -fpass-by-value-is-noalias.
+void take_big(Big b) { (void)b; }
+
+// CIR: cir.func {{.*}}@take_big(%arg0: !cir.ptr<!rec_Big> {{.*}}llvm.byval = 
!rec_Big{{.*}})
+// LLVM-CIR: define dso_local void @take_big(ptr noalias noundef 
byval(%struct.Big) align 8 %{{.+}})
+// LLVM-OGCG: define dso_local void @take_big(ptr noundef byval(%struct.Big) 
align 8 %{{.+}})

_______________________________________________
cfe-commits mailing list
[email protected]
https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits

Reply via email to