Skip to content

Commit 0f0665d

Browse files
authored
[CUDA][HIP] check dtor in deferred diag (llvm#129117)
Currently the deferred diag fails to diagnose calling of host function in host device function in device compilation triggered by destructors. This can be further divided into two issuse: 1. the deferred diag visitor does not visit dtor of member and parent class when visiting dtor, which it should 2. the deferred diag visitor does not visit virtual dtor of explicit template class instantiation, which it should Due to these issues, some constexpr functions which call host functions are emitted on device side, which causes undefind symbols in linking stage, as revealed by llvm#108548 By fixing these issue, clang will diag the issues early during compilation instead of linking.
1 parent e6a0ee3 commit 0f0665d

File tree

5 files changed

+176
-1
lines changed

5 files changed

+176
-1
lines changed

clang/include/clang/Sema/Sema.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4392,11 +4392,11 @@ class Sema final : public SemaBase {
43924392
// Whether the callee should be ignored in CUDA/HIP/OpenMP host/device check.
43934393
bool shouldIgnoreInHostDeviceCheck(FunctionDecl *Callee);
43944394

4395-
private:
43964395
/// Function or variable declarations to be checked for whether the deferred
43974396
/// diagnostics should be emitted.
43984397
llvm::SmallSetVector<Decl *, 4> DeclsToCheckForDeferredDiags;
43994398

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

clang/lib/Sema/Sema.cpp

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1798,6 +1798,47 @@ class DeferredDiagnosticsEmitter
17981798
Inherited::visitUsedDecl(Loc, D);
17991799
}
18001800

1801+
// Visitor member and parent dtors called by this dtor.
1802+
void VisitCalledDestructors(CXXDestructorDecl *DD) {
1803+
const CXXRecordDecl *RD = DD->getParent();
1804+
1805+
// Visit the dtors of all members
1806+
for (const FieldDecl *FD : RD->fields()) {
1807+
QualType FT = FD->getType();
1808+
if (const auto *RT = FT->getAs<RecordType>())
1809+
if (const auto *ClassDecl = dyn_cast<CXXRecordDecl>(RT->getDecl()))
1810+
if (ClassDecl->hasDefinition())
1811+
if (CXXDestructorDecl *MemberDtor = ClassDecl->getDestructor())
1812+
asImpl().visitUsedDecl(MemberDtor->getLocation(), MemberDtor);
1813+
}
1814+
1815+
// Also visit base class dtors
1816+
for (const auto &Base : RD->bases()) {
1817+
QualType BaseType = Base.getType();
1818+
if (const auto *RT = BaseType->getAs<RecordType>())
1819+
if (const auto *BaseDecl = dyn_cast<CXXRecordDecl>(RT->getDecl()))
1820+
if (BaseDecl->hasDefinition())
1821+
if (CXXDestructorDecl *BaseDtor = BaseDecl->getDestructor())
1822+
asImpl().visitUsedDecl(BaseDtor->getLocation(), BaseDtor);
1823+
}
1824+
}
1825+
1826+
void VisitDeclStmt(DeclStmt *DS) {
1827+
// Visit dtors called by variables that need destruction
1828+
for (auto *D : DS->decls())
1829+
if (auto *VD = dyn_cast<VarDecl>(D))
1830+
if (VD->isThisDeclarationADefinition() &&
1831+
VD->needsDestruction(S.Context)) {
1832+
QualType VT = VD->getType();
1833+
if (const auto *RT = VT->getAs<RecordType>())
1834+
if (const auto *ClassDecl = dyn_cast<CXXRecordDecl>(RT->getDecl()))
1835+
if (ClassDecl->hasDefinition())
1836+
if (CXXDestructorDecl *Dtor = ClassDecl->getDestructor())
1837+
asImpl().visitUsedDecl(Dtor->getLocation(), Dtor);
1838+
}
1839+
1840+
Inherited::VisitDeclStmt(DS);
1841+
}
18011842
void checkVar(VarDecl *VD) {
18021843
assert(VD->isFileVarDecl() &&
18031844
"Should only check file-scope variables");
@@ -1839,6 +1880,8 @@ class DeferredDiagnosticsEmitter
18391880
if (auto *S = FD->getBody()) {
18401881
this->Visit(S);
18411882
}
1883+
if (CXXDestructorDecl *Dtor = dyn_cast<CXXDestructorDecl>(FD))
1884+
asImpl().VisitCalledDestructors(Dtor);
18421885
UsePath.pop_back();
18431886
InUsePath.erase(FD);
18441887
}

clang/lib/Sema/SemaCUDA.cpp

Lines changed: 15 additions & 0 deletions
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.

clang/lib/Sema/SemaDecl.cpp

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20473,6 +20473,21 @@ Sema::FunctionEmissionStatus Sema::getEmissionStatus(const FunctionDecl *FD,
2047320473

2047420474
if (IsEmittedForExternalSymbol())
2047520475
return FunctionEmissionStatus::Emitted;
20476+
20477+
// If FD is a virtual destructor of an explicit instantiation
20478+
// of a template class, return Emitted.
20479+
if (auto *Destructor = dyn_cast<CXXDestructorDecl>(FD)) {
20480+
if (Destructor->isVirtual()) {
20481+
if (auto *Spec = dyn_cast<ClassTemplateSpecializationDecl>(
20482+
Destructor->getParent())) {
20483+
TemplateSpecializationKind TSK =
20484+
Spec->getTemplateSpecializationKind();
20485+
if (TSK == TSK_ExplicitInstantiationDeclaration ||
20486+
TSK == TSK_ExplicitInstantiationDefinition)
20487+
return FunctionEmissionStatus::Emitted;
20488+
}
20489+
}
20490+
}
2047620491
}
2047720492

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

clang/test/SemaCUDA/dtor.cu

Lines changed: 102 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,102 @@
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<float>::~B() is inferred to
36+
// have implicit device attr since dtors of its members and parent classes can
37+
// be executed on device. This causes a diagnostic since B<float>::~B() must
38+
// be emitted, and it eventually causes host_fun() called on device side.
39+
namespace ExplicitInstantiationDtorNoAttr {
40+
void host_fun() // dev-note {{'host_fun' declared here}}
41+
{}
42+
43+
template <unsigned>
44+
constexpr void hd_fun() {
45+
host_fun(); // dev-error{{reference to __host__ function 'host_fun' in __host__ __device__ function}}
46+
}
47+
48+
struct A {
49+
constexpr ~A() { // dev-note {{called by '~B'}}
50+
hd_fun<8>(); // dev-note {{called by '~A'}}
51+
}
52+
};
53+
54+
template <typename T>
55+
struct B {
56+
public:
57+
virtual ~B() = default;
58+
A _a;
59+
};
60+
61+
template <typename T>
62+
struct C {
63+
public:
64+
virtual ~C() = default;
65+
};
66+
67+
template class B<float>;
68+
template class C<float>;
69+
__device__ void foo() {
70+
C<float> x;
71+
}
72+
}
73+
74+
// Dtors of implicit template class instantiation are not
75+
// conservatively inferred because the invalid usage can
76+
// be diagnosed.
77+
namespace ImplicitInstantiation {
78+
void host_fun() // dev-note {{'host_fun' declared here}}
79+
{}
80+
81+
template <unsigned>
82+
constexpr void hd_fun() {
83+
host_fun(); // dev-error {{reference to __host__ function 'host_fun' in __host__ __device__ function}}
84+
}
85+
86+
struct A {
87+
constexpr ~A() { // dev-note {{called by '~B'}}
88+
hd_fun<8>(); // dev-note {{called by '~A'}}
89+
}
90+
};
91+
92+
template <typename T>
93+
struct B {
94+
public:
95+
~B() = default; // dev-note {{called by 'foo'}}
96+
A _a;
97+
};
98+
99+
__device__ void foo() {
100+
B<float> x;
101+
}
102+
}

0 commit comments

Comments
 (0)