Skip to content

Commit 1b127d9

Browse files
committed
[closure-lifetime-fixup] Delete dead trivial phi nodes.
Given certain CFGs, the SSA updater will insert unnecessary phi arguments that all take the same "initial" value (the .none from the entry block). As an example, consider the following CFG: ``` +-------+ +------+ | 2 | <-- | 0 | +-------+ +------+ | | | | | v | +------+ +---+ | | 1 | --> | 3 | | +------+ +---+ | | | | | | | v | | +------+ | | | 4 | | | +------+ | | | | | | | | v | | +------+ | +---------> | 5 | | +------+ | | | | | v | +-------+ +------+ | | Throw | <-- | 6 | | +-------+ +------+ | | | | | v | +------+ | | Exit | <-----+ +------+ ``` In this case, if our some value is actually in BB3, we will actually insert a phi node in BB5 that has all .none incoming values. This is causing us from the perspective of the ownership verifier to be leaking that phi node in BBThrow. The reason why is that arguments are a location in SIL where the specific case of the enum is erased, forcing us to use @owned if the enum has any non-trivial cases. These of course will not be balanced by any destroy_values reachable from the copy_value (since otherwise, we would have a .some definition along that path), so it makes sense. Rather than try to insert destroy_values, we just realize that in this case we know that all the incoming values are really the .none from the entry block and delete the argument, eliminating the problem.
1 parent 819a56e commit 1b127d9

File tree

1 file changed

+111
-7
lines changed

1 file changed

+111
-7
lines changed

lib/SILOptimizer/Mandatory/ClosureLifetimeFixup.cpp

Lines changed: 111 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -118,6 +118,89 @@ static void findReachableExitBlocks(SILInstruction *i,
118118
}
119119
}
120120

