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
