Skip to content

Commit 1ff1826

Browse files
authored
Merge pull request #59715 from meg-gupta/updateloadborrowimmutability
Update LoadBorrowImmutability and PrunedLiveness to handle reborrows
2 parents 2ffec63 + 40d6526 commit 1ff1826

File tree

9 files changed

+314
-59
lines changed

9 files changed

+314
-59
lines changed

include/swift/SIL/OwnershipUtils.h

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -578,15 +578,20 @@ struct BorrowedValue {
578578
///
579579
/// \p deadEndBlocks is optional during transition. It will be completely
580580
/// removed in an upcoming commit.
581-
bool areUsesWithinLocalScope(ArrayRef<Operand *> uses,
582-
DeadEndBlocks *deadEndBlocks) const;
581+
bool areUsesWithinTransitiveScope(ArrayRef<Operand *> uses,
582+
DeadEndBlocks *deadEndBlocks) const;
583583

584584
/// Given a local borrow scope introducer, visit all non-forwarding consuming
585585
/// users. This means that this looks through guaranteed block arguments. \p
586586
/// visitor is *not* called on Reborrows, only on final scope ending uses.
587587
bool
588588
visitExtendedScopeEndingUses(function_ref<bool(Operand *)> visitor) const;
589589

590+
/// Visit all lifetime ending operands of the entire borrow scope including
591+
/// reborrows
592+
bool
593+
visitTransitiveLifetimeEndingUses(function_ref<bool(Operand *)> func) const;
594+
590595
void print(llvm::raw_ostream &os) const;
591596
SWIFT_DEBUG_DUMP { print(llvm::dbgs()); }
592597

include/swift/SIL/PrunedLiveness.h

Lines changed: 28 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -164,7 +164,6 @@ class PrunedLiveBlocks {
164164
}
165165

166166
void initializeDefBlock(SILBasicBlock *defBB) {
167-
assert(!seenUse && "cannot initialize more defs with partial liveness");
168167
markBlockLive(defBB, LiveWithin);
169168
}
170169

@@ -229,6 +228,15 @@ class PrunedLiveness {
229228
/// ending uses that extend liveness into a loop body.
230229
SmallSetVector<SILInstruction *, 8> *nonLifetimeEndingUsesInLiveOut;
231230

231+
private:
232+
bool isWithinBoundaryHelper(SILInstruction *inst, SILValue def) const;
233+
234+
bool areUsesWithinBoundaryHelper(ArrayRef<Operand *> uses, SILValue def,
235+
DeadEndBlocks *deadEndBlocks) const;
236+
237+
bool areUsesOutsideBoundaryHelper(ArrayRef<Operand *> uses, SILValue def,
238+
DeadEndBlocks *deadEndBlocks) const;
239+
232240
public:
233241
PrunedLiveness(SmallVectorImpl<SILBasicBlock *> *discoveredBlocks = nullptr,
234242
SmallSetVector<SILInstruction *, 8>
@@ -342,6 +350,25 @@ class PrunedLiveness {
342350
bool areUsesOutsideBoundary(ArrayRef<Operand *> uses,
343351
DeadEndBlocks *deadEndBlocks) const;
344352

353+
/// PrunedLiveness utilities can be used with multiple defs. This api can be
354+
/// used to check if \p inst occurs in between the definition \p def and the
355+
/// liveness boundary.
356+
// This api varies from isWithinBoundary(SILInstruction *inst) which cannot
357+
// distinguish when \p inst is a use before definition in the same block as
358+
// the definition.
359+
bool isWithinBoundaryOfDef(SILInstruction *inst, SILValue def) const;
360+
361+
/// Returns true when all \p uses are between \p def and the liveness boundary
362+
/// \p deadEndBlocks is optional.
363+
bool areUsesWithinBoundaryOfDef(ArrayRef<Operand *> uses, SILValue def,
364+
DeadEndBlocks *deadEndBlocks) const;
365+
366+
/// Returns true if any of the \p uses are before the \p def or after the
367+
/// liveness boundary
368+
/// \p deadEndBlocks is optional.
369+
bool areUsesOutsideBoundaryOfDef(ArrayRef<Operand *> uses, SILValue def,
370+
DeadEndBlocks *deadEndBlocks) const;
371+
345372
/// Compute liveness for a single SSA definition.
346373
void computeSSALiveness(SILValue def);
347374
};

lib/SIL/Utils/OwnershipUtils.cpp

Lines changed: 43 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -689,13 +689,20 @@ llvm::raw_ostream &swift::operator<<(llvm::raw_ostream &os,
689689
/// Add this scopes live blocks into the PrunedLiveness result.
690690
void BorrowedValue::computeLiveness(PrunedLiveness &liveness) const {
691691
liveness.initializeDefBlock(value->getParentBlock());
692-
visitLocalScopeEndingUses([&](Operand *endOp) {
693-
liveness.updateForUse(endOp->getUser(), true);
692+
visitTransitiveLifetimeEndingUses([&](Operand *endOp) {
693+
if (endOp->getOperandOwnership() == OperandOwnership::EndBorrow) {
694+
liveness.updateForUse(endOp->getUser(), /*lifetimeEnding*/ true);
695+
return true;
696+
}
697+
assert(endOp->getOperandOwnership() == OperandOwnership::Reborrow);
698+
auto *succBlock = cast<BranchInst>(endOp->getUser())->getDestBB();
699+
liveness.initializeDefBlock(succBlock);
700+
liveness.updateForUse(endOp->getUser(), /*lifetimeEnding*/ false);
694701
return true;
695702
});
696703
}
697704

698-
bool BorrowedValue::areUsesWithinLocalScope(
705+
bool BorrowedValue::areUsesWithinTransitiveScope(
699706
ArrayRef<Operand *> uses, DeadEndBlocks *deadEndBlocks) const {
700707
// First make sure that we actually have a local scope. If we have a non-local
701708
// scope, then we have something (like a SILFunctionArgument) where a larger
@@ -741,6 +748,38 @@ bool BorrowedValue::visitExtendedScopeEndingUses(
741748
return true;
742749
}
743750

751+
bool BorrowedValue::visitTransitiveLifetimeEndingUses(
752+
function_ref<bool(Operand *)> visitor) const {
753+
assert(isLocalScope());
754+
755+
SmallPtrSetVector<SILValue, 4> reborrows;
756+
757+
auto visitEnd = [&](Operand *scopeEndingUse) {
758+
if (scopeEndingUse->getOperandOwnership() == OperandOwnership::Reborrow) {
759+
BorrowingOperand(scopeEndingUse)
760+
.visitBorrowIntroducingUserResults([&](BorrowedValue borrowedValue) {
761+
reborrows.insert(borrowedValue.value);
762+
return true;
763+
});
764+
// visitor on the reborrow
765+
return visitor(scopeEndingUse);
766+
}
767+
// visitor on the end_borrow
768+
return visitor(scopeEndingUse);
769+
};
770+
771+
if (!visitLocalScopeEndingUses(visitEnd))
772+
return false;
773+
774+
// reborrows grows in this loop.
775+
for (unsigned idx = 0; idx < reborrows.size(); ++idx) {
776+
if (!BorrowedValue(reborrows[idx]).visitLocalScopeEndingUses(visitEnd))
777+
return false;
778+
}
779+
780+
return true;
781+
}
782+
744783
bool BorrowedValue::visitInteriorPointerOperandHelper(
745784
function_ref<void(InteriorPointerOperand)> func,
746785
BorrowedValue::InteriorPointerOperandVisitorKind kind) const {
@@ -975,7 +1014,7 @@ bool AddressOwnership::areUsesWithinLifetime(
9751014
SILValue root = base.getOwnershipReferenceRoot();
9761015
BorrowedValue borrow(root);
9771016
if (borrow)
978-
return borrow.areUsesWithinLocalScope(uses, &deadEndBlocks);
1017+
return borrow.areUsesWithinTransitiveScope(uses, &deadEndBlocks);
9791018

9801019
// --- A reference no borrow scope. Currently happens for project_box.
9811020

lib/SIL/Utils/PrunedLiveness.cpp

Lines changed: 83 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -127,67 +127,124 @@ bool PrunedLiveness::updateForBorrowingOperand(Operand *op) {
127127
}
128128

129129
void PrunedLiveness::extendAcrossLiveness(PrunedLiveness &otherLivesness) {
130-
// update this liveness for all the interesting users in otherLivesness.
130+
// update this liveness for all the interesting users in otherLiveness.
131131
for (std::pair<SILInstruction *, bool> userAndEnd : otherLivesness.users) {
132132
updateForUse(userAndEnd.first, userAndEnd.second);
133133
}
134134
}
135135

136-
bool PrunedLiveness::isWithinBoundary(SILInstruction *inst) const {
136+
bool PrunedLiveness::isWithinBoundaryHelper(SILInstruction *inst,
137+
SILValue def) const {
137138
SILBasicBlock *block = inst->getParent();
139+
140+
/// Returns true if \p inst is before \p def in this block.
141+
auto foundInBlockBeforeDef = [](SILInstruction *inst, SILBasicBlock *block,
142+
SILValue def) {
143+
if (!def || def->getParentBlock() != block) {
144+
return false;
145+
}
146+
auto *defInst = def->getDefiningInstruction();
147+
if (!defInst) {
148+
return false;
149+
}
150+
// Check if instruction is before the definition
151+
for (SILInstruction &it :
152+
make_range(block->begin(), defInst->getIterator())) {
153+
if (&it == inst) {
154+
return true;
155+
}
156+
}
157+
return false;
158+
};
159+
138160
switch (getBlockLiveness(block)) {
139161
case PrunedLiveBlocks::Dead:
140162
return false;
141-
case PrunedLiveBlocks::LiveWithin:
142-
break;
143163
case PrunedLiveBlocks::LiveOut:
144-
return true;
145-
}
146-
// The boundary is within this block. This instruction is before the boundary
147-
// iff any interesting uses occur after it.
148-
for (SILInstruction &inst :
164+
return !foundInBlockBeforeDef(inst, block, def);
165+
case PrunedLiveBlocks::LiveWithin:
166+
if (foundInBlockBeforeDef(inst, block, def)) {
167+
return false;
168+
}
169+
// The boundary is within this block. This instruction is before the
170+
// boundary iff any interesting uses occur after it.
171+
for (SILInstruction &it :
149172
make_range(std::next(inst->getIterator()), block->end())) {
150-
switch (isInterestingUser(&inst)) {
151-
case PrunedLiveness::NonUser:
152-
break;
153-
case PrunedLiveness::NonLifetimeEndingUse:
154-
case PrunedLiveness::LifetimeEndingUse:
155-
return true;
173+
switch (isInterestingUser(&it)) {
174+
case PrunedLiveness::NonUser:
175+
break;
176+
case PrunedLiveness::NonLifetimeEndingUse:
177+
case PrunedLiveness::LifetimeEndingUse:
178+
return true;
179+
}
156180
}
181+
return false;
157182
}
158-
return false;
159183
}
160184

161-
bool PrunedLiveness::areUsesWithinBoundary(ArrayRef<Operand *> uses,
162-
DeadEndBlocks *deadEndBlocks) const {
185+
bool PrunedLiveness::isWithinBoundary(SILInstruction *inst) const {
186+
return isWithinBoundaryHelper(inst, /*def*/ SILValue());
187+
}
188+
189+
bool PrunedLiveness::isWithinBoundaryOfDef(SILInstruction *inst,
190+
SILValue def) const {
191+
return isWithinBoundaryHelper(inst, def);
192+
}
193+
194+
bool PrunedLiveness::areUsesWithinBoundaryHelper(
195+
ArrayRef<Operand *> uses, SILValue def,
196+
DeadEndBlocks *deadEndBlocks) const {
163197
auto checkDeadEnd = [deadEndBlocks](SILInstruction *inst) {
164198
return deadEndBlocks && deadEndBlocks->isDeadEnd(inst->getParent());
165199
};
166200

167201
for (auto *use : uses) {
168202
auto *user = use->getUser();
169-
if (!isWithinBoundary(user) && !checkDeadEnd(user))
203+
if (!isWithinBoundaryHelper(user, def) && !checkDeadEnd(user))
170204
return false;
171205
}
172206
return true;
173207
}
174208

175-
bool PrunedLiveness::areUsesOutsideBoundary(
176-
ArrayRef<Operand *> uses, DeadEndBlocks *deadEndBlocks) const {
209+
bool PrunedLiveness::areUsesWithinBoundary(ArrayRef<Operand *> uses,
210+
DeadEndBlocks *deadEndBlocks) const {
211+
return areUsesWithinBoundaryHelper(uses, SILValue(), deadEndBlocks);
212+
}
213+
214+
bool PrunedLiveness::areUsesWithinBoundaryOfDef(
215+
ArrayRef<Operand *> uses, SILValue def,
216+
DeadEndBlocks *deadEndBlocks) const {
217+
return areUsesWithinBoundaryHelper(uses, def, deadEndBlocks);
218+
}
219+
220+
bool PrunedLiveness::areUsesOutsideBoundaryHelper(
221+
ArrayRef<Operand *> uses, SILValue def,
222+
DeadEndBlocks *deadEndBlocks) const {
177223
auto checkDeadEnd = [deadEndBlocks](SILInstruction *inst) {
178224
return deadEndBlocks && deadEndBlocks->isDeadEnd(inst->getParent());
179225
};
180226

181227
for (auto *use : uses) {
182228
auto *user = use->getUser();
183-
if (isWithinBoundary(user) || checkDeadEnd(user))
229+
if (isWithinBoundaryHelper(user, def) || checkDeadEnd(user))
184230
return false;
185231
}
186232
return true;
187233
}
188234

189-
// An SSA def meets all the criteria for pruned liveness--def dominates all uses
190-
// with no holes in the liverange. The lifetime-ending uses are also
235+
bool PrunedLiveness::areUsesOutsideBoundary(
236+
ArrayRef<Operand *> uses, DeadEndBlocks *deadEndBlocks) const {
237+
return areUsesOutsideBoundaryHelper(uses, SILValue(), deadEndBlocks);
238+
}
239+
240+
bool PrunedLiveness::areUsesOutsideBoundaryOfDef(
241+
ArrayRef<Operand *> uses, SILValue def,
242+
DeadEndBlocks *deadEndBlocks) const {
243+
return areUsesOutsideBoundaryHelper(uses, def, deadEndBlocks);
244+
}
245+
246+
// An SSA def meets all the criteria for pruned liveness--def dominates all
247+
// uses with no holes in the liverange. The lifetime-ending uses are also
191248
// recorded--destroy_value or end_borrow. However destroy_values may not
192249
// jointly-post dominate if dead-end blocks are present.
193250
void PrunedLiveness::computeSSALiveness(SILValue def) {
@@ -278,8 +335,8 @@ void PrunedLivenessBoundary::compute(const PrunedLiveness &liveness,
278335
// Process each block that has not been visited and is not LiveOut.
279336
switch (liveness.getBlockLiveness(bb)) {
280337
case PrunedLiveBlocks::LiveOut:
281-
// A lifetimeEndBlock may be determined to be LiveOut after analyzing the
282-
// extended liveness. It is irrelevant for finding the boundary.
338+
// A lifetimeEndBlock may be determined to be LiveOut after analyzing
339+
// the extended liveness. It is irrelevant for finding the boundary.
283340
break;
284341
case PrunedLiveBlocks::LiveWithin: {
285342
// The liveness boundary is inside this block. Insert a final destroy

0 commit comments

Comments
 (0)