Skip to content

Commit f5480ad

Browse files
committed
Cleanup canonicalize OSSA reborrow handling and add tests
1 parent a06e4d4 commit f5480ad

File tree

2 files changed

+105
-25
lines changed

2 files changed

+105
-25
lines changed

lib/SILOptimizer/Utils/CanonicalizeOSSALifetime.cpp

Lines changed: 26 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -121,17 +121,18 @@ static DestroyValueInst *dynCastToDestroyOf(SILInstruction *instruction,
121121

122122
bool CanonicalizeOSSALifetime::computeCanonicalLiveness() {
123123
defUseWorklist.initialize(getCurrentDef());
124+
// Only the first level of reborrows need to be consider. All nested inner
125+
// adjacent reborrows and phis are encapsulated within their lifetimes.
126+
SILPhiArgument *arg;
127+
if ((arg = dyn_cast<SILPhiArgument>(getCurrentDef())) && arg->isPhi()) {
128+
visitAdjacentReborrowsOfPhi(arg, [&](SILPhiArgument *reborrow) {
129+
defUseWorklist.insert(reborrow);
130+
return true;
131+
});
132+
}
124133
while (SILValue value = defUseWorklist.pop()) {
125-
SILPhiArgument *arg;
126-
if ((arg = dyn_cast<SILPhiArgument>(value)) && arg->isPhi()) {
127-
visitAdjacentReborrowsOfPhi(arg, [&](SILPhiArgument *reborrow) {
128-
defUseWorklist.insert(reborrow);
129-
return true;
130-
});
131-
}
132134
for (Operand *use : value->getUses()) {
133135
auto *user = use->getUser();
134-
135136
// Recurse through copies.
136137
if (auto *copy = dyn_cast<CopyValueInst>(user)) {
137138
defUseWorklist.insert(copy);
@@ -187,31 +188,31 @@ bool CanonicalizeOSSALifetime::computeCanonicalLiveness() {
187188
case OperandOwnership::InteriorPointer:
188189
case OperandOwnership::GuaranteedForwarding:
189190
case OperandOwnership::EndBorrow:
190-
// Guaranteed values are considered uses of the value when the value
191-
// owneed. If user is a guaranteed phi, then the owned lifetime either
192-
// dominates it or its lifetime ends at an outer adjacent reborrow.
191+
// Guaranteed values are exposed by inner adjacent reborrows. If user is
192+
// a guaranteed phi (GuaranteedForwarding), then the owned lifetime
193+
// either dominates it or its lifetime ends at an outer adjacent
194+
// reborrow. Only instructions that end the reborrow lifetime should
195+
// actually affect liveness of the outer owned value.
193196
liveness.updateForUse(user, /*lifetimeEnding*/ false);
194197
break;
195198
case OperandOwnership::Reborrow:
196-
BranchInst *branch;
197-
if (!(branch = dyn_cast<BranchInst>(user))) {
198-
// Non-phi reborrows (tuples, etc) never end the lifetime of the owned
199-
// value.
200-
liveness.updateForUse(user, /*lifetimeEnding*/ false);
201-
defUseWorklist.insert(cast<SingleValueInstruction>(user));
202-
break;
203-
}
204-
if (is_contained(user->getOperandValues(), getCurrentDef())) {
205-
// An adjacent phi consumes the value being reborrowed. Although this
206-
// use doesn't end the lifetime, this user does.
207-
liveness.updateForUse(user, /*lifetimeEnding*/ true);
199+
BranchInst *branch = cast<BranchInst>(user);
200+
// This is a cheap variation on visitEnclosingDef. We already know that
201+
// getCurrentDef() is the enclosing def for this use. If the reborrow's
202+
// has a enclosing def is an outer adjacent phi then this branch must
203+
// consume getCurrentDef() as the outer phi operand.
204+
if (is_contained(branch->getOperandValues(), getCurrentDef())) {
205+
// An adjacent phi consumes the value being reborrowed. Although this
206+
// use doesn't end the lifetime, this branch does end the lifetime by
207+
// consuming the owned value.
208+
liveness.updateForUse(branch, /*lifetimeEnding*/ true);
208209
break;
209210
}
210211
// No adjacent phi consumes the value. This use is not lifetime ending.
211-
liveness.updateForUse(user, /*lifetimeEnding*/ false);
212+
liveness.updateForUse(branch, /*lifetimeEnding*/ false);
212213
// This branch reborrows a guaranteed phi whose lifetime is dependent on
213214
// currentDef. Uses of the reborrowing phi extend liveness.
214-
auto *reborrow = branch->getArgForOperand(use);
215+
auto *reborrow = PhiOperand(use).getValue();
215216
defUseWorklist.insert(reborrow);
216217
break;
217218
}

test/SILOptimizer/copy_propagation_canonicalize_with_reborrows.sil

Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -271,3 +271,82 @@ exit:
271271
return %retval : $()
272272
}
273273

274+
// The owned phi %5 has an inner adjacent phi %4, so its end_borrow is
275+
// considered a use of the owned lifetime. The end_borrow/destroy
276+
// maintain their position.
277+
//
278+
// CHECK-LABEL: sil [ossa] @reborrow_adjacent_to_ownedphi : $@convention(thin) (@owned X) -> () {
279+
// CHECK: end_borrow
280+
// CHECK: destroy_value
281+
sil [ossa] @reborrow_adjacent_to_ownedphi : $@convention(thin) (@owned X) -> () {
282+
bb0(%0 : @owned $X):
283+
%1 = begin_borrow %0 : $X
284+
br bb1(%1 : $X, %0 : $X)
285+
286+
bb1(%4 : @guaranteed $X, %5 : @owned $X):
287+
end_borrow %4 : $X
288+
destroy_value %5 : $X
289+
%23 = tuple ()
290+
return %23 : $()
291+
}
292+
293+
// The owned phi %5 has an inner adjacent phi %4, so its reborrow use,
294+
// %6 is considered a use of the owned value. Since %5 is also
295+
// consumed by an owned phi in bb2, the owned lifetime extends no further.
296+
//
297+
// Lifetime analysis is successful; the copy/destroy in bb1 are removed. The
298+
// end_borrow/destroy maintain their position.
299+
//
300+
// CHECK-LABEL: sil [ossa] @reborrow_adjacent_to_consume : $@convention(thin) (@owned X) -> () {
301+
// CHECK: bb1(%{{.*}} : @guaranteed $X, %{{.*}} : @owned $X):
302+
// CHECK-NEXT: br bb2
303+
// CHECK: bb2(%{{.*}} : @guaranteed $X, %{{.*}} : @owned $X):
304+
// CHECK-NEXT: end_borrow
305+
// CHECK-NEXT: destroy_value
306+
sil [ossa] @reborrow_adjacent_to_consume : $@convention(thin) (@owned X) -> () {
307+
bb0(%0 : @owned $X):
308+
%1 = begin_borrow %0 : $X
309+
br bb1(%1 : $X, %0 : $X)
310+
311+
bb1(%4 : @guaranteed $X, %5 : @owned $X):
312+
%copy = copy_value %5 : $X
313+
destroy_value %copy : $X
314+
br bb2(%4 : $X, %5 : $X)
315+
316+
bb2(%6 : @guaranteed $X, %7 : @owned $X):
317+
end_borrow %6 : $X
318+
destroy_value %7 : $X
319+
%23 = tuple ()
320+
return %23 : $()
321+
}
322+
323+
// The owned phi %5 has an inner adjacent phi %4, so its reborrow use,
324+
// %6 is considered a use of the owned value. Since %5 is not consumed
325+
// by an owned phi in bb2, the owned lifetime extends to the uses of
326+
// the second reborrow, %6. The copy/destroy in bb2 are removed. The
327+
// end_borrow/destroy maintain their position.
328+
//
329+
// Lifetime analysis is successful; the copy/destroy in bb2 are removed.
330+
//
331+
// CHECK-LABEL: sil [ossa] @reborrow_no_adjacent_consume : $@convention(thin) (@owned X) -> () {
332+
// CHECK: bb1(%{{.*}} : @guaranteed $X, %{{.*}} : @owned $X):
333+
// CHECK-NEXT: br bb2
334+
// CHECK: bb2(%{{.*}} : @guaranteed $X):
335+
// CHECK-NEXT: end_borrow
336+
// CHECK-NEXT: destroy_value
337+
sil [ossa] @reborrow_no_adjacent_consume : $@convention(thin) (@owned X) -> () {
338+
bb0(%0 : @owned $X):
339+
%1 = begin_borrow %0 : $X
340+
br bb1(%1 : $X, %0 : $X)
341+
342+
bb1(%4 : @guaranteed $X, %5 : @owned $X):
343+
br bb2(%4 : $X)
344+
345+
bb2(%6 : @guaranteed $X):
346+
%copy = copy_value %5 : $X
347+
destroy_value %copy : $X
348+
end_borrow %6 : $X
349+
destroy_value %5 : $X
350+
%23 = tuple ()
351+
return %23 : $()
352+
}

0 commit comments

Comments
 (0)