Skip to content

Commit 53d3dda

Browse files
authored
Merge pull request #33179 from eeckstein/existential-box-elimination
SILCombine: optimize casts of existential boxes.
2 parents 9c2a045 + 662f03e commit 53d3dda

File tree

10 files changed

+647
-57
lines changed

10 files changed

+647
-57
lines changed

include/swift/SILOptimizer/Utils/InstOptUtils.h

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -226,6 +226,36 @@ void eraseUsesOfInstruction(
226226
/// value itself)
227227
void eraseUsesOfValue(SILValue value);
228228

229+
/// Gets the concrete value which is stored in an existential box.
230+
/// Returns %value in following pattern:
231+
///
232+
/// %existentialBox = alloc_existential_box $Error, $ConcreteError
233+
/// %a = project_existential_box $ConcreteError in %existentialBox : $Error
234+
/// store %value to %a : $*ConcreteError
235+
///
236+
/// Returns an invalid SILValue in case there are multiple stores or any unknown
237+
/// users of \p existentialBox.
238+
/// The \p ignoreUser is ignored in the user list of \p existentialBox.
239+
SILValue
240+
getConcreteValueOfExistentialBox(AllocExistentialBoxInst *existentialBox,
241+
SILInstruction *ignoreUser);
242+
243+
/// Gets the concrete value which is stored in an existential box, which itself
244+
/// is stored in \p addr.
245+
/// Returns %value in following pattern:
246+
///
247+
/// %b = alloc_existential_box $Error, $ConcreteError
248+
/// %a = project_existential_box $ConcreteError in %b : $Error
249+
/// store %value to %a : $*ConcreteError
250+
/// %addr = alloc_stack $Error
251+
/// store %b to %addr : $*Error
252+
///
253+
/// Returns an invalid SILValue in case there are multiple stores or any unknown
254+
/// users of \p addr or the existential box.
255+
/// The \p ignoreUser is ignored in the user list of \p addr.
256+
SILValue getConcreteValueOfExistentialBoxAddr(SILValue addr,
257+
SILInstruction *ignoreUser);
258+
229259
FullApplySite findApplyFromDevirtualizedResult(SILValue value);
230260

231261
/// Cast a value into the expected, ABI compatible type if necessary.

lib/SILOptimizer/SILCombiner/SILCombine.cpp

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -240,6 +240,16 @@ bool SILCombiner::runOnFunction(SILFunction &F) {
240240
return Changed;
241241
}
242242

243+
void SILCombiner::eraseInstIncludingUsers(SILInstruction *inst) {
244+
for (SILValue result : inst->getResults()) {
245+
while (!result->use_empty()) {
246+
eraseInstIncludingUsers(result->use_begin()->getUser());
247+
}
248+
}
249+
eraseInstFromFunction(*inst);
250+
}
251+
252+
243253
//===----------------------------------------------------------------------===//
244254
// Entry Points
245255
//===----------------------------------------------------------------------===//

lib/SILOptimizer/SILCombiner/SILCombiner.h

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -147,6 +147,10 @@ class SILCombiner :
147147
return nullptr;
148148
}
149149

