[clang] [CIR] Wire AMDGPU into the call-convention lowering pass (PR #220197)
via cfe-commits
cfe-commits at lists.llvm.org
Tue Oct 20 23:11:00 PDT 2026
https://github.com/skc7 updated https://github.com/llvm/llvm-project/pull/220197
>From 0bc4aded0fe8ddac78acd15a9657d14dbdfb9be5 Mon Sep 17 00:00:00 2001
From: skc7 <Krishna.Sankisa at amd.com>
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>)
+}
More information about the cfe-commits
mailing list