[clang] [CIR] Add x86_64 scalar calling-convention lowering (PR #209636)
via cfe-commits
cfe-commits at lists.llvm.org
Wed Jul 15 15:00:53 PDT 2026
https://github.com/adams381 updated https://github.com/llvm/llvm-project/pull/209636
>From 63848c2cc05a9597f7fe44130217890b4d3fc05f 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/2] [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 651a1319cfe5d..c655c2adf402b 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 4d5b6499120bff25eb71f28df33103e0c173d8a4 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/2] [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()
More information about the cfe-commits
mailing list