Skip to content

Commit 8a003a4

Browse files
authored
Coverage: fix handling of constructors and top-level decls (SR-7446) (#15966)
* [Coverage] Instrument constructor initializers (SR-7446) We need to instrument constructor initializers, instead of the delegating constructors which just call them. rdar://39460313 * [Coverage] Remove dead code, NFC * [Coverage] Use a shared profiler for constructors and member initializers This fixes coverage reporting for member initializers and cuts down on repeated AST traversals of pattern bindings within nominal type decls. This allows us to remove some defensive heuristic code which dealt with closures and if-exprs within member initializers.
1 parent 429ed87 commit 8a003a4

10 files changed

+279
-155
lines changed

lib/SIL/SILProfiler.cpp

Lines changed: 133 additions & 101 deletions
Original file line numberDiff line numberDiff line change
@@ -26,87 +26,52 @@
2626

2727
using namespace swift;
2828

29-
static bool isClosureWithBody(AbstractClosureExpr *ACE) {
29+
/// Check if a closure has a body.
30+
static bool doesClosureHaveBody(AbstractClosureExpr *ACE) {
3031
if (auto *CE = dyn_cast<ClosureExpr>(ACE))
3132
return CE->getBody();
3233
if (auto *autoCE = dyn_cast<AutoClosureExpr>(ACE))
3334
return autoCE->getBody();
3435
return false;
3536
}
3637

38+
/// Check whether a root AST node is unmapped, i.e not profiled.
3739
static bool isUnmapped(ASTNode N) {
3840
if (auto *E = N.dyn_cast<Expr *>()) {
3941
auto *CE = dyn_cast<AbstractClosureExpr>(E);
40-
return !CE || (!isa<AutoClosureExpr>(CE) && CE->isImplicit()) ||
41-
!isClosureWithBody(CE);
42+
43+
// Only map closure expressions with bodies.
44+
if (!CE || !doesClosureHaveBody(CE))
45+
return true;
46+
47+
// Don't map implicit closures, unless they're autoclosures.
48+
if (!isa<AutoClosureExpr>(CE) && CE->isImplicit())
49+
return true;
50+
51+
return false;
4252
}
4353

4454
auto *D = N.get<Decl *>();
4555
if (auto *AFD = dyn_cast<AbstractFunctionDecl>(D)) {
56+
// Don't map functions without bodies.
4657
if (!AFD->getBody())
4758
return true;
4859

60+
// Map all *structors, even if they are implicit.
61+
if (isa<ConstructorDecl>(D) || isa<DestructorDecl>(D))
62+
return false;
63+
64+
// Map implicit getters.
4965
if (auto *accessor = dyn_cast<AccessorDecl>(AFD))
5066
if (accessor->isImplicit() && accessor->isGetter())
5167
return false;
5268
}
5369

54-
if (isa<ConstructorDecl>(D) || isa<DestructorDecl>(D))
55-
return false;
56-
57-
return D->isImplicit() || isa<EnumCaseDecl>(D);
58-
}
59-
60-
/// A simple heuristic to determine whether \p E contains a definition of a
61-
/// closure. This is not complete, but it suffices to cheaply filter away some
62-
/// redundant coverage mappings.
63-
static bool containsClosure(Expr *E) {
64-
Expr *candidateExpr = E;
65-
if (auto *ce = dyn_cast<CallExpr>(E))
66-
candidateExpr = ce->getDirectCallee();
67-
return dyn_cast_or_null<AbstractClosureExpr>(candidateExpr);
68-
}
69-
70-
/// Walk the non-static initializers in \p PBD.
71-
static void walkPatternForProfiling(PatternBindingDecl *PBD, ASTWalker &Walker,
72-
bool AllowClosures = true) {
73-
if (PBD && !PBD->isStatic())
74-
for (auto E : PBD->getPatternList())
75-
if (E.getInit())
76-
if (AllowClosures || !containsClosure(E.getInit()))
77-
E.getInit()->walk(Walker);
78-
}
79-
80-
/// Walk the AST of \c Root and related nodes that are relevant for profiling.
81-
static void walkFunctionForProfiling(AbstractFunctionDecl *Root,
82-
ASTWalker &Walker) {
83-
Root->walk(Walker);
84-
85-
// We treat non-closure member initializers as part of the constructor for
86-
// profiling.
87-
if (auto *CD = dyn_cast<ConstructorDecl>(Root)) {
88-
auto *NominalType =
89-
CD->getDeclContext()->getAsNominalTypeOrNominalTypeExtensionContext();
90-
for (auto *Member : NominalType->getMembers()) {
91-
// Find pattern binding declarations that have initializers.
92-
if (auto *PBD = dyn_cast<PatternBindingDecl>(Member))
93-
walkPatternForProfiling(PBD, Walker, /*AllowClosures=*/false);
94-
}
95-
}
96-
}
70+
// Skip any remaining implicit, or otherwise unsupported decls.
71+
if (D->isImplicit() || isa<EnumCaseDecl>(D))
72+
return true;
9773

98-
/// Walk \p D for profiling.
99-
static void walkForProfiling(ASTNode N, ASTWalker &Walker) {
100-
if (auto *D = N.dyn_cast<Decl *>()) {
101-
if (auto *AFD = dyn_cast<AbstractFunctionDecl>(D))
102-
walkFunctionForProfiling(AFD, Walker);
103-
else if (auto *PBD = dyn_cast<PatternBindingDecl>(D))
104-
walkPatternForProfiling(PBD, Walker);
105-
else if (auto *TLCD = dyn_cast<TopLevelCodeDecl>(D))
106-
TLCD->walk(Walker);
107-
} else if (auto *E = N.dyn_cast<Expr *>()) {
108-
cast<AbstractClosureExpr>(E)->walk(Walker);
109-
}
74+
return false;
11075
}
11176

11277
namespace swift {
@@ -123,27 +88,43 @@ static bool hasASTBeenTypeChecked(ASTNode N) {
12388
return !SF || SF->ASTStage >= SourceFile::TypeChecked;
12489
}
12590

91+
/// Check whether a mapped AST node requires a new profiler.
92+
static bool canCreateProfilerForAST(ASTNode N) {
93+
assert(hasASTBeenTypeChecked(N) && "Cannot use this AST for profiling");
94+
95+
if (auto *D = N.dyn_cast<Decl *>()) {
96+
// Any mapped function may be profiled. There's an exception for
97+
// constructors because all of the constructors for a type share a single
98+
// profiler.
99+
if (auto *AFD = dyn_cast<AbstractFunctionDecl>(D))
100+
return !isa<ConstructorDecl>(AFD);
101+
102+
if (isa<TopLevelCodeDecl>(D))
103+
return true;
104+
105+
if (isa<NominalTypeDecl>(D))
106+
return true;
107+
} else {
108+
auto *E = N.get<Expr *>();
109+
if (isa<AbstractClosureExpr>(E))
110+
return true;
111+
}
112+
return false;
113+
}
114+
126115
SILProfiler *SILProfiler::create(SILModule &M, ForDefinition_t forDefinition,
127116
ASTNode N) {
128117
// Avoid generating profiling state for declarations.
129118
if (!forDefinition)
130119
return nullptr;
131120

132-
assert(hasASTBeenTypeChecked(N) && "Cannot use this AST for code coverage");
133-
134-
if (auto *D = N.dyn_cast<Decl *>()) {
135-
assert(isa<AbstractFunctionDecl>(D) ||
136-
isa<TopLevelCodeDecl>(D) && "Cannot create profiler");
137-
} else if (auto *E = N.dyn_cast<Expr *>()) {
138-
assert(isa<AbstractClosureExpr>(E) && "Cannot create profiler");
139-
} else {
140-
llvm_unreachable("Invalid AST node for profiling");
141-
}
142-
143121
const auto &Opts = M.getOptions();
144122
if (!doesASTRequireProfiling(M, N) && Opts.UseProfile.empty())
145123
return nullptr;
146124

125+
if (!canCreateProfilerForAST(N))
126+
llvm_unreachable("Invalid AST node for profiling");
127+
147128
auto *Buf = M.allocate<SILProfiler>(1);
148129
auto *SP = ::new (Buf) SILProfiler(M, N, Opts.EmitProfileCoverageMapping);
149130
SP->assignRegionCounters();
@@ -152,6 +133,15 @@ SILProfiler *SILProfiler::create(SILModule &M, ForDefinition_t forDefinition,
152133

153134
namespace {
154135

136+
/// Walk the non-static initializers in \p PBD.
137+
static void walkPatternForProfiling(PatternBindingDecl *PBD,
138+
ASTWalker &Walker) {
139+
if (PBD && !PBD->isStatic())
140+
for (auto E : PBD->getPatternList())
141+
if (E.getInit())
142+
E.getInit()->walk(Walker);
143+
}
144+
155145
/// Special logic for handling closure visitation.
156146
///
157147
/// To prevent a closure from being mapped twice, avoid recursively walking
@@ -170,25 +160,35 @@ std::pair<bool, Expr *> visitClosureExpr(ASTWalker &Walker,
170160
/// An ASTWalker that maps ASTNodes to profiling counters.
171161
struct MapRegionCounters : public ASTWalker {
172162
/// The next counter value to assign.
173-
unsigned NextCounter;
163+
unsigned NextCounter = 0;
174164

175165
/// The map of statements to counters.
176166
llvm::DenseMap<ASTNode, unsigned> &CounterMap;
177167

168+
/// A flag indicating whether we're walking a nominal type.
169+
bool WithinNominalType = false;
170+
178171
MapRegionCounters(llvm::DenseMap<ASTNode, unsigned> &CounterMap)
179-
: NextCounter(0), CounterMap(CounterMap) {}
172+
: CounterMap(CounterMap) {}
180173

181174
bool walkToDeclPre(Decl *D) override {
182175
if (isUnmapped(D))
183176
return false;
177+
184178
if (auto *AFD = dyn_cast<AbstractFunctionDecl>(D)) {
185-
bool FirstDecl = Parent.isNull();
186-
if (FirstDecl)
179+
// Don't map a nested function unless it's a constructor.
180+
bool continueWalk = Parent.isNull() || isa<ConstructorDecl>(AFD);
181+
if (continueWalk)
187182
CounterMap[AFD->getBody()] = NextCounter++;
188-
return FirstDecl;
189-
}
190-
if (auto *TLCD = dyn_cast<TopLevelCodeDecl>(D))
183+
return continueWalk;
184+
} else if (auto *TLCD = dyn_cast<TopLevelCodeDecl>(D)) {
191185
CounterMap[TLCD->getBody()] = NextCounter++;
186+
} else if (isa<NominalTypeDecl>(D)) {
187+
bool continueWalk = Parent.isNull();
188+
if (continueWalk)
189+
WithinNominalType = true;
190+
return continueWalk;
191+
}
192192
return true;
193193
}
194194

@@ -560,7 +560,11 @@ struct CoverageMapping : public ASTWalker {
560560
/// \brief A stack of active repeat-while loops.
561561
std::vector<RepeatWhileStmt *> RepeatWhileStack;
562562

563-
CounterExpr *ExitCounter;
563+
CounterExpr *ExitCounter = nullptr;
564+
565+
Stmt *ImplicitTopLevelBody = nullptr;
566+
567+
NominalTypeDecl *ParentNominalType = nullptr;
564568

565569
/// \brief Return true if \c Node has an associated counter.
566570
bool hasCounter(ASTNode Node) { return CounterMap.count(Node); }
@@ -762,19 +766,40 @@ struct CoverageMapping : public ASTWalker {
762766
bool walkToDeclPre(Decl *D) override {
763767
if (isUnmapped(D))
764768
return false;
769+
765770
if (auto *AFD = dyn_cast<AbstractFunctionDecl>(D)) {
766-
bool FirstDecl = Parent.isNull();
767-
if (FirstDecl)
768-
assignCounter(AFD->getBody());
769-
return FirstDecl;
770-
}
771-
else if (auto *TLCD = dyn_cast<TopLevelCodeDecl>(D))
771+
// Don't map a nested function unless it's a constructor.
772+
bool continueWalk = Parent.isNull() || isa<ConstructorDecl>(AFD);
773+
if (continueWalk) {
774+
CounterExpr &funcCounter = assignCounter(AFD->getBody());
775+
776+
if (isa<ConstructorDecl>(AFD))
777+
addToCounter(ParentNominalType, funcCounter);
778+
}
779+
return continueWalk;
780+
} else if (auto *TLCD = dyn_cast<TopLevelCodeDecl>(D)) {
772781
assignCounter(TLCD->getBody());
782+
ImplicitTopLevelBody = TLCD->getBody();
783+
} else if (auto *NTD = dyn_cast<NominalTypeDecl>(D)) {
784+
bool continueWalk = Parent.isNull();
785+
if (continueWalk) {
786+
ParentNominalType = NTD;
787+
assignCounter(NTD, CounterExpr::Zero());
788+
pushRegion(NTD);
789+
}
790+
return continueWalk;
791+
}
792+
return true;
793+
}
794+
795+
bool walkToDeclPost(Decl *D) override {
796+
if (isa<TopLevelCodeDecl>(D))
797+
ImplicitTopLevelBody = nullptr;
773798
return true;
774799
}
775800

776801
std::pair<bool, Stmt *> walkToStmtPre(Stmt *S) override {
777-
if (S->isImplicit())
802+
if (S->isImplicit() && S != ImplicitTopLevelBody)
778803
return {true, S};
779804

780805
if (!RegionStack.empty())
@@ -835,7 +860,7 @@ struct CoverageMapping : public ASTWalker {
835860
}
836861

837862
Stmt *walkToStmtPost(Stmt *S) override {
838-
if (S->isImplicit())
863+
if (S->isImplicit() && S != ImplicitTopLevelBody)
839864
return S;
840865

841866
if (isa<BraceStmt>(S)) {
@@ -907,11 +932,8 @@ struct CoverageMapping : public ASTWalker {
907932
return Result;
908933
} else if (auto *IE = dyn_cast<IfExpr>(E)) {
909934
CounterExpr &ThenCounter = assignCounter(IE->getThenExpr());
910-
if (RegionStack.empty())
911-
assignCounter(IE->getElseExpr());
912-
else
913-
assignCounter(IE->getElseExpr(),
914-
CounterExpr::Sub(getCurrentCounter(), ThenCounter));
935+
assignCounter(IE->getElseExpr(),
936+
CounterExpr::Sub(getCurrentCounter(), ThenCounter));
915937
}
916938

917939
if (hasCounter(E))
@@ -951,6 +973,16 @@ static StringRef getCurrentFileName(ASTNode Root) {
951973
return {};
952974
}
953975

976+
static void walkTopLevelNodeForProfiling(ASTNode Root, ASTWalker &Walker) {
977+
Root.walk(Walker);
978+
979+
// Visit extensions when walking through a nominal type.
980+
auto *NTD = dyn_cast_or_null<NominalTypeDecl>(Root.dyn_cast<Decl *>());
981+
if (NTD)
982+
for (ExtensionDecl *ED : NTD->getExtensions())
983+
ED->walk(Walker);
984+
}
985+
954986
void SILProfiler::assignRegionCounters() {
955987
const auto &SM = M.getASTContext().SourceMgr;
956988

@@ -970,31 +1002,31 @@ void SILProfiler::assignRegionCounters() {
9701002
TLCD->getStartLoc().printLineAndColumn(OS, SM);
9711003
CurrentFuncLinkage = FormalLinkage::HiddenUnique;
9721004
} else {
973-
llvm_unreachable("Unsupported decl");
974-
}
975-
} else {
976-
auto *E = Root.get<Expr *>();
977-
if (auto *CE = dyn_cast<AbstractClosureExpr>(E)) {
978-
CurrentFuncName = SILDeclRef(CE).mangle();
1005+
auto *NTD = cast<NominalTypeDecl>(D);
1006+
llvm::raw_string_ostream OS{CurrentFuncName};
1007+
OS << "__ntd_" << NTD->getNameStr() << "_";
1008+
NTD->getStartLoc().printLineAndColumn(OS, SM);
9791009
CurrentFuncLinkage = FormalLinkage::HiddenUnique;
980-
} else {
981-
llvm_unreachable("Unsupported expr");
9821010
}
1011+
} else {
1012+
auto *CE = cast<AbstractClosureExpr>(Root.get<Expr *>());
1013+
CurrentFuncName = SILDeclRef(CE).mangle();
1014+
CurrentFuncLinkage = FormalLinkage::HiddenUnique;
9831015
}
9841016

9851017
PGOFuncName = llvm::getPGOFuncName(
9861018
CurrentFuncName, getEquivalentPGOLinkage(CurrentFuncLinkage),
9871019
CurrentFileName);
9881020

989-
walkForProfiling(Root, Mapper);
1021+
walkTopLevelNodeForProfiling(Root, Mapper);
9901022

9911023
NumRegionCounters = Mapper.NextCounter;
9921024
// TODO: Mapper needs to calculate a function hash as it goes.
9931025
PGOFuncHash = 0x0;
9941026

9951027
if (EmitCoverageMapping) {
9961028
CoverageMapping Coverage(SM);
997-
walkForProfiling(Root, Coverage);
1029+
walkTopLevelNodeForProfiling(Root, Coverage);
9981030
CovMap =
9991031
Coverage.emitSourceRegions(M, CurrentFuncName, PGOFuncName, PGOFuncHash,
10001032
RegionCounterMap, CurrentFileName);
@@ -1012,7 +1044,7 @@ void SILProfiler::assignRegionCounters() {
10121044
}
10131045
PGOMapping pgoMapper(RegionLoadedCounterMap, LoadedCounts,
10141046
RegionCondToParentMap);
1015-
walkForProfiling(Root, pgoMapper);
1047+
walkTopLevelNodeForProfiling(Root, pgoMapper);
10161048
}
10171049
}
10181050

0 commit comments

Comments
 (0)