https://github.com/skc7 updated https://github.com/llvm/llvm-project/pull/220197
>From 0bc4aded0fe8ddac78acd15a9657d14dbdfb9be5 Mon Sep 17 00:00:00 2001 From: skc7 <[email protected]> Date: Tue, 1 Sep 2026 14:31:15 +0530 Subject: [PATCH] [CIR] Wire AMDGPU into the call-convention lowering pass --- clang/include/clang/CIR/Dialect/Passes.h | 2 +- clang/include/clang/CIR/Dialect/Passes.td | 14 ++-- .../Dialect/Transforms/CIRABIClassifier.cpp | 21 ++++-- .../CIR/Dialect/Transforms/CIRABIClassifier.h | 15 +++- .../Transforms/CIRABIClassifierAMDGPU.cpp | 61 +++++++++++++++ .../lib/CIR/Dialect/Transforms/CMakeLists.txt | 1 + .../Transforms/CallConvLoweringPass.cpp | 74 ++++++++++++++----- .../TargetLowering/CIRABIRewriteContext.cpp | 37 +++++++++- .../abi-lowering/amdgpu-calling-conv.cir | 59 +++++++++++++++ .../abi-lowering/amdgpu-indirect-nyi.cir | 37 ++++++++++ .../abi-lowering/amdgpu-kernel-hip-ptr.cir | 53 +++++++++++++ 11 files changed, 338 insertions(+), 36 deletions(-) create mode 100644 clang/lib/CIR/Dialect/Transforms/CIRABIClassifierAMDGPU.cpp create mode 100644 clang/test/CIR/Transforms/abi-lowering/amdgpu-calling-conv.cir create mode 100644 clang/test/CIR/Transforms/abi-lowering/amdgpu-indirect-nyi.cir create mode 100644 clang/test/CIR/Transforms/abi-lowering/amdgpu-kernel-hip-ptr.cir diff --git a/clang/include/clang/CIR/Dialect/Passes.h b/clang/include/clang/CIR/Dialect/Passes.h index dedfc9fa351796..117822486cc3a2 100644 --- a/clang/include/clang/CIR/Dialect/Passes.h +++ b/clang/include/clang/CIR/Dialect/Passes.h @@ -20,7 +20,7 @@ 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 }; +enum class CallConvTarget { None, Test, X86_64, AMDGPU }; } // namespace cir namespace mlir { diff --git a/clang/include/clang/CIR/Dialect/Passes.td b/clang/include/clang/CIR/Dialect/Passes.td index 9b260af789384c..a5335ab3606dd6 100644 --- a/clang/include/clang/CIR/Dialect/Passes.td +++ b/clang/include/clang/CIR/Dialect/Passes.td @@ -225,10 +225,12 @@ def CallConvLowering : Pass<"cir-call-conv-lowering", "mlir::ModuleOp"> { Two driver modes select how each function's classification is computed: - - `target=<name>` selects an ABI target. Currently only `"test"` (the - MLIR test target in `mlir/lib/ABI/Targets/Test/`) is supported. Real - targets (x86_64, AArch64, ...) will be added once the LLVM ABI library - ships them. + - `target=<name>` selects an ABI target. The clang pipeline picks the + target from the module triple. Supported targets: + - `"x86_64"`: System V. + - `"amdgpu"`: AMDGPU. + - `"test"`: the MLIR test target in `mlir/lib/ABI/Targets/Test/`. + Other targets will be added as the LLVM ABI library ships them. - `classification-attr=<name>` reads a `DictionaryAttr` named `<name>` from each `cir.func` and parses it via the test-target injection helper. Used by tests to inject arbitrary classifications without @@ -246,7 +248,9 @@ def CallConvLowering : Pass<"cir-call-conv-lowering", "mlir::ModuleOp"> { clEnumValN(cir::CallConvTarget::Test, "test", "MLIR test ABI target"), clEnumValN(cir::CallConvTarget::X86_64, "x86_64", - "x86_64 System V") + "x86_64 System V"), + clEnumValN(cir::CallConvTarget::AMDGPU, "amdgpu", + "AMDGPU") )}]>, Option<"classificationAttr", "classification-attr", "std::string", /*default=*/"\"\"", diff --git a/clang/lib/CIR/Dialect/Transforms/CIRABIClassifier.cpp b/clang/lib/CIR/Dialect/Transforms/CIRABIClassifier.cpp index bae1bafd5580c8..c58bff7e3d9cbb 100644 --- a/clang/lib/CIR/Dialect/Transforms/CIRABIClassifier.cpp +++ b/clang/lib/CIR/Dialect/Transforms/CIRABIClassifier.cpp @@ -22,11 +22,20 @@ using namespace mlir; using namespace mlir::abi; using namespace cir; +/// FIXME: Only the AMDGPU kernel convention is mapped. Every other convention +/// is classified as C. +llvm::CallingConv::ID cir::convertCallingConv(cir::CallingConv cc) { + if (cc == cir::CallingConv::AMDGPUKernel) + return llvm::CallingConv::AMDGPU_KERNEL; + return llvm::CallingConv::C; +} + std::optional<FunctionClassification> CIRABIClassifierCommon::classifyVariadicCall(cir::CIRCallOpInterface call, cir::FuncOp callee) { return classifyVariadicCall(call, callee.getFunctionType(), - getTargetInfo(callee)); + getTargetInfo(callee), + convertCallingConv(callee.getCallingConv())); } std::optional<FunctionClassification> @@ -34,11 +43,13 @@ CIRABIClassifierCommon::classifyIndirectCall(cir::CIRCallOpInterface call) { cir::FuncType funcTy = getIndirectCalleeType(call); const llvm::abi::TargetInfo &targetInfo = getTargetInfo(call->getParentOfType<cir::FuncOp>()); + // An indirect callee is an ordinary function pointer, so it uses the C + // convention. if (call.getNumArgOperands() > funcTy.getNumInputs()) - return classifyVariadicCall(call, funcTy, targetInfo); - return classifySignature(funcTy.getReturnType(), funcTy.getInputs(), - requiredArgs(funcTy), targetInfo, - [&]() { return call->emitOpError(); }); + return classifyVariadicCall(call, funcTy, targetInfo, llvm::CallingConv::C); + return classifySignature( + funcTy.getReturnType(), funcTy.getInputs(), requiredArgs(funcTy), + targetInfo, llvm::CallingConv::C, [&]() { return call->emitOpError(); }); } namespace { diff --git a/clang/lib/CIR/Dialect/Transforms/CIRABIClassifier.h b/clang/lib/CIR/Dialect/Transforms/CIRABIClassifier.h index d1799eccb37be5..36a4bee8390fa2 100644 --- a/clang/lib/CIR/Dialect/Transforms/CIRABIClassifier.h +++ b/clang/lib/CIR/Dialect/Transforms/CIRABIClassifier.h @@ -29,6 +29,7 @@ #include "llvm/ABI/TargetInfo.h" #include "llvm/ADT/STLFunctionalExtras.h" #include "llvm/ADT/StringRef.h" +#include "llvm/IR/CallingConv.h" #include <memory> #include <optional> @@ -135,18 +136,20 @@ class CIRABIClassifierCommon : public CIRABIClassifier { private: /// Classify a signature returning \p retCIR and taking \p inputs, whose - /// first \p required entries are declared parameters. + /// first \p required entries are declared parameters, under \p callConv. std::optional<mlir::abi::FunctionClassification> classifySignature(mlir::Type retCIR, mlir::TypeRange inputs, llvm::abi::RequiredArgs required, const llvm::abi::TargetInfo &targetInfo, + llvm::CallingConv::ID callConv, 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); + const llvm::abi::TargetInfo &targetInfo, + llvm::CallingConv::ID callConv); }; /// The MLIR test ABI target's classifier, whose predictable rules approximate @@ -165,6 +168,14 @@ std::unique_ptr<CIRABIClassifier> createX86_64ABIClassifier(const mlir::DataLayout &dl, mlir::ModuleOp modOp, const ABIClassifierOptions &options); +/// The AMDGPU classifier. +std::unique_ptr<CIRABIClassifier> +createAMDGPUABIClassifier(const mlir::DataLayout &dl, mlir::ModuleOp modOp); + +/// Map a CIR calling convention to the llvm::CallingConv the ABI classifier +/// uses. Conventions the classifier does not distinguish fall back to C. +llvm::CallingConv::ID convertCallingConv(cir::CallingConv cc); + //===----------------------------------------------------------------------===// // Classification helpers defined in CallConvLoweringPass.cpp //===----------------------------------------------------------------------===// diff --git a/clang/lib/CIR/Dialect/Transforms/CIRABIClassifierAMDGPU.cpp b/clang/lib/CIR/Dialect/Transforms/CIRABIClassifierAMDGPU.cpp new file mode 100644 index 00000000000000..b7683a81fa6a55 --- /dev/null +++ b/clang/lib/CIR/Dialect/Transforms/CIRABIClassifierAMDGPU.cpp @@ -0,0 +1,61 @@ +//===- CIRABIClassifierAMDGPU.cpp - AMDGPU 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 AMDGPU classifier, which classifies with the LLVM +// ABI Lowering Library's AMDGPU target. +// +//===----------------------------------------------------------------------===// + +#include "CIRABIClassifier.h" + +#include "clang/CIR/Dialect/IR/CIRDialect.h" + +using namespace mlir; +using namespace mlir::abi; +using namespace cir; + +namespace { + +class AMDGPUABIClassifier final : public CIRABIClassifierCommon { +public: + AMDGPUABIClassifier(const mlir::DataLayout &dl, mlir::ModuleOp modOp) + : CIRABIClassifierCommon(dl, modOp) {} + + std::optional<ArgClassification> classifyVAArg(cir::VAArgOp vaArg) override { + vaArg->emitOpError() + << "not yet implemented in CallConvLowering for AMDGPU"; + return std::nullopt; + } + +protected: + /// AMDGPU has no per-function feature that changes its ABI rules. + const llvm::abi::TargetInfo &getTargetInfo(cir::FuncOp) override; + +private: + std::unique_ptr<llvm::abi::TargetInfo> target; +}; + +const llvm::abi::TargetInfo &AMDGPUABIClassifier::getTargetInfo(cir::FuncOp) { + if (!target) { + // HIP passes a generic pointer kernel argument as a global one. + auto langOpts = modOp->getAttrOfType<cir::LoweringLangOptionsAttr>( + cir::CIRDialect::getLoweringLangOptionsAttrName()); + target = llvm::abi::createAMDGPUTargetInfo( + typeMapper.getTypeBuilder(), + /*CoerceGenericPtrArgToGlobal=*/langOpts && langOpts.getHip()); + } + return *target; +} + +} // namespace + +std::unique_ptr<CIRABIClassifier> +cir::createAMDGPUABIClassifier(const mlir::DataLayout &dl, + mlir::ModuleOp modOp) { + return std::make_unique<AMDGPUABIClassifier>(dl, modOp); +} diff --git a/clang/lib/CIR/Dialect/Transforms/CMakeLists.txt b/clang/lib/CIR/Dialect/Transforms/CMakeLists.txt index 47c4f180196d32..42fceb0590efda 100644 --- a/clang/lib/CIR/Dialect/Transforms/CMakeLists.txt +++ b/clang/lib/CIR/Dialect/Transforms/CMakeLists.txt @@ -16,6 +16,7 @@ add_clang_library(MLIRCIRTransforms LibOpt.cpp RecordTypeConverter.cpp CIRABIClassifier.cpp + CIRABIClassifierAMDGPU.cpp CIRABIClassifierX86_64.cpp DEPENDS diff --git a/clang/lib/CIR/Dialect/Transforms/CallConvLoweringPass.cpp b/clang/lib/CIR/Dialect/Transforms/CallConvLoweringPass.cpp index e0ae978b0db8f4..a978d3a899c2a8 100644 --- a/clang/lib/CIR/Dialect/Transforms/CallConvLoweringPass.cpp +++ b/clang/lib/CIR/Dialect/Transforms/CallConvLoweringPass.cpp @@ -308,8 +308,12 @@ static mlir::Type abiTypeToCIR(const llvm::abi::Type *ty, MLIRContext *ctx) { .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)); + .Case([&](const llvm::abi::PointerType *ptrTy) { + auto voidTy = cir::VoidType::get(ctx); + if (unsigned as = ptrTy->getAddrSpace()) + return cir::PointerType::get( + voidTy, cir::TargetAddressSpaceAttr::get(ctx, as)); + return cir::PointerType::get(voidTy); }) .Case([&](const llvm::abi::VectorType *vecTy) -> mlir::Type { mlir::Type elemCIR = abiTypeToCIR(vecTy->getElementType(), ctx); @@ -554,11 +558,12 @@ static const llvm::abi::Type *mapCIRType(mlir::Type type, /// CIRABIRewriteContext. /// /// Direct: the value passes in register(s). A coercion is forwarded in the -/// four cases where the value has to be rebuilt on the wire: an aggregate -/// unpacked into the register(s) holding it, a scalar too wide for one register -/// split into a tuple of them, a scalar the classifier widens to fill its -/// eightbyte, and a value whose live bytes start partway into its storage -/// because a leading eightbyte holds no field (getDirectOffset). canFlatten +/// cases where the value has to be rebuilt on the wire: an aggregate unpacked +/// into the register(s) holding it, a scalar too wide for one register split +/// into a tuple of them, a scalar the classifier widens to fill its +/// eightbyte, a pointer moved into another address space, and a value whose +/// live bytes start partway into its storage because a leading eightbyte +/// holds no field (getDirectOffset). canFlatten /// follows the classifier's CanBeFlattened, so the rewriter splits a /// multi-field coerced struct into individual wire arguments unless the /// classifier asked to keep it intact. Any other scalar passes in its @@ -595,6 +600,26 @@ convertABIArgInfo(const llvm::abi::ArgInfo &info, MLIRContext *ctx, // than emit a read of the wrong bytes. if (offset && coerceIsRegisterTuple) return std::nullopt; + // A pointer moved to another address space keeps its pointee, which + // abiTypeToCIR cannot recover. + auto origPtr = dyn_cast_if_present<cir::PointerType>(origTy); + const auto *coercePtr = + dyn_cast_if_present<llvm::abi::PointerType>(coerceAbi); + if (origPtr && coercePtr && !offset) { + // Compare numbers, since an explicit address_space(0) keeps a + // target_address_space(0) attribute that a plain pointer lacks. + auto origAS = dyn_cast_if_present<cir::TargetAddressSpaceAttr>( + origPtr.getAddrSpace()); + if (coercePtr->getAddrSpace() != (origAS ? origAS.getValue() : 0)) { + auto coercedPtr = cast<cir::PointerType>(abiTypeToCIR(coerceAbi, ctx)); + ArgClassification classified = ArgClassification::getDirect( + cir::PointerType::get(origPtr.getPointee(), + coercedPtr.getAddrSpace()), + /*offset=*/0); + classified.canFlatten = info.getCanBeFlattened(); + return classified; + } + } // Compare widths rather than identity: a coerce no wider than the natural // type carries the same value and needs no rewrite. auto origInt = dyn_cast_if_present<cir::IntType>(origTy); @@ -638,6 +663,10 @@ convertABIArgInfo(const llvm::abi::ArgInfo &info, MLIRContext *ctx, mlir::Type extendedTy = abiTypeToCIR(info.getCoerceToType(), ctx); return ArgClassification::getExtend(extendedTy, info.isSignExt()); } + // ArgClassification has no address space for the indirect pointer yet. + if (info.isIndirectAliased() || + (info.isIndirect() && info.getIndirectAddrSpace() != 0)) + return std::nullopt; if (info.isIndirect()) return ArgClassification::getIndirect(info.getIndirectAlign(), info.getIndirectByVal()); @@ -664,12 +693,13 @@ llvm::abi::RequiredArgs cir::requiredArgs(cir::FuncType fnTy) { /// 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 +/// point as passed through an ellipsis. \p callConv is the calling convention +/// the signature is classified under. Returns std::nullopt and emits an NYI /// error via \p emitError if the signature uses a type the bridge does not /// handle yet. std::optional<FunctionClassification> CIRABIClassifierCommon::classifySignature( mlir::Type retCIR, mlir::TypeRange inputs, llvm::abi::RequiredArgs required, - const llvm::abi::TargetInfo &targetInfo, + const llvm::abi::TargetInfo &targetInfo, llvm::CallingConv::ID callConv, llvm::function_ref<mlir::InFlightDiagnostic()> emitError) { assert(retCIR && "signature return type must be non-null"); assert((!required.allowsOptionalArgs() || @@ -698,8 +728,8 @@ std::optional<FunctionClassification> CIRABIClassifierCommon::classifySignature( for (mlir::Type a : inputs) argAbi.push_back(mapCIRType(a, typeMapper, dl, modOp)); - std::unique_ptr<llvm::abi::FunctionInfo> fi = llvm::abi::FunctionInfo::create( - llvm::CallingConv::C, retAbi, argAbi, required); + std::unique_ptr<llvm::abi::FunctionInfo> fi = + llvm::abi::FunctionInfo::create(callConv, retAbi, argAbi, required); targetInfo.computeInfo(*fi); // convertABIArgInfo returns nullopt when the classifier picks a coercion this @@ -769,6 +799,7 @@ CIRABIClassifierCommon::classifyFunction(cir::FuncOp func) { cir::FuncType fnTy = func.getFunctionType(); return classifySignature(fnTy.getReturnType(), fnTy.getInputs(), requiredArgs(fnTy), getTargetInfo(func), + convertCallingConv(func.getCallingConv()), [&]() { return func.emitOpError(); }); } @@ -779,9 +810,9 @@ std::optional<ArgClassification> CIRABIClassifierCommon::classifyVarArgType( mlir::Type ty, const llvm::abi::TargetInfo &targetInfo, llvm::function_ref<mlir::InFlightDiagnostic()> emitError) { MLIRContext *ctx = modOp->getContext(); - std::optional<FunctionClassification> fc = - classifySignature(cir::VoidType::get(ctx), mlir::TypeRange(ty), - llvm::abi::RequiredArgs(0), targetInfo, emitError); + std::optional<FunctionClassification> fc = classifySignature( + cir::VoidType::get(ctx), mlir::TypeRange(ty), llvm::abi::RequiredArgs(0), + targetInfo, llvm::CallingConv::C, emitError); if (!fc) return std::nullopt; return fc->argInfos[0]; @@ -797,13 +828,14 @@ std::optional<ArgClassification> CIRABIClassifierCommon::classifyVarArgType( std::optional<FunctionClassification> CIRABIClassifierCommon::classifyVariadicCall( cir::CIRCallOpInterface call, cir::FuncType calleeTy, - const llvm::abi::TargetInfo &targetInfo) { + const llvm::abi::TargetInfo &targetInfo, llvm::CallingConv::ID callConv) { assert(calleeTy.isVarArg() && "only a variadic callee can take more operands than it declares"); Operation *op = call.getOperation(); - return classifySignature( - calleeTy.getReturnType(), call.getArgOperands().getTypes(), - requiredArgs(calleeTy), targetInfo, [&]() { return op->emitOpError(); }); + return classifySignature(calleeTy.getReturnType(), + call.getArgOperands().getTypes(), + requiredArgs(calleeTy), targetInfo, callConv, + [&]() { return op->emitOpError(); }); } /// Whether \p fc gives the callee access to memory through a pointer the ABI @@ -905,8 +937,8 @@ cir::classifyFunction(cir::FuncOp func, const DataLayout &dl, returnType); } - // The x86_64 target classifies through CIRABIClassifierCommon, so only the - // test target reaches here. + // The x86_64 and AMDGPU targets classify 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), @@ -928,6 +960,8 @@ createABIClassifier(cir::CallConvTarget target, return createTestTargetABIClassifier(dl); case cir::CallConvTarget::X86_64: return createX86_64ABIClassifier(dl, modOp, options); + case cir::CallConvTarget::AMDGPU: + return createAMDGPUABIClassifier(dl, modOp); case cir::CallConvTarget::None: break; } diff --git a/clang/lib/CIR/Dialect/Transforms/TargetLowering/CIRABIRewriteContext.cpp b/clang/lib/CIR/Dialect/Transforms/TargetLowering/CIRABIRewriteContext.cpp index c7ca6151029eb4..46e06b814776f3 100644 --- a/clang/lib/CIR/Dialect/Transforms/TargetLowering/CIRABIRewriteContext.cpp +++ b/clang/lib/CIR/Dialect/Transforms/TargetLowering/CIRABIRewriteContext.cpp @@ -484,14 +484,39 @@ mlir::Value emitCoercionToMemory(mlir::OpBuilder &builder, mlir::Location loc, dstTy == slotTy ? 0 : offset); } +/// Cast \p src to \p dstTy when both are pointers to the same pointee, so only +/// the address space changes. Returns null when a real coercion is needed. +static mlir::Value +emitAddrSpaceCoercion(mlir::OpBuilder &builder, mlir::Location loc, + mlir::Type dstTy, mlir::Value src, + SmallPtrSetImpl<mlir::Operation *> &createdOps, + unsigned offset) { + auto srcPtrTy = dyn_cast<cir::PointerType>(src.getType()); + auto dstPtrTy = dyn_cast<cir::PointerType>(dstTy); + if (offset || !srcPtrTy || !dstPtrTy || + srcPtrTy.getPointee() != dstPtrTy.getPointee()) + return {}; + assert(srcPtrTy.getAddrSpace() != dstPtrTy.getAddrSpace() && + "pointers to the same pointee must differ in address space"); + auto cast = cir::CastOp::create(builder, loc, dstTy, + cir::CastKind::address_space, src); + createdOps.insert(cast); + return cast; +} + /// Coerce \p src to type \p dstTy by going through memory and load the whole /// coerced value back out. Builds on emitCoercionToMemory, adding the final -/// load of the destination-typed view. +/// load of the destination-typed view. A pointer that only changes address +/// space is cast instead. mlir::Value emitCoercion(mlir::OpBuilder &builder, mlir::Location loc, mlir::Type dstTy, mlir::Value src, mlir::Block *slotBlock, const mlir::DataLayout &dl, SmallPtrSetImpl<mlir::Operation *> &createdOps, unsigned offset) { + if (mlir::Value cast = + emitAddrSpaceCoercion(builder, loc, dstTy, src, createdOps, offset)) + return cast; + mlir::Value dstSlot = emitCoercionToMemory(builder, loc, dstTy, src, slotBlock, dl, createdOps, offset); auto load = cir::LoadOp::create(builder, loc, dstSlot); @@ -794,14 +819,20 @@ replaceUsesFromSlot(mlir::OpBuilder &builder, mlir::Location loc, /// \p origTy through a coercion slot at the start of \p slotBlock, and point /// the uses of \p val outside \p createdOps at the result with /// replaceUsesFromSlot. A direct \p offset lands on the coerced side, so the -/// view of \p origTy starts at the slot. The coercion's operations are added -/// to \p createdOps. +/// view of \p origTy starts at the slot. A pointer that only changes address +/// space is cast instead. The coercion's operations are added to +/// \p createdOps. static mlir::LogicalResult emitCoercionAndReplaceUses(mlir::OpBuilder &builder, mlir::Location loc, mlir::Type origTy, mlir::Value src, mlir::Value val, mlir::Block *slotBlock, const mlir::DataLayout &dl, SmallPtrSetImpl<mlir::Operation *> &createdOps, unsigned offset) { + if (mlir::Value cast = emitAddrSpaceCoercion(builder, loc, origTy, src, + createdOps, offset)) { + val.replaceAllUsesExcept(cast, createdOps); + return mlir::success(); + } mlir::Value view = emitCoercionToMemory(builder, loc, origTy, src, slotBlock, dl, createdOps, offset); return replaceUsesFromSlot(builder, loc, val, view, dl, createdOps); diff --git a/clang/test/CIR/Transforms/abi-lowering/amdgpu-calling-conv.cir b/clang/test/CIR/Transforms/abi-lowering/amdgpu-calling-conv.cir new file mode 100644 index 00000000000000..4043621d054bca --- /dev/null +++ b/clang/test/CIR/Transforms/abi-lowering/amdgpu-calling-conv.cir @@ -0,0 +1,59 @@ +// RUN: cir-opt %s -cir-call-conv-lowering=target=amdgpu | FileCheck %s + +// Checks that CallConvLowering under target=amdgpu classifies a function by +// its calling convention. Default functions get extension and Ignore rewrites +// applied to definitions and call sites. Kernels go to the kernel classifier. + +!s8i = !cir.int<s, 8> +!s32i = !cir.int<s, 32> + +!rec_E0 = !cir.struct<"E0" {}> + +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>>> +} { + + // Signed sub-register integer is sign-extended. + cir.func @take_s8(%arg0: !s8i) { + cir.return + } + + // CHECK: cir.func{{.*}} @take_s8(%arg0: !s8i {llvm.signext}) + + // 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}) -> () + + // A zero-field record classifies as Ignore: the empty argument is dropped + // and the real argument shifts down to the first slot. + cir.func @take_empty(%arg0: !rec_E0, %arg1: !s32i) -> !s32i { + cir.return %arg1 : !s32i + } + + // CHECK: cir.func{{.*}} @take_empty(%arg0: !s32i) -> !s32i + // CHECK-NEXT: cir.return %arg0 : !s32i + + // A kernel scalar argument stays Direct: the AMDGPU_KERNEL convention is + // routed to the kernel classifier and the signature is unchanged. + cir.func @kernel_scalar(%arg0: !s32i) cc(amdgpu_kernel) { + cir.return + } + + // CHECK: cir.func{{.*}} @kernel_scalar(%arg0: !s32i){{.*}}cc(amdgpu_kernel) + + // Without HIP in the module's lang options, a kernel pointer stays generic. + cir.func @kernel_ptr(%arg0: !cir.ptr<!s32i>) cc(amdgpu_kernel) { + cir.return + } + + // CHECK: cir.func{{.*}} @kernel_ptr(%arg0: !cir.ptr<!s32i>){{.*}}cc(amdgpu_kernel) +} diff --git a/clang/test/CIR/Transforms/abi-lowering/amdgpu-indirect-nyi.cir b/clang/test/CIR/Transforms/abi-lowering/amdgpu-indirect-nyi.cir new file mode 100644 index 00000000000000..c925f21bc5189e --- /dev/null +++ b/clang/test/CIR/Transforms/abi-lowering/amdgpu-indirect-nyi.cir @@ -0,0 +1,37 @@ +// RUN: not cir-opt %s -cir-call-conv-lowering=target=amdgpu 2>&1 | FileCheck %s + +// AMDGPU classifications whose indirect pointer is not in the default address +// space are not lowered yet, so they are reported rather than misclassified. + +!s32i = !cir.int<s, 32> +!rec_Pair = !cir.struct<"Pair" {data !s32i, data !s32i}> +!rec_Big = !cir.struct<"Big" {data !cir.array<!s32i x 17>}> + +module attributes { + dlti.dl_spec = #dlti.dl_spec< + #dlti.dl_entry<i32, dense<32>: vector<2xi64>>, + #dlti.dl_entry<i64, dense<64>: vector<2xi64>>> +} { + + // A multi-field kernel argument is passed byref in addrspace(4). + cir.func @kern_pair(%arg0: !rec_Pair) cc(amdgpu_kernel) { + cir.return + } + + // CHECK: not yet implemented for the ABI coercion of type '!cir.struct<"Pair" + + // An argument past the 16-register budget is passed byref in addrspace(5). + cir.func @take_big(%arg0: !rec_Big) { + cir.return + } + + // CHECK: not yet implemented for the ABI coercion of type '!cir.struct<"Big" + + // A return past the register budget goes through an sret in addrspace(5). + cir.func @ret_big(%arg0: !cir.ptr<!rec_Big>) -> !rec_Big { + %0 = cir.load %arg0 : !cir.ptr<!rec_Big>, !rec_Big + cir.return %0 : !rec_Big + } + + // CHECK: not yet implemented for the ABI coercion of type '!cir.struct<"Big" +} diff --git a/clang/test/CIR/Transforms/abi-lowering/amdgpu-kernel-hip-ptr.cir b/clang/test/CIR/Transforms/abi-lowering/amdgpu-kernel-hip-ptr.cir new file mode 100644 index 00000000000000..ba35c4bc74b475 --- /dev/null +++ b/clang/test/CIR/Transforms/abi-lowering/amdgpu-kernel-hip-ptr.cir @@ -0,0 +1,53 @@ +// RUN: cir-opt %s -cir-call-conv-lowering=target=amdgpu | FileCheck %s + +// Under HIP a generic pointer kernel argument is passed as a global pointer. +// The HIP flag comes from the module's lowering_lang_options. + +!s32i = !cir.int<s, 32> +!rec_PtrWrap = !cir.struct<"PtrWrap" {data !cir.ptr<!s32i>}> + +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.lowering_lang_options = #cir.lowering_lang_options< + exceptions = false, threadsafe_statics = true, cuda = true, + cuda_is_device = true, hip = true, hlsl = false, gpu_rdc = false, + openmp = false, openmp_is_target_device = false, clang_abi_compat = 17> +} { + + // The kernel casts the incoming global pointer back to generic before the + // body's spill reads it. + cir.func @kernel_ptr(%arg0: !cir.ptr<!s32i>) cc(amdgpu_kernel) { + %0 = cir.alloca "p" align(8) init : !cir.ptr<!cir.ptr<!s32i>> + cir.store %arg0, %0 : !cir.ptr<!s32i>, !cir.ptr<!cir.ptr<!s32i>> + cir.return + } + + // CHECK: cir.func{{.*}} @kernel_ptr(%[[ARG:[^:]+]]: !cir.ptr<!s32i, target_address_space(1)>){{.*}}cc(amdgpu_kernel) + // CHECK-NEXT: %[[GEN:.*]] = cir.cast address_space %[[ARG]] : !cir.ptr<!s32i, target_address_space(1)> -> !cir.ptr<!s32i> + // CHECK: cir.store %[[GEN]], %{{.*}} : !cir.ptr<!s32i>, !cir.ptr<!cir.ptr<!s32i>> + + // A pointer already outside the generic address space is left alone. + cir.func @kernel_global_ptr(%arg0: !cir.ptr<!s32i, target_address_space(1)>) + cc(amdgpu_kernel) { + cir.return + } + + // CHECK: cir.func{{.*}} @kernel_global_ptr(%arg0: !cir.ptr<!s32i, target_address_space(1)>){{.*}}cc(amdgpu_kernel) + // CHECK-NOT: cir.cast + + // A struct holding only a pointer is unwrapped to it, then coerced. + cir.func @kernel_ptr_wrap(%arg0: !rec_PtrWrap) cc(amdgpu_kernel) { + cir.return + } + + // CHECK: cir.func{{.*}} @kernel_ptr_wrap(%{{.*}}: !cir.ptr<{{.*}}, target_address_space(1)>){{.*}}cc(amdgpu_kernel) + + // Only kernels are coerced. + cir.func @device_ptr(%arg0: !cir.ptr<!s32i>) { + cir.return + } + + // CHECK: cir.func{{.*}} @device_ptr(%arg0: !cir.ptr<!s32i>) +} _______________________________________________ cfe-commits mailing list [email protected] https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits
