Skip to content

Commit a96890c

Browse files
authored
Merge pull request #24929 from eeckstein/optimize-keypath
2 parents b170ad7 + da38e3a commit a96890c

File tree

4 files changed

+508
-4
lines changed

4 files changed

+508
-4
lines changed

lib/SILOptimizer/SILCombiner/SILCombiner.h

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -226,6 +226,7 @@ class SILCombiner :
226226
SILInstruction *visitRetainValueAddrInst(RetainValueAddrInst *CI);
227227
SILInstruction *visitPartialApplyInst(PartialApplyInst *AI);
228228
SILInstruction *visitApplyInst(ApplyInst *AI);
229+
SILInstruction *visitBeginApplyInst(BeginApplyInst *BAI);
229230
SILInstruction *visitTryApplyInst(TryApplyInst *AI);
230231
SILInstruction *optimizeStringObject(BuiltinInst *BI);
231232
SILInstruction *visitBuiltinInst(BuiltinInst *BI);
@@ -299,6 +300,10 @@ class SILCombiner :
299300

300301
SILInstruction *optimizeApplyOfConvertFunctionInst(FullApplySite AI,
301302
ConvertFunctionInst *CFI);
303+
304+
bool tryOptimizeKeypath(ApplyInst *AI);
305+
bool tryOptimizeInoutKeypath(BeginApplyInst *AI);
306+
302307
// Optimize concatenation of string literals.
303308
// Constant-fold concatenation of string literals known at compile-time.
304309
SILInstruction *optimizeConcatenationOfStringLiterals(ApplyInst *AI);

lib/SILOptimizer/SILCombiner/SILCombinerApplyVisitors.cpp

Lines changed: 195 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,10 +30,13 @@
3030
#include "llvm/ADT/DenseMap.h"
3131
#include "llvm/ADT/SmallPtrSet.h"
3232
#include "llvm/ADT/SmallVector.h"
33+
#include "llvm/ADT/Statistic.h"
3334

3435
using namespace swift;
3536
using namespace swift::PatternMatch;
3637

38+
STATISTIC(NumOptimizedKeypaths, "Number of optimized keypath instructions");
39+
3740
/// Remove pointless reabstraction thunk closures.
3841
/// partial_apply %reabstraction_thunk_typeAtoB(
3942
/// partial_apply %reabstraction_thunk_typeBtoA %closure_typeB))
@@ -548,6 +551,189 @@ SILCombiner::optimizeApplyOfConvertFunctionInst(FullApplySite AI,
548551
return NAI;
549552
}
550553