150+
// Erases \p inst and all of its users, recursively.
151+
// The caller has to make sure that all users are removable (e.g. dead).
152+
void eraseInstIncludingUsers(SILInstruction *inst);
153+
150154
SILInstruction *eraseInstFromFunction(SILInstruction &I,
151155
bool AddOperandsToWorklist = true) {
152156
SILBasicBlock::iterator nullIter;

lib/SILOptimizer/SILCombiner/SILCombinerCastVisitors.cpp

Lines changed: 97 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -337,12 +337,53 @@ SILCombiner::visitUncheckedRefCastAddrInst(UncheckedRefCastAddrInst *URCI) {
337337
return eraseInstFromFunction(*URCI);
338338
}
339339

340+
template <class CastInst>
341+
static bool canBeUsedAsCastDestination(SILValue value, CastInst *castInst,
342+
DominanceAnalysis *DA) {
343+
return value &&
344+
value->getType() == castInst->getTargetLoweredType().getObjectType() &&
345+
DA->get(castInst->getFunction())->properlyDominates(value, castInst);
346+
}
347+
348+
340349
SILInstruction *
341350
SILCombiner::
342351
visitUnconditionalCheckedCastAddrInst(UnconditionalCheckedCastAddrInst *UCCAI) {
343352
if (UCCAI->getFunction()->hasOwnership())
344353
return nullptr;
345354

355+
// Optimize the unconditional_checked_cast_addr in this pattern:
356+
//
357+
// %box = alloc_existential_box $Error, $ConcreteError
358+
// %a = project_existential_box $ConcreteError in %b : $Error
359+
// store %value to %a : $*ConcreteError
360+
// %err = alloc_stack $Error
361+
// store %box to %err : $*Error
362+
// %dest = alloc_stack $ConcreteError
363+
// unconditional_checked_cast_addr Error in %err : $*Error to
364+
// ConcreteError in %dest : $*ConcreteError
365+
//
366+
// to:
367+
// ...
368+
// retain_value %value : $ConcreteError
369+
// destroy_addr %err : $*Error
370+
// store %value to %dest $*ConcreteError
371+
//
372+
// This lets the alloc_existential_box become dead and it can be removed in
373+
// following optimizations.
374+
SILValue val = getConcreteValueOfExistentialBoxAddr(UCCAI->getSrc(), UCCAI);
375+
if (canBeUsedAsCastDestination(val, UCCAI, DA)) {
376+
SILBuilderContext builderCtx(Builder.getModule(), Builder.getTrackingList());
377+
SILBuilderWithScope builder(UCCAI, builderCtx);
378+
SILLocation loc = UCCAI->getLoc();
379+
builder.createRetainValue(loc, val, builder.getDefaultAtomicity());
380+
builder.createDestroyAddr(loc, UCCAI->getSrc());
381+
builder.createStore(loc, val, UCCAI->getDest(),
382+
StoreOwnershipQualifier::Unqualified);
383+
return eraseInstFromFunction(*UCCAI);
384+
}
385+
386+
// Perform the purly type-based cast optimization.
346387
if (CastOpt.optimizeUnconditionalCheckedCastAddrInst(UCCAI))
347388
MadeChange = true;
348389

@@ -522,6 +563,62 @@ visitCheckedCastAddrBranchInst(CheckedCastAddrBranchInst *CCABI) {
522563
if (CCABI->getFunction()->hasOwnership())
523564
return nullptr;
524565

566+
// Optimize the checked_cast_addr_br in this pattern:
567+
//
568+
// %box = alloc_existential_box $Error, $ConcreteError
569+
// %a = project_existential_box $ConcreteError in %b : $Error
570+
// store %value to %a : $*ConcreteError
571+
// %err = alloc_stack $Error
572+
// store %box to %err : $*Error
573+
// %dest = alloc_stack $ConcreteError
574+
// checked_cast_addr_br <consumption-kind> Error in %err : $*Error to
575+
// ConcreteError in %dest : $*ConcreteError, success_bb, failing_bb
576+
//
577+
// to:
578+
// ...
579+
// retain_value %value : $ConcreteError
580+
// destroy_addr %err : $*Error // if consumption-kind is take
581+
// store %value to %dest $*ConcreteError
582+
// br success_bb
583+
//
584+
// This lets the alloc_existential_box become dead and it can be removed in
585+
// following optimizations.
586+
//
587+
// TODO: Also handle the WillFail case.
588+
SILValue val = getConcreteValueOfExistentialBoxAddr(CCABI->getSrc(), CCABI);
589+
if (canBeUsedAsCastDestination(val, CCABI, DA)) {
590+
SILBuilderContext builderCtx(Builder.getModule(), Builder.getTrackingList());
591+
SILBuilderWithScope builder(CCABI, builderCtx);
592+
SILLocation loc = CCABI->getLoc();
593+
builder.createRetainValue(loc, val, builder.getDefaultAtomicity());
594+
switch (CCABI->getConsumptionKind()) {
595+
case CastConsumptionKind::TakeAlways:
596+
case CastConsumptionKind::TakeOnSuccess:
597+
builder.createDestroyAddr(loc, CCABI->getSrc());
598+
break;
599+
case CastConsumptionKind::CopyOnSuccess:
600+
break;
601+
case CastConsumptionKind::BorrowAlways:
602+
llvm_unreachable("BorrowAlways is not supported on addresses");
603+
}
604+
builder.createStore(loc, val, CCABI->getDest(),
605+
StoreOwnershipQualifier::Unqualified);
606+
607+
// Replace the cast with a constant conditional branch.
608+
// Don't just create an unconditional branch to not change the CFG in
609+
// SILCombine. SimplifyCFG will clean that up.
610+
//
611+
// Another possibility would be to run this optimization in SimplifyCFG.
612+
// But this has other problems, like it's more difficult to reason about a
613+
// consistent dominator tree in SimplifyCFG.
614+
SILType boolTy = SILType::getBuiltinIntegerType(1, builder.getASTContext());
615+
auto *trueVal = builder.createIntegerLiteral(loc, boolTy, 1);
616+
builder.createCondBranch(loc, trueVal, CCABI->getSuccessBB(),
617+
CCABI->getFailureBB());
618+
return eraseInstFromFunction(*CCABI);
619+
}
620+
621+
// Perform the purly type-based cast optimization.
525622
if (CastOpt.optimizeCheckedCastAddrBranchInst(CCABI))
526623
MadeChange = true;
527624

lib/SILOptimizer/SILCombiner/SILCombinerMiscVisitors.cpp

Lines changed: 29 additions & 55 deletions
Original file line numberDiff line numberDiff line change
@@ -55,66 +55,40 @@ SILCombiner::visitAllocExistentialBoxInst(AllocExistentialBoxInst *AEBI) {
5555
// debug_value %6#0 : $Error
5656
// strong_release %6#0 : $Error
5757

58-
StoreInst *SingleStore = nullptr;
59-
StrongReleaseInst *SingleRelease = nullptr;
60-
ProjectExistentialBoxInst *SingleProjection = nullptr;
61-
62-
// For each user U of the alloc_existential_box...
63-
for (auto U : getNonDebugUses(AEBI)) {
64-
65-
if (auto *PEBI = dyn_cast<ProjectExistentialBoxInst>(U->getUser())) {
66-
if (SingleProjection) return nullptr;
67-
SingleProjection = PEBI;
68-
for (auto AddrUse : getNonDebugUses(PEBI)) {
69-
// Record stores into the box.
70-
if (auto *SI = dyn_cast<StoreInst>(AddrUse->getUser())) {
71-
// If this is not the only store into the box then bail out.
72-
if (SingleStore) return nullptr;
73-
SingleStore = SI;
74-
continue;
75-
}
76-
// If there are other users to the box value address then bail out.
77-
return nullptr;
78-
}
79-
continue;
80-
}
58+
SILValue boxedValue =
59+
getConcreteValueOfExistentialBox(AEBI, /*ignoreUser*/ nullptr);
60+
if (!boxedValue)
61+
return nullptr;
8162

82-
// Record releases of the box.
83-
if (auto *RI = dyn_cast<StrongReleaseInst>(U->getUser())) {
63+
// Check if the box is released at a single place. That's the end of its
64+
// lifetime.
65+
StrongReleaseInst *singleRelease = nullptr;
66+
for (Operand *use : AEBI->getUses()) {
67+
if (auto *RI = dyn_cast<StrongReleaseInst>(use->getUser())) {
8468
// If this is not the only release of the box then bail out.
85-
if (SingleRelease) return nullptr;
86-
SingleRelease = RI;
87-
continue;
69+
if (singleRelease)
70+
return nullptr;
71+
singleRelease = RI;
8872
}
89-
90-
// If there are other users to the box then bail out.
91-
return nullptr;
92-
}
93-
94-
if (SingleStore && SingleRelease) {
95-
assert(SingleProjection && "store without a projection");
96-
// Release the value that was stored into the existential box. The box
97-
// is going away so we need to release the stored value.
98-
// NOTE: It's important that the release is inserted at the single
99-
// release of the box and not at the store, because a balancing retain could
100-
// be _after_ the store, e.g:
101-
// %box = alloc_existential_box
102-
// %addr = project_existential_box %box
103-
// store %value to %addr
104-
// retain_value %value // must insert the release after this retain
105-
// strong_release %box
106-
Builder.setInsertionPoint(SingleRelease);
107-
Builder.createReleaseValue(AEBI->getLoc(), SingleStore->getSrc(),
108-
SingleRelease->getAtomicity());
109-
110-
// Erase the instruction that stores into the box and the release that
111-
// releases the box, and finally, release the box.
112-
eraseInstFromFunction(*SingleRelease);
113-
eraseInstFromFunction(*SingleStore);
114-
eraseInstFromFunction(*SingleProjection);
115-
return eraseInstFromFunction(*AEBI);
11673
}
74+
if (!singleRelease)
75+
return nullptr;
11776

77+
// Release the value that was stored into the existential box. The box
78+
// is going away so we need to release the stored value.
79+
// NOTE: It's important that the release is inserted at the single
80+
// release of the box and not at the store, because a balancing retain could
81+
// be _after_ the store, e.g:
82+
// %box = alloc_existential_box
83+
// %addr = project_existential_box %box
84+
// store %value to %addr
85+
// retain_value %value // must insert the release after this retain
86+
// strong_release %box
87+
Builder.setInsertionPoint(singleRelease);
88+
Builder.createReleaseValue(AEBI->getLoc(), boxedValue,
89+
singleRelease->getAtomicity());
90+
91+
eraseInstIncludingUsers(AEBI);
11892
return nullptr;
11993
}
12094

lib/SILOptimizer/Utils/InstOptUtils.cpp

Lines changed: 90 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,11 @@ using namespace swift;
4343
static llvm::cl::opt<bool> EnableExpandAll("enable-expand-all",
4444
llvm::cl::init(false));
4545

46+
static llvm::cl::opt<bool> KeepWillThrowCall(
47+
"keep-will-throw-call", llvm::cl::init(false),
48+
llvm::cl::desc(
49+
"Keep calls to swift_willThrow, even if the throw is optimized away"));
50+
4651
/// Creates an increment on \p Ptr before insertion point \p InsertPt that
4752
/// creates a strong_retain if \p Ptr has reference semantics itself or a
4853
/// retain_value if \p Ptr is a non-trivial value without reference-semantics.
@@ -667,6 +672,91 @@ void swift::eraseUsesOfValue(SILValue v) {
667672
}
668673
}
669674

675+
SILValue swift::
676+
getConcreteValueOfExistentialBox(AllocExistentialBoxInst *existentialBox,
677+
SILInstruction *ignoreUser) {
678+
StoreInst *singleStore = nullptr;
679+
for (Operand *use : getNonDebugUses(existentialBox)) {
680+
SILInstruction *user = use->getUser();
681+
switch (user->getKind()) {
682+
case SILInstructionKind::StrongRetainInst:
683+
case SILInstructionKind::StrongReleaseInst:
684+
break;
685+
case SILInstructionKind::ProjectExistentialBoxInst: {
686+
auto *projectedAddr = cast<ProjectExistentialBoxInst>(user);
687+
for (Operand *addrUse : getNonDebugUses(projectedAddr)) {
688+
if (auto *store = dyn_cast<StoreInst>(addrUse->getUser())) {
689+
assert(store->getSrc() != projectedAddr &&
690+
"cannot store an address");
691+
// Bail if there are multiple stores.
692+
if (singleStore)
693+
return SILValue();
694+
singleStore = store;
695+
continue;
696+
}
697+
// If there are other users to the box value address then bail out.
698+
return SILValue();
699+
}
700+
break;
701+
}
702+
case SILInstructionKind::BuiltinInst: {
703+
auto *builtin = cast<BuiltinInst>(user);
704+
if (KeepWillThrowCall ||
705+
builtin->getBuiltinInfo().ID != BuiltinValueKind::WillThrow) {
706+
return SILValue();
707+
}
708+
break;
709+
}
710+
default:
711+
if (user != ignoreUser)
712+
return SILValue();
713+
break;
714+
}
715+
}
716+
if (!singleStore)
717+
return SILValue();
718+
return singleStore->getSrc();
719+
}
720+
721+
SILValue swift::
722+
getConcreteValueOfExistentialBoxAddr(SILValue addr, SILInstruction *ignoreUser) {
723+
auto *stackLoc = dyn_cast<AllocStackInst>(addr);
724+
if (!stackLoc)
725+
return SILValue();
726+
727+
StoreInst *singleStackStore = nullptr;
728+
for (Operand *stackUse : stackLoc->getUses()) {
729+
SILInstruction *stackUser = stackUse->getUser();
730+
switch (stackUser->getKind()) {
731+
case SILInstructionKind::DeallocStackInst:
732+
case SILInstructionKind::DebugValueAddrInst:
733+
case SILInstructionKind::LoadInst:
734+
break;
735+
case SILInstructionKind::StoreInst: {
736+
auto *store = cast<StoreInst>(stackUser);
737+
assert(store->getSrc() != stackLoc && "cannot store an address");
738+
// Bail if there are multiple stores.
739+
if (singleStackStore)
740+
return SILValue();
741+
singleStackStore = store;
742+
break;
743+
}
744+
default:
745+
if (stackUser != ignoreUser)
746+
return SILValue();
747+
break;
748+
}
749+
}
750+
if (!singleStackStore)
751+
return SILValue();
752+
753+
auto *box = dyn_cast<AllocExistentialBoxInst>(singleStackStore->getSrc());
754+
if (!box)
755+
return SILValue();
756+
757+
return getConcreteValueOfExistentialBox(box, singleStackStore);
758+
}
759+
670760
// Devirtualization of functions with covariant return types produces
671761
// a result that is not an apply, but takes an apply as an
672762
// argument. Attempt to dig the apply out from this result.

0 commit comments

Comments
 (0)