Skip to content

Commit 2226ea7

Browse files
authored
Merge pull request swiftlang#36462 from atrick/rescue-valuelifetime
Rescue the ValueLifetimeAnalysis utility.
2 parents c4c91e2 + 2f34014 commit 2226ea7

File tree

3 files changed

+169
-52
lines changed

3 files changed

+169
-52
lines changed

include/swift/SILOptimizer/Utils/ValueLifetime.h

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,23 @@
2525

2626
namespace swift {
2727

28+
/// Record the last use points and CFG edges that form the boundary of a
29+
/// SILValue's lifetime.
30+
///
31+
/// Only valid when critical edges are disallowed (currently OSSA-only).
32+
///
33+
/// Useful when the client processes the boundary differently depending on the
34+
/// last user.
35+
///
36+
/// TODO: When critical edges are universally prohibited, completely replace the
37+
/// Frontier implementation by adding a utility method that populates a vector
38+
/// of insertion points based on this boundary, where each each
39+
/// block-terminating lastUser adds an insertion point at each successor block.
40+
struct ValueLifetimeBoundary {
41+
SmallVector<SILInstruction *, 8> lastUsers;
42+
SmallVector<SILBasicBlock *, 8> boundaryEdges;
43+
};
44+
2845
/// Computes the lifetime frontier for a given value with respect to a
2946
/// given set of uses. The lifetime frontier is the list of instructions
3047
/// following the last uses. The set of uses can be passed by the clients
@@ -103,6 +120,16 @@ class ValueLifetimeAnalysis {
103120
propagateLiveness();
104121
}
105122

123+
/// Compute the LifetimeBoundary--the last users and boundary edges. This
124+
/// always succeeds.
125+
///
126+
/// Precondition: no critical edges
127+
///
128+
/// Note: this should never use DeadEndBlocks. If the client cares about
129+
/// dead-end CFG edges because it is inserting destroys, it can handle those
130+
/// specially when processing LifetimeBoundary.boundaryEdges.
131+
void computeLifetimeBoundary(ValueLifetimeBoundary &boundary);
132+
106133
enum Mode {
107134
/// Don't split critical edges if the frontier instructions are located on
108135
/// a critical edges. Instead fail.
@@ -178,6 +205,12 @@ class ValueLifetimeAnalysis {
178205

179206
/// Returns the last use of the value in the live block \p bb.
180207
SILInstruction *findLastUserInBlock(SILBasicBlock *bb);
208+
209+
void computeLifetime(
210+
llvm::function_ref<bool(SILBasicBlock *)> visitBlock,
211+
llvm::function_ref<void(SILInstruction *)> visitLastUser,
212+
llvm::function_ref<void(SILBasicBlock *predBB, SILBasicBlock *succBB)>
213+
visitBoundaryEdge);
181214
};
182215

183216
/// Destroys \p valueOrStackLoc at \p frontier.

lib/SILOptimizer/Utils/ValueLifetime.cpp

Lines changed: 102 additions & 52 deletions
Original file line numberDiff line numberDiff line change
@@ -90,28 +90,21 @@ SILInstruction *ValueLifetimeAnalysis::findLastUserInBlock(SILBasicBlock *bb) {
9090
llvm_unreachable("Expected to find use of value in block!");
9191
}
9292

93-
bool ValueLifetimeAnalysis::computeFrontier(FrontierImpl &frontier, Mode mode,
94-
DeadEndBlocks *deBlocks) {
93+
// FIXME: remove the visitBlock callback once DeadEndBlocks is removed.
94+
void ValueLifetimeAnalysis::computeLifetime(
95+
llvm::function_ref<bool(SILBasicBlock *)> visitBlock,
96+
llvm::function_ref<void(SILInstruction *)> visitLastUser,
97+
llvm::function_ref<void(SILBasicBlock *predBB, SILBasicBlock *succBB)>
98+
visitBoundaryEdge) {
9599
assert(!isAliveAtBeginOfBlock(getFunction()->getEntryBlock()) &&
96100
"Can't compute frontier for def which does not dominate all uses");
97101

98-
bool noCriticalEdges = true;
99-
100-
// Exit-blocks from the lifetime region. The value is live at the end of
101-
// a predecessor block but not in the frontier block itself.
102-
BasicBlockSetVector<16> frontierBlocks(getFunction());
103-
104-
// Blocks where the value is live at the end of the block and which have
105-
// a frontier block as successor.
106-
BasicBlockSetVector<16> liveOutBlocks(getFunction());
107-
108102
/// The lifetime ends if we have a live block and a not-live successor.
109103
for (SILBasicBlock *bb : liveBlocks) {
110-
if (deBlocks && deBlocks->isDeadEnd(bb))
104+
if (!visitBlock(bb))
111105
continue;
112106

113107
bool liveInSucc = false;
114-
bool deadInSucc = false;
115108
bool usedAndRedefinedInSucc = false;
116109
for (const SILSuccessor &succ : bb->getSuccessors()) {
117110
if (isAliveAtBeginOfBlock(succ)) {
@@ -128,63 +121,120 @@ bool ValueLifetimeAnalysis::computeFrontier(FrontierImpl &frontier, Mode mode,
128121
"blocks");
129122
usedAndRedefinedInSucc = true;
130123
}
131-
} else if (!deBlocks || !deBlocks->isDeadEnd(succ)) {
132-
deadInSucc = true;
133124
}
134125
}
135126
if (usedAndRedefinedInSucc) {
136127
// Here, the basic block bb uses the value and later redefines the value.
137128
// Therefore, this value's lifetime ends after its last use preceding the
138129
// re-definition of the value.
139-
//
140-
// We know that we can not have a SILArgument here since the SILArgument
141-
// dominates all instructions in the same block.
142130
auto ii = defValue.get<SILInstruction *>()->getReverseIterator();
143131
for (; ii != bb->rend(); ++ii) {
144132
if (userSet.count(&*ii)) {
145-
frontier.push_back(&*std::next(ii));
133+
visitLastUser(&*ii);
146134
break;
147135
}
148136
}
149137
assert(ii != bb->rend() &&
150138
"There must be a user in bb before definition");
151139
}
152-
if (!liveInSucc) {
153-
// The value is not live in any of the successor blocks. This means the
154-
// block contains a last use of the value. The next instruction after
155-
// the last use is part of the frontier.
156-
SILInstruction *lastUser = findLastUserInBlock(bb);
157-
if (!isa<TermInst>(lastUser)) {
158-
frontier.push_back(&*std::next(lastUser->getIterator()));
159-
continue;
160-
}
161-
// In case the last user is a TermInst there is no further instruction in
162-
// the block which can be the frontier. Instead we add all successor
163-
// blocks to the frontier (see below).
164-
// If the TermInst exits the function (e.g. 'return' or 'throw'), there
165-
// are no successors and we have to bail.
166-
if (!deadInSucc) {
167-
assert(cast<TermInst>(lastUser)->isFunctionExiting() &&
168-
"The final using TermInst must have successors");
169-
assert(mode != AllowToModifyCFG &&
170-
"Cannot bail if the mode is AllowToModifyCFG");
171-
return false;
172-
}
173-
}
174-
if (deadInSucc) {
175-
if (mode == UsersMustPostDomDef)
176-
return false;
177-
178-
// The value is not live in some of the successor blocks.
179-
liveOutBlocks.insert(bb);
140+
if (liveInSucc) {
180141
for (const SILSuccessor &succ : bb->getSuccessors()) {
181-
if (!isAliveAtBeginOfBlock(succ)) {
182-
// It's an "exit" edge from the lifetime region.
183-
frontierBlocks.insert(succ);
184-
}
142+
if (!isAliveAtBeginOfBlock(succ))
143+
visitBoundaryEdge(bb, succ);
185144
}
145+
} else {
146+
// The value is not live in any of the successor blocks. This means the
147+
// block contains a last use of the value.
148+
visitLastUser(findLastUserInBlock(bb));
186149
}
187150
}
151+
}
152+
153+
// Compute a LifetimeBoundary.
154+
//
155+
// Precondition: no critical edges.
156+
void ValueLifetimeAnalysis::computeLifetimeBoundary(
157+
ValueLifetimeBoundary &boundary) {
158+
auto visitBlock = [&](SILBasicBlock *) { return true; };
159+
auto visitLastUser = [&](SILInstruction *lastUser) {
160+
boundary.lastUsers.push_back(lastUser);
161+
};
162+
auto visitBoundaryEdge = [&](SILBasicBlock *, SILBasicBlock *succBB) {
163+
boundary.boundaryEdges.push_back(succBB);
164+
};
165+
computeLifetime(visitBlock, visitLastUser, visitBoundaryEdge);
166+
}
167+
168+
// FIXME: There is no need for a Mode within the algorithm once critical edges
169+
// are universally prohibited.
170+
//
171+
// FIXME: DeadEndBlocks does not affect value lifetime. It
172+
// should be completely removed and handled by the client.
173+
bool ValueLifetimeAnalysis::computeFrontier(FrontierImpl &frontier, Mode mode,
174+
DeadEndBlocks *deBlocks) {
175+
bool noCriticalEdges = true;
176+
177+
// Exit-blocks from the lifetime region. The value is live at the end of
178+
// a predecessor block but not in the frontier block itself.
179+
BasicBlockSetVector<16> frontierBlocks(getFunction());
180+
181+
// Blocks where the value is live at the end of the block and which have
182+
// a frontier block as successor.
183+
BasicBlockSetVector<16> liveOutBlocks(getFunction());
184+
185+
auto visitBlock = [&](SILBasicBlock *bb) {
186+
return !deBlocks || !deBlocks->isDeadEnd(bb);
187+
};
188+
189+
bool foundInvalidLastUser = false;
190+
auto visitLastUser = [&](SILInstruction *lastUser) {
191+
if (!isa<TermInst>(lastUser)) {
192+
// The next instruction after the last use is part of the frontier.
193+
frontier.push_back(&*std::next(lastUser->getIterator()));
194+
return;
195+
}
196+
// FIXME: DeadObjectElimination and StackPromotion don't currently handle
197+
// last use terminators, for no good reason. Fix them, then remove the silly
198+
// UsersMustPostDomDef mode.
199+
if (mode == UsersMustPostDomDef) {
200+
foundInvalidLastUser = true;
201+
return;
202+
}
203+
// The last user is a TermInst, and the value is not live into any successor
204+
// blocks (the usedAndRedefinedInSucc case is never a terminator). Since
205+
// there is no further instruction in the block which can be the
206+
// frontier, add all successor blocks to the frontier.
207+
auto *termBB = lastUser->getParent();
208+
for (const SILSuccessor &succ : termBB->getSuccessors()) {
209+
assert(!isAliveAtBeginOfBlock(succ)
210+
&& "out-of-sync with computeLifetime");
211+
212+
if (deBlocks && deBlocks->isDeadEnd(succ))
213+
continue;
214+
215+
// The successor's first instruction will be added to the frontier. Fake
216+
// this block as live-out so edge splitting works.
217+
liveOutBlocks.insert(termBB);
218+
frontierBlocks.insert(succ);
219+
}
220+
};
221+
auto visitBoundaryEdge = [&](SILBasicBlock *predBB, SILBasicBlock *succBB) {
222+
if (deBlocks && deBlocks->isDeadEnd(succBB))
223+
return;
224+
225+
if (mode == UsersMustPostDomDef) {
226+
foundInvalidLastUser = true;
227+
return;
228+
}
229+
liveOutBlocks.insert(predBB);
230+
frontierBlocks.insert(succBB);
231+
};
232+
233+
// Populate frontierBlocks and call visitLastUser().
234+
computeLifetime(visitBlock, visitLastUser, visitBoundaryEdge);
235+
if (foundInvalidLastUser)
236+
return false;
237+
188238
// Handle "exit" edges from the lifetime region.
189239
BasicBlockSet unhandledFrontierBlocks(getFunction());
190240
bool unhandledFrontierBlocksFound = false;

test/SILOptimizer/stack_promotion.sil

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -820,3 +820,37 @@ bb14: // Preds: bb12 bb1
820820
%32 = struct $Int32 (%31 : $Builtin.Int32) // user: %33
821821
return %32 : $Int32 // id: %33
822822
} // end sil function 'promote_with_unreachable_block_nest_bug'
823+
824+
// Take XX as an argument, but don't actually return or escape it. I'm
825+
// not sure how this happens in practice, but stack promotion requires
826+
// that the argument does not escape.
827+
sil @tryInitXX : $@convention(method) (@owned XX) -> (@owned XX, @error Error) {
828+
bb0(%0 : $XX):
829+
%1 = alloc_ref $XX
830+
strong_release %0 : $XX
831+
return %1 : $XX
832+
}
833+
834+
// FIXME: Stack promotion currently fails whenever the last use is a
835+
// terminator. This is a ridiculous limitation.
836+
//
837+
// CHECK-LABEL: sil @testUsedByTryApply : $@convention(thin) () -> (@owned XX, @error Error) {
838+
// CHECK: alloc_ref $XX
839+
// CHECK: try_apply
840+
// CHECK: bb1(%{{.*}} : $XX):
841+
// CHECK-NEXT: return
842+
// CHECK: bb2(%{{.*}}: $Error):
843+
// CHECK-NEXT: throw
844+
// CHECK-LABEL: } // end sil function 'testUsedByTryApply'
845+
sil @testUsedByTryApply : $@convention(thin) () -> (@owned XX, @error Error) {
846+
bb0:
847+
%0 = alloc_ref $XX
848+
%1 = function_ref @tryInitXX : $@convention(method) (@owned XX) -> (@owned XX, @error Error)
849+
try_apply %1(%0) : $@convention(method) (@owned XX) -> (@owned XX, @error Error), normal bb1, error bb2
850+
851+
bb1(%4 : $XX):
852+
return %4 : $XX
853+
854+
bb2(%5 : $Error):
855+
throw %5 : $Error
856+
}

0 commit comments

Comments
 (0)