https://github.com/steffenlarsen created https://github.com/llvm/llvm-project/pull/231153
CodeGen and CIRGen both decide which global variables and functions to emit on the host or device side of a CUDA/HIP compilation with copies of the same check. Move the check to ASTContext::shouldEmitCUDADecl to reduce replicated surface. Assisted-by: Claude Opus 5.5 >From 888a88c545a578a36c256b4b546d6a2342dea1e4 Mon Sep 17 00:00:00 2001 From: Steffen Holst Larsen <[email protected]> Date: Fri, 9 Oct 2026 05:18:44 -0500 Subject: [PATCH] [clang][CIR][CUDA][HIP] Unify CUDA decl emission check (NFC) CodeGen and CIRGen both decide which global variables and functions to emit on the host or device side of a CUDA/HIP compilation with copies of the same check. Move the check to ASTContext::shouldEmitCUDADecl to reduce replicated surface. Assisted-by: Claude Opus 5.5 Signed-off-by: Steffen Holst Larsen <[email protected]> --- clang/include/clang/AST/ASTContext.h | 7 ++++ clang/lib/AST/ASTContext.cpp | 39 +++++++++++++++++++ clang/lib/CIR/CodeGen/CIRGenModule.cpp | 54 ++------------------------ clang/lib/CIR/CodeGen/CIRGenModule.h | 4 -- clang/lib/CodeGen/CodeGenModule.cpp | 50 ++---------------------- clang/lib/CodeGen/CodeGenModule.h | 3 -- 6 files changed, 52 insertions(+), 105 deletions(-) diff --git a/clang/include/clang/AST/ASTContext.h b/clang/include/clang/AST/ASTContext.h index 1349637273d1e..04cc2da25cecb 100644 --- a/clang/include/clang/AST/ASTContext.h +++ b/clang/include/clang/AST/ASTContext.h @@ -3664,6 +3664,13 @@ class ASTContext : public RefCountedBase<ASTContext> { /// it is not used. bool DeclMustBeEmitted(const Decl *D); + /// Determines if the global variable or function \p D is emitted on the side + /// of the CUDA/HIP compilation, i.e. host or device, that is being compiled, + /// based on its host/device related attributes. + /// + /// \returns false if CodeGen never emits \p D in this compilation. + bool shouldEmitCUDADecl(const ValueDecl *D) const; + /// Visits all versions of a multiversioned function with the passed /// predicate. void forEachMultiversionedFunctionVersion( diff --git a/clang/lib/AST/ASTContext.cpp b/clang/lib/AST/ASTContext.cpp index 02d656feb0cee..3a7a94d49da3e 100644 --- a/clang/lib/AST/ASTContext.cpp +++ b/clang/lib/AST/ASTContext.cpp @@ -15,6 +15,7 @@ #include "CXXABI.h" #include "clang/AST/APValue.h" #include "clang/AST/ASTConcept.h" +#include "clang/AST/ASTLambda.h" #include "clang/AST/ASTMutationListener.h" #include "clang/AST/ASTStructuralEquivalence.h" #include "clang/AST/ASTTypeTraits.h" @@ -13502,6 +13503,44 @@ bool ASTContext::DeclMustBeEmitted(const Decl *D) { return false; } +template <typename AttrT> static bool hasImplicitAttr(const ValueDecl *D) { + if (auto *A = D->getAttr<AttrT>()) + return A->isImplicit(); + return D->isImplicit(); +} + +bool ASTContext::shouldEmitCUDADecl(const ValueDecl *D) const { + assert(LangOpts.CUDA && "Should not be called by non-CUDA languages"); + assert((isa<FunctionDecl>(D) || isa<VarDecl>(D)) && + "Expected Variable or Function"); + + if (const auto *VD = dyn_cast<VarDecl>(D)) { + // We need to emit host-side 'shadows' for all global device-side variables + // because the CUDA runtime needs their size and host-side address in order + // to provide access to their device-side incarnations. + return !LangOpts.CUDAIsDevice || VD->hasAttr<CUDADeviceAttr>() || + VD->hasAttr<CUDAConstantAttr>() || VD->hasAttr<CUDASharedAttr>() || + VD->getType()->isCUDADeviceBuiltinSurfaceType() || + VD->getType()->isCUDADeviceBuiltinTextureType(); + } + + const auto *FD = cast<FunctionDecl>(D); + // Device-only functions are the only things we skip on the host. + if (!LangOpts.CUDAIsDevice) + return FD->hasAttr<CUDAHostAttr>() || !FD->hasAttr<CUDADeviceAttr>(); + + // Non-constexpr non-lambda implicit host device functions are not emitted + // unless they are used on device side. + return (FD->hasAttr<CUDADeviceAttr>() && + !(LangOpts.OffloadImplicitHostDeviceTemplates && + hasImplicitAttr<CUDAHostAttr>(FD) && + hasImplicitAttr<CUDADeviceAttr>(FD) && !FD->isConstexpr() && + !isLambdaCallOperator(FD) && + !CUDAImplicitHostDeviceFunUsedByDevice.count(FD))) || + FD->hasAttr<CUDAGlobalAttr>() || + (LangOpts.HIPStdPar && !FD->hasAttr<CUDAHostAttr>()); +} + void ASTContext::forEachMultiversionedFunctionVersion( const FunctionDecl *FD, llvm::function_ref<void(FunctionDecl *)> Pred) const { diff --git a/clang/lib/CIR/CodeGen/CIRGenModule.cpp b/clang/lib/CIR/CodeGen/CIRGenModule.cpp index db505dc9824d0..787cfc9e09657 100644 --- a/clang/lib/CIR/CodeGen/CIRGenModule.cpp +++ b/clang/lib/CIR/CodeGen/CIRGenModule.cpp @@ -522,28 +522,6 @@ void CIRGenModule::emitDeferred() { } } -template <typename AttrT> static bool hasImplicitAttr(const ValueDecl *decl) { - if (!decl) - return false; - if (auto *attr = decl->getAttr<AttrT>()) - return attr->isImplicit(); - return decl->isImplicit(); -} - -// TODO(cir): This should be shared with OG Codegen. -bool CIRGenModule::shouldEmitCUDAGlobalVar(const VarDecl *global) const { - assert(langOpts.CUDA && "Should not be called by non-CUDA languages"); - // We need to emit host-side 'shadows' for all global - // device-side variables because the CUDA runtime needs their - // size and host-side address in order to provide access to - // their device-side incarnations. - return !langOpts.CUDAIsDevice || global->hasAttr<CUDADeviceAttr>() || - global->hasAttr<CUDAConstantAttr>() || - global->hasAttr<CUDASharedAttr>() || - global->getType()->isCUDADeviceBuiltinSurfaceType() || - global->getType()->isCUDADeviceBuiltinTextureType(); -} - void CIRGenModule::printPostfixForExternalizedDecl(llvm::raw_ostream &os, const Decl *d) { // ptxas does not allow '.' in symbol names. On the other hand, HIP prefers @@ -587,34 +565,8 @@ void CIRGenModule::emitGlobal(clang::GlobalDecl gd) { } // If this is CUDA, be selective about which declarations we emit. - // Non-constexpr non-lambda implicit host device functions are not emitted - // unless they are used on device side. - if (langOpts.CUDA) { - assert((isa<FunctionDecl>(global) || isa<VarDecl>(global)) && - "Expected Variable or Function"); - if (const auto *varDecl = dyn_cast<VarDecl>(global)) { - if (!shouldEmitCUDAGlobalVar(varDecl)) - return; - // TODO(cir): This should be shared with OG Codegen. - } else if (langOpts.CUDAIsDevice) { - const auto *functionDecl = dyn_cast<FunctionDecl>(global); - if ((!global->hasAttr<CUDADeviceAttr>() || - (langOpts.OffloadImplicitHostDeviceTemplates && - hasImplicitAttr<CUDAHostAttr>(functionDecl) && - hasImplicitAttr<CUDADeviceAttr>(functionDecl) && - !functionDecl->isConstexpr() && - !isLambdaCallOperator(functionDecl) && - !getASTContext().CUDAImplicitHostDeviceFunUsedByDevice.count( - functionDecl))) && - !global->hasAttr<CUDAGlobalAttr>() && - !(langOpts.HIPStdPar && isa<FunctionDecl>(global) && - !global->hasAttr<CUDAHostAttr>())) - return; - // Device-only functions are the only things we skip. - } else if (!global->hasAttr<CUDAHostAttr>() && - global->hasAttr<CUDADeviceAttr>()) - return; - } + if (langOpts.CUDA && !getASTContext().shouldEmitCUDADecl(global)) + return; if (langOpts.OpenMP) { // If this is OpenMP, check if it is legal to emit this global normally. @@ -781,7 +733,7 @@ void CIRGenModule::addGlobalDtor(cir::FuncOp dtor, void CIRGenModule::handleCXXStaticMemberVarInstantiation(VarDecl *vd) { VarDecl::DefinitionKind dk = vd->isThisDeclarationADefinition(); if ((dk == VarDecl::Definition && vd->hasAttr<DLLImportAttr>()) || - (langOpts.CUDA && !shouldEmitCUDAGlobalVar(vd))) + (langOpts.CUDA && !getASTContext().shouldEmitCUDADecl(vd))) return; TemplateSpecializationKind tsk = vd->getTemplateSpecializationKind(); diff --git a/clang/lib/CIR/CodeGen/CIRGenModule.h b/clang/lib/CIR/CodeGen/CIRGenModule.h index 22c3c391ee7d8..ee6f528b4938d 100644 --- a/clang/lib/CIR/CodeGen/CIRGenModule.h +++ b/clang/lib/CIR/CodeGen/CIRGenModule.h @@ -784,10 +784,6 @@ class CIRGenModule : public CIRGenTypeCache { static void setInitializer(cir::GlobalOp &op, mlir::Attribute value); - // Whether a global variable should be emitted by CUDA/HIP host/device - // related attributes. - bool shouldEmitCUDAGlobalVar(const VarDecl *global) const; - /// Print the postfix for externalized static variable or kernels for single /// source offloading languages CUDA and HIP. The unique postfix is created /// using either the CUID argument, or the file's UniqueID and active macros. diff --git a/clang/lib/CodeGen/CodeGenModule.cpp b/clang/lib/CodeGen/CodeGenModule.cpp index bce2106a74988..c058c2a7abdf4 100644 --- a/clang/lib/CodeGen/CodeGenModule.cpp +++ b/clang/lib/CodeGen/CodeGenModule.cpp @@ -4749,14 +4749,6 @@ ConstantAddress CodeGenModule::GetWeakRefReference(const ValueDecl *VD) { return ConstantAddress(Aliasee, DeclTy, Alignment); } -template <typename AttrT> static bool hasImplicitAttr(const ValueDecl *D) { - if (!D) - return false; - if (auto *A = D->getAttr<AttrT>()) - return A->isImplicit(); - return D->isImplicit(); -} - static bool shouldSkipAliasEmission(const CodeGenModule &CGM, const ValueDecl *Global) { const LangOptions &LangOpts = CGM.getLangOpts(); @@ -4789,19 +4781,6 @@ static bool shouldSkipAliasEmission(const CodeGenModule &CGM, return false; } -bool CodeGenModule::shouldEmitCUDAGlobalVar(const VarDecl *Global) const { - assert(LangOpts.CUDA && "Should not be called by non-CUDA languages"); - // We need to emit host-side 'shadows' for all global - // device-side variables because the CUDA runtime needs their - // size and host-side address in order to provide access to - // their device-side incarnations. - return !LangOpts.CUDAIsDevice || Global->hasAttr<CUDADeviceAttr>() || - Global->hasAttr<CUDAConstantAttr>() || - Global->hasAttr<CUDASharedAttr>() || - Global->getType()->isCUDADeviceBuiltinSurfaceType() || - Global->getType()->isCUDADeviceBuiltinTextureType(); -} - void CodeGenModule::EmitGlobal(GlobalDecl GD) { const auto *Global = cast<ValueDecl>(GD.getDecl()); @@ -4826,31 +4805,8 @@ void CodeGenModule::EmitGlobal(GlobalDecl GD) { return emitCPUDispatchDefinition(GD); // If this is CUDA, be selective about which declarations we emit. - // Non-constexpr non-lambda implicit host device functions are not emitted - // unless they are used on device side. - if (LangOpts.CUDA) { - assert((isa<FunctionDecl>(Global) || isa<VarDecl>(Global)) && - "Expected Variable or Function"); - if (const auto *VD = dyn_cast<VarDecl>(Global)) { - if (!shouldEmitCUDAGlobalVar(VD)) - return; - } else if (LangOpts.CUDAIsDevice) { - const auto *FD = dyn_cast<FunctionDecl>(Global); - if ((!Global->hasAttr<CUDADeviceAttr>() || - (LangOpts.OffloadImplicitHostDeviceTemplates && - hasImplicitAttr<CUDAHostAttr>(FD) && - hasImplicitAttr<CUDADeviceAttr>(FD) && !FD->isConstexpr() && - !isLambdaCallOperator(FD) && - !getContext().CUDAImplicitHostDeviceFunUsedByDevice.count(FD))) && - !Global->hasAttr<CUDAGlobalAttr>() && - !(LangOpts.HIPStdPar && isa<FunctionDecl>(Global) && - !Global->hasAttr<CUDAHostAttr>())) - return; - // Device-only functions are the only things we skip. - } else if (!Global->hasAttr<CUDAHostAttr>() && - Global->hasAttr<CUDADeviceAttr>()) - return; - } + if (LangOpts.CUDA && !getContext().shouldEmitCUDADecl(Global)) + return; if (LangOpts.OpenMP) { // If this is OpenMP, check if it is legal to emit this global normally. @@ -7042,7 +6998,7 @@ static void ReplaceUsesOfNonProtoTypeWithRealFunction(llvm::GlobalValue *Old, void CodeGenModule::HandleCXXStaticMemberVarInstantiation(VarDecl *VD) { auto DK = VD->isThisDeclarationADefinition(); if ((DK == VarDecl::Definition && VD->hasAttr<DLLImportAttr>()) || - (LangOpts.CUDA && !shouldEmitCUDAGlobalVar(VD))) + (LangOpts.CUDA && !getContext().shouldEmitCUDADecl(VD))) return; TemplateSpecializationKind TSK = VD->getTemplateSpecializationKind(); diff --git a/clang/lib/CodeGen/CodeGenModule.h b/clang/lib/CodeGen/CodeGenModule.h index cb1f910366829..cd0b775c567f9 100644 --- a/clang/lib/CodeGen/CodeGenModule.h +++ b/clang/lib/CodeGen/CodeGenModule.h @@ -654,9 +654,6 @@ class CodeGenModule : public CodeGenTypeCache { void createHLSLRuntime(); bool shouldEmitFunction(GlobalDecl GD); - // Whether a global variable should be emitted by CUDA/HIP host/device - // related attributes. - bool shouldEmitCUDAGlobalVar(const VarDecl *VD) const; bool shouldOpportunisticallyEmitVTables(); /// Map used to be sure we don't emit the same CompoundLiteral twice. llvm::DenseMap<const CompoundLiteralExpr *, llvm::GlobalVariable *> _______________________________________________ cfe-commits mailing list [email protected] https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits
