When clang explicitly instantiates a class template, it must emit device-side bodies for the implicit `__host__ __device__` members so the vtable and instantiation symbols resolve. Some of those members chain into host-only calls (for example libstdc++ destructors that eventually call ::operator delete). If no device code actually uses the class, the user still sees errors about calling a `__host__` function from device code, even though they wrote no device code that touches it. Overload ambiguity in the same context behaves the same way. This patch defers device-side errors in implicit `__host__ __device__` functions reached only via an explicit template instantiation. At the end of the translation unit, clang checks whether a real device caller exists. If one does, the deferred errors are surfaced with the usual call-stack notes. If not, the diagnostics are dropped and the function gets a trap body, so the vtable and instantiation symbols stay well-formed and the failure only fires if device code ever calls the function. This works without -fgpu-defer-diag.
104 lines
3.9 KiB
C++
104 lines
3.9 KiB
C++
#include "clang/Sema/SemaBase.h"
|
|
#include "clang/Sema/Sema.h"
|
|
#include "clang/Sema/SemaCUDA.h"
|
|
|
|
namespace clang {
|
|
|
|
SemaBase::SemaBase(Sema &S) : SemaRef(S) {}
|
|
|
|
ASTContext &SemaBase::getASTContext() const { return SemaRef.Context; }
|
|
DiagnosticsEngine &SemaBase::getDiagnostics() const { return SemaRef.Diags; }
|
|
const LangOptions &SemaBase::getLangOpts() const { return SemaRef.LangOpts; }
|
|
DeclContext *SemaBase::getCurContext() const { return SemaRef.CurContext; }
|
|
|
|
SemaBase::ImmediateDiagBuilder::~ImmediateDiagBuilder() {
|
|
// If we aren't active, there is nothing to do.
|
|
if (!isActive())
|
|
return;
|
|
|
|
// Otherwise, we need to emit the diagnostic. First clear the diagnostic
|
|
// builder itself so it won't emit the diagnostic in its own destructor.
|
|
//
|
|
// This seems wasteful, in that as written the DiagnosticBuilder dtor will
|
|
// do its own needless checks to see if the diagnostic needs to be
|
|
// emitted. However, because we take care to ensure that the builder
|
|
// objects never escape, a sufficiently smart compiler will be able to
|
|
// eliminate that code.
|
|
Clear();
|
|
|
|
// Dispatch to Sema to emit the diagnostic.
|
|
SemaRef.EmitDiagnostic(DiagID, *this);
|
|
}
|
|
|
|
PartialDiagnostic SemaBase::PDiag(unsigned DiagID) {
|
|
return PartialDiagnostic(DiagID, SemaRef.Context.getDiagAllocator());
|
|
}
|
|
|
|
const SemaBase::SemaDiagnosticBuilder &
|
|
operator<<(const SemaBase::SemaDiagnosticBuilder &Diag,
|
|
const PartialDiagnostic &PD) {
|
|
if (Diag.ImmediateDiag)
|
|
PD.Emit(*Diag.ImmediateDiag);
|
|
else if (Diag.PartialDiagId)
|
|
Diag.S.DeviceDeferredDiags[Diag.Fn][*Diag.PartialDiagId].second = PD;
|
|
return Diag;
|
|
}
|
|
|
|
void SemaBase::SemaDiagnosticBuilder::AddFixItHint(
|
|
const FixItHint &Hint) const {
|
|
if (ImmediateDiag)
|
|
ImmediateDiag->AddFixItHint(Hint);
|
|
else if (PartialDiagId)
|
|
S.DeviceDeferredDiags[Fn][*PartialDiagId].second.AddFixItHint(Hint);
|
|
}
|
|
|
|
llvm::DenseMap<CanonicalDeclPtr<const FunctionDecl>,
|
|
std::vector<PartialDiagnosticAt>> &
|
|
SemaBase::SemaDiagnosticBuilder::getDeviceDeferredDiags() const {
|
|
return S.DeviceDeferredDiags;
|
|
}
|
|
|
|
Sema::SemaDiagnosticBuilder SemaBase::Diag(SourceLocation Loc,
|
|
unsigned DiagID) {
|
|
bool IsError =
|
|
getDiagnostics().getDiagnosticIDs()->isDefaultMappingAsError(DiagID);
|
|
bool ShouldDefer = getLangOpts().CUDA && getLangOpts().GPUDeferDiag &&
|
|
DiagnosticIDs::isDeferrable(DiagID) &&
|
|
(SemaRef.DeferDiags || !IsError);
|
|
// Even without -fgpu-defer-diag, defer device-side errors inside an
|
|
// implicit-H+D explicit instantiation so end-of-TU classification can
|
|
// choose between surfacing them or emitting a trap body.
|
|
if (!ShouldDefer && getLangOpts().CUDA && getLangOpts().CUDAIsDevice &&
|
|
DiagnosticIDs::isDeferrable(DiagID) &&
|
|
SemaCUDA::isImplicitHDExplicitInstantiation(
|
|
SemaRef.getCurFunctionDecl(/*AllowLambda=*/true)))
|
|
ShouldDefer = true;
|
|
auto SetIsLastErrorImmediate = [&](bool Flag) {
|
|
if (IsError)
|
|
SemaRef.IsLastErrorImmediate = Flag;
|
|
};
|
|
if (!ShouldDefer) {
|
|
SetIsLastErrorImmediate(true);
|
|
return SemaDiagnosticBuilder(SemaDiagnosticBuilder::K_Immediate, Loc,
|
|
DiagID, SemaRef.getCurFunctionDecl(), SemaRef);
|
|
}
|
|
|
|
SemaDiagnosticBuilder DB = getLangOpts().CUDAIsDevice
|
|
? SemaRef.CUDA().DiagIfDeviceCode(Loc, DiagID)
|
|
: SemaRef.CUDA().DiagIfHostCode(Loc, DiagID);
|
|
SetIsLastErrorImmediate(DB.isImmediate());
|
|
return DB;
|
|
}
|
|
|
|
Sema::SemaDiagnosticBuilder SemaBase::Diag(SourceLocation Loc,
|
|
const PartialDiagnostic &PD) {
|
|
return Diag(Loc, PD.getDiagID()) << PD;
|
|
}
|
|
|
|
SemaBase::SemaDiagnosticBuilder SemaBase::DiagCompat(SourceLocation Loc,
|
|
unsigned CompatDiagId) {
|
|
return Diag(Loc,
|
|
DiagnosticIDs::getCXXCompatDiagId(getLangOpts(), CompatDiagId));
|
|
}
|
|
} // namespace clang
|