Skip to content

Commit b5c0b6d

Browse files
authored
Merge pull request swiftlang#30381 from eeckstein/lazy_properties
Improve optimization of lazy properties.
2 parents b3606cd + b51284f commit b5c0b6d

File tree

17 files changed

+341
-29
lines changed

17 files changed

+341
-29
lines changed

docs/SIL.rst

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -936,6 +936,19 @@ This is currently true if the function is an addressor that was lazily
936936
generated from a global variable access. Note that the initialization
937937
function itself does not need this attribute. It is private and only
938938
called within the addressor.
939+
::
940+
941+
sil-function-purpose ::= 'lazy_getter'
942+
943+
The function is a getter of a lazy property for which the backing storage is
944+
an ``Optional`` of the property's type. The getter contains a top-level
945+
``switch_enum`` (or ``switch_enum_addr``), which tests if the lazy property
946+
is already computed. In the ``None``-case, the property is computed and stored
947+
to the backing storage of the property.
948+
949+
After the first call of a lazy property getter, it is guaranteed that the
950+
property is computed and consecutive calls always execute the ``Some``-case of
951+
the top-level ``switch_enum``.
939952
::
940953

941954
sil-function-attribute ::= '[weak_imported]'

include/swift/SIL/SILFunction.h

Lines changed: 18 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -118,6 +118,13 @@ class SILFunction
118118
public:
119119
using BlockListType = llvm::iplist<SILBasicBlock>;
120120

121+
// For more information see docs/SIL.rst
122+
enum class Purpose : uint8_t {
123+
None,
124+
GlobalInit,
125+
LazyPropertyGetter
126+
};
127+
121128
private:
122129
friend class SILBasicBlock;
123130
friend class SILModule;
@@ -183,6 +190,8 @@ class SILFunction
183190
/// should use weak linking.
184191
AvailabilityContext Availability;
185192

193+
Purpose specialPurpose = Purpose::None;
194+
186195
/// This is the number of uses of this SILFunction inside the SIL.
187196
/// It does not include references from debug scopes.
188197
unsigned RefCount = 0;
@@ -806,6 +815,8 @@ class SILFunction
806815
void setEffectsKind(EffectsKind E) {
807816
EffectsKindAttr = unsigned(E);
808817
}
818+
819+
Purpose getSpecialPurpose() const { return specialPurpose; }
809820

810821
/// Get this function's global_init attribute.
811822
///
@@ -819,8 +830,13 @@ class SILFunction
819830
/// generated from a global variable access. Note that the initialization
820831
/// function itself does not need this attribute. It is private and only
821832
/// called within the addressor.
822-
bool isGlobalInit() const { return GlobalInitFlag; }
823-
void setGlobalInit(bool isGI) { GlobalInitFlag = isGI; }
833+
bool isGlobalInit() const { return specialPurpose == Purpose::GlobalInit; }
834+
835+
bool isLazyPropertyGetter() const {
836+
return specialPurpose == Purpose::LazyPropertyGetter;
837+
}
838+
839+
void setSpecialPurpose(Purpose purpose) { specialPurpose = purpose; }
824840

825841
/// Return whether this function has a foreign implementation which can
826842
/// be emitted on demand.

lib/ParseSIL/ParseSIL.cpp

