Skip to content

Commit 64fcbd9

Browse files
committed
[Profiler] Support lazy variable initializers
Start visiting LazyInitializerExpr for profiling, such that we emit a profile counter when initializing the initial value for the first time. rdar://43393937
1 parent d915202 commit 64fcbd9

File tree

5 files changed

+118
-20
lines changed

5 files changed

+118
-20
lines changed

lib/SIL/IR/SILProfiler.cpp

Lines changed: 36 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -196,6 +196,13 @@ static bool skipExpr(Expr *E) {
196196
return !E->getStartLoc().isValid() || !E->getEndLoc().isValid();
197197
}
198198

199+
/// Whether the children of an unmapped decl should still be walked.
200+
static bool shouldWalkUnmappedDecl(const Decl *D) {
201+
// We want to walk into the initializer for a pattern binding decl. This
202+
// allows us to map LazyInitializerExprs.
203+
return isa<PatternBindingDecl>(D);
204+
}
205+
199206
/// An ASTWalker that maps ASTNodes to profiling counters.
200207
struct MapRegionCounters : public ASTWalker {
201208
/// The next counter value to assign.
@@ -207,6 +214,12 @@ struct MapRegionCounters : public ASTWalker {
207214
MapRegionCounters(llvm::DenseMap<ASTNode, unsigned> &CounterMap)
208215
: CounterMap(CounterMap) {}
209216

217+
LazyInitializerWalking getLazyInitializerWalkingBehavior() override {
218+
// We want to walk lazy initializers present in the synthesized getter for
219+
// a lazy variable.
220+
return LazyInitializerWalking::InAccessor;
221+
}
222+
210223
void mapRegion(ASTNode N) {
211224
CounterMap[N] = NextCounter;
212225

@@ -225,7 +238,7 @@ struct MapRegionCounters : public ASTWalker {
225238

226239
bool walkToDeclPre(Decl *D) override {
227240
if (isUnmapped(D))
228-
return false;
241+
return shouldWalkUnmappedDecl(D);
229242

230243
if (auto *AFD = dyn_cast<AbstractFunctionDecl>(D)) {
231244
return visitFunctionDecl(*this, AFD, [&] { mapRegion(AFD->getBody()); });
@@ -274,14 +287,8 @@ struct MapRegionCounters : public ASTWalker {
274287
mapRegion(IE->getThenExpr());
275288
}
276289

277-
// rdar://42792053
278-
// TODO: There's an outstanding issue here with LazyInitializerExpr. A LIE
279-
// is copied into the body of a property getter after type-checking (before
280-
// coverage). ASTWalker only visits this expression once via the property's
281-
// VarDecl, and does not visit it again within the getter. This results in
282-
// missing coverage. SILGen treats the init expr as part of the getter, but
283-
// its SILProfiler has no information about the init because the LIE isn't
284-
// visited here.
290+
if (isa<LazyInitializerExpr>(E))
291+
mapRegion(E);
285292

286293
return {true, E};
287294
}
@@ -579,7 +586,7 @@ struct PGOMapping : public ASTWalker {
579586

580587
bool walkToDeclPre(Decl *D) override {
581588
if (isUnmapped(D))
582-
return false;
589+
return shouldWalkUnmappedDecl(D);
583590
if (auto *AFD = dyn_cast<AbstractFunctionDecl>(D)) {
584591
return visitFunctionDecl(*this, AFD, [&] {
585592
setKnownExecutionCount(AFD->getBody());
@@ -591,6 +598,12 @@ struct PGOMapping : public ASTWalker {
591598
return true;
592599
}
593600

601+
LazyInitializerWalking getLazyInitializerWalkingBehavior() override {
602+
// We want to walk lazy initializers present in the synthesized getter for
603+
// a lazy variable.
604+
return LazyInitializerWalking::InAccessor;
605+
}
606+
594607
std::pair<bool, Stmt *> walkToStmtPre(Stmt *S) override {
595608
unsigned parent = getParentCounter();
596609
auto parentCount = LoadedCounts.Counts[parent];
@@ -674,6 +687,9 @@ struct PGOMapping : public ASTWalker {
674687
}
675688
setExecutionCount(elseExpr, subtract(count, thenCount));
676689
}
690+
if (isa<LazyInitializerExpr>(E))
691+
setKnownExecutionCount(E);
692+
677693
return {true, E};
678694
}
679695
};
@@ -903,6 +919,12 @@ struct CoverageMapping : public ASTWalker {
903919
public:
904920
CoverageMapping(const SourceManager &SM) : SM(SM) {}
905921

922+
LazyInitializerWalking getLazyInitializerWalkingBehavior() override {
923+
// We want to walk lazy initializers present in the synthesized getter for
924+
// a lazy variable.
925+
return LazyInitializerWalking::InAccessor;
926+
}
927+
906928
/// Generate the coverage counter mapping regions from collected
907929
/// source regions.
908930
SILCoverageMap *emitSourceRegions(
@@ -930,7 +952,7 @@ struct CoverageMapping : public ASTWalker {
930952

931953
bool walkToDeclPre(Decl *D) override {
932954
if (isUnmapped(D))
933-
return false;
955+
return shouldWalkUnmappedDecl(D);
934956

935957
if (auto *AFD = dyn_cast<AbstractFunctionDecl>(D)) {
936958
return visitFunctionDecl(*this, AFD, [&] {
@@ -1124,6 +1146,9 @@ struct CoverageMapping : public ASTWalker {
11241146
}
11251147
}
11261148

1149+
if (isa<LazyInitializerExpr>(E))
1150+
assignCounter(E);
1151+
11271152
if (hasCounter(E) && !Parent.isNull())
11281153
pushRegion(E);
11291154
return {true, E};

lib/SILGen/SILGenExpr.cpp

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -415,7 +415,9 @@ namespace {
415415
return RValue(SGF, E, SGF.emitAddressOfLValue(E->getSubExpr(),
416416
std::move(lv)));
417417
}
418-
418+
419+
RValue visitLazyInitializerExpr(LazyInitializerExpr *E, SGFContext C);
420+
419421
RValue visitApplyExpr(ApplyExpr *E, SGFContext C);
420422

421423
RValue visitDiscardAssignmentExpr(DiscardAssignmentExpr *E, SGFContext C) {
@@ -708,6 +710,15 @@ tryEmitAsBridgingConversion(SILGenFunction &SGF, Expr *E, bool isExplicit,
708710
return SGF.emitConvertedRValue(subExpr, conversion, C);
709711
}
710712

713+
RValue RValueEmitter::visitLazyInitializerExpr(LazyInitializerExpr *E,
714+
SGFContext C) {
715+
// We need to emit a profiler count increment specifically for the lazy
716+
// initialization, as we don't want to record an increment for every call to
717+
// the getter.
718+
SGF.emitProfilerIncrement(E);
719+
return visit(E->getSubExpr(), C);
720+
}
721+
711722
RValue RValueEmitter::visitApplyExpr(ApplyExpr *E, SGFContext C) {
712723
return SGF.emitApplyExpr(E, C);
713724
}

test/Profiler/coverage_class.swift

Lines changed: 0 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -52,11 +52,3 @@ struct S2 {
5252
// CHECK-NEXT: [[@LINE+1]]:17 -> [[@LINE+1]]:27 : 0
5353
var m1: Int = g1 ? 0 : 1
5454
}
55-
56-
// Test that the crash from SR-8429 is avoided. Follow-up work is
57-
// needed to generate the correct coverage mapping here. Coverage for
58-
// `offset` should be associated with its getter, not the class
59-
// constructor.
60-
class C2 {
61-
lazy var offset: Int = true ? 30 : 55
62-
}

test/Profiler/coverage_lazy.swift

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
// RUN: %target-swift-frontend -Xllvm -sil-full-demangle -profile-generate -profile-coverage-mapping -emit-sil -module-name coverage_lazy %s | %FileCheck %s
2+
// RUN: %target-swift-frontend -profile-generate -profile-coverage-mapping -emit-ir %s
3+
4+
// Test that the crash from SR-8429 is avoided, and that we generate the
5+
// correct coverage.
6+
class C {
7+
// CHECK-LABEL: sil hidden [lazy_getter] [noinline] @$s13coverage_lazy1CC6offsetSivg : $@convention(method) (@guaranteed C) -> Int
8+
// CHECK: switch_enum {{%[0-9]+}} : $Optional<Int>, case #Optional.some!enumelt: {{bb[0-9]}}, case #Optional.none!enumelt: [[INITBB:bb[0-9]]]
9+
// CHECK: [[INITBB]]
10+
// CHECK-NEXT: string_literal
11+
// CHECK-NEXT: integer_literal $Builtin.Int64, 0
12+
// CHECK-NEXT: integer_literal $Builtin.Int32, 4
13+
// CHECK-NEXT: integer_literal $Builtin.Int32, 2
14+
// CHECK-NEXT: int_instrprof_increment
15+
// CHECK: function_ref @$sSb6randomSbyFZ : $@convention(method) (@thin Bool.Type) -> Bool
16+
// CHECK: cond_br {{%[0-9]+}}, [[TRUEBB:bb[0-9]]], {{bb[0-9]}}
17+
// CHECK: [[TRUEBB]]
18+
// CHECK-NEXT: string_literal
19+
// CHECK-NEXT: integer_literal $Builtin.Int64, 0
20+
// CHECK-NEXT: integer_literal $Builtin.Int32, 4
21+
// CHECK-NEXT: integer_literal $Builtin.Int32, 3
22+
23+
// CHECK-LABEL: sil_coverage_map {{.*}} // coverage_lazy.C.offset.getter : Swift.Int
24+
// CHECK-NEXT: [[@LINE+4]]:38 -> [[@LINE+4]]:40 : 3
25+
// CHECK-NEXT: [[@LINE+3]]:43 -> [[@LINE+3]]:45 : (2 - 3)
26+
// CHECK-NEXT: [[@LINE+2]]:26 -> [[@LINE+2]]:45 : 2
27+
// CHECK-NEXT: }
28+
lazy var offset: Int = .random() ? 30 : 55
29+
}

test/Profiler/pgo_lazy.swift

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
// RUN: %empty-directory(%t)
2+
// RUN: %target-build-swift %s -profile-generate -Xfrontend -disable-incremental-llvm-codegen -module-name pgo_lazy -o %t/main
3+
4+
// This unusual use of 'sh' allows the path of the profraw file to be
5+
// substituted by %target-run.
6+
// RUN: %target-codesign %t/main
7+
// RUN: %target-run sh -c 'env LLVM_PROFILE_FILE=$1 $2' -- %t/default.profraw %t/main
8+
9+
// RUN: %llvm-profdata merge %t/default.profraw -o %t/default.profdata
10+
11+
// RUN: %target-swift-frontend %s -Xllvm -sil-full-demangle -profile-use=%t/default.profdata -emit-sorted-sil -emit-sil -module-name pgo_lazy -o - | %FileCheck %s --check-prefix=SIL
12+
// RUN: %target-swift-frontend %s -Xllvm -sil-full-demangle -profile-use=%t/default.profdata -O -emit-sorted-sil -emit-sil -module-name pgo_lazy -o - | %FileCheck %s --check-prefix=SIL
13+
// RUN: %target-swift-frontend %s -Xllvm -sil-full-demangle -profile-use=%t/default.profdata -emit-ir -module-name pgo_lazy -o - | %FileCheck %s --check-prefix=IR
14+
// RUN: %target-swift-frontend %s -Xllvm -sil-full-demangle -profile-use=%t/default.profdata -O -emit-ir -module-name pgo_lazy -o - | %FileCheck %s --check-prefix=IR
15+
16+
// REQUIRES: profile_runtime
17+
// REQUIRES: executable_test
18+
// REQUIRES: OS=macosx
19+
20+
var cond = true
21+
22+
public struct S {
23+
// SIL-LABEL: sil [lazy_getter] [noinline] @$s8pgo_lazy1SV1xSivg : $@convention(method) (@inout S) -> Int !function_entry_count(35)
24+
// SIL: cond_br {{.*}} !true_count(5) !false_count(30)
25+
public lazy var x = cond ? 2 : 3
26+
}
27+
28+
func triggerLazy() -> Int {
29+
var s = S()
30+
return s.x
31+
}
32+
public var total = 0
33+
for _ in 0 ..< 5 {
34+
total += triggerLazy()
35+
}
36+
cond = false
37+
for _ in 0 ..< 30 {
38+
total += triggerLazy()
39+
}
40+
41+
// IR: !{!"branch_weights", i32 6, i32 31}

0 commit comments

Comments
 (0)