Skip to content

Commit d29bca5

Browse files
committed
[ownership] Extract out the computation of implicit uses for BorrowingOperands and InteriorPointerOperands into utilities on those classes.
I am currently working on updating SimplifyCFG for ownership. One thing that I am finding that I need is the ability to compute the lifetime of a guaranteed argument from a checked_cast_br/switch_enum in order to convert said instructions to a br. The issue comes from br acting as a consuming (lifetime ending) use of a borrowed value, unlike checked_cast_br/switch_enum (which are treated like forwarding instructions). This means that during the conversion we need to insert a begin_borrow on the value before the new br instruction and then create an end_borrow at the end of the successor block's argument's lifetime. By refactoring out this code from the ownership verifier, I can guarantee that said lifetime computation will always match what the ownership verifier does internally preventing them from getting out of sync.
1 parent 43574f7 commit d29bca5

File tree

3 files changed

+201
-167
lines changed

3 files changed

+201
-167
lines changed

include/swift/SIL/OwnershipUtils.h

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -196,6 +196,18 @@ struct BorrowingOperand {
196196
void visitConsumingUsesOfBorrowIntroducingUserResults(
197197
function_ref<void(Operand *)> visitor) const;
198198

199+
/// Compute the implicit uses that this borrowing operand "injects" into the
200+
/// set of its operands uses.
201+
///
202+
/// E.x.: end_apply uses.
203+
///
204+
/// \p errorFunction a callback that if non-null is passed an operand that
205+
/// triggers a mal-formed SIL error. This is just needed for the ownership
206+
/// verifier to emit good output.
207+
bool getImplicitUses(
208+
SmallVectorImpl<Operand *> &foundUses,
209+
std::function<void(Operand *)> *errorFunction = nullptr) const;
210+
199211
void print(llvm::raw_ostream &os) const;
200212
SWIFT_DEBUG_DUMP { print(llvm::dbgs()); }
201213

@@ -467,6 +479,16 @@ struct InteriorPointerOperand {
467479
llvm_unreachable("Covered switch isn't covered?!");
468480
}
469481

482+
/// Compute the list of implicit uses that this interior pointer operand puts
483+
/// on its parent guaranted value.
484+
///
485+
/// Example: Uses of a ref_element_addr can not occur outside of the lifetime
486+
/// of the instruction's operand. The uses of that address act as liveness
487+
/// requirements to ensure that the underlying class is alive at all use
488+
/// points.
489+
bool getImplicitUses(SmallVectorImpl<Operand *> &foundUses,
490+
std::function<void(Operand *)> *onError = nullptr);
491+
470492
private:
471493
/// Internal constructor for failable static constructor. Please do not expand
472494
/// its usage since it assumes the code passed in is well formed.

lib/SIL/Utils/OwnershipUtils.cpp

Lines changed: 150 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212

1313
#include "swift/SIL/OwnershipUtils.h"
1414
#include "swift/Basic/Defer.h"
15+
#include "swift/SIL/InstructionUtils.h"
1516
#include "swift/SIL/LinearLifetimeChecker.h"
1617
#include "swift/SIL/Projection.h"
1718
#include "swift/SIL/SILArgument.h"
@@ -266,6 +267,48 @@ void BorrowingOperand::visitUserResultConsumingUses(
266267
}
267268
}
268269

270+
bool BorrowingOperand::getImplicitUses(
271+
SmallVectorImpl<Operand *> &foundUses,
272+
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;
310+
}
311+
269312
//===----------------------------------------------------------------------===//
270313
// Borrow Introducers
271314
//===----------------------------------------------------------------------===//
@@ -459,6 +502,113 @@ bool BorrowedValue::visitInteriorPointerOperands(
459502
return true;
460503
}
461504