121+
/// We use this to ensure that we properly handle recursive cases by revisiting
122+
/// phi nodes that we are tracking. This just makes it easier to reproduce in a
123+
/// test case.
124+
static llvm::cl::opt<bool> ReverseInitialWorklist(
125+
"sil-closure-lifetime-fixup-reverse-phi-order", llvm::cl::init(false),
126+
llvm::cl::desc(
127+
"Reverse the order in which we visit phis for testing purposes"),
128+
llvm::cl::Hidden);
129+
130+
// Finally, we need to prune phis inserted by the SSA updater that
131+
// only take the .none from the entry block. This means that they are
132+
// not actually reachable from the .some() so we know that we do not
133+
// need to lifetime extend there at all. As an additional benefit, we
134+
// eliminate the need to balance these arguments to satisfy the
135+
// ownership verifier. This occurs since arguments are a place in SIL
136+
// where the trivialness of an enums case is erased.
137+
static void
138+
cleanupDeadTrivialPhiArgs(SILValue initialValue,
139+
SmallVectorImpl<SILPhiArgument *> &insertedPhis) {
140+
// Just for testing purposes.
141+
if (ReverseInitialWorklist) {
142+
std::reverse(insertedPhis.begin(), insertedPhis.end());
143+
}
144+
SmallVector<SILPhiArgument *, 8> worklist(insertedPhis.begin(),
145+
insertedPhis.end());
146+
sortUnique(insertedPhis);
147+
SmallVector<SILValue, 8> incomingValues;
148+
149+
while (!worklist.empty()) {
150+
// Clear the incoming values array after each iteration.
151+
SWIFT_DEFER { incomingValues.clear(); };
152+
153+
auto *phi = worklist.pop_back_val();
154+
{
155+
auto it = lower_bound(insertedPhis, phi);
156+
if (it == insertedPhis.end() || *it != phi)
157+
continue;
158+
}
159+
160+
// TODO: When we split true phi arguments from transformational terminators,
161+
// this will always succeed and the assert can go away.
162+
bool foundPhiValues = phi->getIncomingPhiValues(incomingValues);
163+
(void)foundPhiValues;
164+
assert(foundPhiValues && "Should always have 'true' phi arguments since "
165+
"these were inserted by the SSA updater.");
166+
if (llvm::any_of(incomingValues,
167+
[&](SILValue v) { return v != initialValue; }))
168+
continue;
169+
170+
// Remove it from our insertedPhis list to prevent us from re-visiting this.
171+
{
172+
auto it = lower_bound(insertedPhis, phi);
173+
assert((it != insertedPhis.end() && *it == phi) &&
174+
"Should have found the phi");
175+
insertedPhis.erase(it);
176+
}
177+
178+
// See if any of our users are branch or cond_br. If so, we may have
179+
// exposed additional unneeded phis. Add it back to the worklist in such a
180+
// case.
181+
for (auto *op : phi->getUses()) {
182+
auto *user = op->getUser();
183+
184+
if (!isa<BranchInst>(user) && !isa<CondBranchInst>(user))
185+
continue;
186+
187+
auto *termInst = cast<TermInst>(user);
188+
for (auto succBlockArgList : termInst->getSuccessorBlockArguments()) {
189+
copy_if(succBlockArgList, std::back_inserter(worklist),
190+
[&](SILPhiArgument *succArg) -> bool {
191+
auto it = lower_bound(insertedPhis, succArg);
192+
return it != insertedPhis.end() && *it == succArg;
193+
});
194+
}
195+
}
196+
197+
// Then RAUW the phi with the entryBlockOptionalNone and erase the
198+
// argument.
199+
phi->replaceAllUsesWith(initialValue);
200+
erasePhiArgument(phi->getParent(), phi->getIndex());
201+
}
202+
}
203+
121204
/// Extend the lifetime of the convert_escape_to_noescape's operand to the end
122205
/// of the function.
123206
///
@@ -157,7 +240,8 @@ static void extendLifetimeToEndOfFunction(SILFunction &fn,
157240
// Ok. At this point we know that Cvt is not in the entry block... so we can
158241
// use SILSSAUpdater::GetValueInMiddleOfBlock() to extend the object's
159242
// lifetime respecting loops.
160-
SILSSAUpdater updater;
243+
SmallVector<SILPhiArgument *, 8> insertedPhis;
244+
SILSSAUpdater updater(&insertedPhis);
161245
updater.Initialize(optionalEscapingClosureTy);
162246

163247
// Create an Optional<() -> ()>.none in the entry block of the function and
@@ -166,10 +250,11 @@ static void extendLifetimeToEndOfFunction(SILFunction &fn,
166250
// Since we know that Cvt is not in the entry block and this must be, we know
167251
// that it is safe to use the SSAUpdater's getValueInMiddleOfBlock with this
168252
// value.
169-
updater.AddAvailableValue(fn.getEntryBlock(), [&]() -> SILValue {
253+
SILValue entryBlockOptionalNone = [&]() -> SILValue {
170254
SILBuilderWithScope b(fn.getEntryBlock()->begin());
171255
return b.createOptionalNone(loc, optionalEscapingClosureTy);
172-
}());
256+
}();
257+
updater.AddAvailableValue(fn.getEntryBlock(), entryBlockOptionalNone);
173258

174259
// Create a copy of the convert_escape_to_no_escape and add it as an available
175260
// value to the SSA updater.
@@ -206,6 +291,14 @@ static void extendLifetimeToEndOfFunction(SILFunction &fn,
206291
SILValue v = updater.GetValueAtEndOfBlock(block);
207292
SILBuilderWithScope(safeDestructionPt).createDestroyValue(loc, v);
208293
}
294+
295+
// Finally, we need to prune phis inserted by the SSA updater that only take
296+
// the .none from the entry block.
297+
//
298+
// TODO: Should we sort inserted phis before or after we initialize
299+
// the worklist or maybe backwards? We should investigate how the
300+
// SSA updater adds phi nodes to this list to resolve this question.
301+
cleanupDeadTrivialPhiArgs(entryBlockOptionalNone, insertedPhis);
209302
}
210303

211304
static SILInstruction *lookThroughRebastractionUsers(
@@ -724,16 +817,19 @@ static bool fixupCopyBlockWithoutEscaping(CopyBlockWithoutEscapingInst *cb,
724817
auto optionalEscapingClosureTy =
725818
SILType::getOptionalType(sentinelClosure->getType());
726819

727-
SILSSAUpdater updater;
820+
SmallVector<SILPhiArgument *, 8> insertedPhis;
821+
SILSSAUpdater updater(&insertedPhis);
728822
updater.Initialize(optionalEscapingClosureTy);
729823

730824
// Create the Optional.none as the beginning available value.
825+
SILValue entryBlockOptionalNone;
731826
{
732827
SILBuilderWithScope b(fn.getEntryBlock()->begin());
733-
updater.AddAvailableValue(
734-
fn.getEntryBlock(),
735-
b.createOptionalNone(autoGenLoc, optionalEscapingClosureTy));
828+
entryBlockOptionalNone =
829+
b.createOptionalNone(autoGenLoc, optionalEscapingClosureTy);
830+
updater.AddAvailableValue(fn.getEntryBlock(), entryBlockOptionalNone);
736831
}
832+
assert(entryBlockOptionalNone);
737833

738834
// Then create the Optional.some(closure sentinel).
739835
//
@@ -790,6 +886,14 @@ static bool fixupCopyBlockWithoutEscaping(CopyBlockWithoutEscapingInst *cb,
790886
}
791887
}
792888

889+
// Finally, we need to prune phis inserted by the SSA updater that only take
890+
// the .none from the entry block.
891+
//
892+
// TODO: Should we sort inserted phis before or after we initialize
893+
// the worklist or maybe backwards? We should investigate how the
894+
// SSA updater adds phi nodes to this list to resolve this question.
895+
cleanupDeadTrivialPhiArgs(entryBlockOptionalNone, insertedPhis);
896+
793897
return true;
794898
}
795899

0 commit comments

Comments
 (0)