554+
/// Ends the begin_access "scope" if a begin_access was inserted for optimizing
555+
/// a keypath pattern.
556+
static void insertEndAccess(BeginAccessInst *&beginAccess, bool isModify,
557+
SILBuilder &builder) {
558+
if (beginAccess) {
559+
builder.createEndAccess(beginAccess->getLoc(), beginAccess,
560+
/*aborted*/ false);
561+
if (isModify)
562+
beginAccess->setAccessKind(SILAccessKind::Modify);
563+
beginAccess = nullptr;
564+
}
565+
}
566+
567+
/// Creates the projection pattern for a keypath instruction.
568+
///
569+
/// Currently only the StoredProperty pattern is handled.
570+
/// TODO: handle other patterns, like getters/setters, optional chaining, etc.
571+
///
572+
/// Returns false if \p keyPath is not a keypath instruction or if there is any
573+
/// other reason why the optimization cannot be done.
574+
static SILValue createKeypathProjections(SILValue keyPath, SILValue root,
575+
SILLocation loc,
576+
BeginAccessInst *&beginAccess,
577+
SILBuilder &builder) {
578+
if (auto *upCast = dyn_cast<UpcastInst>(keyPath))
579+
keyPath = upCast->getOperand();
580+
581+
// Is it a keypath instruction at all?
582+
auto *kpInst = dyn_cast<KeyPathInst>(keyPath);
583+
if (!kpInst || !kpInst->hasPattern())
584+
return SILValue();
585+
586+
auto components = kpInst->getPattern()->getComponents();
587+
588+
// Check if the keypath only contains patterns which we support.
589+
for (const KeyPathPatternComponent &comp : components) {
590+
if (comp.getKind() != KeyPathPatternComponent::Kind::StoredProperty)
591+
return SILValue();
592+
}
593+
594+
SILValue addr = root;
595+
for (const KeyPathPatternComponent &comp : components) {
596+
assert(comp.getKind() == KeyPathPatternComponent::Kind::StoredProperty);
597+
VarDecl *storedProperty = comp.getStoredPropertyDecl();
598+
SILValue elementAddr;
599+
if (addr->getType().getStructOrBoundGenericStruct()) {
600+
addr = builder.createStructElementAddr(loc, addr, storedProperty);
601+
} else if (addr->getType().getClassOrBoundGenericClass()) {
602+
LoadInst *Ref = builder.createLoad(loc, addr,
603+
LoadOwnershipQualifier::Unqualified);
604+
insertEndAccess(beginAccess, /*isModify*/ false, builder);
605+
addr = builder.createRefElementAddr(loc, Ref, storedProperty);
606+
607+
// Class members need access enforcement.
608+
if (builder.getModule().getOptions().EnforceExclusivityDynamic) {
609+
beginAccess = builder.createBeginAccess(loc, addr, SILAccessKind::Read,
610+
SILAccessEnforcement::Dynamic,
611+
/*noNestedConflict*/ false,
612+
/*fromBuiltin*/ false);
613+
addr = beginAccess;
614+
}
615+
} else {
616+
// This should never happen, as a stored-property pattern can only be
617+
// applied to classes and structs. But to be safe - and future prove -
618+
// let's handle this case and bail.
619+
insertEndAccess(beginAccess, /*isModify*/ false, builder);
620+
return SILValue();
621+
}
622+
}
623+
return addr;
624+
}
625+
626+
/// Try to optimize a keypath application with an apply instruction.
627+
///
628+
/// Replaces (simplified SIL):
629+
/// %kp = keypath ...
630+
/// apply %keypath_runtime_function(%addr, %kp, %root_object)
631+
/// with:
632+
/// %addr = struct_element_addr/ref_element_addr %root_object
633+
/// ...
634+
/// load/store %addr
635+
bool SILCombiner::tryOptimizeKeypath(ApplyInst *AI) {
636+
SILFunction *callee = AI->getReferencedFunction();
637+
if (!callee)
638+
return false;
639+
640+
if (AI->getNumArguments() != 3)
641+
return false;
642+
643+
SILValue keyPath, rootAddr, valueAddr;
644+
bool isModify = false;
645+
if (callee->getName() == "swift_setAtWritableKeyPath" ||
646+
callee->getName() == "swift_setAtReferenceWritableKeyPath") {
647+
keyPath = AI->getArgument(1);
648+
rootAddr = AI->getArgument(0);
649+
valueAddr = AI->getArgument(2);
650+
isModify = true;
651+
} else if (callee->getName() == "swift_getAtKeyPath") {
652+
keyPath = AI->getArgument(2);
653+
rootAddr = AI->getArgument(1);
654+
valueAddr = AI->getArgument(0);
655+
} else {
656+
return false;
657+
}
658+
659+
BeginAccessInst *beginAccess = nullptr;
660+
SILValue projectedAddr = createKeypathProjections(keyPath, rootAddr,
661+
AI->getLoc(), beginAccess,
662+
Builder);
663+
if (!projectedAddr)
664+
return false;
665+
666+
if (isModify) {
667+
Builder.createCopyAddr(AI->getLoc(), valueAddr, projectedAddr,
668+
IsTake, IsNotInitialization);
669+
} else {
670+
Builder.createCopyAddr(AI->getLoc(), projectedAddr, valueAddr,
671+
IsNotTake, IsInitialization);
672+
}
673+
insertEndAccess(beginAccess, isModify, Builder);
674+
eraseInstFromFunction(*AI);
675+
++NumOptimizedKeypaths;
676+
return true;
677+
}
678+
679+
/// Try to optimize a keypath application with an apply instruction.
680+
///
681+
/// Replaces (simplified SIL):
682+
/// %kp = keypath ...
683+
/// %inout_addr = begin_apply %keypath_runtime_function(%kp, %root_object)
684+
/// // use %inout_addr
685+
/// end_apply
686+
/// with:
687+
/// %addr = struct_element_addr/ref_element_addr %root_object
688+
/// // use %inout_addr
689+
bool SILCombiner::tryOptimizeInoutKeypath(BeginApplyInst *AI) {
690+
SILFunction *callee = AI->getReferencedFunction();
691+
if (!callee)
692+
return false;
693+
694+
if (AI->getNumArguments() != 2)
695+
return false;
696+
697+
SILValue keyPath = AI->getArgument(1);
698+
SILValue rootAddr = AI->getArgument(0);
699+
bool isModify = false;
700+
if (callee->getName() == "swift_modifyAtWritableKeyPath" ||
701+
callee->getName() == "swift_modifyAtReferenceWritableKeyPath") {
702+
isModify = true;
703+
} else if (callee->getName() != "swift_readAtKeyPath") {
704+
return false;
705+
}
706+
707+
SILInstructionResultArray yields = AI->getYieldedValues();
708+
if (yields.size() != 1)
709+
return false;
710+
711+
SILValue valueAddr = yields[0];
712+
Operand *AIUse = AI->getTokenResult()->getSingleUse();
713+
if (!AIUse)
714+
return false;
715+
EndApplyInst *endApply = dyn_cast<EndApplyInst>(AIUse->getUser());
716+
if (!endApply)
717+
return false;
718+
719+
BeginAccessInst *beginAccess = nullptr;
720+
SILValue projectedAddr = createKeypathProjections(keyPath, rootAddr,
721+
AI->getLoc(), beginAccess,
722+
Builder);
723+
if (!projectedAddr)
724+
return false;
725+
726+
// Replace the projected address.
727+
valueAddr->replaceAllUsesWith(projectedAddr);
728+
729+
Builder.setInsertionPoint(endApply);
730+
insertEndAccess(beginAccess, isModify, Builder);
731+
eraseInstFromFunction(*endApply);
732+
eraseInstFromFunction(*AI);
733+
++NumOptimizedKeypaths;
734+
return true;
735+
}
736+
551737
bool
552738
SILCombiner::recursivelyCollectARCUsers(UserListTy &Uses, ValueBase *Value) {
553739
// FIXME: We could probably optimize this case too
@@ -1327,6 +1513,9 @@ SILInstruction *SILCombiner::visitApplyInst(ApplyInst *AI) {
13271513
if (auto *CFI = dyn_cast<ConvertFunctionInst>(AI->getCallee()))
13281514
return optimizeApplyOfConvertFunctionInst(AI, CFI);
13291515

1516+
if (tryOptimizeKeypath(AI))
1517+
return nullptr;
1518+
13301519
// Optimize readonly functions with no meaningful users.
13311520
SILFunction *SF = AI->getReferencedFunction();
13321521
if (SF && SF->getEffectsKind() < EffectsKind::ReleaseNone) {
@@ -1393,6 +1582,12 @@ SILInstruction *SILCombiner::visitApplyInst(ApplyInst *AI) {
13931582
return nullptr;
13941583
}
13951584

1585+
SILInstruction *SILCombiner::visitBeginApplyInst(BeginApplyInst *BAI) {
1586+
if (tryOptimizeInoutKeypath(BAI))
1587+
return nullptr;
1588+
return nullptr;
1589+
}
1590+
13961591
bool SILCombiner::
13971592
isTryApplyResultNotUsed(UserListTy &AcceptedUses, TryApplyInst *TAI) {
13981593
SILBasicBlock *NormalBB = TAI->getNormalBB();

lib/SILOptimizer/Transforms/DeadObjectElimination.cpp

Lines changed: 39 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,9 @@ STATISTIC(DeadAllocRefEliminated,
5252
STATISTIC(DeadAllocStackEliminated,
5353
"number of AllocStack instructions removed");
5454

55+
STATISTIC(DeadKeyPathEliminated,
56+
"number of keypath instructions removed");
57+
5558
STATISTIC(DeadAllocApplyEliminated,
5659
"number of allocating Apply instructions removed");
5760

@@ -208,6 +211,9 @@ static bool canZapInstruction(SILInstruction *Inst, bool acceptRefCountInsts,
208211
if (isa<InjectEnumAddrInst>(Inst))
209212
return true;
210213

214+
if (isa<KeyPathInst>(Inst))
215+
return true;
216+
211217
// We know that the destructor has no side effects so we can remove the
212218
// deallocation instruction too.
213219
if (isa<DeallocationInst>(Inst) || isa<AllocationInst>(Inst))
@@ -643,17 +649,20 @@ class DeadObjectElimination : public SILFunctionTransform {
643649
llvm::SmallVector<SILInstruction*, 16> Allocations;
644650

645651
void collectAllocations(SILFunction &Fn) {
646-
for (auto &BB : Fn)
652+
for (auto &BB : Fn) {
647653
for (auto &II : BB) {
648-
if (isa<AllocationInst>(&II))
649-
Allocations.push_back(&II);
650-
else if (isAllocatingApply(&II))
654+
if (isa<AllocationInst>(&II) ||
655+
isAllocatingApply(&II) ||
656+
isa<KeyPathInst>(&II)) {
651657
Allocations.push_back(&II);
658+
}
652659
}
660+
}
653661
}
654662

655663
bool processAllocRef(AllocRefInst *ARI);
656664
bool processAllocStack(AllocStackInst *ASI);
665+
bool processKeyPath(KeyPathInst *KPI);
657666
bool processAllocBox(AllocBoxInst *ABI){ return false;}
658667
bool processAllocApply(ApplyInst *AI, DeadEndBlocks &DEBlocks);
659668

@@ -668,6 +677,8 @@ class DeadObjectElimination : public SILFunctionTransform {
668677
Changed |= processAllocRef(A);
669678
else if (auto *A = dyn_cast<AllocStackInst>(II))
670679
Changed |= processAllocStack(A);
680+
else if (auto *KPI = dyn_cast<KeyPathInst>(II))
681+
Changed |= processKeyPath(KPI);
671682
else if (auto *A = dyn_cast<AllocBoxInst>(II))
672683
Changed |= processAllocBox(A);
673684
else if (auto *A = dyn_cast<ApplyInst>(II))
@@ -749,6 +760,30 @@ bool DeadObjectElimination::processAllocStack(AllocStackInst *ASI) {
749760
return true;
750761
}
751762

763+
bool DeadObjectElimination::processKeyPath(KeyPathInst *KPI) {
764+
UserList UsersToRemove;
765+
if (hasUnremovableUsers(KPI, UsersToRemove, /*acceptRefCountInsts=*/ true,
766+
/*onlyAcceptTrivialStores*/ false)) {
767+
LLVM_DEBUG(llvm::dbgs() << " Found a use that cannot be zapped...\n");
768+
return false;
769+
}
770+
771+
// For simplicity just bail if the keypath has a non-trivial operands.
772+
// TODO: don't bail but insert compensating destroys for such operands.
773+
for (const Operand &Op : KPI->getAllOperands()) {
774+
if (!Op.get()->getType().isTrivial(*KPI->getFunction()))
775+
return false;
776+
}
777+
778+
// Remove the keypath and all of its users.
779+
removeInstructions(
780+
ArrayRef<SILInstruction*>(UsersToRemove.begin(), UsersToRemove.end()));
781+
LLVM_DEBUG(llvm::dbgs() << " Success! Eliminating keypath.\n");
782+
783+
++DeadKeyPathEliminated;
784+
return true;
785+
}
786+
752787
/// If AI is the version of an initializer where we pass in either an apply or
753788
/// an alloc_ref to initialize in place, validate that we are able to continue
754789
/// optimizing and return To

0 commit comments

Comments
 (0)