Skip to content

Commit 601ea65

Browse files
committed
[ownership] Add a new ReborrowVerifier
This updates how we model reborrow's lifetimes for ownership verification. Today we follow and combine a borrow's lifetime through phi args as well. Owned values lifetimes end at a phi arg. This discrepency in modeling lifetimes leads to the OwnershipVerifier raising errors incorrectly for cases such as this, where the borrow and the base value do not dominate the end_borrow: bb0: cond_br undef, bb1, bb2 bb1: %copy0 = copy_value %0 %borrow0 = begin_borrow %copy0 br bb3(%borrow0, %copy0) bb2: %copy1 = copy_value %1 %borrow1 = begin_borrow %copy1 br bb3(%borrow1, %copy1) bb3(%borrow, %baseVal): end_borrow %borrow destroy_value %baseVal This PR adds a new ReborrowVerifier. The ownership verifier collects borrow's lifetime ending users and populates the worklist of the ReborrowVerifier with reborrows and the corresponding base value. ReborrowVerifier then verifies that the lifetime of the reborrow is within the lifetime of the base value.
1 parent 3c2f1e4 commit 601ea65

13 files changed

+1656
-295
lines changed

include/swift/SIL/LinearLifetimeChecker.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,7 @@ class LinearLifetimeChecker {
5050
class ErrorBuilder;
5151

5252
private:
53+
friend class ReborrowVerifier;
5354
friend class SILOwnershipVerifier;
5455
friend class SILValueOwnershipChecker;
5556

include/swift/SIL/OwnershipUtils.h

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,9 @@ bool isOwnedForwardingInstruction(SILInstruction *inst);
6666
/// previous terminator.
6767
bool isOwnedForwardingValue(SILValue value);
6868

69+
/// Returns true if the instruction is a 'reborrow'.
70+
bool isReborrowInstruction(const SILInstruction *inst);
71+
6972
class BorrowingOperandKind {
7073
public:
7174
enum Kind : uint8_t {
@@ -134,7 +137,7 @@ struct BorrowingOperand {
134137
return BorrowingOperand(*kind, op);
135138
}
136139

137-
void visitEndScopeInstructions(function_ref<void(Operand *)> func) const;
140+
void visitLocalEndScopeInstructions(function_ref<void(Operand *)> func) const;
138141

139142
/// Returns true if this borrow scope operand consumes guaranteed
140143
/// values and produces a new scope afterwards.
@@ -204,7 +207,7 @@ struct BorrowingOperand {
204207
/// \p errorFunction a callback that if non-null is passed an operand that
205208
/// triggers a mal-formed SIL error. This is just needed for the ownership
206209
/// verifier to emit good output.
207-
bool getImplicitUses(
210+
void getImplicitUses(
208211
SmallVectorImpl<Operand *> &foundUses,
209212
std::function<void(Operand *)> *errorFunction = nullptr) const;
210213

include/swift/SIL/SILValue.h

Lines changed: 99 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,8 @@ class SILInstruction;
4040
class SILLocation;
4141
class DeadEndBlocks;
4242
class ValueBaseUseIterator;
43-
class ValueUseIterator;
43+
class ConsumingUseIterator;
44+
class NonConsumingUseIterator;
4445
class SILValue;
4546

4647
/// An enumeration which contains values for all the concrete ValueBase
@@ -263,10 +264,20 @@ class ValueBase : public SILNode, public SILAllocated<ValueBase> {
263264

264265
using use_iterator = ValueBaseUseIterator;
265266
using use_range = iterator_range<use_iterator>;
267+
using consuming_use_iterator = ConsumingUseIterator;
268+
using consuming_use_range = iterator_range<consuming_use_iterator>;
269+
using non_consuming_use_iterator = NonConsumingUseIterator;
270+
using non_consuming_use_range = iterator_range<non_consuming_use_iterator>;
266271

267272
inline use_iterator use_begin() const;
268273
inline use_iterator use_end() const;
269274

275+
inline consuming_use_iterator consuming_use_begin() const;
276+
inline consuming_use_iterator consuming_use_end() const;
277+
278+
inline non_consuming_use_iterator non_consuming_use_begin() const;
279+
inline non_consuming_use_iterator non_consuming_use_end() const;
280+
270281
/// Returns a range of all uses, which is useful for iterating over all uses.
271282
/// To ignore debug-info instructions use swift::getNonDebugUses instead
272283
/// (see comment in DebugUtils.h).
@@ -285,6 +296,12 @@ class ValueBase : public SILNode, public SILAllocated<ValueBase> {
285296
/// and it has a single consuming user. Returns .none otherwise.
286297
inline Operand *getSingleConsumingUse() const;
287298

299+
/// Returns a range of all consuming uses
300+
inline consuming_use_range getConsumingUses() const;
301+
302+
/// Returns a range of all non consuming uses
303+
inline non_consuming_use_range getNonConsumingUses() const;
304+
288305
template <class T>
289306
inline T *getSingleUserOfType() const;
290307

@@ -711,8 +728,10 @@ class Operand {
711728
TheValue->FirstUse = this;
712729
}
713730

731+
friend class ValueBase;
714732
friend class ValueBaseUseIterator;
715-
friend class ValueUseIterator;
733+
friend class ConsumingUseIterator;
734+
friend class NonConsumingUseIterator;
716735
template <unsigned N> friend class FixedOperandList;
717736
friend class TrailingOperandsList;
718737
};
@@ -729,6 +748,7 @@ using OperandValueArrayRef = ArrayRefView<Operand, SILValue, getSILValueType>;
729748
/// An iterator over all uses of a ValueBase.
730749
class ValueBaseUseIterator : public std::iterator<std::forward_iterator_tag,
731750
Operand*, ptrdiff_t> {
751+
protected:
732752
Operand *Cur;
733753
public:
734754
ValueBaseUseIterator() = default;
@@ -770,6 +790,74 @@ inline ValueBase::use_iterator ValueBase::use_end() const {
770790
inline iterator_range<ValueBase::use_iterator> ValueBase::getUses() const {
771791
return { use_begin(), use_end() };
772792
}
793+
794+
class ConsumingUseIterator : public ValueBaseUseIterator {
795+
public:
796+
explicit ConsumingUseIterator(Operand *cur) : ValueBaseUseIterator(cur) {}
797+
ConsumingUseIterator &operator++() {
798+
assert(Cur && "incrementing past end()!");
799+
assert(Cur->isConsumingUse());
800+
while ((Cur = Cur->NextUse)) {
801+
if (Cur->isConsumingUse())
802+
break;
803+
}
804+
return *this;
805+
}
806+
807+
ConsumingUseIterator operator++(int unused) {
808+
ConsumingUseIterator copy = *this;
809+
++*this;
810+
return copy;
811+
}
812+
};
813+
814+
inline ValueBase::consuming_use_iterator
815+
ValueBase::consuming_use_begin() const {
816+
auto cur = FirstUse;
817+
while (cur && !cur->isConsumingUse()) {
818+
cur = cur->NextUse;
819+
}
820+
return ValueBase::consuming_use_iterator(cur);
821+
}
822+
823+
inline ValueBase::consuming_use_iterator ValueBase::consuming_use_end() const {
824+
return ValueBase::consuming_use_iterator(nullptr);
825+
}
826+
827+
class NonConsumingUseIterator : public ValueBaseUseIterator {
828+
public:
829+
explicit NonConsumingUseIterator(Operand *cur) : ValueBaseUseIterator(cur) {}
830+
NonConsumingUseIterator &operator++() {
831+
assert(Cur && "incrementing past end()!");
832+
assert(!Cur->isConsumingUse());
833+
while ((Cur = Cur->NextUse)) {
834+
if (!Cur->isConsumingUse())
835+
break;
836+
}
837+
return *this;
838+
}
839+
840+
NonConsumingUseIterator operator++(int unused) {
841+
NonConsumingUseIterator copy = *this;
842+
++*this;
843+
return copy;
844+
}
845+
};
846+
847+
inline ValueBase::non_consuming_use_iterator
848+
ValueBase::non_consuming_use_begin() const {
849+
auto cur = FirstUse;
850+
while (cur && cur->isConsumingUse()) {
851+
cur = cur->NextUse;
852+
}
853+
return ValueBase::non_consuming_use_iterator(cur);
854+
}
855+
856+
inline ValueBase::non_consuming_use_iterator
857+
ValueBase::non_consuming_use_end() const {
858+
return ValueBase::non_consuming_use_iterator(nullptr);
859+
}
860+
773861
inline bool ValueBase::hasOneUse() const {
774862
auto I = use_begin(), E = use_end();
775863
if (I == E) return false;
@@ -806,6 +894,15 @@ inline Operand *ValueBase::getSingleConsumingUse() const {
806894
return result;
807895
}
808896

897+
inline ValueBase::consuming_use_range ValueBase::getConsumingUses() const {
898+
return {consuming_use_begin(), consuming_use_end()};
899+
}
900+
901+
inline ValueBase::non_consuming_use_range
902+
ValueBase::getNonConsumingUses() const {
903+
return {non_consuming_use_begin(), non_consuming_use_end()};
904+
}
905+
809906
inline bool ValueBase::hasTwoUses() const {
810907
auto iter = use_begin(), end = use_end();
811908
for (unsigned i = 0; i < 2; ++i) {

lib/SIL/Utils/OwnershipUtils.cpp

Lines changed: 12 additions & 49 deletions
Original file line numberDiff line numberDiff line change
@@ -125,6 +125,15 @@ bool swift::isOwnershipForwardingInst(SILInstruction *i) {
125125
return isOwnershipForwardingValueKind(SILNodeKind(i->getKind()));
126126
}
127127

128+
bool swift::isReborrowInstruction(const SILInstruction *i) {
129+
switch (i->getKind()) {
130+
case SILInstructionKind::BranchInst:
131+
return true;
132+
default:
133+
return false;
134+
}
135+
}
136+
128137
//===----------------------------------------------------------------------===//
129138
// Borrowing Operand
130139
//===----------------------------------------------------------------------===//
@@ -163,7 +172,7 @@ llvm::raw_ostream &swift::operator<<(llvm::raw_ostream &os,
163172
return os;
164173
}
165174

166-
void BorrowingOperand::visitEndScopeInstructions(
175+
void BorrowingOperand::visitLocalEndScopeInstructions(
167176
function_ref<void(Operand *)> func) const {
168177
switch (kind) {
169178
case BorrowingOperandKind::BeginBorrow:
@@ -181,18 +190,8 @@ void BorrowingOperand::visitEndScopeInstructions(
181190
return;
182191
}
183192
case BorrowingOperandKind::Branch:
184-
for (auto *succBlock :
185-
cast<BranchInst>(op->getUser())->getSuccessorBlocks()) {
186-
auto *arg = succBlock->getArgument(op->getOperandNumber());
187-
for (auto *use : arg->getUses()) {
188-
if (use->isConsumingUse()) {
189-
func(use);
190-
}
191-
}
192-
}
193193
return;
194194
}
195-
llvm_unreachable("Covered switch isn't covered");
196195
}
197196

198197
void BorrowingOperand::visitBorrowIntroducingUserResults(
@@ -267,46 +266,10 @@ void BorrowingOperand::visitUserResultConsumingUses(
267266
}
268267
}
269268

270-
bool BorrowingOperand::getImplicitUses(
269+
void BorrowingOperand::getImplicitUses(
271270
SmallVectorImpl<Operand *> &foundUses,
272271
std::function<void(Operand *)> *errorFunction) const {
273-
if (!areAnyUserResultsBorrowIntroducers()) {
274-
visitEndScopeInstructions([&](Operand *op) { foundUses.push_back(op); });
275-
return false;
276-
}
277-
278-
// Ok, we have an instruction that introduces a new borrow scope and its
279-
// result is that borrow scope. In such a case, we need to not just add the
280-
// end scope instructions of this scoped operand, but also look through any
281-
// guaranteed phis and add their end_borrow to this list as well.
282-
SmallVector<BorrowingOperand, 8> worklist;
283-
SmallPtrSet<Operand *, 8> visitedValue;
284-
worklist.push_back(*this);
285-
visitedValue.insert(op);
286-
bool foundError = false;
287-
while (!worklist.empty()) {
288-
auto scopedOperand = worklist.pop_back_val();
289-
scopedOperand.visitConsumingUsesOfBorrowIntroducingUserResults(
290-
[&](Operand *op) {
291-
if (auto subSub = BorrowingOperand::get(op)) {
292-
if (!visitedValue.insert(op).second) {
293-
if (errorFunction) {
294-
(*errorFunction)(op);
295-
}
296-
foundError = true;
297-
return;
298-
}
299-
300-
worklist.push_back(*subSub);
301-
visitedValue.insert(subSub->op);
302-
return;
303-
}
304-
305-
foundUses.push_back(op);
306-
});
307-
}
308-
309-
return foundError;
272+
visitLocalEndScopeInstructions([&](Operand *op) { foundUses.push_back(op); });
310273
}
311274

312275
//===----------------------------------------------------------------------===//

lib/SIL/Verifier/CMakeLists.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,5 +2,6 @@ target_sources(swiftSIL PRIVATE
22
LoadBorrowImmutabilityChecker.cpp
33
LinearLifetimeChecker.cpp
44
MemoryLifetime.cpp
5+
ReborrowVerifier.cpp
56
SILOwnershipVerifier.cpp
67
SILVerifier.cpp)

lib/SIL/Verifier/LinearLifetimeChecker.cpp

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -305,10 +305,14 @@ void State::checkForSameBlockUseAfterFree(Operand *consumingUse,
305305
// must be instructions in the given block. Make sure that the non consuming
306306
// user is strictly before the consuming user.
307307
for (auto *nonConsumingUse : nonConsumingUsesInBlock) {
308-
if (std::find_if(consumingUse->getUser()->getIterator(), userBlock->end(),
309-
[&nonConsumingUse](const SILInstruction &i) -> bool {
310-
return nonConsumingUse->getUser() == &i;
311-
}) == userBlock->end()) {
308+
if (nonConsumingUse->getUser() != consumingUse->getUser()) {
309+
if (std::find_if(consumingUse->getUser()->getIterator(), userBlock->end(),
310+
[&nonConsumingUse](const SILInstruction &i) -> bool {
311+
return nonConsumingUse->getUser() == &i;
312+
}) == userBlock->end()) {
313+
continue;
314+
}
315+
} else if (isReborrowInstruction(consumingUse->getUser())) {
312316
continue;
313317
}
314318

0 commit comments

Comments
 (0)