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 <[email protected]> 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 <[email protected]> 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 <[email protected]> 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 <[email protected]> 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 <[email protected]> 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 _______________________________________________ cfe-commits mailing list [email protected] https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits
