Skip to content

Commit 0ceb4ef

Browse files
yxsamliutstellar
authored andcommitted
[CUDA][HIP] fix virtual dtor host/device attr (llvm#128926)
When inferring host device attr of virtual dtor of explicit template class instantiation, clang should be conservative. This guarantees dtors that may call host functions not to have implicit device attr, therefore will not be emitted on device side. Backports: 0f0665d d37a392 Fixes: llvm#108548
1 parent 1058e69 commit 0ceb4ef

File tree

6 files changed

+204
-3
lines changed

6 files changed

+204
-3
lines changed

clang/docs/HIPSupport.rst

+20
Original file line numberDiff line numberDiff line change
@@ -286,6 +286,26 @@ Example Usage
286286
basePtr->virtualFunction(); // Allowed since obj is constructed in device code
287287
}
288288

289+
Host and Device Attributes of Default Destructors
290+
===================================================
291+
292+
If a default destructor does not have explicit host or device attributes,
293+
clang infers these attributes based on the destructors of its data members
294+
and base classes. If any conflicts are detected among these destructors,
295+
clang diagnoses the issue. Otherwise, clang adds an implicit host or device
296+
attribute according to whether the data members's and base classes's
297+
destructors can execute on the host or device side.
298+
299+
For explicit template classes with virtual destructors, which must be emitted,
300+
the inference adopts a conservative approach. In this case, implicit host or
301+
device attributes from member and base class destructors are ignored. This
302+
precaution is necessary because, although a constexpr destructor carries
303+
implicit host or device attributes, a constexpr function may call a
304+
non-constexpr function, which is by default a host function.
305+
306+
Users can override the inferred host and device attributes of default
307+
destructors by adding explicit host and device attributes to them.
308+
289309
C++ Standard Parallelism Offload Support: Compiler And Runtime
290310
==============================================================
291311

clang/include/clang/Sema/Sema.h

+1-1
Original file line numberDiff line numberDiff line change
@@ -4336,11 +4336,11 @@ class Sema final : public SemaBase {
43364336
// Whether the callee should be ignored in CUDA/HIP/OpenMP host/device check.
43374337
bool shouldIgnoreInHostDeviceCheck(FunctionDecl *Callee);
43384338

4339-
private:
43404339
/// Function or variable declarations to be checked for whether the deferred
43414340
/// diagnostics should be emitted.
43424341
llvm::SmallSetVector<Decl *, 4> DeclsToCheckForDeferredDiags;
43434342

4343+
private:
43444344
/// Map of current shadowing declarations to shadowed declarations. Warn if
43454345
/// it looks like the user is trying to modify the shadowing declaration.
43464346
llvm::DenseMap<const NamedDecl *, const NamedDecl *> ShadowingDecls;

clang/lib/Sema/Sema.cpp

+43
Original file line numberDiff line numberDiff line change
@@ -1789,6 +1789,47 @@ class DeferredDiagnosticsEmitter
17891789
Inherited::visitUsedDecl(Loc, D);
17901790
}
17911791

