Skip to content

Commit 2c31aa2

Browse files
committed
Speed up deferred diagnostic emitter
Move function emitDeferredDiags from Sema to DeferredDiagsEmitter since it is only used by DeferredDiagsEmitter. Also skip visited functions to avoid exponential compile time. Differential Revision: https://reviews.llvm.org/D77028
1 parent e1730cf commit 2c31aa2

File tree

5 files changed

+181
-54
lines changed

5 files changed

+181
-54
lines changed

clang/include/clang/Sema/Sema.h

-3
Original file line numberDiff line numberDiff line change
@@ -1502,9 +1502,6 @@ class Sema final {
15021502
public:
15031503
// Emit all deferred diagnostics.
15041504
void emitDeferredDiags();
1505-
// Emit any deferred diagnostics for FD and erase them from the map in which
1506-
// they're stored.
1507-
void emitDeferredDiags(FunctionDecl *FD, bool ShowCallStack);
15081505

15091506
enum TUFragmentKind {
15101507
/// The global module fragment, between 'module;' and a module-declaration.

clang/lib/Sema/Sema.cpp

+100-51
Original file line numberDiff line numberDiff line change
@@ -1440,59 +1440,72 @@ Sema::Diag(SourceLocation Loc, const PartialDiagnostic& PD) {
14401440
static void emitCallStackNotes(Sema &S, FunctionDecl *FD) {
14411441
auto FnIt = S.DeviceKnownEmittedFns.find(FD);
14421442
while (FnIt != S.DeviceKnownEmittedFns.end()) {
1443+
// Respect error limit.
1444+
if (S.Diags.hasFatalErrorOccurred())
1445+
return;
14431446
DiagnosticBuilder Builder(
14441447
S.Diags.Report(FnIt->second.Loc, diag::note_called_by));
14451448
Builder << FnIt->second.FD;
1446-
Builder.setForceEmit();
1447-
14481449
FnIt = S.DeviceKnownEmittedFns.find(FnIt->second.FD);
14491450
}
14501451
}
14511452

1452-
// Emit any deferred diagnostics for FD and erase them from the map in which
1453-
// they're stored.
1454-
void Sema::emitDeferredDiags(FunctionDecl *FD, bool ShowCallStack) {
1455-
auto It = DeviceDeferredDiags.find(FD);
1456-
if (It == DeviceDeferredDiags.end())
1457-
return;
1458-
bool HasWarningOrError = false;
1459-
bool FirstDiag = true;
1460-
for (PartialDiagnosticAt &PDAt : It->second) {
1461-
const SourceLocation &Loc = PDAt.first;
1462-
const PartialDiagnostic &PD = PDAt.second;
1463-
HasWarningOrError |= getDiagnostics().getDiagnosticLevel(
1464-
PD.getDiagID(), Loc) >= DiagnosticsEngine::Warning;
1465-
{
1466-
DiagnosticBuilder Builder(Diags.Report(Loc, PD.getDiagID()));
1467-
Builder.setForceEmit();
1468-
PD.Emit(Builder);
1469-
}
1470-
1471-
// Emit the note on the first diagnostic in case too many diagnostics cause
1472-
// the note not emitted.
1473-
if (FirstDiag && HasWarningOrError && ShowCallStack) {
1474-
emitCallStackNotes(*this, FD);
1475-
FirstDiag = false;
1476-
}
1477-
}
1478-
1479-
}
1480-
14811453
namespace {
1454+
14821455
/// Helper class that emits deferred diagnostic messages if an entity directly
14831456
/// or indirectly using the function that causes the deferred diagnostic
14841457
/// messages is known to be emitted.
1458+
///
1459+
/// During parsing of AST, certain diagnostic messages are recorded as deferred
1460+
/// diagnostics since it is unknown whether the functions containing such
1461+
/// diagnostics will be emitted. A list of potentially emitted functions and
1462+
/// variables that may potentially trigger emission of functions are also
1463+
/// recorded. DeferredDiagnosticsEmitter recursively visits used functions
1464+
/// by each function to emit deferred diagnostics.
1465+
///
1466+
/// During the visit, certain OpenMP directives or initializer of variables
1467+
/// with certain OpenMP attributes will cause subsequent visiting of any
1468+
/// functions enter a state which is called OpenMP device context in this
1469+
/// implementation. The state is exited when the directive or initializer is
1470+
/// exited. This state can change the emission states of subsequent uses
1471+
/// of functions.
1472+
///
1473+
/// Conceptually the functions or variables to be visited form a use graph
1474+
/// where the parent node uses the child node. At any point of the visit,
1475+
/// the tree nodes traversed from the tree root to the current node form a use
1476+
/// stack. The emission state of the current node depends on two factors:
1477+
/// 1. the emission state of the root node
1478+
/// 2. whether the current node is in OpenMP device context
1479+
/// If the function is decided to be emitted, its contained deferred diagnostics
1480+
/// are emitted, together with the information about the use stack.
1481+
///
14851482
class DeferredDiagnosticsEmitter
14861483
: public UsedDeclVisitor<DeferredDiagnosticsEmitter> {
14871484
public:
14881485
typedef UsedDeclVisitor<DeferredDiagnosticsEmitter> Inherited;
1489-
llvm::SmallSet<CanonicalDeclPtr<Decl>, 4> Visited;
1490-
llvm::SmallVector<CanonicalDeclPtr<FunctionDecl>, 4> UseStack;
1491-
bool ShouldEmit;
1486+
1487+
// Whether the function is already in the current use-path.
1488+
llvm::SmallSet<CanonicalDeclPtr<Decl>, 4> InUsePath;
1489+
1490+
// The current use-path.
1491+
llvm::SmallVector<CanonicalDeclPtr<FunctionDecl>, 4> UsePath;
1492+
1493+
// Whether the visiting of the function has been done. Done[0] is for the
1494+
// case not in OpenMP device context. Done[1] is for the case in OpenMP
1495+
// device context. We need two sets because diagnostics emission may be
1496+
// different depending on whether it is in OpenMP device context.
1497+
llvm::SmallSet<CanonicalDeclPtr<Decl>, 4> DoneMap[2];
1498+
1499+
// Emission state of the root node of the current use graph.
1500+
bool ShouldEmitRootNode;
1501+
1502+
// Current OpenMP device context level. It is initialized to 0 and each
1503+
// entering of device context increases it by 1 and each exit decreases
1504+
// it by 1. Non-zero value indicates it is currently in device context.
14921505
unsigned InOMPDeviceContext;
14931506

14941507
DeferredDiagnosticsEmitter(Sema &S)
1495-
: Inherited(S), ShouldEmit(false), InOMPDeviceContext(0) {}
1508+
: Inherited(S), ShouldEmitRootNode(false), InOMPDeviceContext(0) {}
14961509

14971510
void VisitOMPTargetDirective(OMPTargetDirective *Node) {
14981511
++InOMPDeviceContext;
@@ -1525,36 +1538,72 @@ class DeferredDiagnosticsEmitter
15251538
}
15261539

15271540
void checkFunc(SourceLocation Loc, FunctionDecl *FD) {
1528-
FunctionDecl *Caller = UseStack.empty() ? nullptr : UseStack.back();
1529-
auto IsKnownEmitted = S.getEmissionStatus(FD, /*Final=*/true) ==
1530-
Sema::FunctionEmissionStatus::Emitted;
1531-
if (!Caller)
1532-
ShouldEmit = IsKnownEmitted;
1533-
if ((!ShouldEmit && !S.getLangOpts().OpenMP && !Caller) ||
1534-
S.shouldIgnoreInHostDeviceCheck(FD) || Visited.count(FD))
1541+
auto &Done = DoneMap[InOMPDeviceContext];
1542+
FunctionDecl *Caller = UsePath.empty() ? nullptr : UsePath.back();
1543+
if ((!ShouldEmitRootNode && !S.getLangOpts().OpenMP && !Caller) ||
1544+
S.shouldIgnoreInHostDeviceCheck(FD) || InUsePath.count(FD))
15351545
return;
15361546
// Finalize analysis of OpenMP-specific constructs.
1537-
if (Caller && S.LangOpts.OpenMP && UseStack.size() == 1)
1547+
if (Caller && S.LangOpts.OpenMP && UsePath.size() == 1)
15381548
S.finalizeOpenMPDelayedAnalysis(Caller, FD, Loc);
15391549
if (Caller)
15401550
S.DeviceKnownEmittedFns[FD] = {Caller, Loc};
1541-
if (ShouldEmit || InOMPDeviceContext)
1542-
S.emitDeferredDiags(FD, Caller);
1543-
Visited.insert(FD);
1544-
UseStack.push_back(FD);
1551+
// Always emit deferred diagnostics for the direct users. This does not
1552+
// lead to explosion of diagnostics since each user is visited at most
1553+
// twice.
1554+
if (ShouldEmitRootNode || InOMPDeviceContext)
1555+
emitDeferredDiags(FD, Caller);
1556+
// Do not revisit a function if the function body has been completely
1557+
// visited before.
1558+
if (Done.count(FD))
1559+
return;
1560+
InUsePath.insert(FD);
1561+
UsePath.push_back(FD);
15451562
if (auto *S = FD->getBody()) {
15461563
this->Visit(S);
15471564
}
1548-
UseStack.pop_back();
1549-
Visited.erase(FD);
1565+
UsePath.pop_back();
1566+
InUsePath.erase(FD);
1567+
Done.insert(FD);
15501568
}
15511569

15521570
void checkRecordedDecl(Decl *D) {
1553-
if (auto *FD = dyn_cast<FunctionDecl>(D))
1571+
if (auto *FD = dyn_cast<FunctionDecl>(D)) {
1572+
ShouldEmitRootNode = S.getEmissionStatus(FD, /*Final=*/true) ==
1573+
Sema::FunctionEmissionStatus::Emitted;
15541574
checkFunc(SourceLocation(), FD);
1555-
else
1575+
} else
15561576
checkVar(cast<VarDecl>(D));
15571577
}
1578+
1579+
// Emit any deferred diagnostics for FD
1580+
void emitDeferredDiags(FunctionDecl *FD, bool ShowCallStack) {
1581+
auto It = S.DeviceDeferredDiags.find(FD);
1582+
if (It == S.DeviceDeferredDiags.end())
1583+
return;
1584+
bool HasWarningOrError = false;
1585+
bool FirstDiag = true;
1586+
for (PartialDiagnosticAt &PDAt : It->second) {
1587+
// Respect error limit.
1588+
if (S.Diags.hasFatalErrorOccurred())
1589+
return;
1590+
const SourceLocation &Loc = PDAt.first;
1591+
const PartialDiagnostic &PD = PDAt.second;
1592+
HasWarningOrError |=
1593+
S.getDiagnostics().getDiagnosticLevel(PD.getDiagID(), Loc) >=
1594+
DiagnosticsEngine::Warning;
1595+
{
1596+
DiagnosticBuilder Builder(S.Diags.Report(Loc, PD.getDiagID()));
1597+
PD.Emit(Builder);
1598+
}
1599+
// Emit the note on the first diagnostic in case too many diagnostics
1600+
// cause the note not emitted.
1601+
if (FirstDiag && HasWarningOrError && ShowCallStack) {
1602+
emitCallStackNotes(S, FD);
1603+
FirstDiag = false;
1604+
}
1605+
}
1606+
}
15581607
};
15591608
} // namespace
15601609

+25
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
// RUN: not %clang_cc1 -std=c++11 -triple x86_64-unknown-linux-gnu \
2+
// RUN: -emit-llvm -o - %s 2>&1 | FileCheck %s
3+
// RUN: not %clang_cc1 -std=c++11 -triple x86_64-unknown-linux-gnu \
4+
// RUN: -fcuda-is-device -emit-llvm -o - %s 2>&1 \
5+
// RUN: | FileCheck %s
6+
7+
// Check no crash due to deferred diagnostics.
8+
9+
#include "Inputs/cuda.h"
10+
11+
// CHECK: error: invalid output constraint '=h' in asm
12+
// CHECK-NOT: core dump
13+
inline __host__ __device__ int foo() {
14+
short h;
15+
__asm__("dont care" : "=h"(h) : "f"(0.0), "d"(0.0), "h"(0), "r"(0), "l"(0));
16+
return 0;
17+
}
18+
19+
void host_fun() {
20+
foo();
21+
}
22+
23+
__global__ void kernel() {
24+
foo();
25+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
// RUN: not %clang_cc1 -fcxx-exceptions -fcuda-is-device -fsyntax-only \
2+
// RUN: -ferror-limit 2 2>&1 %s | FileCheck %s
3+
4+
#include "Inputs/cuda.h"
5+
6+
// CHECK: cannot use 'throw' in __host__ __device__ function
7+
// CHECK: cannot use 'throw' in __host__ __device__ function
8+
// CHECK-NOT: cannot use 'throw' in __host__ __device__ function
9+
// CHECK: too many errors emitted, stopping now
10+
11+
inline __host__ __device__ void hasInvalid() {
12+
throw NULL;
13+
}
14+
15+
__global__ void use0() {
16+
hasInvalid();
17+
hasInvalid();
18+
hasInvalid();
19+
hasInvalid();
20+
}

clang/test/SemaCUDA/deferred-diags.cu

+36
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
// RUN: %clang_cc1 -fcxx-exceptions -fcuda-is-device -fsyntax-only -verify %s
2+
3+
#include "Inputs/cuda.h"
4+
5+
// Error, instantiated on device.
6+
inline __host__ __device__ void hasInvalid() {
7+
throw NULL;
8+
// expected-error@-1 2{{cannot use 'throw' in __host__ __device__ function}}
9+
}
10+
11+
static __device__ void use0() {
12+
hasInvalid(); // expected-note {{called by 'use0'}}
13+
hasInvalid(); // expected-note {{called by 'use0'}}
14+
}
15+
16+
// To avoid excessive diagnostic messages, deferred diagnostics are only
17+
// emitted the first time a function is called.
18+
static __device__ void use1() {
19+
use0(); // expected-note 2{{called by 'use1'}}
20+
use0();
21+
}
22+
23+
static __device__ void use2() {
24+
use1(); // expected-note 2{{called by 'use2'}}
25+
use1();
26+
}
27+
28+
static __device__ void use3() {
29+
use2(); // expected-note 2{{called by 'use3'}}
30+
use2();
31+
}
32+
33+
__global__ void use4() {
34+
use3(); // expected-note 2{{called by 'use4'}}
35+
use3();
36+
}

0 commit comments

Comments
 (0)