Files
swift-mirror/include/swift/SIL/OwnershipUseVisitor.h
Andrew Trick d73a081fe7 Add pointer escape and dependent use tracking to InteriorLiveness
Rig TransitiveAddressWalker to keep track of enough information for passes to
correctly check for pointer escapes and dependence uses. Requires for precise
bail-outs and asserts.
2025-03-02 23:51:34 -08:00

511 lines
17 KiB
C++

//===--- OwnershipUseVisitor.h -------------------------------*- C++ -*----===//
//
// This source file is part of the Swift.org open source project
//
// Copyright (c) 2014 - 2023 Apple Inc. and the Swift project authors
// Licensed under Apache License v2.0 with Runtime Library Exception
//
// See https://swift.org/LICENSE.txt for license information
// See https://swift.org/CONTRIBUTORS.txt for the list of Swift project authors
//
//===----------------------------------------------------------------------===//
///
/// A visitor that classifies the uses of an OSSA value. The main entry points
/// are:
///
/// bool OwnershipUseVisitor::visitLifetimeEndingUses(SILValue ssaDef)
///
/// bool OwnershipUseVisitor::visitInteriorUses(SILValue ssaDef)
///
/// Extensions of the visitor determine how to handle pointer escapes,
/// reborrows, inner borrows, and scoped addresses.
#ifndef SWIFT_SIL_OWNERSHIPUSEVISITOR_H
#define SWIFT_SIL_OWNERSHIPUSEVISITOR_H
#include "swift/SIL/NodeBits.h"
#include "swift/SIL/OwnershipUtils.h"
#include "swift/SIL/ScopedAddressUtils.h"
#include "swift/SIL/SILArgument.h"
#include "swift/SIL/SILBasicBlock.h"
#include "swift/SIL/SILInstruction.h"
#include "swift/SIL/SILValue.h"
namespace swift {
enum class OwnershipUseKind { LifetimeEnding, NonLifetimeEnding };
/// Impl provides:
///
/// - NodeSet visited (if interior uses are visited)
///
/// - 'bool handleUsePoint(Operand *use, UseLifetimeConstraint)'
///
/// Where 'use->get()' is either the original SSA def, or the SILValue that
/// introduces an inner scope for which 'use' ends the scope.
///
/// Impl overrides:
///
/// - 'bool handlePointerEscape(Operand *use)' (default false)
///
/// - bool handleOwnedPhi(Operand *phiOper) { return true; }
///
/// - 'bool handleOuterReborrow(Operand *phiOper)' (default true)
///
/// - 'bool handleGuaranteedForward(Operand *use)' (default true)
///
/// - 'bool handleInnerBorrow(BorrowingOperand)' (default true)
///
/// - 'bool handleScopedAddress(ScopedAddressValue)' (default true)
///
/// These may be overridden with a transformation that adds uses to the use's
/// instruction or phi, but it may not modify the use-list containing \p
/// use or \p phiOper or in any outer scopes.
template <typename Impl>
class OwnershipUseVisitorBase {
protected:
/// If this returns true, then handleUsePoint will be called as if the pointer
/// escape is an instantaneous use. The implementation may decide to track
/// AddressUseKind::PointerEscaping. Bail-out by default for safety.
bool handlePointerEscape(Operand *use) {
return false;
}
/// Handle a owned forwarding phi of the original SSA def. Later, the phi
/// will itself be visited as a use but its scope's uses will not be
/// transitively visited. The implementation may track owned phis to
/// continue processing extended liveness.
///
/// Return true to continue visiting other uses.
bool handleOwnedPhi(Operand *phiOper) { return true; }
/// Handle a transitive reborrow of the original SSA def. Later, the reborrow
/// will itself be visited as a use but its scope's uses will not be
/// transitively visited. The implementation may track outer reborrows to
/// continue processing extended liveness.
///
/// Return true to continue visiting other uses.
///
/// Note: this can lead to infinite recursion if the client does not maintain
/// a visited set.
bool handleOuterReborrow(Operand *phiOper) { return true; }
/// Handle a guaranteed forwarding instruction. After the returns, this will
/// itself be visited as a use, but its uses will not be transitively
/// visited. The implementation may transtitively follow uses or track
/// unenclosed guaranteed phis to insert missing reborrows.
///
/// Return true to continue visiting other uses.
bool handleGuaranteedForwardingPhi(Operand *phiOper) { return true; }
/// If this returns true, then handleUsePoint will be called on the scope
/// ending operands.
///
/// Handles begin_borrow, load_borrow, store_borrow, begin_apply
///
/// Allows the implementation to complete inner scopes before considering
/// their scope ending operations as uses of the outer scope.
bool handleInnerBorrow(BorrowingOperand borrowingOperand) {
return true;
}
/// If this returns true, then handleUsePoint will be called on the scope
/// ending operands.
///
/// Allows the implementation to complete inner scopes before considering
/// their scope ending operations as uses of the outer scope.
///
/// This may add uses to the inner scope, but it may not modify the use-list
/// containing \p scopedAddress or in any outer scopes.
bool handleScopedAddress(ScopedAddressValue scopedAddress) {
return true;
}
};
/// Visit uses relevant to liveness of a single OSSA value.
template <typename Impl>
class OwnershipUseVisitor : OwnershipUseVisitorBase<Impl> {
protected:
Impl &asImpl() { return static_cast<Impl &>(*this); }
bool handleUsePoint(Operand *use, UseLifetimeConstraint useConstraint) {
asImpl().handleUsePoint(use, useConstraint);
return true;
}
public:
/// Assumes that ssaDef's lifetime is complete (linear).
bool visitLifetimeEndingUses(SILValue ssaDef);
/// Does not assume that ssaDef's lifetime is complete.
///
/// If ssaDef is either an owned phi or reborrow, then find inner adjacent
/// phis and treat them just like inner borrow scopes.
bool visitInteriorUses(SILValue ssaDef);
protected:
bool visitConsumes(SILValue ssaDef);
bool visitExtends(SILValue ssaDef);
bool visitOuterBorrow(SILValue borrowBegin);
bool visitOuterBorrowScopeEnd(Operand *borrowEnd);
bool visitInnerBorrow(Operand *borrowingOperand);
bool visitInnerBorrowScopeEnd(Operand *borrowEnd);
bool visitOwnedUse(Operand *use);
bool visitGuaranteedUses(SILValue guaranteedValue) {
for (Operand *use : guaranteedValue->getUses()) {
if (!visitGuaranteedUse(use))
return false;
}
return true;
}
bool visitGuaranteedUse(Operand *use);
bool visitInteriorPointerUses(Operand *use);
};
/// Recursively visit all lifetime-ending uses that contribute to the ownership
/// live range of \p ssaDef. This assumes that ssaDef has a complete
/// lifetime and ignores non-lifetime-ending uses.
template <typename Impl>
bool OwnershipUseVisitor<Impl>::visitLifetimeEndingUses(SILValue ssaDef) {
switch (ssaDef->getOwnershipKind()) {
case OwnershipKind::Owned:
return visitConsumes(ssaDef);
case OwnershipKind::Guaranteed:
return visitOuterBorrow(ssaDef);
case OwnershipKind::None:
if (ssaDef->isFromVarDecl()) {
return visitExtends(ssaDef);
}
LLVM_FALLTHROUGH;
case OwnershipKind::Any:
case OwnershipKind::Unowned:
llvm_unreachable("requires an owned or guaranteed orignalDef");
}
return true;
}
template <typename Impl>
bool OwnershipUseVisitor<Impl>::visitConsumes(SILValue ssaDef) {
for (Operand *use : ssaDef->getUses()) {
// extend_lifetime instructions are non-consuming but need to be visited
// because together with consuming uses they enclose all users of the value.
if (isa<ExtendLifetimeInst>(use->getUser())) {
if (!handleUsePoint(use, UseLifetimeConstraint::NonLifetimeEnding))
return false;
continue;
}
if (use->isConsuming()) {
if (PhiOperand(use) && !asImpl().handleOwnedPhi(use))
return false;
if (!handleUsePoint(use, UseLifetimeConstraint::LifetimeEnding))
return false;
}
}
return true;
}
template <typename Impl>
bool OwnershipUseVisitor<Impl>::visitExtends(SILValue ssaDef) {
for (Operand *use : ssaDef->getUses()) {
if (isa<ExtendLifetimeInst>(use->getUser())) {
if (!handleUsePoint(use, UseLifetimeConstraint::NonLifetimeEnding))
return false;
continue;
}
}
return true;
}
template <typename Impl>
bool OwnershipUseVisitor<Impl>::visitOuterBorrow(SILValue borrowBegin) {
BorrowedValue borrow(borrowBegin);
assert(borrow && "guaranteed values have no lifetime ending uses");
return borrow.visitLocalScopeEndingUses([this](Operand *borrowEnd) {
return visitOuterBorrowScopeEnd(borrowEnd);
});
}
template <typename Impl>
bool OwnershipUseVisitor<Impl>::visitOuterBorrowScopeEnd(Operand *borrowEnd) {
switch (borrowEnd->getOperandOwnership()) {
case OperandOwnership::EndBorrow:
return handleUsePoint(borrowEnd, UseLifetimeConstraint::LifetimeEnding);
case OperandOwnership::Reborrow:
if (!asImpl().handleOuterReborrow(borrowEnd))
return false;
return handleUsePoint(borrowEnd, UseLifetimeConstraint::LifetimeEnding);
case OperandOwnership::InstantaneousUse:
assert(isa<ExtendLifetimeInst>(borrowEnd->getUser()));
return handleUsePoint(borrowEnd, UseLifetimeConstraint::NonLifetimeEnding);
default:
llvm_unreachable("expected borrow scope end");
}
}
/// Visit the lifetime-ending uses of borrow scope
/// (begin_borrow, load_borrow, or begin_apply).
template <typename Impl>
bool OwnershipUseVisitor<Impl>::visitInnerBorrow(Operand *borrowingOperand) {
auto bo = BorrowingOperand(borrowingOperand);
assert(bo && "unexpected Borrow operand ownership");
if (bo.getScopeIntroducingUserResult()) {
if (!asImpl().handleInnerBorrow(borrowingOperand))
return false;
return bo.visitScopeEndingUses(
[&](Operand *borrowEnd) {
return visitInnerBorrowScopeEnd(borrowEnd);
},
[&](Operand *unknownUse) {
return asImpl().handlePointerEscape(unknownUse);
});
}
if (auto dependentValue = bo.getDependentUserResult()) {
switch (dependentValue->getOwnershipKind()) {
case OwnershipKind::Guaranteed:
if (!handleUsePoint(borrowingOperand,
UseLifetimeConstraint::NonLifetimeEnding)) {
return false;
}
return visitGuaranteedUses(dependentValue);
case OwnershipKind::Any:
case OwnershipKind::None:
case OwnershipKind::Owned: // non-escapable
case OwnershipKind::Unowned:
break;
}
}
return asImpl().handlePointerEscape(borrowingOperand);
}
/// Note: borrowEnd->get() may be a borrow introducer for an inner scope, or a
/// borrow scopes that does not introduce a borrowed value (begin_apply).
template <typename Impl>
bool OwnershipUseVisitor<Impl>::visitInnerBorrowScopeEnd(Operand *borrowEnd) {
switch (borrowEnd->getOperandOwnership()) {
case OperandOwnership::EndBorrow:
case OperandOwnership::Reborrow:
return handleUsePoint(borrowEnd, UseLifetimeConstraint::NonLifetimeEnding);
case OperandOwnership::DestroyingConsume:
case OperandOwnership::ForwardingConsume: {
// partial_apply [on_stack] and mark_dependence [nonescaping] can introduce
// borrowing operand and can have destroy_value, return, or store consumes.
//
// TODO: When we have a C++ ForwardingUseDefWalker, walk the use-def
// chain to ensure we have a partial_apply [on_stack] or mark_dependence
// [nonescaping] def.
return handleUsePoint(borrowEnd, UseLifetimeConstraint::NonLifetimeEnding);
}
case OperandOwnership::InstantaneousUse: {
auto builtinUser = dyn_cast<BuiltinInst>(borrowEnd->getUser());
if (builtinUser && builtinUser->getBuiltinKind() ==
BuiltinValueKind::EndAsyncLetLifetime) {
return handleUsePoint(borrowEnd,
UseLifetimeConstraint::NonLifetimeEnding);
}
LLVM_FALLTHROUGH;
}
default:
llvm_unreachable("expected borrow scope end");
}
}
/// Recursively visit all uses that contribute to the ownership live range of \p
/// ssaDef. This does not assume that ssaDef has a complete lifetime
/// and visits non-lifetime-ending uses.
///
/// If ssaDef is a phi (owned or reborrowed), then find its inner adjacent phis
/// and treat them like inner borrows.
template <typename Impl>
bool OwnershipUseVisitor<Impl>::visitInteriorUses(SILValue ssaDef) {
switch (ssaDef->getOwnershipKind()) {
case OwnershipKind::Owned: {
for (Operand *use : ssaDef->getUses()) {
if (!visitOwnedUse(use))
return false;
}
return true;
}
case OwnershipKind::Guaranteed: {
return visitGuaranteedUses(ssaDef);
}
case OwnershipKind::Any:
case OwnershipKind::None:
case OwnershipKind::Unowned:
llvm_unreachable("requires an owned or guaranteed orignalDef");
}
}
template <typename Impl>
bool OwnershipUseVisitor<Impl>::visitOwnedUse(Operand *use) {
switch (use->getOperandOwnership()) {
case OperandOwnership::NonUse:
return true;
case OperandOwnership::ForwardingConsume:
case OperandOwnership::DestroyingConsume:
if (auto phiOper = PhiOperand(use)) {
if (!asImpl().handleOwnedPhi(use))
return false;
}
return handleUsePoint(use, UseLifetimeConstraint::LifetimeEnding);
case OperandOwnership::AnyInteriorPointer:
return visitInteriorPointerUses(use);
case OperandOwnership::PointerEscape:
// TODO: Change ProjectBox ownership to InteriorPointer and allow them to
// take owned values.
if (isa<ProjectBoxInst>(use->getUser())) {
return visitInteriorPointerUses(use);
}
if (!asImpl().handlePointerEscape(use))
return false;
LLVM_FALLTHROUGH;
case OperandOwnership::InstantaneousUse:
case OperandOwnership::ForwardingUnowned:
case OperandOwnership::UnownedInstantaneousUse:
case OperandOwnership::BitwiseEscape:
return handleUsePoint(use, UseLifetimeConstraint::NonLifetimeEnding);
case OperandOwnership::Borrow:
return visitInnerBorrow(use);
// TODO: InteriorPointer should be handled like AnyInteriorPointer.
case OperandOwnership::InteriorPointer:
case OperandOwnership::TrivialUse:
case OperandOwnership::EndBorrow:
case OperandOwnership::Reborrow:
case OperandOwnership::GuaranteedForwarding:
llvm_unreachable("ownership incompatible with an owned value");
}
}
template <typename Impl>
bool OwnershipUseVisitor<Impl>::visitGuaranteedUse(Operand *use) {
switch (use->getOperandOwnership()) {
case OperandOwnership::NonUse:
return true;
case OperandOwnership::PointerEscape:
// TODO: Change ProjectBox ownership to InteriorPointer and allow them to
// take owned values.
if (isa<ProjectBoxInst>(use->getUser())) {
return visitInteriorPointerUses(use);
}
if (!asImpl().handlePointerEscape(use))
return false;
LLVM_FALLTHROUGH;
case OperandOwnership::InstantaneousUse:
case OperandOwnership::ForwardingUnowned:
case OperandOwnership::UnownedInstantaneousUse:
case OperandOwnership::BitwiseEscape:
return handleUsePoint(use, UseLifetimeConstraint::NonLifetimeEnding);
case OperandOwnership::EndBorrow:
return handleUsePoint(use, UseLifetimeConstraint::LifetimeEnding);
case OperandOwnership::Reborrow:
if (!asImpl().handleOuterReborrow(use))
return false;
return handleUsePoint(use, UseLifetimeConstraint::LifetimeEnding);
case OperandOwnership::GuaranteedForwarding:
if (!handleUsePoint(use, UseLifetimeConstraint::NonLifetimeEnding))
return false;
if (PhiOperand(use)) {
return asImpl().handleGuaranteedForwardingPhi(use);
}
if (!asImpl().visited.insert(use->getUser()->asSILNode()))
return true;
return ForwardingOperand(use).visitForwardedValues([&](SILValue result) {
// Do not include transitive uses with 'none' ownership
if (result->getOwnershipKind() != OwnershipKind::None) {
return visitGuaranteedUses(result);
}
return true;
});
case OperandOwnership::Borrow:
return visitInnerBorrow(use);
case OperandOwnership::InteriorPointer:
case OperandOwnership::AnyInteriorPointer:
return visitInteriorPointerUses(use);
case OperandOwnership::TrivialUse:
case OperandOwnership::ForwardingConsume:
case OperandOwnership::DestroyingConsume:
llvm_unreachable("ownership incompatible with a guaranteed value");
}
}
template <typename Impl>
bool OwnershipUseVisitor<Impl>::visitInteriorPointerUses(Operand *use) {
assert(use->getOperandOwnership() == OperandOwnership::InteriorPointer
|| use->getOperandOwnership() == OperandOwnership::AnyInteriorPointer
|| isa<ProjectBoxInst>(use->getUser()));
if (auto scopedAddress = ScopedAddressValue::forUse(use)) {
// e.g. client may need to insert end_borrow if scopedAddress is a store_borrow.
if (!asImpl().handleScopedAddress(scopedAddress))
return false;
return scopedAddress.visitScopeEndingUses([this](Operand *end) {
return handleUsePoint(end, UseLifetimeConstraint::NonLifetimeEnding);
});
}
handleUsePoint(use, UseLifetimeConstraint::NonLifetimeEnding);
auto interiorPtrOp = InteriorPointerOperand(use);
if (interiorPtrOp.getProjectedAddress()->use_empty()) {
// findTransitiveUses reports PointerEscape for a dead interior address. But
// the use-point was already recorded above. So it's safe to simply return.
return true;
}
// TODO: findTransitiveUses should be a visitor so we're not recursively
// allocating use vectors and potentially merging the use points.
//
// TODO: handleInnerBorrow needs to be called for any transitive load_borrow
// uses to ensure their lifetimes are complete. For now, findTransitiveUses
// just assumes that all scopes are incomplete.
SmallVector<Operand *, 8> interiorUses;
auto useKind = InteriorPointerOperand(use).findTransitiveUses(&interiorUses);
if (useKind != AddressUseKind::NonEscaping) {
if (!asImpl().handlePointerEscape(use))
return false;
}
for (auto *interiorUse : interiorUses) {
if (!handleUsePoint(interiorUse, UseLifetimeConstraint::NonLifetimeEnding))
return false;
}
return true;
}
} // namespace swift
#endif