505+
//===----------------------------------------------------------------------===//
506+
// InteriorPointerOperand
507+
//===----------------------------------------------------------------------===//
508+
509+
bool InteriorPointerOperand::getImplicitUses(
510+
SmallVectorImpl<Operand *> &foundUses,
511+
std::function<void(Operand *)> *onError) {
512+
SILValue projectedAddress = getProjectedAddress();
513+
SmallVector<Operand *, 8> worklist(projectedAddress->getUses());
514+
515+
bool foundError = false;
516+
517+
while (!worklist.empty()) {
518+
auto *op = worklist.pop_back_val();
519+
520+
// Skip type dependent operands.
521+
if (op->isTypeDependent())
522+
continue;
523+
524+
// Before we do anything, add this operand to our implicit regular user
525+
// list.
526+
foundUses.push_back(op);
527+
528+
// Then update the worklist with new things to find if we recognize this
529+
// inst and then continue. If we fail, we emit an error at the bottom of the
530+
// loop that we didn't recognize the user.
531+
auto *user = op->getUser();
532+
533+
// First, eliminate "end point uses" that we just need to check liveness at
534+
// and do not need to check transitive uses of.
535+
if (isa<LoadInst>(user) || isa<CopyAddrInst>(user) ||
536+
isIncidentalUse(user) || isa<StoreInst>(user) ||
537+
isa<StoreBorrowInst>(user) || isa<PartialApplyInst>(user) ||
538+
isa<DestroyAddrInst>(user) || isa<AssignInst>(user) ||
539+
isa<AddressToPointerInst>(user) || isa<YieldInst>(user) ||
540+
isa<LoadUnownedInst>(user) || isa<StoreUnownedInst>(user) ||
541+
isa<EndApplyInst>(user) || isa<LoadWeakInst>(user) ||
542+
isa<StoreWeakInst>(user) || isa<AssignByWrapperInst>(user) ||
543+
isa<BeginUnpairedAccessInst>(user) ||
544+
isa<EndUnpairedAccessInst>(user) || isa<WitnessMethodInst>(user) ||
545+
isa<SwitchEnumAddrInst>(user) || isa<CheckedCastAddrBranchInst>(user) ||
546+
isa<SelectEnumAddrInst>(user)) {
547+
continue;
548+
}
549+
550+
// Then handle users that we need to look at transitive uses of.
551+
if (Projection::isAddressProjection(user) ||
552+
isa<ProjectBlockStorageInst>(user) ||
553+
isa<OpenExistentialAddrInst>(user) ||
554+
isa<InitExistentialAddrInst>(user) || isa<BeginAccessInst>(user) ||
555+
isa<TailAddrInst>(user) || isa<IndexAddrInst>(user)) {
556+
for (SILValue r : user->getResults()) {
557+
llvm::copy(r->getUses(), std::back_inserter(worklist));
558+
}
559+
continue;
560+
}
561+
562+
if (auto *builtin = dyn_cast<BuiltinInst>(user)) {
563+
if (auto kind = builtin->getBuiltinKind()) {
564+
if (*kind == BuiltinValueKind::TSanInoutAccess) {
565+
continue;
566+
}
567+
}
568+
}
569+
570+
// If we have a load_borrow, add it's end scope to the liveness requirement.
571+
if (auto *lbi = dyn_cast<LoadBorrowInst>(user)) {
572+
transform(lbi->getEndBorrows(), std::back_inserter(foundUses),
573+
[](EndBorrowInst *ebi) { return &ebi->getAllOperands()[0]; });
574+
continue;
575+
}
576+
577+
// TODO: Merge this into the full apply site code below.
578+
if (auto *beginApply = dyn_cast<BeginApplyInst>(user)) {
579+
// TODO: Just add this to implicit regular user list?
580+
llvm::copy(beginApply->getTokenResult()->getUses(),
581+
std::back_inserter(foundUses));
582+
continue;
583+
}
584+
585+
if (auto fas = FullApplySite::isa(user)) {
586+
continue;
587+
}
588+
589+
if (auto *mdi = dyn_cast<MarkDependenceInst>(user)) {
590+
// If this is the base, just treat it as a liveness use.
591+
if (op->get() == mdi->getBase()) {
592+
continue;
593+
}
594+
595+
// If we are the value use, look through it.
596+
llvm::copy(mdi->getValue()->getUses(), std::back_inserter(worklist));
597+
continue;
598+
}
599+
600+
// We were unable to recognize this user, so return true that we failed.
601+
if (onError) {
602+
(*onError)(op);
603+
}
604+
foundError = true;
605+
}
606+
607+
// We were able to recognize all of the uses of the address, so return false
608+
// that we did not find any errors.
609+
return foundError;
610+
}
611+
462612
//===----------------------------------------------------------------------===//
463613
// Owned Value Introducers
464614
//===----------------------------------------------------------------------===//

0 commit comments

Comments
 (0)