-
Notifications
You must be signed in to change notification settings - Fork 10.5k
[ownership] Add a new ReborrowVerifier #34438
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Conversation
include/swift/SIL/ReborrowVerifier.h
Outdated
bool verify(); | ||
|
||
// Add a guaranteed phiArg and its base value to the worklist for verification | ||
void add(SILPhiArgument *phiArg, SILValue baseVal) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Does this need to be in SIL? Can we hide it as a library detail?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'll update it.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done.
76c1790
to
7fb3b79
Compare
f863ea3
to
48b505a
Compare
@swift-ci test |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Some initial comments around perf/etc and some things for me that I need to think about. I want to look through this again before this goes in.
// this. | ||
class ReborrowVerifier { | ||
/// worklist of reborrows and their base value | ||
llvm::SmallVector<std::pair<SILPhiArgument *, SILValue>, 8> worklist; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You don't need llvm:: on SmallVector.
// lies within the lifetime of its base value. It uses LinearLifetimeChecker for | ||
// this. | ||
class ReborrowVerifier { | ||
/// worklist of reborrows and their base value |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Caps plz. I would also put space in between ivars.
auto item = worklist.pop_back_val(); | ||
auto *phiArg = item.first; | ||
auto baseVal = item.second; | ||
SmallVector<Operand *, 4> phiArgUses(phiArg->getUses()); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I would hoist these small things out. That way if we go big, we do not need to reallocate.
@@ -0,0 +1,63 @@ | |||
//===--- ReborrowVerifier.cpp ----------------------------------------===// |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This header is messed up. I think it isn't 80 columns. Also can you change the copyright year to 2020?
@@ -50,6 +50,7 @@ class LinearLifetimeChecker { | |||
class ErrorBuilder; | |||
|
|||
private: | |||
friend class ReborrowVerifier; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Rather than doing this, can you expose a higher level API on LinearLifetimeChecker? Maybe an asserting variant of checkUses?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Am doing this to pass LinearLifetimeChecker::ErrorBuilder
with continuous error number with a common errorMessageCounter
. I would have used the public LinearLifetimeChecker ::validateLifetime
but would loose the nice error numbering.
What do you think about exposing a version of LinearLifetimeChecker ::validateLifetime
that has an errorBuilder parameter ?
include/swift/SIL/SILValue.h
Outdated
@@ -285,6 +285,12 @@ class ValueBase : public SILNode, public SILAllocated<ValueBase> { | |||
/// and it has a single consuming user. Returns .none otherwise. | |||
inline Operand *getSingleConsumingUse() const; | |||
|
|||
inline SmallVector<Operand *, 4> |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can you use an iterator here instead of returning a SmallVector?
Also why do you need the verifying argument?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
When verifying with OwnershipVerifierTextualErrorDumper - we don't want this assert to fire: https://github.com/apple/swift/blob/58d07a4efd0d9e5a38dc4b0d029d6f80d7152a62/include/swift/SIL/SILValue.h#L586. That reminds me that callers should pass true
only when verifying with OwnershipVerifierTextualErrorDumper.
lib/SIL/Utils/OwnershipUtils.cpp
Outdated
|
||
foundUses.push_back(op); | ||
}); | ||
[&](Operand *op) { foundUses.push_back(op); }); | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I need to think about this. Just to make sure it works in my brain.
} | ||
if (!isa<TermInst>(&i)) { | ||
return true; | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I need to think about this. This is used for more than just the reborrow verifier.
@@ -215,6 +233,79 @@ bool SILValueOwnershipChecker::isCompatibleDefUse( | |||
return false; | |||
} | |||
|
|||
// Look for reborrows and find their base value and populate ReborrowVerifier's | |||
// worklist. | |||
void SILValueOwnershipChecker::populateReborrowVerifier( |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I would stick this in ReborrowVerifier. SILValueOwnershipChecker doesn't need to know about this detail.
worklist.push_back(std::make_pair(phiArg, baseVal)); | ||
} | ||
|
||
// check if the operand is visited |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
3 slashes for doxygen comment.
// check if the phi arg and base value are visited | ||
bool isVisitedPhiArg(SILPhiArgument *phiArg, SILValue baseVal) { | ||
auto itPhiArg = visitedPhiArgs.find(phiArg); | ||
if (itPhiArg != visitedPhiArgs.end() && (*itPhiArg).second == baseVal) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You can just return this. That is:
return itPhiArg != visitedPhiArgs.end() && ...
/// ignore "leaks". | ||
DeadEndBlocks &deadEndBlocks; | ||
/// A cache map of borrow lifetime ending operands and their base value | ||
llvm::DenseMap<Operand *, SILValue> visitedOps; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Do these need to be full on DenseMaps? Seems like they could be SmallDenseMaps.
Build failed |
Build failed |
48b505a
to
fd6e3b0
Compare
include/swift/SIL/SILValue.h
Outdated
<< ". Not in map!\n" | ||
<< *this; | ||
llvm_unreachable("standard error assertion"); | ||
if (!verifying) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If we want to do this, why not pass back an optional?
fd6e3b0
to
09c275f
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Some more comments. I want to understand this more before it goes in/have my questions answered.
include/swift/SIL/SILValue.h
Outdated
#ifndef NDEBUG | ||
if (!canAcceptKind(kind)) { | ||
llvm::errs() << "Can not lookup lifetime constraint: " << kind | ||
<< ". Not in map!\n" |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think this is needed here. Here is my suggestion, why don't we add two entry points, one that asserts and the other that returns an optional. That way the user can make the choice.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Right, I reverted to original
include/swift/SIL/SILValue.h
Outdated
@@ -770,6 +781,71 @@ inline ValueBase::use_iterator ValueBase::use_end() const { | |||
inline iterator_range<ValueBase::use_iterator> ValueBase::getUses() const { | |||
return { use_begin(), use_end() }; | |||
} | |||
|
|||
class ConsumingUseIterator : public ValueBaseUseIterator { | |||
public: |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This needs to be git-clang-formated.
lib/SIL/Utils/OwnershipUtils.cpp
Outdated
@@ -229,13 +229,6 @@ void BorrowingOperand::visitConsumingUsesOfBorrowIntroducingUserResults( | |||
// This enables one to walk the def-use chain of guaranteed phis for a | |||
// single guaranteed scope. | |||
value.visitLocalScopeEndingUses([&](Operand *valueUser) { | |||
if (auto subBorrowScopeOp = BorrowingOperand::get(valueUser)) { | |||
if (subBorrowScopeOp->consumesGuaranteedValues()) { | |||
subBorrowScopeOp->visitUserResultConsumingUses(func); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The point of this method was to be able to chain through a piece-wise guaranteed lifetime. Given that we are no longer doing that I don't think that this method makes sense as written anymore.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I am leaving BorrowingOperand::visitConsumingUsesOfBorrowIntroducingUserResults unchanged now. It gets called transitively from BorrowedValue::areUsesWithinScope which gets used in SemanticARCOpt
lib/SIL/Utils/OwnershipUtils.cpp
Outdated
SmallVector<BorrowingOperand, 8> worklist; | ||
SmallPtrSet<Operand *, 8> visitedValue; | ||
worklist.push_back(*this); | ||
visitedValue.insert(op); | ||
bool foundError = false; | ||
|
||
while (!worklist.empty()) { | ||
auto scopedOperand = worklist.pop_back_val(); | ||
scopedOperand.visitConsumingUsesOfBorrowIntroducingUserResults( |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think this at this point should just be visitEndScopeInstructions, no?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
yes, changed it : )
} | ||
// guaranteed phi args are verifier as part of | ||
// ReborrowVerifier | ||
if (nonConsumingUse->get().getOwnershipKind() == |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't understand why this is here. This is a detail of the ReborrowVerifier leaking into the LinearLifetimeChecker. Can you elaborate here?
foundError |= | ||
initialScopedOperand->getImplicitUses(nonLifetimeEndingUsers, &error); | ||
SmallVector<Operand *, 4> borrowLifetimeEndingUsers; | ||
initialScopedOperand->getImplicitUses(borrowLifetimeEndingUsers, &error); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
My thought: why not just make the reborrow verifier a struct that is created here that takes the initialScopedOperand and verifies based off of that? Would be simpler and details wouldn't leak out.
a1f7fc7
to
58f716e
Compare
@swift-ci test |
Build failed |
return false; | ||
// Reborrows are currently represented as a consuming | ||
// use. Do not raise error for reborrows. | ||
if (isReborrowInstruction(&i)) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I thought about this in a deeper manner. The condition we are really looking for here is if the consuming use and the non consuming use are the same instruction. We can just hoist that out of the if into an earlier if statement and continue. I.e.:
for (auto *nonConsumingUse : nonConsumingUsesInBlock) {
auto *consumingUser = consumingUse->getUser();
if (nonConsumingUse->getUser() != consumingUser)) {
if (std::find_if(... normal code ...)) {
continue;
}
} else {
if (isReborrowInstruction(consumingUser))
continue;
}
... throw error ...
}
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I am also worried about baking in isReborrowInstruction into the verifier itself. It is meant to be a more general utility than this. Maybe add a flag that controls whether or not we allow for reborrows and have it disabled by default? Then at least we are only making this change where it is needed.
Another thing, on the reborrow failure path we could emit a better error as well (since it is not exactly an outside of lifetime use).
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I refactored. But am not sure why would we need a flag ? Reborrows are never consuming
SmallVector<Operand *, 4> phiArgUses(phiArg->getUses().begin(), | ||
phiArg->getUses().end()); | ||
|
||
// Verify whethre the guaranteed phi arg lies within the lifetime of the base |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
whethre -> whether
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Looking much better! Some more smaller things.
I looked (but maybe I needed to look harder = p, so if I missed it feel free to ignore). Can you add multiple diamond tests. Something that does:
checked_cast_br -> switch_enum?
SILValue baseVal) { | ||
SmallPtrSet<SILBasicBlock *, 4> visitedBlocks; | ||
bool result = false; | ||
SmallVector<Operand *, 4> phiArgUses(phiArg->getUses().begin(), |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think you can just pass in phiArg->getUses(). I would try it.
if (isVisitedOp(borrowLifetimeEndOp, baseVal)) | ||
continue; | ||
|
||
// Process reborrow |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I would add a TODO here that says if we add more reborrow instructions this should be abstracted onto a ReborrowingOperand ADT type so we can abstract over terminator vs non-terminator. I am fine doing that latter.
|
||
// A guaranteed phi arg ends the borrow scope of its incoming value and begins a | ||
// new borrow scope. ReborrowVerifier validates the lifetime of the reborrow | ||
// lies within the lifetime of its base value. It uses LinearLifetimeChecker for |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Three slashes here.
@@ -0,0 +1,267 @@ | |||
|
|||
// RUN: %target-sil-opt -sil-ownership-verifier-enable-testing -ownership-verifier-textual-error-dumper -enable-sil-verify-all=0 %s -o /dev/null 2>&1 | %FileCheck %s |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nit: Can you put the RUN line on the top line and put a space in between the REQUIRES: asserts and the imports.
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.
58f716e
to
601ea65
Compare
@swift-ci test |
Build failed |
Build failed |
@swift-ci test |
Build failed |
@swift-ci test OS X Platform |
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:
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.