Lines changed: 11 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -910,7 +910,7 @@ static bool parseDeclSILOptional(bool *isTransparent,
910910
IsExactSelfClass_t *isExactSelfClass,
911911
SILFunction **dynamicallyReplacedFunction,
912912
Identifier *objCReplacementFor,
913-
bool *isGlobalInit,
913+
SILFunction::Purpose *specialPurpose,
914914
Inline_t *inlineStrategy,
915915
OptimizationMode *optimizationMode,
916916
bool *isLet,
@@ -955,8 +955,10 @@ static bool parseDeclSILOptional(bool *isTransparent,
955955
else if (isWithoutActuallyEscapingThunk
956956
&& SP.P.Tok.getText() == "without_actually_escaping")
957957
*isWithoutActuallyEscapingThunk = true;
958-
else if (isGlobalInit && SP.P.Tok.getText() == "global_init")
959-
*isGlobalInit = true;
958+
else if (specialPurpose && SP.P.Tok.getText() == "global_init")
959+
*specialPurpose = SILFunction::Purpose::GlobalInit;
960+
else if (specialPurpose && SP.P.Tok.getText() == "lazy_getter")
961+
*specialPurpose = SILFunction::Purpose::LazyPropertyGetter;
960962
else if (isWeakImported && SP.P.Tok.getText() == "weak_imported") {
961963
if (M.getASTContext().LangOpts.Target.isOSBinFormatCOFF())
962964
SP.P.diagnose(SP.P.Tok, diag::attr_unsupported_on_target,
@@ -5465,7 +5467,8 @@ bool SILParserTUState::parseDeclSIL(Parser &P) {
54655467
IsExactSelfClass_t isExactSelfClass = IsNotExactSelfClass;
54665468
bool hasOwnershipSSA = false;
54675469
IsThunk_t isThunk = IsNotThunk;
5468-
bool isGlobalInit = false, isWeakImported = false;
5470+
SILFunction::Purpose specialPurpose = SILFunction::Purpose::None;
5471+
bool isWeakImported = false;
54695472
AvailabilityContext availability = AvailabilityContext::alwaysAvailable();
54705473
bool isWithoutActuallyEscapingThunk = false;
54715474
Inline_t inlineStrategy = InlineDefault;
@@ -5480,8 +5483,9 @@ bool SILParserTUState::parseDeclSIL(Parser &P) {
54805483
parseDeclSILOptional(
54815484
&isTransparent, &isSerialized, &isCanonical, &hasOwnershipSSA,
54825485
&isThunk, &isDynamic, &isExactSelfClass, &DynamicallyReplacedFunction,
5483-
&objCReplacementFor, &isGlobalInit, &inlineStrategy, &optimizationMode, nullptr,
5484-
&isWeakImported, &availability, &isWithoutActuallyEscapingThunk, &Semantics,
5486+
&objCReplacementFor, &specialPurpose, &inlineStrategy,
5487+
&optimizationMode, nullptr, &isWeakImported, &availability,
5488+
&isWithoutActuallyEscapingThunk, &Semantics,
54855489
&SpecAttrs, &ClangDecl, &MRK, FunctionState, M) ||
54865490
P.parseToken(tok::at_sign, diag::expected_sil_function_name) ||
54875491
P.parseIdentifier(FnName, FnNameLoc, diag::expected_sil_function_name) ||
@@ -5515,7 +5519,7 @@ bool SILParserTUState::parseDeclSIL(Parser &P) {
55155519
DynamicallyReplacedFunction);
55165520
if (!objCReplacementFor.empty())
55175521
FunctionState.F->setObjCReplacement(objCReplacementFor);
5518-
FunctionState.F->setGlobalInit(isGlobalInit);
5522+
FunctionState.F->setSpecialPurpose(specialPurpose);
55195523
FunctionState.F->setAlwaysWeakImported(isWeakImported);
55205524
FunctionState.F->setAvailabilityForLinkage(availability);
55215525
FunctionState.F->setWithoutActuallyEscapingThunk(

lib/SIL/SILFunctionBuilder.cpp

Lines changed: 18 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -157,7 +157,9 @@ SILFunction *SILFunctionBuilder::getOrCreateFunction(
157157
inlineStrategy, EK);
158158
F->setDebugScope(new (mod) SILDebugScope(loc, F));
159159

160-
F->setGlobalInit(constant.isGlobal());
160+
if (constant.isGlobal())
161+
F->setSpecialPurpose(SILFunction::Purpose::GlobalInit);
162+
161163
if (constant.hasDecl()) {
162164
auto decl = constant.getDecl();
163165

@@ -172,6 +174,21 @@ SILFunction *SILFunctionBuilder::getOrCreateFunction(
172174
// Add attributes for e.g. computed properties.
173175
addFunctionAttributes(F, storage->getAttrs(), mod,
174176
getOrCreateDeclaration);
177+
178+
auto *varDecl = dyn_cast<VarDecl>(storage);
179+
if (varDecl && varDecl->getAttrs().hasAttribute<LazyAttr>() &&
180+
accessor->getAccessorKind() == AccessorKind::Get) {
181+
F->setSpecialPurpose(SILFunction::Purpose::LazyPropertyGetter);
182+
183+
// Lazy property getters should not get inlined because they are usually
184+
// non-tivial functions (otherwise the user would not implement it as
185+
// lazy property). Inlining such getters would most likely not benefit
186+
// other optimizations because the top-level switch_enum cannot be
187+
// constant folded in most cases.
188+
// Also, not inlining lazy property getters enables optimizing them in
189+
// CSE.
190+
F->setInlineStrategy(NoInline);
191+
}
175192
}
176193
addFunctionAttributes(F, decl->getAttrs(), mod, getOrCreateDeclaration,
177194
constant);

lib/SIL/SILPrinter.cpp

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2444,8 +2444,16 @@ void SILFunction::print(SILPrintContext &PrintCtx) const {
24442444
if (isWithoutActuallyEscapingThunk())
24452445
OS << "[without_actually_escaping] ";
24462446

2447-
if (isGlobalInit())
2447+
switch (getSpecialPurpose()) {
2448+
case SILFunction::Purpose::None:
2449+
break;
2450+
case SILFunction::Purpose::GlobalInit:
24482451
OS << "[global_init] ";
2452+
break;
2453+
case SILFunction::Purpose::LazyPropertyGetter:
2454+
OS << "[lazy_getter] ";
2455+
break;
2456+
}
24492457
if (isAlwaysWeakImported())
24502458
OS << "[weak_imported] ";
24512459
auto availability = getAvailabilityForLinkage();

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
}

lib/Serialization/DeserializeSIL.cpp

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -511,14 +511,14 @@ SILDeserializer::readSILFunctionChecked(DeclID FID, SILFunction *existingFn,
511511
IdentifierID replacedFunctionID;
512512
GenericSignatureID genericSigID;
513513
unsigned rawLinkage, isTransparent, isSerialized, isThunk,
514-
isWithoutactuallyEscapingThunk, isGlobal, inlineStrategy,
514+
isWithoutactuallyEscapingThunk, specialPurpose, inlineStrategy,
515515
optimizationMode, effect, numSpecAttrs, hasQualifiedOwnership,
516516
isWeakImported, LIST_VER_TUPLE_PIECES(available),
517517
isDynamic, isExactSelfClass;
518518
ArrayRef<uint64_t> SemanticsIDs;
519519
SILFunctionLayout::readRecord(
520520
scratch, rawLinkage, isTransparent, isSerialized, isThunk,
521-
isWithoutactuallyEscapingThunk, isGlobal, inlineStrategy,
521+
isWithoutactuallyEscapingThunk, specialPurpose, inlineStrategy,
522522
optimizationMode, effect, numSpecAttrs, hasQualifiedOwnership,
523523
isWeakImported, LIST_VER_TUPLE_PIECES(available),
524524
isDynamic, isExactSelfClass,
@@ -630,7 +630,7 @@ SILDeserializer::readSILFunctionChecked(DeclID FID, SILFunction *existingFn,
630630
fn->setThunk(IsThunk_t(isThunk));
631631
fn->setWithoutActuallyEscapingThunk(bool(isWithoutactuallyEscapingThunk));
632632
fn->setInlineStrategy(Inline_t(inlineStrategy));
633-
fn->setGlobalInit(isGlobal == 1);
633+
fn->setSpecialPurpose(SILFunction::Purpose(specialPurpose));
634634
fn->setEffectsKind(EffectsKind(effect));
635635
fn->setOptimizationMode(OptimizationMode(optimizationMode));
636636
fn->setAlwaysWeakImported(isWeakImported);

lib/Serialization/ModuleFormat.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -55,7 +55,7 @@ const uint16_t SWIFTMODULE_VERSION_MAJOR = 0;
5555
/// describe what change you made. The content of this comment isn't important;
5656
/// it just ensures a conflict if two people change the module format.
5757
/// Don't worry about adhering to the 80-column limit for this line.
58-
const uint16_t SWIFTMODULE_VERSION_MINOR = 546; // Avoid conflict with downstream bump
58+
const uint16_t SWIFTMODULE_VERSION_MINOR = 547; // lazyPropertyGetter flag
5959

6060
/// A standard hash seed used for all string hashes in a serialized module.
6161
///

lib/Serialization/SILFormat.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -273,7 +273,7 @@ namespace sil_block {
273273
BCFixed<2>, // serialized
274274
BCFixed<2>, // thunks: signature optimized/reabstraction
275275
BCFixed<1>, // without_actually_escaping
276-
BCFixed<1>, // global_init
276+
BCFixed<3>, // specialPurpose
277277
BCFixed<2>, // inlineStrategy
278278
BCFixed<2>, // optimizationMode
279279
BCFixed<3>, // side effect info.

0 commit comments

Comments
 (0)