https://github.com/andykaylor updated https://github.com/llvm/llvm-project/pull/233639
>From 99f8798386baef826b6c4cacad64cee7368bd348 Mon Sep 17 00:00:00 2001 From: Andy Kaylor <[email protected]> Date: Mon, 19 Oct 2026 15:09:44 -0700 Subject: [PATCH 1/2] [CIR][NFC] Refactor calling convention lowering classification This change refactors the CIR calling convention lowering pass to move classification into a `CIRABIClassifier` class and creates a subclass of that to perform target-specific handling. In order to make review easier, I have left as much code as possible in its previous location. I'll follow up with another PR that moves definitions into the file where is logically belongs. Assisted-by: Cursor / claude-opus-5.5 --- clang/docs/CIR/ABILowering.md | 5 + .../Dialect/Transforms/CIRABIClassifier.cpp | 144 ++++++++++ .../CIR/Dialect/Transforms/CIRABIClassifier.h | 195 ++++++++++++++ .../Transforms/CIRABIClassifierX86_64.cpp | 86 ++++++ .../lib/CIR/Dialect/Transforms/CMakeLists.txt | 2 + .../Transforms/CallConvLoweringPass.cpp | 245 +++++++----------- .../x86_64-lang-addrspace-nyi.cir | 2 +- .../abi-lowering/x86_64-variadic-nyi.cir | 2 +- 8 files changed, 527 insertions(+), 154 deletions(-) create mode 100644 clang/lib/CIR/Dialect/Transforms/CIRABIClassifier.cpp create mode 100644 clang/lib/CIR/Dialect/Transforms/CIRABIClassifier.h create mode 100644 clang/lib/CIR/Dialect/Transforms/CIRABIClassifierX86_64.cpp diff --git a/clang/docs/CIR/ABILowering.md b/clang/docs/CIR/ABILowering.md index bcd628ca8ddda6..8d9a525e98e2e2 100644 --- a/clang/docs/CIR/ABILowering.md +++ b/clang/docs/CIR/ABILowering.md @@ -535,6 +535,11 @@ The pass takes one of two driver modes via pass options: Exactly one of the two options must be set. +Each driver is a `CIRABIClassifier` subclass under +`clang/lib/CIR/Dialect/Transforms/`. The pass asks the classifier to classify +each function, each call that passes arguments through an ellipsis, each +indirect call, and each `cir.va_arg`. + ### Enabling the Pass `cir-call-conv-lowering` runs by default whenever ClangIR code generation is diff --git a/clang/lib/CIR/Dialect/Transforms/CIRABIClassifier.cpp b/clang/lib/CIR/Dialect/Transforms/CIRABIClassifier.cpp new file mode 100644 index 00000000000000..bae1bafd5580c8 --- /dev/null +++ b/clang/lib/CIR/Dialect/Transforms/CIRABIClassifier.cpp @@ -0,0 +1,144 @@ +//===- CIRABIClassifier.cpp - ABI classification for CIR ------------------===// +// +// 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 +// +//===----------------------------------------------------------------------===// +// +// This file implements the classification shared by the targets that classify +// through the LLVM ABI Lowering Library, and the two test drivers that stand +// in for a target. +// +//===----------------------------------------------------------------------===// + +#include "CIRABIClassifier.h" +#include "TargetLowering/CIRABIRewriteContext.h" + +#include "mlir/ABI/Targets/Test/TestTarget.h" +#include "clang/CIR/Dialect/IR/CIRDialect.h" + +using namespace mlir; +using namespace mlir::abi; +using namespace cir; + +std::optional<FunctionClassification> +CIRABIClassifierCommon::classifyVariadicCall(cir::CIRCallOpInterface call, + cir::FuncOp callee) { + return classifyVariadicCall(call, callee.getFunctionType(), + getTargetInfo(callee)); +} + +std::optional<FunctionClassification> +CIRABIClassifierCommon::classifyIndirectCall(cir::CIRCallOpInterface call) { + cir::FuncType funcTy = getIndirectCalleeType(call); + const llvm::abi::TargetInfo &targetInfo = + getTargetInfo(call->getParentOfType<cir::FuncOp>()); + if (call.getNumArgOperands() > funcTy.getNumInputs()) + return classifyVariadicCall(call, funcTy, targetInfo); + return classifySignature(funcTy.getReturnType(), funcTy.getInputs(), + requiredArgs(funcTy), targetInfo, + [&]() { return call->emitOpError(); }); +} + +namespace { + +/// Classifies with the MLIR test ABI target: predictable rules that +/// approximate x86_64 SysV, used by tests that need a target-independent +/// driver. +class TestTargetClassifier final : public CIRABIClassifier { +public: + explicit TestTargetClassifier(const mlir::DataLayout &dl) : dl(dl) {} + + std::optional<FunctionClassification> + classifyFunction(cir::FuncOp func) final { + return cir::classifyFunction(func, dl, cir::CallConvTarget::Test, + /*classificationAttrName=*/""); + } + + /// The test target classifies only declared parameters. + std::optional<FunctionClassification> + classifyVariadicCall(cir::CIRCallOpInterface call, cir::FuncOp) final { + call->emitOpError() + << "variadic arguments not yet implemented in CallConvLowering"; + return std::nullopt; + } + + std::optional<FunctionClassification> + classifyIndirectCall(cir::CIRCallOpInterface call) final { + cir::FuncType funcTy = getIndirectCalleeType(call); + if (call.getNumArgOperands() > funcTy.getNumInputs()) { + call->emitOpError() + << "variadic arguments not yet implemented in CallConvLowering"; + return std::nullopt; + } + return withReturnVoidness(mlir::abi::test::classify(funcTy.getInputs(), + funcTy.getReturnType(), + dl), + funcTy.getReturnType()); + } + + std::optional<ArgClassification> classifyVAArg(cir::VAArgOp vaArg) final { + vaArg->emitOpError() + << "not yet implemented in CallConvLowering for the test target"; + return std::nullopt; + } + +private: + const mlir::DataLayout &dl; +}; + +/// Reads a pre-built classification from a function attribute, so a test can +/// inject any classification (including shapes no target produces) without +/// depending on a real ABI target. +class InjectedClassifier final : public CIRABIClassifier { +public: + InjectedClassifier(const mlir::DataLayout &dl, llvm::StringRef attrName) + : dl(dl), attrName(attrName) {} + + std::optional<FunctionClassification> + classifyFunction(cir::FuncOp func) final { + return cir::classifyFunction(func, dl, cir::CallConvTarget::None, attrName); + } + + /// The injected classification covers a function's declared parameters, so + /// it cannot describe an operand passed through an ellipsis. + std::optional<FunctionClassification> + classifyVariadicCall(cir::CIRCallOpInterface call, cir::FuncOp) final { + call->emitOpError() + << "variadic arguments not yet implemented in CallConvLowering"; + return std::nullopt; + } + + /// Nor can it describe a callee resolved at run time, which names no + /// function to read the attribute from. + std::optional<FunctionClassification> + classifyIndirectCall(cir::CIRCallOpInterface call) final { + call->emitOpError() << "indirect call cannot be classified in the " + "'classification-attr' driver mode"; + return std::nullopt; + } + + std::optional<ArgClassification> classifyVAArg(cir::VAArgOp vaArg) final { + vaArg->emitOpError() << "cannot be classified in the " + "'classification-attr' driver mode"; + return std::nullopt; + } + +private: + const mlir::DataLayout &dl; + std::string attrName; +}; + +} // namespace + +std::unique_ptr<CIRABIClassifier> +cir::createTestTargetABIClassifier(const mlir::DataLayout &dl) { + return std::make_unique<TestTargetClassifier>(dl); +} + +std::unique_ptr<CIRABIClassifier> +cir::createInjectedABIClassifier(const mlir::DataLayout &dl, + llvm::StringRef attrName) { + return std::make_unique<InjectedClassifier>(dl, attrName); +} diff --git a/clang/lib/CIR/Dialect/Transforms/CIRABIClassifier.h b/clang/lib/CIR/Dialect/Transforms/CIRABIClassifier.h new file mode 100644 index 00000000000000..aba0722bd62482 --- /dev/null +++ b/clang/lib/CIR/Dialect/Transforms/CIRABIClassifier.h @@ -0,0 +1,195 @@ +//===- CIRABIClassifier.h - ABI classification for CIR ----------*- C++ -*-===// +// +// 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 +// +//===----------------------------------------------------------------------===// +// +// This file defines CIRABIClassifier, the interface CallConvLowering uses to +// obtain a mlir::abi::FunctionClassification for a CIR signature. +// +// CIRABIClassifier is the producer half of CIR's calling-convention lowering. +// The consumer half is CIRABIRewriteContext, which turns a classification into +// IR and needs to know nothing about where it came from. +// +//===----------------------------------------------------------------------===// + +#ifndef CLANG_LIB_CIR_DIALECT_TRANSFORMS_CIRABICLASSIFIER_H +#define CLANG_LIB_CIR_DIALECT_TRANSFORMS_CIRABICLASSIFIER_H + +#include "mlir/ABI/ABIRewriteContext.h" +#include "mlir/ABI/ABITypeMapper.h" +#include "mlir/IR/BuiltinOps.h" +#include "mlir/IR/Diagnostics.h" +#include "mlir/Interfaces/DataLayoutInterfaces.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/ADT/STLFunctionalExtras.h" +#include "llvm/ADT/StringRef.h" + +#include <memory> +#include <optional> + +namespace cir { + +/// The target-specific settings a classifier needs beyond what it can read +/// from the module. Every field names the target it belongs to, because a +/// classifier for one target ignores the others' settings. +struct ABIClassifierOptions { + // TODO: Make this class target-independent. + + /// The AVX level the x86_64 classifier sizes a native vector at. + llvm::abi::X86AVXABILevel x86AvxAbiLevel = llvm::abi::X86AVXABILevel::None; + + /// Whether a function's own recorded target features may raise its x86_64 + /// AVX level above x86AvxAbiLevel. + bool allowsX86TargetAttrAvx = true; + + /// The x86_64 flags whose value depends on the target and the requested ABI + /// compatibility version. + llvm::abi::X86ABICompatInfo x86AbiCompat; +}; + +/// Produces the classification CallConvLowering rewrites a signature from. +/// +/// A subclass supplies one target's rules, or one of the two test drivers that +/// stand in for a target. The pass holds a single instance for the whole +/// module. Each method returns std::nullopt after emitting an error on the +/// operation it was given, including for an operation the subclass does not +/// support yet. +class CIRABIClassifier { +public: + virtual ~CIRABIClassifier() = default; + + /// Classify \p func's declared signature. + virtual std::optional<mlir::abi::FunctionClassification> + classifyFunction(cir::FuncOp func) = 0; + + /// Classify a direct call to the variadic function \p callee that passes + /// arguments through the ellipsis. The classification covers every operand + /// of \p call, and classifies the declared parameters the same way as + /// classifyFunction(callee). + virtual std::optional<mlir::abi::FunctionClassification> + classifyVariadicCall(cir::CIRCallOpInterface call, cir::FuncOp callee) = 0; + + /// Classify \p call, whose callee is resolved at run time, from the + /// signature its callee pointer points to. + virtual std::optional<mlir::abi::FunctionClassification> + classifyIndirectCall(cir::CIRCallOpInterface call) = 0; + + /// Classify the type fetched by \p vaArg as an unnamed argument with a full + /// register budget. + virtual std::optional<mlir::abi::ArgClassification> + classifyVAArg(cir::VAArgOp vaArg) = 0; +}; + +/// Classifies through the LLVM ABI Lowering Library. The signature's CIR types +/// are mapped to llvm::abi types, the target's TargetInfo classifies them, and +/// each llvm::abi::ArgInfo is converted to an ArgClassification. +/// +/// A subclass supplies the TargetInfo and classifies `cir.va_arg`, whose +/// expansion is specific to each target. +class CIRABIClassifierCommon : public CIRABIClassifier { +public: + std::optional<mlir::abi::FunctionClassification> + classifyFunction(cir::FuncOp func) override; + + /// The call is classified at the callee's features, not the caller's, + /// because the pass rewrites the definition and its call sites from one + /// classification. Classic instead arranges every call site from the + /// caller and reports a caller whose features disagree with its callee in + /// checkFunctionCallABI, which has no equivalent here yet. + std::optional<mlir::abi::FunctionClassification> + classifyVariadicCall(cir::CIRCallOpInterface call, + cir::FuncOp callee) override; + + /// A callee resolved at run time carries no features of its own, so the + /// call is classified at the features of the function containing it, which + /// is the declaration classic arranges every call site from. A call that + /// passes arguments through an ellipsis is classified from its own + /// operands, as a direct one is. + std::optional<mlir::abi::FunctionClassification> + classifyIndirectCall(cir::CIRCallOpInterface call) override; + +protected: + CIRABIClassifierCommon(const mlir::DataLayout &dl, mlir::ModuleOp modOp) + : dl(dl), modOp(modOp), typeMapper(dl) {} + + /// The library classifier for a function whose recorded target features + /// may refine the ABI rules, or for the module's rules when + /// \p featureSource is null. + virtual const llvm::abi::TargetInfo & + getTargetInfo(cir::FuncOp featureSource) = 0; + + /// Classify \p ty, the type fetched by a `cir.va_arg`, as an unnamed + /// argument with every argument register free. + std::optional<mlir::abi::ArgClassification> + classifyVarArgType(mlir::Type ty, const llvm::abi::TargetInfo &targetInfo, + llvm::function_ref<mlir::InFlightDiagnostic()> emitError); + + const mlir::DataLayout &dl; + mlir::ModuleOp modOp; + mlir::abi::ABITypeMapper typeMapper; + +private: + /// Classify a signature returning \p retCIR and taking \p inputs, whose + /// first \p required entries are declared parameters. + std::optional<mlir::abi::FunctionClassification> + classifySignature(mlir::Type retCIR, mlir::TypeRange inputs, + llvm::abi::RequiredArgs required, + const llvm::abi::TargetInfo &targetInfo, + llvm::function_ref<mlir::InFlightDiagnostic()> emitError); + + /// Classify \p call, which passes more operands than \p calleeTy declares, + /// from the call's operands. + std::optional<mlir::abi::FunctionClassification> + classifyVariadicCall(cir::CIRCallOpInterface call, cir::FuncType calleeTy, + const llvm::abi::TargetInfo &targetInfo); +}; + +/// The MLIR test ABI target's classifier, whose predictable rules approximate +/// x86_64 SysV. +std::unique_ptr<CIRABIClassifier> +createTestTargetABIClassifier(const mlir::DataLayout &dl); + +/// A classifier that reads each function's classification from the +/// DictionaryAttr named \p attrName. +std::unique_ptr<CIRABIClassifier> +createInjectedABIClassifier(const mlir::DataLayout &dl, + llvm::StringRef attrName); + +/// The x86_64 System V classifier. +std::unique_ptr<CIRABIClassifier> +createX86_64ABIClassifier(const mlir::DataLayout &dl, mlir::ModuleOp modOp, + const ABIClassifierOptions &options); + +//===----------------------------------------------------------------------===// +// Classification helpers defined in CallConvLoweringPass.cpp +//===----------------------------------------------------------------------===// + +/// Record on \p fc whether \p returnType is CIR's void. +std::optional<mlir::abi::FunctionClassification> +withReturnVoidness(std::optional<mlir::abi::FunctionClassification> fc, + mlir::Type returnType); + +/// Classify \p func with the test target, or from the attribute named +/// \p classificationAttrName when that is non-empty. +std::optional<mlir::abi::FunctionClassification> +classifyFunction(cir::FuncOp func, const mlir::DataLayout &dl, + cir::CallConvTarget target, + llvm::StringRef classificationAttrName); + +/// Where \p fnTy's declared parameters end and its ellipsis arguments begin. +llvm::abi::RequiredArgs requiredArgs(cir::FuncType fnTy); + +/// The AVX level to classify \p func at: \p base, raised if the function's own +/// recorded feature list enables a wider vector. +llvm::abi::X86AVXABILevel funcAvxLevel(cir::FuncOp func, + llvm::abi::X86AVXABILevel base); + +} // namespace cir + +#endif // CLANG_LIB_CIR_DIALECT_TRANSFORMS_CIRABICLASSIFIER_H diff --git a/clang/lib/CIR/Dialect/Transforms/CIRABIClassifierX86_64.cpp b/clang/lib/CIR/Dialect/Transforms/CIRABIClassifierX86_64.cpp new file mode 100644 index 00000000000000..9d1f02b10dc22d --- /dev/null +++ b/clang/lib/CIR/Dialect/Transforms/CIRABIClassifierX86_64.cpp @@ -0,0 +1,86 @@ +//===- CIRABIClassifierX86_64.cpp - x86_64 System V classification --------===// +// +// 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 +// +//===----------------------------------------------------------------------===// +// +// This file implements the x86_64 System V classifier, which classifies with +// the LLVM ABI Lowering Library's SysV x86_64 target. +// +//===----------------------------------------------------------------------===// + +#include "CIRABIClassifier.h" + +#include "clang/CIR/Dialect/IR/CIRDialect.h" + +#include <array> + +using namespace mlir; +using namespace mlir::abi; +using namespace cir; + +namespace { + +class X86_64ABIClassifier final : public CIRABIClassifierCommon { +public: + X86_64ABIClassifier(const mlir::DataLayout &dl, mlir::ModuleOp modOp, + const ABIClassifierOptions &options) + : CIRABIClassifierCommon(dl, modOp), baseAvxLevel(options.x86AvxAbiLevel), + allowsTargetAttrAvx(options.allowsX86TargetAttrAvx), + abiCompat(options.x86AbiCompat) {} + + /// cir.va_arg is expanded into the SysV register-save-area / overflow-area + /// sequence, which reads the fetched type's classification as an unnamed + /// argument, as classic's X86_64ABIInfo::EmitVAArg does. + std::optional<ArgClassification> classifyVAArg(cir::VAArgOp vaArg) override { + return classifyVarArgType( + vaArg.getType(), getTargetInfo(vaArg->getParentOfType<cir::FuncOp>()), + [&]() { return vaArg->emitOpError(); }); + } + +protected: + /// The AVX level starts at the module's and may be raised by + /// \p featureSource's recorded target features. + const llvm::abi::TargetInfo & + getTargetInfo(cir::FuncOp featureSource) override; + +private: + /// One library classifier per AVX level, built on first use. A + /// per-function target attribute can raise the level above the module's. + static constexpr unsigned numAvxLevels = + static_cast<unsigned>(llvm::abi::X86AVXABILevel::Last) + 1; + std::array<std::unique_ptr<llvm::abi::TargetInfo>, numAvxLevels> targets; + + llvm::abi::X86AVXABILevel baseAvxLevel; + bool allowsTargetAttrAvx; + + /// The x86_64 flags whose value depends on the target and the requested ABI + /// compatibility version. + llvm::abi::X86ABICompatInfo abiCompat; +}; + +const llvm::abi::TargetInfo & +X86_64ABIClassifier::getTargetInfo(cir::FuncOp featureSource) { + llvm::abi::X86AVXABILevel level = baseAvxLevel; + if (allowsTargetAttrAvx && featureSource) + level = funcAvxLevel(featureSource, baseAvxLevel); + assert(static_cast<unsigned>(level) < numAvxLevels && + "a new X86AVXABILevel must move X86AVXABILevel::Last"); + std::unique_ptr<llvm::abi::TargetInfo> &slot = + targets[static_cast<unsigned>(level)]; + if (!slot) + slot = + llvm::abi::createX86_64TargetInfo(typeMapper.getTypeBuilder(), level, + /*Has64BitPointers=*/true, abiCompat); + return *slot; +} + +} // namespace + +std::unique_ptr<CIRABIClassifier> +cir::createX86_64ABIClassifier(const mlir::DataLayout &dl, mlir::ModuleOp modOp, + const ABIClassifierOptions &options) { + return std::make_unique<X86_64ABIClassifier>(dl, modOp, options); +} diff --git a/clang/lib/CIR/Dialect/Transforms/CMakeLists.txt b/clang/lib/CIR/Dialect/Transforms/CMakeLists.txt index 614f3608352426..b3d6862c19f670 100644 --- a/clang/lib/CIR/Dialect/Transforms/CMakeLists.txt +++ b/clang/lib/CIR/Dialect/Transforms/CMakeLists.txt @@ -15,6 +15,8 @@ add_clang_library(MLIRCIRTransforms IdiomRecognizer.cpp LibOpt.cpp RecordTypeConverter.cpp + CIRABIClassifier.cpp + CIRABIClassifierX86_64.cpp DEPENDS MLIRCIRPassIncGen diff --git a/clang/lib/CIR/Dialect/Transforms/CallConvLoweringPass.cpp b/clang/lib/CIR/Dialect/Transforms/CallConvLoweringPass.cpp index 3b9156ee95bb5b..e0ae978b0db8f4 100644 --- a/clang/lib/CIR/Dialect/Transforms/CallConvLoweringPass.cpp +++ b/clang/lib/CIR/Dialect/Transforms/CallConvLoweringPass.cpp @@ -30,6 +30,7 @@ // //===----------------------------------------------------------------------===// +#include "CIRABIClassifier.h" #include "PassDetail.h" #include "TargetLowering/CIRABIRewriteContext.h" @@ -66,8 +67,6 @@ namespace mlir { #include "clang/CIR/Dialect/Passes.h.inc" } // namespace mlir -namespace { - //===----------------------------------------------------------------------===// // x86_64 System V classifier bridge // @@ -349,7 +348,7 @@ static mlir::Type unwrapLongDoubleVectorElement(mlir::Type ty) { return ty; } -/// Map a CIR type to an llvm::abi::Type. classifyX86_64Function pre-filters +/// Map a CIR type to an llvm::abi::Type. classifySignature pre-filters /// the signature, so only the scalar and struct/array types handled here can /// reach this function. static const llvm::abi::Type *mapCIRType(mlir::Type type, @@ -547,7 +546,7 @@ static const llvm::abi::Type *mapCIRType(mlir::Type type, }) .Default([](mlir::Type) -> const llvm::abi::Type * { llvm_unreachable( - "mapCIRType: type not pre-filtered by classifyX86_64Function"); + "mapCIRType: type not pre-filtered by classifySignature"); }); } @@ -654,38 +653,36 @@ convertABIArgInfo(const llvm::abi::ArgInfo &info, MLIRContext *ctx, /// /// The only x86_64 rule that reads this boundary sends an unnamed vector wider /// than 128 bits to memory. -static llvm::abi::RequiredArgs requiredArgs(cir::FuncType fnTy) { +llvm::abi::RequiredArgs cir::requiredArgs(cir::FuncType fnTy) { if (!fnTy.isVarArg()) return llvm::abi::RequiredArgs::All; return llvm::abi::RequiredArgs(fnTy.getNumInputs()); } -/// Classify an x86_64 SysV signature (return type + argument types) using the -/// LLVM ABI library. Shared by the cir.func path, the variadic-call path and -/// the indirect-call path (the latter classifies from the callee function +/// Classify a signature (return type + argument types) using the LLVM ABI +/// library. Shared by the cir.func path, the variadic-call path and the +/// indirect-call path (the latter classifies from the callee function /// pointer's pointee FuncType). \p required marks where the declared /// parameters in \p inputs end. The classifier treats every argument past that /// point as passed through an ellipsis. Returns std::nullopt and emits an NYI /// error via \p emitError if the signature uses a type the bridge does not /// handle yet. -static std::optional<FunctionClassification> classifyX86_64Signature( +std::optional<FunctionClassification> CIRABIClassifierCommon::classifySignature( mlir::Type retCIR, mlir::TypeRange inputs, llvm::abi::RequiredArgs required, - MLIRContext *ctx, const DataLayout &dl, - mlir::abi::ABITypeMapper &typeMapper, - const llvm::abi::TargetInfo &targetInfo, ModuleOp modOp, + const llvm::abi::TargetInfo &targetInfo, llvm::function_ref<mlir::InFlightDiagnostic()> emitError) { assert(retCIR && "signature return type must be non-null"); assert((!required.allowsOptionalArgs() || required.getNumRequiredArgs() <= inputs.size()) && "declared parameters cannot outnumber the classified arguments"); + MLIRContext *ctx = modOp->getContext(); bool voidRet = isa<cir::VoidType>(retCIR); auto reject = [&](mlir::Type t) -> bool { if (isSupportedType(t, dl)) return false; - emitError() - << "x86_64 calling-convention lowering not yet implemented for type " - << t; + emitError() << "calling-convention lowering not yet implemented for type " + << t; return true; }; if (!voidRet && reject(retCIR)) @@ -708,7 +705,7 @@ static std::optional<FunctionClassification> classifyX86_64Signature( // convertABIArgInfo returns nullopt when the classifier picks a coercion this // bridge cannot represent. auto nyiCoercion = [&](mlir::Type t) { - emitError() << "x86_64 calling-convention lowering not yet " + emitError() << "calling-convention lowering not yet " "implemented for the ABI coercion of type " << t; }; @@ -741,8 +738,8 @@ static std::optional<FunctionClassification> classifyX86_64Signature( /// The AVX level to classify \p func at: \p base, raised if the function's own /// recorded feature list enables a wider vector. -static llvm::abi::X86AVXABILevel funcAvxLevel(cir::FuncOp func, - llvm::abi::X86AVXABILevel base) { +llvm::abi::X86AVXABILevel cir::funcAvxLevel(cir::FuncOp func, + llvm::abi::X86AVXABILevel base) { // Only a `target` attribute may raise the level. A multiversioned function // carries a raised feature list too, and must stay at the module's level. assert(!cir::MissingFeatures::opFuncMultiVersioning()); @@ -764,32 +761,27 @@ static llvm::abi::X86AVXABILevel funcAvxLevel(cir::FuncOp func, return avx ? std::max(base, llvm::abi::X86AVXABILevel::AVX) : base; } -/// 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 bridge -/// does not handle yet. -static std::optional<FunctionClassification> -classifyX86_64Function(cir::FuncOp func, const DataLayout &dl, - mlir::abi::ABITypeMapper &typeMapper, - const llvm::abi::TargetInfo &targetInfo, - ModuleOp modOp) { +/// Classify a cir.func using the LLVM ABI library. Returns std::nullopt and +/// emits an NYI error if the signature uses a type the bridge does not handle +/// yet. +std::optional<FunctionClassification> +CIRABIClassifierCommon::classifyFunction(cir::FuncOp func) { cir::FuncType fnTy = func.getFunctionType(); - return classifyX86_64Signature(fnTy.getReturnType(), fnTy.getInputs(), - requiredArgs(fnTy), func->getContext(), dl, - typeMapper, targetInfo, modOp, - [&]() { return func.emitOpError(); }); + return classifySignature(fnTy.getReturnType(), fnTy.getInputs(), + requiredArgs(fnTy), getTargetInfo(func), + [&]() { return func.emitOpError(); }); } /// Classify the single type fetched by a `cir.va_arg` as an unnamed argument /// (RequiredArgs(0)) with a full register budget. Returns std::nullopt and /// emits an NYI error if the type is not one this bridge handles. -static std::optional<ArgClassification> classifyX86_64VarArgType( - mlir::Type ty, MLIRContext *ctx, const DataLayout &dl, - mlir::abi::ABITypeMapper &typeMapper, - const llvm::abi::TargetInfo &targetInfo, ModuleOp modOp, +std::optional<ArgClassification> CIRABIClassifierCommon::classifyVarArgType( + mlir::Type ty, const llvm::abi::TargetInfo &targetInfo, llvm::function_ref<mlir::InFlightDiagnostic()> emitError) { - std::optional<FunctionClassification> fc = classifyX86_64Signature( - cir::VoidType::get(ctx), mlir::TypeRange(ty), llvm::abi::RequiredArgs(0), - ctx, dl, typeMapper, targetInfo, modOp, emitError); + MLIRContext *ctx = modOp->getContext(); + std::optional<FunctionClassification> fc = + classifySignature(cir::VoidType::get(ctx), mlir::TypeRange(ty), + llvm::abi::RequiredArgs(0), targetInfo, emitError); if (!fc) return std::nullopt; return fc->argInfos[0]; @@ -802,17 +794,16 @@ static std::optional<ArgClassification> classifyX86_64VarArgType( /// small struct is passed in registers early in the list and in memory once /// the integer registers are gone. Classifying from the call's operands /// rather than the callee's signature is what makes that accounting right. -static std::optional<FunctionClassification> classifyX86_64VariadicCall( - cir::CIRCallOpInterface call, cir::FuncType calleeTy, const DataLayout &dl, - mlir::abi::ABITypeMapper &typeMapper, - const llvm::abi::TargetInfo &targetInfo, ModuleOp modOp) { +std::optional<FunctionClassification> +CIRABIClassifierCommon::classifyVariadicCall( + cir::CIRCallOpInterface call, cir::FuncType calleeTy, + const llvm::abi::TargetInfo &targetInfo) { assert(calleeTy.isVarArg() && "only a variadic callee can take more operands than it declares"); Operation *op = call.getOperation(); - return classifyX86_64Signature( + return classifySignature( calleeTy.getReturnType(), call.getArgOperands().getTypes(), - requiredArgs(calleeTy), op->getContext(), dl, typeMapper, targetInfo, - modOp, [&]() { return op->emitOpError(); }); + requiredArgs(calleeTy), targetInfo, [&]() { return op->emitOpError(); }); } /// Whether \p fc gives the callee access to memory through a pointer the ABI @@ -855,6 +846,8 @@ static bool classifiesSamePrefix(const FunctionClassification &calleeFc, } #endif +namespace { + struct CallConvLoweringPass : public impl::CallConvLoweringBase<CallConvLoweringPass> { using CallConvLoweringBase::CallConvLoweringBase; @@ -872,15 +865,17 @@ struct CallConvLoweringPass llvm::abi::X86ABICompatInfo x86AbiCompat; }; +} // namespace + /// Record on \p fc whether \p returnType is CIR's void. The x86_64 classifier /// answers this itself, but the other two drivers cannot: the test target is /// dialect-neutral and has no notion of CIR's void, and the /// classification-attr schema carries no return type at all. Both route /// through here so a classification always reaches needsRewrite paired with /// the return type it was built from. -static std::optional<FunctionClassification> -withReturnVoidness(std::optional<FunctionClassification> fc, - mlir::Type returnType) { +std::optional<FunctionClassification> +cir::withReturnVoidness(std::optional<FunctionClassification> fc, + mlir::Type returnType) { if (fc) fc->returnsVoid = mlir::isa<cir::VoidType>(returnType); return fc; @@ -891,8 +886,9 @@ withReturnVoidness(std::optional<FunctionClassification> fc, /// (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, - cir::CallConvTarget target, StringRef classificationAttrName) { +cir::classifyFunction(cir::FuncOp func, const DataLayout &dl, + cir::CallConvTarget target, + StringRef classificationAttrName) { ArrayRef<Type> argTypes = func.getFunctionType().getInputs(); Type returnType = func.getFunctionType().getReturnType(); @@ -909,14 +905,37 @@ classifyFunction(cir::FuncOp func, const DataLayout &dl, returnType); } - // The x86_64 target is handled directly in runOnOperation (it needs a shared - // ABITypeMapper and TargetInfo), so only the test target reaches here. + // The x86_64 target classifies through CIRABIClassifierCommon, so only the + // test target reaches here. assert(target == cir::CallConvTarget::Test && "classifyFunction only handles the test target"); return withReturnVoidness(mlir::abi::test::classify(argTypes, returnType, dl), returnType); } +/// Build the classifier the pass options select. Exactly one of \p target +/// and \p classificationAttrName is set, which runOnOperation checks before +/// calling. +static std::unique_ptr<CIRABIClassifier> +createABIClassifier(cir::CallConvTarget target, + StringRef classificationAttrName, const DataLayout &dl, + ModuleOp modOp, const ABIClassifierOptions &options) { + if (!classificationAttrName.empty()) + return createInjectedABIClassifier(dl, classificationAttrName); + + switch (target) { + case cir::CallConvTarget::Test: + return createTestTargetABIClassifier(dl); + case cir::CallConvTarget::X86_64: + return createX86_64ABIClassifier(dl, modOp, options); + case cir::CallConvTarget::None: + break; + } + llvm_unreachable("an unset driver mode is rejected before this point"); +} + +namespace { + /// Find the cir.func declaration matching a direct cir.call / cir.try_call /// callee, if any. Returns nullptr if the callee is indirect or the symbol /// cannot be resolved. Takes a SymbolTable instead of a ModuleOp so the @@ -966,33 +985,12 @@ void CallConvLoweringPass::runOnOperation() { [&] { rewriteCtx.finalizeParameterSlots(); }); SymbolTable symbolTable(moduleOp); - // A per-function target attribute can raise the AVX level, so one classifier - // per module would misclassify a wide vector in such a function. - static constexpr unsigned numAvxLevels = - static_cast<unsigned>(llvm::abi::X86AVXABILevel::Last) + 1; - bool isX86 = target == cir::CallConvTarget::X86_64; - std::optional<mlir::abi::ABITypeMapper> x86TypeMapper; - std::array<std::unique_ptr<llvm::abi::TargetInfo>, numAvxLevels> x86Targets; - if (isX86) - x86TypeMapper.emplace(dl); - auto x86TargetFor = - [&](llvm::abi::X86AVXABILevel level) -> const llvm::abi::TargetInfo & { - assert(static_cast<unsigned>(level) < numAvxLevels && - "a new X86AVXABILevel must move X86AVXABILevel::Last"); - std::unique_ptr<llvm::abi::TargetInfo> &slot = - x86Targets[static_cast<unsigned>(level)]; - if (!slot) - slot = llvm::abi::createX86_64TargetInfo( - x86TypeMapper->getTypeBuilder(), level, - /*Has64BitPointers=*/true, x86AbiCompat); - return *slot; - }; - llvm::abi::X86AVXABILevel baseAvxLevel = x86AvxAbiLevel.getValue(); - auto avxLevelFor = [&](cir::FuncOp func) -> llvm::abi::X86AVXABILevel { - if (!allowsX86TargetAttrAvx || !func) - return baseAvxLevel; - return funcAvxLevel(func, baseAvxLevel); - }; + cir::ABIClassifierOptions classifierOptions; + classifierOptions.x86AvxAbiLevel = x86AvxAbiLevel; + classifierOptions.allowsX86TargetAttrAvx = allowsX86TargetAttrAvx; + classifierOptions.x86AbiCompat = x86AbiCompat; + std::unique_ptr<cir::CIRABIClassifier> classifier = createABIClassifier( + target, classificationAttr, dl, moduleOp, classifierOptions); // Classify every cir.func up front. No IR mutation happens here, so // later walks can consult any function's classification regardless of @@ -1009,12 +1007,7 @@ void CallConvLoweringPass::runOnOperation() { (hasIncompleteRecordByValue(fnTy.getReturnType()) || llvm::any_of(fnTy.getInputs(), hasIncompleteRecordByValue))) return; - std::optional<FunctionClassification> fc; - if (isX86) - fc = classifyX86_64Function(f, dl, *x86TypeMapper, - x86TargetFor(avxLevelFor(f)), moduleOp); - else - fc = classifyFunction(f, dl, target, classificationAttr); + std::optional<FunctionClassification> fc = classifier->classifyFunction(f); if (!fc) { anyFailed = true; return; @@ -1049,11 +1042,8 @@ void CallConvLoweringPass::runOnOperation() { return; callers[callee].push_back(op); - // Only the x86_64 driver classifies per call site. Under the other - // drivers the classification comes from a fixed per-function source, so - // such a call stays short a classification and rewriteCallSite reports it. cir::FuncType calleeTy = callee.getFunctionType(); - if (!isX86 || call.getNumArgOperands() <= calleeTy.getNumInputs()) + if (call.getNumArgOperands() <= calleeTy.getNumInputs()) return; // A callee declared without a prototype also takes more operands than it // declares, and the verifier allows it. Those extra arguments are named @@ -1065,14 +1055,8 @@ void CallConvLoweringPass::runOnOperation() { anyFailed = true; return; } - // The callee's level, not the caller's: this pass rewrites the definition - // and its call sites from one classification, so they have to agree. - // Classic instead arranges every call site from the caller and reports a - // caller whose level disagrees with its callee in checkFunctionCallABI, - // which has no equivalent here yet. std::optional<FunctionClassification> fc = - classifyX86_64VariadicCall(call, calleeTy, dl, *x86TypeMapper, - x86TargetFor(avxLevelFor(callee)), moduleOp); + classifier->classifyVariadicCall(call, callee); if (!fc) { anyFailed = true; return; @@ -1175,45 +1159,8 @@ void CallConvLoweringPass::runOnOperation() { indirectCalls.push_back(c); }); for (cir::CIRCallOpInterface c : indirectCalls) { - // classification-attr mode injects a per-function classification, which - // cannot describe a callee resolved at run time. Report it rather than - // leave the indirect call unrewritten while direct calls are coerced. - if (!classificationAttr.empty()) { - c->emitOpError() << "indirect call cannot be classified in the " - "'classification-attr' driver mode"; - signalPassFailure(); - return; - } - cir::FuncType funcTy = getIndirectCalleeType(c); - auto classifySignature = - [&](mlir::TypeRange argTypes) -> std::optional<FunctionClassification> { - // A callee resolved at run time carries no features of its own, so the - // level comes from the function containing the call, which is the - // declaration classic arranges every call site from. - if (isX86) - return classifyX86_64Signature( - funcTy.getReturnType(), argTypes, requiredArgs(funcTy), ctx, dl, - *x86TypeMapper, - x86TargetFor(avxLevelFor(c->getParentOfType<cir::FuncOp>())), - moduleOp, [&]() { return c->emitOpError(); }); - return withReturnVoidness( - mlir::abi::test::classify(argTypes, funcTy.getReturnType(), dl), - funcTy.getReturnType()); - }; - - // An argument passed through an ellipsis has no counterpart in the - // pointee's parameter list. On x86_64 such a call is classified from its - // own operands, as a direct one is. Under target=test it is classified - // from the pointee alone, and rewriteCallSite reports the call. - bool classifyCallSite = - isX86 && c.getNumArgOperands() > funcTy.getNumInputs(); std::optional<FunctionClassification> fc = - classifyCallSite - ? classifyX86_64VariadicCall( - c, funcTy, dl, *x86TypeMapper, - x86TargetFor(avxLevelFor(c->getParentOfType<cir::FuncOp>())), - moduleOp) - : classifySignature(funcTy.getInputs()); + classifier->classifyIndirectCall(c); if (!fc) { signalPassFailure(); return; @@ -1226,25 +1173,19 @@ void CallConvLoweringPass::runOnOperation() { } } - if (isX86) { - // Collect the fetches before rewriting any, because rewriting one erases - // it, and a live walk holds the op it is about to visit next. - SmallVector<cir::VAArgOp> vaArgs; - moduleOp.walk([&](cir::VAArgOp v) { vaArgs.push_back(v); }); - for (cir::VAArgOp v : vaArgs) { - cir::FuncOp enclosing = v->getParentOfType<cir::FuncOp>(); - std::optional<ArgClassification> ac = classifyX86_64VarArgType( - v.getType(), ctx, dl, *x86TypeMapper, - x86TargetFor(avxLevelFor(enclosing)), moduleOp, - [&]() { return v->emitOpError(); }); - if (!ac) { - signalPassFailure(); - return; - } - if (failed(rewriteCtx.rewriteVAArg(v.getOperation(), *ac, builder))) { - signalPassFailure(); - return; - } + // Collect the fetches before rewriting any, because rewriting one erases + // it, and a live walk holds the op it is about to visit next. + SmallVector<cir::VAArgOp> vaArgs; + moduleOp.walk([&](cir::VAArgOp v) { vaArgs.push_back(v); }); + for (cir::VAArgOp v : vaArgs) { + std::optional<ArgClassification> ac = classifier->classifyVAArg(v); + if (!ac) { + signalPassFailure(); + return; + } + if (failed(rewriteCtx.rewriteVAArg(v.getOperation(), *ac, builder))) { + signalPassFailure(); + return; } } } 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 index a8350b421757dc..285df67e53c838 100644 --- 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 @@ -15,4 +15,4 @@ module attributes { } -// CHECK: op x86_64 calling-convention lowering not yet implemented for type +// CHECK: op calling-convention lowering not yet implemented for type diff --git a/clang/test/CIR/Transforms/abi-lowering/x86_64-variadic-nyi.cir b/clang/test/CIR/Transforms/abi-lowering/x86_64-variadic-nyi.cir index 52a74f07fd2380..a2aab9c43318b8 100644 --- a/clang/test/CIR/Transforms/abi-lowering/x86_64-variadic-nyi.cir +++ b/clang/test/CIR/Transforms/abi-lowering/x86_64-variadic-nyi.cir @@ -44,7 +44,7 @@ module attributes { cir.func @indirect_nyi(%arg0: !cir.ptr<!cir.func<(!cir.ptr<!s8i>, ...) -> !s32i>>, %arg1: !cir.ptr<!s8i>, %arg2: !cir.ptr<!i96>) { %v = cir.load %arg2 : !cir.ptr<!i96>, !i96 - // CHECK: :[[@LINE+1]]:10: error: 'cir.call' op x86_64 calling-convention lowering not yet implemented for type '!cir.int<s, 96>' + // CHECK: :[[@LINE+1]]:10: error: 'cir.call' op calling-convention lowering not yet implemented for type '!cir.int<s, 96>' %0 = cir.call %arg0(%arg1, %v) : (!cir.ptr<!cir.func<(!cir.ptr<!s8i>, ...) -> !s32i>>, !cir.ptr<!s8i>, !i96) -> !s32i cir.return >From 42479546d224be3ad4929d67ed8334b81e759d70 Mon Sep 17 00:00:00 2001 From: Andy Kaylor <[email protected]> Date: Tue, 20 Oct 2026 15:50:47 -0700 Subject: [PATCH 2/2] Clean up getTargetInfo handling --- clang/lib/CIR/Dialect/Transforms/CIRABIClassifier.h | 3 +-- clang/lib/CIR/Dialect/Transforms/CIRABIClassifierX86_64.cpp | 3 ++- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/clang/lib/CIR/Dialect/Transforms/CIRABIClassifier.h b/clang/lib/CIR/Dialect/Transforms/CIRABIClassifier.h index aba0722bd62482..d1799eccb37be5 100644 --- a/clang/lib/CIR/Dialect/Transforms/CIRABIClassifier.h +++ b/clang/lib/CIR/Dialect/Transforms/CIRABIClassifier.h @@ -119,8 +119,7 @@ class CIRABIClassifierCommon : public CIRABIClassifier { : dl(dl), modOp(modOp), typeMapper(dl) {} /// The library classifier for a function whose recorded target features - /// may refine the ABI rules, or for the module's rules when - /// \p featureSource is null. + /// may refine the ABI rules. virtual const llvm::abi::TargetInfo & getTargetInfo(cir::FuncOp featureSource) = 0; diff --git a/clang/lib/CIR/Dialect/Transforms/CIRABIClassifierX86_64.cpp b/clang/lib/CIR/Dialect/Transforms/CIRABIClassifierX86_64.cpp index 9d1f02b10dc22d..14dabde136e78c 100644 --- a/clang/lib/CIR/Dialect/Transforms/CIRABIClassifierX86_64.cpp +++ b/clang/lib/CIR/Dialect/Transforms/CIRABIClassifierX86_64.cpp @@ -64,7 +64,8 @@ class X86_64ABIClassifier final : public CIRABIClassifierCommon { const llvm::abi::TargetInfo & X86_64ABIClassifier::getTargetInfo(cir::FuncOp featureSource) { llvm::abi::X86AVXABILevel level = baseAvxLevel; - if (allowsTargetAttrAvx && featureSource) + assert(featureSource && "function required for getTargetInfo"); + if (allowsTargetAttrAvx) level = funcAvxLevel(featureSource, baseAvxLevel); assert(static_cast<unsigned>(level) < numAvxLevels && "a new X86AVXABILevel must move X86AVXABILevel::Last"); _______________________________________________ cfe-commits mailing list [email protected] https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits
