Skip to content

Commit ceaed6c

Browse files
committed
[CUDA][HIP] Defer diagnostics for unused implicit H+D functions
Implicit H+D attributes are usually added to host functions by speculative, optimistic heuristics. For example, constexpr functions are implicitly marked H+D assuming they only call other constexpr functions and therefore work on both host and device. In practice, a constexpr function can still call a non-constexpr host function on a runtime path, which makes that body unusable for device code. A recent PR fixed this for implicit H+D functions forced by explicit instantiation: defer device diagnostics until end of TU, then either emit them for an organic device caller or discard them and use the trap-body fallback if CodeGen still needs a device symbol. The same reasoning applies to any implicit H+D function. The device side is speculative until real device code reaches it, so defer these diagnostics generally. This avoids rejecting host-only uses while preserving diagnostics for real device callers.
1 parent 48cb072 commit ceaed6c

6 files changed

Lines changed: 165 additions & 37 deletions

File tree

clang/lib/CodeGen/CGCXX.cpp

Lines changed: 3 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -237,13 +237,9 @@ void CodeGenModule::EmitDefinitionAsAlias(GlobalDecl AliasDecl,
237237
SetCommonAttributes(AliasDecl, Alias);
238238
}
239239

240-
// For an implicit __host__ __device__ destructor, this trap body is reachable
241-
// only when a host-allocated object is destroyed on the device through the
242-
// vtable. HIP documents that pattern as invalid: an object with virtual
243-
// member functions constructed on the host cannot be destroyed on the device.
244-
// Device-side construction either pulls the dtor in as an organic device
245-
// caller (errors surface in Sema) or compiles cleanly (the real body is
246-
// emitted, no trap).
240+
// Invalid implicit H+D functions get a trap body when CodeGen still needs a
241+
// device symbol, such as a vtable slot or explicit instantiation symbol.
242+
// Organic device use surfaces the original Sema diagnostics instead.
247243
bool CodeGenModule::tryEmitCUDADeviceInvalidFunctionBody(GlobalDecl GD,
248244
llvm::Function *Fn) {
249245
if (!getLangOpts().CUDAIsDevice)

clang/lib/Sema/Sema.cpp

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -2103,14 +2103,15 @@ void Sema::emitDeferredDiags() {
21032103
ExternalSource->ReadDeclsToCheckForDeferredDiags(
21042104
DeclsToCheckForDeferredDiags);
21052105

2106-
// For each implicit-H+D-explicit-inst function with deferred errors but no
2107-
// organic device caller, drop the diagnostics and mark for a trap body.
2108-
auto ClassifyImplicitHDExplicitInst = [&]() {
2106+
// For selected implicit-H+D functions with deferred device errors but no
2107+
// organic device caller, drop diagnostics and mark a trap body if CodeGen
2108+
// still needs a device symbol.
2109+
auto ClassifyImplicitHDDeviceDiags = [&]() {
21092110
if (!LangOpts.CUDAIsDevice)
21102111
return;
21112112
for (auto &Pair : DeviceDeferredDiags) {
21122113
const FunctionDecl *FD = Pair.first;
2113-
if (!SemaCUDA::isImplicitHDExplicitInstantiation(FD))
2114+
if (!SemaCUDA::isImplicitHostDeviceFunction(FD))
21142115
continue;
21152116
if (CUDA().DeviceKnownEmittedFns.count(FD))
21162117
continue;
@@ -2129,14 +2130,14 @@ void Sema::emitDeferredDiags() {
21292130

21302131
if ((DeviceDeferredDiags.empty() && !LangOpts.OpenMP) ||
21312132
DeclsToCheckForDeferredDiags.empty()) {
2132-
ClassifyImplicitHDExplicitInst();
2133+
ClassifyImplicitHDDeviceDiags();
21332134
return;
21342135
}
21352136

21362137
DeferredDiagnosticsEmitter DDE(*this);
21372138
for (auto *D : DeclsToCheckForDeferredDiags)
21382139
DDE.checkRecordedDecl(D);
2139-
ClassifyImplicitHDExplicitInst();
2140+
ClassifyImplicitHDDeviceDiags();
21402141
DDE.emitCollectedDiags();
21412142
}
21422143

clang/lib/Sema/SemaBase.cpp

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -65,12 +65,12 @@ Sema::SemaDiagnosticBuilder SemaBase::Diag(SourceLocation Loc,
6565
bool ShouldDefer = getLangOpts().CUDA && getLangOpts().GPUDeferDiag &&
6666
DiagnosticIDs::isDeferrable(DiagID) &&
6767
(SemaRef.DeferDiags || !IsError);
68-
// Even without -fgpu-defer-diag, defer device-side errors inside an
69-
// implicit-H+D explicit instantiation so end-of-TU classification can
70-
// choose between surfacing them or emitting a trap body.
68+
// Even without -fgpu-defer-diag, defer device-side errors inside selected
69+
// implicit-H+D functions so end-of-TU classification can choose between
70+
// surfacing them, discarding them, or emitting a trap body.
7171
if (!ShouldDefer && getLangOpts().CUDA && getLangOpts().CUDAIsDevice &&
7272
DiagnosticIDs::isDeferrable(DiagID) &&
73-
SemaCUDA::isImplicitHDExplicitInstantiation(
73+
SemaCUDA::isImplicitHostDeviceFunction(
7474
SemaRef.getCurFunctionDecl(/*AllowLambda=*/true)))
7575
ShouldDefer = true;
7676
auto SetIsLastErrorImmediate = [&](bool Flag) {

clang/lib/Sema/SemaCUDA.cpp

Lines changed: 18 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -926,7 +926,7 @@ SemaBase::SemaDiagnosticBuilder SemaCUDA::DiagIfDeviceCode(SourceLocation Loc,
926926
if (SemaRef.IsLastErrorImmediate &&
927927
getDiagnostics().getDiagnosticIDs()->isNote(DiagID))
928928
return SemaDiagnosticBuilder::K_Immediate;
929-
if (isImplicitHDExplicitInstantiation(CurFunContext))
929+
if (isImplicitHostDeviceFunction(CurFunContext))
930930
return SemaDiagnosticBuilder::K_Deferred;
931931
return (SemaRef.getEmissionStatus(CurFunContext) ==
932932
Sema::FunctionEmissionStatus::Emitted)
@@ -995,25 +995,23 @@ bool SemaCUDA::CheckCall(SourceLocation Loc, FunctionDecl *Callee) {
995995
// Otherwise, mark the call in our call graph so we can traverse it later.
996996
bool CallerKnownEmitted = SemaRef.getEmissionStatus(Caller) ==
997997
Sema::FunctionEmissionStatus::Emitted;
998-
bool CallerIsImplicitHDExplicitInst =
999-
isImplicitHDExplicitInstantiation(Caller);
1000-
SemaDiagnosticBuilder::Kind DiagKind = [this, Caller, Callee,
1001-
CallerKnownEmitted,
1002-
CallerIsImplicitHDExplicitInst] {
1003-
switch (IdentifyPreference(Caller, Callee)) {
1004-
case CFP_Never:
1005-
case CFP_WrongSide:
1006-
assert(Caller && "Never/wrongSide calls require a non-null caller");
1007-
// If we know the caller will be emitted, we know this wrong-side call
1008-
// will be emitted, so it's an immediate error. Otherwise, defer the
1009-
// error until we know the caller is emitted.
1010-
return (CallerKnownEmitted && !CallerIsImplicitHDExplicitInst)
1011-
? SemaDiagnosticBuilder::K_ImmediateWithCallStack
1012-
: SemaDiagnosticBuilder::K_Deferred;
1013-
default:
1014-
return SemaDiagnosticBuilder::K_Nop;
1015-
}
1016-
}();
998+
bool DeferImplicitHDDeviceDiag = isImplicitHostDeviceFunction(Caller);
999+
SemaDiagnosticBuilder::Kind DiagKind =
1000+
[this, Caller, Callee, CallerKnownEmitted, DeferImplicitHDDeviceDiag] {
1001+
switch (IdentifyPreference(Caller, Callee)) {
1002+
case CFP_Never:
1003+
case CFP_WrongSide:
1004+
assert(Caller && "Never/wrongSide calls require a non-null caller");
1005+
// If we know the caller will be emitted, we know this wrong-side call
1006+
// will be emitted, so it's an immediate error. Otherwise, defer the
1007+
// error until we know the caller is emitted.
1008+
return (CallerKnownEmitted && !DeferImplicitHDDeviceDiag)
1009+
? SemaDiagnosticBuilder::K_ImmediateWithCallStack
1010+
: SemaDiagnosticBuilder::K_Deferred;
1011+
default:
1012+
return SemaDiagnosticBuilder::K_Nop;
1013+
}
1014+
}();
10171015

10181016
bool IsDeviceKernelCall = Callee == getASTContext().getcudaLaunchDeviceDecl();
10191017
bool CallerHD = Caller && Caller->hasAttr<CUDAHostAttr>() &&
Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
1+
// RUN: %clang_cc1 -triple nvptx64-nvidia-cuda -fcuda-is-device \
2+
// RUN: -foffload-implicit-host-device-templates -std=c++14 \
3+
// RUN: -fsyntax-only -verify %s
4+
// RUN: %clang_cc1 -triple amdgcn -fcuda-is-device \
5+
// RUN: -foffload-implicit-host-device-templates -std=c++14 \
6+
// RUN: -fsyntax-only -verify %s
7+
8+
#include "Inputs/cuda.h"
9+
10+
__host__ constexpr int host_only_constexpr_unused() { return 1; }
11+
12+
constexpr int constexpr_unused(int x) {
13+
return x + host_only_constexpr_unused();
14+
}
15+
16+
extern "C" int host_only_template_unused();
17+
18+
template <typename T> int template_unused(T x) {
19+
return x + host_only_template_unused();
20+
}
21+
22+
extern "C" int host_only_forced_unused();
23+
24+
#pragma clang force_cuda_host_device begin
25+
int forced_unused(int x) {
26+
return x + host_only_forced_unused();
27+
}
28+
#pragma clang force_cuda_host_device end
29+
30+
void host_context() {
31+
(void)constexpr_unused(1);
32+
(void)template_unused(1);
33+
(void)forced_unused(1);
34+
}
35+
36+
__host__ constexpr int host_only_constexpr_used() { return 1; }
37+
// expected-note@-1 {{'host_only_constexpr_used' declared here}}
38+
39+
constexpr int constexpr_used(int x) {
40+
return x + host_only_constexpr_used();
41+
// expected-error@-1 {{reference to __host__ function 'host_only_constexpr_used' in __host__ __device__ function}}
42+
}
43+
44+
extern "C" int host_only_template_used();
45+
// expected-note@-1 {{'host_only_template_used' declared here}}
46+
47+
template <typename T> int template_used(T x) {
48+
return x + host_only_template_used();
49+
// expected-error@-1 {{reference to __host__ function 'host_only_template_used' in __host__ __device__ function}}
50+
}
51+
52+
extern "C" int host_only_forced_used();
53+
// expected-note@-1 {{'host_only_forced_used' declared here}}
54+
55+
#pragma clang force_cuda_host_device begin
56+
int forced_used(int x) {
57+
return x + host_only_forced_used();
58+
// expected-error@-1 {{reference to __host__ function 'host_only_forced_used' in __host__ __device__ function}}
59+
}
60+
#pragma clang force_cuda_host_device end
61+
62+
__device__ int device_caller() {
63+
return constexpr_used(1) + template_used(1) + forced_used(1);
64+
// expected-note@-1 {{called by 'device_caller'}}
65+
// expected-note@-2 {{called by 'device_caller'}}
66+
// expected-note@-3 {{called by 'device_caller'}}
67+
}
68+
69+
__global__ void kernel(int *out) {
70+
*out = device_caller();
71+
// expected-note@-1 {{called by 'kernel'}}
72+
// expected-note@-2 {{called by 'kernel'}}
73+
// expected-note@-3 {{called by 'kernel'}}
74+
}
Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
// RUN: %clang_cc1 -triple nvptx64-nvidia-cuda -fcuda-is-device \
2+
// RUN: -foffload-implicit-host-device-templates -std=c++14 \
3+
// RUN: -fsyntax-only -verify %s
4+
// RUN: %clang_cc1 -triple amdgcn -fcuda-is-device \
5+
// RUN: -foffload-implicit-host-device-templates -std=c++14 \
6+
// RUN: -fsyntax-only -verify %s
7+
8+
#include "Inputs/cuda.h"
9+
10+
__host__ __device__ constexpr int pick_constexpr_unused(long);
11+
__host__ __device__ constexpr int pick_constexpr_unused(unsigned long);
12+
13+
constexpr int constexpr_unused(int x) {
14+
return pick_constexpr_unused(x);
15+
}
16+
17+
__host__ __device__ constexpr int pick_constexpr_used(long);
18+
// expected-note@-1 {{candidate function}}
19+
__host__ __device__ constexpr int pick_constexpr_used(unsigned long);
20+
// expected-note@-1 {{candidate function}}
21+
22+
constexpr int constexpr_used(int x) {
23+
return pick_constexpr_used(x);
24+
// expected-error@-1 {{call to 'pick_constexpr_used' is ambiguous}}
25+
}
26+
27+
__host__ __device__ int pick_template_unused(long);
28+
__host__ __device__ int pick_template_unused(unsigned long);
29+
30+
template <typename T> int template_unused(T x) {
31+
return pick_template_unused(x);
32+
}
33+
34+
void host_only() {
35+
(void)constexpr_unused(1);
36+
(void)template_unused(1);
37+
}
38+
39+
__host__ __device__ int pick_template_used(long);
40+
// expected-note@-1 {{candidate function}}
41+
__host__ __device__ int pick_template_used(unsigned long);
42+
// expected-note@-1 {{candidate function}}
43+
44+
template <typename T> int template_used(T x) {
45+
return pick_template_used(x);
46+
// expected-error@-1 {{call to 'pick_template_used' is ambiguous}}
47+
}
48+
49+
__device__ int device_caller() {
50+
return constexpr_used(1) + template_used(1);
51+
// expected-note@-1 {{called by 'device_caller'}}
52+
// expected-note@-2 {{called by 'device_caller'}}
53+
}
54+
55+
__global__ void kernel(int *out) {
56+
*out = device_caller();
57+
// expected-note@-1 {{called by 'kernel'}}
58+
// expected-note@-2 {{called by 'kernel'}}
59+
}

0 commit comments

Comments
 (0)