https://github.com/steffenlarsen updated https://github.com/llvm/llvm-project/pull/226435
>From fae8370258f54af17a63d1fd333a0a28fc8290bb Mon Sep 17 00:00:00 2001 From: Steffen Holst Larsen <[email protected]> Date: Tue, 1 Sep 2026 09:00:32 -0500 Subject: [PATCH 1/6] [CIR][AMDGPU] Route printf in device code through the OpenCL runtime This commit implements CIR support for device-side printf on AMDGPU targets. Previously, any call to printf fell through to a library call, which does not exist on the device, so any kernel calling printf failed to build. For OGCG, the AMDGPU target routes printf calls through the OpenCL printf runtime, which takes the format string and a buffer of appended arguments. This implementation leverages this lowering by first emitting a call to a marker function `__cir_amdgpu_printf` during CIRGen, which is then lowered to the appropriate OpenCL printf runtime call during the LLVM IR lowering, leveraging the existing `llvm::emitAMDGPUPrintfCall` function. --- clang/include/clang/CIR/LowerToLLVM.h | 6 +++ clang/lib/CIR/CodeGen/CIRGenBuiltin.cpp | 2 +- clang/lib/CIR/CodeGen/CIRGenBuiltinAMDGPU.cpp | 39 ++++++++++++++++ clang/lib/CIR/CodeGen/CIRGenFunction.h | 3 ++ clang/lib/CIR/FrontendAction/CIRGenAction.cpp | 8 +++- .../CIR/Lowering/DirectToLLVM/LowerToLLVM.cpp | 46 +++++++++++++++++++ clang/test/CIR/CodeGenHIP/device-printf.hip | 29 ++++++++++++ 7 files changed, 130 insertions(+), 3 deletions(-) create mode 100644 clang/test/CIR/CodeGenHIP/device-printf.hip diff --git a/clang/include/clang/CIR/LowerToLLVM.h b/clang/include/clang/CIR/LowerToLLVM.h index d97ac5265c282..68a9460281baf 100644 --- a/clang/include/clang/CIR/LowerToLLVM.h +++ b/clang/include/clang/CIR/LowerToLLVM.h @@ -33,6 +33,12 @@ lowerDirectlyFromCIRToLLVMIR(mlir::ModuleOp mlirModule, llvm::LLVMContext &llvmCtx, bool enableOpenMP, llvm::StringRef mlirSaveTempsOutFile = {}, llvm::vfs::FileSystem *fs = nullptr); + +// Expand calls to the internal __cir_amdgpu_printf marker CIRGen emits for a +// device-side printf into the real AMDGPU sequence. Must run after the +// module has been translated to LLVM IR and before device-library bitcode +// linking. +void expandAMDGPUDevicePrintf(llvm::Module &module); } // namespace direct } // namespace cir diff --git a/clang/lib/CIR/CodeGen/CIRGenBuiltin.cpp b/clang/lib/CIR/CodeGen/CIRGenBuiltin.cpp index 2b223c8ae1939..d4d31a7f2ae65 100644 --- a/clang/lib/CIR/CodeGen/CIRGenBuiltin.cpp +++ b/clang/lib/CIR/CodeGen/CIRGenBuiltin.cpp @@ -3105,7 +3105,7 @@ RValue CIRGenFunction::emitBuiltinExpr(const GlobalDecl &gd, unsigned builtinID, if ((getTarget().getTriple().isAMDGCN() || getTarget().getTriple().isSPIRV()) && getLangOpts().HIP) - return errorBuiltinNYI(*this, e, builtinID); + return RValue::get(emitAMDGPUDevicePrintfCallExpr(e)); } break; case Builtin::BI__builtin_canonicalize: diff --git a/clang/lib/CIR/CodeGen/CIRGenBuiltinAMDGPU.cpp b/clang/lib/CIR/CodeGen/CIRGenBuiltinAMDGPU.cpp index 58ef4b1bd4b61..a0daed0938a33 100644 --- a/clang/lib/CIR/CodeGen/CIRGenBuiltinAMDGPU.cpp +++ b/clang/lib/CIR/CodeGen/CIRGenBuiltinAMDGPU.cpp @@ -1096,3 +1096,42 @@ CIRGenFunction::emitAMDGPUBuiltinExpr(unsigned builtinId, return std::nullopt; } } + +// Emit AMDGPU printf CIR stand-in function call. This stand-in function call is +// lowered to the appropriate call structure during LLVM IR lowering. +mlir::Value +CIRGenFunction::emitAMDGPUDevicePrintfCallExpr(const CallExpr *expr) { + assert(cgm.getTriple().isAMDGCN() || + (cgm.getTriple().isSPIRV() && + cgm.getTriple().getVendor() == llvm::Triple::AMD)); + assert(expr->getBuiltinCallee() == Builtin::BIprintf || + expr->getBuiltinCallee() == Builtin::BI__builtin_printf); + assert(expr->getNumArgs() >= 1); + + const FunctionProtoType *funcPrototype = + expr->getDirectCallee()->getType()->getAs<FunctionProtoType>(); + CallArgList args; + emitCallArgs(args, funcPrototype, expr->arguments(), expr->getDirectCallee()); + + mlir::Location loc = getLoc(expr->getBeginLoc()); + + // We don't know how to emit non-scalar varargs. + bool hasNonScalar = llvm::any_of(args, [&](const CallArg &a) { + return a.hasLValue() || !a.getKnownRValue().isScalar(); + }); + if (hasNonScalar) { + cgm.errorUnsupported(expr, "non-scalar args to printf"); + return builder.getConstInt(loc, builder.getSInt32Ty(), 0); + } + + llvm::SmallVector<mlir::Value, 8> callArgs; + for (const CallArg &a : args) + callArgs.push_back(a.getKnownRValue().getValue()); + + // int __cir_amdgpu_printf(char *format, ...); + auto fnTy = cir::FuncType::get({cir::PointerType::get(builder.getSInt8Ty())}, + builder.getSInt32Ty(), + /*isVarArg=*/true); + cir::FuncOp fn = cgm.createRuntimeFunction(fnTy, "__cir_amdgpu_printf"); + return builder.createCallOp(loc, fn, callArgs).getResult(); +} diff --git a/clang/lib/CIR/CodeGen/CIRGenFunction.h b/clang/lib/CIR/CodeGen/CIRGenFunction.h index b232abf5d9299..0f65753374843 100644 --- a/clang/lib/CIR/CodeGen/CIRGenFunction.h +++ b/clang/lib/CIR/CodeGen/CIRGenFunction.h @@ -2363,6 +2363,9 @@ class CIRGenFunction : public CIRGenTypeCache { /// Emit a device-side printf call for NVPTX targets. mlir::Value emitNVPTXDevicePrintfCallExpr(const CallExpr *expr); + /// Emit a device-side printf call for AMDGPU targets. + mlir::Value emitAMDGPUDevicePrintfCallExpr(const CallExpr *expr); + LValue emitOpaqueValueLValue(const OpaqueValueExpr *e); LValue emitConditionalOperatorLValue(const AbstractConditionalOperator *expr); diff --git a/clang/lib/CIR/FrontendAction/CIRGenAction.cpp b/clang/lib/CIR/FrontendAction/CIRGenAction.cpp index 240601f9834e5..e25a251150cf9 100644 --- a/clang/lib/CIR/FrontendAction/CIRGenAction.cpp +++ b/clang/lib/CIR/FrontendAction/CIRGenAction.cpp @@ -65,8 +65,12 @@ lowerFromCIRToLLVMIR(mlir::ModuleOp MLIRModule, llvm::LLVMContext &LLVMCtx, bool EnableOpenMP, llvm::StringRef mlirSaveTempsOutFile = {}, llvm::vfs::FileSystem *fs = nullptr) { - return direct::lowerDirectlyFromCIRToLLVMIR(MLIRModule, LLVMCtx, EnableOpenMP, - mlirSaveTempsOutFile, fs); + std::unique_ptr<llvm::Module> LLVMModule = + direct::lowerDirectlyFromCIRToLLVMIR(MLIRModule, LLVMCtx, EnableOpenMP, + mlirSaveTempsOutFile, fs); + if (LLVMModule) + direct::expandAMDGPUDevicePrintf(*LLVMModule); + return LLVMModule; } class CIRGenConsumer : public clang::ASTConsumer { diff --git a/clang/lib/CIR/Lowering/DirectToLLVM/LowerToLLVM.cpp b/clang/lib/CIR/Lowering/DirectToLLVM/LowerToLLVM.cpp index bd529f71b38ed..c55de0d19fc5d 100644 --- a/clang/lib/CIR/Lowering/DirectToLLVM/LowerToLLVM.cpp +++ b/clang/lib/CIR/Lowering/DirectToLLVM/LowerToLLVM.cpp @@ -49,12 +49,15 @@ #include "llvm/ADT/MapVector.h" #include "llvm/ADT/StringMap.h" #include "llvm/ADT/TypeSwitch.h" +#include "llvm/IR/IRBuilder.h" #include "llvm/IR/Module.h" #include "llvm/Support/Casting.h" #include "llvm/Support/ErrorHandling.h" #include "llvm/Support/TimeProfiler.h" #include "llvm/Support/VirtualFileSystem.h" #include "llvm/Support/raw_ostream.h" +#include "llvm/Transforms/Utils/AMDGPUEmitPrintf.h" +#include "llvm/Transforms/Utils/Local.h" using namespace cir; using namespace llvm; @@ -5914,6 +5917,49 @@ void populateCIRToLLVMPasses(mlir::OpPassManager &pm, bool enableOpenMP) { pm.addPass(mlir::omp::createHostOpFilteringPass()); } +// Expand calls to the internal __cir_amdgpu_printf marker CIRGen emits for a +// device-side printf into the real AMDGPU sequence. +void expandAMDGPUDevicePrintf(llvm::Module &module) { + llvm::Function *marker = module.getFunction("__cir_amdgpu_printf"); + if (!marker) + return; + + // CIR records the requested lowering as a module flag. + bool isBuffered = false; + if (llvm::Metadata *md = + module.getModuleFlag(cir::CIRDialect::getAMDGPUPrintfKindAttrName())) + if (auto *mdStr = llvm::dyn_cast<llvm::MDString>(md)) + isBuffered = mdStr->getString() == "buffered"; + + // Snapshot marker's users before mutating anything. + llvm::SmallVector<llvm::User *, 8> users(marker->user_begin(), + marker->user_end()); + for (llvm::User *u : users) { + auto *cb = llvm::cast<llvm::CallBase>(u); + + // CIR emits an invoke rather than a call when the marker call site is + // inside a region that requires unwinding, even though the device printf + // sequence emitted below can never throw. Normalize those invokes to + // calls. + llvm::CallInst *ci = llvm::dyn_cast<llvm::CallInst>(cb); + if (!ci) { + assert(llvm::isa<llvm::InvokeInst>(cb) && + "unexpected non-call user of printf marker"); + ci = llvm::changeToCall(llvm::cast<llvm::InvokeInst>(cb)); + } + + llvm::IRBuilder<> irb(ci); + llvm::SmallVector<llvm::Value *, 8> args(ci->args()); + llvm::Value *res = llvm::emitAMDGPUPrintfCall(irb, args, isBuffered); + if (res && !ci->use_empty()) + ci->replaceAllUsesWith(res); + ci->eraseFromParent(); + } + + assert(marker->use_empty() && "printf marker should have no remaining uses"); + marker->eraseFromParent(); +} + std::unique_ptr<llvm::Module> lowerDirectlyFromCIRToLLVMIR(mlir::ModuleOp mlirModule, LLVMContext &llvmCtx, bool enableOpenMP, StringRef mlirSaveTempsOutFile, diff --git a/clang/test/CIR/CodeGenHIP/device-printf.hip b/clang/test/CIR/CodeGenHIP/device-printf.hip new file mode 100644 index 0000000000000..5e7b3039cccf4 --- /dev/null +++ b/clang/test/CIR/CodeGenHIP/device-printf.hip @@ -0,0 +1,29 @@ +// REQUIRES: amdgpu-registered-target +// RUN: %clang_cc1 -triple amdgcn-amd-amdhsa -target-cpu gfx90a -x hip \ +// RUN: -std=c++11 -fclangir -fcuda-is-device -emit-llvm %s -o %t-cir.ll +// RUN: FileCheck --check-prefix=LLVM --input-file=%t-cir.ll %s +// RUN: %clang_cc1 -triple amdgcn-amd-amdhsa -target-cpu gfx90a -x hip \ +// RUN: -std=c++11 -fcuda-is-device -emit-llvm %s -o %t.ll +// RUN: FileCheck --check-prefix=LLVM --input-file=%t.ll %s + +// printf fell through to a library call, which does not exist on the device: +// the reference survived to the device link and failed there as an undefined +// symbol. AMDGPU implements it through the OpenCL printf runtime. +// +// CIRGen cannot build the sequence itself -- llvm::emitAMDGPUPrintfCall works +// through an IRBuilder -- so it emits a marker call that is expanded once the +// module has been translated to LLVM IR. It has to happen there rather than +// later, because the __ockl_* calls are what make the device-library linker +// pull in ockl. +// +// The checks are shared with the classic CodeGen run line. + +#define __device__ __attribute__((device)) +extern "C" __device__ int printf(const char *, ...); + +// LLVM-LABEL: @_Z1pi +// LLVM: call i64 @__ockl_printf_begin(i64 +// LLVM: call i64 @__ockl_printf_append_string_n(i64 +// LLVM: call i64 @__ockl_printf_append_args(i64 +// LLVM-NOT: call{{.*}}@__cir_amdgpu_printf +__device__ void p(int v) { printf("%d\n", v); } >From 11652782b7f435a11921d549c5d18a000e5ba1bc Mon Sep 17 00:00:00 2001 From: Steffen Holst Larsen <[email protected]> Date: Fri, 25 Sep 2026 06:43:03 -0500 Subject: [PATCH 2/6] Remove unnecessary over-verbose test comment Signed-off-by: Steffen Holst Larsen <[email protected]> --- clang/test/CIR/CodeGenHIP/device-printf.hip | 8 -------- 1 file changed, 8 deletions(-) diff --git a/clang/test/CIR/CodeGenHIP/device-printf.hip b/clang/test/CIR/CodeGenHIP/device-printf.hip index 5e7b3039cccf4..539106d720058 100644 --- a/clang/test/CIR/CodeGenHIP/device-printf.hip +++ b/clang/test/CIR/CodeGenHIP/device-printf.hip @@ -9,14 +9,6 @@ // printf fell through to a library call, which does not exist on the device: // the reference survived to the device link and failed there as an undefined // symbol. AMDGPU implements it through the OpenCL printf runtime. -// -// CIRGen cannot build the sequence itself -- llvm::emitAMDGPUPrintfCall works -// through an IRBuilder -- so it emits a marker call that is expanded once the -// module has been translated to LLVM IR. It has to happen there rather than -// later, because the __ockl_* calls are what make the device-library linker -// pull in ockl. -// -// The checks are shared with the classic CodeGen run line. #define __device__ __attribute__((device)) extern "C" __device__ int printf(const char *, ...); >From 6115a12e18e3441bca633474739f7174aa0de3e5 Mon Sep 17 00:00:00 2001 From: Steffen Holst Larsen <[email protected]> Date: Fri, 25 Sep 2026 06:46:21 -0500 Subject: [PATCH 3/6] Use new triple Signed-off-by: Steffen Holst Larsen <[email protected]> --- clang/test/CIR/CodeGenHIP/device-printf.hip | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/clang/test/CIR/CodeGenHIP/device-printf.hip b/clang/test/CIR/CodeGenHIP/device-printf.hip index 539106d720058..8f08a5982dcfd 100644 --- a/clang/test/CIR/CodeGenHIP/device-printf.hip +++ b/clang/test/CIR/CodeGenHIP/device-printf.hip @@ -1,9 +1,9 @@ // REQUIRES: amdgpu-registered-target -// RUN: %clang_cc1 -triple amdgcn-amd-amdhsa -target-cpu gfx90a -x hip \ -// RUN: -std=c++11 -fclangir -fcuda-is-device -emit-llvm %s -o %t-cir.ll +// RUN: %clang_cc1 -triple=amdgpu9.0a-amd-amdhsa -x hip -std=c++11 \ +// RUN: -fclangir -fcuda-is-device -emit-llvm %s -o %t-cir.ll // RUN: FileCheck --check-prefix=LLVM --input-file=%t-cir.ll %s -// RUN: %clang_cc1 -triple amdgcn-amd-amdhsa -target-cpu gfx90a -x hip \ -// RUN: -std=c++11 -fcuda-is-device -emit-llvm %s -o %t.ll +// RUN: %clang_cc1 -triple=amdgpu9.0a-amd-amdhsa -x hip -std=c++11 \ +// RUN: -fcuda-is-device -emit-llvm %s -o %t.ll // RUN: FileCheck --check-prefix=LLVM --input-file=%t.ll %s // printf fell through to a library call, which does not exist on the device: >From 44b51b829bf56a93a71b21a706e04bd8d9447210 Mon Sep 17 00:00:00 2001 From: Steffen Holst Larsen <[email protected]> Date: Mon, 28 Sep 2026 01:14:16 -0500 Subject: [PATCH 4/6] Fix buffered printf Signed-off-by: Steffen Holst Larsen <[email protected]> --- .../CIR/Lowering/DirectToLLVM/LowerToLLVM.cpp | 24 +++++++++++++++---- clang/test/CIR/CodeGenHIP/device-printf.hip | 11 +++++++++ 2 files changed, 31 insertions(+), 4 deletions(-) diff --git a/clang/lib/CIR/Lowering/DirectToLLVM/LowerToLLVM.cpp b/clang/lib/CIR/Lowering/DirectToLLVM/LowerToLLVM.cpp index c55de0d19fc5d..e0a6df59c8ab7 100644 --- a/clang/lib/CIR/Lowering/DirectToLLVM/LowerToLLVM.cpp +++ b/clang/lib/CIR/Lowering/DirectToLLVM/LowerToLLVM.cpp @@ -5924,10 +5924,11 @@ void expandAMDGPUDevicePrintf(llvm::Module &module) { if (!marker) return; - // CIR records the requested lowering as a module flag. + // CIR records the requested lowering as a module flag. The flag is stored + // under the LLVM-side name (see amendModule in LowerToLLVMIR.cpp), not the + // CIR attribute name. bool isBuffered = false; - if (llvm::Metadata *md = - module.getModuleFlag(cir::CIRDialect::getAMDGPUPrintfKindAttrName())) + if (llvm::Metadata *md = module.getModuleFlag("amdgpu_printf_kind")) if (auto *mdStr = llvm::dyn_cast<llvm::MDString>(md)) isBuffered = mdStr->getString() == "buffered"; @@ -5948,9 +5949,24 @@ void expandAMDGPUDevicePrintf(llvm::Module &module) { ci = llvm::changeToCall(llvm::cast<llvm::InvokeInst>(cb)); } - llvm::IRBuilder<> irb(ci); + // Buffered lowering splits the call site's block and build new control flow + // of its own. It expects to be the one driving codegen for the rest of the + // block, as it would if called from normal frontend codegen. Since we're + // expanding a marker call after the fact, split off everything that + // was already emitted after it into its own block first, then + // reconnect to that block once the real printf sequence has been + // built. + llvm::BasicBlock *originalBB = ci->getParent(); llvm::SmallVector<llvm::Value *, 8> args(ci->args()); + llvm::BasicBlock *continuation = + originalBB->splitBasicBlock(ci->getNextNode()); + originalBB->getTerminator()->eraseFromParent(); + + llvm::IRBuilder<> irb(originalBB); + irb.SetCurrentDebugLocation(ci->getDebugLoc()); llvm::Value *res = llvm::emitAMDGPUPrintfCall(irb, args, isBuffered); + irb.CreateBr(continuation); + if (res && !ci->use_empty()) ci->replaceAllUsesWith(res); ci->eraseFromParent(); diff --git a/clang/test/CIR/CodeGenHIP/device-printf.hip b/clang/test/CIR/CodeGenHIP/device-printf.hip index 8f08a5982dcfd..815ea2f732a39 100644 --- a/clang/test/CIR/CodeGenHIP/device-printf.hip +++ b/clang/test/CIR/CodeGenHIP/device-printf.hip @@ -5,6 +5,12 @@ // RUN: %clang_cc1 -triple=amdgpu9.0a-amd-amdhsa -x hip -std=c++11 \ // RUN: -fcuda-is-device -emit-llvm %s -o %t.ll // RUN: FileCheck --check-prefix=LLVM --input-file=%t.ll %s +// RUN: %clang_cc1 -triple=amdgpu9.0a-amd-amdhsa -x hip -std=c++11 \ +// RUN: -fclangir -fcuda-is-device -mprintf-kind=buffered -emit-llvm %s -o %t-cir-buffered.ll +// RUN: FileCheck --check-prefix=BUFFERED --input-file=%t-cir-buffered.ll %s +// RUN: %clang_cc1 -triple=amdgpu9.0a-amd-amdhsa -x hip -std=c++11 \ +// RUN: -fcuda-is-device -mprintf-kind=buffered -emit-llvm %s -o %t-buffered.ll +// RUN: FileCheck --check-prefix=BUFFERED --input-file=%t-buffered.ll %s // printf fell through to a library call, which does not exist on the device: // the reference survived to the device link and failed there as an undefined @@ -18,4 +24,9 @@ extern "C" __device__ int printf(const char *, ...); // LLVM: call i64 @__ockl_printf_append_string_n(i64 // LLVM: call i64 @__ockl_printf_append_args(i64 // LLVM-NOT: call{{.*}}@__cir_amdgpu_printf +// BUFFERED-LABEL: @_Z1pi +// BUFFERED: call ptr addrspace(1) @__printf_alloc(i32 +// BUFFERED: end.block: +// BUFFERED: argpush.block: +// BUFFERED-NOT: call{{.*}}@__cir_amdgpu_printf __device__ void p(int v) { printf("%d\n", v); } >From 8acd4ab4b7621241537a3bea5eb1ea748da43e1f Mon Sep 17 00:00:00 2001 From: Steffen Holst Larsen <[email protected]> Date: Mon, 28 Sep 2026 01:26:46 -0500 Subject: [PATCH 5/6] cir-translate and nothrow Signed-off-by: Steffen Holst Larsen <[email protected]> --- clang/lib/CIR/CodeGen/CIRGenBuiltinAMDGPU.cpp | 5 ++- .../CIR/Lowering/DirectToLLVM/LowerToLLVM.cpp | 15 ++------- clang/test/CIR/CodeGenHIP/device-printf.hip | 33 +++++++++++++++++++ clang/test/CIR/Tools/amdgpu-printf.cir | 21 ++++++++++++ clang/tools/cir-translate/cir-translate.cpp | 1 + 5 files changed, 61 insertions(+), 14 deletions(-) create mode 100644 clang/test/CIR/Tools/amdgpu-printf.cir diff --git a/clang/lib/CIR/CodeGen/CIRGenBuiltinAMDGPU.cpp b/clang/lib/CIR/CodeGen/CIRGenBuiltinAMDGPU.cpp index a0daed0938a33..8ba724bf758d5 100644 --- a/clang/lib/CIR/CodeGen/CIRGenBuiltinAMDGPU.cpp +++ b/clang/lib/CIR/CodeGen/CIRGenBuiltinAMDGPU.cpp @@ -1133,5 +1133,8 @@ CIRGenFunction::emitAMDGPUDevicePrintfCallExpr(const CallExpr *expr) { builder.getSInt32Ty(), /*isVarArg=*/true); cir::FuncOp fn = cgm.createRuntimeFunction(fnTy, "__cir_amdgpu_printf"); - return builder.createCallOp(loc, fn, callArgs).getResult(); + cir::CallOp call = builder.createCallOp(loc, fn, callArgs); + // The sequence the marker expands into never unwinds. + call.setNothrowAttr(builder.getUnitAttr()); + return call.getResult(); } diff --git a/clang/lib/CIR/Lowering/DirectToLLVM/LowerToLLVM.cpp b/clang/lib/CIR/Lowering/DirectToLLVM/LowerToLLVM.cpp index e0a6df59c8ab7..3a98c2a1e5789 100644 --- a/clang/lib/CIR/Lowering/DirectToLLVM/LowerToLLVM.cpp +++ b/clang/lib/CIR/Lowering/DirectToLLVM/LowerToLLVM.cpp @@ -57,7 +57,6 @@ #include "llvm/Support/VirtualFileSystem.h" #include "llvm/Support/raw_ostream.h" #include "llvm/Transforms/Utils/AMDGPUEmitPrintf.h" -#include "llvm/Transforms/Utils/Local.h" using namespace cir; using namespace llvm; @@ -5936,18 +5935,8 @@ void expandAMDGPUDevicePrintf(llvm::Module &module) { llvm::SmallVector<llvm::User *, 8> users(marker->user_begin(), marker->user_end()); for (llvm::User *u : users) { - auto *cb = llvm::cast<llvm::CallBase>(u); - - // CIR emits an invoke rather than a call when the marker call site is - // inside a region that requires unwinding, even though the device printf - // sequence emitted below can never throw. Normalize those invokes to - // calls. - llvm::CallInst *ci = llvm::dyn_cast<llvm::CallInst>(cb); - if (!ci) { - assert(llvm::isa<llvm::InvokeInst>(cb) && - "unexpected non-call user of printf marker"); - ci = llvm::changeToCall(llvm::cast<llvm::InvokeInst>(cb)); - } + // CIRGen marks the marker call nothrow, so it is never an invoke. + auto *ci = llvm::cast<llvm::CallInst>(u); // Buffered lowering splits the call site's block and build new control flow // of its own. It expects to be the one driving codegen for the rest of the diff --git a/clang/test/CIR/CodeGenHIP/device-printf.hip b/clang/test/CIR/CodeGenHIP/device-printf.hip index 815ea2f732a39..13e36580e8ca1 100644 --- a/clang/test/CIR/CodeGenHIP/device-printf.hip +++ b/clang/test/CIR/CodeGenHIP/device-printf.hip @@ -1,14 +1,18 @@ // REQUIRES: amdgpu-registered-target // RUN: %clang_cc1 -triple=amdgpu9.0a-amd-amdhsa -x hip -std=c++11 \ +// RUN: -fcxx-exceptions -fexceptions \ // RUN: -fclangir -fcuda-is-device -emit-llvm %s -o %t-cir.ll // RUN: FileCheck --check-prefix=LLVM --input-file=%t-cir.ll %s // RUN: %clang_cc1 -triple=amdgpu9.0a-amd-amdhsa -x hip -std=c++11 \ +// RUN: -fcxx-exceptions -fexceptions \ // RUN: -fcuda-is-device -emit-llvm %s -o %t.ll // RUN: FileCheck --check-prefix=LLVM --input-file=%t.ll %s // RUN: %clang_cc1 -triple=amdgpu9.0a-amd-amdhsa -x hip -std=c++11 \ +// RUN: -fcxx-exceptions -fexceptions \ // RUN: -fclangir -fcuda-is-device -mprintf-kind=buffered -emit-llvm %s -o %t-cir-buffered.ll // RUN: FileCheck --check-prefix=BUFFERED --input-file=%t-cir-buffered.ll %s // RUN: %clang_cc1 -triple=amdgpu9.0a-amd-amdhsa -x hip -std=c++11 \ +// RUN: -fcxx-exceptions -fexceptions \ // RUN: -fcuda-is-device -mprintf-kind=buffered -emit-llvm %s -o %t-buffered.ll // RUN: FileCheck --check-prefix=BUFFERED --input-file=%t-buffered.ll %s @@ -30,3 +34,32 @@ extern "C" __device__ int printf(const char *, ...); // BUFFERED: argpush.block: // BUFFERED-NOT: call{{.*}}@__cir_amdgpu_printf __device__ void p(int v) { printf("%d\n", v); } + +// The result of printf must be forwarded to its users. CIR places the +// expansion blocks after the block that uses the result, so the use is matched +// in any order. +// LLVM-LABEL: @_Z1ri +// LLVM-DAG: [[RES:%.*]] = call i64 @__ockl_printf_append_args(i64 +// LLVM-DAG: [[RET:%.*]] = trunc i64 [[RES]] to i32 +// LLVM-DAG: {{store|ret}} i32 [[RET]] +// LLVM-NOT: call{{.*}}@__cir_amdgpu_printf +// BUFFERED-LABEL: @_Z1ri +// BUFFERED-DAG: %printf_result = sext i1 %{{.*}} to i32 +// BUFFERED-DAG: {{store|ret}} i32 %printf_result +// BUFFERED-NOT: call{{.*}}@__cir_amdgpu_printf +__device__ int r(int v) { return printf("%d\n", v); } + +// Device printf cannot throw, so a pending cleanup must not turn it into an +// invoke with an exception-handling path. +struct D { __device__ ~D(); }; +// LLVM-LABEL: @_Z2ehi( +// LLVM-NOT: personality +// LLVM: call i64 @__ockl_printf_begin(i64 +// LLVM-NOT: {{invoke|landingpad|resume}} +// LLVM-NOT: call{{.*}}@__cir_amdgpu_printf +// BUFFERED-LABEL: @_Z2ehi( +// BUFFERED-NOT: personality +// BUFFERED: call ptr addrspace(1) @__printf_alloc(i32 +// BUFFERED-NOT: {{invoke|landingpad|resume}} +// BUFFERED-NOT: call{{.*}}@__cir_amdgpu_printf +__device__ void eh(int v) { D d; printf("%d\n", v); } diff --git a/clang/test/CIR/Tools/amdgpu-printf.cir b/clang/test/CIR/Tools/amdgpu-printf.cir new file mode 100644 index 0000000000000..8383b9ed4c3ee --- /dev/null +++ b/clang/test/CIR/Tools/amdgpu-printf.cir @@ -0,0 +1,21 @@ +// RUN: cir-translate --cir-to-llvmir --target amdgcn-amd-amdhsa --disable-cc-lowering %s -o %t.ll +// RUN: FileCheck %s -input-file %t.ll -check-prefix=LLVM + +!s32i = !cir.int<s, 32> +!s8i = !cir.int<s, 8> + +module { + cir.func @p(%fmt: !cir.ptr<!s8i>, %v: !s32i) -> !s32i { + %0 = cir.call @__cir_amdgpu_printf(%fmt, %v) nothrow : (!cir.ptr<!s8i>, !s32i) -> !s32i + cir.return %0 : !s32i + } + cir.func private @__cir_amdgpu_printf(!cir.ptr<!s8i>, ...) -> !s32i +} + +// LLVM-LABEL: define{{.*}} i32 @p( +// LLVM: call i64 @__ockl_printf_begin(i64 +// LLVM-DAG: call i64 @__ockl_printf_append_string_n(i64 +// LLVM-DAG: [[RES:%.*]] = call i64 @__ockl_printf_append_args(i64 +// LLVM-DAG: [[RET:%.*]] = trunc i64 [[RES]] to i32 +// LLVM-DAG: ret i32 [[RET]] +// LLVM-NOT: __cir_amdgpu_printf diff --git a/clang/tools/cir-translate/cir-translate.cpp b/clang/tools/cir-translate/cir-translate.cpp index cefa7d2996f30..aa9cd06094665 100644 --- a/clang/tools/cir-translate/cir-translate.cpp +++ b/clang/tools/cir-translate/cir-translate.cpp @@ -173,6 +173,7 @@ void registerToLLVMTranslation() { enableOpenMP); if (!llvmModule) return mlir::failure(); + cir::direct::expandAMDGPUDevicePrintf(*llvmModule); llvmModule->renumberMetadataForAssembly(); llvmModule->print(output, nullptr); return mlir::success(); >From beaf34b82b7103a9a93c9aa6e4e098bd4b4c115d Mon Sep 17 00:00:00 2001 From: Steffen Holst Larsen <[email protected]> Date: Mon, 28 Sep 2026 01:48:11 -0500 Subject: [PATCH 6/6] Link TransformUtils Signed-off-by: Steffen Holst Larsen <[email protected]> --- clang/lib/CIR/Lowering/DirectToLLVM/CMakeLists.txt | 1 + 1 file changed, 1 insertion(+) diff --git a/clang/lib/CIR/Lowering/DirectToLLVM/CMakeLists.txt b/clang/lib/CIR/Lowering/DirectToLLVM/CMakeLists.txt index d3d0805afb4d8..2cc003d4af1c4 100644 --- a/clang/lib/CIR/Lowering/DirectToLLVM/CMakeLists.txt +++ b/clang/lib/CIR/Lowering/DirectToLLVM/CMakeLists.txt @@ -1,6 +1,7 @@ set(LLVM_LINK_COMPONENTS Core Support + TransformUtils ) add_clang_library(clangCIRLoweringDirectToLLVM _______________________________________________ cfe-commits mailing list [email protected] https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits
