| //===--- SILOwnershipVerifier.cpp -----------------------------------------===// |
| // |
| // This source file is part of the Swift.org open source project |
| // |
| // Copyright (c) 2014 - 2017 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 |
| // |
| //===----------------------------------------------------------------------===// |
| |
| #define DEBUG_TYPE "sil-ownership-verifier" |
| |
| #include "UseOwnershipRequirement.h" |
| #include "swift/AST/ASTContext.h" |
| #include "swift/AST/AnyFunctionRef.h" |
| #include "swift/AST/Decl.h" |
| #include "swift/AST/GenericEnvironment.h" |
| #include "swift/AST/Module.h" |
| #include "swift/AST/Types.h" |
| #include "swift/Basic/Range.h" |
| #include "swift/Basic/STLExtras.h" |
| #include "swift/Basic/TransformArrayRef.h" |
| #include "swift/ClangImporter/ClangModule.h" |
| #include "swift/SIL/BasicBlockUtils.h" |
| #include "swift/SIL/Dominance.h" |
| #include "swift/SIL/DynamicCasts.h" |
| #include "swift/SIL/OwnershipChecker.h" |
| #include "swift/SIL/PrettyStackTrace.h" |
| #include "swift/SIL/Projection.h" |
| #include "swift/SIL/SILBuiltinVisitor.h" |
| #include "swift/SIL/SILDebugScope.h" |
| #include "swift/SIL/SILFunction.h" |
| #include "swift/SIL/SILModule.h" |
| #include "swift/SIL/SILOpenedArchetypesTracker.h" |
| #include "swift/SIL/SILVTable.h" |
| #include "swift/SIL/SILVisitor.h" |
| #include "swift/SIL/TypeLowering.h" |
| #include "llvm/ADT/DenseSet.h" |
| #include "llvm/ADT/PostOrderIterator.h" |
| #include "llvm/ADT/StringSet.h" |
| #include "llvm/Support/CommandLine.h" |
| #include "llvm/Support/Debug.h" |
| #include <algorithm> |
| |
| using namespace swift; |
| |
| // This is an option to put the SILOwnershipVerifier in testing mode. This |
| // causes the following: |
| // |
| // 1. Instead of printing an error message and aborting, the verifier will print |
| // the message and continue. This allows for FileCheck testing of the verifier. |
| // |
| // 2. SILInstruction::verifyOperandOwnership() is disabled. This is used for |
| // verification in SILBuilder. This causes errors to be printed twice, once when |
| // we build the IR and a second time when we perform a full verification of the |
| // IR. For testing purposes, we just want the later. |
| llvm::cl::opt<bool> IsSILOwnershipVerifierTestingEnabled( |
| "sil-ownership-verifier-enable-testing", |
| llvm::cl::desc("Put the sil ownership verifier in testing mode. See " |
| "comment in SILOwnershipVerifier.cpp above option for more " |
| "information.")); |
| |
| //===----------------------------------------------------------------------===// |
| // Generalized User |
| //===----------------------------------------------------------------------===// |
| |
| namespace { |
| |
| /// This is a class that models normal users and also cond_br users that are |
| /// associated with the block in the target block. This is safe to do since in |
| /// Semantic SIL, cond_br with non-trivial arguments are not allowed to have |
| /// critical edges. |
| class GeneralizedUser { |
| using InnerTy = llvm::PointerIntPair<SILInstruction *, 1>; |
| InnerTy User; |
| |
| public: |
| GeneralizedUser(SILInstruction *I) : User(I) { |
| assert(!isa<CondBranchInst>(I)); |
| } |
| |
| GeneralizedUser(CondBranchInst *I) : User(I) {} |
| |
| GeneralizedUser(CondBranchInst *I, unsigned SuccessorIndex) |
| : User(I, SuccessorIndex) { |
| assert(SuccessorIndex == CondBranchInst::TrueIdx || |
| SuccessorIndex == CondBranchInst::FalseIdx); |
| } |
| |
| GeneralizedUser(const GeneralizedUser &Other) : User(Other.User) {} |
| GeneralizedUser &operator=(const GeneralizedUser &Other) { |
| User = Other.User; |
| return *this; |
| } |
| |
| operator SILInstruction *() { return User.getPointer(); } |
| operator const SILInstruction *() const { return User.getPointer(); } |
| |
| SILInstruction *getInst() const { return User.getPointer(); } |
| |
| SILBasicBlock *getParent() const; |
| |
| bool isCondBranchUser() const { |
| return isa<CondBranchInst>(User.getPointer()); |
| } |
| |
| unsigned getCondBranchSuccessorID() const { |
| assert(isCondBranchUser()); |
| return User.getInt(); |
| } |
| |
| SILBasicBlock::iterator getIterator() const { |
| return User.getPointer()->getIterator(); |
| } |
| |
| void *getAsOpaqueValue() const { |
| return llvm::PointerLikeTypeTraits<InnerTy>::getAsVoidPointer(User); |
| } |
| |
| static GeneralizedUser getFromOpaqueValue(void *p) { |
| InnerTy TmpUser = |
| llvm::PointerLikeTypeTraits<InnerTy>::getFromVoidPointer(p); |
| if (auto *CBI = dyn_cast<CondBranchInst>(TmpUser.getPointer())) { |
| return GeneralizedUser(CBI, TmpUser.getInt()); |
| } |
| return GeneralizedUser(TmpUser.getPointer()); |
| } |
| |
| enum { |
| NumLowBitsAvailable = |
| llvm::PointerLikeTypeTraits<InnerTy>::NumLowBitsAvailable |
| }; |
| }; |
| |
| } // end anonymous namespace |
| |
| SILBasicBlock *GeneralizedUser::getParent() const { |
| if (!isCondBranchUser()) { |
| return getInst()->getParent(); |
| } |
| |
| auto *CBI = cast<CondBranchInst>(getInst()); |
| unsigned Number = getCondBranchSuccessorID(); |
| if (Number == CondBranchInst::TrueIdx) |
| return CBI->getTrueBB(); |
| return CBI->getFalseBB(); |
| } |
| |
| namespace llvm { |
| |
| template <> class PointerLikeTypeTraits<GeneralizedUser> { |
| |
| public: |
| static void *getAsVoidPointer(GeneralizedUser v) { |
| return v.getAsOpaqueValue(); |
| } |
| |
| static GeneralizedUser getFromVoidPointer(void *p) { |
| return GeneralizedUser::getFromOpaqueValue(p); |
| } |
| |
| enum { NumLowBitsAvailable = GeneralizedUser::NumLowBitsAvailable }; |
| }; |
| |
| } // namespace llvm |
| |
| //===----------------------------------------------------------------------===// |
| // Utility |
| //===----------------------------------------------------------------------===// |
| |
| static bool compatibleOwnershipKinds(ValueOwnershipKind K1, |
| ValueOwnershipKind K2) { |
| return K1.merge(K2).hasValue(); |
| } |
| |
| /// Returns true if \p Kind is trivial or if \p Kind is compatible with \p |
| /// ComparisonKind. |
| static bool |
| trivialOrCompatibleOwnershipKinds(ValueOwnershipKind Kind, |
| ValueOwnershipKind ComparisonKind) { |
| return compatibleOwnershipKinds(Kind, ValueOwnershipKind::Trivial) || |
| compatibleOwnershipKinds(Kind, ComparisonKind); |
| } |
| |
| static bool isValueAddressOrTrivial(SILValue V, SILModule &M) { |
| return V->getType().isAddress() || |
| V.getOwnershipKind() == ValueOwnershipKind::Trivial || |
| V.getOwnershipKind() == ValueOwnershipKind::Any; |
| } |
| |
| static bool isUnsafeGuaranteedBuiltin(SILInstruction *I) { |
| auto *BI = dyn_cast<BuiltinInst>(I); |
| if (!BI) |
| return false; |
| auto BuiltinKind = BI->getBuiltinKind(); |
| return BuiltinKind == BuiltinValueKind::UnsafeGuaranteed; |
| } |
| |
| // These operations forward both owned and guaranteed ownership. |
| static bool isOwnershipForwardingValueKind(SILNodeKind K) { |
| switch (K) { |
| case SILNodeKind::TupleInst: |
| case SILNodeKind::StructInst: |
| case SILNodeKind::EnumInst: |
| case SILNodeKind::OpenExistentialRefInst: |
| case SILNodeKind::UpcastInst: |
| case SILNodeKind::UncheckedRefCastInst: |
| case SILNodeKind::ConvertFunctionInst: |
| case SILNodeKind::RefToBridgeObjectInst: |
| case SILNodeKind::BridgeObjectToRefInst: |
| case SILNodeKind::UnconditionalCheckedCastInst: |
| case SILNodeKind::UncheckedEnumDataInst: |
| case SILNodeKind::MarkUninitializedInst: |
| case SILNodeKind::SelectEnumInst: |
| case SILNodeKind::SwitchEnumInst: |
| case SILNodeKind::CheckedCastBranchInst: |
| case SILNodeKind::DestructureStructInst: |
| case SILNodeKind::DestructureTupleInst: |
| return true; |
| default: |
| return false; |
| } |
| } |
| |
| // These operations forward guaranteed ownership, but don't necessarily forward |
| // owned values. |
| static bool isGuaranteedForwardingValueKind(SILNodeKind K) { |
| switch (K) { |
| case SILNodeKind::TupleExtractInst: |
| case SILNodeKind::StructExtractInst: |
| case SILNodeKind::OpenExistentialValueInst: |
| case SILNodeKind::OpenExistentialBoxValueInst: |
| return true; |
| default: |
| return isOwnershipForwardingValueKind(K); |
| } |
| } |
| |
| static bool isGuaranteedForwardingValue(SILValue V) { |
| if (auto *SVI = dyn_cast<SingleValueInstruction>(V)) |
| if (isUnsafeGuaranteedBuiltin(SVI)) |
| return true; |
| return isGuaranteedForwardingValueKind( |
| V->getKindOfRepresentativeSILNodeInObject()); |
| } |
| |
| static bool isGuaranteedForwardingInst(SILInstruction *I) { |
| if (isUnsafeGuaranteedBuiltin(I)) |
| return true; |
| return isGuaranteedForwardingValueKind(SILNodeKind(I->getKind())); |
| } |
| |
| static bool isOwnershipForwardingInst(SILInstruction *I) { |
| if (isUnsafeGuaranteedBuiltin(I)) |
| return true; |
| return isOwnershipForwardingValueKind(SILNodeKind(I->getKind())); |
| } |
| |
| //===----------------------------------------------------------------------===// |
| // OwnershipCompatibilityUseChecker |
| //===----------------------------------------------------------------------===// |
| |
| namespace { |
| |
| struct ErrorBehaviorKind { |
| enum inner_t { |
| Invalid = 0, |
| ReturnFalse = 1, |
| PrintMessage = 2, |
| Assert = 4, |
| PrintMessageAndReturnFalse = PrintMessage | ReturnFalse, |
| PrintMessageAndAssert = PrintMessage | Assert, |
| } Value; |
| |
| ErrorBehaviorKind() : Value(Invalid) {} |
| ErrorBehaviorKind(inner_t Inner) : Value(Inner) { assert(Value != Invalid); } |
| |
| bool shouldAssert() const { |
| assert(Value != Invalid); |
| return Value & Assert; |
| } |
| |
| bool shouldPrintMessage() const { |
| assert(Value != Invalid); |
| return Value & PrintMessage; |
| } |
| |
| bool shouldReturnFalse() const { |
| assert(Value != Invalid); |
| return Value & ReturnFalse; |
| } |
| }; |
| |
| struct OwnershipUseCheckerResult { |
| bool HasCompatibleOwnership; |
| bool ShouldCheckForDataflowViolations; |
| |
| OwnershipUseCheckerResult(bool HasCompatibleOwnership, |
| UseLifetimeConstraint OwnershipRequirement) |
| : HasCompatibleOwnership(HasCompatibleOwnership), |
| ShouldCheckForDataflowViolations(bool(OwnershipRequirement)) {} |
| }; |
| |
| class OwnershipCompatibilityUseChecker |
| : public SILInstructionVisitor<OwnershipCompatibilityUseChecker, |
| OwnershipUseCheckerResult> { |
| public: |
| private: |
| SILModule &Mod; |
| const Operand &Op; |
| SILValue BaseValue; |
| ErrorBehaviorKind ErrorBehavior; |
| |
| public: |
| /// Create a new OwnershipCompatibilityUseChecker. |
| /// |
| /// In most cases, one should only pass in \p Op and \p BaseValue will be set |
| /// to Op.get(). In cases where one is trying to verify subobjects, Op.get() |
| /// should be the subobject and Value should be the parent object. An example |
| /// of where one would want to do this is in the case of value projections |
| /// like struct_extract. |
| OwnershipCompatibilityUseChecker(SILModule &M, const Operand &Op, |
| SILValue BaseValue, |
| ErrorBehaviorKind ErrorBehavior) |
| : Mod(M), Op(Op), BaseValue(BaseValue), ErrorBehavior(ErrorBehavior) { |
| assert((BaseValue == Op.get() || |
| BaseValue.getOwnershipKind() == ValueOwnershipKind::Guaranteed) && |
| "Guaranteed values are the only values allowed to have subobject"); |
| // We only support subobjects on objects. |
| assert((BaseValue->getType().isObject() || !isCheckingSubObject()) && |
| "Checking a subobject, but do not have an object base value?!"); |
| } |
| |
| bool isCheckingSubObject() const { return Op.get() != BaseValue; } |
| |
| SILValue getValue() const { return Op.get(); } |
| |
| ValueOwnershipKind getOwnershipKind() const { |
| assert(getValue().getOwnershipKind() == Op.get().getOwnershipKind() && |
| "Expected ownership kind of parent value and operand"); |
| return getValue().getOwnershipKind(); |
| } |
| |
| unsigned getOperandIndex() const { return Op.getOperandNumber(); } |
| |
| SILType getType() const { return Op.get()->getType(); } |
| |
| bool compatibleWithOwnership(ValueOwnershipKind Kind) const { |
| return compatibleOwnershipKinds(getOwnershipKind(), Kind); |
| } |
| |
| bool hasExactOwnership(ValueOwnershipKind Kind) const { |
| return getOwnershipKind() == Kind; |
| } |
| |
| bool isAddressOrTrivialType() const { |
| if (getType().isAddress()) |
| return true; |
| return getOwnershipKind() == ValueOwnershipKind::Trivial || |
| getOwnershipKind() == ValueOwnershipKind::Any; |
| } |
| |
| /// Depending on our initialization, either return false or call Func and |
| /// throw an error. |
| bool handleError(llvm::function_ref<void()> &&MessagePrinterFunc) const { |
| if (ErrorBehavior.shouldPrintMessage()) { |
| MessagePrinterFunc(); |
| } |
| |
| if (ErrorBehavior.shouldReturnFalse()) { |
| return false; |
| } |
| |
| assert(ErrorBehavior.shouldAssert() && "At this point, we should assert"); |
| llvm_unreachable("triggering standard assertion failure routine"); |
| } |
| |
| OwnershipUseCheckerResult visitForwardingInst(SILInstruction *I, |
| ArrayRef<Operand> Ops); |
| OwnershipUseCheckerResult visitForwardingInst(SILInstruction *I) { |
| return visitForwardingInst(I, I->getAllOperands()); |
| } |
| |
| /// Visit a terminator instance that performs a transform like |
| /// operation. E.x.: switch_enum, checked_cast_br. This does not include br or |
| /// cond_br. |
| OwnershipUseCheckerResult visitTransformingTerminatorInst(TermInst *TI); |
| |
| OwnershipUseCheckerResult |
| visitEnumArgument(EnumDecl *E, ValueOwnershipKind RequiredConvention); |
| OwnershipUseCheckerResult |
| visitApplyParameter(ValueOwnershipKind RequiredConvention, |
| UseLifetimeConstraint Requirement); |
| OwnershipUseCheckerResult |
| visitFullApply(FullApplySite apply); |
| |
| /// Check if \p User as compatible ownership with the SILValue that we are |
| /// checking. |
| /// |
| /// \returns true if the user is a use that must be checked for dataflow |
| /// violations. |
| bool check(SILInstruction *User) { |
| auto Result = visit(User); |
| if (!Result.HasCompatibleOwnership) { |
| return handleError([&]() { |
| llvm::errs() << "Function: '" << User->getFunction()->getName() << "'\n" |
| << "Have operand with incompatible ownership?!\n" |
| << "Value: " << *getValue() << "BaseValue: " << *BaseValue |
| << "User: " << *User << "Conv: " << getOwnershipKind() |
| << "\n\n"; |
| }); |
| } |
| |
| assert((!Result.ShouldCheckForDataflowViolations || |
| !isAddressOrTrivialType()) && |
| "Address or trivial types should never be checked for dataflow " |
| "violations"); |
| |
| return Result.ShouldCheckForDataflowViolations; |
| } |
| |
| OwnershipUseCheckerResult visitCallee(CanSILFunctionType SubstCalleeType); |
| OwnershipUseCheckerResult |
| checkTerminatorArgumentMatchesDestBB(SILBasicBlock *DestBB, unsigned OpIndex); |
| |
| // Create declarations for all instructions, so we get a warning at compile |
| // time if any instructions do not have an implementation. |
| #define INST(Id, Parent) \ |
| OwnershipUseCheckerResult visit##Id(Id *); |
| #include "swift/SIL/SILNodes.def" |
| }; |
| |
| } // end anonymous namespace |
| |
| /// Implementation for instructions without operands. These should never be |
| /// visited. |
| #define NO_OPERAND_INST(INST) \ |
| OwnershipUseCheckerResult \ |
| OwnershipCompatibilityUseChecker::visit##INST##Inst(INST##Inst *I) { \ |
| assert(I->getNumOperands() == 0 && \ |
| "Expected instruction without operands?!"); \ |
| llvm_unreachable("Instruction without operand can not be compatible with " \ |
| "any def's OwnershipValueKind"); \ |
| } |
| NO_OPERAND_INST(AllocBox) |
| NO_OPERAND_INST(AllocExistentialBox) |
| NO_OPERAND_INST(AllocGlobal) |
| NO_OPERAND_INST(AllocStack) |
| NO_OPERAND_INST(FloatLiteral) |
| NO_OPERAND_INST(FunctionRef) |
| NO_OPERAND_INST(GlobalAddr) |
| NO_OPERAND_INST(GlobalValue) |
| NO_OPERAND_INST(IntegerLiteral) |
| NO_OPERAND_INST(Metatype) |
| NO_OPERAND_INST(ObjCProtocol) |
| NO_OPERAND_INST(RetainValue) |
| NO_OPERAND_INST(RetainValueAddr) |
| NO_OPERAND_INST(StringLiteral) |
| NO_OPERAND_INST(ConstStringLiteral) |
| NO_OPERAND_INST(StrongRetain) |
| NO_OPERAND_INST(StrongRetainUnowned) |
| NO_OPERAND_INST(UnownedRetain) |
| NO_OPERAND_INST(Unreachable) |
| NO_OPERAND_INST(Unwind) |
| #undef NO_OPERAND_INST |
| |
| /// Instructions whose arguments are always compatible with one convention. |
| #define CONSTANT_OWNERSHIP_INST(OWNERSHIP, USE_LIFETIME_CONSTRAINT, INST) \ |
| OwnershipUseCheckerResult \ |
| OwnershipCompatibilityUseChecker::visit##INST##Inst(INST##Inst *I) { \ |
| assert(I->getNumOperands() && "Expected to have non-zero operands"); \ |
| if (ValueOwnershipKind::OWNERSHIP == ValueOwnershipKind::Trivial) { \ |
| assert(isAddressOrTrivialType() && \ |
| "Trivial ownership requires a trivial type or an address"); \ |
| } \ |
| \ |
| return {compatibleWithOwnership(ValueOwnershipKind::OWNERSHIP), \ |
| UseLifetimeConstraint::USE_LIFETIME_CONSTRAINT}; \ |
| } |
| CONSTANT_OWNERSHIP_INST(Guaranteed, MustBeLive, RefElementAddr) |
| CONSTANT_OWNERSHIP_INST(Guaranteed, MustBeLive, OpenExistentialValue) |
| CONSTANT_OWNERSHIP_INST(Guaranteed, MustBeLive, OpenExistentialBoxValue) |
| CONSTANT_OWNERSHIP_INST(Owned, MustBeInvalidated, AutoreleaseValue) |
| CONSTANT_OWNERSHIP_INST(Owned, MustBeInvalidated, DeallocBox) |
| CONSTANT_OWNERSHIP_INST(Owned, MustBeInvalidated, DeallocExistentialBox) |
| CONSTANT_OWNERSHIP_INST(Owned, MustBeInvalidated, DeallocRef) |
| CONSTANT_OWNERSHIP_INST(Owned, MustBeInvalidated, DestroyValue) |
| CONSTANT_OWNERSHIP_INST(Owned, MustBeInvalidated, ReleaseValue) |
| CONSTANT_OWNERSHIP_INST(Owned, MustBeInvalidated, ReleaseValueAddr) |
| CONSTANT_OWNERSHIP_INST(Owned, MustBeInvalidated, StrongRelease) |
| CONSTANT_OWNERSHIP_INST(Owned, MustBeInvalidated, StrongUnpin) |
| CONSTANT_OWNERSHIP_INST(Owned, MustBeInvalidated, UnownedRelease) |
| CONSTANT_OWNERSHIP_INST(Owned, MustBeInvalidated, InitExistentialRef) |
| CONSTANT_OWNERSHIP_INST(Owned, MustBeInvalidated, EndLifetime) |
| CONSTANT_OWNERSHIP_INST(Trivial, MustBeLive, AbortApply) |
| CONSTANT_OWNERSHIP_INST(Trivial, MustBeLive, AddressToPointer) |
| CONSTANT_OWNERSHIP_INST(Trivial, MustBeLive, BeginAccess) |
| CONSTANT_OWNERSHIP_INST(Trivial, MustBeLive, BeginUnpairedAccess) |
| CONSTANT_OWNERSHIP_INST(Trivial, MustBeLive, BindMemory) |
| CONSTANT_OWNERSHIP_INST(Trivial, MustBeLive, CheckedCastAddrBranch) |
| CONSTANT_OWNERSHIP_INST(Trivial, MustBeLive, CondFail) |
| CONSTANT_OWNERSHIP_INST(Trivial, MustBeLive, CopyAddr) |
| CONSTANT_OWNERSHIP_INST(Trivial, MustBeLive, DeallocStack) |
| CONSTANT_OWNERSHIP_INST(Trivial, MustBeLive, DebugValueAddr) |
| CONSTANT_OWNERSHIP_INST(Trivial, MustBeLive, DeinitExistentialAddr) |
| CONSTANT_OWNERSHIP_INST(Trivial, MustBeLive, DestroyAddr) |
| CONSTANT_OWNERSHIP_INST(Trivial, MustBeLive, EndAccess) |
| CONSTANT_OWNERSHIP_INST(Trivial, MustBeLive, EndApply) |
| CONSTANT_OWNERSHIP_INST(Trivial, MustBeLive, EndUnpairedAccess) |
| CONSTANT_OWNERSHIP_INST(Trivial, MustBeLive, IndexAddr) |
| CONSTANT_OWNERSHIP_INST(Trivial, MustBeLive, IndexRawPointer) |
| CONSTANT_OWNERSHIP_INST(Trivial, MustBeLive, InitBlockStorageHeader) |
| CONSTANT_OWNERSHIP_INST(Trivial, MustBeLive, InitEnumDataAddr) |
| CONSTANT_OWNERSHIP_INST(Trivial, MustBeLive, InitExistentialAddr) |
| CONSTANT_OWNERSHIP_INST(Trivial, MustBeLive, InitExistentialMetatype) |
| CONSTANT_OWNERSHIP_INST(Trivial, MustBeLive, InjectEnumAddr) |
| CONSTANT_OWNERSHIP_INST(Trivial, MustBeLive, IsUnique) |
| CONSTANT_OWNERSHIP_INST(Trivial, MustBeLive, IsUniqueOrPinned) |
| CONSTANT_OWNERSHIP_INST(Trivial, MustBeLive, Load) |
| CONSTANT_OWNERSHIP_INST(Trivial, MustBeLive, LoadBorrow) |
| CONSTANT_OWNERSHIP_INST(Trivial, MustBeLive, LoadUnowned) |
| CONSTANT_OWNERSHIP_INST(Trivial, MustBeLive, LoadWeak) |
| CONSTANT_OWNERSHIP_INST(Trivial, MustBeLive, MarkFunctionEscape) |
| CONSTANT_OWNERSHIP_INST(Trivial, MustBeLive, MarkUninitializedBehavior) |
| CONSTANT_OWNERSHIP_INST(Trivial, MustBeLive, ObjCExistentialMetatypeToObject) |
| CONSTANT_OWNERSHIP_INST(Trivial, MustBeLive, ObjCMetatypeToObject) |
| CONSTANT_OWNERSHIP_INST(Trivial, MustBeLive, ObjCToThickMetatype) |
| CONSTANT_OWNERSHIP_INST(Trivial, MustBeLive, OpenExistentialAddr) |
| CONSTANT_OWNERSHIP_INST(Trivial, MustBeLive, OpenExistentialMetatype) |
| CONSTANT_OWNERSHIP_INST(Trivial, MustBeLive, PointerToAddress) |
| CONSTANT_OWNERSHIP_INST(Trivial, MustBeLive, PointerToThinFunction) |
| CONSTANT_OWNERSHIP_INST(Trivial, MustBeLive, ProjectBlockStorage) |
| CONSTANT_OWNERSHIP_INST(Trivial, MustBeLive, ProjectValueBuffer) |
| CONSTANT_OWNERSHIP_INST(Trivial, MustBeLive, RawPointerToRef) |
| CONSTANT_OWNERSHIP_INST(Trivial, MustBeLive, SelectEnumAddr) |
| CONSTANT_OWNERSHIP_INST(Trivial, MustBeLive, SelectValue) |
| CONSTANT_OWNERSHIP_INST(Trivial, MustBeLive, StructElementAddr) |
| CONSTANT_OWNERSHIP_INST(Trivial, MustBeLive, SwitchEnumAddr) |
| CONSTANT_OWNERSHIP_INST(Trivial, MustBeLive, SwitchValue) |
| CONSTANT_OWNERSHIP_INST(Trivial, MustBeLive, TailAddr) |
| CONSTANT_OWNERSHIP_INST(Trivial, MustBeLive, ThickToObjCMetatype) |
| CONSTANT_OWNERSHIP_INST(Trivial, MustBeLive, ThinFunctionToPointer) |
| CONSTANT_OWNERSHIP_INST(Trivial, MustBeLive, ThinToThickFunction) |
| CONSTANT_OWNERSHIP_INST(Trivial, MustBeLive, TupleElementAddr) |
| CONSTANT_OWNERSHIP_INST(Trivial, MustBeLive, UncheckedAddrCast) |
| CONSTANT_OWNERSHIP_INST(Trivial, MustBeLive, UncheckedRefCastAddr) |
| CONSTANT_OWNERSHIP_INST(Trivial, MustBeLive, UncheckedTakeEnumDataAddr) |
| CONSTANT_OWNERSHIP_INST(Trivial, MustBeLive, UnconditionalCheckedCastAddr) |
| CONSTANT_OWNERSHIP_INST(Trivial, MustBeLive, UnmanagedToRef) |
| CONSTANT_OWNERSHIP_INST(Trivial, MustBeLive, AllocValueBuffer) |
| CONSTANT_OWNERSHIP_INST(Trivial, MustBeLive, DeallocValueBuffer) |
| #undef CONSTANT_OWNERSHIP_INST |
| |
| /// Instructions whose arguments are always compatible with one convention. |
| #define CONSTANT_OR_TRIVIAL_OWNERSHIP_INST(OWNERSHIP, USE_LIFETIME_CONSTRAINT, \ |
| INST) \ |
| OwnershipUseCheckerResult \ |
| OwnershipCompatibilityUseChecker::visit##INST##Inst(INST##Inst *I) { \ |
| assert(I->getNumOperands() && "Expected to have non-zero operands"); \ |
| if (ValueOwnershipKind::OWNERSHIP == ValueOwnershipKind::Trivial) { \ |
| assert(isAddressOrTrivialType() && \ |
| "Trivial ownership requires a trivial type or an address"); \ |
| } \ |
| \ |
| if (compatibleWithOwnership(ValueOwnershipKind::Trivial)) { \ |
| return {true, UseLifetimeConstraint::MustBeLive}; \ |
| } \ |
| return {compatibleWithOwnership(ValueOwnershipKind::OWNERSHIP), \ |
| UseLifetimeConstraint::USE_LIFETIME_CONSTRAINT}; \ |
| } |
| CONSTANT_OR_TRIVIAL_OWNERSHIP_INST(Owned, MustBeInvalidated, |
| CheckedCastValueBranch) |
| CONSTANT_OR_TRIVIAL_OWNERSHIP_INST(Owned, MustBeInvalidated, |
| UnconditionalCheckedCastValue) |
| CONSTANT_OR_TRIVIAL_OWNERSHIP_INST(Owned, MustBeInvalidated, |
| InitExistentialValue) |
| CONSTANT_OR_TRIVIAL_OWNERSHIP_INST(Owned, MustBeInvalidated, |
| DeinitExistentialValue) |
| #undef CONSTANT_OR_TRIVIAL_OWNERSHIP_INST |
| |
| #define ACCEPTS_ANY_OWNERSHIP_INST(INST) \ |
| OwnershipUseCheckerResult \ |
| OwnershipCompatibilityUseChecker::visit##INST##Inst(INST##Inst *I) { \ |
| return {true, UseLifetimeConstraint::MustBeLive}; \ |
| } |
| ACCEPTS_ANY_OWNERSHIP_INST(BeginBorrow) |
| ACCEPTS_ANY_OWNERSHIP_INST(CopyValue) |
| ACCEPTS_ANY_OWNERSHIP_INST(DebugValue) |
| ACCEPTS_ANY_OWNERSHIP_INST(FixLifetime) |
| ACCEPTS_ANY_OWNERSHIP_INST(UncheckedBitwiseCast) // Is this right? |
| ACCEPTS_ANY_OWNERSHIP_INST(WitnessMethod) // Is this right? |
| ACCEPTS_ANY_OWNERSHIP_INST(ProjectBox) // The result is a T*. |
| ACCEPTS_ANY_OWNERSHIP_INST(DynamicMethodBranch) |
| ACCEPTS_ANY_OWNERSHIP_INST(UncheckedTrivialBitCast) |
| ACCEPTS_ANY_OWNERSHIP_INST(ExistentialMetatype) |
| ACCEPTS_ANY_OWNERSHIP_INST(ValueMetatype) |
| ACCEPTS_ANY_OWNERSHIP_INST(UncheckedOwnershipConversion) |
| #undef ACCEPTS_ANY_OWNERSHIP_INST |
| |
| // Trivial if trivial typed, otherwise must accept owned? |
| #define ACCEPTS_ANY_NONTRIVIAL_OWNERSHIP_OR_METATYPE(USE_LIFETIME_CONSTRAINT, \ |
| INST) \ |
| OwnershipUseCheckerResult \ |
| OwnershipCompatibilityUseChecker::visit##INST##Inst(INST##Inst *I) { \ |
| assert(I->getNumOperands() && "Expected to have non-zero operands"); \ |
| if (getType().is<AnyMetatypeType>()) { \ |
| return {true, UseLifetimeConstraint::MustBeLive}; \ |
| } \ |
| bool compatible = hasExactOwnership(ValueOwnershipKind::Any) || \ |
| !compatibleWithOwnership(ValueOwnershipKind::Trivial); \ |
| return {compatible, UseLifetimeConstraint::USE_LIFETIME_CONSTRAINT}; \ |
| } |
| ACCEPTS_ANY_NONTRIVIAL_OWNERSHIP_OR_METATYPE(MustBeLive, ClassMethod) |
| ACCEPTS_ANY_NONTRIVIAL_OWNERSHIP_OR_METATYPE(MustBeLive, ObjCMethod) |
| ACCEPTS_ANY_NONTRIVIAL_OWNERSHIP_OR_METATYPE(MustBeLive, ObjCSuperMethod) |
| #undef ACCEPTS_ANY_NONTRIVIAL_OWNERSHIP_OR_METATYPE |
| |
| // Trivial if trivial typed, otherwise must accept owned? |
| #define ACCEPTS_ANY_NONTRIVIAL_OWNERSHIP(USE_LIFETIME_CONSTRAINT, INST) \ |
| OwnershipUseCheckerResult \ |
| OwnershipCompatibilityUseChecker::visit##INST##Inst(INST##Inst *I) { \ |
| assert(I->getNumOperands() && "Expected to have non-zero operands"); \ |
| bool compatible = hasExactOwnership(ValueOwnershipKind::Any) || \ |
| !compatibleWithOwnership(ValueOwnershipKind::Trivial); \ |
| return {compatible, UseLifetimeConstraint::USE_LIFETIME_CONSTRAINT}; \ |
| } |
| ACCEPTS_ANY_NONTRIVIAL_OWNERSHIP(MustBeLive, SuperMethod) |
| ACCEPTS_ANY_NONTRIVIAL_OWNERSHIP(MustBeLive, BridgeObjectToWord) |
| ACCEPTS_ANY_NONTRIVIAL_OWNERSHIP(MustBeLive, CopyBlock) |
| ACCEPTS_ANY_NONTRIVIAL_OWNERSHIP(MustBeLive, OpenExistentialBox) |
| ACCEPTS_ANY_NONTRIVIAL_OWNERSHIP(MustBeLive, RefTailAddr) |
| ACCEPTS_ANY_NONTRIVIAL_OWNERSHIP(MustBeLive, RefToRawPointer) |
| ACCEPTS_ANY_NONTRIVIAL_OWNERSHIP(MustBeLive, RefToUnmanaged) |
| ACCEPTS_ANY_NONTRIVIAL_OWNERSHIP(MustBeLive, RefToUnowned) |
| ACCEPTS_ANY_NONTRIVIAL_OWNERSHIP(MustBeLive, SetDeallocating) |
| ACCEPTS_ANY_NONTRIVIAL_OWNERSHIP(MustBeLive, StrongPin) |
| ACCEPTS_ANY_NONTRIVIAL_OWNERSHIP(MustBeLive, UnownedToRef) |
| ACCEPTS_ANY_NONTRIVIAL_OWNERSHIP(MustBeLive, CopyUnownedValue) |
| ACCEPTS_ANY_NONTRIVIAL_OWNERSHIP(MustBeLive, ProjectExistentialBox) |
| ACCEPTS_ANY_NONTRIVIAL_OWNERSHIP(MustBeLive, UnmanagedRetainValue) |
| ACCEPTS_ANY_NONTRIVIAL_OWNERSHIP(MustBeLive, UnmanagedReleaseValue) |
| ACCEPTS_ANY_NONTRIVIAL_OWNERSHIP(MustBeLive, UnmanagedAutoreleaseValue) |
| #undef ACCEPTS_ANY_NONTRIVIAL_OWNERSHIP |
| |
| OwnershipUseCheckerResult |
| OwnershipCompatibilityUseChecker::visitForwardingInst(SILInstruction *I, ArrayRef<Operand> Ops) { |
| assert(I->getNumOperands() && "Expected to have non-zero operands"); |
| assert(isOwnershipForwardingInst(I) && |
| "Expected to have an ownership forwarding inst"); |
| |
| // Find the first index where we have a trivial value. |
| auto Iter = find_if(Ops, [&I](const Operand &Op) -> bool { |
| if (I->isTypeDependentOperand(Op)) |
| return false; |
| return Op.get().getOwnershipKind() != ValueOwnershipKind::Trivial; |
| }); |
| |
| // All trivial. |
| if (Iter == Ops.end()) { |
| return {compatibleWithOwnership(ValueOwnershipKind::Trivial), |
| UseLifetimeConstraint::MustBeLive}; |
| } |
| |
| unsigned Index = std::distance(Ops.begin(), Iter); |
| ValueOwnershipKind Base = Ops[Index].get().getOwnershipKind(); |
| |
| for (const Operand &Op : Ops.slice(Index + 1)) { |
| if (I->isTypeDependentOperand(Op)) |
| continue; |
| auto OpKind = Op.get().getOwnershipKind(); |
| if (OpKind.merge(ValueOwnershipKind::Trivial)) |
| continue; |
| |
| auto MergedValue = Base.merge(OpKind.Value); |
| if (!MergedValue.hasValue()) { |
| return {false, UseLifetimeConstraint::MustBeInvalidated}; |
| } |
| Base = MergedValue.getValue(); |
| } |
| |
| // We only need to treat a forwarded instruction as a lifetime ending use of |
| // it is owned. |
| auto lifetimeConstraint = hasExactOwnership(ValueOwnershipKind::Owned) |
| ? UseLifetimeConstraint::MustBeInvalidated |
| : UseLifetimeConstraint::MustBeLive; |
| return {true, lifetimeConstraint}; |
| } |
| |
| #define FORWARD_ANY_OWNERSHIP_INST(INST) \ |
| OwnershipUseCheckerResult \ |
| OwnershipCompatibilityUseChecker::visit##INST##Inst(INST##Inst *I) { \ |
| return visitForwardingInst(I); \ |
| } |
| FORWARD_ANY_OWNERSHIP_INST(Tuple) |
| FORWARD_ANY_OWNERSHIP_INST(Struct) |
| FORWARD_ANY_OWNERSHIP_INST(Object) |
| FORWARD_ANY_OWNERSHIP_INST(Enum) |
| FORWARD_ANY_OWNERSHIP_INST(OpenExistentialRef) |
| FORWARD_ANY_OWNERSHIP_INST(Upcast) |
| FORWARD_ANY_OWNERSHIP_INST(UncheckedRefCast) |
| FORWARD_ANY_OWNERSHIP_INST(ConvertFunction) |
| FORWARD_ANY_OWNERSHIP_INST(RefToBridgeObject) |
| FORWARD_ANY_OWNERSHIP_INST(BridgeObjectToRef) |
| FORWARD_ANY_OWNERSHIP_INST(UnconditionalCheckedCast) |
| FORWARD_ANY_OWNERSHIP_INST(MarkUninitialized) |
| FORWARD_ANY_OWNERSHIP_INST(UncheckedEnumData) |
| FORWARD_ANY_OWNERSHIP_INST(DestructureStruct) |
| FORWARD_ANY_OWNERSHIP_INST(DestructureTuple) |
| #undef FORWARD_ANY_OWNERSHIP_INST |
| |
| // An instruction that forwards a constant ownership or trivial ownership. |
| #define FORWARD_CONSTANT_OR_TRIVIAL_OWNERSHIP_INST( \ |
| OWNERSHIP, USE_LIFETIME_CONSTRAINT, INST) \ |
| OwnershipUseCheckerResult \ |
| OwnershipCompatibilityUseChecker::visit##INST##Inst(INST##Inst *I) { \ |
| assert(I->getNumOperands() && "Expected to have non-zero operands"); \ |
| assert(isGuaranteedForwardingInst(I) && \ |
| "Expected an ownership forwarding inst"); \ |
| if (ValueOwnershipKind::OWNERSHIP != ValueOwnershipKind::Trivial && \ |
| hasExactOwnership(ValueOwnershipKind::Trivial)) { \ |
| assert(isAddressOrTrivialType() && \ |
| "Trivial ownership requires a trivial type or an address"); \ |
| return {true, UseLifetimeConstraint::MustBeLive}; \ |
| } \ |
| if (ValueOwnershipKind::OWNERSHIP == ValueOwnershipKind::Trivial) { \ |
| assert(isAddressOrTrivialType() && \ |
| "Trivial ownership requires a trivial type or an address"); \ |
| } \ |
| \ |
| return {compatibleWithOwnership(ValueOwnershipKind::OWNERSHIP), \ |
| UseLifetimeConstraint::USE_LIFETIME_CONSTRAINT}; \ |
| } |
| FORWARD_CONSTANT_OR_TRIVIAL_OWNERSHIP_INST(Guaranteed, MustBeLive, TupleExtract) |
| FORWARD_CONSTANT_OR_TRIVIAL_OWNERSHIP_INST(Guaranteed, MustBeLive, |
| StructExtract) |
| #undef CONSTANT_OR_TRIVIAL_OWNERSHIP_INST |
| |
| OwnershipUseCheckerResult |
| OwnershipCompatibilityUseChecker::visitDeallocPartialRefInst( |
| DeallocPartialRefInst *I) { |
| if (getValue() == I->getInstance()) { |
| return {compatibleWithOwnership(ValueOwnershipKind::Owned), |
| UseLifetimeConstraint::MustBeInvalidated}; |
| } |
| |
| return {compatibleWithOwnership(ValueOwnershipKind::Trivial), |
| UseLifetimeConstraint::MustBeLive}; |
| } |
| |
| OwnershipUseCheckerResult |
| OwnershipCompatibilityUseChecker::visitEndBorrowArgumentInst( |
| EndBorrowArgumentInst *I) { |
| // If we are currently checking an end_borrow_argument as a subobject, then we |
| // treat this as just a use. |
| if (isCheckingSubObject()) |
| return {true, UseLifetimeConstraint::MustBeLive}; |
| |
| // Otherwise, we must be checking an actual argument. Make sure it is guaranteed! |
| auto lifetimeConstraint = hasExactOwnership(ValueOwnershipKind::Guaranteed) |
| ? UseLifetimeConstraint::MustBeInvalidated |
| : UseLifetimeConstraint::MustBeLive; |
| return {true, lifetimeConstraint}; |
| } |
| |
| OwnershipUseCheckerResult |
| OwnershipCompatibilityUseChecker::visitSelectEnumInst(SelectEnumInst *I) { |
| if (getValue() == I->getEnumOperand()) { |
| return {true, UseLifetimeConstraint::MustBeLive}; |
| } |
| |
| return visitForwardingInst(I, I->getAllOperands().drop_front()); |
| } |
| |
| OwnershipUseCheckerResult |
| OwnershipCompatibilityUseChecker::visitAllocRefInst(AllocRefInst *I) { |
| assert(I->getNumOperands() != 0 |
| && "If we reach this point, we must have a tail operand"); |
| return {compatibleWithOwnership(ValueOwnershipKind::Trivial), |
| UseLifetimeConstraint::MustBeLive}; |
| } |
| |
| OwnershipUseCheckerResult |
| OwnershipCompatibilityUseChecker::visitAllocRefDynamicInst( |
| AllocRefDynamicInst *I) { |
| assert(I->getNumOperands() != 0 && |
| "If we reach this point, we must have a tail operand"); |
| return {compatibleWithOwnership(ValueOwnershipKind::Trivial), |
| UseLifetimeConstraint::MustBeLive}; |
| } |
| |
| OwnershipUseCheckerResult |
| OwnershipCompatibilityUseChecker::checkTerminatorArgumentMatchesDestBB( |
| SILBasicBlock *DestBB, unsigned OpIndex) { |
| // Grab the ownership kind of the destination block. |
| ValueOwnershipKind DestBlockArgOwnershipKind = |
| DestBB->getArgument(OpIndex)->getOwnershipKind(); |
| |
| // Then if we do not have an enum, make sure that the conventions match. |
| EnumDecl *E = getType().getEnumOrBoundGenericEnum(); |
| if (!E) { |
| bool matches = compatibleWithOwnership(DestBlockArgOwnershipKind); |
| auto lifetimeConstraint = hasExactOwnership(ValueOwnershipKind::Owned) |
| ? UseLifetimeConstraint::MustBeInvalidated |
| : UseLifetimeConstraint::MustBeLive; |
| return {matches, lifetimeConstraint}; |
| } |
| |
| return visitEnumArgument(E, DestBlockArgOwnershipKind); |
| } |
| |
| OwnershipUseCheckerResult |
| OwnershipCompatibilityUseChecker::visitBranchInst(BranchInst *BI) { |
| return checkTerminatorArgumentMatchesDestBB(BI->getDestBB(), |
| getOperandIndex()); |
| } |
| |
| OwnershipUseCheckerResult |
| OwnershipCompatibilityUseChecker::visitCondBranchInst(CondBranchInst *CBI) { |
| // If our conditional branch is the condition, it is trivial. Check that the |
| // ownership kind is trivial. |
| if (CBI->isConditionOperandIndex(getOperandIndex())) |
| return {compatibleWithOwnership(ValueOwnershipKind::Trivial), |
| UseLifetimeConstraint::MustBeLive}; |
| |
| // Otherwise, make sure that our operand matches the |
| if (CBI->isTrueOperandIndex(getOperandIndex())) { |
| unsigned TrueOffset = 1; |
| return checkTerminatorArgumentMatchesDestBB(CBI->getTrueBB(), |
| getOperandIndex() - TrueOffset); |
| } |
| |
| assert(CBI->isFalseOperandIndex(getOperandIndex()) && |
| "If an operand is not the condition index or a true operand index, it " |
| "must be a false operand index"); |
| unsigned FalseOffset = 1 + CBI->getTrueOperands().size(); |
| return checkTerminatorArgumentMatchesDestBB(CBI->getFalseBB(), |
| getOperandIndex() - FalseOffset); |
| } |
| |
| OwnershipUseCheckerResult |
| OwnershipCompatibilityUseChecker::visitSwitchEnumInst(SwitchEnumInst *SEI) { |
| return visitTransformingTerminatorInst(SEI); |
| } |
| |
| OwnershipUseCheckerResult |
| OwnershipCompatibilityUseChecker::visitCheckedCastBranchInst( |
| CheckedCastBranchInst *SEI) { |
| return visitTransformingTerminatorInst(SEI); |
| } |
| |
| OwnershipUseCheckerResult |
| OwnershipCompatibilityUseChecker::visitTransformingTerminatorInst( |
| TermInst *TI) { |
| // If our operand was trivial, return early. |
| if (compatibleWithOwnership(ValueOwnershipKind::Trivial)) |
| return {true, UseLifetimeConstraint::MustBeLive}; |
| |
| // Then we need to go through all of our destinations and make sure that if |
| // they have a payload, the payload's convention matches our |
| // convention. |
| // |
| // *NOTE* we assume that all of our types line up and are checked by the |
| // normal verifier. |
| for (auto *Succ : TI->getParent()->getSuccessorBlocks()) { |
| // This must be a no-payload case... continue. |
| if (Succ->args_size() == 0) |
| continue; |
| |
| // If we have a trivial value or a value with ownership kind that matches |
| // the switch_enum, then continue. |
| auto OwnershipKind = Succ->getArgument(0)->getOwnershipKind(); |
| if (OwnershipKind == ValueOwnershipKind::Trivial || |
| compatibleWithOwnership(OwnershipKind)) |
| continue; |
| |
| // Otherwise, emit an error. |
| handleError([&]() { |
| llvm::errs() |
| << "Function: '" << Succ->getParent()->getName() << "'\n" |
| << "Error! Argument ownership kind does not match terminator!\n" |
| << "Terminator: " << *TI << "Argument: " << *Succ->getArgument(0) |
| << "Expected convention: " << getOwnershipKind() << ".\n" |
| << "Actual convention: " << OwnershipKind << '\n' |
| << '\n'; |
| }); |
| } |
| |
| // Finally, if everything lines up, emit that we match and are a lifetime |
| // ending point if we are owned. |
| auto lifetimeConstraint = hasExactOwnership(ValueOwnershipKind::Owned) |
| ? UseLifetimeConstraint::MustBeInvalidated |
| : UseLifetimeConstraint::MustBeLive; |
| return {true, lifetimeConstraint}; |
| } |
| |
| OwnershipUseCheckerResult |
| OwnershipCompatibilityUseChecker::visitReturnInst(ReturnInst *RI) { |
| SILModule &M = RI->getModule(); |
| bool IsTrivial = RI->getOperand()->getType().isTrivial(M); |
| SILFunctionConventions fnConv = RI->getFunction()->getConventions(); |
| auto Results = fnConv.getDirectSILResults(); |
| if (Results.empty() || IsTrivial) { |
| return {compatibleWithOwnership(ValueOwnershipKind::Trivial), |
| UseLifetimeConstraint::MustBeLive}; |
| } |
| |
| CanGenericSignature Sig = fnConv.funcTy->getGenericSignature(); |
| |
| // Find the first index where we have a trivial value. |
| auto Iter = find_if(Results, [&M, &Sig](const SILResultInfo &Info) -> bool { |
| return Info.getOwnershipKind(M, Sig) != ValueOwnershipKind::Trivial; |
| }); |
| |
| // If we have all trivial, then we must be trivial. Why wasn't our original |
| // type trivial? This is a hard error since this is a logic error in our code |
| // here. |
| if (Iter == Results.end()) |
| llvm_unreachable("Should have already checked a trivial type?!"); |
| |
| ValueOwnershipKind Base = Iter->getOwnershipKind(M, Sig); |
| |
| for (const SILResultInfo &ResultInfo : |
| SILFunctionConventions::DirectSILResultRange(std::next(Iter), |
| Results.end())) { |
| auto RKind = ResultInfo.getOwnershipKind(M, Sig); |
| // Ignore trivial types. |
| if (RKind.merge(ValueOwnershipKind::Trivial)) |
| continue; |
| |
| auto MergedValue = Base.merge(RKind); |
| // If we fail to merge all types in, bail. We can not come up with a proper |
| // result type. |
| if (!MergedValue.hasValue()) { |
| return {false, UseLifetimeConstraint::MustBeLive}; |
| } |
| // In case Base is Any. |
| Base = MergedValue.getValue(); |
| } |
| |
| if (auto *E = getType().getEnumOrBoundGenericEnum()) { |
| return visitEnumArgument(E, Base); |
| } |
| |
| return {compatibleWithOwnership(Base), |
| UseLifetimeConstraint::MustBeInvalidated}; |
| } |
| |
| OwnershipUseCheckerResult |
| OwnershipCompatibilityUseChecker::visitEndBorrowInst(EndBorrowInst *I) { |
| // We do not consider the original value to be a verified use. But the value |
| // does need to be alive. |
| if (getOperandIndex() == EndBorrowInst::OriginalValue) |
| return {true, UseLifetimeConstraint::MustBeLive}; |
| // The borrowed value is a verified use though of the begin_borrow. |
| return {compatibleWithOwnership(ValueOwnershipKind::Guaranteed), |
| UseLifetimeConstraint::MustBeInvalidated}; |
| } |
| |
| OwnershipUseCheckerResult |
| OwnershipCompatibilityUseChecker::visitThrowInst(ThrowInst *I) { |
| return {compatibleWithOwnership(ValueOwnershipKind::Owned), |
| UseLifetimeConstraint::MustBeInvalidated}; |
| } |
| |
| OwnershipUseCheckerResult |
| OwnershipCompatibilityUseChecker::visitStoreUnownedInst(StoreUnownedInst *I) { |
| if (getValue() == I->getSrc()) |
| return {compatibleWithOwnership(ValueOwnershipKind::Owned), |
| UseLifetimeConstraint::MustBeInvalidated}; |
| return {compatibleWithOwnership(ValueOwnershipKind::Trivial), |
| UseLifetimeConstraint::MustBeLive}; |
| } |
| |
| OwnershipUseCheckerResult |
| OwnershipCompatibilityUseChecker::visitStoreWeakInst(StoreWeakInst *I) { |
| // A store_weak instruction implies that the value to be stored to be live, |
| // but it does not touch the strong reference count of the value. |
| if (getValue() == I->getSrc()) |
| return {true, UseLifetimeConstraint::MustBeLive}; |
| return {compatibleWithOwnership(ValueOwnershipKind::Trivial), |
| UseLifetimeConstraint::MustBeLive}; |
| } |
| |
| OwnershipUseCheckerResult |
| OwnershipCompatibilityUseChecker::visitStoreBorrowInst(StoreBorrowInst *I) { |
| if (getValue() == I->getSrc()) |
| return {compatibleWithOwnership(ValueOwnershipKind::Guaranteed), |
| UseLifetimeConstraint::MustBeLive}; |
| return {compatibleWithOwnership(ValueOwnershipKind::Trivial), |
| UseLifetimeConstraint::MustBeLive}; |
| } |
| |
| // FIXME: Why not use SILArgumentConvention here? |
| OwnershipUseCheckerResult OwnershipCompatibilityUseChecker::visitCallee( |
| CanSILFunctionType SubstCalleeType) { |
| ParameterConvention Conv = SubstCalleeType->getCalleeConvention(); |
| switch (Conv) { |
| case ParameterConvention::Indirect_In: |
| case ParameterConvention::Indirect_In_Constant: |
| assert(!SILModuleConventions(Mod).isSILIndirect( |
| SILParameterInfo(SubstCalleeType, Conv))); |
| return {compatibleWithOwnership(ValueOwnershipKind::Owned), |
| UseLifetimeConstraint::MustBeInvalidated}; |
| case ParameterConvention::Indirect_In_Guaranteed: |
| assert(!SILModuleConventions(Mod).isSILIndirect( |
| SILParameterInfo(SubstCalleeType, Conv))); |
| return {compatibleWithOwnership(ValueOwnershipKind::Owned), |
| UseLifetimeConstraint::MustBeLive}; |
| case ParameterConvention::Indirect_Inout: |
| case ParameterConvention::Indirect_InoutAliasable: |
| llvm_unreachable("Illegal convention for callee"); |
| case ParameterConvention::Direct_Unowned: |
| if (isAddressOrTrivialType()) |
| return {compatibleWithOwnership(ValueOwnershipKind::Trivial), |
| UseLifetimeConstraint::MustBeLive}; |
| // We accept unowned, owned, and guaranteed in unowned positions. |
| return {true, UseLifetimeConstraint::MustBeLive}; |
| case ParameterConvention::Direct_Owned: |
| return {compatibleWithOwnership(ValueOwnershipKind::Owned), |
| UseLifetimeConstraint::MustBeInvalidated}; |
| case ParameterConvention::Direct_Guaranteed: |
| return {compatibleWithOwnership(ValueOwnershipKind::Guaranteed), |
| UseLifetimeConstraint::MustBeLive}; |
| } |
| |
| llvm_unreachable("Unhandled ParameterConvention in switch."); |
| } |
| |
| // Visit an enum value that is passed at argument position, including block |
| // arguments, apply arguments, and return values. |
| // |
| // The operand definition's ownership kind may be known to be "trivial", |
| // but it is still valid to pass that enum to a argument nontrivial type. |
| // For example: |
| // |
| // %val = enum $Optional<SomeClass>, #Optional.none // trivial ownership |
| // apply %f(%val) : (@owned Optional<SomeClass>) // owned argument |
| OwnershipUseCheckerResult OwnershipCompatibilityUseChecker::visitEnumArgument( |
| EnumDecl *E, ValueOwnershipKind RequiredKind) { |
| // If this value is already categorized as a trivial ownership kind, it is |
| // safe to pass to any argument convention. |
| if (compatibleWithOwnership(ValueOwnershipKind::Trivial)) { |
| return {true, UseLifetimeConstraint::MustBeLive}; |
| } |
| |
| // The operand has a non-trivial ownership kind. It must match the argument |
| // convention. |
| auto ownership = getOwnershipKind(); |
| auto lifetimeConstraint = (ownership == ValueOwnershipKind::Owned) |
| ? UseLifetimeConstraint::MustBeInvalidated |
| : UseLifetimeConstraint::MustBeLive; |
| return {compatibleOwnershipKinds(ownership, RequiredKind), |
| lifetimeConstraint}; |
| } |
| |
| // We allow for trivial cases of enums with non-trivial cases to be passed in |
| // non-trivial argument positions. This fits with modeling of a |
| // SILFunctionArgument as a phi in a global program graph. |
| OwnershipUseCheckerResult OwnershipCompatibilityUseChecker::visitApplyParameter( |
| ValueOwnershipKind Kind, UseLifetimeConstraint Requirement) { |
| // Check if we have an enum. If not, then we just check against the passed in |
| // convention. |
| EnumDecl *E = getType().getEnumOrBoundGenericEnum(); |
| if (!E) { |
| return {compatibleWithOwnership(Kind), Requirement}; |
| } |
| return visitEnumArgument(E, Kind); |
| } |
| |
| // Handle Apply and TryApply. |
| OwnershipUseCheckerResult OwnershipCompatibilityUseChecker:: |
| visitFullApply(FullApplySite apply) { |
| // If we are visiting the callee, handle it specially. |
| if (getOperandIndex() == 0) |
| return visitCallee(apply.getSubstCalleeType()); |
| |
| // Indirect return arguments are address types. |
| if (isAddressOrTrivialType()) |
| return {compatibleWithOwnership(ValueOwnershipKind::Trivial), |
| UseLifetimeConstraint::MustBeLive}; |
| |
| unsigned argIndex = apply.getCalleeArgIndex(Op); |
| SILParameterInfo paramInfo = |
| apply.getSubstCalleeConv().getParamInfoForSILArg(argIndex); |
| |
| switch (paramInfo.getConvention()) { |
| case ParameterConvention::Indirect_In: |
| case ParameterConvention::Direct_Owned: |
| return visitApplyParameter(ValueOwnershipKind::Owned, |
| UseLifetimeConstraint::MustBeInvalidated); |
| case ParameterConvention::Indirect_In_Constant: |
| case ParameterConvention::Direct_Unowned: |
| // We accept unowned, owned, and guaranteed in unowned positions. |
| return {true, UseLifetimeConstraint::MustBeLive}; |
| case ParameterConvention::Indirect_In_Guaranteed: |
| case ParameterConvention::Direct_Guaranteed: |
| return visitApplyParameter(ValueOwnershipKind::Guaranteed, |
| UseLifetimeConstraint::MustBeLive); |
| // The following conventions should take address types. |
| case ParameterConvention::Indirect_Inout: |
| case ParameterConvention::Indirect_InoutAliasable: |
| llvm_unreachable("Unexpected non-trivial parameter convention."); |
| } |
| } |
| |
| OwnershipUseCheckerResult |
| OwnershipCompatibilityUseChecker::visitBeginApplyInst(BeginApplyInst *I) { |
| return visitFullApply(I); |
| } |
| |
| OwnershipUseCheckerResult |
| OwnershipCompatibilityUseChecker::visitApplyInst(ApplyInst *I) { |
| return visitFullApply(I); |
| } |
| |
| OwnershipUseCheckerResult |
| OwnershipCompatibilityUseChecker::visitTryApplyInst(TryApplyInst *I) { |
| return visitFullApply(I); |
| } |
| |
| OwnershipUseCheckerResult |
| OwnershipCompatibilityUseChecker::visitPartialApplyInst(PartialApplyInst *I) { |
| // All non-trivial types should be captured. |
| if (isAddressOrTrivialType()) { |
| return {compatibleWithOwnership(ValueOwnershipKind::Trivial), |
| UseLifetimeConstraint::MustBeLive}; |
| } |
| return {compatibleWithOwnership(ValueOwnershipKind::Owned), |
| UseLifetimeConstraint::MustBeInvalidated}; |
| } |
| |
| OwnershipUseCheckerResult |
| OwnershipCompatibilityUseChecker::visitYieldInst(YieldInst *I) { |
| // Indirect return arguments are address types. |
| if (isAddressOrTrivialType()) |
| return {compatibleWithOwnership(ValueOwnershipKind::Trivial), |
| UseLifetimeConstraint::MustBeLive}; |
| |
| auto fnType = I->getFunction()->getLoweredFunctionType(); |
| auto yieldInfo = fnType->getYields()[getOperandIndex()]; |
| switch (yieldInfo.getConvention()) { |
| case ParameterConvention::Indirect_In: |
| case ParameterConvention::Direct_Owned: |
| return visitApplyParameter(ValueOwnershipKind::Owned, |
| UseLifetimeConstraint::MustBeInvalidated); |
| case ParameterConvention::Indirect_In_Constant: |
| case ParameterConvention::Direct_Unowned: |
| // We accept unowned, owned, and guaranteed in unowned positions. |
| return {true, UseLifetimeConstraint::MustBeLive}; |
| case ParameterConvention::Indirect_In_Guaranteed: |
| case ParameterConvention::Direct_Guaranteed: |
| return visitApplyParameter(ValueOwnershipKind::Guaranteed, |
| UseLifetimeConstraint::MustBeLive); |
| // The following conventions should take address types. |
| case ParameterConvention::Indirect_Inout: |
| case ParameterConvention::Indirect_InoutAliasable: |
| llvm_unreachable("Unexpected non-trivial parameter convention."); |
| } |
| } |
| |
| OwnershipUseCheckerResult |
| OwnershipCompatibilityUseChecker::visitAssignInst(AssignInst *I) { |
| if (getValue() == I->getSrc()) { |
| if (isAddressOrTrivialType()) { |
| return {compatibleWithOwnership(ValueOwnershipKind::Trivial), |
| UseLifetimeConstraint::MustBeLive}; |
| } |
| return {compatibleWithOwnership(ValueOwnershipKind::Owned), |
| UseLifetimeConstraint::MustBeInvalidated}; |
| } |
| |
| return {true, UseLifetimeConstraint::MustBeLive}; |
| } |
| |
| OwnershipUseCheckerResult |
| OwnershipCompatibilityUseChecker::visitStoreInst(StoreInst *I) { |
| if (getValue() == I->getSrc()) { |
| if (isAddressOrTrivialType()) { |
| return {compatibleWithOwnership(ValueOwnershipKind::Trivial), |
| UseLifetimeConstraint::MustBeLive}; |
| } |
| return {compatibleWithOwnership(ValueOwnershipKind::Owned), |
| UseLifetimeConstraint::MustBeInvalidated}; |
| } |
| return {true, UseLifetimeConstraint::MustBeLive}; |
| } |
| |
| OwnershipUseCheckerResult |
| OwnershipCompatibilityUseChecker::visitMarkDependenceInst( |
| MarkDependenceInst *MDI) { |
| // We always treat mark dependence as a use that keeps a value alive. We will |
| // be introducing a begin_dependence/end_dependence version of this later. |
| return {true, UseLifetimeConstraint::MustBeLive}; |
| } |
| |
| OwnershipUseCheckerResult |
| OwnershipCompatibilityUseChecker::visitKeyPathInst(KeyPathInst *I) { |
| // KeyPath moves the value in memory out of address operands, but the |
| // ownership checker doesn't reason about that yet. |
| if (isAddressOrTrivialType()) { |
| return {compatibleWithOwnership(ValueOwnershipKind::Trivial), |
| UseLifetimeConstraint::MustBeLive}; |
| } |
| return {compatibleWithOwnership(ValueOwnershipKind::Owned), |
| UseLifetimeConstraint::MustBeInvalidated}; |
| } |
| |
| //===----------------------------------------------------------------------===// |
| // Builtin Use Checker |
| //===----------------------------------------------------------------------===// |
| |
| namespace { |
| |
| class OwnershipCompatibilityBuiltinUseChecker |
| : public SILBuiltinVisitor<OwnershipCompatibilityBuiltinUseChecker, |
| OwnershipUseCheckerResult> { |
| |
| const OwnershipCompatibilityUseChecker &ParentChecker; |
| |
| public: |
| OwnershipCompatibilityBuiltinUseChecker( |
| OwnershipCompatibilityUseChecker &ParentChecker) |
| : ParentChecker(ParentChecker) {} |
| |
| SILValue getValue() const { return ParentChecker.getValue(); } |
| |
| ValueOwnershipKind getOwnershipKind() const { |
| return ParentChecker.getOwnershipKind(); |
| } |
| |
| unsigned getOperandIndex() const { return ParentChecker.getOperandIndex(); } |
| |
| SILType getType() const { return ParentChecker.getType(); } |
| |
| bool compatibleWithOwnership(ValueOwnershipKind Kind) const { |
| return ParentChecker.compatibleWithOwnership(Kind); |
| } |
| |
| bool isAddressOrTrivialType() const { |
| return ParentChecker.isAddressOrTrivialType(); |
| } |
| |
| OwnershipUseCheckerResult visitLLVMIntrinsic(BuiltinInst *BI, |
| llvm::Intrinsic::ID ID) { |
| // LLVM intrinsics do not traffic in ownership, so if we have a result, it |
| // must be trivial. |
| return {true, UseLifetimeConstraint::MustBeLive}; |
| } |
| |
| // BUILTIN_TYPE_CHECKER_OPERATION does not live past the type checker. |
| #define BUILTIN_TYPE_CHECKER_OPERATION(ID, NAME) |
| |
| #define BUILTIN(ID, NAME, ATTRS) \ |
| OwnershipUseCheckerResult visit##ID(BuiltinInst *BI, StringRef Attr); |
| #include "swift/AST/Builtins.def" |
| |
| OwnershipUseCheckerResult check(BuiltinInst *BI) { return visit(BI); } |
| }; |
| |
| } // end anonymous namespace |
| |
| OwnershipUseCheckerResult |
| OwnershipCompatibilityBuiltinUseChecker::visitUnsafeGuaranteed(BuiltinInst *BI, |
| StringRef Attr) { |
| // We accept owned or guaranteed values here. |
| if (compatibleWithOwnership(ValueOwnershipKind::Guaranteed)) |
| return {true, UseLifetimeConstraint::MustBeLive}; |
| return {compatibleWithOwnership(ValueOwnershipKind::Owned), |
| UseLifetimeConstraint::MustBeInvalidated}; |
| } |
| |
| // This is correct today since we do not have any builtins which return |
| // @guaranteed parameters. This means that we can only have a lifetime ending |
| // use with our builtins if it is owned. |
| #define CONSTANT_OWNERSHIP_BUILTIN(OWNERSHIP, USE_LIFETIME_CONSTRAINT, ID) \ |
| OwnershipUseCheckerResult \ |
| OwnershipCompatibilityBuiltinUseChecker::visit##ID(BuiltinInst *BI, \ |
| StringRef Attr) { \ |
| return {compatibleWithOwnership(ValueOwnershipKind::OWNERSHIP), \ |
| UseLifetimeConstraint::USE_LIFETIME_CONSTRAINT}; \ |
| } |
| CONSTANT_OWNERSHIP_BUILTIN(Owned, MustBeLive, ErrorInMain) |
| CONSTANT_OWNERSHIP_BUILTIN(Owned, MustBeLive, UnexpectedError) |
| CONSTANT_OWNERSHIP_BUILTIN(Owned, MustBeLive, WillThrow) |
| CONSTANT_OWNERSHIP_BUILTIN(Trivial, MustBeLive, AShr) |
| CONSTANT_OWNERSHIP_BUILTIN(Trivial, MustBeLive, Add) |
| CONSTANT_OWNERSHIP_BUILTIN(Trivial, MustBeLive, Alignof) |
| CONSTANT_OWNERSHIP_BUILTIN(Trivial, MustBeLive, AllocRaw) |
| CONSTANT_OWNERSHIP_BUILTIN(Trivial, MustBeLive, And) |
| CONSTANT_OWNERSHIP_BUILTIN(Trivial, MustBeLive, AssertConf) |
| CONSTANT_OWNERSHIP_BUILTIN(Trivial, MustBeLive, AssignCopyArrayNoAlias) |
| CONSTANT_OWNERSHIP_BUILTIN(Trivial, MustBeLive, AssignCopyArrayFrontToBack) |
| CONSTANT_OWNERSHIP_BUILTIN(Trivial, MustBeLive, AssignCopyArrayBackToFront) |
| CONSTANT_OWNERSHIP_BUILTIN(Trivial, MustBeLive, AssignTakeArray) |
| CONSTANT_OWNERSHIP_BUILTIN(Trivial, MustBeLive, AssumeNonNegative) |
| CONSTANT_OWNERSHIP_BUILTIN(Trivial, MustBeLive, AtomicLoad) |
| CONSTANT_OWNERSHIP_BUILTIN(Trivial, MustBeLive, AtomicRMW) |
| CONSTANT_OWNERSHIP_BUILTIN(Trivial, MustBeLive, AtomicStore) |
| CONSTANT_OWNERSHIP_BUILTIN(Trivial, MustBeLive, BitCast) |
| CONSTANT_OWNERSHIP_BUILTIN(Trivial, MustBeLive, CanBeObjCClass) |
| CONSTANT_OWNERSHIP_BUILTIN(Trivial, MustBeLive, CmpXChg) |
| CONSTANT_OWNERSHIP_BUILTIN(Trivial, MustBeLive, CondUnreachable) |
| CONSTANT_OWNERSHIP_BUILTIN(Trivial, MustBeLive, CopyArray) |
| CONSTANT_OWNERSHIP_BUILTIN(Trivial, MustBeLive, DeallocRaw) |
| CONSTANT_OWNERSHIP_BUILTIN(Trivial, MustBeLive, DestroyArray) |
| CONSTANT_OWNERSHIP_BUILTIN(Trivial, MustBeLive, ExactSDiv) |
| CONSTANT_OWNERSHIP_BUILTIN(Trivial, MustBeLive, ExactUDiv) |
| CONSTANT_OWNERSHIP_BUILTIN(Trivial, MustBeLive, ExtractElement) |
| CONSTANT_OWNERSHIP_BUILTIN(Trivial, MustBeLive, FAdd) |
| CONSTANT_OWNERSHIP_BUILTIN(Trivial, MustBeLive, FCMP_OEQ) |
| CONSTANT_OWNERSHIP_BUILTIN(Trivial, MustBeLive, FCMP_OGE) |
| CONSTANT_OWNERSHIP_BUILTIN(Trivial, MustBeLive, FCMP_OGT) |
| CONSTANT_OWNERSHIP_BUILTIN(Trivial, MustBeLive, FCMP_OLE) |
| CONSTANT_OWNERSHIP_BUILTIN(Trivial, MustBeLive, FCMP_OLT) |
| CONSTANT_OWNERSHIP_BUILTIN(Trivial, MustBeLive, FCMP_ONE) |
| CONSTANT_OWNERSHIP_BUILTIN(Trivial, MustBeLive, FCMP_ORD) |
| CONSTANT_OWNERSHIP_BUILTIN(Trivial, MustBeLive, FCMP_UEQ) |
| CONSTANT_OWNERSHIP_BUILTIN(Trivial, MustBeLive, FCMP_UGE) |
| CONSTANT_OWNERSHIP_BUILTIN(Trivial, MustBeLive, FCMP_UGT) |
| CONSTANT_OWNERSHIP_BUILTIN(Trivial, MustBeLive, FCMP_ULE) |
| CONSTANT_OWNERSHIP_BUILTIN(Trivial, MustBeLive, FCMP_ULT) |
| CONSTANT_OWNERSHIP_BUILTIN(Trivial, MustBeLive, FCMP_UNE) |
| CONSTANT_OWNERSHIP_BUILTIN(Trivial, MustBeLive, FCMP_UNO) |
| CONSTANT_OWNERSHIP_BUILTIN(Trivial, MustBeLive, FDiv) |
| CONSTANT_OWNERSHIP_BUILTIN(Trivial, MustBeLive, FMul) |
| CONSTANT_OWNERSHIP_BUILTIN(Trivial, MustBeLive, FNeg) |
| CONSTANT_OWNERSHIP_BUILTIN(Trivial, MustBeLive, FPExt) |
| CONSTANT_OWNERSHIP_BUILTIN(Trivial, MustBeLive, FPToSI) |
| CONSTANT_OWNERSHIP_BUILTIN(Trivial, MustBeLive, FPToUI) |
| CONSTANT_OWNERSHIP_BUILTIN(Trivial, MustBeLive, FPTrunc) |
| CONSTANT_OWNERSHIP_BUILTIN(Trivial, MustBeLive, FRem) |
| CONSTANT_OWNERSHIP_BUILTIN(Trivial, MustBeLive, FSub) |
| CONSTANT_OWNERSHIP_BUILTIN(Trivial, MustBeLive, Fence) |
| CONSTANT_OWNERSHIP_BUILTIN(Trivial, MustBeLive, GetObjCTypeEncoding) |
| CONSTANT_OWNERSHIP_BUILTIN(Trivial, MustBeLive, ICMP_EQ) |
| CONSTANT_OWNERSHIP_BUILTIN(Trivial, MustBeLive, ICMP_NE) |
| CONSTANT_OWNERSHIP_BUILTIN(Trivial, MustBeLive, ICMP_SGE) |
| CONSTANT_OWNERSHIP_BUILTIN(Trivial, MustBeLive, ICMP_SGT) |
| CONSTANT_OWNERSHIP_BUILTIN(Trivial, MustBeLive, ICMP_SLE) |
| CONSTANT_OWNERSHIP_BUILTIN(Trivial, MustBeLive, ICMP_SLT) |
| CONSTANT_OWNERSHIP_BUILTIN(Trivial, MustBeLive, ICMP_UGE) |
| CONSTANT_OWNERSHIP_BUILTIN(Trivial, MustBeLive, ICMP_UGT) |
| CONSTANT_OWNERSHIP_BUILTIN(Trivial, MustBeLive, ICMP_ULE) |
| CONSTANT_OWNERSHIP_BUILTIN(Trivial, MustBeLive, ICMP_ULT) |
| CONSTANT_OWNERSHIP_BUILTIN(Trivial, MustBeLive, InsertElement) |
| CONSTANT_OWNERSHIP_BUILTIN(Trivial, MustBeLive, IntToFPWithOverflow) |
| CONSTANT_OWNERSHIP_BUILTIN(Trivial, MustBeLive, IntToPtr) |
| CONSTANT_OWNERSHIP_BUILTIN(Trivial, MustBeLive, IsOptionalType) |
| CONSTANT_OWNERSHIP_BUILTIN(Trivial, MustBeLive, IsPOD) |
| CONSTANT_OWNERSHIP_BUILTIN(Trivial, MustBeLive, IsSameMetatype) |
| CONSTANT_OWNERSHIP_BUILTIN(Trivial, MustBeLive, LShr) |
| CONSTANT_OWNERSHIP_BUILTIN(Trivial, MustBeLive, Mul) |
| CONSTANT_OWNERSHIP_BUILTIN(Trivial, MustBeLive, OnFastPath) |
| CONSTANT_OWNERSHIP_BUILTIN(Trivial, MustBeLive, Once) |
| CONSTANT_OWNERSHIP_BUILTIN(Trivial, MustBeLive, OnceWithContext) |
| CONSTANT_OWNERSHIP_BUILTIN(Trivial, MustBeLive, Or) |
| CONSTANT_OWNERSHIP_BUILTIN(Trivial, MustBeLive, PtrToInt) |
| CONSTANT_OWNERSHIP_BUILTIN(Trivial, MustBeLive, SAddOver) |
| CONSTANT_OWNERSHIP_BUILTIN(Trivial, MustBeLive, SDiv) |
| CONSTANT_OWNERSHIP_BUILTIN(Trivial, MustBeLive, SExt) |
| CONSTANT_OWNERSHIP_BUILTIN(Trivial, MustBeLive, SExtOrBitCast) |
| CONSTANT_OWNERSHIP_BUILTIN(Trivial, MustBeLive, SIToFP) |
| CONSTANT_OWNERSHIP_BUILTIN(Trivial, MustBeLive, SMulOver) |
| CONSTANT_OWNERSHIP_BUILTIN(Trivial, MustBeLive, SRem) |
| CONSTANT_OWNERSHIP_BUILTIN(Trivial, MustBeLive, SSubOver) |
| CONSTANT_OWNERSHIP_BUILTIN(Trivial, MustBeLive, SToSCheckedTrunc) |
| CONSTANT_OWNERSHIP_BUILTIN(Trivial, MustBeLive, SToUCheckedTrunc) |
| CONSTANT_OWNERSHIP_BUILTIN(Trivial, MustBeLive, SUCheckedConversion) |
| CONSTANT_OWNERSHIP_BUILTIN(Trivial, MustBeLive, Shl) |
| CONSTANT_OWNERSHIP_BUILTIN(Trivial, MustBeLive, Sizeof) |
| CONSTANT_OWNERSHIP_BUILTIN(Trivial, MustBeLive, StaticReport) |
| CONSTANT_OWNERSHIP_BUILTIN(Trivial, MustBeLive, Strideof) |
| CONSTANT_OWNERSHIP_BUILTIN(Trivial, MustBeLive, Sub) |
| CONSTANT_OWNERSHIP_BUILTIN(Trivial, MustBeLive, TakeArrayNoAlias) |
| CONSTANT_OWNERSHIP_BUILTIN(Trivial, MustBeLive, TakeArrayBackToFront) |
| CONSTANT_OWNERSHIP_BUILTIN(Trivial, MustBeLive, TakeArrayFrontToBack) |
| CONSTANT_OWNERSHIP_BUILTIN(Trivial, MustBeLive, Trunc) |
| CONSTANT_OWNERSHIP_BUILTIN(Trivial, MustBeLive, TruncOrBitCast) |
| CONSTANT_OWNERSHIP_BUILTIN(Trivial, MustBeLive, TSanInoutAccess) |
| CONSTANT_OWNERSHIP_BUILTIN(Trivial, MustBeLive, UAddOver) |
| CONSTANT_OWNERSHIP_BUILTIN(Trivial, MustBeLive, UDiv) |
| CONSTANT_OWNERSHIP_BUILTIN(Trivial, MustBeLive, UIToFP) |
| CONSTANT_OWNERSHIP_BUILTIN(Trivial, MustBeLive, UMulOver) |
| CONSTANT_OWNERSHIP_BUILTIN(Trivial, MustBeLive, URem) |
| CONSTANT_OWNERSHIP_BUILTIN(Trivial, MustBeLive, USCheckedConversion) |
| CONSTANT_OWNERSHIP_BUILTIN(Trivial, MustBeLive, USubOver) |
| CONSTANT_OWNERSHIP_BUILTIN(Trivial, MustBeLive, UToSCheckedTrunc) |
| CONSTANT_OWNERSHIP_BUILTIN(Trivial, MustBeLive, UToUCheckedTrunc) |
| CONSTANT_OWNERSHIP_BUILTIN(Trivial, MustBeLive, Unreachable) |
| CONSTANT_OWNERSHIP_BUILTIN(Trivial, MustBeLive, UnsafeGuaranteedEnd) |
| CONSTANT_OWNERSHIP_BUILTIN(Trivial, MustBeLive, Xor) |
| CONSTANT_OWNERSHIP_BUILTIN(Trivial, MustBeLive, ZExt) |
| CONSTANT_OWNERSHIP_BUILTIN(Trivial, MustBeLive, ZExtOrBitCast) |
| CONSTANT_OWNERSHIP_BUILTIN(Trivial, MustBeLive, ZeroInitializer) |
| CONSTANT_OWNERSHIP_BUILTIN(Trivial, MustBeLive, Swift3ImplicitObjCEntrypoint) |
| #undef CONSTANT_OWNERSHIP_BUILTIN |
| |
| // Builtins that should be lowered to SIL instructions so we should never see |
| // them. |
| #define BUILTINS_THAT_SHOULD_HAVE_BEEN_LOWERED_TO_SILINSTS(ID) \ |
| OwnershipUseCheckerResult \ |
| OwnershipCompatibilityBuiltinUseChecker::visit##ID(BuiltinInst *BI, \ |
| StringRef Attr) { \ |
| llvm_unreachable("Builtin should have been lowered to SIL instruction?!"); \ |
| } |
| BUILTINS_THAT_SHOULD_HAVE_BEEN_LOWERED_TO_SILINSTS(Retain) |
| BUILTINS_THAT_SHOULD_HAVE_BEEN_LOWERED_TO_SILINSTS(Release) |
| BUILTINS_THAT_SHOULD_HAVE_BEEN_LOWERED_TO_SILINSTS(Autorelease) |
| BUILTINS_THAT_SHOULD_HAVE_BEEN_LOWERED_TO_SILINSTS(TryPin) |
| BUILTINS_THAT_SHOULD_HAVE_BEEN_LOWERED_TO_SILINSTS(Unpin) |
| BUILTINS_THAT_SHOULD_HAVE_BEEN_LOWERED_TO_SILINSTS(Load) |
| BUILTINS_THAT_SHOULD_HAVE_BEEN_LOWERED_TO_SILINSTS(LoadRaw) |
| BUILTINS_THAT_SHOULD_HAVE_BEEN_LOWERED_TO_SILINSTS(LoadInvariant) |
| BUILTINS_THAT_SHOULD_HAVE_BEEN_LOWERED_TO_SILINSTS(Take) |
| BUILTINS_THAT_SHOULD_HAVE_BEEN_LOWERED_TO_SILINSTS(Destroy) |
| BUILTINS_THAT_SHOULD_HAVE_BEEN_LOWERED_TO_SILINSTS(Assign) |
| BUILTINS_THAT_SHOULD_HAVE_BEEN_LOWERED_TO_SILINSTS(Init) |
| BUILTINS_THAT_SHOULD_HAVE_BEEN_LOWERED_TO_SILINSTS(CastToNativeObject) |
| BUILTINS_THAT_SHOULD_HAVE_BEEN_LOWERED_TO_SILINSTS(UnsafeCastToNativeObject) |
| BUILTINS_THAT_SHOULD_HAVE_BEEN_LOWERED_TO_SILINSTS(CastFromNativeObject) |
| BUILTINS_THAT_SHOULD_HAVE_BEEN_LOWERED_TO_SILINSTS(CastToBridgeObject) |
| BUILTINS_THAT_SHOULD_HAVE_BEEN_LOWERED_TO_SILINSTS( |
| CastReferenceFromBridgeObject) |
| BUILTINS_THAT_SHOULD_HAVE_BEEN_LOWERED_TO_SILINSTS( |
| CastBitPatternFromBridgeObject) |
| BUILTINS_THAT_SHOULD_HAVE_BEEN_LOWERED_TO_SILINSTS(BridgeToRawPointer) |
| BUILTINS_THAT_SHOULD_HAVE_BEEN_LOWERED_TO_SILINSTS(BridgeFromRawPointer) |
| BUILTINS_THAT_SHOULD_HAVE_BEEN_LOWERED_TO_SILINSTS(CastReference) |
| BUILTINS_THAT_SHOULD_HAVE_BEEN_LOWERED_TO_SILINSTS(ReinterpretCast) |
| BUILTINS_THAT_SHOULD_HAVE_BEEN_LOWERED_TO_SILINSTS(AddressOf) |
| BUILTINS_THAT_SHOULD_HAVE_BEEN_LOWERED_TO_SILINSTS(GepRaw) |
| BUILTINS_THAT_SHOULD_HAVE_BEEN_LOWERED_TO_SILINSTS(Gep) |
| BUILTINS_THAT_SHOULD_HAVE_BEEN_LOWERED_TO_SILINSTS(GetTailAddr) |
| BUILTINS_THAT_SHOULD_HAVE_BEEN_LOWERED_TO_SILINSTS(CondFail) |
| BUILTINS_THAT_SHOULD_HAVE_BEEN_LOWERED_TO_SILINSTS(FixLifetime) |
| BUILTINS_THAT_SHOULD_HAVE_BEEN_LOWERED_TO_SILINSTS(IsUnique) |
| BUILTINS_THAT_SHOULD_HAVE_BEEN_LOWERED_TO_SILINSTS(IsUniqueOrPinned) |
| BUILTINS_THAT_SHOULD_HAVE_BEEN_LOWERED_TO_SILINSTS(IsUnique_native) |
| BUILTINS_THAT_SHOULD_HAVE_BEEN_LOWERED_TO_SILINSTS(IsUniqueOrPinned_native) |
| BUILTINS_THAT_SHOULD_HAVE_BEEN_LOWERED_TO_SILINSTS(BindMemory) |
| BUILTINS_THAT_SHOULD_HAVE_BEEN_LOWERED_TO_SILINSTS(AllocWithTailElems) |
| BUILTINS_THAT_SHOULD_HAVE_BEEN_LOWERED_TO_SILINSTS(ProjectTailElems) |
| #undef BUILTINS_THAT_SHOULD_HAVE_BEEN_LOWERED_TO_SILINSTS |
| |
| OwnershipUseCheckerResult |
| OwnershipCompatibilityUseChecker::visitBuiltinInst(BuiltinInst *BI) { |
| return OwnershipCompatibilityBuiltinUseChecker(*this).check(BI); |
| } |
| |
| //===----------------------------------------------------------------------===// |
| // SILValueOwnershipChecker |
| //===----------------------------------------------------------------------===// |
| |
| namespace { |
| |
| // TODO: This class uses a bunch of global state like variables. It should be |
| // refactored into a large state object that is used by functions. |
| class SILValueOwnershipChecker { |
| /// The result of performing the check. |
| llvm::Optional<bool> Result; |
| |
| /// The module that we are in. |
| SILModule &Mod; |
| |
| /// A cache of dead-end basic blocks that we use to determine if we can |
| /// ignore "leaks". |
| DeadEndBlocks &DEBlocks; |
| |
| /// The value whose ownership we will check. |
| SILValue Value; |
| |
| /// The action that the checker should perform on detecting an error. |
| ErrorBehaviorKind ErrorBehavior; |
| |
| /// The worklist that we will use for our iterative reachability query. |
| llvm::SmallVector<SILBasicBlock *, 32> Worklist; |
| |
| /// The set of blocks with lifetime ending uses. |
| llvm::SmallPtrSet<SILBasicBlock *, 8> BlocksWithLifetimeEndingUses; |
| |
| /// The set of blocks with non-lifetime ending uses and the associated |
| /// non-lifetime ending use SILInstruction. |
| llvm::SmallDenseMap<SILBasicBlock *, GeneralizedUser, 8> |
| BlocksWithNonLifetimeEndingUses; |
| |
| /// The blocks that we have already visited. |
| llvm::SmallPtrSet<SILBasicBlock *, 32> &VisitedBlocks; |
| |
| /// A list of successor blocks that we must visit by the time the algorithm |
| /// terminates. |
| llvm::SmallPtrSet<SILBasicBlock *, 8> SuccessorBlocksThatMustBeVisited; |
| |
| /// The list of lifetime ending users that we found. Only valid if check is |
| /// successful. |
| llvm::SmallVector<GeneralizedUser, 16> LifetimeEndingUsers; |
| |
| /// The list of non lifetime ending users that we found. Only valid if check |
| /// is successful. |
| llvm::SmallVector<GeneralizedUser, 16> RegularUsers; |
| |
| public: |
| SILValueOwnershipChecker( |
| SILModule &M, DeadEndBlocks &DEBlocks, SILValue V, |
| ErrorBehaviorKind ErrorBehavior, |
| llvm::SmallPtrSet<SILBasicBlock *, 32> &VisitedBlocks) |
| : Result(), Mod(M), DEBlocks(DEBlocks), Value(V), |
| ErrorBehavior(ErrorBehavior), |
| VisitedBlocks(VisitedBlocks) { |
| assert(Value && "Can not initialize a checker with an empty SILValue"); |
| } |
| |
| ~SILValueOwnershipChecker() = default; |
| SILValueOwnershipChecker(SILValueOwnershipChecker &) = delete; |
| SILValueOwnershipChecker(SILValueOwnershipChecker &&) = delete; |
| |
| bool check() { |
| if (Result.hasValue()) |
| return Result.getValue(); |
| |
| DEBUG(llvm::dbgs() << "Verifying ownership of: " << *Value); |
| Result = checkUses() && checkDataflow(); |
| |
| return Result.getValue(); |
| } |
| |
| using user_array_transform = std::function<SILInstruction *(GeneralizedUser)>; |
| using user_array = TransformArrayRef<user_array_transform>; |
| |
| /// A function that returns a range of lifetime ending users found for the |
| /// given value. |
| user_array getLifetimeEndingUsers() const { |
| assert(Result.hasValue() && "Can not call until check() is called"); |
| assert(Result.getValue() && "Can not call if check() returned false"); |
| |
| user_array_transform Transform( |
| [](GeneralizedUser User) -> SILInstruction * { |
| return User.getInst(); |
| }); |
| return user_array(ArrayRef<GeneralizedUser>(LifetimeEndingUsers), |
| Transform); |
| } |
| |
| /// A function that returns a range of regular (i.e. "non lifetime ending") |
| /// users found for the given value. |
| user_array getRegularUsers() const { |
| assert(Result.hasValue() && "Can not call until check() is called"); |
| assert(Result.getValue() && "Can not call if check() returned false"); |
| |
| user_array_transform Transform( |
| [](GeneralizedUser User) -> SILInstruction * { |
| return User.getInst(); |
| }); |
| return user_array(ArrayRef<GeneralizedUser>(RegularUsers), Transform); |
| } |
| |
| private: |
| bool checkUses(); |
| bool checkDataflow(); |
| void checkDataflowEndConditions(); |
| void |
| gatherUsers(llvm::SmallVectorImpl<GeneralizedUser> &LifetimeEndingUsers, |
| llvm::SmallVectorImpl<GeneralizedUser> &NonLifetimeEndingUsers); |
| void uniqueNonLifetimeEndingUsers( |
| ArrayRef<GeneralizedUser> NonLifetimeEndingUsers); |
| |
| /// Returns true if the given block is in the BlocksWithLifetimeEndingUses |
| /// set. This is a helper to extract out large logging messages so that the |
| /// main logic is easy to read. |
| bool doesBlockDoubleConsume( |
| SILBasicBlock *UserBlock, |
| llvm::Optional<GeneralizedUser> LifetimeEndingUser = None, |
| bool ShouldInsert = false); |
| |
| /// Returns true if the given block contains a non-lifetime ending use that is |
| /// strictly later in the block than a lifetime ending use. If all |
| /// non-lifetime ending uses are before the lifetime ending use, the block is |
| /// removed from the BlocksWithNonLifetimeEndingUses map to show that the uses |
| /// were found to properly be post-dominated by a lifetime ending use. |
| bool doesBlockContainUseAfterFree(GeneralizedUser LifetimeEndingUser, |
| SILBasicBlock *UserBlock); |
| |
| bool checkValueWithoutLifetimeEndingUses(); |
| |
| bool checkFunctionArgWithoutLifetimeEndingUses(SILFunctionArgument *Arg); |
| |
| bool isGuaranteedFunctionArgWithLifetimeEndingUses( |
| SILFunctionArgument *Arg, |
| const llvm::SmallVectorImpl<GeneralizedUser> &LifetimeEndingUsers) const; |
| bool isSubobjectProjectionWithLifetimeEndingUses( |
| SILValue Value, |
| const llvm::SmallVectorImpl<GeneralizedUser> &LifetimeEndingUsers) const; |
| |
| /// Depending on our initialization, either return false or call Func and |
| /// throw an error. |
| bool handleError(llvm::function_ref<void()> &&MessagePrinterFunc) const { |
| if (ErrorBehavior.shouldPrintMessage()) { |
| MessagePrinterFunc(); |
| } |
| |
| if (ErrorBehavior.shouldReturnFalse()) { |
| return false; |
| } |
| |
| assert(ErrorBehavior.shouldAssert() && "At this point, we should assert"); |
| llvm_unreachable("triggering standard assertion failure routine"); |
| } |
| }; |
| |
| } // end anonymous namespace |
| |
| bool SILValueOwnershipChecker::doesBlockContainUseAfterFree( |
| GeneralizedUser LifetimeEndingUser, SILBasicBlock *UserBlock) { |
| auto Iter = BlocksWithNonLifetimeEndingUses.find(UserBlock); |
| if (Iter == BlocksWithNonLifetimeEndingUses.end()) |
| return false; |
| |
| GeneralizedUser NonLifetimeEndingUser = Iter->second; |
| |
| // Make sure that the non-lifetime ending use is before the lifetime ending |
| // use. Otherwise, we have a use after free. |
| |
| // First check if our lifetime ending user is a cond_br. In such a case, we |
| // always consider the non-lifetime ending use to be a use after free. |
| if (LifetimeEndingUser.isCondBranchUser()) { |
| return !handleError([&]() { |
| llvm::errs() << "Function: '" << Value->getFunction()->getName() << "'\n" |
| << "Found use after free?!\n" |
| << "Value: " << *Value |
| << "Consuming User: " << *LifetimeEndingUser |
| << "Non Consuming User: " << *Iter->second << "Block: bb" |
| << UserBlock->getDebugID() << "\n\n"; |
| }); |
| } |
| |
| // Ok. At this point, we know that our lifetime ending user is not a cond |
| // branch user. Check if our non-lifetime ending use is. In such a case, we |
| // know that our non lifetime ending user is properly post-dominated so we can |
| // erase the non lifetime ending use and continue. |
| if (NonLifetimeEndingUser.isCondBranchUser()) { |
| BlocksWithNonLifetimeEndingUses.erase(Iter); |
| return false; |
| } |
| |
| // Otherwise, we know that both of our users are non-cond branch users and |
| // thus must be instructions in the given block. Make sure that the non |
| // lifetime ending user is strictly before the lifetime ending user. |
| if (std::find_if(LifetimeEndingUser.getIterator(), UserBlock->end(), |
| [&NonLifetimeEndingUser](const SILInstruction &I) -> bool { |
| return NonLifetimeEndingUser == &I; |
| }) != UserBlock->end()) { |
| return !handleError([&] { |
| llvm::errs() << "Function: '" << Value->getFunction()->getName() << "'\n" |
| << "Found use after free?!\n" |
| << "Value: " << *Value |
| << "Consuming User: " << *LifetimeEndingUser |
| << "Non Consuming User: " << *Iter->second << "Block: bb" |
| << UserBlock->getDebugID() << "\n\n"; |
| }); |
| } |
| |
| // Erase the use since we know that it is properly joint post-dominated. |
| BlocksWithNonLifetimeEndingUses.erase(Iter); |
| return false; |
| } |
| |
| bool SILValueOwnershipChecker::doesBlockDoubleConsume( |
| SILBasicBlock *UserBlock, |
| llvm::Optional<GeneralizedUser> LifetimeEndingUser, bool ShouldInsert) { |
| if ((ShouldInsert && BlocksWithLifetimeEndingUses.insert(UserBlock).second) || |
| !BlocksWithLifetimeEndingUses.count(UserBlock)) |
| return false; |
| |
| return !handleError([&] { |
| llvm::errs() << "Function: '" << Value->getFunction()->getName() << "'\n" |
| << "Found over consume?!\n" |
| << "Value: " << *Value; |
| if (LifetimeEndingUser.hasValue()) |
| llvm::errs() << "User: " << *LifetimeEndingUser.getValue(); |
| llvm::errs() << "Block: bb" << UserBlock->getDebugID() << "\n\n"; |
| }); |
| } |
| |
| void SILValueOwnershipChecker::gatherUsers( |
| llvm::SmallVectorImpl<GeneralizedUser> &LifetimeEndingUsers, |
| llvm::SmallVectorImpl<GeneralizedUser> &NonLifetimeEndingUsers) { |
| |
| // See if Value is guaranteed. If we are guaranteed and not forwarding, then |
| // we need to look through subobject uses for more uses. Otherwise, if we are |
| // forwarding, we do not create any lifetime ending users/non lifetime ending |
| // users since we verify against our base. |
| auto OwnershipKind = Value.getOwnershipKind(); |
| bool IsGuaranteed = OwnershipKind == ValueOwnershipKind::Guaranteed; |
| |
| if (IsGuaranteed && isGuaranteedForwardingValue(Value)) |
| return; |
| |
| // Then gather up our initial list of users. |
| llvm::SmallVector<Operand *, 8> Users; |
| std::copy(Value->use_begin(), Value->use_end(), std::back_inserter(Users)); |
| |
| auto addCondBranchToList = [](llvm::SmallVectorImpl<GeneralizedUser> &List, |
| CondBranchInst *CBI, unsigned OperandIndex) { |
| if (CBI->isConditionOperandIndex(OperandIndex)) { |
| List.emplace_back(CBI); |
| return; |
| } |
| |
| bool isTrueOperand = CBI->isTrueOperandIndex(OperandIndex); |
| List.emplace_back(CBI, isTrueOperand ? CondBranchInst::TrueIdx |
| : CondBranchInst::FalseIdx); |
| }; |
| |
| while (!Users.empty()) { |
| Operand *Op = Users.pop_back_val(); |
| SILInstruction *User = Op->getUser(); |
| |
| // If this op is a type dependent operand, skip it. It is not interesting |
| // from an ownership perspective. |
| if (User->isTypeDependentOperand(*Op)) |
| continue; |
| |
| if (OwnershipCompatibilityUseChecker(Mod, *Op, Value, ErrorBehavior) |
| .check(User)) { |
| DEBUG(llvm::dbgs() << " Lifetime Ending User: " << *User); |
| if (auto *CBI = dyn_cast<CondBranchInst>(User)) { |
| addCondBranchToList(LifetimeEndingUsers, CBI, Op->getOperandNumber()); |
| } else { |
| LifetimeEndingUsers.emplace_back(User); |
| } |
| } else { |
| DEBUG(llvm::dbgs() << " Regular User: " << *User); |
| if (auto *CBI = dyn_cast<CondBranchInst>(User)) { |
| addCondBranchToList(NonLifetimeEndingUsers, CBI, |
| Op->getOperandNumber()); |
| } else { |
| NonLifetimeEndingUsers.emplace_back(User); |
| } |
| } |
| |
| // If our base value is not guaranteed or our intermediate value is not an |
| // ownership forwarding inst, continue. We do not want to visit any |
| // subobjects recursively. |
| if (!IsGuaranteed || !isGuaranteedForwardingInst(User)) { |
| continue; |
| } |
| |
| // At this point, we know that we must have a forwarded subobject. Since the |
| // base type is guaranteed, we know that the subobject is either guaranteed |
| // or trivial. We now split into two cases, if the user is a terminator or |
| // not. If we do not have a terminator, then just add the uses of all of |
| // User's results to the worklist. |
| if (User->getResults().size()) { |
| for (SILValue result : User->getResults()) { |
| if (result.getOwnershipKind() == ValueOwnershipKind::Trivial) { |
| continue; |
| } |
| |
| // Now, we /must/ have a guaranteed subobject, so let's assert that the |
| // user is actually guaranteed and add the subobject's users to our |
| // worklist. |
| assert(result.getOwnershipKind() == ValueOwnershipKind::Guaranteed && |
| "Our value is guaranteed and this is a forwarding instruction. " |
| "Should have guaranteed ownership as well."); |
| copy(result->getUses(), std::back_inserter(Users)); |
| } |
| |
| continue; |
| } |
| |
| assert(User->getResults().empty()); |
| |
| auto *TI = dyn_cast<TermInst>(User); |
| if (!TI) { |
| continue; |
| } |
| |
| // Otherwise if we have a terminator, add any as uses any |
| // end_borrow_argument to ensure that the subscope is completely enclsed |
| // within the super scope. all of the arguments to the work list. We require |
| // all of our arguments to be either trivial or guaranteed. |
| for (auto &Succ : TI->getSuccessors()) { |
| auto *BB = Succ.getBB(); |
| |
| // If we do not have any arguments, then continue. |
| if (BB->args_empty()) |
| continue; |
| |
| // Otherwise, make sure that all arguments are trivial or guaranteed. If |
| // we fail, emit an error. |
| // |
| // TODO: We could ignore this error and emit a more specific error on the |
| // actual terminator. |
| for (auto *BBArg : BB->getArguments()) { |
| // *NOTE* We do not emit an error here since we want to allow for more |
| // specific errors to be found during use_verification. |
| // |
| // TODO: Add a flag that associates the terminator instruction with |
| // needing to be verified. If it isn't verified appropriately, assert |
| // when the verifier is destroyed. |
| if (!trivialOrCompatibleOwnershipKinds(BBArg->getOwnershipKind(), |
| OwnershipKind)) { |
| // This is where the error would go. |
| continue; |
| } |
| |
| // If we have a trivial value, just continue. |
| if (BBArg->getOwnershipKind() == ValueOwnershipKind::Trivial) |
| continue; |
| |
| // Otherwise, |
| std::copy(BBArg->use_begin(), BBArg->use_end(), std::back_inserter(Users)); |
| } |
| } |
| } |
| } |
| |
| // Unique our non lifetime ending user list by only selecting the last user in |
| // each block. |
| void SILValueOwnershipChecker::uniqueNonLifetimeEndingUsers( |
| ArrayRef<GeneralizedUser> NonLifetimeEndingUsers) { |
| for (GeneralizedUser User : NonLifetimeEndingUsers) { |
| auto *UserBlock = User.getParent(); |
| // First try to associate User with User->getParent(). |
| auto Result = |
| BlocksWithNonLifetimeEndingUses.insert(std::make_pair(UserBlock, User)); |
| |
| // If the insertion succeeds, then we know that there is no more work to |
| // be done, so process the next use. |
| if (Result.second) |
| continue; |
| |
| // If the insertion fails, then we have at least two non-lifetime ending |
| // uses in the same block. Since we are performing a liveness type of |
| // dataflow, we only need the last non-lifetime ending use to show that all |
| // lifetime ending uses post dominate both. |
| // |
| // We begin by checking if the first use is a cond_br use from the previous |
| // block. In such a case, we always use the already stored value and |
| // continue. |
| if (User.isCondBranchUser()) { |
| continue; |
| } |
| |
| // Then, we check if Use is after Result.first->second in the use list. If |
| // Use is not later, then we wish to keep the already mapped value, not use, |
| // so continue. |
| if (std::find_if(Result.first->second.getIterator(), UserBlock->end(), |
| [&User](const SILInstruction &I) -> bool { |
| return User == &I; |
| }) == UserBlock->end()) { |
| continue; |
| } |
| |
| // At this point, we know that User is later in the Block than |
| // Result.first->second, so store Use instead. |
| Result.first->second = User; |
| } |
| } |
| |
| bool SILValueOwnershipChecker::checkFunctionArgWithoutLifetimeEndingUses( |
| SILFunctionArgument *Arg) { |
| switch (Arg->getOwnershipKind()) { |
| case ValueOwnershipKind::Guaranteed: |
| case ValueOwnershipKind::Unowned: |
| case ValueOwnershipKind::Trivial: |
| return true; |
| case ValueOwnershipKind::Any: |
| llvm_unreachable( |
| "Function arguments should never have ValueOwnershipKind::Any"); |
| case ValueOwnershipKind::Owned: |
| break; |
| } |
| |
| if (DEBlocks.isDeadEnd(Arg->getParent())) |
| return true; |
| |
| return !handleError([&] { |
| llvm::errs() << "Function: '" << Arg->getFunction()->getName() << "'\n" |
| << " Owned function parameter without life ending uses!\n" |
| << "Value: " << *Arg << '\n'; |
| }); |
| } |
| |
| bool SILValueOwnershipChecker::checkValueWithoutLifetimeEndingUses() { |
| DEBUG(llvm::dbgs() << " No lifetime ending users?! Bailing early.\n"); |
| if (auto *Arg = dyn_cast<SILFunctionArgument>(Value)) { |
| if (checkFunctionArgWithoutLifetimeEndingUses(Arg)) { |
| return true; |
| } |
| } |
| |
| // Check if we are a guaranteed subobject. In such a case, we should never |
| // have lifetime ending uses, since our lifetime is guaranteed by our |
| // operand, so there is nothing further to do. So just return true. |
| if (isGuaranteedForwardingValue(Value) && |
| Value.getOwnershipKind() == ValueOwnershipKind::Guaranteed) |
| return true; |
| |
| // If we have an unowned value, then again there is nothing left to do. |
| if (Value.getOwnershipKind() == ValueOwnershipKind::Unowned) |
| return true; |
| |
| if (auto *ParentBlock = Value->getParentBlock()) { |
| if (DEBlocks.isDeadEnd(ParentBlock)) { |
| DEBUG(llvm::dbgs() << " Ignoring transitively unreachable value " |
| << "without users!\n" |
| << " Function: '" << Value->getFunction()->getName() |
| << "'\n" |
| << " Value: " << *Value << '\n'); |
| return true; |
| } |
| } |
| |
| if (!isValueAddressOrTrivial(Value, Mod)) { |
| return !handleError([&] { |
| llvm::errs() << "Function: '" << Value->getFunction()->getName() << "'\n" |
| << "Non trivial values, non address values, and non " |
| "guaranteed function args must have at least one " |
| "lifetime ending use?!\n" |
| << "Value: " << *Value << '\n'; |
| }); |
| } |
| |
| return true; |
| } |
| |
| bool SILValueOwnershipChecker::isGuaranteedFunctionArgWithLifetimeEndingUses( |
| SILFunctionArgument *Arg, |
| const llvm::SmallVectorImpl<GeneralizedUser> &LifetimeEndingUsers) const { |
| if (Arg->getOwnershipKind() != ValueOwnershipKind::Guaranteed) |
| return true; |
| |
| return handleError([&] { |
| llvm::errs() << " Function: '" << Arg->getFunction()->getName() << "'\n" |
| << " Guaranteed function parameter with life ending uses!\n" |
| << " Value: " << *Arg; |
| for (const auto &U : LifetimeEndingUsers) { |
| llvm::errs() << " Lifetime Ending User: " << *U; |
| } |
| llvm::errs() << '\n'; |
| }); |
| } |
| |
| bool SILValueOwnershipChecker::isSubobjectProjectionWithLifetimeEndingUses( |
| SILValue Value, |
| const llvm::SmallVectorImpl<GeneralizedUser> &LifetimeEndingUsers) const { |
| return handleError([&] { |
| llvm::errs() << " Function: '" << Value->getFunction()->getName() |
| << "'\n" |
| << " Subobject projection with life ending uses!\n" |
| << " Value: " << *Value; |
| for (const auto &U : LifetimeEndingUsers) { |
| llvm::errs() << " Lifetime Ending User: " << *U; |
| } |
| llvm::errs() << '\n'; |
| }); |
| } |
| |
| bool SILValueOwnershipChecker::checkUses() { |
| DEBUG(llvm::dbgs() << " Gathering and classifying uses!\n"); |
| |
| // First go through V and gather up its uses. While we do this we: |
| // |
| // 1. Verify that none of the uses are in the same block. This would be an |
| // overconsume so in this case we assert. |
| // 2. Verify that the uses are compatible with our ownership convention. |
| gatherUsers(LifetimeEndingUsers, RegularUsers); |
| |
| // We can only have no lifetime ending uses if we have: |
| // |
| // 1. A trivial typed value. |
| // 2. An address type value. |
| // 3. A guaranteed function argument. |
| // |
| // In the first two cases, it is easy to see that there is nothing further to |
| // do but return false. |
| // |
| // In the case of a function argument, one must think about the issues a bit |
| // more. Specifically, we should have /no/ lifetime ending uses of a |
| // guaranteed function argument, since a guaranteed function argument should |
| // outlive the current function always. |
| if (LifetimeEndingUsers.empty() && checkValueWithoutLifetimeEndingUses()) { |
| return false; |
| } |
| |
| DEBUG(llvm::dbgs() << " Found lifetime ending users! Performing initial " |
| "checks\n"); |
| |
| // See if we have a guaranteed function address. Guaranteed function addresses |
| // should never have any lifetime ending uses. |
| if (auto *Arg = dyn_cast<SILFunctionArgument>(Value)) { |
| if (!isGuaranteedFunctionArgWithLifetimeEndingUses(Arg, |
| LifetimeEndingUsers)) { |
| return false; |
| } |
| } |
| |
| // Check if we are an instruction that forwards forwards guaranteed |
| // ownership. In such a case, we are a subobject projection. We should not |
| // have any lifetime ending uses. |
| if (isGuaranteedForwardingValue(Value) && |
| Value.getOwnershipKind() == ValueOwnershipKind::Guaranteed) { |
| if (!isSubobjectProjectionWithLifetimeEndingUses(Value, |
| LifetimeEndingUsers)) { |
| return false; |
| } |
| } |
| |
| // Then add our non lifetime ending users and their blocks to the |
| // BlocksWithNonLifetimeEndingUses map. While we do this, if we have multiple |
| // uses in the same block, we only accept the last use since from a liveness |
| // perspective that is all we care about. |
| uniqueNonLifetimeEndingUsers(RegularUsers); |
| |
| // Finally, we go through each one of our lifetime ending users performing the |
| // following operation: |
| // |
| // 1. Verifying that no two lifetime ending users are in the same block. This |
| // is accomplished by adding the user blocks to the |
| // BlocksWithLifetimeEndingUses list. This avoids double consumes. |
| // |
| // 2. Verifying that no predecessor is a block with a lifetime ending use. The |
| // reason why this is necessary is because we wish to not add elements to the |
| // worklist twice. Thus we want to check if we have already visited a |
| // predecessor. |
| llvm::SmallVector<std::pair<GeneralizedUser, SILBasicBlock *>, 32> |
| PredsToAddToWorklist; |
| for (GeneralizedUser User : LifetimeEndingUsers) { |
| SILBasicBlock *UserBlock = User.getParent(); |
| // If the block does over consume, we either assert or return false. We only |
| // return false when debugging. |
| if (doesBlockDoubleConsume(UserBlock, User, true)) { |
| return handleError([] {}); |
| } |
| |
| // Then check if the given block has a use after free. |
| if (doesBlockContainUseAfterFree(User, UserBlock)) { |
| return handleError([] {}); |
| } |
| |
| // If this user is in the same block as the value, do not visit |
| // predecessors. We must be extra tolerant here since we allow for |
| // unreachable code. |
| if (UserBlock == Value->getParentBlock()) |
| continue; |
| |
| // Then for each predecessor of this block... |
| for (auto *Pred : UserBlock->getPredecessorBlocks()) { |
| // If this block is not a block that we have already put on the list, add |
| // it to the worklist. |
| PredsToAddToWorklist.push_back({User, Pred}); |
| } |
| } |
| |
| for (const auto &I : LifetimeEndingUsers) { |
| // Finally add the user block to the visited list so we do not try to add it |
| // to our must visit successor list. |
| VisitedBlocks.insert(I.getParent()); |
| } |
| |
| // Make sure not to add predecessors to our worklist if we only have 1 |
| // lifetime ending user and it is in the same block as our def. |
| if (LifetimeEndingUsers.size() == 1 && |
| LifetimeEndingUsers[0].getParent() == Value->getParentBlock()) { |
| return true; |
| } |
| |
| // Now that we have marked all of our producing blocks, we go through our |
| // PredsToAddToWorklist list and add our preds, making sure that none of these |
| // preds are in BlocksWithLifetimeEndingUses. |
| for (auto Pair : PredsToAddToWorklist) { |
| GeneralizedUser User = Pair.first; |
| SILBasicBlock *PredBlock = Pair.second; |
| |
| // Make sure that the predecessor is not in our |
| // BlocksWithLifetimeEndingUses list. |
| if (doesBlockDoubleConsume(PredBlock, User)) { |
| return handleError([] {}); |
| } |
| |
| if (!VisitedBlocks.insert(PredBlock).second) |
| continue; |
| Worklist.push_back(PredBlock); |
| } |
| |
| return true; |
| } |
| |
| bool SILValueOwnershipChecker::checkDataflow() { |
| DEBUG(llvm::dbgs() << " Beginning to check dataflow constraints\n"); |
| // Until the worklist is empty... |
| while (!Worklist.empty()) { |
| // Grab the next block to visit. |
| SILBasicBlock *BB = Worklist.pop_back_val(); |
| DEBUG(llvm::dbgs() << " Visiting Block: bb" << BB->getDebugID() << '\n'); |
| |
| // Since the block is on our worklist, we know already that it is not a |
| // block with lifetime ending uses, due to the invariants of our loop. |
| |
| // First remove BB from the SuccessorBlocksThatMustBeVisited list. This |
| // ensures that when the algorithm terminates, we know that BB was not the |
| // beginning of a non-covered path to the exit. |
| SuccessorBlocksThatMustBeVisited.erase(BB); |
| |
| // Then remove BB from BlocksWithNonLifetimeEndingUses so we know that |
| // this block was properly joint post-dominated by our lifetime ending |
| // users. |
| BlocksWithNonLifetimeEndingUses.erase(BB); |
| |
| // Ok, now we know that we do not have an overconsume. If this block does |
| // not end in a no return function, we need to update our state for our |
| // successors to make sure by the end of the traversal we visit them. |
| // |
| // We must consider such no-return blocks since we may be running during |
| // SILGen before NoReturn folding has run. |
| for (SILBasicBlock *SuccBlock : BB->getSuccessorBlocks()) { |
| // If we already visited the successor, there is nothing to do since we |
| // already visited the successor. |
| if (VisitedBlocks.count(SuccBlock)) |
| continue; |
| |
| // Then check if the successor is a transitively unreachable block. In |
| // such a case, we ignore it since we are going to leak along that path. |
| if (DEBlocks.isDeadEnd(SuccBlock)) |
| continue; |
| |
| // Otherwise, add the successor to our SuccessorBlocksThatMustBeVisited |
| // set to ensure that we assert if we do not visit it by the end of the |
| // algorithm. |
| SuccessorBlocksThatMustBeVisited.insert(SuccBlock); |
| } |
| |
| // If we are at the dominating block of our walk, continue. There is nothing |
| // further to do since we do not want to visit the predecessors of our |
| // dominating block. On the other hand, we do want to add its successors to |
| // the SuccessorBlocksThatMustBeVisited set. |
| if (BB == Value->getParentBlock()) |
| continue; |
| |
| // Then for each predecessor of this block: |
| // |
| // 1. If we have visited the predecessor already, that it is not a block |
| // with lifetime ending uses. If it is a block with uses, then we have a |
| // double release... so assert. If not, we continue. |
| // |
| // 2. We add the predecessor to the worklist if we have not visited it yet. |
| for (auto *PredBlock : BB->getPredecessorBlocks()) { |
| if (doesBlockDoubleConsume(PredBlock)) { |
| return handleError([] {}); |
| } |
| |
| if (VisitedBlocks.count(PredBlock)) { |
| continue; |
| } |
| |
| VisitedBlocks.insert(PredBlock); |
| Worklist.push_back(PredBlock); |
| } |
| } |
| |
| // Make sure that we visited all successor blocks that we needed to visit to |
| // make sure we didn't leak. |
| if (!SuccessorBlocksThatMustBeVisited.empty()) { |
| return handleError([&] { |
| llvm::errs() |
| << "Function: '" << Value->getFunction()->getName() << "'\n" |
| << "Error! Found a leak due to a consuming post-dominance failure!\n" |
| << " Value: " << *Value << " Post Dominating Failure Blocks:\n"; |
| for (auto *BB : SuccessorBlocksThatMustBeVisited) { |
| llvm::errs() << " bb" << BB->getDebugID(); |
| } |
| llvm::errs() << '\n'; |
| }); |
| } |
| |
| // Make sure that we do not have any lifetime ending uses left to visit that |
| // are not transitively unreachable blocks. If we do, then these non lifetime |
| // ending uses must be outside of our "alive" blocks implying a use-after |
| // free. |
| if (!BlocksWithNonLifetimeEndingUses.empty()) { |
| for (auto &Pair : BlocksWithNonLifetimeEndingUses) { |
| if (DEBlocks.isDeadEnd(Pair.first)) { |
| continue; |
| } |
| |
| return handleError([&] { |
| llvm::errs() << "Function: '" << Value->getFunction()->getName() |
| << "'\n" |
| << "Found use after free due to unvisited non lifetime " |
| "ending uses?!\n" |
| << "Value: " << *Value << " Remaining Users:\n"; |
| for (auto &Pair : BlocksWithNonLifetimeEndingUses) { |
| llvm::errs() << "User:" << *Pair.second << "Block: bb" |
| << Pair.first->getDebugID() << "\n"; |
| } |
| llvm::errs() << "\n"; |
| }); |
| } |
| } |
| |
| return true; |
| } |
| |
| //===----------------------------------------------------------------------===// |
| // Top Level Entrypoints |
| //===----------------------------------------------------------------------===// |
| |
| void SILInstruction::verifyOperandOwnership() const { |
| #ifndef NDEBUG |
| if (isStaticInitializerInst()) |
| return; |
| |
| // If SILOwnership is not enabled, do not perform verification. |
| if (!getModule().getOptions().EnableSILOwnership) |
| return; |
| |
| // If the given function has unqualified ownership or we have been asked by |
| // the user not to verify this function, there is nothing to verify. |
| if (!getFunction()->hasQualifiedOwnership() || |
| !getFunction()->shouldVerifyOwnership()) |
| return; |
| |
| // If we are testing the verifier, bail so we only print errors once when |
| // performing a full verification, instead of additionally in the SILBuilder. |
| if (IsSILOwnershipVerifierTestingEnabled) |
| return; |
| |
| // If this is a terminator instruction, do not verify in SILBuilder. This is |
| // because when building a new function, one must create the destination block |
| // first which is an unnatural pattern and pretty brittle. |
| if (isa<TermInst>(this)) |
| return; |
| |
| ErrorBehaviorKind ErrorBehavior; |
| if (IsSILOwnershipVerifierTestingEnabled) { |
| ErrorBehavior = ErrorBehaviorKind::PrintMessageAndReturnFalse; |
| } else { |
| ErrorBehavior = ErrorBehaviorKind::PrintMessageAndAssert; |
| } |
| auto *Self = const_cast<SILInstruction *>(this); |
| for (const Operand &Op : getAllOperands()) { |
| if (isTypeDependentOperand(Op)) |
| continue; |
| // Skip any SILUndef that we see. |
| if (isa<SILUndef>(Op.get())) |
| continue; |
| OwnershipCompatibilityUseChecker(getModule(), Op, Op.get(), ErrorBehavior) |
| .check(Self); |
| } |
| #endif |
| } |
| |
| void SILValue::verifyOwnership(SILModule &Mod, DeadEndBlocks *DEBlocks) const { |
| #ifndef NDEBUG |
| // If we are SILUndef, just bail. SILUndef can pair with anything. Any uses of |
| // the SILUndef will make sure that the matching checks out. |
| if (isa<SILUndef>(*this)) |
| return; |
| |
| // Since we do not have SILUndef, we now know that getFunction() should return |
| // a real function. Assert in case this assumption is no longer true. |
| SILFunction *F = (*this)->getFunction(); |
| assert(F && "Instructions and arguments should have a function"); |
| |
| // If the given function has unqualified ownership or we have been asked by |
| // the user not to verify this function, there is nothing to verify. |
| if (!F->hasQualifiedOwnership() || !F->shouldVerifyOwnership()) |
| return; |
| |
| ErrorBehaviorKind ErrorBehavior; |
| if (IsSILOwnershipVerifierTestingEnabled) { |
| ErrorBehavior = ErrorBehaviorKind::PrintMessageAndReturnFalse; |
| } else { |
| ErrorBehavior = ErrorBehaviorKind::PrintMessageAndAssert; |
| } |
| llvm::SmallPtrSet<SILBasicBlock *, 32> LiveBlocks; |
| if (DEBlocks) { |
| SILValueOwnershipChecker(Mod, *DEBlocks, *this, ErrorBehavior, LiveBlocks) |
| .check(); |
| } else { |
| DeadEndBlocks DEBlocks((*this)->getFunction()); |
| SILValueOwnershipChecker(Mod, DEBlocks, *this, ErrorBehavior, LiveBlocks) |
| .check(); |
| } |
| #endif |
| } |
| |
| bool OwnershipChecker::checkValue(SILValue Value) { |
| RegularUsers.clear(); |
| LifetimeEndingUsers.clear(); |
| LiveBlocks.clear(); |
| |
| // If we are SILUndef, just bail. SILUndef can pair with anything. Any uses of |
| // the SILUndef will make sure that the matching checks out. |
| if (isa<SILUndef>(Value)) |
| return false; |
| |
| // Since we do not have SILUndef, we now know that getFunction() should return |
| // a real function. Assert in case this assumption is no longer true. |
| SILFunction *F = Value->getFunction(); |
| assert(F && "Instructions and arguments should have a function"); |
| |
| // If the given function has unqualified ownership, there is nothing further |
| // to verify. |
| if (!F->hasQualifiedOwnership()) |
| return false; |
| |
| ErrorBehaviorKind ErrorBehavior(ErrorBehaviorKind::ReturnFalse); |
| SILValueOwnershipChecker Checker(Mod, DEBlocks, Value, ErrorBehavior, |
| LiveBlocks); |
| if (!Checker.check()) { |
| return false; |
| } |
| |
| // TODO: Make this more efficient. |
| copy(Checker.getRegularUsers(), std::back_inserter(RegularUsers)); |
| copy(Checker.getLifetimeEndingUsers(), |
| std::back_inserter(LifetimeEndingUsers)); |
| return true; |
| } |