1792+
// Visitor member and parent dtors called by this dtor.
1793+
void VisitCalledDestructors(CXXDestructorDecl *DD) {
1794+
const CXXRecordDecl *RD = DD->getParent();
1795+
1796+
// Visit the dtors of all members
1797+
for (const FieldDecl *FD : RD->fields()) {
1798+
QualType FT = FD->getType();
1799+
if (const auto *RT = FT->getAs<RecordType>())
1800+
if (const auto *ClassDecl = dyn_cast<CXXRecordDecl>(RT->getDecl()))
1801+
if (ClassDecl->hasDefinition())
1802+
if (CXXDestructorDecl *MemberDtor = ClassDecl->getDestructor())
1803+
asImpl().visitUsedDecl(MemberDtor->getLocation(), MemberDtor);
1804+
}
1805+
1806+
// Also visit base class dtors
1807+
for (const auto &Base : RD->bases()) {
1808+
QualType BaseType = Base.getType();
1809+
if (const auto *RT = BaseType->getAs<RecordType>())
1810+
if (const auto *BaseDecl = dyn_cast<CXXRecordDecl>(RT->getDecl()))
1811+
if (BaseDecl->hasDefinition())
1812+
if (CXXDestructorDecl *BaseDtor = BaseDecl->getDestructor())
1813+
asImpl().visitUsedDecl(BaseDtor->getLocation(), BaseDtor);
1814+
}
1815+
}
1816+
1817+
void VisitDeclStmt(DeclStmt *DS) {
1818+
// Visit dtors called by variables that need destruction
1819+
for (auto *D : DS->decls())
1820+
if (auto *VD = dyn_cast<VarDecl>(D))
1821+
if (VD->isThisDeclarationADefinition() &&
1822+
VD->needsDestruction(S.Context)) {
1823+
QualType VT = VD->getType();
1824+
if (const auto *RT = VT->getAs<RecordType>())
1825+
if (const auto *ClassDecl = dyn_cast<CXXRecordDecl>(RT->getDecl()))
1826+
if (ClassDecl->hasDefinition())
1827+
if (CXXDestructorDecl *Dtor = ClassDecl->getDestructor())
1828+
asImpl().visitUsedDecl(Dtor->getLocation(), Dtor);
1829+
}
1830+
1831+
Inherited::VisitDeclStmt(DS);
1832+
}
17921833
void checkVar(VarDecl *VD) {
17931834
assert(VD->isFileVarDecl() &&
17941835
"Should only check file-scope variables");
@@ -1830,6 +1871,8 @@ class DeferredDiagnosticsEmitter
18301871
if (auto *S = FD->getBody()) {
18311872
this->Visit(S);
18321873
}
1874+
if (CXXDestructorDecl *Dtor = dyn_cast<CXXDestructorDecl>(FD))
1875+
asImpl().VisitCalledDestructors(Dtor);
18331876
UsePath.pop_back();
18341877
InUsePath.erase(FD);
18351878
}

clang/lib/Sema/SemaCUDA.cpp

