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

Reply via email to