Skip to content

Commit 314a65d

Browse files
committed
Coverage: fix handling of constructors and top-level decls (SR-7446) (swiftlang#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. (cherry picked from commit 8a003a4)
1 parent 2c807ed commit 314a65d

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
@@ -25,87 +25,52 @@
2525

2626
using namespace swift;
2727

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

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

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

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

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

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

11176
namespace swift {
@@ -122,27 +87,43 @@ static bool hasASTBeenTypeChecked(ASTNode N) {
12287
return !SF || SF->ASTStage >= SourceFile::TypeChecked;
12388
}
12489

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

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

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

152133
namespace {
153134

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

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

167+
/// A flag indicating whether we're walking a nominal type.
168+
bool WithinNominalType = false;
169+
177170
MapRegionCounters(llvm::DenseMap<ASTNode, unsigned> &CounterMap)
178-
: NextCounter(0), CounterMap(CounterMap) {}
171+
: CounterMap(CounterMap) {}
179172

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

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

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

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

775800
std::pair<bool, Stmt *> walkToStmtPre(Stmt *S) override {
776-
if (S->isImplicit())
801+
if (S->isImplicit() && S != ImplicitTopLevelBody)
777802
return {true, S};
778803

779804
if (!RegionStack.empty())
@@ -834,7 +859,7 @@ struct CoverageMapping : public ASTWalker {
834859
}
835860

836861
Stmt *walkToStmtPost(Stmt *S) override {
837-
if (S->isImplicit())
862+
if (S->isImplicit() && S != ImplicitTopLevelBody)
838863
return S;
839864

840865
if (isa<BraceStmt>(S)) {
@@ -906,11 +931,8 @@ struct CoverageMapping : public ASTWalker {
906931
return Result;
907932
} else if (auto *IE = dyn_cast<IfExpr>(E)) {
908933
CounterExpr &ThenCounter = assignCounter(IE->getThenExpr());
909-
if (RegionStack.empty())
910-
assignCounter(IE->getElseExpr());
911-
else
912-
assignCounter(IE->getElseExpr(),
913-
CounterExpr::Sub(getCurrentCounter(), ThenCounter));
934+
assignCounter(IE->getElseExpr(),
935+
CounterExpr::Sub(getCurrentCounter(), ThenCounter));
914936
}
915937

916938
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)