Skip to content

Commit c851753

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 ed4dbd9 commit c851753

File tree

3 files changed

+118
-5
lines changed

3 files changed

+118
-5
lines changed

lib/SIL/IR/SILProfiler.cpp

Lines changed: 38 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -837,6 +837,12 @@ struct CoverageMapping : public ASTWalker {
837837

838838
Stmt *ImplicitTopLevelBody = nullptr;
839839

840+
/// The number of parent MacroExpansionExprs.
841+
unsigned MacroDepth = 0;
842+
843+
/// Whether the current walk is within a macro expansion.
844+
bool isInMacroExpansion() const { return MacroDepth > 0; }
845+
840846
/// Return true if \c Ref has an associated counter.
841847
bool hasCounter(ProfileCounterRef Ref) { return CounterExprs.count(Ref); }
842848

@@ -1027,6 +1033,11 @@ struct CoverageMapping : public ASTWalker {
10271033
llvm::dbgs() << "\n";
10281034
});
10291035

1036+
// Don't record regions in macro expansions, they don't have source
1037+
// locations that can be meaningfully mapped to source code.
1038+
if (isInMacroExpansion())
1039+
return;
1040+
10301041
// Don't bother recording regions that are only present for scoping.
10311042
if (Region.isForScopingOnly())
10321043
return;
@@ -1142,15 +1153,26 @@ struct CoverageMapping : public ASTWalker {
11421153
return nullptr;
11431154

11441155
using MappedRegion = SILCoverageMap::MappedRegion;
1156+
auto FileSourceRange = SM.getRangeForBuffer(*SF->getBufferID());
11451157

11461158
std::vector<MappedRegion> Regions;
11471159
SourceRange OuterRange;
11481160
for (const auto &Region : SourceRegions) {
11491161
assert(Region.hasStartLoc() && "invalid region");
11501162
assert(Region.hasEndLoc() && "incomplete region");
11511163

1152-
// Build up the outer range from the union of all coverage regions.
11531164
SourceRange Range(Region.getStartLoc(), Region.getEndLoc());
1165+
1166+
// Make sure we haven't ended up with any source locations outside the
1167+
// SourceFile (e.g for generated code such as macros), asserting in an
1168+
// asserts build, dropping in a non-asserts build.
1169+
if (!FileSourceRange.contains(Range.Start) ||
1170+
!FileSourceRange.contains(Range.End)) {
1171+
assert(false && "range outside of file");
1172+
continue;
1173+
}
1174+
1175+
// Build up the outer range from the union of all coverage regions.
11541176
if (!OuterRange) {
11551177
OuterRange = Range;
11561178
} else {
@@ -1517,6 +1539,16 @@ struct CoverageMapping : public ASTWalker {
15171539
assignKnownCounter(E);
15181540
}
15191541

1542+
// If we have a macro expansion, push a scoping-only region, and note that
1543+
// we're in a macro expansion. We'll discard any regions recorded within the
1544+
// macro, but will adjust for any control flow that may have happened within
1545+
// the macro.
1546+
if (isa<MacroExpansionExpr>(E)) {
1547+
assignCounter(E, getCurrentCounter());
1548+
pushRegion(SourceMappingRegion::scopingOnly(E, SM));
1549+
MacroDepth += 1;
1550+
}
1551+
15201552
if (isa<LazyInitializerExpr>(E))
15211553
assignKnownCounter(E);
15221554

@@ -1579,6 +1611,11 @@ struct CoverageMapping : public ASTWalker {
15791611
if (hasCounter(E))
15801612
exitRegion(E);
15811613

1614+
if (isa<MacroExpansionExpr>(E)) {
1615+
assert(isInMacroExpansion());
1616+
MacroDepth -= 1;
1617+
}
1618+
15821619
return Action::Continue(E);
15831620
}
15841621
};

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: 61 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -19,20 +19,41 @@ 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
3152
}
3253

3354
@addMembers
3455
struct S2 {
35-
// CHECK: sil_coverage_map{{.*}}variable initialization expression of coverage_macros.S2.(_storage
56+
// CHECK-LABEL: sil_coverage_map{{.*}}variable initialization expression of coverage_macros.S2.(_storage
3657
private var _storage = S1()
3758

3859
@accessViaStorage
@@ -43,7 +64,43 @@ struct S2 {
4364
var y: Int = 17
4465
}
4566

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

0 commit comments

Comments
 (0)