[clang] [CIR] Add x86_64 scalar calling-convention lowering (PR #209636)
via cfe-commits
cfe-commits at lists.llvm.org
Fri Jul 17 14:02:44 PDT 2026
https://github.com/adams381 updated https://github.com/llvm/llvm-project/pull/209636
>From b41c998307ddfeff7aef68b1c8b7735b8e1b5919 Mon Sep 17 00:00:00 2001
From: Adam Smith <adams at nvidia.com>
Date: Tue, 14 Jul 2026 14:25:48 -0700
Subject: [PATCH 1/5] [CIR] Add x86_64 scalar calling-convention lowering
CallConvLowering has a test target and a classification-injection mode
but no production ABI classifier. Add a target=x86_64 mode that bridges
the LLVM ABI Lowering Library's System V x86_64 classifier to the pass:
it maps scalar CIR types to llvm::abi types, runs the classifier, and
converts the result into the mlir::abi FunctionClassification that
CIRABIRewriteContext already consumes.
This first step handles scalar signatures only -- integer up to 64 bits,
pointer, bool, f32, and f64. Any other type (records, arrays, vectors,
complex, wide or uncommon floats, _BitInt, __int128, member pointers)
is reported through emitOpError as not-yet-implemented so it fails
loudly instead of being misclassified; those types are added in stacked
follow-up PRs. The pass stays exercised through cir-opt only; wiring it
into the -fclangir pipeline is a separate change.
Assisted-by: Cursor / claude-opus-4.8
---
clang/include/clang/CIR/Dialect/Passes.h | 2 +
clang/include/clang/CIR/Dialect/Passes.td | 4 +-
.../lib/CIR/Dialect/Transforms/CMakeLists.txt | 1 +
.../Transforms/CallConvLoweringPass.cpp | 188 +++++++++++++++++-
.../abi-lowering/x86_64-scalars.cir | 74 +++++++
5 files changed, 266 insertions(+), 3 deletions(-)
create mode 100644 clang/test/CIR/Transforms/abi-lowering/x86_64-scalars.cir
diff --git a/clang/include/clang/CIR/Dialect/Passes.h b/clang/include/clang/CIR/Dialect/Passes.h
index 7d317879ad739..28ec8d936bb05 100644
--- a/clang/include/clang/CIR/Dialect/Passes.h
+++ b/clang/include/clang/CIR/Dialect/Passes.h
@@ -28,6 +28,8 @@ std::unique_ptr<Pass> createCIREHABILoweringPass();
std::unique_ptr<Pass> createCXXABILoweringPass();
std::unique_ptr<Pass> createTargetLoweringPass();
std::unique_ptr<Pass> createCallConvLoweringPass();
+std::unique_ptr<Pass> createCallConvLoweringPass(llvm::StringRef target,
+ unsigned x86AvxAbiLevel);
std::unique_ptr<Pass> createHoistAllocasPass();
std::unique_ptr<Pass> createLoweringPreparePass();
std::unique_ptr<Pass> createLoweringPreparePass(clang::ASTContext *astCtx);
diff --git a/clang/include/clang/CIR/Dialect/Passes.td b/clang/include/clang/CIR/Dialect/Passes.td
index 9cdeb4d42d5a1..1c3c8ead1842f 100644
--- a/clang/include/clang/CIR/Dialect/Passes.td
+++ b/clang/include/clang/CIR/Dialect/Passes.td
@@ -240,11 +240,13 @@ def CallConvLowering : Pass<"cir-call-conv-lowering", "mlir::ModuleOp"> {
let dependentDialects = ["cir::CIRDialect"];
let options = [
Option<"target", "target", "std::string", /*default=*/"\"\"",
- "Target whose ABI rules drive classification (currently: test)">,
+ "Target whose ABI rules drive classification (test, x86_64)">,
Option<"classificationAttr", "classification-attr", "std::string",
/*default=*/"\"\"",
"Function attribute name carrying a pre-built FunctionClassification "
"DictionaryAttr (alternative to target=, used by tests)">,
+ Option<"x86AvxAbiLevel", "x86-avx-abi-level", "unsigned", /*default=*/"0",
+ "AVX ABI level for the x86_64 target (0=None, 1=AVX, 2=AVX512)">,
];
}
diff --git a/clang/lib/CIR/Dialect/Transforms/CMakeLists.txt b/clang/lib/CIR/Dialect/Transforms/CMakeLists.txt
index 5edbfcf467f90..82078bf2e8f73 100644
--- a/clang/lib/CIR/Dialect/Transforms/CMakeLists.txt
+++ b/clang/lib/CIR/Dialect/Transforms/CMakeLists.txt
@@ -22,6 +22,7 @@ add_clang_library(MLIRCIRTransforms
clangAST
clangBasic
+ LLVMABI
MLIRABI
MLIRAnalysis
MLIRDLTIDialect
diff --git a/clang/lib/CIR/Dialect/Transforms/CallConvLoweringPass.cpp b/clang/lib/CIR/Dialect/Transforms/CallConvLoweringPass.cpp
index c00947593517e..5d153c9699cc6 100644
--- a/clang/lib/CIR/Dialect/Transforms/CallConvLoweringPass.cpp
+++ b/clang/lib/CIR/Dialect/Transforms/CallConvLoweringPass.cpp
@@ -34,6 +34,7 @@
#include "TargetLowering/CIRABIRewriteContext.h"
#include "mlir/ABI/ABIRewriteContext.h"
+#include "mlir/ABI/ABITypeMapper.h"
#include "mlir/ABI/Targets/Test/TestTarget.h"
#include "mlir/Dialect/DLTI/DLTI.h"
#include "mlir/IR/Builders.h"
@@ -43,6 +44,10 @@
#include "mlir/Pass/Pass.h"
#include "clang/CIR/Dialect/IR/CIRDialect.h"
#include "clang/CIR/Dialect/Passes.h"
+#include "llvm/ABI/FunctionInfo.h"
+#include "llvm/ABI/TargetInfo.h"
+#include "llvm/ABI/Types.h"
+#include "llvm/IR/CallingConv.h"
using namespace mlir;
using namespace mlir::abi;
@@ -55,6 +60,156 @@ namespace mlir {
namespace {
+//===----------------------------------------------------------------------===//
+// x86_64 System V classifier bridge (scalar types)
+//
+// Maps scalar CIR types to llvm::abi::Type, runs the LLVM ABI Lowering
+// Library's SysV x86_64 classifier, and converts the result back into the
+// dialect-agnostic mlir::abi::FunctionClassification that CIRABIRewriteContext
+// consumes. Only integer / pointer / bool / f32 / f64 signatures are handled;
+// aggregates and other leaf types are reported NYI by classifyX86_64 so an
+// unsupported signature fails the pass instead of being misclassified.
+//===----------------------------------------------------------------------===//
+
+/// llvm::Align requires a power of two; DataLayout can report non-power-of-two
+/// alignments for unusual types.
+static llvm::Align safeAlign(uint64_t a) {
+ return llvm::Align(llvm::PowerOf2Ceil(std::max<uint64_t>(a, 1)));
+}
+
+/// The scalar CIR types the x86_64 bridge handles. A regular integer up to
+/// 64 bits, pointer, bool, void, f32, or f64 is a single-register Direct or
+/// Extend argument. `_BitInt`, `__int128`, and wider/other types need coercion
+/// or indirect passing, which this scalar bridge does not do.
+static bool isSupportedScalarType(mlir::Type ty) {
+ if (isa<cir::VoidType, cir::BoolType, cir::PointerType, cir::SingleType,
+ cir::DoubleType>(ty))
+ return true;
+ if (auto intTy = dyn_cast<cir::IntType>(ty))
+ return !intTy.getIsBitInt() && intTy.getWidth() <= 64;
+ return false;
+}
+
+/// Convert an llvm::abi::Type coercion type back to a scalar CIR type.
+static mlir::Type abiTypeToCIR(const llvm::abi::Type *ty, MLIRContext *ctx) {
+ if (!ty)
+ return nullptr;
+ if (ty->isVoid())
+ return cir::VoidType::get(ctx);
+ if (auto *intTy = llvm::dyn_cast<llvm::abi::IntegerType>(ty))
+ return cir::IntType::get(ctx, intTy->getSizeInBits().getFixedValue(),
+ intTy->isSigned());
+ if (auto *fltTy = llvm::dyn_cast<llvm::abi::FloatType>(ty)) {
+ const llvm::fltSemantics *sem = fltTy->getSemantics();
+ if (sem == &llvm::APFloat::IEEEsingle())
+ return cir::SingleType::get(ctx);
+ if (sem == &llvm::APFloat::IEEEdouble())
+ return cir::DoubleType::get(ctx);
+ }
+ if (llvm::isa<llvm::abi::PointerType>(ty))
+ return cir::PointerType::get(cir::VoidType::get(ctx));
+ return nullptr;
+}
+
+/// Map a scalar CIR type to an llvm::abi::Type. classifyX86_64 pre-filters the
+/// signature, so only the scalar types handled here can reach this function.
+static const llvm::abi::Type *mapCIRType(mlir::Type type,
+ mlir::abi::ABITypeMapper &typeMapper,
+ const DataLayout &dl) {
+ llvm::abi::TypeBuilder &tb = typeMapper.getTypeBuilder();
+ if (auto intTy = dyn_cast<cir::IntType>(type))
+ return tb.getIntegerType(intTy.getWidth(),
+ safeAlign(dl.getTypeABIAlignment(type)),
+ intTy.isSigned());
+ if (isa<cir::PointerType>(type))
+ return tb.getPointerType(dl.getTypeSizeInBits(type),
+ safeAlign(dl.getTypeABIAlignment(type)),
+ /*AddressSpace=*/0);
+ if (isa<cir::BoolType>(type))
+ return tb.getIntegerType(dl.getTypeSizeInBits(type),
+ safeAlign(dl.getTypeABIAlignment(type)),
+ /*Signed=*/false);
+ if (isa<cir::VoidType>(type))
+ return tb.getVoidType();
+ if (isa<cir::SingleType>(type))
+ return tb.getFloatType(llvm::APFloat::IEEEsingle(),
+ safeAlign(dl.getTypeABIAlignment(type)));
+ if (isa<cir::DoubleType>(type))
+ return tb.getFloatType(llvm::APFloat::IEEEdouble(),
+ safeAlign(dl.getTypeABIAlignment(type)));
+ llvm_unreachable("mapCIRType: type not pre-filtered by classifyX86_64");
+}
+
+/// Convert an llvm::abi::ArgInfo for a scalar type into the ArgClassification
+/// consumed by CIRABIRewriteContext.
+static ArgClassification convertABIArgInfo(const llvm::abi::ArgInfo &info,
+ MLIRContext *ctx,
+ mlir::Type origTy) {
+ if (info.isDirect())
+ return ArgClassification::getDirect(nullptr);
+ if (info.isExtend()) {
+ if (origTy && isa<cir::BoolType>(origTy))
+ return ArgClassification::getExtend(nullptr, info.isSignExt());
+ if (origTy && !isa<cir::IntType>(origTy))
+ return ArgClassification::getDirect(nullptr);
+ mlir::Type coerced = abiTypeToCIR(info.getCoerceToType(), ctx);
+ return ArgClassification::getExtend(coerced, info.isSignExt());
+ }
+ if (info.isIndirect())
+ return ArgClassification::getIndirect(info.getIndirectAlign(),
+ info.getIndirectByVal());
+ return ArgClassification::getIgnore();
+}
+
+/// Classify a cir.func for x86_64 SysV using the LLVM ABI library. Returns
+/// std::nullopt and emits an NYI error if the signature uses a type the scalar
+/// bridge does not handle yet.
+static std::optional<FunctionClassification>
+classifyX86_64(cir::FuncOp func, const DataLayout &dl,
+ mlir::abi::ABITypeMapper &typeMapper,
+ const llvm::abi::TargetInfo &targetInfo) {
+ MLIRContext *ctx = func->getContext();
+ cir::FuncType fnTy = func.getFunctionType();
+ mlir::Type retCIR = fnTy.getReturnType();
+ bool voidRet = !retCIR || isa<cir::VoidType>(retCIR);
+
+ auto reject = [&](mlir::Type t) -> bool {
+ if (isSupportedScalarType(t))
+ return false;
+ func.emitOpError()
+ << "x86_64 calling-convention lowering not yet implemented for type "
+ << t;
+ return true;
+ };
+ if (!voidRet && reject(retCIR))
+ return std::nullopt;
+ for (mlir::Type a : fnTy.getInputs())
+ if (reject(a))
+ return std::nullopt;
+
+ const llvm::abi::Type *retAbi =
+ voidRet ? typeMapper.getTypeBuilder().getVoidType()
+ : mapCIRType(retCIR, typeMapper, dl);
+ SmallVector<const llvm::abi::Type *> argAbi;
+ for (mlir::Type a : fnTy.getInputs())
+ argAbi.push_back(mapCIRType(a, typeMapper, dl));
+
+ std::unique_ptr<llvm::abi::FunctionInfo> fi =
+ llvm::abi::FunctionInfo::create(llvm::CallingConv::C, retAbi, argAbi);
+ targetInfo.computeInfo(*fi);
+
+ FunctionClassification fc;
+ mlir::Type origRet = voidRet ? mlir::Type() : retCIR;
+ fc.returnInfo = convertABIArgInfo(fi->getReturnInfo(), ctx, origRet);
+ auto inputs = fnTy.getInputs();
+ for (unsigned i = 0, e = fi->arg_size(); i < e; ++i) {
+ mlir::Type origArg = i < inputs.size() ? inputs[i] : mlir::Type();
+ fc.argInfos.push_back(
+ convertABIArgInfo(fi->getArgInfo(i).Info, ctx, origArg));
+ }
+ return fc;
+}
+
bool needsRewrite(const FunctionClassification &fc) {
if ((fc.returnInfo.kind != ArgKind::Direct) || fc.returnInfo.coercedType)
return true;
@@ -95,7 +250,10 @@ classifyFunction(cir::FuncOp func, const DataLayout &dl, StringRef target,
if (target == "test")
return mlir::abi::test::classify(argTypes, returnType, dl);
- func.emitOpError() << "unknown target '" << target << "' (supported: test)";
+ // Note: the "x86_64" target is handled directly in runOnOperation (it needs
+ // a shared ABITypeMapper and TargetInfo), so it never reaches here.
+ func.emitOpError() << "unknown target '" << target
+ << "' (supported: test, x86_64)";
return std::nullopt;
}
@@ -140,13 +298,30 @@ void CallConvLoweringPass::runOnOperation() {
CIRABIRewriteContext rewriteCtx(moduleOp, dl);
SymbolTable symbolTable(moduleOp);
+ // For the x86_64 target, build the LLVM ABI library classifier once and
+ // reuse it (and its type mapper) across every function.
+ std::optional<mlir::abi::ABITypeMapper> x86TypeMapper;
+ std::unique_ptr<llvm::abi::TargetInfo> x86Target;
+ if (target == "x86_64") {
+ x86TypeMapper.emplace(dl);
+ auto avx =
+ static_cast<llvm::abi::X86AVXABILevel>(x86AvxAbiLevel.getValue());
+ x86Target = llvm::abi::createX86_64TargetInfo(
+ x86TypeMapper->getTypeBuilder(), avx, /*Has64BitPointers=*/true,
+ llvm::abi::ABICompatInfo());
+ }
+
// Classify every cir.func up front. No IR mutation happens here, so
// later walks can consult any function's classification regardless of
// visitation order.
llvm::MapVector<cir::FuncOp, FunctionClassification> classifications;
bool anyFailed = false;
moduleOp.walk([&](cir::FuncOp f) {
- auto fc = classifyFunction(f, dl, target, classificationAttr);
+ std::optional<FunctionClassification> fc;
+ if (x86Target)
+ fc = classifyX86_64(f, dl, *x86TypeMapper, *x86Target);
+ else
+ fc = classifyFunction(f, dl, target, classificationAttr);
if (!fc) {
anyFailed = true;
return;
@@ -226,3 +401,12 @@ void CallConvLoweringPass::runOnOperation() {
std::unique_ptr<Pass> mlir::createCallConvLoweringPass() {
return std::make_unique<CallConvLoweringPass>();
}
+
+std::unique_ptr<Pass>
+mlir::createCallConvLoweringPass(llvm::StringRef target,
+ unsigned x86AvxAbiLevel) {
+ CallConvLoweringOptions options;
+ options.target = target.str();
+ options.x86AvxAbiLevel = x86AvxAbiLevel;
+ return std::make_unique<CallConvLoweringPass>(options);
+}
diff --git a/clang/test/CIR/Transforms/abi-lowering/x86_64-scalars.cir b/clang/test/CIR/Transforms/abi-lowering/x86_64-scalars.cir
new file mode 100644
index 0000000000000..006f6532f281d
--- /dev/null
+++ b/clang/test/CIR/Transforms/abi-lowering/x86_64-scalars.cir
@@ -0,0 +1,74 @@
+// RUN: cir-opt %s -cir-call-conv-lowering=target=x86_64 | FileCheck %s
+// RUN: cir-opt %s -cir-call-conv-lowering=target=x86_64 -cir-to-llvm -o - 2>/dev/null \
+// RUN: | mlir-translate -mlir-to-llvmir --allow-unregistered-dialect \
+// RUN: | FileCheck %s --check-prefix=LLVM
+
+!s8i = !cir.int<s, 8>
+!u16i = !cir.int<u, 16>
+!s32i = !cir.int<s, 32>
+!s64i = !cir.int<s, 64>
+
+module attributes {
+ dlti.dl_spec = #dlti.dl_spec<
+ #dlti.dl_entry<i1, dense<8>: vector<2xi64>>,
+ #dlti.dl_entry<i8, dense<8>: vector<2xi64>>,
+ #dlti.dl_entry<i16, dense<16>: vector<2xi64>>,
+ #dlti.dl_entry<i32, dense<32>: vector<2xi64>>,
+ #dlti.dl_entry<i64, dense<64>: vector<2xi64>>>
+} {
+
+ // Register-sized integers are Direct: signature and call sites unchanged.
+ cir.func @passthrough(%arg0: !s32i, %arg1: !s64i) -> !s32i {
+ cir.return %arg0 : !s32i
+ }
+
+ // CHECK: cir.func{{.*}} @passthrough(%arg0: !s32i, %arg1: !s64i) -> !s32i
+ // CHECK-NEXT: cir.return %arg0 : !s32i
+
+ // Floating-point scalars are Direct.
+ cir.func @floats(%arg0: !cir.float, %arg1: !cir.double) -> !cir.double {
+ cir.return %arg1 : !cir.double
+ }
+
+ // CHECK: cir.func{{.*}} @floats(%arg0: !cir.float, %arg1: !cir.double) -> !cir.double
+
+ // Pointers are Direct.
+ cir.func @take_ptr(%arg0: !cir.ptr<!s32i>) -> !cir.ptr<!s32i> {
+ cir.return %arg0 : !cir.ptr<!s32i>
+ }
+
+ // CHECK: cir.func{{.*}} @take_ptr(%arg0: !cir.ptr<!s32i>) -> !cir.ptr<!s32i>
+
+ // Signed sub-register integer is sign-extended.
+ cir.func @take_s8(%arg0: !s8i) {
+ cir.return
+ }
+
+ // CHECK: cir.func{{.*}} @take_s8(%arg0: !s8i {llvm.signext})
+
+ // Unsigned sub-register integer is zero-extended.
+ cir.func @take_u16(%arg0: !u16i) {
+ cir.return
+ }
+
+ // CHECK: cir.func{{.*}} @take_u16(%arg0: !u16i {llvm.zeroext})
+
+ // bool is zero-extended.
+ cir.func @take_bool(%arg0: !cir.bool) {
+ cir.return
+ }
+
+ // CHECK: cir.func{{.*}} @take_bool(%arg0: !cir.bool {llvm.zeroext})
+
+ // Call site picks up the same extension attribute on the operand.
+ cir.func @call_s8(%arg0: !s8i) {
+ cir.call @take_s8(%arg0) : (!s8i) -> ()
+ cir.return
+ }
+
+ // CHECK: cir.call @take_s8(%arg0) : (!s8i {llvm.signext}) -> ()
+}
+
+// LLVM: define void @take_s8(i8 signext %{{.+}})
+// LLVM: define void @take_u16(i16 zeroext %{{.+}})
+// LLVM: define void @take_bool(i1 zeroext %{{.+}})
>From 47be65cc78ac5ad347c63e7de90c511223eda142 Mon Sep 17 00:00:00 2001
From: Adam Smith <adams at nvidia.com>
Date: Wed, 15 Jul 2026 15:00:43 -0700
Subject: [PATCH 2/5] [CIR] Address review comments on x86_64 scalars
Convert abiTypeToCIR/mapCIRType to TypeSwitch, and read the real
address space on pointers instead of assuming 0.
Add asserts for the Extend/Indirect/voidRet cases the classifier can
never actually produce for this scalar-only type set, verified
against every ArgInfo::getExtend/getIndirect* call site in
llvm/lib/ABI/Targets/X86.cpp. Document all four convertABIArgInfo
branches, rename classifyX86_64 to classifyX86_64Function and the
misleading `coerced` local to extendedTy, and replace the `target`
string comparisons with an internal enum so the unknown-target
diagnostic and dispatch share one source of truth instead of a
hand-written name list.
Add return-side sign/zero-extension test coverage; the existing
tests only covered argument extension.
Assisted-by: Cursor / claude-opus-4.8
---
.../Transforms/CallConvLoweringPass.cpp | 177 ++++++++++++------
.../abi-lowering/x86_64-scalars.cir | 17 ++
2 files changed, 140 insertions(+), 54 deletions(-)
diff --git a/clang/lib/CIR/Dialect/Transforms/CallConvLoweringPass.cpp b/clang/lib/CIR/Dialect/Transforms/CallConvLoweringPass.cpp
index 5d153c9699cc6..987a6e42e43ad 100644
--- a/clang/lib/CIR/Dialect/Transforms/CallConvLoweringPass.cpp
+++ b/clang/lib/CIR/Dialect/Transforms/CallConvLoweringPass.cpp
@@ -47,6 +47,7 @@
#include "llvm/ABI/FunctionInfo.h"
#include "llvm/ABI/TargetInfo.h"
#include "llvm/ABI/Types.h"
+#include "llvm/ADT/TypeSwitch.h"
#include "llvm/IR/CallingConv.h"
using namespace mlir;
@@ -67,8 +68,8 @@ namespace {
// Library's SysV x86_64 classifier, and converts the result back into the
// dialect-agnostic mlir::abi::FunctionClassification that CIRABIRewriteContext
// consumes. Only integer / pointer / bool / f32 / f64 signatures are handled;
-// aggregates and other leaf types are reported NYI by classifyX86_64 so an
-// unsupported signature fails the pass instead of being misclassified.
+// aggregates and other leaf types are reported NYI by classifyX86_64Function
+// so an unsupported signature fails the pass instead of being misclassified.
//===----------------------------------------------------------------------===//
/// llvm::Align requires a power of two; DataLayout can report non-power-of-two
@@ -94,54 +95,91 @@ static bool isSupportedScalarType(mlir::Type ty) {
static mlir::Type abiTypeToCIR(const llvm::abi::Type *ty, MLIRContext *ctx) {
if (!ty)
return nullptr;
- if (ty->isVoid())
- return cir::VoidType::get(ctx);
- if (auto *intTy = llvm::dyn_cast<llvm::abi::IntegerType>(ty))
- return cir::IntType::get(ctx, intTy->getSizeInBits().getFixedValue(),
- intTy->isSigned());
- if (auto *fltTy = llvm::dyn_cast<llvm::abi::FloatType>(ty)) {
- const llvm::fltSemantics *sem = fltTy->getSemantics();
- if (sem == &llvm::APFloat::IEEEsingle())
- return cir::SingleType::get(ctx);
- if (sem == &llvm::APFloat::IEEEdouble())
- return cir::DoubleType::get(ctx);
- }
- if (llvm::isa<llvm::abi::PointerType>(ty))
- return cir::PointerType::get(cir::VoidType::get(ctx));
- return nullptr;
+ return llvm::TypeSwitch<const llvm::abi::Type *, mlir::Type>(ty)
+ .Case(
+ [&](const llvm::abi::VoidType *) { return cir::VoidType::get(ctx); })
+ .Case([&](const llvm::abi::IntegerType *intTy) {
+ return cir::IntType::get(ctx, intTy->getSizeInBits().getFixedValue(),
+ intTy->isSigned());
+ })
+ .Case([&](const llvm::abi::FloatType *fltTy) -> mlir::Type {
+ const llvm::fltSemantics *sem = fltTy->getSemantics();
+ if (sem == &llvm::APFloat::IEEEsingle())
+ return cir::SingleType::get(ctx);
+ if (sem == &llvm::APFloat::IEEEdouble())
+ return cir::DoubleType::get(ctx);
+ return nullptr;
+ })
+ .Case([&](const llvm::abi::PointerType *) {
+ return cir::PointerType::get(cir::VoidType::get(ctx));
+ })
+ .Default([](const llvm::abi::Type *) -> mlir::Type { return nullptr; });
}
-/// Map a scalar CIR type to an llvm::abi::Type. classifyX86_64 pre-filters the
-/// signature, so only the scalar types handled here can reach this function.
+/// Map a scalar CIR type to an llvm::abi::Type. classifyX86_64Function
+/// pre-filters the signature, so only the scalar types handled here can
+/// reach this function.
static const llvm::abi::Type *mapCIRType(mlir::Type type,
mlir::abi::ABITypeMapper &typeMapper,
const DataLayout &dl) {
llvm::abi::TypeBuilder &tb = typeMapper.getTypeBuilder();
- if (auto intTy = dyn_cast<cir::IntType>(type))
- return tb.getIntegerType(intTy.getWidth(),
- safeAlign(dl.getTypeABIAlignment(type)),
- intTy.isSigned());
- if (isa<cir::PointerType>(type))
- return tb.getPointerType(dl.getTypeSizeInBits(type),
- safeAlign(dl.getTypeABIAlignment(type)),
- /*AddressSpace=*/0);
- if (isa<cir::BoolType>(type))
- return tb.getIntegerType(dl.getTypeSizeInBits(type),
- safeAlign(dl.getTypeABIAlignment(type)),
- /*Signed=*/false);
- if (isa<cir::VoidType>(type))
- return tb.getVoidType();
- if (isa<cir::SingleType>(type))
- return tb.getFloatType(llvm::APFloat::IEEEsingle(),
- safeAlign(dl.getTypeABIAlignment(type)));
- if (isa<cir::DoubleType>(type))
- return tb.getFloatType(llvm::APFloat::IEEEdouble(),
- safeAlign(dl.getTypeABIAlignment(type)));
- llvm_unreachable("mapCIRType: type not pre-filtered by classifyX86_64");
+ return llvm::TypeSwitch<mlir::Type, const llvm::abi::Type *>(type)
+ .Case([&](cir::IntType intTy) {
+ return tb.getIntegerType(intTy.getWidth(),
+ safeAlign(dl.getTypeABIAlignment(type)),
+ intTy.isSigned());
+ })
+ .Case([&](cir::PointerType ptrTy) {
+ unsigned addrSpace = 0;
+ if (auto targetAsAttr =
+ dyn_cast_if_present<cir::TargetAddressSpaceAttr>(
+ ptrTy.getAddrSpace()))
+ addrSpace = targetAsAttr.getValue();
+ return tb.getPointerType(dl.getTypeSizeInBits(type),
+ safeAlign(dl.getTypeABIAlignment(type)),
+ addrSpace);
+ })
+ .Case([&](cir::BoolType) {
+ return tb.getIntegerType(dl.getTypeSizeInBits(type),
+ safeAlign(dl.getTypeABIAlignment(type)),
+ /*Signed=*/false);
+ })
+ .Case([&](cir::VoidType) { return tb.getVoidType(); })
+ .Case([&](cir::SingleType) {
+ return tb.getFloatType(llvm::APFloat::IEEEsingle(),
+ safeAlign(dl.getTypeABIAlignment(type)));
+ })
+ .Case([&](cir::DoubleType) {
+ return tb.getFloatType(llvm::APFloat::IEEEdouble(),
+ safeAlign(dl.getTypeABIAlignment(type)));
+ })
+ .Default([](mlir::Type) -> const llvm::abi::Type * {
+ llvm_unreachable(
+ "mapCIRType: type not pre-filtered by classifyX86_64Function");
+ });
}
/// Convert an llvm::abi::ArgInfo for a scalar type into the ArgClassification
/// consumed by CIRABIRewriteContext.
+///
+/// Direct: every scalar this bridge maps passes as-is, so no coercion type
+/// is needed (nullptr means "same as the original CIR type").
+///
+/// Extend: bool or a sub-register integer needs a signext/zeroext attribute.
+/// Every ArgInfo::getExtend() call site in the x86_64 classifier
+/// (llvm/lib/ABI/Targets/X86.cpp) is gated on the operand being an integer,
+/// so a non-integer, non-bool origTy here would mean the classifier
+/// disagreed with its own source -- asserted rather than silently handled.
+///
+/// Indirect: needed once records/_BitInt/vectors are supported (sret,
+/// byval, and large _BitInt all classify Indirect), but
+/// X86_64TargetInfo::getIndirectResult()/getIndirectReturnResult() only
+/// return Indirect for aggregates or _BitInt, neither of which the
+/// scalar-only type set this bridge accepts can produce. Unreachable until
+/// a later PR adds those types and the record/vector/complex/int type gate
+/// that belongs here.
+///
+/// Ignore: a void return has no register or stack slot.
static ArgClassification convertABIArgInfo(const llvm::abi::ArgInfo &info,
MLIRContext *ctx,
mlir::Type origTy) {
@@ -150,14 +188,14 @@ static ArgClassification convertABIArgInfo(const llvm::abi::ArgInfo &info,
if (info.isExtend()) {
if (origTy && isa<cir::BoolType>(origTy))
return ArgClassification::getExtend(nullptr, info.isSignExt());
- if (origTy && !isa<cir::IntType>(origTy))
- return ArgClassification::getDirect(nullptr);
- mlir::Type coerced = abiTypeToCIR(info.getCoerceToType(), ctx);
- return ArgClassification::getExtend(coerced, info.isSignExt());
+ assert((!origTy || isa<cir::IntType>(origTy)) &&
+ "the x86_64 classifier only returns Extend for integers and bool");
+ mlir::Type extendedTy = abiTypeToCIR(info.getCoerceToType(), ctx);
+ return ArgClassification::getExtend(extendedTy, info.isSignExt());
}
if (info.isIndirect())
- return ArgClassification::getIndirect(info.getIndirectAlign(),
- info.getIndirectByVal());
+ llvm_unreachable("Indirect classification is impossible for the "
+ "scalar-only type set this bridge accepts");
return ArgClassification::getIgnore();
}
@@ -165,13 +203,14 @@ static ArgClassification convertABIArgInfo(const llvm::abi::ArgInfo &info,
/// std::nullopt and emits an NYI error if the signature uses a type the scalar
/// bridge does not handle yet.
static std::optional<FunctionClassification>
-classifyX86_64(cir::FuncOp func, const DataLayout &dl,
- mlir::abi::ABITypeMapper &typeMapper,
- const llvm::abi::TargetInfo &targetInfo) {
+classifyX86_64Function(cir::FuncOp func, const DataLayout &dl,
+ mlir::abi::ABITypeMapper &typeMapper,
+ const llvm::abi::TargetInfo &targetInfo) {
MLIRContext *ctx = func->getContext();
cir::FuncType fnTy = func.getFunctionType();
mlir::Type retCIR = fnTy.getReturnType();
- bool voidRet = !retCIR || isa<cir::VoidType>(retCIR);
+ assert(retCIR && "FuncType::getReturnType() never returns null");
+ bool voidRet = isa<cir::VoidType>(retCIR);
auto reject = [&](mlir::Type t) -> bool {
if (isSupportedScalarType(t))
@@ -210,6 +249,36 @@ classifyX86_64(cir::FuncOp func, const DataLayout &dl,
return fc;
}
+/// The classifier targets this pass drives internally via a shared
+/// ABITypeMapper/TargetInfo. (The classification-attr injection mode is
+/// orthogonal -- it names an arbitrary attribute, not a fixed target -- and
+/// stays string-keyed.) The name/enum pairs below are the single source of
+/// truth for both parsing the `target` pass option and the unknown-target
+/// diagnostic in classifyFunction.
+enum class CallConvTarget { Test, X86_64 };
+
+const std::pair<llvm::StringRef, CallConvTarget> kCallConvTargets[] = {
+ {"test", CallConvTarget::Test},
+ {"x86_64", CallConvTarget::X86_64},
+};
+
+std::optional<CallConvTarget> parseCallConvTarget(StringRef target) {
+ for (const auto &[name, value] : kCallConvTargets)
+ if (target == name)
+ return value;
+ return std::nullopt;
+}
+
+std::string supportedCallConvTargets() {
+ std::string result;
+ for (const auto &entry : kCallConvTargets) {
+ if (!result.empty())
+ result += ", ";
+ result += entry.first.str();
+ }
+ return result;
+}
+
bool needsRewrite(const FunctionClassification &fc) {
if ((fc.returnInfo.kind != ArgKind::Direct) || fc.returnInfo.coercedType)
return true;
@@ -247,13 +316,13 @@ classifyFunction(cir::FuncOp func, const DataLayout &dl, StringRef target,
attr, [&]() { return func.emitOpError(); });
}
- if (target == "test")
+ if (parseCallConvTarget(target) == CallConvTarget::Test)
return mlir::abi::test::classify(argTypes, returnType, dl);
// Note: the "x86_64" target is handled directly in runOnOperation (it needs
// a shared ABITypeMapper and TargetInfo), so it never reaches here.
func.emitOpError() << "unknown target '" << target
- << "' (supported: test, x86_64)";
+ << "' (supported: " << supportedCallConvTargets() << ")";
return std::nullopt;
}
@@ -302,7 +371,7 @@ void CallConvLoweringPass::runOnOperation() {
// reuse it (and its type mapper) across every function.
std::optional<mlir::abi::ABITypeMapper> x86TypeMapper;
std::unique_ptr<llvm::abi::TargetInfo> x86Target;
- if (target == "x86_64") {
+ if (parseCallConvTarget(target) == CallConvTarget::X86_64) {
x86TypeMapper.emplace(dl);
auto avx =
static_cast<llvm::abi::X86AVXABILevel>(x86AvxAbiLevel.getValue());
@@ -319,7 +388,7 @@ void CallConvLoweringPass::runOnOperation() {
moduleOp.walk([&](cir::FuncOp f) {
std::optional<FunctionClassification> fc;
if (x86Target)
- fc = classifyX86_64(f, dl, *x86TypeMapper, *x86Target);
+ fc = classifyX86_64Function(f, dl, *x86TypeMapper, *x86Target);
else
fc = classifyFunction(f, dl, target, classificationAttr);
if (!fc) {
diff --git a/clang/test/CIR/Transforms/abi-lowering/x86_64-scalars.cir b/clang/test/CIR/Transforms/abi-lowering/x86_64-scalars.cir
index 006f6532f281d..6498702b0f26e 100644
--- a/clang/test/CIR/Transforms/abi-lowering/x86_64-scalars.cir
+++ b/clang/test/CIR/Transforms/abi-lowering/x86_64-scalars.cir
@@ -67,8 +67,25 @@ module attributes {
}
// CHECK: cir.call @take_s8(%arg0) : (!s8i {llvm.signext}) -> ()
+
+ // A sub-register integer return is also extended, not just arguments.
+ cir.func @ret_s8() -> !s8i {
+ %0 = cir.const #cir.int<0> : !s8i
+ cir.return %0 : !s8i
+ }
+
+ // CHECK: cir.func{{.*}} @ret_s8() -> (!s8i {llvm.signext})
+
+ cir.func @ret_u16() -> !u16i {
+ %0 = cir.const #cir.int<0> : !u16i
+ cir.return %0 : !u16i
+ }
+
+ // CHECK: cir.func{{.*}} @ret_u16() -> (!u16i {llvm.zeroext})
}
// LLVM: define void @take_s8(i8 signext %{{.+}})
// LLVM: define void @take_u16(i16 zeroext %{{.+}})
// LLVM: define void @take_bool(i1 zeroext %{{.+}})
+// LLVM: define signext i8 @ret_s8()
+// LLVM: define zeroext i16 @ret_u16()
>From 781afe367ebe40b3a95c7819c28992ef0d09b78d Mon Sep 17 00:00:00 2001
From: Adam Smith <adams at nvidia.com>
Date: Thu, 16 Jul 2026 13:44:47 -0700
Subject: [PATCH 3/5] [CIR][NFC] Tidy up x86_64 scalar calling-convention
lowering
This moves the floating-point semantics-to-type mapping out of the
calling-convention pass and into a shared cir::getFloatingPointType helper
in CIRTypes, alongside cir::isSized. It still only handles f32 and f64; any
other semantics now trips an llvm_unreachable instead of returning null,
though the scalar classifier never lets one through.
It also gives the x86_64 AVX pass option its real type,
llvm::abi::X86AVXABILevel, instead of a plain unsigned that we had to cast
back to the enum at the call site.
The target name lookup is now a constexpr array indexed by the
CallConvTarget enum, with a Last sentinel and a static_assert so the names
and the enum can't drift apart.
---
clang/include/clang/CIR/Dialect/IR/CIRTypes.h | 10 +++++
clang/include/clang/CIR/Dialect/Passes.h | 6 ++-
clang/include/clang/CIR/Dialect/Passes.td | 10 ++++-
clang/lib/CIR/Dialect/IR/CIRTypes.cpp | 12 +++++
.../Transforms/CallConvLoweringPass.cpp | 45 ++++++++-----------
clang/lib/CIR/Dialect/Transforms/PassDetail.h | 1 +
6 files changed, 54 insertions(+), 30 deletions(-)
diff --git a/clang/include/clang/CIR/Dialect/IR/CIRTypes.h b/clang/include/clang/CIR/Dialect/IR/CIRTypes.h
index c5f4127040ca0..ccae66e774bbf 100644
--- a/clang/include/clang/CIR/Dialect/IR/CIRTypes.h
+++ b/clang/include/clang/CIR/Dialect/IR/CIRTypes.h
@@ -24,6 +24,10 @@
#include "clang/CIR/Dialect/IR/CIROpsEnums.h"
#include "clang/CIR/Interfaces/CIRTypeInterfaces.h"
+namespace llvm {
+struct fltSemantics;
+} // namespace llvm
+
namespace cir {
namespace detail {
@@ -42,6 +46,12 @@ bool isValidFundamentalIntWidth(unsigned width);
/// void, or abstract types.
bool isSized(mlir::Type ty);
+/// Returns the CIR floating-point type for a given semantics. Mirrors
+/// llvm::Type::getFloatingPointTy; currently only covers the semantics
+/// CIR's scalar ABI classifier can reach (f32/f64).
+mlir::Type getFloatingPointType(const llvm::fltSemantics &sem,
+ mlir::MLIRContext *ctx);
+
//===----------------------------------------------------------------------===//
// AddressSpace helpers
//===----------------------------------------------------------------------===//
diff --git a/clang/include/clang/CIR/Dialect/Passes.h b/clang/include/clang/CIR/Dialect/Passes.h
index 28ec8d936bb05..9ac2b4a018b10 100644
--- a/clang/include/clang/CIR/Dialect/Passes.h
+++ b/clang/include/clang/CIR/Dialect/Passes.h
@@ -14,6 +14,7 @@
#define CLANG_CIR_DIALECT_PASSES_H
#include "mlir/Pass/Pass.h"
+#include "llvm/ABI/TargetInfo.h"
namespace clang {
class ASTContext;
@@ -28,8 +29,9 @@ std::unique_ptr<Pass> createCIREHABILoweringPass();
std::unique_ptr<Pass> createCXXABILoweringPass();
std::unique_ptr<Pass> createTargetLoweringPass();
std::unique_ptr<Pass> createCallConvLoweringPass();
-std::unique_ptr<Pass> createCallConvLoweringPass(llvm::StringRef target,
- unsigned x86AvxAbiLevel);
+std::unique_ptr<Pass>
+createCallConvLoweringPass(llvm::StringRef target,
+ llvm::abi::X86AVXABILevel x86AvxAbiLevel);
std::unique_ptr<Pass> createHoistAllocasPass();
std::unique_ptr<Pass> createLoweringPreparePass();
std::unique_ptr<Pass> createLoweringPreparePass(clang::ASTContext *astCtx);
diff --git a/clang/include/clang/CIR/Dialect/Passes.td b/clang/include/clang/CIR/Dialect/Passes.td
index 1c3c8ead1842f..6ba5367163636 100644
--- a/clang/include/clang/CIR/Dialect/Passes.td
+++ b/clang/include/clang/CIR/Dialect/Passes.td
@@ -245,8 +245,14 @@ def CallConvLowering : Pass<"cir-call-conv-lowering", "mlir::ModuleOp"> {
/*default=*/"\"\"",
"Function attribute name carrying a pre-built FunctionClassification "
"DictionaryAttr (alternative to target=, used by tests)">,
- Option<"x86AvxAbiLevel", "x86-avx-abi-level", "unsigned", /*default=*/"0",
- "AVX ABI level for the x86_64 target (0=None, 1=AVX, 2=AVX512)">,
+ Option<"x86AvxAbiLevel", "x86-avx-abi-level", "llvm::abi::X86AVXABILevel",
+ /*default=*/"llvm::abi::X86AVXABILevel::None",
+ "AVX ABI level for the x86_64 target",
+ [{::llvm::cl::values(
+ clEnumValN(llvm::abi::X86AVXABILevel::None, "none", "No AVX"),
+ clEnumValN(llvm::abi::X86AVXABILevel::AVX, "avx", "AVX"),
+ clEnumValN(llvm::abi::X86AVXABILevel::AVX512, "avx512", "AVX512")
+ )}]>,
];
}
diff --git a/clang/lib/CIR/Dialect/IR/CIRTypes.cpp b/clang/lib/CIR/Dialect/IR/CIRTypes.cpp
index afd3364af4ec2..1ab68de025ca4 100644
--- a/clang/lib/CIR/Dialect/IR/CIRTypes.cpp
+++ b/clang/lib/CIR/Dialect/IR/CIRTypes.cpp
@@ -23,6 +23,7 @@
#include "clang/CIR/Dialect/IR/CIROpsEnums.h"
#include "clang/CIR/Dialect/IR/CIRTypesDetails.h"
#include "clang/CIR/MissingFeatures.h"
+#include "llvm/ADT/APFloat.h"
#include "llvm/ADT/APInt.h"
#include "llvm/ADT/APSInt.h"
#include "llvm/ADT/TypeSwitch.h"
@@ -38,6 +39,17 @@ bool cir::isSized(mlir::Type ty) {
return false;
}
+mlir::Type cir::getFloatingPointType(const llvm::fltSemantics &sem,
+ mlir::MLIRContext *ctx) {
+ if (&sem == &llvm::APFloat::IEEEsingle())
+ return cir::SingleType::get(ctx);
+ if (&sem == &llvm::APFloat::IEEEdouble())
+ return cir::DoubleType::get(ctx);
+ llvm_unreachable("getFloatingPointType: floating-point semantics not yet "
+ "handled; add a case here as new float kinds become "
+ "supported");
+}
+
//===----------------------------------------------------------------------===//
// CIR Custom Parser/Printer Signatures
//===----------------------------------------------------------------------===//
diff --git a/clang/lib/CIR/Dialect/Transforms/CallConvLoweringPass.cpp b/clang/lib/CIR/Dialect/Transforms/CallConvLoweringPass.cpp
index 987a6e42e43ad..0396a3444c25e 100644
--- a/clang/lib/CIR/Dialect/Transforms/CallConvLoweringPass.cpp
+++ b/clang/lib/CIR/Dialect/Transforms/CallConvLoweringPass.cpp
@@ -102,13 +102,8 @@ static mlir::Type abiTypeToCIR(const llvm::abi::Type *ty, MLIRContext *ctx) {
return cir::IntType::get(ctx, intTy->getSizeInBits().getFixedValue(),
intTy->isSigned());
})
- .Case([&](const llvm::abi::FloatType *fltTy) -> mlir::Type {
- const llvm::fltSemantics *sem = fltTy->getSemantics();
- if (sem == &llvm::APFloat::IEEEsingle())
- return cir::SingleType::get(ctx);
- if (sem == &llvm::APFloat::IEEEdouble())
- return cir::DoubleType::get(ctx);
- return nullptr;
+ .Case([&](const llvm::abi::FloatType *fltTy) {
+ return cir::getFloatingPointType(*fltTy->getSemantics(), ctx);
})
.Case([&](const llvm::abi::PointerType *) {
return cir::PointerType::get(cir::VoidType::get(ctx));
@@ -252,29 +247,29 @@ classifyX86_64Function(cir::FuncOp func, const DataLayout &dl,
/// The classifier targets this pass drives internally via a shared
/// ABITypeMapper/TargetInfo. (The classification-attr injection mode is
/// orthogonal -- it names an arbitrary attribute, not a fixed target -- and
-/// stays string-keyed.) The name/enum pairs below are the single source of
-/// truth for both parsing the `target` pass option and the unknown-target
-/// diagnostic in classifyFunction.
-enum class CallConvTarget { Test, X86_64 };
-
-const std::pair<llvm::StringRef, CallConvTarget> kCallConvTargets[] = {
- {"test", CallConvTarget::Test},
- {"x86_64", CallConvTarget::X86_64},
-};
+/// stays string-keyed.) kCallConvTargetNames is the single source of truth
+/// for both parsing the `target` pass option and the unknown-target
+/// diagnostic in classifyFunction; it is indexed by the enum value below.
+enum class CallConvTarget { Test, X86_64, Last = X86_64 };
+
+constexpr llvm::StringRef kCallConvTargetNames[] = {"test", "x86_64"};
+static_assert(std::size(kCallConvTargetNames) ==
+ static_cast<size_t>(CallConvTarget::Last) + 1,
+ "kCallConvTargetNames must have one entry per CallConvTarget");
std::optional<CallConvTarget> parseCallConvTarget(StringRef target) {
- for (const auto &[name, value] : kCallConvTargets)
- if (target == name)
- return value;
+ for (size_t i = 0; i < std::size(kCallConvTargetNames); ++i)
+ if (target == kCallConvTargetNames[i])
+ return static_cast<CallConvTarget>(i);
return std::nullopt;
}
std::string supportedCallConvTargets() {
std::string result;
- for (const auto &entry : kCallConvTargets) {
+ for (llvm::StringRef name : kCallConvTargetNames) {
if (!result.empty())
result += ", ";
- result += entry.first.str();
+ result += name.str();
}
return result;
}
@@ -373,11 +368,9 @@ void CallConvLoweringPass::runOnOperation() {
std::unique_ptr<llvm::abi::TargetInfo> x86Target;
if (parseCallConvTarget(target) == CallConvTarget::X86_64) {
x86TypeMapper.emplace(dl);
- auto avx =
- static_cast<llvm::abi::X86AVXABILevel>(x86AvxAbiLevel.getValue());
x86Target = llvm::abi::createX86_64TargetInfo(
- x86TypeMapper->getTypeBuilder(), avx, /*Has64BitPointers=*/true,
- llvm::abi::ABICompatInfo());
+ x86TypeMapper->getTypeBuilder(), x86AvxAbiLevel.getValue(),
+ /*Has64BitPointers=*/true, llvm::abi::ABICompatInfo());
}
// Classify every cir.func up front. No IR mutation happens here, so
@@ -473,7 +466,7 @@ std::unique_ptr<Pass> mlir::createCallConvLoweringPass() {
std::unique_ptr<Pass>
mlir::createCallConvLoweringPass(llvm::StringRef target,
- unsigned x86AvxAbiLevel) {
+ llvm::abi::X86AVXABILevel x86AvxAbiLevel) {
CallConvLoweringOptions options;
options.target = target.str();
options.x86AvxAbiLevel = x86AvxAbiLevel;
diff --git a/clang/lib/CIR/Dialect/Transforms/PassDetail.h b/clang/lib/CIR/Dialect/Transforms/PassDetail.h
index ef42a85cc2751..cc585fdec7865 100644
--- a/clang/lib/CIR/Dialect/Transforms/PassDetail.h
+++ b/clang/lib/CIR/Dialect/Transforms/PassDetail.h
@@ -11,6 +11,7 @@
#include "mlir/IR/Dialect.h"
#include "mlir/Pass/Pass.h"
+#include "llvm/ABI/TargetInfo.h"
namespace cir {
class CIRDialect;
>From 4c619e3a0fab2123515c4b3c97bf53afc5c2bd5d Mon Sep 17 00:00:00 2001
From: Adam Smith <adams at nvidia.com>
Date: Fri, 17 Jul 2026 08:47:32 -0700
Subject: [PATCH 4/5] [CIR] Refine x86_64 scalar calling-convention lowering
This narrows cir::getFloatingPointType to return cir::FPTypeInterface
rather than a generic mlir::Type, since it only ever returns a CIR
floating-point type.
A pointer whose address space isn't the default or an already-lowered
target address space is now rejected with an NYI diagnostic instead of
silently coercing a LangAddressSpaceAttr to address space 0. Nothing
lowers those to a target address space before this pass yet, so a visible
failure is better than a wrong result.
The `target` pass option is a CallConvTarget enum with clEnumValN now, so
the option machinery parses the name straight into the enum. This drops
the parallel name table and the hand-rolled parsing that had to be kept in
sync with the enum.
An unexpected ArgInfo classification is an assert(isIgnore()) rather than a
llvm_unreachable on Indirect, so a future classification kind can't slip
through unnoticed.
This drops safeAlign now that IntType::getABIAlignment returns a
power-of-two alignment (#210187); the DataLayout alignment goes straight to
llvm::Align.
---
clang/include/clang/CIR/Dialect/IR/CIRTypes.h | 4 +-
clang/include/clang/CIR/Dialect/Passes.h | 9 +-
clang/include/clang/CIR/Dialect/Passes.td | 11 ++-
clang/lib/CIR/Dialect/IR/CIRTypes.cpp | 4 +-
.../Transforms/CallConvLoweringPass.cpp | 86 ++++++-------------
clang/lib/CIR/Dialect/Transforms/PassDetail.h | 1 +
.../x86_64-lang-addrspace-nyi.cir | 18 ++++
7 files changed, 66 insertions(+), 67 deletions(-)
create mode 100644 clang/test/CIR/Transforms/abi-lowering/x86_64-lang-addrspace-nyi.cir
diff --git a/clang/include/clang/CIR/Dialect/IR/CIRTypes.h b/clang/include/clang/CIR/Dialect/IR/CIRTypes.h
index ccae66e774bbf..bb0e9b080c578 100644
--- a/clang/include/clang/CIR/Dialect/IR/CIRTypes.h
+++ b/clang/include/clang/CIR/Dialect/IR/CIRTypes.h
@@ -49,8 +49,8 @@ bool isSized(mlir::Type ty);
/// Returns the CIR floating-point type for a given semantics. Mirrors
/// llvm::Type::getFloatingPointTy; currently only covers the semantics
/// CIR's scalar ABI classifier can reach (f32/f64).
-mlir::Type getFloatingPointType(const llvm::fltSemantics &sem,
- mlir::MLIRContext *ctx);
+cir::FPTypeInterface getFloatingPointType(const llvm::fltSemantics &sem,
+ mlir::MLIRContext *ctx);
//===----------------------------------------------------------------------===//
// AddressSpace helpers
diff --git a/clang/include/clang/CIR/Dialect/Passes.h b/clang/include/clang/CIR/Dialect/Passes.h
index 9ac2b4a018b10..0b8142fc394bd 100644
--- a/clang/include/clang/CIR/Dialect/Passes.h
+++ b/clang/include/clang/CIR/Dialect/Passes.h
@@ -16,6 +16,13 @@
#include "mlir/Pass/Pass.h"
#include "llvm/ABI/TargetInfo.h"
+namespace cir {
+/// The ABI target whose calling-convention rules drive CallConvLowering.
+/// None is the unset state used when the pass runs in classification-attr
+/// mode instead of selecting a target.
+enum class CallConvTarget { None, Test, X86_64 };
+} // namespace cir
+
namespace clang {
class ASTContext;
}
@@ -30,7 +37,7 @@ std::unique_ptr<Pass> createCXXABILoweringPass();
std::unique_ptr<Pass> createTargetLoweringPass();
std::unique_ptr<Pass> createCallConvLoweringPass();
std::unique_ptr<Pass>
-createCallConvLoweringPass(llvm::StringRef target,
+createCallConvLoweringPass(cir::CallConvTarget target,
llvm::abi::X86AVXABILevel x86AvxAbiLevel);
std::unique_ptr<Pass> createHoistAllocasPass();
std::unique_ptr<Pass> createLoweringPreparePass();
diff --git a/clang/include/clang/CIR/Dialect/Passes.td b/clang/include/clang/CIR/Dialect/Passes.td
index 6ba5367163636..04ff1c92cb7f7 100644
--- a/clang/include/clang/CIR/Dialect/Passes.td
+++ b/clang/include/clang/CIR/Dialect/Passes.td
@@ -239,8 +239,15 @@ def CallConvLowering : Pass<"cir-call-conv-lowering", "mlir::ModuleOp"> {
let constructor = "mlir::createCallConvLoweringPass()";
let dependentDialects = ["cir::CIRDialect"];
let options = [
- Option<"target", "target", "std::string", /*default=*/"\"\"",
- "Target whose ABI rules drive classification (test, x86_64)">,
+ Option<"target", "target", "cir::CallConvTarget",
+ /*default=*/"cir::CallConvTarget::None",
+ "Target whose ABI rules drive classification",
+ [{::llvm::cl::values(
+ clEnumValN(cir::CallConvTarget::Test, "test",
+ "MLIR test ABI target"),
+ clEnumValN(cir::CallConvTarget::X86_64, "x86_64",
+ "x86_64 System V")
+ )}]>,
Option<"classificationAttr", "classification-attr", "std::string",
/*default=*/"\"\"",
"Function attribute name carrying a pre-built FunctionClassification "
diff --git a/clang/lib/CIR/Dialect/IR/CIRTypes.cpp b/clang/lib/CIR/Dialect/IR/CIRTypes.cpp
index 1ab68de025ca4..6fffc64e8f7cc 100644
--- a/clang/lib/CIR/Dialect/IR/CIRTypes.cpp
+++ b/clang/lib/CIR/Dialect/IR/CIRTypes.cpp
@@ -39,8 +39,8 @@ bool cir::isSized(mlir::Type ty) {
return false;
}
-mlir::Type cir::getFloatingPointType(const llvm::fltSemantics &sem,
- mlir::MLIRContext *ctx) {
+cir::FPTypeInterface cir::getFloatingPointType(const llvm::fltSemantics &sem,
+ mlir::MLIRContext *ctx) {
if (&sem == &llvm::APFloat::IEEEsingle())
return cir::SingleType::get(ctx);
if (&sem == &llvm::APFloat::IEEEdouble())
diff --git a/clang/lib/CIR/Dialect/Transforms/CallConvLoweringPass.cpp b/clang/lib/CIR/Dialect/Transforms/CallConvLoweringPass.cpp
index 0396a3444c25e..bb5b194ee0433 100644
--- a/clang/lib/CIR/Dialect/Transforms/CallConvLoweringPass.cpp
+++ b/clang/lib/CIR/Dialect/Transforms/CallConvLoweringPass.cpp
@@ -72,19 +72,18 @@ namespace {
// so an unsupported signature fails the pass instead of being misclassified.
//===----------------------------------------------------------------------===//
-/// llvm::Align requires a power of two; DataLayout can report non-power-of-two
-/// alignments for unusual types.
-static llvm::Align safeAlign(uint64_t a) {
- return llvm::Align(llvm::PowerOf2Ceil(std::max<uint64_t>(a, 1)));
-}
-
/// The scalar CIR types the x86_64 bridge handles. A regular integer up to
/// 64 bits, pointer, bool, void, f32, or f64 is a single-register Direct or
/// Extend argument. `_BitInt`, `__int128`, and wider/other types need coercion
/// or indirect passing, which this scalar bridge does not do.
static bool isSupportedScalarType(mlir::Type ty) {
- if (isa<cir::VoidType, cir::BoolType, cir::PointerType, cir::SingleType,
- cir::DoubleType>(ty))
+ // A pointer is only handled in the default address space (null) or an
+ // already-lowered target address space. A LangAddressSpaceAttr must be
+ // lowered before this pass, so reject it rather than silently dropping it.
+ if (auto ptrTy = dyn_cast<cir::PointerType>(ty))
+ return !ptrTy.getAddrSpace() ||
+ mlir::isa<cir::TargetAddressSpaceAttr>(ptrTy.getAddrSpace());
+ if (isa<cir::VoidType, cir::BoolType, cir::SingleType, cir::DoubleType>(ty))
return true;
if (auto intTy = dyn_cast<cir::IntType>(ty))
return !intTy.getIsBitInt() && intTy.getWidth() <= 64;
@@ -121,7 +120,7 @@ static const llvm::abi::Type *mapCIRType(mlir::Type type,
return llvm::TypeSwitch<mlir::Type, const llvm::abi::Type *>(type)
.Case([&](cir::IntType intTy) {
return tb.getIntegerType(intTy.getWidth(),
- safeAlign(dl.getTypeABIAlignment(type)),
+ llvm::Align(dl.getTypeABIAlignment(type)),
intTy.isSigned());
})
.Case([&](cir::PointerType ptrTy) {
@@ -131,22 +130,22 @@ static const llvm::abi::Type *mapCIRType(mlir::Type type,
ptrTy.getAddrSpace()))
addrSpace = targetAsAttr.getValue();
return tb.getPointerType(dl.getTypeSizeInBits(type),
- safeAlign(dl.getTypeABIAlignment(type)),
+ llvm::Align(dl.getTypeABIAlignment(type)),
addrSpace);
})
.Case([&](cir::BoolType) {
return tb.getIntegerType(dl.getTypeSizeInBits(type),
- safeAlign(dl.getTypeABIAlignment(type)),
+ llvm::Align(dl.getTypeABIAlignment(type)),
/*Signed=*/false);
})
.Case([&](cir::VoidType) { return tb.getVoidType(); })
.Case([&](cir::SingleType) {
return tb.getFloatType(llvm::APFloat::IEEEsingle(),
- safeAlign(dl.getTypeABIAlignment(type)));
+ llvm::Align(dl.getTypeABIAlignment(type)));
})
.Case([&](cir::DoubleType) {
return tb.getFloatType(llvm::APFloat::IEEEdouble(),
- safeAlign(dl.getTypeABIAlignment(type)));
+ llvm::Align(dl.getTypeABIAlignment(type)));
})
.Default([](mlir::Type) -> const llvm::abi::Type * {
llvm_unreachable(
@@ -188,9 +187,7 @@ static ArgClassification convertABIArgInfo(const llvm::abi::ArgInfo &info,
mlir::Type extendedTy = abiTypeToCIR(info.getCoerceToType(), ctx);
return ArgClassification::getExtend(extendedTy, info.isSignExt());
}
- if (info.isIndirect())
- llvm_unreachable("Indirect classification is impossible for the "
- "scalar-only type set this bridge accepts");
+ assert(info.isIgnore() && "Unexpected classification");
return ArgClassification::getIgnore();
}
@@ -244,36 +241,6 @@ classifyX86_64Function(cir::FuncOp func, const DataLayout &dl,
return fc;
}
-/// The classifier targets this pass drives internally via a shared
-/// ABITypeMapper/TargetInfo. (The classification-attr injection mode is
-/// orthogonal -- it names an arbitrary attribute, not a fixed target -- and
-/// stays string-keyed.) kCallConvTargetNames is the single source of truth
-/// for both parsing the `target` pass option and the unknown-target
-/// diagnostic in classifyFunction; it is indexed by the enum value below.
-enum class CallConvTarget { Test, X86_64, Last = X86_64 };
-
-constexpr llvm::StringRef kCallConvTargetNames[] = {"test", "x86_64"};
-static_assert(std::size(kCallConvTargetNames) ==
- static_cast<size_t>(CallConvTarget::Last) + 1,
- "kCallConvTargetNames must have one entry per CallConvTarget");
-
-std::optional<CallConvTarget> parseCallConvTarget(StringRef target) {
- for (size_t i = 0; i < std::size(kCallConvTargetNames); ++i)
- if (target == kCallConvTargetNames[i])
- return static_cast<CallConvTarget>(i);
- return std::nullopt;
-}
-
-std::string supportedCallConvTargets() {
- std::string result;
- for (llvm::StringRef name : kCallConvTargetNames) {
- if (!result.empty())
- result += ", ";
- result += name.str();
- }
- return result;
-}
-
bool needsRewrite(const FunctionClassification &fc) {
if ((fc.returnInfo.kind != ArgKind::Direct) || fc.returnInfo.coercedType)
return true;
@@ -294,8 +261,8 @@ struct CallConvLoweringPass
/// (e.g. injection-driver mode but the function is missing the attribute,
/// or the attribute is malformed).
std::optional<FunctionClassification>
-classifyFunction(cir::FuncOp func, const DataLayout &dl, StringRef target,
- StringRef classificationAttrName) {
+classifyFunction(cir::FuncOp func, const DataLayout &dl,
+ cir::CallConvTarget target, StringRef classificationAttrName) {
ArrayRef<Type> argTypes = func.getFunctionType().getInputs();
Type returnType = func.getFunctionType().getReturnType();
@@ -311,14 +278,11 @@ classifyFunction(cir::FuncOp func, const DataLayout &dl, StringRef target,
attr, [&]() { return func.emitOpError(); });
}
- if (parseCallConvTarget(target) == CallConvTarget::Test)
- return mlir::abi::test::classify(argTypes, returnType, dl);
-
- // Note: the "x86_64" target is handled directly in runOnOperation (it needs
- // a shared ABITypeMapper and TargetInfo), so it never reaches here.
- func.emitOpError() << "unknown target '" << target
- << "' (supported: " << supportedCallConvTargets() << ")";
- return std::nullopt;
+ // The x86_64 target is handled directly in runOnOperation (it needs a shared
+ // ABITypeMapper and TargetInfo), so only the test target reaches here.
+ assert(target == cir::CallConvTarget::Test &&
+ "classifyFunction only handles the test target");
+ return mlir::abi::test::classify(argTypes, returnType, dl);
}
/// Find the cir.func declaration matching a direct cir.call / cir.try_call
@@ -343,7 +307,9 @@ void CallConvLoweringPass::runOnOperation() {
ModuleOp moduleOp = getOperation();
MLIRContext *ctx = &getContext();
- if (target.empty() == classificationAttr.empty()) {
+ bool haveTarget = target != cir::CallConvTarget::None;
+ bool haveAttr = !classificationAttr.empty();
+ if (haveTarget == haveAttr) {
moduleOp.emitOpError() << "CallConvLowering requires exactly one of "
"'target' or 'classification-attr' pass options";
signalPassFailure();
@@ -366,7 +332,7 @@ void CallConvLoweringPass::runOnOperation() {
// reuse it (and its type mapper) across every function.
std::optional<mlir::abi::ABITypeMapper> x86TypeMapper;
std::unique_ptr<llvm::abi::TargetInfo> x86Target;
- if (parseCallConvTarget(target) == CallConvTarget::X86_64) {
+ if (target == cir::CallConvTarget::X86_64) {
x86TypeMapper.emplace(dl);
x86Target = llvm::abi::createX86_64TargetInfo(
x86TypeMapper->getTypeBuilder(), x86AvxAbiLevel.getValue(),
@@ -465,10 +431,10 @@ std::unique_ptr<Pass> mlir::createCallConvLoweringPass() {
}
std::unique_ptr<Pass>
-mlir::createCallConvLoweringPass(llvm::StringRef target,
+mlir::createCallConvLoweringPass(cir::CallConvTarget target,
llvm::abi::X86AVXABILevel x86AvxAbiLevel) {
CallConvLoweringOptions options;
- options.target = target.str();
+ options.target = target;
options.x86AvxAbiLevel = x86AvxAbiLevel;
return std::make_unique<CallConvLoweringPass>(options);
}
diff --git a/clang/lib/CIR/Dialect/Transforms/PassDetail.h b/clang/lib/CIR/Dialect/Transforms/PassDetail.h
index cc585fdec7865..b7703cf4a08b9 100644
--- a/clang/lib/CIR/Dialect/Transforms/PassDetail.h
+++ b/clang/lib/CIR/Dialect/Transforms/PassDetail.h
@@ -11,6 +11,7 @@
#include "mlir/IR/Dialect.h"
#include "mlir/Pass/Pass.h"
+#include "clang/CIR/Dialect/Passes.h"
#include "llvm/ABI/TargetInfo.h"
namespace cir {
diff --git a/clang/test/CIR/Transforms/abi-lowering/x86_64-lang-addrspace-nyi.cir b/clang/test/CIR/Transforms/abi-lowering/x86_64-lang-addrspace-nyi.cir
new file mode 100644
index 0000000000000..a8350b421757d
--- /dev/null
+++ b/clang/test/CIR/Transforms/abi-lowering/x86_64-lang-addrspace-nyi.cir
@@ -0,0 +1,18 @@
+// RUN: not cir-opt %s -cir-call-conv-lowering=target=x86_64 2>&1 | FileCheck %s
+
+!s32i = !cir.int<s, 32>
+
+module attributes {
+ dlti.dl_spec = #dlti.dl_spec<
+ #dlti.dl_entry<i32, dense<32>: vector<2xi64>>,
+ #dlti.dl_entry<i64, dense<64>: vector<2xi64>>>
+} {
+
+ cir.func @lang_addrspace_ptr(
+ %arg0: !cir.ptr<!s32i, lang_address_space(offload_global)>) {
+ cir.return
+ }
+
+}
+
+// CHECK: op x86_64 calling-convention lowering not yet implemented for type
>From a91ee5699a5a1a84117c2a450a209dcbdbc728e1 Mon Sep 17 00:00:00 2001
From: Adam Smith <adams at nvidia.com>
Date: Fri, 17 Jul 2026 13:16:43 -0700
Subject: [PATCH 5/5] [CIR] Cover all CIR float types in
cir::getFloatingPointType
This turns cir::getFloatingPointType into a switch over
llvm::APFloat::SemanticsToEnum and maps every floating-point type CIR has
(FP16, BF16, single, double, FP80, FP128), rather than only the f32/f64
that the scalar calling-convention classifier happens to use.
For a semantics CIR has no type for (PPCDoubleDouble, the Float8 formats)
it returns a null type instead of llvm_unreachable. This is a shared
helper in a general location, so a caller that reaches an unhandled kind
should get a null it can diagnose rather than a crash.
A unit test covers the six mappings and the null result.
---
clang/include/clang/CIR/Dialect/IR/CIRTypes.h | 6 +--
clang/lib/CIR/Dialect/IR/CIRTypes.cpp | 21 ++++++--
clang/unittests/CIR/CMakeLists.txt | 1 +
.../CIR/GetFloatingPointTypeTest.cpp | 53 +++++++++++++++++++
4 files changed, 73 insertions(+), 8 deletions(-)
create mode 100644 clang/unittests/CIR/GetFloatingPointTypeTest.cpp
diff --git a/clang/include/clang/CIR/Dialect/IR/CIRTypes.h b/clang/include/clang/CIR/Dialect/IR/CIRTypes.h
index bb0e9b080c578..f72d10d236612 100644
--- a/clang/include/clang/CIR/Dialect/IR/CIRTypes.h
+++ b/clang/include/clang/CIR/Dialect/IR/CIRTypes.h
@@ -46,9 +46,9 @@ bool isValidFundamentalIntWidth(unsigned width);
/// void, or abstract types.
bool isSized(mlir::Type ty);
-/// Returns the CIR floating-point type for a given semantics. Mirrors
-/// llvm::Type::getFloatingPointTy; currently only covers the semantics
-/// CIR's scalar ABI classifier can reach (f32/f64).
+/// Returns the CIR floating-point type for the given semantics, or a null
+/// type if CIR has no type for it (e.g. PPCDoubleDouble or a Float8 format).
+/// Mirrors llvm::Type::getFloatingPointTy.
cir::FPTypeInterface getFloatingPointType(const llvm::fltSemantics &sem,
mlir::MLIRContext *ctx);
diff --git a/clang/lib/CIR/Dialect/IR/CIRTypes.cpp b/clang/lib/CIR/Dialect/IR/CIRTypes.cpp
index 6fffc64e8f7cc..59930749ccff0 100644
--- a/clang/lib/CIR/Dialect/IR/CIRTypes.cpp
+++ b/clang/lib/CIR/Dialect/IR/CIRTypes.cpp
@@ -41,13 +41,24 @@ bool cir::isSized(mlir::Type ty) {
cir::FPTypeInterface cir::getFloatingPointType(const llvm::fltSemantics &sem,
mlir::MLIRContext *ctx) {
- if (&sem == &llvm::APFloat::IEEEsingle())
+ switch (llvm::APFloat::SemanticsToEnum(sem)) {
+ case llvm::APFloat::S_IEEEhalf:
+ return cir::FP16Type::get(ctx);
+ case llvm::APFloat::S_BFloat:
+ return cir::BF16Type::get(ctx);
+ case llvm::APFloat::S_IEEEsingle:
return cir::SingleType::get(ctx);
- if (&sem == &llvm::APFloat::IEEEdouble())
+ case llvm::APFloat::S_IEEEdouble:
return cir::DoubleType::get(ctx);
- llvm_unreachable("getFloatingPointType: floating-point semantics not yet "
- "handled; add a case here as new float kinds become "
- "supported");
+ case llvm::APFloat::S_x87DoubleExtended:
+ return cir::FP80Type::get(ctx);
+ case llvm::APFloat::S_IEEEquad:
+ return cir::FP128Type::get(ctx);
+ default:
+ // CIR has no type for the remaining semantics (PPCDoubleDouble, the
+ // Float8 formats). Return null and let the caller report it.
+ return {};
+ }
}
//===----------------------------------------------------------------------===//
diff --git a/clang/unittests/CIR/CMakeLists.txt b/clang/unittests/CIR/CMakeLists.txt
index 446ca36fc39b0..93adf199486a9 100644
--- a/clang/unittests/CIR/CMakeLists.txt
+++ b/clang/unittests/CIR/CMakeLists.txt
@@ -6,6 +6,7 @@ include_directories(${MLIR_TABLEGEN_OUTPUT_DIR})
add_distinct_clang_unittest(CIRUnitTests
CallOpTest.cpp
ControlFlowTest.cpp
+ GetFloatingPointTypeTest.cpp
IntTypeABIAlignTest.cpp
PointerLikeTest.cpp
RecordTypeMetadataTest.cpp
diff --git a/clang/unittests/CIR/GetFloatingPointTypeTest.cpp b/clang/unittests/CIR/GetFloatingPointTypeTest.cpp
new file mode 100644
index 0000000000000..de9f34e1c0539
--- /dev/null
+++ b/clang/unittests/CIR/GetFloatingPointTypeTest.cpp
@@ -0,0 +1,53 @@
+//===----------------------------------------------------------------------===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+//
+// Unit tests for cir::getFloatingPointType.
+//
+//===----------------------------------------------------------------------===//
+
+#include "mlir/IR/MLIRContext.h"
+#include "clang/CIR/Dialect/IR/CIRDialect.h"
+#include "clang/CIR/Dialect/IR/CIRTypes.h"
+#include "llvm/ADT/APFloat.h"
+#include "gtest/gtest.h"
+
+using namespace mlir;
+using namespace cir;
+
+namespace {
+
+class GetFloatingPointTypeTest : public ::testing::Test {
+protected:
+ GetFloatingPointTypeTest() { context.loadDialect<cir::CIRDialect>(); }
+
+ MLIRContext context;
+};
+
+// Every floating-point semantics CIR has a type for maps to that type.
+TEST_F(GetFloatingPointTypeTest, MapsSupportedSemantics) {
+ EXPECT_TRUE(mlir::isa<cir::FP16Type>(
+ getFloatingPointType(llvm::APFloat::IEEEhalf(), &context)));
+ EXPECT_TRUE(mlir::isa<cir::BF16Type>(
+ getFloatingPointType(llvm::APFloat::BFloat(), &context)));
+ EXPECT_TRUE(mlir::isa<cir::SingleType>(
+ getFloatingPointType(llvm::APFloat::IEEEsingle(), &context)));
+ EXPECT_TRUE(mlir::isa<cir::DoubleType>(
+ getFloatingPointType(llvm::APFloat::IEEEdouble(), &context)));
+ EXPECT_TRUE(mlir::isa<cir::FP80Type>(
+ getFloatingPointType(llvm::APFloat::x87DoubleExtended(), &context)));
+ EXPECT_TRUE(mlir::isa<cir::FP128Type>(
+ getFloatingPointType(llvm::APFloat::IEEEquad(), &context)));
+}
+
+// A semantics CIR has no type for returns a null type rather than asserting.
+TEST_F(GetFloatingPointTypeTest, ReturnsNullForUnsupportedSemantics) {
+ EXPECT_FALSE(
+ getFloatingPointType(llvm::APFloat::PPCDoubleDouble(), &context));
+}
+
+} // namespace
More information about the cfe-commits
mailing list