Skip to content

Commit b51284f

Browse files
committed
CSE: optimize calls of lazy property getters
If such a call is dominated by another call to the same getter, it is replaced by a direct load of the property - assuming that it is already computed. rdar://problem/34715412
1 parent 2de4256 commit b51284f

File tree

2 files changed

+244
-5
lines changed

2 files changed

+244
-5
lines changed

lib/SILOptimizer/Transforms/CSE.cpp

Lines changed: 106 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,9 @@
3131
#include "swift/SILOptimizer/PassManager/Passes.h"
3232
#include "swift/SILOptimizer/PassManager/Transforms.h"
3333
#include "swift/SILOptimizer/Utils/InstOptUtils.h"
34+
#include "swift/SILOptimizer/Utils/SILInliner.h"
35+
#include "swift/SILOptimizer/Utils/SILOptFunctionBuilder.h"
36+
#include "swift/SILOptimizer/Utils/BasicBlockOptUtils.h"
3437
#include "llvm/ADT/Hashing.h"
3538
#include "llvm/ADT/STLExtras.h"
3639
#include "llvm/ADT/ScopedHashTable.h"
@@ -444,6 +447,10 @@ namespace swift {
444447
/// eliminating trivially redundant instructions and using simplifyInstruction
445448
/// to canonicalize things as it goes. It is intended to be fast and catch
446449
/// obvious cases so that SILCombine and other passes are more effective.
450+
///
451+
/// It also optimizes calls to lazy property getters: If such a call is
452+
/// dominated by another call to the same getter, it is replaced by a direct
453+
/// load of the property - assuming that it is already computed.
447454
class CSE {
448455
public:
449456
typedef llvm::ScopedHashTableVal<SimpleValue, ValueBase *> SimpleValueHTType;
@@ -462,11 +469,21 @@ class CSE {
462469

463470
SideEffectAnalysis *SEA;
464471

465-
CSE(bool RunsOnHighLevelSil, SideEffectAnalysis *SEA)
466-
: SEA(SEA), RunsOnHighLevelSil(RunsOnHighLevelSil) {}
472+
SILOptFunctionBuilder &FuncBuilder;
473+
474+
/// The set of calls to lazy property getters which can be replace by a direct
475+
/// load of the property value.
476+
llvm::SmallVector<ApplyInst *, 8> lazyPropertyGetters;
477+
478+
CSE(bool RunsOnHighLevelSil, SideEffectAnalysis *SEA,
479+
SILOptFunctionBuilder &FuncBuilder)
480+
: SEA(SEA), FuncBuilder(FuncBuilder),
481+
RunsOnHighLevelSil(RunsOnHighLevelSil) {}
467482

468483
bool processFunction(SILFunction &F, DominanceInfo *DT);
469-
484+
485+
bool processLazyPropertyGetters();
486+
470487
bool canHandle(SILInstruction *Inst);
471488

472489
private:
@@ -576,6 +593,40 @@ bool CSE::processFunction(SILFunction &Fm, DominanceInfo *DT) {
576593
return Changed;
577594
}
578595

596+
/// Replace lazy property getters (which are dominated by the same getter)
597+
/// by a direct load of the value.
598+
bool CSE::processLazyPropertyGetters() {
599+
bool changed = false;
600+
for (ApplyInst *ai : lazyPropertyGetters) {
601+
SILFunction *getter = ai->getReferencedFunctionOrNull();
602+
assert(getter && getter->isLazyPropertyGetter());
603+
SILBasicBlock *callBlock = ai->getParent();
604+
605+
// Inline the getter...
606+
SILInliner::inlineFullApply(ai, SILInliner::InlineKind::PerformanceInline,
607+
FuncBuilder);
608+
609+
// ...and fold the switch_enum in the first block to the Optional.some case.
610+
// The Optional.none branch becomes dead.
611+
auto *sei = cast<SwitchEnumInst>(callBlock->getTerminator());
612+
ASTContext &ctxt = callBlock->getParent()->getModule().getASTContext();
613+
EnumElementDecl *someDecl = ctxt.getOptionalSomeDecl();
614+
SILBasicBlock *someDest = sei->getCaseDestination(someDecl);
615+
assert(someDest->getNumArguments() == 1);
616+
SILValue enumVal = sei->getOperand();
617+
SILBuilder builder(sei);
618+
SILType ty = enumVal->getType().getEnumElementType(someDecl,
619+
sei->getModule(), builder.getTypeExpansionContext());
620+
auto *ued =
621+
builder.createUncheckedEnumData(sei->getLoc(), enumVal, someDecl, ty);
622+
builder.createBranch(sei->getLoc(), someDest, { ued });
623+
sei->eraseFromParent();
624+
changed = true;
625+
++NumCSE;
626+
}
627+
return changed;
628+
}
629+
579630
namespace {
580631
// A very simple cloner for cloning instructions inside
581632
// the same function. The only interesting thing it does
@@ -768,6 +819,34 @@ bool CSE::processOpenExistentialRef(OpenExistentialRefInst *Inst,
768819
return true;
769820
}
770821

822+
/// Returns true if \p ai is a call to a lazy property getter, which we can
823+
/// handle.
824+
static bool isLazyPropertyGetter(ApplyInst *ai) {
825+
SILFunction *callee = ai->getReferencedFunctionOrNull();
826+
if (!callee || callee->isExternalDeclaration() ||
827+
!callee->isLazyPropertyGetter())
828+
return false;
829+
830+
// Check if the first block has a switch_enum of an Optional.
831+
// We don't handle getters of generic types, which have a switch_enum_addr.
832+
// This will be obsolete with opaque values anyway.
833+
auto *SEI = dyn_cast<SwitchEnumInst>(callee->getEntryBlock()->getTerminator());
834+
if (!SEI)
835+
return false;
836+
837+
ASTContext &ctxt = SEI->getFunction()->getModule().getASTContext();
838+
EnumElementDecl *someDecl = ctxt.getOptionalSomeDecl();
839+
840+
for (unsigned i = 0, e = SEI->getNumCases(); i != e; ++i) {
841+
auto Entry = SEI->getCase(i);
842+
if (Entry.first == someDecl) {
843+
SILBasicBlock *destBlock = Entry.second;
844+
return destBlock->getNumArguments() == 1;
845+
}
846+
}
847+
return false;
848+
}
849+
771850
bool CSE::processNode(DominanceInfoNode *Node) {
772851
SILBasicBlock *BB = Node->getBlock();
773852
bool Changed = false;
@@ -817,6 +896,16 @@ bool CSE::processNode(DominanceInfoNode *Node) {
817896
if (SILInstruction *AvailInst = AvailableValues->lookup(Inst)) {
818897
LLVM_DEBUG(llvm::dbgs() << "SILCSE CSE: " << *Inst << " to: "
819898
<< *AvailInst << '\n');
899+
900+
auto *AI = dyn_cast<ApplyInst>(Inst);
901+
if (AI && isLazyPropertyGetter(AI)) {
902+
// We do the actual transformation for lazy property getters later. It
903+
// changes the CFG and we don't want to disturb the dominator tree walk
904+
// here.
905+
lazyPropertyGetters.push_back(AI);
906+
continue;
907+
}
908+
820909
// Instructions producing a new opened archetype need a special handling,
821910
// because replacing these instructions may require a replacement
822911
// of the opened archetype type operands in some of the uses.
@@ -874,6 +963,9 @@ bool CSE::canHandle(SILInstruction *Inst) {
874963
if (MB == SILInstruction::MemoryBehavior::None)
875964
return true;
876965

966+
if (isLazyPropertyGetter(AI))
967+
return true;
968+
877969
return false;
878970
}
879971
if (auto *BI = dyn_cast<BuiltinInst>(Inst)) {
@@ -1172,8 +1264,9 @@ class SILCSE : public SILFunctionTransform {
11721264
DominanceAnalysis* DA = getAnalysis<DominanceAnalysis>();
11731265

11741266
auto *SEA = PM->getAnalysis<SideEffectAnalysis>();
1267+
SILOptFunctionBuilder FuncBuilder(*this);
11751268

1176-
CSE C(RunsOnHighLevelSil, SEA);
1269+
CSE C(RunsOnHighLevelSil, SEA, FuncBuilder);
11771270
bool Changed = false;
11781271

11791272
// Perform the traditional CSE.
@@ -1182,7 +1275,15 @@ class SILCSE : public SILFunctionTransform {
11821275
// Perform CSE of existential and witness_method instructions.
11831276
Changed |= CSEExistentialCalls(getFunction(),
11841277
DA->get(getFunction()));
1185-
if (Changed) {
1278+
1279+
// Handle calls to lazy property getters, which are collected in
1280+
// processFunction().
1281+
if (C.processLazyPropertyGetters()) {
1282+
// Cleanup the dead blocks from the inlined lazy property getters.
1283+
removeUnreachableBlocks(*getFunction());
1284+
1285+
invalidateAnalysis(SILAnalysis::InvalidationKind::Everything);
1286+
} else if (Changed) {
11861287
invalidateAnalysis(SILAnalysis::InvalidationKind::CallsAndInstructions);
11871288
}
11881289
}
Lines changed: 138 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,138 @@
1+
// RUN: %target-swift-frontend -primary-file %s -O -sil-verify-all -module-name=test -emit-sil | %FileCheck %s
2+
3+
// Also do an end-to-end test to check if the generated code is correct.
4+
// RUN: %empty-directory(%t)
5+
// RUN: %target-build-swift -O -Xllvm -module-name=test %s -o %t/a.out
6+
// RUN: %target-run %t/a.out | %FileCheck %s -check-prefix=CHECK-OUTPUT
7+
// REQUIRES: executable_test
8+
9+
var g = 0
10+
11+
final class Myclass {
12+
lazy var lvar: Int = {
13+
print("lvar init")
14+
return g
15+
}()
16+
}
17+
18+
struct Mystruct {
19+
lazy var lvar: Int = {
20+
print("lvar init")
21+
return g
22+
}()
23+
}
24+
25+
// CHECK-LABEL: sil {{.*}} @$s4test0A7_simpleySiAA7MyclassCF
26+
// CHECK: [[GETTER:%[0-9]+]] = function_ref @$s4test7MyclassC4lvarSivg
27+
// CHECK: [[V1:%[0-9]+]] = apply [[GETTER]](%0)
28+
// CHECK: [[V2OPT:%[0-9]+]] = load
29+
// CHECK: [[V2:%[0-9]+]] = unchecked_enum_data [[V2OPT]]
30+
// CHECK: [[V1VAL:%[0-9]+]] = struct_extract [[V1]]
31+
// CHECK: [[V2VAL:%[0-9]+]] = struct_extract [[V2]]
32+
// CHECK: builtin "sadd{{.*}}"([[V1VAL]] {{.*}}, [[V2VAL]]
33+
// CHECK: } // end sil function '$s4test0A7_simpleySiAA7MyclassCF'
34+
@inline(never)
35+
func test_simple(_ c: Myclass) -> Int {
36+
g = 27
37+
let v1 = c.lvar
38+
let v2 = c.lvar
39+
return v1 &+ v2
40+
}
41+
42+
// CHECK-LABEL: sil {{.*}} @$s4test0A4_cfgySiAA7MyclassC_SbtF
43+
// CHECK: [[GETTER:%[0-9]+]] = function_ref @$s4test7MyclassC4lvarSivg
44+
// CHECK: [[V1:%[0-9]+]] = apply [[GETTER]](%0)
45+
// CHECK: bb1:
46+
// CHECK: [[V2OPT:%[0-9]+]] = load
47+
// CHECK: [[V2:%[0-9]+]] = unchecked_enum_data [[V2OPT]]
48+
// CHECK: [[V2VAL:%[0-9]+]] = struct_extract [[V2]]
49+
// CHECK: br bb3([[V2VAL]]
50+
// CHECK: bb2:
51+
// CHECK: bb3([[V2PHI:%[0-9]+]] {{.*}}):
52+
// CHECK: [[V1VAL:%[0-9]+]] = struct_extract [[V1]]
53+
// CHECK: builtin "sadd{{.*}}"([[V1VAL]] {{.*}}, [[V2PHI]]
54+
// CHECK: } // end sil function '$s4test0A4_cfgySiAA7MyclassC_SbtF'
55+
@inline(never)
56+
func test_cfg(_ c: Myclass, _ b: Bool) -> Int {
57+
g = 10
58+
let v1 = c.lvar
59+
var v2: Int
60+
if b {
61+
v2 = c.lvar
62+
} else {
63+
v2 = 0
64+
}
65+
return v1 &+ v2
66+
}
67+
68+
// CHECK-LABEL: sil {{.*}} @$s4test0A12_no_hoistingySiAA7MyclassC_SbtF
69+
// CHECK: bb1:
70+
// CHECK: [[GETTER1:%[0-9]+]] = function_ref @$s4test7MyclassC4lvarSivg
71+
// CHECK: = apply [[GETTER1]](%0)
72+
// CHECK: bb2:
73+
// CHECK: [[GETTER2:%[0-9]+]] = function_ref @$s4test7MyclassC4lvarSivg
74+
// CHECK: = apply [[GETTER2]](%0)
75+
// CHECK: bb3({{.*}}):
76+
// CHECK: } // end sil function '$s4test0A12_no_hoistingySiAA7MyclassC_SbtF'
77+
@inline(never)
78+
func test_no_hoisting(_ c: Myclass, _ b: Bool) -> Int {
79+
var v: Int
80+
if b {
81+
g = 20
82+
v = c.lvar + 2
83+
} else {
84+
g = 30
85+
v = c.lvar + 3
86+
}
87+
return v
88+
}
89+
90+
// CHECK-LABEL: sil {{.*}} @$s4test0A7_structySiAA8MystructVF
91+
// CHECK: [[GETTER:%[0-9]+]] = function_ref @$s4test8MystructV4lvarSivg
92+
// CHECK: [[V1:%[0-9]+]] = apply [[GETTER]]({{.*}})
93+
// CHECK: [[V2OPT:%[0-9]+]] = load
94+
// CHECK: [[V2:%[0-9]+]] = unchecked_enum_data [[V2OPT]]
95+
// CHECK: [[V1VAL:%[0-9]+]] = struct_extract [[V1]]
96+
// CHECK: [[V2VAL:%[0-9]+]] = struct_extract [[V2]]
97+
// CHECK: builtin "sadd{{.*}}"([[V1VAL]] {{.*}}, [[V2VAL]]
98+
// CHECK: } // end sil function '$s4test0A7_structySiAA8MystructVF'
99+
@inline(never)
100+
func test_struct(_ s: Mystruct) -> Int {
101+
var sm = s
102+
g = 42
103+
let v1 = sm.lvar
104+
let v2 = sm.lvar
105+
return v1 &+ v2
106+
}
107+
108+
func calltests() {
109+
// CHECK-OUTPUT-LABEL: test_simple
110+
print("test_simple")
111+
// CHECK-OUTPUT-NEXT: lvar init
112+
// CHECK-OUTPUT-NEXT: 54
113+
print(test_simple(Myclass()))
114+
115+
// CHECK-OUTPUT-LABEL: test_cfg
116+
print("test_cfg")
117+
// CHECK-OUTPUT-NEXT: lvar init
118+
// CHECK-OUTPUT-NEXT: 20
119+
print(test_cfg(Myclass(), true))
120+
121+
// CHECK-OUTPUT-LABEL: test_no_hoisting
122+
print("test_no_hoisting")
123+
// CHECK-OUTPUT-NEXT: lvar init
124+
// CHECK-OUTPUT-NEXT: 22
125+
print(test_no_hoisting(Myclass(), true))
126+
// CHECK-OUTPUT-NEXT: lvar init
127+
// CHECK-OUTPUT-NEXT: 33
128+
print(test_no_hoisting(Myclass(), false))
129+
130+
// CHECK-OUTPUT-LABEL: test_struct
131+
print("test_struct")
132+
// CHECK-OUTPUT-NEXT: lvar init
133+
// CHECK-OUTPUT-NEXT: 84
134+
print(test_struct(Mystruct()))
135+
}
136+
137+
calltests()
138+

0 commit comments

Comments
 (0)