Skip to content

Commit bea6425

Browse files
committed
[Coverage] Avoid recording regions from macro expansions
Ignore any regions recorded while inside a macro expansion, but account for any control flow that may have happened such that the exit count is correctly adjusted. This allows e.g throwing function calls to behave correctly within the expansion. rdar://129081384
1 parent 4684bfb commit bea6425

File tree

3 files changed

+146
-7
lines changed

3 files changed

+146
-7
lines changed

lib/SIL/IR/SILProfiler.cpp

Lines changed: 56 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -468,6 +468,10 @@ class SourceMappingRegion {
468468
/// The region's ending location.
469469
std::optional<SourceLoc> EndLoc;
470470

471+
/// Whether the region is within a macro expansion. Such regions do not
472+
/// get recorded, but are needed to track the counters within the expansion.
473+
bool IsInMacroExpansion = false;
474+
471475
SourceMappingRegion(Kind RegionKind, std::optional<CounterExpr> Counter,
472476
std::optional<SourceLoc> StartLoc)
473477
: RegionKind(RegionKind), Counter(Counter), StartLoc(StartLoc) {
@@ -516,6 +520,14 @@ class SourceMappingRegion {
516520
SourceMappingRegion(SourceMappingRegion &&Region) = default;
517521
SourceMappingRegion &operator=(SourceMappingRegion &&RHS) = default;
518522

523+
bool isInMacroExpansion() const {
524+
return IsInMacroExpansion;
525+
}
526+
527+
void setIsInMacroExpansion() {
528+
IsInMacroExpansion = true;
529+
}
530+
519531
/// Whether this region is for scoping only.
520532
bool isForScopingOnly() const { return RegionKind == Kind::ScopingOnly; }
521533

@@ -837,6 +849,12 @@ struct CoverageMapping : public ASTWalker {
837849

838850
Stmt *ImplicitTopLevelBody = nullptr;
839851

852+
/// The number of parent MacroExpansionExprs.
853+
unsigned MacroDepth = 0;
854+
855+
/// Whether the current walk is within a macro expansion.
856+
bool isInMacroExpansion() const { return MacroDepth > 0; }
857+
840858
/// Return true if \c Ref has an associated counter.
841859
bool hasCounter(ProfileCounterRef Ref) { return CounterExprs.count(Ref); }
842860

@@ -993,6 +1011,10 @@ struct CoverageMapping : public ASTWalker {
9931011

9941012
/// Push a region onto the stack.
9951013
void pushRegion(SourceMappingRegion Region) {
1014+
// Note on the region whether we're currently in a macro expansion.
1015+
if (isInMacroExpansion())
1016+
Region.setIsInMacroExpansion();
1017+
9961018
LLVM_DEBUG({
9971019
llvm::dbgs() << "Pushed region: ";
9981020
Region.print(llvm::dbgs(), SM);
@@ -1027,6 +1049,11 @@ struct CoverageMapping : public ASTWalker {
10271049
llvm::dbgs() << "\n";
10281050
});
10291051

1052+
// Don't record regions in macro expansions, they don't have source
1053+
// locations that can be meaningfully mapped to source code.
1054+
if (Region.isInMacroExpansion())
1055+
return;
1056+
10301057
// Don't bother recording regions that are only present for scoping.
10311058
if (Region.isForScopingOnly())
10321059
return;
@@ -1141,16 +1168,29 @@ struct CoverageMapping : public ASTWalker {
11411168
if (SourceRegions.empty())
11421169
return nullptr;
11431170

1144-
using MappedRegion = SILCoverageMap::MappedRegion;
1171+
auto FileSourceRange = SM.getRangeForBuffer(*SF->getBufferID());
1172+
auto isLocInFile = [&](SourceLoc Loc) {
1173+
return FileSourceRange.contains(Loc) || FileSourceRange.getEnd() == Loc;
1174+
};
11451175

1176+
using MappedRegion = SILCoverageMap::MappedRegion;
11461177
std::vector<MappedRegion> Regions;
11471178
SourceRange OuterRange;
11481179
for (const auto &Region : SourceRegions) {
11491180
assert(Region.hasStartLoc() && "invalid region");
11501181
assert(Region.hasEndLoc() && "incomplete region");
11511182

1152-
// Build up the outer range from the union of all coverage regions.
11531183
SourceRange Range(Region.getStartLoc(), Region.getEndLoc());
1184+
1185+
// Make sure we haven't ended up with any source locations outside the
1186+
// SourceFile (e.g for generated code such as macros), asserting in an
1187+
// asserts build, dropping in a non-asserts build.
1188+
if (!isLocInFile(Range.Start) || !isLocInFile(Range.End)) {
1189+
assert(false && "range outside of file");
1190+
continue;
1191+
}
1192+
1193+
// Build up the outer range from the union of all coverage regions.
11541194
if (!OuterRange) {
11551195
OuterRange = Range;
11561196
} else {
@@ -1522,10 +1562,14 @@ struct CoverageMapping : public ASTWalker {
15221562

15231563
if (hasCounter(E)) {
15241564
pushRegion(SourceMappingRegion::forNode(E, SM));
1525-
} else if (isa<OptionalTryExpr>(E)) {
1565+
} else if (isa<OptionalTryExpr>(E) || isa<MacroExpansionExpr>(E)) {
15261566
// If we have a `try?`, that doesn't already have a counter, record it
15271567
// as a scoping-only region. We need it to scope child error branches,
15281568
// but don't need it in the resulting set of regions.
1569+
//
1570+
// If we have a macro expansion, also push a scoping-only region. We'll
1571+
// discard any regions recorded within the macro, but will adjust for any
1572+
// control flow that may have happened within the macro.
15291573
assignCounter(E, getCurrentCounter());
15301574
pushRegion(SourceMappingRegion::scopingOnly(E, SM));
15311575
}
@@ -1560,6 +1604,10 @@ struct CoverageMapping : public ASTWalker {
15601604
// Already visited the children.
15611605
return Action::SkipChildren(TE);
15621606
}
1607+
1608+
if (isa<MacroExpansionExpr>(E))
1609+
MacroDepth += 1;
1610+
15631611
return shouldWalkIntoExpr(E, Parent, Constant);
15641612
}
15651613

@@ -1576,6 +1624,11 @@ struct CoverageMapping : public ASTWalker {
15761624
Lexer::getLocForEndOfToken(SM, E->getEndLoc()));
15771625
}
15781626

1627+
if (isa<MacroExpansionExpr>(E)) {
1628+
assert(isInMacroExpansion());
1629+
MacroDepth -= 1;
1630+
}
1631+
15791632
if (hasCounter(E))
15801633
exitRegion(E);
15811634

test/Macros/Inputs/syntax_macro_definitions.swift

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -212,6 +212,25 @@ public class NestedDeclInExprMacro: ExpressionMacro {
212212
}
213213
}
214214

215+
public class NullaryFunctionCallMacro: ExpressionMacro {
216+
public static func expansion(
217+
of macro: some FreestandingMacroExpansionSyntax,
218+
in context: some MacroExpansionContext
219+
) -> ExprSyntax {
220+
let calls = macro.arguments.compactMap(\.expression).map { "\($0)()" }
221+
return "(\(raw: calls.joined(separator: ", ")))"
222+
}
223+
}
224+
225+
public class TupleMacro: ExpressionMacro {
226+
public static func expansion(
227+
of macro: some FreestandingMacroExpansionSyntax,
228+
in context: some MacroExpansionContext
229+
) -> ExprSyntax {
230+
return "(\(raw: macro.arguments.map { "\($0)" }.joined()))"
231+
}
232+
}
233+
215234
enum CustomError: Error, CustomStringConvertible {
216235
case message(String)
217236

test/Profiler/coverage_macros.swift

Lines changed: 71 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -19,20 +19,45 @@ macro addMembers() = #externalMacro(module: "MacroDefinition", type: "AddMembers
1919
@freestanding(expression)
2020
macro nestedDeclInExpr() -> () -> Void = #externalMacro(module: "MacroDefinition", type: "NestedDeclInExprMacro")
2121

22+
@freestanding(expression)
23+
macro fnCall<T>(_: () throws -> T) -> T = #externalMacro(module: "MacroDefinition", type: "NullaryFunctionCallMacro")
24+
25+
@freestanding(expression)
26+
macro fnCall<T>(_: () throws -> T, _: () throws -> T) -> (T, T) = #externalMacro(module: "MacroDefinition", type: "NullaryFunctionCallMacro")
27+
28+
@freestanding(expression)
29+
macro id<T>(_: T) -> T = #externalMacro(module: "MacroDefinition", type: "TupleMacro")
30+
31+
// This needs to be matched up here due to the sorting of the SIL; just make
32+
// sure we emit the counter increments for the error branches.
33+
// CHECK-LABEL: sil hidden @$s15coverage_macros5test2Si_SityKF
34+
// CHECK: increment_profiler_counter 0,{{.*}}s15coverage_macros5test2Si_SityKF
35+
// CHECK: {{bb[0-9]+}}({{%[0-9]+}} : $any Error):
36+
// CHECK-NEXT: increment_profiler_counter 1,{{.*}}s15coverage_macros5test2Si_SityKF
37+
// CHECK: {{bb[0-9]+}}({{%[0-9]+}} : $any Error):
38+
// CHECK-NEXT: increment_profiler_counter 2,{{.*}}s15coverage_macros5test2Si_SityKF
39+
2240
// Note we use implicit-check-not, so this test ensures we don't emit
2341
// coverage maps for the macro expansions.
2442

43+
// CHECK-LABEL: sil_coverage_map{{.*}}s15coverage_macros10throwingFnSiyKF
44+
func throwingFn() throws -> Int { 0 }
45+
2546
struct S1 {
26-
// CHECK: sil_coverage_map{{.*}}variable initialization expression of coverage_macros.S1.x
47+
// CHECK-LABEL: sil_coverage_map{{.*}}variable initialization expression of coverage_macros.S1.x
2748
var x: Int = 0
2849

29-
// CHECK: sil_coverage_map{{.*}}variable initialization expression of coverage_macros.S1.y
50+
// CHECK-LABEL: sil_coverage_map{{.*}}variable initialization expression of coverage_macros.S1.y
3051
var y: Int = 0
52+
53+
// FIXME: This shouldn't require parens
54+
// CHECK-LABEL: sil_coverage_map{{.*}}variable initialization expression of coverage_macros.S1.z
55+
var z: Int = (#id(.random() ? 3 : 4)) // CHECK-NEXT: [[@LINE]]:16 -> [[@LINE]]:40 : 0
3156
}
3257

3358
@addMembers
3459
struct S2 {
35-
// CHECK: sil_coverage_map{{.*}}variable initialization expression of coverage_macros.S2.(_storage
60+
// CHECK-LABEL: sil_coverage_map{{.*}}variable initialization expression of coverage_macros.S2.(_storage
3661
private var _storage = S1()
3762

3863
@accessViaStorage
@@ -43,7 +68,49 @@ struct S2 {
4368
var y: Int = 17
4469
}
4570

46-
// CHECK: sil_coverage_map{{.*}}s15coverage_macros3fooyyF
71+
// CHECK-LABEL: sil_coverage_map{{.*}}s15coverage_macros3fooyyF
4772
func foo() {
4873
_ = #nestedDeclInExpr()
4974
}
75+
76+
// For cases where control flow happens in the macro expansion, we drop
77+
// all the regions that occur within the expansion, but account for any
78+
// control flow changes when exiting the expansion.
79+
//
80+
// CHECK-LABEL: sil_coverage_map{{.*}}s15coverage_macros5test1yyKF
81+
func test1() throws { // CHECK-NEXT: [[@LINE]]:21 -> [[@LINE+2]]:2 : 0
82+
_ = try #fnCall(throwingFn) // CHECK-NEXT: [[@LINE]]:30 -> [[@LINE+1]]:2 : (0 - 1)
83+
} // CHECK-NEXT: }
84+
85+
// CHECK-LABEL: sil_coverage_map{{.*}}s15coverage_macros5test2Si_SityKF
86+
func test2() throws -> (Int, Int) { // CHECK-NEXT: [[@LINE]]:35 -> [[@LINE+3]]:2 : 0
87+
let x = try #fnCall(throwingFn, throwingFn) // CHECK-NEXT: [[@LINE]]:46 -> [[@LINE+1]]:11 : ((0 - 1) - 2)
88+
return x // CHECK-NEXT: }
89+
}
90+
91+
// In this case the control flow is entirely contained within the macro, with
92+
// the same exit count, so no change.
93+
//
94+
// CHECK-LABEL: sil_coverage_map{{.*}}s15coverage_macros5test3SiyF
95+
func test3() -> Int { // CHECK-NEXT: [[@LINE]]:21 -> [[@LINE+3]]:2 : 0
96+
let x = #id(.random() ? 3 : 4) // CHECK-NEXT: }
97+
return x
98+
}
99+
100+
// CHECK-LABEL: sil_coverage_map{{.*}}s15coverage_macros5test4Si_SityKF
101+
func test4() throws -> (Int, Int) { // CHECK-NEXT: [[@LINE]]:35 -> [[@LINE+3]]:2 : 0
102+
let x = try #id(#fnCall(throwingFn, throwingFn)) // CHECK-NEXT: [[@LINE]]:51 -> [[@LINE+1]]:11 : ((0 - 1) - 2)
103+
return x // CHECK-NEXT: }
104+
}
105+
106+
// 0: entry, 1: first else, 2: first error branch, 3: second else,
107+
// 4: second error branch, 5: third else, 6: third error branch,
108+
// 7: fourth error branch
109+
// CHECK-LABEL: sil_coverage_map{{.*}}s15coverage_macros5test5Si_S2ityKF
110+
func test5() throws -> (Int, Int, Int) { // CHECK-NEXT: [[@LINE]]:40 -> [[@LINE+6]]:2 : 0
111+
let x = #id(.random() ? try throwingFn() : 4) // CHECK-NEXT: [[@LINE]]:48 -> [[@LINE+4]]:19 : (0 - 2)
112+
let y = #id(.random() ? 5 : try throwingFn()) // CHECK-NEXT: [[@LINE]]:48 -> [[@LINE+3]]:19 : ((0 - 2) - 4)
113+
let z = #id(.random() ? try throwingFn()
114+
: try throwingFn()) // CHECK-NEXT: [[@LINE]]:44 -> [[@LINE+1]]:19 : ((((0 - 2) - 4) - 6) - 7)
115+
return (x, y, z) // CHECK-NEXT: }
116+
}

0 commit comments

Comments
 (0)