+21-2
Original file line numberDiff line numberDiff line change
@@ -372,6 +372,21 @@ bool SemaCUDA::inferTargetForImplicitSpecialMember(CXXRecordDecl *ClassDecl,
372372
CXXMethodDecl *MemberDecl,
373373
bool ConstRHS,
374374
bool Diagnose) {
375+
// If MemberDecl is virtual destructor of an explicit template class
376+
// instantiation, it must be emitted, therefore it needs to be inferred
377+
// conservatively by ignoring implicit host/device attrs of member and parent
378+
// dtors called by it. Also, it needs to be checed by deferred diag visitor.
379+
bool IsExpVDtor = false;
380+
if (isa<CXXDestructorDecl>(MemberDecl) && MemberDecl->isVirtual()) {
381+
if (auto *Spec = dyn_cast<ClassTemplateSpecializationDecl>(ClassDecl)) {
382+
TemplateSpecializationKind TSK = Spec->getTemplateSpecializationKind();
383+
IsExpVDtor = TSK == TSK_ExplicitInstantiationDeclaration ||
384+
TSK == TSK_ExplicitInstantiationDefinition;
385+
}
386+
}
387+
if (IsExpVDtor)
388+
SemaRef.DeclsToCheckForDeferredDiags.insert(MemberDecl);
389+
375390
// If the defaulted special member is defined lexically outside of its
376391
// owning class, or the special member already has explicit device or host
377392
// attributes, do not infer.
@@ -422,7 +437,9 @@ bool SemaCUDA::inferTargetForImplicitSpecialMember(CXXRecordDecl *ClassDecl,
422437
if (!SMOR.getMethod())
423438
continue;
424439

425-
CUDAFunctionTarget BaseMethodTarget = IdentifyTarget(SMOR.getMethod());
440+
CUDAFunctionTarget BaseMethodTarget =
441+
IdentifyTarget(SMOR.getMethod(), IsExpVDtor);
442+
426443
if (!InferredTarget) {
427444
InferredTarget = BaseMethodTarget;
428445
} else {
@@ -466,7 +483,9 @@ bool SemaCUDA::inferTargetForImplicitSpecialMember(CXXRecordDecl *ClassDecl,
466483
if (!SMOR.getMethod())
467484
continue;
468485

469-
CUDAFunctionTarget FieldMethodTarget = IdentifyTarget(SMOR.getMethod());
486+
CUDAFunctionTarget FieldMethodTarget =
487+
IdentifyTarget(SMOR.getMethod(), IsExpVDtor);
488+
470489
if (!InferredTarget) {
471490
InferredTarget = FieldMethodTarget;
472491
} else {

clang/lib/Sema/SemaDecl.cpp

+15
Original file line numberDiff line numberDiff line change
@@ -20388,6 +20388,21 @@ Sema::FunctionEmissionStatus Sema::getEmissionStatus(const FunctionDecl *FD,
2038820388

2038920389
if (IsEmittedForExternalSymbol())
2039020390
return FunctionEmissionStatus::Emitted;
20391+
20392+
// If FD is a virtual destructor of an explicit instantiation
20393+
// of a template class, return Emitted.
20394+
if (auto *Destructor = dyn_cast<CXXDestructorDecl>(FD)) {
20395+
if (Destructor->isVirtual()) {
20396+
if (auto *Spec = dyn_cast<ClassTemplateSpecializationDecl>(
20397+
Destructor->getParent())) {
20398+
TemplateSpecializationKind TSK =
20399+
Spec->getTemplateSpecializationKind();
20400+
if (TSK == TSK_ExplicitInstantiationDeclaration ||
20401+
TSK == TSK_ExplicitInstantiationDefinition)
20402+
return FunctionEmissionStatus::Emitted;
20403+
}
20404+
}
20405+
}
2039120406
}
2039220407

2039320408
// Otherwise, the function is known-emitted if it's in our set of

clang/test/SemaCUDA/dtor.cu

+104
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,104 @@
1+
// RUN: %clang_cc1 %s -std=c++20 -fsyntax-only -verify=host
2+
// RUN: %clang_cc1 %s -std=c++20 -fcuda-is-device -fsyntax-only -verify=dev
3+
4+
// host-no-diagnostics
5+
6+
#include "Inputs/cuda.h"
7+
8+
// Virtual dtor ~B() of explicit instantiation B<float> must
9+
// be emitted, which causes host_fun() called.
10+
namespace ExplicitInstantiationExplicitDevDtor {
11+
void host_fun() // dev-note {{'host_fun' declared here}}
12+
{}
13+
14+
template <unsigned>
15+
constexpr void hd_fun() {
16+
host_fun(); // dev-error {{reference to __host__ function 'host_fun' in __host__ __device__ function}}
17+
}
18+
19+
struct A {
20+
constexpr ~A() { // dev-note {{called by '~B'}}
21+
hd_fun<8>(); // dev-note {{called by '~A'}}
22+
}
23+
};
24+
25+
template <typename T>
26+
struct B {
27+
public:
28+
virtual __device__ ~B() = default;
29+
A _a;
30+
};
31+
32+
template class B<float>;
33+
}
34+
35+
// The implicit host/device attrs of virtual dtor ~B() should be
36+
// conservatively inferred, where constexpr member dtor's should
37+
// not be considered device since they may call host functions.
38+
// Therefore B<float>::~B() should not have implicit device attr.
39+
// However C<float>::~C() should have implicit device attr since
40+
// it is trivial.
41+
namespace ExplicitInstantiationDtorNoAttr {
42+
void host_fun()
43+
{}
44+
45+
template <unsigned>
46+
constexpr void hd_fun() {
47+
host_fun();
48+
}
49+
50+
struct A {
51+
constexpr ~A() {
52+
hd_fun<8>();
53+
}
54+
};
55+
56+
template <typename T>
57+
struct B {
58+
public:
59+
virtual ~B() = default;
60+
A _a;
61+
};
62+
63+
template <typename T>
64+
struct C {
65+
public:
66+
virtual ~C() = default;
67+
};
68+
69+
template class B<float>;
70+
template class C<float>;
71+
__device__ void foo() {
72+
C<float> x;
73+
}
74+
}
75+
76+
// Dtors of implicit template class instantiation are not
77+
// conservatively inferred because the invalid usage can
78+
// be diagnosed.
79+
namespace ImplicitInstantiation {
80+
void host_fun() // dev-note {{'host_fun' declared here}}
81+
{}
82+
83+
template <unsigned>
84+
constexpr void hd_fun() {
85+
host_fun(); // dev-error {{reference to __host__ function 'host_fun' in __host__ __device__ function}}
86+
}
87+
88+
struct A {
89+
constexpr ~A() { // dev-note {{called by '~B'}}
90+
hd_fun<8>(); // dev-note {{called by '~A'}}
91+
}
92+
};
93+
94+
template <typename T>
95+
struct B {
96+
public:
97+
~B() = default; // dev-note {{called by 'foo'}}
98+
A _a;
99+
};
100+
101+
__device__ void foo() {
102+
B<float> x;
103+
}
104+
}

0 commit comments

Comments
 (0)