//===--- MoveOnlyAddressCheckerUtils.cpp ----------------------------------===// // // This source file is part of the Swift.org open source project // // Copyright (c) 2014 - 2022 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 // //===----------------------------------------------------------------------===// /// /// Move Only Checking of Addresses /// ------------------------------- /// /// In this file, we implement move checking of addresses. This allows for the /// compiler to perform move checking of address only lets, vars, inout args, /// and mutating self. /// /// Move Address Checking in Swift /// ------------------------------ /// /// In order to not have to rewrite all of SILGen to avoid copies, Swift has /// taken an approach where SILGen marks moveonly addresses with a special /// marker instruction and emits copies when it attempts to access move only /// addresses. Then this algorithm fixed up SILGen's output by analyzing the /// memory uses of a marked memory root location recursively using AccessPath /// based analyses and then attempting to transform those uses based off of the /// marked kind into one of a few variants of "simple move only address form" /// (see below for more information). If the pass is unable to reason that it /// can safely transform the uses into said form, we emit a diagnostic stating /// the error to the user. If we emit said diagnostic, we then bail early. If we /// do not emit a diagnostic, we then transform the IR so that the move only /// address uses are in said form. This then guarantees that despite SILGen /// emitting move only types with copies, in the end, our move only types are /// never copied. As an additional check, once the pass has run we emit an extra /// diagnostic if we find any copies of move only types so that the user can be /// sure that any accepted program does not copy move only types. /// /// Simple Move Only Address Form /// ----------------------------- /// /// We define a memory location to be in "simple move only address" form (SMOA /// form for ease of typing) to mean that along any path from an init of the /// address to a consume of the address, all uses are guaranteed to be semantic /// "borrow uses" instead of semantic "copy uses". Additionally, SMOA does not /// consider destroy_addr to be a true consuming use since it will rewrite /// destroy_addr as necessary so the consuming uses are defined by consuming /// uses modulo destroy_addr. /// /// An example of a memory location in "simple move only address form" is the /// following: /// /// ``` /// // Memory is defined /// %0 = alloc_stack $Type /// /// // Initial initialization. /// store %input to [init] %0 : $Type /// /// // Sequence of borrow uses. /// %1 = load_borrow %0 : $Type /// apply %f(%1) : $@convention(thin) (@guaranteed Type) -> () /// end_borrow %1 /// apply %f2(%0) : $@convention(thin) (@in_guaranteed Type) -> () /// /// // Assign is ok since we are just consuming the value. /// store %input2 to [assign] %0 : $*Type /// /// // More borrow uses. /// %3 = load_borrow %0 : $*Type /// apply %f(%3) : $@convention(thin) (@guaranteed Type) -> () /// end_borrow %1 /// apply %f2(%0) : $@convention(thin) (@in_guaranteed Type) -> () /// /// // Final destroy /// destroy_addr %0 : $Type /// ``` /// /// An example of an instruction not in SMOA form is: /// /// ``` /// // Memory is defined /// %0 = alloc_stack $Type /// /// // Initial initialization. /// store %input to [init] %0 : $*Type /// /// // Perform a load + copy of %0 to pass as an argument to %f. /// %1 = load [copy] %0 : $*Type /// apply %f(%1) : $@convention(thin) (@guaranteed Type) -> () /// destroy_value %1 : $Type /// /// // Initialize other variable. /// %otherVar = alloc_stack $Type /// copy_addr %0 to [initialization] %otherVar : $*Type /// ... /// /// // Final destroy that is not part of the use set. /// destroy_addr %0 : $*Type /// ``` /// /// The variants of SMOA form can be classified by the specific /// mark_unresolved_non_copyable_value kind put on the checker mark /// instruction and are as follows: /// /// 1. no_consume_or_assign. This means that the address can only be consumed by /// destroy_addr and otherwise is only read from. This simulates guaranteed /// semantics. /// /// 2. consumable_and_assignable. This means that the address can be consumed /// (e.x.: take/pass to a +1 function) or assigned to. Additionally, the value /// is supposed to have its lifetime end along all program paths locally in the /// function. This simulates a local var's semantics. /// /// 3. assignable_but_not_consumable. This means that the address can be /// assigned over, but cannot be taken from. It additionally must have a valid /// value in it and the end of its lifetime. This simulates accesses to class /// fields, globals, and escaping mutable captures where we want the user to be /// able to update the value, but allowing for escapes of the value would break /// memory safety. In all cases where this is used, the /// mark_unresolved_non_copyable_value is used as the initial def of the value /// lifetime. Example: /// /// 4. initable_but_not_consumable. This means that the address can only be /// initialized once but cannot be taken from or assigned over. It is assumed /// that the initial def will always be the mark_unresolved_non_copyable_value /// and that the value will be uninitialized at that point. Example: /// /// Algorithm Stages In Detail /// -------------------------- /// /// To implement this, our algorithm works in 4 stages: a use classification /// stage, a dataflow stage, and then depending on success/failure one of two /// transform stages. /// /// Use Classification Stage /// ~~~~~~~~~~~~~~~~~~~~~~~~ /// /// Here we use an AccessPath based analysis to transitively visit all uses of /// our marked address and classify a use as one of the following kinds of uses: /// /// * init - store [init], copy_addr [init] %dest. /// * destroy - destroy_addr. /// * pureTake - load [take], copy_addr [take] %src. /// * copyTransformableToTake - certain load [copy], certain copy_addr ![take] /// %src of a temporary %dest. /// * reinit - store [assign], copy_addr ![init] %dest /// * borrow - load_borrow, a load [copy] without consuming uses. /// * livenessOnly - a read only use of the address. /// /// We classify these by adding them to several disjoint SetVectors which track /// membership. /// /// When we classify an instruction as copyTransformableToTake, we perform some /// extra preprocessing to determine if we can actually transform this copy to a /// take. This means that we: /// /// 1. For loads, we perform object move only checking. If we find a need for /// multiple copies, we emit an error. If we find no extra copies needed, we /// classify the load [copy] as a take if it has any last consuming uses and a /// borrow if it only has destroy_addr consuming uses. /// /// 2. For copy_addr, we pattern match if a copy_addr is initializing a "simple /// temporary" (an alloc_stack with only one use that initializes it, a /// copy_addr [init] in the same block). In this case, if the copy_addr only has /// destroy_addr consuming uses, we treat it as a borrow... otherwise, we treat /// it as a take. If we find any extra initializations, we fail the visitor so /// we emit a "I don't understand this error" so that users report this case and /// we can extend it as appropriate. /// /// If we fail in either case, if we emit an error, we bail early with success /// so we can assume invariants later in the dataflow stages that make the /// dataflow easier. /// /// Dataflow Stage /// ~~~~~~~~~~~~~~ /// /// To perform our dataflow, we take our classified uses and initialize field /// sensitive pruned liveness with the data. We then use field sensitive pruned /// liveness and our check kinds to determine if all of our copy uses that could /// not be changed into borrows are on the liveness boundary of the memory. If /// they are within the liveness boundary, then we know a copy is needed and we /// emit an error to the user. Otherwise, we know that we can change them /// semantically into a take. /// /// Success Transformation /// ~~~~~~~~~~~~~~~~~~~~~~ /// /// Now that we know that we can change our address into "simple move only /// address form", we transform the IR in the following way: /// /// 1. Any load [copy] that are classified as borrows are changed to /// load_borrow. /// 2. Any load [copy] that are classified as takes are changed to load [take]. /// 3. Any copy_addr [init] temporary allocation are eliminated with their /// destroy_addr. All uses are placed on the source address. /// 4. Any destroy_addr that is paired with a copyTransformableToTake is /// eliminated. /// /// Fail Transformation /// ~~~~~~~~~~~~~~~~~~~ /// /// If we emit any diagnostics, we loop through the function one last time after /// we are done processing and convert all load [copy]/copy_addr of move only /// types into their explicit forms. We take a little more compile time, but we /// are going to fail anyways at this point, so it is ok to do so since we will /// fail before attempting to codegen into LLVM IR. /// /// Final Black Box Checks on Success /// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ /// /// Finally since we want to be able to guarantee to users 100% that the /// compiler will reject programs even if the checker gives a false success for /// some reason due to human compiler writer error, we do a last pass over the /// IR and emit an error diagnostic on any copies of move only types that we /// see. The error states to the user that this is a compiler bug and to file a /// bug report. Since it is a completely separate, simple implementation, this /// gives the user of our implementation the confidence to know that the /// compiler even in the face of complexity in the checker will emit correct /// code. /// //===----------------------------------------------------------------------===// #define DEBUG_TYPE "sil-move-only-checker" #include "swift/AST/AccessScope.h" #include "swift/AST/DiagnosticEngine.h" #include "swift/AST/DiagnosticsSIL.h" #include "swift/AST/SemanticAttrs.h" #include "swift/Basic/Assertions.h" #include "swift/Basic/Debug.h" #include "swift/Basic/Defer.h" #include "swift/Basic/FrozenMultiMap.h" #include "swift/Basic/SmallBitVector.h" #include "swift/SIL/ApplySite.h" #include "swift/SIL/BasicBlockBits.h" #include "swift/SIL/BasicBlockData.h" #include "swift/SIL/BasicBlockDatastructures.h" #include "swift/SIL/BasicBlockUtils.h" #include "swift/SIL/Consumption.h" #include "swift/SIL/DebugUtils.h" #include "swift/SIL/FieldSensitivePrunedLiveness.h" #include "swift/SIL/InstructionUtils.h" #include "swift/SIL/MemAccessUtils.h" #include "swift/SIL/OSSACompleteLifetime.h" #include "swift/SIL/OwnershipUtils.h" #include "swift/SIL/PrunedLiveness.h" #include "swift/SIL/SILArgument.h" #include "swift/SIL/SILArgumentConvention.h" #include "swift/SIL/SILBasicBlock.h" #include "swift/SIL/SILBuilder.h" #include "swift/SIL/SILFunction.h" #include "swift/SIL/SILInstruction.h" #include "swift/SIL/SILUndef.h" #include "swift/SIL/SILValue.h" #include "swift/SILOptimizer/Analysis/ClosureScope.h" #include "swift/SILOptimizer/Analysis/DeadEndBlocksAnalysis.h" #include "swift/SILOptimizer/Analysis/DominanceAnalysis.h" #include "swift/SILOptimizer/Analysis/NonLocalAccessBlockAnalysis.h" #include "swift/SILOptimizer/PassManager/Transforms.h" #include "swift/SILOptimizer/Utils/InstructionDeleter.h" #include "swift/SILOptimizer/Utils/OSSACanonicalizeOwned.h" #include "llvm/ADT/DenseMap.h" #include "llvm/ADT/MapVector.h" #include "llvm/ADT/PointerIntPair.h" #include "llvm/ADT/PointerUnion.h" #include "llvm/ADT/STLExtras.h" #include "llvm/ADT/SmallBitVector.h" #include "llvm/ADT/SmallPtrSet.h" #include "llvm/ADT/SmallVector.h" #include "llvm/Support/Debug.h" #include "llvm/Support/ErrorHandling.h" #include "MoveOnlyAddressCheckerUtils.h" #include "MoveOnlyBorrowToDestructureUtils.h" #include "MoveOnlyDiagnostics.h" #include "MoveOnlyObjectCheckerUtils.h" #include "MoveOnlyTypeUtils.h" #include "MoveOnlyUtils.h" #include using namespace swift; using namespace swift::siloptimizer; llvm::cl::opt DisableMoveOnlyAddressCheckerLifetimeExtension( "move-only-address-checker-disable-lifetime-extension", llvm::cl::init(false), llvm::cl::desc("Disable the lifetime extension of non-consumed fields of " "move-only values.")); //===----------------------------------------------------------------------===// // MARK: Utilities //===----------------------------------------------------------------------===// struct RAIILLVMDebug { StringRef str; RAIILLVMDebug(StringRef str) : str(str) { LLVM_DEBUG(llvm::dbgs() << "===>>> Starting " << str << '\n'); } RAIILLVMDebug(StringRef str, SILInstruction *u) : str(str) { LLVM_DEBUG(llvm::dbgs() << "===>>> Starting " << str << ":" << *u); } ~RAIILLVMDebug() { LLVM_DEBUG(llvm::dbgs() << "===<<< Completed " << str << '\n'); } }; static void insertDebugValueBefore(SILInstruction *insertPt, DebugVarCarryingInst debugVar, llvm::function_ref operand) { if (!debugVar) { return; } auto varInfo = debugVar.getVarInfo(); if (!varInfo) { return; } SILBuilderWithScope debugInfoBuilder(insertPt); debugInfoBuilder.setCurrentDebugScope(debugVar->getDebugScope()); debugInfoBuilder.createDebugValue(debugVar->getLoc(), operand(), *varInfo, DontPoisonRefs, UsesMoveableValueDebugInfo); } static void convertMemoryReinitToInitForm(SILInstruction *memInst, DebugVarCarryingInst debugVar) { SILValue dest; switch (memInst->getKind()) { default: llvm_unreachable("unsupported?!"); case SILInstructionKind::CopyAddrInst: { auto *cai = cast(memInst); cai->setIsInitializationOfDest(IsInitialization_t::IsInitialization); dest = cai->getDest(); break; } case SILInstructionKind::StoreInst: { auto *si = cast(memInst); si->setOwnershipQualifier(StoreOwnershipQualifier::Init); dest = si->getDest(); break; } } // Insert a new debug_value instruction after the reinitialization, so that // the debugger knows that the variable is in a usable form again. insertDebugValueBefore(memInst->getNextInstruction(), debugVar, [&]{ return debugVar.getOperandForDebugValueClone(); }); } /// Is this a reinit instruction that we know how to convert into its init form. static bool isReinitToInitConvertibleInst(SILInstruction *memInst) { switch (memInst->getKind()) { default: return false; case SILInstructionKind::CopyAddrInst: { auto *cai = cast(memInst); return !cai->isInitializationOfDest(); } case SILInstructionKind::StoreInst: { auto *si = cast(memInst); return si->getOwnershipQualifier() == StoreOwnershipQualifier::Assign; } } } using ScopeRequiringFinalInit = DiagnosticEmitter::ScopeRequiringFinalInit; /// If \p markedAddr's operand must be initialized at the end of the scope it /// introduces, visit those scope ending ends. /// /// Examples: /// (1) inout function argument. Must be initialized at function exit. /// /// sil [ossa] @f : $(inout MOV) -> () /// entry(%addr : $*MOV): /// ... /// return %t : $() // %addr must be initialized here /// /// (2) coroutine. Must be initialized at end_apply/abort_apply. /// /// (%addr, %token) = begin_apply ... -> @yields @inout MOV /// bbN: /// end_apply %token // %addr must be initialized here /// bbM: /// abort_apply %token // %addr must be initialized here /// /// (3) modify access. Must be initialized at end_access. /// /// %addr = begin_access [modify] %location /// /// end_access %addr // %addr must be initialized here /// /// To enforce this requirement, function exiting instructions are treated as /// liveness uses of such addresses, ensuring that the address is initialized at /// that point. static bool visitScopeEndsRequiringInit( MarkUnresolvedNonCopyableValueInst *markedAddr, llvm::function_ref visit) { SILValue operand = markedAddr->getOperand(); // TODO: This should really be a property of the marker instruction. switch (markedAddr->getCheckKind()) { case MarkUnresolvedNonCopyableValueInst::CheckKind:: AssignableButNotConsumable: case MarkUnresolvedNonCopyableValueInst::CheckKind::ConsumableAndAssignable: break; case MarkUnresolvedNonCopyableValueInst::CheckKind::InitableButNotConsumable: case MarkUnresolvedNonCopyableValueInst::CheckKind::NoConsumeOrAssign: return false; case MarkUnresolvedNonCopyableValueInst::CheckKind::Invalid: llvm_unreachable("invalid check!?"); } // Look through wrappers. if (auto m = dyn_cast(operand)) { operand = m->getOperand(); } // Check for inout types of arguments that are marked with consumable and // assignable. if (auto *fArg = dyn_cast(operand)) { switch (fArg->getArgumentConvention()) { case SILArgumentConvention::Indirect_In: case SILArgumentConvention::Indirect_Out: case SILArgumentConvention::Indirect_In_Guaranteed: case SILArgumentConvention::Indirect_In_CXX: case SILArgumentConvention::Direct_Guaranteed: case SILArgumentConvention::Direct_Owned: case SILArgumentConvention::Direct_Unowned: case SILArgumentConvention::Pack_Guaranteed: case SILArgumentConvention::Pack_Owned: case SILArgumentConvention::Pack_Out: return false; case SILArgumentConvention::Indirect_Inout: case SILArgumentConvention::Indirect_InoutAliasable: case SILArgumentConvention::Pack_Inout: LLVM_DEBUG(llvm::dbgs() << "Found inout arg: " << *fArg); SmallVector exitBlocks; markedAddr->getFunction()->findExitingBlocks(exitBlocks); for (auto *block : exitBlocks) { visit(block->getTerminator(), ScopeRequiringFinalInit::InoutArgument); } return true; } } // Check for yields from a modify coroutine. if (auto bai = dyn_cast_or_null(operand->getDefiningInstruction())) { for (auto *use : bai->getEndApplyUses()) { auto *inst = use->getUser(); assert(isa(inst) || isa(inst) || isa(inst)); visit(inst, ScopeRequiringFinalInit::Coroutine); } return true; } // Check for modify accesses. if (auto access = dyn_cast(operand)) { if (access->getAccessKind() != SILAccessKind::Modify) { return false; } for (auto *inst : access->getEndAccesses()) { visit(inst, ScopeRequiringFinalInit::ModifyMemoryAccess); } return true; } return false; } static bool isCopyableValue(SILValue value) { if (value->getType().isMoveOnly()) return false; if (isa(value)) return false; return true; } //===----------------------------------------------------------------------===// // MARK: Find Candidate Mark Must Checks //===----------------------------------------------------------------------===// void swift::siloptimizer:: searchForCandidateAddressMarkUnresolvedNonCopyableValueInsts( SILFunction *fn, PostOrderAnalysis *poa, llvm::SmallSetVector &moveIntroducersToProcess, DiagnosticEmitter &diagnosticEmitter) { auto *po = poa->get(fn); for (auto *block : po->getPostOrder()) { for (auto &ii : llvm::make_range(block->rbegin(), block->rend())) { auto *mmci = dyn_cast(&ii); if (!mmci || !mmci->hasMoveCheckerKind() || !mmci->getType().isAddress()) continue; moveIntroducersToProcess.insert(mmci); } } } //===----------------------------------------------------------------------===// // MARK: Use State //===----------------------------------------------------------------------===// namespace { struct UseState { MarkUnresolvedNonCopyableValueInst *address; DominanceInfo *const domTree; // FIXME: [partial_consume_of_deiniting_aggregate_with_drop_deinit] Keep track // of the projections out of which a use emerges and use that to tell // whether a particular partial consume is allowed. // // For example, give the TransitiveAddressWalker's worklist a // client-dependent context and look in that to determine whether a // relevant drop_deinit has been seen when erroring on partial // destructuring. bool sawDropDeinit = false; using InstToBitMap = llvm::SmallMapVector; std::optional cachedNumSubelements; /// The blocks that consume fields of the value. /// /// A map from blocks to a bit vector recording which fields were destroyed /// in each. llvm::SmallMapVector consumingBlocks; /// A map from destroy_addr to the part of the type that it destroys. llvm::SmallMapVector destroys; /// Maps a non-consuming use to the part of the type that it requires /// liveness for. InstToBitMap nonconsumingUses; /// A map from a load [copy] or load [take] that we determined must be /// converted to a load_borrow to the part of the type tree that it needs to /// borrow. /// /// NOTE: This does not include actual load_borrow which are treated /// just as liveness uses. /// /// NOTE: load_borrow that we actually copy, we canonicalize early to a load /// [copy] + begin_borrow so that we do not need to convert load_borrow to a /// normal load when rewriting. llvm::SmallMapVector borrows; /// A copy_addr, load [copy], or load [take] that we determine is semantically /// truly a take mapped to the part of the type tree that it needs to use. /// /// DISCUSSION: A copy_addr [init] or load [copy] are considered actually /// takes if they are not destroyed with a destroy_addr/destroy_value. We /// consider them to be takes since after the transform they must be a take. /// /// Importantly, these we know are never copied and are only consumed once. InstToBitMap takeInsts; /// A map from a copy_addr, load [copy], or load [take] that we determine /// semantically are true copies to the part of the type tree they must copy. /// /// DISCUSSION: One of these instructions being a true copy means that their /// result or destination is used in a way that some sort of extra copy is /// needed. Example: /// /// %0 = load [take] %addr /// %1 = copy_value %0 /// consume(%0) /// consume(%1) /// /// Notice how the load [take] above semantically requires a copy since it was /// consumed twice even though SILGen emitted it as a load [take]. /// /// We represent these separately from \p takeInsts since: /// /// 1. InstToBitMap copyInsts; /// A map from an instruction that initializes memory to the description of /// the part of the type tree that it initializes. InstToBitMap initInsts; SmallFrozenMultiMap initToValueMultiMap; /// memInstMustReinitialize insts. Contains both insts like copy_addr/store /// [assign] that are reinits that we will convert to inits and true reinits. InstToBitMap reinitInsts; SmallFrozenMultiMap reinitToValueMultiMap; /// The set of drop_deinits of this mark_unresolved_non_copyable_value llvm::SmallSetVector dropDeinitInsts; /// Instructions indicating the end of a scope at which addr must be /// initialized. /// /// Adding such instructions to liveness forces the value to be initialized at /// them as required. /// /// See visitScopeEndsRequiringInit. llvm::MapVector scopeEndsRequiringInit; /// We add debug_values to liveness late after we diagnose, but before we /// hoist destroys to ensure that we do not hoist destroys out of access /// scopes. DebugValueInst *debugValue = nullptr; UseState(DominanceInfo *domTree) : domTree(domTree) {} SILFunction *getFunction() const { return address->getFunction(); } /// The number of fields in the exploded type. unsigned getNumSubelements() { if (!cachedNumSubelements) { cachedNumSubelements = TypeSubElementCount(address); } return *cachedNumSubelements; } SmallBitVector &getOrCreateAffectedBits(SILInstruction *inst, InstToBitMap &map) { auto iter = map.find(inst); if (iter == map.end()) { iter = map.insert({inst, SmallBitVector(getNumSubelements())}).first; } return iter->second; } void setAffectedBits(SILInstruction *inst, SmallBitVector const &bits, InstToBitMap &map) { getOrCreateAffectedBits(inst, map) |= bits; } void setAffectedBits(SILInstruction *inst, TypeTreeLeafTypeRange range, InstToBitMap &map) { range.setBits(getOrCreateAffectedBits(inst, map)); } void recordLivenessUse(SILInstruction *inst, SmallBitVector const &bits) { setAffectedBits(inst, bits, nonconsumingUses); } void recordLivenessUse(SILInstruction *inst, TypeTreeLeafTypeRange range) { setAffectedBits(inst, range, nonconsumingUses); } void recordReinitUse(SILInstruction *inst, SILValue value, TypeTreeLeafTypeRange range) { reinitToValueMultiMap.insert(inst, value); setAffectedBits(inst, range, reinitInsts); } void recordInitUse(SILInstruction *inst, SILValue value, TypeTreeLeafTypeRange range) { initToValueMultiMap.insert(inst, value); setAffectedBits(inst, range, initInsts); } void recordTakeUse(SILInstruction *inst, TypeTreeLeafTypeRange range) { setAffectedBits(inst, range, takeInsts); } void recordCopyUse(SILInstruction *inst, TypeTreeLeafTypeRange range) { setAffectedBits(inst, range, copyInsts); } /// Returns true if this is an instruction that is used by the pass to ensure /// that we reinit said argument if we consumed it in a region of code. /// /// Example: /// /// 1. In the case of an inout argument, this will contain the terminator /// instruction. /// 2. In the case of a ref_element_addr or a global, this will contain the /// end_access. std::optional isImplicitEndOfLifetimeLivenessUses(SILInstruction *inst) const { auto iter = scopeEndsRequiringInit.find(inst); if (iter == scopeEndsRequiringInit.end()) { return std::nullopt; } return {iter->second}; } /// Returns true if the given instruction is within the same block as a reinit /// and precedes a reinit instruction in that block. bool precedesReinitInSameBlock(SILInstruction *inst) const { SILBasicBlock *block = inst->getParent(); llvm::SmallSetVector sameBlockReinits; // First, search for all reinits that are within the same block. for (auto &reinit : reinitInsts) { if (reinit.first->getParent() != block) continue; sameBlockReinits.insert(reinit.first); } if (sameBlockReinits.empty()) return false; // Walk down from the given instruction to see if we encounter a reinit. for (auto ii = std::next(inst->getIterator()); ii != block->end(); ++ii) { if (sameBlockReinits.contains(&*ii)) return true; } return false; } void clear() { address = nullptr; cachedNumSubelements = std::nullopt; consumingBlocks.clear(); destroys.clear(); nonconsumingUses.clear(); borrows.clear(); copyInsts.clear(); takeInsts.clear(); initInsts.clear(); initToValueMultiMap.reset(); reinitInsts.clear(); reinitToValueMultiMap.reset(); dropDeinitInsts.clear(); scopeEndsRequiringInit.clear(); debugValue = nullptr; } void dump() { llvm::dbgs() << "AddressUseState!\n"; llvm::dbgs() << "Destroys:\n"; for (auto pair : destroys) { llvm::dbgs() << *pair.first; } llvm::dbgs() << "LivenessUses:\n"; for (auto pair : nonconsumingUses) { llvm::dbgs() << *pair.first; } llvm::dbgs() << "Borrows:\n"; for (auto pair : borrows) { llvm::dbgs() << *pair.first; } llvm::dbgs() << "Takes:\n"; for (auto pair : takeInsts) { llvm::dbgs() << *pair.first; } llvm::dbgs() << "Copies:\n"; for (auto pair : copyInsts) { llvm::dbgs() << *pair.first; } llvm::dbgs() << "Inits:\n"; for (auto pair : initInsts) { llvm::dbgs() << *pair.first; } llvm::dbgs() << "Reinits:\n"; for (auto pair : reinitInsts) { llvm::dbgs() << *pair.first; } llvm::dbgs() << "DropDeinits:\n"; for (auto *inst : dropDeinitInsts) { llvm::dbgs() << *inst; } llvm::dbgs() << "Implicit End Of Lifetime Liveness Users:\n"; for (auto pair : scopeEndsRequiringInit) { llvm::dbgs() << pair.first; } llvm::dbgs() << "Debug Value User:\n"; if (debugValue) { llvm::dbgs() << *debugValue; } } void freezeMultiMaps() { initToValueMultiMap.setFrozen(); reinitToValueMultiMap.setFrozen(); } SmallBitVector &getOrCreateConsumingBlock(SILBasicBlock *block) { auto iter = consumingBlocks.find(block); if (iter == consumingBlocks.end()) { iter = consumingBlocks.insert({block, SmallBitVector(getNumSubelements())}) .first; } return iter->second; } void recordConsumingBlock(SILBasicBlock *block, TypeTreeLeafTypeRange range) { auto &consumingBits = getOrCreateConsumingBlock(block); range.setBits(consumingBits); } void recordConsumingBlock(SILBasicBlock *block, SmallBitVector &bits) { auto &consumingBits = getOrCreateConsumingBlock(block); consumingBits |= bits; } void initializeLiveness(FieldSensitiveMultiDefPrunedLiveRange &prunedLiveness); void initializeImplicitEndOfLifetimeLivenessUses() { visitScopeEndsRequiringInit(address, [&](auto *inst, auto kind) { LLVM_DEBUG(llvm::dbgs() << " Adding scope end as liveness user: " << *inst); scopeEndsRequiringInit[inst] = kind; }); } bool isConsume(SILInstruction *inst, SmallBitVector const &bits) const { { auto iter = takeInsts.find(inst); if (iter != takeInsts.end()) { if (bits.anyCommon(iter->second)) return true; } } { auto iter = copyInsts.find(inst); if (iter != copyInsts.end()) { if (bits.anyCommon(iter->second)) return true; } } return false; } bool isCopy(SILInstruction *inst, const SmallBitVector &bv) const { auto iter = copyInsts.find(inst); if (iter != copyInsts.end()) { if (bv.anyCommon(iter->second)) return true; } return false; } bool isLivenessUse(SILInstruction *inst, SmallBitVector const &bits) const { { auto iter = nonconsumingUses.find(inst); if (iter != nonconsumingUses.end()) { if (bits.anyCommon(iter->second)) return true; } } { auto iter = borrows.find(inst); if (iter != borrows.end()) { if (iter->second.intersects(bits)) return true; } } if (!isReinitToInitConvertibleInst(inst)) { auto iter = reinitInsts.find(inst); if (iter != reinitInsts.end()) { if (bits.anyCommon(iter->second)) return true; } } // An "inout terminator use" is an implicit liveness use of the entire // value. This is because we need to ensure that our inout value is // reinitialized along exit paths. if (scopeEndsRequiringInit.count(inst)) return true; return false; } bool isInitUse(SILInstruction *inst, const SmallBitVector &requiredBits) const { { auto iter = initInsts.find(inst); if (iter != initInsts.end()) { if (requiredBits.anyCommon(iter->second)) return true; } } if (isReinitToInitConvertibleInst(inst)) { auto iter = reinitInsts.find(inst); if (iter != reinitInsts.end()) { if (requiredBits.anyCommon(iter->second)) return true; } } return false; } }; } // namespace //===----------------------------------------------------------------------===// // MARK: Partial Apply Utilities //===----------------------------------------------------------------------===// static bool findNonEscapingPartialApplyUses(PartialApplyInst *pai, TypeTreeLeafTypeRange leafRange, UseState &useState) { StackList worklist(pai->getFunction()); for (auto *use : pai->getUses()) worklist.push_back(use); LLVM_DEBUG(llvm::dbgs() << "Searching for partial apply uses!\n"); while (!worklist.empty()) { auto *use = worklist.pop_back_val(); if (use->isTypeDependent()) continue; auto *user = use->getUser(); // These instructions do not cause us to escape. if (isIncidentalUse(user) || isa(user)) continue; // Look through these instructions. if (isa(user) || isa(user) || isa(user) || // If we capture this partial_apply in another partial_apply, then we // know that said partial_apply must not have escaped the value since // otherwise we could not have an inout_aliasable argument or be // on_stack. Process it recursively so that we treat uses of that // partial_apply and applies of that partial_apply as uses of our // partial_apply. // // We have this separately from the other look through sections so that // we can make it clearer what we are doing here. isa(user) || // Likewise with convert_function. Any valid function conversion that // doesn't prevent stack promotion of the closure must retain the // invariants on its transitive uses. isa(user)) { for (auto *use : cast(user)->getUses()) worklist.push_back(use); continue; } // If we have a mark_dependence and are the value, look through the // mark_dependence. if (auto *mdi = dyn_cast(user)) { if (mdi->getValue() == use->get()) { for (auto *use : mdi->getUses()) worklist.push_back(use); continue; } } if (auto apply = FullApplySite::isa(user)) { // If we apply the function or pass the function off to an apply, then we // need to treat the function application as a liveness use of the // variable since if the partial_apply is invoked within the function // application, we may access the captured variable. useState.recordLivenessUse(user, leafRange); if (apply.beginsCoroutineEvaluation()) { // If we have a coroutine, we need to treat the abort_apply and // end_apply as liveness uses since once we execute one of those // instructions, we have returned control to the coroutine which means // that we could then access the captured variable again. auto *bai = cast(user); SmallVector endApplies; SmallVector abortApplies; bai->getCoroutineEndPoints(endApplies, abortApplies); for (auto *eai : endApplies) useState.recordLivenessUse(eai, leafRange); for (auto *aai : abortApplies) useState.recordLivenessUse(aai, leafRange); } continue; } LLVM_DEBUG( llvm::dbgs() << "Found instruction we did not understand... returning false!\n"); LLVM_DEBUG(llvm::dbgs() << "Instruction: " << *user); return false; } return true; } static bool addressBeginsInitialized(MarkUnresolvedNonCopyableValueInst *address) { // FIXME: Whether the initial use is an initialization ought to be entirely // derivable from the CheckKind of the mark instruction. SILValue operand = address->getOperand(); if (auto *mdi = dyn_cast(operand)) { operand = mdi->getValue(); } { // Then check if our markedValue is from an argument that is in, // in_guaranteed, inout, or inout_aliasable, consider the marked address to // be the initialization point. if (auto *c = dyn_cast(operand)) operand = c->getOperand(); if (auto *fArg = dyn_cast(operand)) { switch (fArg->getArgumentConvention()) { case swift::SILArgumentConvention::Indirect_In: case swift::SILArgumentConvention::Indirect_In_Guaranteed: case swift::SILArgumentConvention::Indirect_Inout: case swift::SILArgumentConvention::Indirect_InoutAliasable: case swift::SILArgumentConvention::Indirect_In_CXX: // We need to add our address to the initInst array to make sure that // later invariants that we assert upon remain true. LLVM_DEBUG( llvm::dbgs() << "Found in/in_guaranteed/inout/inout_aliasable argument as " "an init... adding mark_unresolved_non_copyable_value as " "init!\n"); // We cheat here slightly and use our address's operand. return true; break; case swift::SILArgumentConvention::Indirect_Out: llvm_unreachable("Should never have out addresses here"); case swift::SILArgumentConvention::Direct_Owned: case swift::SILArgumentConvention::Direct_Unowned: case swift::SILArgumentConvention::Direct_Guaranteed: case swift::SILArgumentConvention::Pack_Inout: case swift::SILArgumentConvention::Pack_Guaranteed: case swift::SILArgumentConvention::Pack_Owned: case swift::SILArgumentConvention::Pack_Out: llvm_unreachable("Working with addresses"); } } } // A read or write access always begins on an initialized value. if (auto access = dyn_cast(operand)) { switch (access->getAccessKind()) { case SILAccessKind::Deinit: case SILAccessKind::Read: case SILAccessKind::Modify: LLVM_DEBUG(llvm::dbgs() << "Found move only arg closure box use... " "adding mark_unresolved_non_copyable_value as init!\n"); return true; break; case SILAccessKind::Init: break; } } // See if our address is from a closure guaranteed box that we did not promote // to an address. In such a case, just treat our // mark_unresolved_non_copyable_value as the init of our value. if (auto *projectBox = dyn_cast(stripAccessMarkers(operand))) { if (auto *fArg = dyn_cast(projectBox->getOperand())) { if (fArg->isClosureCapture()) { assert(fArg->getArgumentConvention() == SILArgumentConvention::Direct_Guaranteed && "Just a paranoid assert check to make sure this code is thought " "about if we change the convention in some way"); // We need to add our address to the initInst array to make sure that // later invariants that we assert upon remain true. LLVM_DEBUG(llvm::dbgs() << "Found move only arg closure box use... " "adding mark_unresolved_non_copyable_value as init!\n"); return true; } } else if (isa( lookThroughOwnershipInsts(projectBox->getOperand()))) { LLVM_DEBUG(llvm::dbgs() << "Found move only var allocbox use... " "adding mark_unresolved_non_copyable_value as init!\n"); return true; } } // Check if our address is from a ref_element_addr. In such a case, we treat // the mark_unresolved_non_copyable_value as the initialization. if (isa(stripAccessMarkers(operand))) { LLVM_DEBUG(llvm::dbgs() << "Found ref_element_addr use... " "adding mark_unresolved_non_copyable_value as init!\n"); return true; } // Check if our address is from a global_addr. In such a case, we treat the // mark_unresolved_non_copyable_value as the initialization. if (isa(stripAccessMarkers(operand))) { LLVM_DEBUG(llvm::dbgs() << "Found global_addr use... " "adding mark_unresolved_non_copyable_value as init!\n"); return true; } if (auto *ptai = dyn_cast(stripAccessMarkers(operand))) { assert(ptai->isStrict()); LLVM_DEBUG(llvm::dbgs() << "Found pointer to address use... " "adding mark_unresolved_non_copyable_value as init!\n"); return true; } if (isa_and_nonnull( stripAccessMarkers(operand)->getDefiningInstruction())) { LLVM_DEBUG(llvm::dbgs() << "Adding accessor coroutine begin_apply as init!\n"); return true; } if (isa(stripAccessMarkers(operand))) { LLVM_DEBUG(llvm::dbgs() << "Adding enum projection as init!\n"); return true; } // Assume a strict check of a temporary or formal access is initialized // before the check. if (auto *asi = dyn_cast(stripAccessMarkers(operand)); asi && address->isStrict()) { LLVM_DEBUG(llvm::dbgs() << "Adding strict-marked alloc_stack as init!\n"); return true; } // Assume a strict-checked value initialized before the check. if (address->isStrict()) { LLVM_DEBUG(llvm::dbgs() << "Adding strict marker as init!\n"); return true; } // Assume a value whose deinit has been dropped has been initialized. if (isa(operand)) { LLVM_DEBUG(llvm::dbgs() << "Adding copyable_to_move_only_wrapper as init!\n"); return true; } // Assume a value wrapped in a MoveOnlyWrapper is initialized. if (isa(operand)) { LLVM_DEBUG(llvm::dbgs() << "Adding copyable_to_move_only_wrapper as init!\n"); return true; } return false; } void UseState::initializeLiveness( FieldSensitiveMultiDefPrunedLiveRange &liveness) { assert(liveness.getNumSubElements() == getNumSubelements()); // We begin by initializing all of our init uses. for (auto initInstAndValue : initInsts) { LLVM_DEBUG(llvm::dbgs() << "Found def: " << *initInstAndValue.first); liveness.initializeDef(initInstAndValue.first, initInstAndValue.second); } // If we have a reinitInstAndValue that we are going to be able to convert // into a simple init, add it as an init. We are going to consider the rest of // our reinit uses to be liveness uses. for (auto reinitInstAndValue : reinitInsts) { if (isReinitToInitConvertibleInst(reinitInstAndValue.first)) { LLVM_DEBUG(llvm::dbgs() << "Found def: " << *reinitInstAndValue.first); liveness.initializeDef(reinitInstAndValue.first, reinitInstAndValue.second); } } bool beginsInitialized = addressBeginsInitialized(address); if (beginsInitialized) { recordInitUse(address, address, liveness.getTopLevelSpan()); liveness.initializeDef(SILValue(address), liveness.getTopLevelSpan()); } // Now that we have finished initialization of defs, change our multi-maps // from their array form to their map form. liveness.finishedInitializationOfDefs(); LLVM_DEBUG(llvm::dbgs() << "Liveness with just inits:\n"; liveness.print(llvm::dbgs())); for (auto initInstAndValue : initInsts) { // If our init inst is a store_borrow, treat the end_borrow as liveness // uses. // // NOTE: We do not need to check for access scopes here since store_borrow // can only apply to alloc_stack today. if (auto *sbi = dyn_cast(initInstAndValue.first)) { // We can only store_borrow if our mark_unresolved_non_copyable_value is a // no_consume_or_assign. assert(address->getCheckKind() == MarkUnresolvedNonCopyableValueInst:: CheckKind::NoConsumeOrAssign && "store_borrow implies no_consume_or_assign since we cannot " "consume a borrowed inited value"); for (auto *ebi : sbi->getEndBorrows()) { liveness.updateForUse(ebi, initInstAndValue.second, false /*lifetime ending*/); } } } // Now at this point, we have defined all of our defs so we can start adding // uses to the liveness. for (auto reinitInstAndValue : reinitInsts) { recordConsumingBlock(reinitInstAndValue.first->getParent(), reinitInstAndValue.second); if (!isReinitToInitConvertibleInst(reinitInstAndValue.first)) { liveness.updateForUse(reinitInstAndValue.first, reinitInstAndValue.second, false /*lifetime ending*/); LLVM_DEBUG(llvm::dbgs() << "Added liveness for reinit: " << *reinitInstAndValue.first; liveness.print(llvm::dbgs())); } } // Then add all of the takes that we saw propagated up to the top of our // block. Since we have done this for all of our defs for (auto takeInstAndValue : takeInsts) { liveness.updateForUse(takeInstAndValue.first, takeInstAndValue.second, true /*lifetime ending*/); recordConsumingBlock(takeInstAndValue.first->getParent(), takeInstAndValue.second); LLVM_DEBUG(llvm::dbgs() << "Added liveness for take: " << *takeInstAndValue.first; liveness.print(llvm::dbgs())); } for (auto copyInstAndValue : copyInsts) { liveness.updateForUse(copyInstAndValue.first, copyInstAndValue.second, true /*lifetime ending*/); recordConsumingBlock(copyInstAndValue.first->getParent(), copyInstAndValue.second); LLVM_DEBUG(llvm::dbgs() << "Added liveness for copy: " << *copyInstAndValue.first; liveness.print(llvm::dbgs())); } for (auto destroyInstAndValue : destroys) { recordConsumingBlock(destroyInstAndValue.first->getParent(), destroyInstAndValue.second); } // Do the same for our borrow and liveness insts. for (auto livenessInstAndValue : borrows) { liveness.updateForUse(livenessInstAndValue.first, livenessInstAndValue.second, false /*lifetime ending*/); auto *li = cast(livenessInstAndValue.first); auto accessPathWithBase = AccessPathWithBase::computeInScope(li->getOperand()); if (auto *beginAccess = dyn_cast_or_null(accessPathWithBase.base)) { for (auto *endAccess : beginAccess->getEndAccesses()) { liveness.updateForUse(endAccess, livenessInstAndValue.second, false /*lifetime ending*/); } } // NOTE: We used to add the destroy_value of our loads here to liveness. We // instead add them to the livenessUses array so that we can successfully // find them later when performing a forward traversal to find them for // error purposes. LLVM_DEBUG(llvm::dbgs() << "Added liveness for borrow: " << *livenessInstAndValue.first; liveness.print(llvm::dbgs())); } auto updateForLivenessAccess = [&](BeginAccessInst *beginAccess, const SmallBitVector &livenessMask) { for (auto *endAccess : beginAccess->getEndAccesses()) { liveness.updateForUse(endAccess, livenessMask, false /*lifetime ending*/); } }; for (auto livenessInstAndValue : nonconsumingUses) { if (auto *lbi = dyn_cast(livenessInstAndValue.first)) { auto accessPathWithBase = AccessPathWithBase::computeInScope(lbi->getOperand()); if (auto *beginAccess = dyn_cast_or_null(accessPathWithBase.base)) { updateForLivenessAccess(beginAccess, livenessInstAndValue.second); } else { for (auto *ebi : lbi->getEndBorrows()) { liveness.updateForUse(ebi, livenessInstAndValue.second, false /*lifetime ending*/); } } } else if (auto *bai = dyn_cast(livenessInstAndValue.first)) { updateForLivenessAccess(bai, livenessInstAndValue.second); } else { liveness.updateForUse(livenessInstAndValue.first, livenessInstAndValue.second, false /*lifetime ending*/); } LLVM_DEBUG(llvm::dbgs() << "Added liveness for livenessInst: " << *livenessInstAndValue.first; liveness.print(llvm::dbgs())); } // Finally, if we have an inout argument or an access scope associated with a // ref_element_addr or global_addr, add a liveness use of the entire value on // the implicit end lifetime instruction. For inout this is terminators for // ref_element_addr, global_addr it is the end_access instruction. for (auto pair : scopeEndsRequiringInit) { liveness.updateForUse(pair.first, TypeTreeLeafTypeRange(address), false /*lifetime ending*/); LLVM_DEBUG(llvm::dbgs() << "Added liveness for scope end: " << pair.first; liveness.print(llvm::dbgs())); } LLVM_DEBUG(llvm::dbgs() << "Final Liveness:\n"; liveness.print(llvm::dbgs())); } //===----------------------------------------------------------------------===// // MARK: Global Block State //===----------------------------------------------------------------------===// namespace { struct BlockState { using Map = llvm::DenseMap; /// This is either the liveness up or take up inst that projects /// up. We set this state according to the following rules: /// /// 1. If we are tracking a takeUp, we always take it even if we have a /// livenessUp. /// /// 2. If we have a livenessUp and do not have a take up, we track that /// instead. /// /// The reason why we do this is that we want to catch use after frees when /// non-consuming uses are later than a consuming use. SILInstruction *userUp; /// If we are init down, then we know that we can not transfer our take /// through this block and should stop traversing. bool isInitDown; BlockState() : userUp(nullptr) {} BlockState(SILInstruction *userUp, bool isInitDown) : userUp(userUp), isInitDown(isInitDown) {} }; } // namespace //===----------------------------------------------------------------------===// // MARK: Forward Declaration of Main Checker //===----------------------------------------------------------------------===// namespace { struct ConsumeInfo { /// Map blocks on the lifetime boundary to the last consuming instruction. llvm::MapVector, 1>> finalBlockConsumes; bool isFrozen = false; public: void print(llvm::raw_ostream &os) const { for (auto &blockInstRangePairVector : finalBlockConsumes) { os << "Dumping state for block bb" << blockInstRangePairVector.first->getDebugID() << '\n'; for (auto &instRangePairVector : blockInstRangePairVector.second) { auto *inst = instRangePairVector.first; if (!inst) continue; os << "Inst: " << *inst; os << "Range: " << instRangePairVector.second; os << '\n'; } } } void clear() { finalBlockConsumes.clear(); isFrozen = false; } /// This is expensive! Only use it in debug mode! bool hasUnclaimedConsumes() const { assert(isFrozen); bool foundAny = false; for (auto range : finalBlockConsumes) { for (auto elt : range.second) { foundAny |= bool(elt.first); } } return foundAny; } void recordFinalConsume(SILInstruction *inst, SmallBitVector const &bits) { assert(!isFrozen); auto *block = inst->getParent(); auto iter = finalBlockConsumes.find(block); if (iter == finalBlockConsumes.end()) { iter = finalBlockConsumes.insert({block, {}}).first; } LLVM_DEBUG(llvm::dbgs() << "Recorded Final Consume: " << *inst); for (auto &pair : iter->second) { if (pair.first == inst) { pair.second |= bits; return; } } iter->second.emplace_back(inst, bits); } void finishRecordingFinalConsumes() { assert(!isFrozen); for (auto &pair : finalBlockConsumes) { llvm::stable_sort( pair.second, [](const std::pair &lhs, const std::pair &rhs) { return lhs.first < rhs.first; }); } isFrozen = true; LLVM_DEBUG(llvm::dbgs() << "Final recorded consumes!\n"; print(llvm::dbgs())); } // Return true if this instruction is marked as a final consume point of the // current def's live range. A consuming instruction can only be claimed once // because instructions like `tuple` can consume the same value via multiple // operands. // // Can only be used once frozen. bool claimConsume(SILInstruction *inst, SmallBitVector const &bits) { assert(isFrozen); bool claimedConsume = false; auto &iter = finalBlockConsumes[inst->getParent()]; for (unsigned i : indices(iter)) { auto &instRangePair = iter[i]; if (instRangePair.first == inst && instRangePair.second == bits) { instRangePair.first = nullptr; claimedConsume = true; LLVM_DEBUG(llvm::dbgs() << "Claimed consume: " << *inst); } } return claimedConsume; } ConsumeInfo() {} ConsumeInfo(CanonicalOSSAConsumeInfo const &) = delete; ConsumeInfo &operator=(ConsumeInfo const &) = delete; }; struct MoveOnlyAddressCheckerPImpl { bool changed = false; SILFunction *fn; /// A set of mark_unresolved_non_copyable_value that we are actually going to /// process. llvm::SmallSetVector moveIntroducersToProcess; /// The instruction deleter used by \p canonicalizer. InstructionDeleter deleter; /// State to run OSSACanonicalizeOwned. OSSACanonicalizer canonicalizer; /// Per mark must check address use state. UseState addressUseState; /// Diagnostic emission routines wrapped around a consuming use cache. This /// ensures that we only emit a single error per use per marked value. DiagnosticEmitter &diagnosticEmitter; /// Information about destroys that we use when inserting destroys. ConsumeInfo consumes; DeadEndBlocksAnalysis *deba; /// PostOrderAnalysis used by the BorrowToDestructureTransform. PostOrderAnalysis *poa; /// Allocator used by the BorrowToDestructureTransform. borrowtodestructure::IntervalMapAllocator &allocator; MoveOnlyAddressCheckerPImpl( SILFunction *fn, DiagnosticEmitter &diagnosticEmitter, DominanceInfo *domTree, PostOrderAnalysis *poa, DeadEndBlocksAnalysis *deba, borrowtodestructure::IntervalMapAllocator &allocator) : fn(fn), deleter(), canonicalizer(fn, domTree, deba, deleter), addressUseState(domTree), diagnosticEmitter(diagnosticEmitter), deba(deba), poa(poa), allocator(allocator) { deleter.setCallbacks(std::move( InstModCallbacks().onDelete([&](SILInstruction *instToDelete) { if (auto *mvi = dyn_cast(instToDelete)) moveIntroducersToProcess.remove(mvi); instToDelete->eraseFromParent(); }))); diagnosticEmitter.initCanonicalizer(&canonicalizer); } /// Search through the current function for candidate /// mark_unresolved_non_copyable_value [noimplicitcopy]. If we find one that /// does not fit a pattern that we understand, emit an error diagnostic /// telling the programmer that the move checker did not know how to recognize /// this code pattern. /// /// Returns true if we emitted a diagnostic. Returns false otherwise. bool searchForCandidateMarkUnresolvedNonCopyableValueInsts(); /// Emits an error diagnostic for \p markedValue. void performObjectCheck(MarkUnresolvedNonCopyableValueInst *markedValue); bool performSingleCheck(MarkUnresolvedNonCopyableValueInst *markedValue); void insertDestroysOnBoundary(MarkUnresolvedNonCopyableValueInst *markedValue, FieldSensitiveMultiDefPrunedLiveRange &liveness, FieldSensitivePrunedLivenessBoundary &boundary); void rewriteUses(MarkUnresolvedNonCopyableValueInst *markedValue, FieldSensitiveMultiDefPrunedLiveRange &liveness, const FieldSensitivePrunedLivenessBoundary &boundary); /// Identifies and diagnoses reinitializations that are reachable from a /// discard statement. void checkForReinitAfterDiscard(); void handleSingleBlockDestroy(SILInstruction *destroy, bool isReinit); }; class ExtendUnconsumedLiveness { UseState addressUseState; FieldSensitiveMultiDefPrunedLiveRange &liveness; FieldSensitivePrunedLivenessBoundary &boundary; enum class DestroyKind { Destroy, Take, Reinit, }; using DestroysCollection = llvm::SmallMapVector; using ConsumingBlocksCollection = SmallPtrSetVector; public: ExtendUnconsumedLiveness(UseState addressUseState, FieldSensitiveMultiDefPrunedLiveRange &liveness, FieldSensitivePrunedLivenessBoundary &boundary) : addressUseState(addressUseState), liveness(liveness), boundary(boundary) {} void run(); void runOnField(unsigned element, DestroysCollection &destroys, ConsumingBlocksCollection &consumingBlocks); private: bool hasDefAfter(SILInstruction *inst, unsigned element); bool isLiveAtBegin(SILBasicBlock *block, unsigned element, bool isLiveAtEnd, DestroysCollection const &destroys); bool shouldAddDestroyToLiveness(SILInstruction *destroy, unsigned element, BasicBlockSet const &consumedAtExitBlocks, BasicBlockSetVector const &consumedAtEntryBlocks); void addPreviousInstructionToLiveness(SILInstruction *inst, unsigned element); }; } // namespace //===----------------------------------------------------------------------===// // MARK: CopiedLoadBorrowElimination //===----------------------------------------------------------------------===// namespace { struct CopiedLoadBorrowEliminationState { SILFunction *fn; StackList targets; CopiedLoadBorrowEliminationState(SILFunction *fn) : fn(fn), targets(fn) {} void process() { if (targets.empty()) return; while (!targets.empty()) { auto *lbi = targets.pop_back_val(); SILBuilderWithScope builder(lbi); SILValue li = builder.emitLoadValueOperation( lbi->getLoc(), lbi->getOperand(), LoadOwnershipQualifier::Copy); SILValue borrow = builder.createBeginBorrow(lbi->getLoc(), li); for (auto *ebi : lbi->getEndBorrows()) { auto *next = ebi->getNextInstruction(); SILBuilderWithScope builder(next); auto loc = RegularLocation::getAutoGeneratedLocation(); builder.emitDestroyValueOperation(loc, li); } lbi->replaceAllUsesWith(borrow); lbi->eraseFromParent(); } LLVM_DEBUG(llvm::dbgs() << "After Load Borrow Elim. Func Dump Start! "; fn->print(llvm::dbgs())); LLVM_DEBUG(llvm::dbgs() << "After Load Borrow Elim. Func Dump End!\n"); } }; static TransitiveAddressWalkerTransitiveUseVisitation shouldVisitAsEndPointUse(Operand *op) { // If an access is static and marked as "no nested conflict", we use that // in switch codegen to mark an opaque sub-access that move-only checking // should not look through. if (auto *bai = dyn_cast(op->getUser())) { if (bai->getEnforcement() == SILAccessEnforcement::Static && bai->hasNoNestedConflict()) { return TransitiveAddressWalkerTransitiveUseVisitation::OnlyUser; } } // A drop_deinit consumes the deinit bit. if (isa(op->getUser())) { return TransitiveAddressWalkerTransitiveUseVisitation::BothUserAndUses; } // An unchecked_take_enum_data_addr consumes all bits except the remaining // element's. if (isa(op->getUser())) { return TransitiveAddressWalkerTransitiveUseVisitation::BothUserAndUses; } return TransitiveAddressWalkerTransitiveUseVisitation::OnlyUses; } /// An early transform that we run to convert any load_borrow that are copied /// directly or that have any subelement that is copied to a load [copy]. This /// lets the rest of the optimization handle these as appropriate. struct CopiedLoadBorrowEliminationVisitor : public TransitiveAddressWalker { CopiedLoadBorrowEliminationState &state; CopiedLoadBorrowEliminationVisitor(CopiedLoadBorrowEliminationState &state) : state(state) {} CopiedLoadBorrowEliminationVisitor::TransitiveUseVisitation visitTransitiveUseAsEndPointUse(Operand *op) { return shouldVisitAsEndPointUse(op); } bool visitUse(Operand *op) { LLVM_DEBUG(llvm::dbgs() << "CopiedLBElim visiting "; llvm::dbgs() << " User: " << *op->getUser()); auto *lbi = dyn_cast(op->getUser()); if (!lbi) return true; LLVM_DEBUG(llvm::dbgs() << "Found load_borrow: " << *lbi); StackList useWorklist(lbi->getFunction()); for (auto *use : lbi->getUses()) useWorklist.push_back(use); bool shouldConvertToLoadCopy = false; while (!useWorklist.empty()) { auto *nextUse = useWorklist.pop_back_val(); switch (nextUse->getOperandOwnership()) { case OperandOwnership::NonUse: case OperandOwnership::ForwardingUnowned: case OperandOwnership::PointerEscape: continue; // These might be uses that we need to perform a destructure or insert // struct_extracts for. case OperandOwnership::TrivialUse: case OperandOwnership::InstantaneousUse: case OperandOwnership::UnownedInstantaneousUse: case OperandOwnership::InteriorPointer: case OperandOwnership::AnyInteriorPointer: case OperandOwnership::BitwiseEscape: { // Look through copy_value of a move only value. We treat copy_value of // copyable values as normal uses. if (auto *cvi = dyn_cast(nextUse->getUser())) { if (!isCopyableValue(cvi->getOperand())) { shouldConvertToLoadCopy = true; break; } } continue; } case OperandOwnership::ForwardingConsume: case OperandOwnership::DestroyingConsume: { if (auto *dvi = dyn_cast(nextUse->getUser())) { auto value = dvi->getOperand(); auto *pai = dyn_cast_or_null( value->getDefiningInstruction()); if (pai && pai->isOnStack()) { // A destroy_value of an on_stack partial apply isn't actually a // consuming use--it closes a borrow scope. continue; } } // We can only hit this if our load_borrow was copied. llvm_unreachable("We should never hit this"); } case OperandOwnership::GuaranteedForwarding: { SmallVector forwardedValues; auto *fn = nextUse->getUser()->getFunction(); ForwardingOperand(nextUse).visitForwardedValues([&](SILValue value) { if (value->getType().isTrivial(fn)) return true; forwardedValues.push_back(value); return true; }); // If we do not have any forwarded values, just continue. if (forwardedValues.empty()) continue; while (!forwardedValues.empty()) { for (auto *use : forwardedValues.pop_back_val()->getUses()) useWorklist.push_back(use); } continue; } case OperandOwnership::Borrow: LLVM_DEBUG(llvm::dbgs() << " Found recursive borrow!\n"); // Look through borrows. for (auto value : nextUse->getUser()->getResults()) { for (auto *use : value->getUses()) { if (use->get()->getType().isTrivial(*use->getFunction())) { continue; } useWorklist.push_back(use); } } continue; case OperandOwnership::EndBorrow: LLVM_DEBUG(llvm::dbgs() << " Found end borrow!\n"); continue; case OperandOwnership::Reborrow: llvm_unreachable("Unsupported for now?!"); } if (shouldConvertToLoadCopy) break; } LLVM_DEBUG(llvm::dbgs() << "Load Borrow was copied: " << (shouldConvertToLoadCopy ? "true" : "false") << '\n'); if (!shouldConvertToLoadCopy) return true; state.targets.push_back(lbi); return true; } }; } // namespace //===----------------------------------------------------------------------===// // MARK: Partial Consume/Reinit Checking //===----------------------------------------------------------------------===// static bool hasExplicitFixedLayoutAnnotation(NominalTypeDecl *nominal) { return nominal->getAttrs().hasAttribute() || nominal->getAttrs().hasAttribute(); } static std::optional shouldEmitPartialMutationErrorForType(SILType ty, NominalTypeDecl *nominal, SILFunction *fn) { // A non-frozen type can't be partially mutated within code built in its // defining module if that code will be emitted into a client. if (fn->getLinkage() == SILLinkage::PublicNonABI && nominal->getFormalAccess() < AccessLevel::Public && nominal->isUsableFromInline() && !hasExplicitFixedLayoutAnnotation(nominal)) { return {PartialMutationError::nonfrozenUsableFromInlineType(ty, *nominal)}; } // Otherwise, a type can be mutated partially in its defining module. if (nominal->getModuleContext() == fn->getModule().getSwiftModule()) return std::nullopt; // It's defined in another module and used here; it has to be visible. assert(nominal ->getFormalAccessScope( /*useDC=*/fn->getDeclContext(), /*treatUsableFromInlineAsPublic=*/true) .isPublicOrPackage()); // Partial mutation is supported only for frozen/fixed-layout types from // other modules. if (hasExplicitFixedLayoutAnnotation(nominal)) return std::nullopt; return {PartialMutationError::nonfrozenImportedType(ty, *nominal)}; } /// Whether an error should be emitted in response to a partial consumption. static std::optional shouldEmitPartialMutationError(UseState &useState, PartialMutation::Kind kind, SILInstruction *user, SILType useType, TypeTreeLeafTypeRange usedBits) { SILFunction *fn = useState.getFunction(); // We walk down from our ancestor to our projection, emitting an error if // any of our types have a deinit. auto iterType = useState.address->getType(); if (iterType.isMoveOnlyWrapped()) return {}; TypeOffsetSizePair pair(usedBits); auto targetType = useType; TypeOffsetSizePair iterPair(iterType, fn); LLVM_DEBUG(llvm::dbgs() << " Iter Type: " << iterType << '\n' << " Target Type: " << targetType << '\n'); // Allowing full object consumption in a deinit is still not allowed. if (iterType == targetType && !isa(user)) { // Don't allow whole-value consumption of `self` from a `deinit`. if (!fn->getModule().getASTContext().LangOpts .hasFeature(Feature::ConsumeSelfInDeinit) && kind == PartialMutation::Kind::Consume && useState.sawDropDeinit // TODO: Revisit this when we introduce deinits on enums. && !targetType.getEnumOrBoundGenericEnum()) { LLVM_DEBUG(llvm::dbgs() << " IterType is TargetType in deinit! " "Not allowed yet"); return {PartialMutationError::consumeDuringDeinit(iterType)}; } LLVM_DEBUG(llvm::dbgs() << " IterType is TargetType! Exiting early " "without emitting error!\n"); return {}; } auto feature = partialMutationFeature(kind); if (feature && !fn->getModule().getASTContext().LangOpts.hasFeature(*feature) && !isa(user)) { LLVM_DEBUG(llvm::dbgs() << " " << feature->getName() << " disabled!\n"); // If the types equal, just bail early. // Emit the error. return {PartialMutationError::featureDisabled(iterType, kind)}; } LLVM_DEBUG(llvm::dbgs() << " MoveOnlyPartialConsumption enabled!\n"); // Otherwise, walk the type looking for the deinit and visibility. while (iterType != targetType) { // If we have a nominal type as our parent type, see if it has a // deinit. We know that it must be non-copyable since copyable types // cannot contain non-copyable types and that our parent root type must be // an enum, tuple, or struct. if (auto *nom = iterType.getNominalOrBoundGenericNominal()) { if (auto error = shouldEmitPartialMutationErrorForType( iterType, nom, user->getFunction())) { return error; } // FIXME: [partial_consume_of_deiniting_aggregate_with_drop_deinit] The // second half of this condition should consider whether this // user's projection path and whether this value had its deinit // dropped. auto isAllowedPartialConsume = (kind == PartialMutation::Kind::Consume) && useState.sawDropDeinit && (nom == useState.address->getType().getNominalOrBoundGenericNominal()); if (nom->getValueTypeDestructor() && !isAllowedPartialConsume) { // If we find one, emit an error since we are going to have to extract // through the deinit. Emit a nice error saying what it is. Since we // are emitting an error, we do a bit more work and construct the // actual projection string. return {PartialMutationError::hasDeinit(iterType, *nom)}; } } // Otherwise, walk one level towards our child type. We unconditionally // unwrap since we should never fail here due to earlier checking. std::tie(iterPair, iterType) = *pair.walkOneLevelTowardsChild(iterPair, iterType, targetType, fn); } return {}; } static bool checkForPartialMutation(UseState &useState, DiagnosticEmitter &diagnosticEmitter, PartialMutation::Kind kind, SILInstruction *user, SILType useType, TypeTreeLeafTypeRange usedBits, PartialMutation partialMutateKind) { // We walk down from our ancestor to our projection, emitting an error if // any of our types have a deinit. auto error = shouldEmitPartialMutationError(useState, kind, user, useType, usedBits); if (!error) return false; diagnosticEmitter.emitCannotPartiallyMutateError( useState.address, error.value(), user, usedBits, partialMutateKind); return true; } namespace { struct PartialReinitChecker { UseState &useState; DiagnosticEmitter &diagnosticEmitter; PartialReinitChecker(UseState &useState, DiagnosticEmitter &diagnosticEmitter) : useState(useState), diagnosticEmitter(diagnosticEmitter) {} void performPartialReinitChecking(FieldSensitiveMultiDefPrunedLiveRange &liveness); }; } // namespace void PartialReinitChecker::performPartialReinitChecking( FieldSensitiveMultiDefPrunedLiveRange &liveness) { // Perform checks that rely on liveness information. for (auto initToValues : useState.initToValueMultiMap.getRange()) { LLVM_DEBUG(llvm::dbgs() << "Checking init: " << *initToValues.first); bool emittedError = false; for (SILValue value : initToValues.second) { LLVM_DEBUG(llvm::dbgs() << " Checking operand value: " << value); // By computing the bits here directly, we do not need to worry about // having to split contiguous ranges into separate representable SILTypes. SmallBitVector neededElements(useState.getNumSubelements()); SmallVector ranges; TypeTreeLeafTypeRange::get(value, useState.address, ranges); for (auto range : ranges) { for (unsigned index : range.getRange()) { emittedError = !liveness.findEarlierConsumingUse( initToValues.first, index, [&](SILInstruction *consumingInst) -> bool { return !checkForPartialMutation( useState, diagnosticEmitter, PartialMutation::Kind::Reinit, initToValues.first, value->getType(), TypeTreeLeafTypeRange(index, index + 1), PartialMutation::reinit(*consumingInst)); }); // If we emitted an error for this index break. We only want to emit // one error per value. if (emittedError) break; } } // If we emitted an error for this value break. We only want to emit one // error per instruction. if (emittedError) break; } } for (auto reinitToValues : useState.reinitToValueMultiMap.getRange()) { if (!isReinitToInitConvertibleInst(reinitToValues.first)) continue; LLVM_DEBUG(llvm::dbgs() << "Checking reinit: " << *reinitToValues.first); bool emittedError = false; for (SILValue value : reinitToValues.second) { LLVM_DEBUG(llvm::dbgs() << " Checking operand value: " << value); // By computing the bits here directly, we do not need to worry about // having to split contiguous ranges into separate representable SILTypes. SmallBitVector neededElements(useState.getNumSubelements()); SmallVector ranges; TypeTreeLeafTypeRange::get(value, useState.address, ranges); for (auto range : ranges) { for (unsigned index : range.getRange()) { emittedError = !liveness.findEarlierConsumingUse( reinitToValues.first, index, [&](SILInstruction *consumingInst) -> bool { return !checkForPartialMutation( useState, diagnosticEmitter, PartialMutation::Kind::Reinit, reinitToValues.first, value->getType(), TypeTreeLeafTypeRange(index, index + 1), PartialMutation::reinit(*consumingInst)); }); if (emittedError) break; } } if (emittedError) break; } } } //===----------------------------------------------------------------------===// // MARK: GatherLexicalLifetimeUseVisitor //===----------------------------------------------------------------------===// namespace { /// Visit all of the uses of value in preparation for running our algorithm. struct GatherUsesVisitor : public TransitiveAddressWalker { MoveOnlyAddressCheckerPImpl &moveChecker; UseState &useState; MarkUnresolvedNonCopyableValueInst *markedValue; DiagnosticEmitter &diagnosticEmitter; // Pruned liveness used to validate that load [take]/load [copy] can be // converted to load_borrow without violating exclusivity. BitfieldRef liveness; GatherUsesVisitor(MoveOnlyAddressCheckerPImpl &moveChecker, UseState &useState, MarkUnresolvedNonCopyableValueInst *markedValue, DiagnosticEmitter &diagnosticEmitter) : moveChecker(moveChecker), useState(useState), markedValue(markedValue), diagnosticEmitter(diagnosticEmitter) {} bool visitUse(Operand *op); TransitiveUseVisitation visitTransitiveUseAsEndPointUse(Operand *op); void reset(MarkUnresolvedNonCopyableValueInst *address) { useState.address = address; } void clear() { useState.clear(); } /// For now always markedValue. If we start using this for move address /// checking, we need to check against the operand of the markedValue. This is /// because for move checking, our marker is placed along the variables /// initialization so we are always going to have all later uses from the /// marked value. For the move operator though we will want this to be the /// base address that we are checking which should be the operand of the mark /// must check value. SILValue getRootAddress() const { return markedValue; } ASTContext &getASTContext() { return markedValue->getFunction()->getASTContext(); } /// Returns true if we emitted an error. bool checkForExclusivityHazards(LoadInst *li) { BitfieldRef::StackState state(liveness, li->getFunction()); LLVM_DEBUG(llvm::dbgs() << "Checking for exclusivity hazards for: " << *li); // Grab our access path with in scope. We want to find the inner most access // scope. auto accessPathWithBase = AccessPathWithBase::computeInScope(li->getOperand()); auto accessPath = accessPathWithBase.accessPath; // TODO: Make this a we don't understand error. assert(accessPath.isValid() && "Invalid access path?!"); auto *bai = dyn_cast_or_null(accessPathWithBase.base); if (!bai) { LLVM_DEBUG(llvm::dbgs() << " No begin access... so no exclusivity violation!\n"); return false; } bool emittedError = false; liveness->initializeDef(bai); liveness->computeSimple(); for (auto *consumingUse : li->getConsumingUses()) { if (!liveness->isWithinBoundary( consumingUse->getUser(), moveChecker.deba->get(consumingUse->getFunction()))) { diagnosticEmitter.emitAddressExclusivityHazardDiagnostic( markedValue, consumingUse->getUser()); emittedError = true; } } return emittedError; } void onError(Operand *op) { LLVM_DEBUG(llvm::dbgs() << " Found use unrecognized by the walker!\n"; op->getUser()->print(llvm::dbgs())); } }; } // end anonymous namespace GatherUsesVisitor::TransitiveUseVisitation GatherUsesVisitor::visitTransitiveUseAsEndPointUse(Operand *op) { return shouldVisitAsEndPointUse(op); } // Filter out recognized uses that do not write to memory. // // TODO: Ensure that all of the conditional-write logic below is encapsulated in // mayWriteToMemory and just call that instead. Possibly add additional // verification that visitAccessPathUses recognizes all instructions that may // propagate pointers (even though they don't write). bool GatherUsesVisitor::visitUse(Operand *op) { // If this operand is for a dependent type, then it does not actually access // the operand's address value. It only uses the metatype defined by the // operation (e.g. open_existential). if (op->isTypeDependent()) { return true; } if (isa(op->getUser())) { // FIXME: [partial_consume_of_deiniting_aggregate_with_drop_deinit] Once // drop_deinits are handled properly, this should be deleted. useState.sawDropDeinit = true; } // For convenience, grab the user of op. auto *user = op->getUser(); LLVM_DEBUG(llvm::dbgs() << "Visiting user: " << *user;); // First check if we have init/reinit. These are quick/simple. if (noncopyable::memInstMustInitialize(op)) { LLVM_DEBUG(llvm::dbgs() << "Found init: " << *user); // TODO: What about copy_addr of itself. We really should just pre-process // those maybe. SmallVector leafRanges; TypeTreeLeafTypeRange::get(op, getRootAddress(), leafRanges); if (!leafRanges.size()) { LLVM_DEBUG(llvm::dbgs() << "Failed to form leaf type range!\n"); return false; } for (auto leafRange : leafRanges) { useState.recordInitUse(user, op->get(), leafRange); } return true; } if (noncopyable::memInstMustReinitialize(op)) { LLVM_DEBUG(llvm::dbgs() << "Found reinit: " << *user); SmallVector leafRanges; TypeTreeLeafTypeRange::get(op, getRootAddress(), leafRanges); if (!leafRanges.size()) { LLVM_DEBUG(llvm::dbgs() << "Failed to form leaf type range!\n"); return false; } for (auto leafRange : leafRanges) { useState.recordReinitUse(user, op->get(), leafRange); } return true; } // Then handle destroy_addr specially. We want to as part of our dataflow to // ignore destroy_addr, so we need to track it separately from other uses. if (auto *dvi = dyn_cast(user)) { LLVM_DEBUG(llvm::dbgs() << "Found destroy_addr: " << *dvi); SmallVector leafRanges; TypeTreeLeafTypeRange::get(op, getRootAddress(), leafRanges); if (!leafRanges.size()) { LLVM_DEBUG(llvm::dbgs() << "Failed to form leaf type range!\n"); return false; } for (auto leafRange : leafRanges) { useState.destroys.insert({dvi, leafRange}); } return true; } // Ignore dealloc_stack. if (isa(user)) return true; // Ignore end_access. if (isa(user)) return true; // Ignore end_cow_mutation_addr. if (isa(user)) { return true; } // Ignore sanitizer markers. if (auto bu = dyn_cast(user)) { if (bu->getBuiltinKind() == BuiltinValueKind::TSanInoutAccess) { return true; } } // This visitor looks through store_borrow instructions but does visit the // end_borrow of the store_borrow. If we see such an end_borrow, register the // store_borrow instead. Since we use sets, if we visit multiple end_borrows, // we will only record the store_borrow once. if (auto *ebi = dyn_cast(user)) { if (auto *sbi = dyn_cast(ebi->getOperand())) { LLVM_DEBUG(llvm::dbgs() << "Found store_borrow: " << *sbi); SmallVector leafRanges; TypeTreeLeafTypeRange::get(op, getRootAddress(), leafRanges); if (!leafRanges.size()) { LLVM_DEBUG(llvm::dbgs() << "Failed to form leaf type range!\n"); return false; } for (auto leafRange : leafRanges) { useState.recordInitUse(user, op->get(), leafRange); } return true; } } if (auto *di = dyn_cast(user)) { // Save the debug_value if it is attached directly to this // mark_unresolved_non_copyable_value. If the underlying storage we're // checking is immutable, then the access being checked is not confined to // an explicit access, but every other use of the storage must also be // immutable, so it is fine if we see debug_values or other uses that aren't // directly related to the current marked use; they will have to behave // compatibly anyway. if (di->getOperand() == getRootAddress()) { useState.debugValue = di; } return true; } // At this point, we have handled all of the non-loadTakeOrCopy/consuming // uses. if (auto *copyAddr = dyn_cast(user)) { assert(op->getOperandNumber() == CopyAddrInst::Src && "Should have dest above in memInstMust{Rei,I}nitialize"); SmallVector leafRanges; TypeTreeLeafTypeRange::get(op, getRootAddress(), leafRanges); if (!leafRanges.size()) { LLVM_DEBUG(llvm::dbgs() << "Failed to form leaf type range!\n"); return false; } for (auto leafRange : leafRanges) { // If we have a non-move only type, just treat this as a liveness use. if (isCopyableValue(copyAddr->getSrc())) { LLVM_DEBUG(llvm::dbgs() << "Found copy of copyable type. Treating as liveness use! " << *user); useState.recordLivenessUse(user, leafRange); continue; } if (markedValue->getCheckKind() == MarkUnresolvedNonCopyableValueInst::CheckKind::NoConsumeOrAssign) { if (isa( stripAccessMarkers(markedValue->getOperand()))) { LLVM_DEBUG(llvm::dbgs() << "Found mark must check [nocopy] use of escaping box: " << *user); diagnosticEmitter.emitAddressEscapingClosureCaptureLoadedAndConsumed( markedValue); continue; } LLVM_DEBUG(llvm::dbgs() << "Found mark must check [nocopy] error: " << *user); diagnosticEmitter.emitAddressDiagnosticNoCopy(markedValue, copyAddr); continue; } // TODO: Add borrow checking here like below. // If we have a copy_addr, we are either going to have a take or a // copy... in either case, this copy_addr /is/ going to be a consuming // operation. Make sure to check if we semantically destructure. checkForPartialMutation(useState, diagnosticEmitter, PartialMutation::Kind::Consume, op->getUser(), op->get()->getType(), leafRange, PartialMutation::consume()); if (copyAddr->isTakeOfSrc()) { LLVM_DEBUG(llvm::dbgs() << "Found take: " << *user); useState.recordTakeUse(user, leafRange); } else { LLVM_DEBUG(llvm::dbgs() << "Found copy: " << *user); useState.recordCopyUse(user, leafRange); } } return true; } // Then find load [copy], load [take] that are really takes since we need // copies for the loaded value. If we find that we need copies at that level // (due to e.x.: multiple consuming uses), we emit an error and bail. This // ensures that later on, we can assume that all of our load [take], load // [copy] actually follow move semantics at the object level and thus are // viewed as a consume requiring a copy. This is important since SILGen often // emits code of this form and we need to recognize it as a copy of the // underlying var. if (auto *li = dyn_cast(user)) { // Before we do anything, see if this load is of a copyable field or is a // trivial load. If it is, then we just treat this as a liveness requiring // use. auto qualifier = li->getOwnershipQualifier(); if (qualifier == LoadOwnershipQualifier::Trivial || isCopyableValue(li)) { SmallVector leafRanges; TypeTreeLeafTypeRange::get(op, getRootAddress(), leafRanges); if (!leafRanges.size()) { LLVM_DEBUG(llvm::dbgs() << "Failed to form leaf type range!\n"); return false; } for (auto leafRange : leafRanges) { switch (qualifier) { case LoadOwnershipQualifier::Unqualified: llvm_unreachable("unqualified load in ossa!?"); case LoadOwnershipQualifier::Take: useState.recordTakeUse(user, leafRange); break; case LoadOwnershipQualifier::Copy: LLVM_FALLTHROUGH; case LoadOwnershipQualifier::Trivial: useState.recordLivenessUse(user, leafRange); break; } } return true; } // We must have a load [take] or load [copy] here since we are in OSSA. OSSACanonicalizer::LivenessState livenessState(moveChecker.canonicalizer, li); unsigned numDiagnostics = moveChecker.diagnosticEmitter.getDiagnosticCount(); // Before we do anything, run the borrow to destructure transform to reduce // copies through borrows. BorrowToDestructureTransform borrowToDestructure( moveChecker.allocator, markedValue, li, moveChecker.diagnosticEmitter, moveChecker.poa); if (!borrowToDestructure.transform()) { assert(moveChecker.diagnosticEmitter .didEmitCheckerDoesntUnderstandDiagnostic()); LLVM_DEBUG(llvm::dbgs() << "Failed to perform borrow to destructure transform!\n"); return false; } // If we emitted an error diagnostic, do not transform further and instead // mark that we emitted an early diagnostic and return true. if (numDiagnostics != moveChecker.diagnosticEmitter.getDiagnosticCount()) { LLVM_DEBUG(llvm::dbgs() << "Emitting borrow to destructure error!\n"); return true; } // Now, validate that what we will transform into a take isn't a take that // would invalidate a field that has a deinit. SmallVector leafRanges; TypeTreeLeafTypeRange::get(op, getRootAddress(), leafRanges); if (!leafRanges.size()) { LLVM_DEBUG(llvm::dbgs() << "Failed to compute leaf range for: " << *op->get()); return false; } // Canonicalize the lifetime of the load [take], load [copy]. LLVM_DEBUG(llvm::dbgs() << "Running copy propagation!\n"); moveChecker.changed |= moveChecker.canonicalizer.canonicalize(); // Export the drop_deinit's discovered by the ObjectChecker into the // AddressChecker to preserve it for later use. We need to do this since // the ObjectChecker's state gets cleared after running on this LoadInst. for (auto *dropDeinit : moveChecker.canonicalizer.getDropDeinitUses()) moveChecker.addressUseState.dropDeinitInsts.insert(dropDeinit); // If we are asked to perform no_consume_or_assign checking or // assignable_but_not_consumable checking, if we found any consumes of our // load, then we need to emit an error. auto checkKind = markedValue->getCheckKind(); if (checkKind != MarkUnresolvedNonCopyableValueInst::CheckKind:: ConsumableAndAssignable) { if (moveChecker.canonicalizer.foundAnyConsumingUses()) { LLVM_DEBUG(llvm::dbgs() << "Found mark must check [nocopy] error: " << *user); auto operand = stripAccessAndIdentityCasts(markedValue->getOperand()); auto *fArg = dyn_cast(operand); auto *ptrToAddr = dyn_cast(operand); // If we have a closure captured that we specialized, we should have a // no consume or assign and should emit a normal guaranteed diagnostic. if (fArg && fArg->isClosureCapture() && fArg->getArgumentConvention().isInoutConvention()) { assert( checkKind == MarkUnresolvedNonCopyableValueInst::CheckKind::NoConsumeOrAssign); moveChecker.diagnosticEmitter.emitObjectGuaranteedDiagnostic( markedValue); return true; } // If we have a function argument that is no_consume_or_assign and we do // not have any partial apply uses, then we know that we have a use of // an address only borrowed parameter that we need to if ((fArg || ptrToAddr) && checkKind == MarkUnresolvedNonCopyableValueInst::CheckKind:: NoConsumeOrAssign && !moveChecker.canonicalizer.hasPartialApplyConsumingUse()) { moveChecker.diagnosticEmitter.emitObjectGuaranteedDiagnostic( markedValue); return true; } // Finally try to emit either a global or class field error... if (!moveChecker.diagnosticEmitter .emitGlobalOrClassFieldLoadedAndConsumed(markedValue)) { // And otherwise if we failed emit an escaping closure error. moveChecker.diagnosticEmitter .emitAddressEscapingClosureCaptureLoadedAndConsumed(markedValue); } return true; } // If set, this will tell the checker that we can change this load into // a load_borrow. SmallVector leafRanges; TypeTreeLeafTypeRange::get(op, getRootAddress(), leafRanges); if (!leafRanges.size()) { LLVM_DEBUG(llvm::dbgs() << "Failed to form leaf type range!\n"); return false; } LLVM_DEBUG(llvm::dbgs() << "Found potential borrow: " << *user); if (checkForExclusivityHazards(li)) { LLVM_DEBUG(llvm::dbgs() << "Found exclusivity violation?!\n"); return true; } for (auto leafRange : leafRanges) { useState.borrows.insert({user, leafRange}); // If we had a load [copy], borrow then we know that all of its destroys // must have been destroy_value. So we can just gather up those // destroy_value and use then to create liveness to ensure that our // value is alive over the entire borrow scope we are going to create. LLVM_DEBUG(llvm::dbgs() << "Adding destroys from load as liveness uses " "since they will become end_borrows.\n"); for (auto *consumeUse : li->getConsumingUses()) { auto *dvi = cast(consumeUse->getUser()); useState.recordLivenessUse(dvi, leafRange); } } return true; } // First check if we had any consuming uses that actually needed a // copy. This will always be an error and we allow the user to recompile // and eliminate the error. This just allows us to rely on invariants // later. if (moveChecker.canonicalizer.foundConsumingUseRequiringCopy()) { LLVM_DEBUG(llvm::dbgs() << "Found that load at object level requires copies!\n"); // If we failed to understand how to perform the check or did not find // any targets... continue. In the former case we want to fail with a // checker did not understand diagnostic later and in the former, we // succeeded. // Otherwise, emit the diagnostic. moveChecker.diagnosticEmitter.emitObjectOwnedDiagnostic(markedValue); LLVM_DEBUG(llvm::dbgs() << "Emitted early object level diagnostic.\n"); return true; } for (auto leafRange : leafRanges) { if (!moveChecker.canonicalizer.foundFinalConsumingUses()) { LLVM_DEBUG(llvm::dbgs() << "Found potential borrow inst: " << *user); if (checkForExclusivityHazards(li)) { LLVM_DEBUG(llvm::dbgs() << "Found exclusivity violation?!\n"); return true; } useState.borrows.insert({user, leafRange}); // If we had a load [copy], borrow then we know that all of its destroys // must have been destroy_value. So we can just gather up those // destroy_value and use then to create liveness to ensure that our // value is alive over the entire borrow scope we are going to create. LLVM_DEBUG(llvm::dbgs() << "Adding destroys from load as liveness uses " "since they will become end_borrows.\n"); for (auto *consumeUse : li->getConsumingUses()) { auto *dvi = cast(consumeUse->getUser()); useState.recordLivenessUse(dvi, leafRange); } } else { // Now that we know that we are going to perform a take, perform a // checkForDestructure. checkForPartialMutation(useState, diagnosticEmitter, PartialMutation::Kind::Consume, op->getUser(), op->get()->getType(), leafRange, PartialMutation::consume()); // If we emitted an error diagnostic, do not transform further and // instead mark that we emitted an early diagnostic and return true. if (numDiagnostics != moveChecker.diagnosticEmitter.getDiagnosticCount()) { LLVM_DEBUG(llvm::dbgs() << "Emitting destructure through deinit error!\n"); return true; } // If we had a load [copy], store this into the copy list. These are the // things that we must merge into destroy_addr or reinits after we are // done checking. The load [take] are already complete and good to go. if (li->getOwnershipQualifier() == LoadOwnershipQualifier::Take) { LLVM_DEBUG(llvm::dbgs() << "Found take inst: " << *user); useState.recordTakeUse(user, leafRange); } else { LLVM_DEBUG(llvm::dbgs() << "Found copy inst: " << *user); useState.recordCopyUse(user, leafRange); } } } return true; } // Now that we have handled or loadTakeOrCopy, we need to now track our // additional pure takes. if (noncopyable::memInstMustConsume(op)) { // If we don't have a consumeable and assignable check kind, then we can't // consume. Emit an error. // // NOTE: Since SILGen eagerly loads loadable types from memory, this // generally will only handle address only types. if (markedValue->getCheckKind() != MarkUnresolvedNonCopyableValueInst:: CheckKind::ConsumableAndAssignable) { auto *fArg = dyn_cast( stripAccessMarkers(markedValue->getOperand())); if (fArg && fArg->isClosureCapture() && fArg->getType().isAddress()) { moveChecker.diagnosticEmitter.emitPromotedBoxArgumentError(markedValue, fArg); } else { moveChecker.diagnosticEmitter .emitAddressEscapingClosureCaptureLoadedAndConsumed(markedValue); } return true; } SmallVector leafRanges; TypeTreeLeafTypeRange::get(op, getRootAddress(), leafRanges); if (!leafRanges.size()) { LLVM_DEBUG(llvm::dbgs() << "Failed to form leaf type range!\n"); return false; } for (auto leafRange : leafRanges) { // Now check if we have a destructure through deinit. If we do, emit an // error. unsigned numDiagnostics = moveChecker.diagnosticEmitter.getDiagnosticCount(); checkForPartialMutation(useState, diagnosticEmitter, PartialMutation::Kind::Consume, op->getUser(), op->get()->getType(), leafRange, PartialMutation::consume()); if (numDiagnostics != moveChecker.diagnosticEmitter.getDiagnosticCount()) { LLVM_DEBUG(llvm::dbgs() << "Emitting destructure through deinit error!\n"); return true; } LLVM_DEBUG(llvm::dbgs() << "Pure consuming use: " << *user); useState.recordTakeUse(user, leafRange); } return true; } if (auto fas = FullApplySite::isa(user)) { switch (fas.getArgumentConvention(*op)) { case SILArgumentConvention::Indirect_In_Guaranteed: { LLVM_DEBUG(llvm::dbgs() << "in_guaranteed argument to function application\n"); SmallVector leafRanges; TypeTreeLeafTypeRange::get(op, getRootAddress(), leafRanges); if (!leafRanges.size()) { LLVM_DEBUG(llvm::dbgs() << "Failed to form leaf type range!\n"); return false; } for (auto leafRange : leafRanges) { useState.recordLivenessUse(user, leafRange); } return true; } case SILArgumentConvention::Indirect_Inout: case SILArgumentConvention::Indirect_InoutAliasable: case SILArgumentConvention::Indirect_In_CXX: case SILArgumentConvention::Indirect_In: case SILArgumentConvention::Indirect_Out: case SILArgumentConvention::Direct_Unowned: case SILArgumentConvention::Direct_Owned: case SILArgumentConvention::Direct_Guaranteed: case SILArgumentConvention::Pack_Inout: case SILArgumentConvention::Pack_Owned: case SILArgumentConvention::Pack_Guaranteed: case SILArgumentConvention::Pack_Out: break; } } if (auto *yi = dyn_cast(user)) { if (yi->getYieldInfoForOperand(*op).isGuaranteedInCaller()) { LLVM_DEBUG(llvm::dbgs() << "coroutine yield\n"); SmallVector leafRanges; TypeTreeLeafTypeRange::get(op, getRootAddress(), leafRanges); if (!leafRanges.size()) { LLVM_DEBUG(llvm::dbgs() << "Failed to form leaf type range!\n"); return false; } for (auto leafRange : leafRanges) { useState.recordLivenessUse(user, leafRange); } return true; } } if (auto *pas = dyn_cast(user)) { if (auto *fArg = dyn_cast( stripAccessMarkers(markedValue->getOperand()))) { // If we are processing an inout convention and we emitted an error on the // partial_apply, we shouldn't process this // mark_unresolved_non_copyable_value, but squelch the compiler doesn't // understand error. if (fArg->getArgumentConvention().isInoutConvention() && pas->getCalleeFunction()->hasSemanticsAttr( semantics::NO_MOVEONLY_DIAGNOSTICS)) { LLVM_DEBUG(llvm::dbgs() << "has no_moveonly_diagnostics attribute!\n"); diagnosticEmitter.emitEarlierPassEmittedDiagnostic(markedValue); return false; } } // If our partial apply takes this parameter as an inout parameter and it // has the no move only diagnostics marker on it, do not emit an error // either. if (auto *f = pas->getCalleeFunction()) { if (f->hasSemanticsAttr(semantics::NO_MOVEONLY_DIAGNOSTICS)) { if (ApplySite(pas).getCaptureConvention(*op).isInoutConvention()) { LLVM_DEBUG(llvm::dbgs() << "has no_moveonly_diagnostics attribute!\n"); diagnosticEmitter.emitEarlierPassEmittedDiagnostic(markedValue); return false; } } } if (pas->isOnStack() || ApplySite(pas).getArgumentConvention(*op).isInoutConvention()) { LLVM_DEBUG(llvm::dbgs() << "Found on stack partial apply or inout usage!\n"); // On-stack partial applications and their final consumes are always a // liveness use of their captures. SmallVector leafRanges; TypeTreeLeafTypeRange::get(op, getRootAddress(), leafRanges); if (!leafRanges.size()) { LLVM_DEBUG(llvm::dbgs() << "Failed to compute leaf range!\n"); return false; } for (auto leafRange : leafRanges) { // Attempt to find calls of the non-escaping partial apply and places // where the partial apply is passed to a function. We treat those as // liveness uses. If we find a use we don't understand, we return false // here. if (!findNonEscapingPartialApplyUses(pas, leafRange, useState)) { LLVM_DEBUG(llvm::dbgs() << "Failed to understand use of a " "non-escaping partial apply?!\n"); return false; } } return true; } } if (auto *explicitCopy = dyn_cast(op->getUser())) { assert(op->getOperandNumber() == ExplicitCopyAddrInst::Src && "Dest should have been handled earlier"); assert(!explicitCopy->isTakeOfSrc() && "If we had a take of src, this should have already been identified " "as a must consume"); SmallVector leafRanges; TypeTreeLeafTypeRange::get(op, getRootAddress(), leafRanges); if (!leafRanges.size()) { LLVM_DEBUG(llvm::dbgs() << "Failed to compute leaf range!\n"); return false; } for (auto leafRange : leafRanges) { useState.recordLivenessUse(user, leafRange); } return true; } if (isa(op->getUser())) { LLVM_DEBUG(llvm::dbgs() << "fix_lifetime use\n"); SmallVector leafRanges; TypeTreeLeafTypeRange::get(op, getRootAddress(), leafRanges); if (!leafRanges.size()) { LLVM_DEBUG(llvm::dbgs() << "Failed to compute leaf range!\n"); return false; } for (auto leafRange : leafRanges) { useState.recordLivenessUse(user, leafRange); } return true; } if (auto *access = dyn_cast(op->getUser())) { switch (access->getAccessKind()) { // Treat an opaque read access as a borrow liveness use for the duration // of the access. case SILAccessKind::Read: { LLVM_DEBUG(llvm::dbgs() << "begin_access [read]\n"); SmallVector leafRanges; TypeTreeLeafTypeRange::get(op, getRootAddress(), leafRanges); if (!leafRanges.size()) { LLVM_DEBUG(llvm::dbgs() << "Failed to form leaf type range!\n"); return false; } for (auto leafRange : leafRanges) { useState.recordLivenessUse(user, leafRange); } return true; } // Treat a deinit access as a consume of the entire value. case SILAccessKind::Deinit: llvm_unreachable("should have been handled by `memInstMustConsume`"); case SILAccessKind::Init: case SILAccessKind::Modify: llvm_unreachable("should look through these kinds of accesses currently"); } } if (isa(user)) { SmallVector leafRanges; TypeTreeLeafTypeRange::get(op, getRootAddress(), leafRanges); if (!leafRanges.size()) { LLVM_DEBUG(llvm::dbgs() << "Failed to form leaf type range!\n"); return false; } for (auto leafRange : leafRanges) { useState.recordLivenessUse(user, leafRange); } return true; } // If we don't fit into any of those categories, just track as a liveness // use. We assume all such uses must only be reads to the memory. So we assert // to be careful. LLVM_DEBUG(llvm::dbgs() << "Found liveness use: " << *user); SmallVector leafRanges; TypeTreeLeafTypeRange::get(op, getRootAddress(), leafRanges); if (!leafRanges.size()) { LLVM_DEBUG(llvm::dbgs() << "Failed to compute leaf range!\n"); return false; } #ifndef NDEBUG if (user->mayWriteToMemory()) { // TODO: `unchecked_take_enum_addr` should inherently be understood as // non-side-effecting when it's nondestructive. auto ue = dyn_cast(user); if (!ue || ue->isDestructive()) { llvm::errs() << "Found a write classified as a liveness use?!\n"; llvm::errs() << "Use: " << *user; llvm_unreachable("standard failure"); } } #endif for (auto leafRange : leafRanges) { useState.recordLivenessUse(user, leafRange); } return true; } //===----------------------------------------------------------------------===// // MARK: Global Dataflow //===----------------------------------------------------------------------===// namespace { using InstLeafTypePair = std::pair; using InstOptionalLeafTypePair = std::pair>; /// Post process the found liveness and emit errors if needed. TODO: Better /// name. struct GlobalLivenessChecker { UseState &addressUseState; DiagnosticEmitter &diagnosticEmitter; FieldSensitiveMultiDefPrunedLiveRange &liveness; SmallBitVector livenessVector; bool hadAnyErrorUsers = false; GlobalLivenessChecker(UseState &addressUseState, DiagnosticEmitter &diagnosticEmitter, FieldSensitiveMultiDefPrunedLiveRange &liveness) : addressUseState(addressUseState), diagnosticEmitter(diagnosticEmitter), liveness(liveness) {} /// Returns true if we emitted any errors. bool compute(); bool testInstVectorLiveness(UseState::InstToBitMap &instsToTest); void clear() { livenessVector.clear(); hadAnyErrorUsers = false; } }; } // namespace bool GlobalLivenessChecker::testInstVectorLiveness( UseState::InstToBitMap &instsToTest) { bool emittedDiagnostic = false; for (auto takeInstAndValue : instsToTest) { LLVM_DEBUG(llvm::dbgs() << " Checking: " << *takeInstAndValue.first); // Check if we are in the boundary... // If the bit vector does not contain any set bits, then we know that we did // not have any boundary violations for any leaf node of our root value. if (!liveness.isWithinBoundary(takeInstAndValue.first, takeInstAndValue.second)) { // TODO: Today, we don't tell the user the actual field itself where the // violation occurred and just instead just shows the two instructions. We // could be more specific though... LLVM_DEBUG(llvm::dbgs() << " Not within the boundary.\n"); continue; } LLVM_DEBUG(llvm::dbgs() << " Within the boundary! Emitting an error\n"); // Ok, we have an error and via the bit vector know which specific leaf // elements of our root type were within the per field boundary. We need to // go find the next reachable use that overlap with its sub-element. We only // emit a single error per use even if we get multiple sub elements that // match it. That helps reduce the amount of errors. // // DISCUSSION: It is important to note that this follows from the separation // of concerns behind this pass: we have simplified how we handle liveness // by losing this information. That being said, since we are erroring it is // ok that we are taking a little more time since we are not going to // codegen this code. // // That being said, set the flag that we saw at least one error, so we can // exit early after this loop. hadAnyErrorUsers = true; // B/c of the separation of concerns with our liveness, we now need to walk // blocks to go find the specific later takes that are reachable from this // take. It is ok that we are doing a bit more work here since we are going // to exit and not codegen. auto *errorUser = takeInstAndValue.first; auto errorSpan = takeInstAndValue.second; // First walk from errorUser to the end of the block, looking for a take or // a liveness use. If we find a single block error, emit the error and // continue. if (errorUser != errorUser->getParent()->getTerminator()) { bool foundSingleBlockError = false; for (auto ii = std::next(errorUser->getIterator()), ie = errorUser->getParent()->end(); ii != ie; ++ii) { if (addressUseState.isConsume(&*ii, errorSpan)) { diagnosticEmitter.emitAddressDiagnostic( addressUseState.address, &*ii, errorUser, true /*is consuming*/); foundSingleBlockError = true; emittedDiagnostic = true; break; } if (addressUseState.isLivenessUse(&*ii, errorSpan)) { diagnosticEmitter.emitAddressDiagnostic( addressUseState.address, &*ii, errorUser, false /*is consuming*/, addressUseState.isImplicitEndOfLifetimeLivenessUses(&*ii)); foundSingleBlockError = true; emittedDiagnostic = true; break; } // Check if we have a non-consuming liveness use. // // DISCUSSION: In certain cases, we only represent uses like end_borrow // in liveness and not in address use state. This ensures that we // properly emit a diagnostic in these cases. // // TODO: We should include liveness uses of the load_borrow itself in an // array and emit an error on those instead since it would be a better // error than using end_borrow here. { if (liveness.isInterestingUserOfKind( &*ii, FieldSensitivePrunedLiveness::NonLifetimeEndingUse, errorSpan)) { diagnosticEmitter.emitAddressDiagnostic( addressUseState.address, &*ii, errorUser, false /*is consuming*/, addressUseState.isImplicitEndOfLifetimeLivenessUses(&*ii)); foundSingleBlockError = true; emittedDiagnostic = true; break; } } if (addressUseState.isInitUse(&*ii, errorSpan)) { llvm::errs() << "Should not have errored if we see an init?! Init: " << *ii; llvm_unreachable("Standard compiler error"); } } if (foundSingleBlockError) continue; } // If we didn't find a single block error, then we need to go search for our // liveness error in successor blocks. We know that this means that our // current block must be live out. Do a quick check just to be careful. using IsLive = FieldSensitivePrunedLiveBlocks::IsLive; SmallVector isLiveArray; #ifndef NDEBUG liveness.getBlockLiveness(errorUser->getParent(), errorSpan, isLiveArray); assert(llvm::all_of( isLiveArray, [](IsLive liveness) { return liveness = IsLive::LiveOut; }) && "Should be live out?!"); isLiveArray.clear(); #endif BasicBlockWorklist worklist(errorUser->getFunction()); for (auto *succBlock : errorUser->getParent()->getSuccessorBlocks()) worklist.pushIfNotVisited(succBlock); LLVM_DEBUG(llvm::dbgs() << "Performing forward traversal from errorUse " "looking for the cause of liveness!\n"); llvm::SmallSetVector violatingInst; bool foundSingleBlockError = false; while (auto *block = worklist.pop()) { LLVM_DEBUG(llvm::dbgs() << "Visiting block: bb" << block->getDebugID() << "\n"); SWIFT_DEFER { isLiveArray.clear(); }; liveness.getBlockLiveness(block, takeInstAndValue.second, isLiveArray); // If we hit an init or dead along all bits in the block, we do not need // to process further successors. bool shouldVisitSuccessors = false; // Now search forward for uses. for (auto isLive : isLiveArray) { switch (isLive) { case IsLive::Dead: case IsLive::DeadToLiveEdge: LLVM_DEBUG(llvm::dbgs() << " Dead block!\n"); // Ignore a dead block. Our error use could not be in such a block. // // This can happen for instance along an exit block of a loop where // the error use is within the loop. continue; case IsLive::LiveOut: { LLVM_DEBUG(llvm::dbgs() << " Live out block!\n"); // If we see a live out block that is also a def block, skip. SmallBitVector defBits(addressUseState.getNumSubelements()); liveness.isDefBlock(block, errorSpan, defBits); if (!(defBits & errorSpan).none()) { LLVM_DEBUG(llvm::dbgs() << " Also a def block; skipping!\n"); continue; } LLVM_FALLTHROUGH; } case IsLive::LiveWithin: if (isLive == IsLive::LiveWithin) LLVM_DEBUG(llvm::dbgs() << " Live within block!\n"); bool foundInit = false; for (auto &blockInst : *block) { LLVM_DEBUG(llvm::dbgs() << " Inst: " << blockInst); if (addressUseState.isConsume(&blockInst, errorSpan)) { LLVM_DEBUG(llvm::dbgs() << " Is consume!\n"); diagnosticEmitter.emitAddressDiagnostic(addressUseState.address, &blockInst, errorUser, true /*is consuming*/); foundSingleBlockError = true; emittedDiagnostic = true; break; } if (addressUseState.isLivenessUse(&blockInst, errorSpan)) { LLVM_DEBUG(llvm::dbgs() << " Is liveness use!\n"); diagnosticEmitter.emitAddressDiagnostic( addressUseState.address, &blockInst, errorUser, false /*is consuming*/, addressUseState.isImplicitEndOfLifetimeLivenessUses( &blockInst)); foundSingleBlockError = true; emittedDiagnostic = true; break; } // If we find an init use for this bit... just break. if (addressUseState.isInitUse(&blockInst, errorSpan)) { foundInit = true; break; } } // If we did not find an init and visited the entire block... we need // to visit successors for at least one bit. if (!foundInit) shouldVisitSuccessors = true; assert((isLive == IsLive::LiveOut || foundSingleBlockError || foundInit) && "Should either have a pure live out, found an init, or we " "should have found " "an error."); } // If we found an error, break out of the loop. We don't have further // work to do. if (foundSingleBlockError) { break; } } // If we found an error, just bail without processing additional blocks. if (foundSingleBlockError) break; // If we saw only dead blocks or found inits for all bits... then we do // not need to process further if (!shouldVisitSuccessors) continue; // If we didn't find a single block error, add successors to the worklist // and visit them. for (auto *succBlock : block->getSuccessorBlocks()) worklist.pushIfNotVisited(succBlock); } } return emittedDiagnostic; } bool GlobalLivenessChecker::compute() { // Then revisit our takes, this time checking if we are within the boundary // and if we are, emit an error. LLVM_DEBUG(llvm::dbgs() << "Checking takes for errors!\n"); bool emittedDiagnostic = false; emittedDiagnostic |= testInstVectorLiveness(addressUseState.takeInsts); emittedDiagnostic |= testInstVectorLiveness(addressUseState.copyInsts); // If we emitted an error user, we should always emit at least one // diagnostic. If we didn't there is a bug in the implementation. assert(hadAnyErrorUsers == emittedDiagnostic); return hadAnyErrorUsers; } //===----------------------------------------------------------------------===// // MARK: Main Pass Implementation //===----------------------------------------------------------------------===// /// Create a new destroy_value instruction before the specified instruction and /// record it as a final consume. static void insertDestroyBeforeInstruction(UseState &addressUseState, SILInstruction *nextInstruction, SILValue baseAddress, SmallBitVector &bv, ConsumeInfo &consumes) { if (!baseAddress->getUsersOfType().empty()) { // If there are _any_ store_borrow users, then all users of the address are // store_borrows (and dealloc_stacks). Nothing is stored, there's nothing // to destroy. return; } // If we need all bits... if (bv.all()) { // And our next instruction is a destroy_addr on the base address, just // claim that destroy instead of inserting another destroy_addr. if (auto *dai = dyn_cast(nextInstruction)) { if (dai->getOperand() == baseAddress) { consumes.recordFinalConsume(dai, bv); return; } } // Otherwise, create a new destroy addr on the entire address. SILBuilderWithScope builder(nextInstruction); auto loc = RegularLocation::getAutoGeneratedLocation(nextInstruction->getLoc()); auto *dai = builder.createDestroyAddr(loc, baseAddress); consumes.recordFinalConsume(dai, bv); addressUseState.destroys.insert({dai, TypeTreeLeafTypeRange(0, bv.size())}); return; } // Otherwise, we have a partially destroyed type. Create new destroy addr for // each contiguous range of elts. This should only happen for structs/tuples. SILBuilderWithScope builder(nextInstruction); auto loc = RegularLocation::getAutoGeneratedLocation(nextInstruction->getLoc()); SmallVector> valuesToDestroy; TypeTreeLeafTypeRange::constructProjectionsForNeededElements( baseAddress, nextInstruction, addressUseState.domTree, bv, valuesToDestroy); while (!valuesToDestroy.empty()) { auto tuple = valuesToDestroy.pop_back_val(); SILValue value; TypeTreeLeafTypeRange range; NeedsDestroy_t needsDestroy; std::tie(value, range, needsDestroy) = tuple; if (value->getType().isTrivial(*nextInstruction->getFunction())) continue; SILInstruction *destroyingInst; if (needsDestroy) { auto *dai = builder.createDestroyAddr(loc, value); destroyingInst = dai; } else { destroyingInst = value.getDefiningInstruction(); } SmallBitVector consumedBits(bv.size()); range.setBits(consumedBits); consumes.recordFinalConsume(destroyingInst, consumedBits); addressUseState.destroys.insert({destroyingInst, range}); } } void MoveOnlyAddressCheckerPImpl::insertDestroysOnBoundary( MarkUnresolvedNonCopyableValueInst *markedValue, FieldSensitiveMultiDefPrunedLiveRange &liveness, FieldSensitivePrunedLivenessBoundary &boundary) { using IsInterestingUser = FieldSensitivePrunedLiveness::IsInterestingUser; LLVM_DEBUG(llvm::dbgs() << "Inserting destroys on boundary!\n"); // If we're in no_consume_or_assign mode, we don't insert destroys, as we've // already checked that there are no consumes. There can only be borrow uses, // which means no destruction is needed at all. // // NOTE: This also implies that we do not need to insert invalidating // debug_value undef since our value will not be invalidated. if (markedValue->getCheckKind() == MarkUnresolvedNonCopyableValueInst::CheckKind::NoConsumeOrAssign) { LLVM_DEBUG(llvm::dbgs() << " Skipping destroy insertion b/c no_consume_or_assign\n"); consumes.finishRecordingFinalConsumes(); return; } LLVM_DEBUG(llvm::dbgs() << " Visiting users!\n"); auto debugVar = DebugVarCarryingInst::getFromValue( stripAccessMarkers(markedValue->getOperand())); // Local helper that insert a debug_value undef to invalidate a noncopyable // value that has been moved. Importantly, for LLVM to recognize that we are // referring to the same debug variable as the original definition, we have to // use the same debug scope and location as the original debug var. auto insertUndefDebugValue = [&debugVar](SILInstruction *insertPt) { insertDebugValueBefore(insertPt, debugVar, [&] { return SILUndef::get(debugVar.getOperandForDebugValueClone()); }); }; // Control flow merge blocks used as insertion points. llvm::DenseMap mergeBlocks; for (auto &pair : boundary.getLastUsers()) { auto *inst = pair.first; auto &bv = pair.second; LLVM_DEBUG(llvm::dbgs() << " User: " << *inst); auto interestingUser = liveness.getInterestingUser(inst); SmallVector, 4> ranges; if (interestingUser) { interestingUser->getContiguousRanges(ranges, bv); } for (auto rangePair : ranges) { SmallBitVector bits(bv.size()); rangePair.first.setBits(bits); switch (rangePair.second) { case IsInterestingUser::LifetimeEndingUse: { LLVM_DEBUG( llvm::dbgs() << " Lifetime ending use! Recording final consume!\n"); // If we have a consuming use, when we stop at the consuming use we want // the value to still be around. We only want the value to be // invalidated once the consume operation has occured. Thus we always // place the debug_value undef strictly after the consuming operation. if (auto *ti = dyn_cast(inst)) { for (auto *succBlock : ti->getSuccessorBlocks()) { insertUndefDebugValue(&succBlock->front()); } } else { insertUndefDebugValue(inst->getNextInstruction()); } consumes.recordFinalConsume(inst, bits); continue; } case IsInterestingUser::NonUser: break; case IsInterestingUser::NonLifetimeEndingUse: LLVM_DEBUG(llvm::dbgs() << " NonLifetimeEndingUse! " "inserting destroy before instruction!\n"); // If we are dealing with an inout parameter, we will have modeled our // last use by treating a return inst as a last use. Since it doesn't // have any successors, this results in us not inserting any // destroy_addr. if (isa(inst)) { auto *block = inst->getParent(); for (auto *succBlock : block->getSuccessorBlocks()) { auto iter = mergeBlocks.find(succBlock); if (iter == mergeBlocks.end()) { iter = mergeBlocks.insert({succBlock, bits}).first; } else { // NOTE: We use |= here so that different regions of the same // terminator get updated appropriately. SmallBitVector &alreadySetBits = iter->second; bool hadCommon = alreadySetBits.anyCommon(bits); alreadySetBits |= bits; if (hadCommon) continue; } auto *insertPt = &*succBlock->begin(); insertDestroyBeforeInstruction(addressUseState, insertPt, liveness.getRootValue(), bits, consumes); // We insert the debug_value undef /after/ the last use since we // want the value to be around when we stop at the last use // instruction. insertUndefDebugValue(insertPt); } continue; } // If we have an implicit end of lifetime use, we do not insert a // destroy_addr. Instead, we insert an undef debug value after the // use. This occurs if we have an end_access associated with a // global_addr or a ref_element_addr field access. if (addressUseState.isImplicitEndOfLifetimeLivenessUses(inst)) { LLVM_DEBUG( llvm::dbgs() << " Use was an implicit end of lifetime liveness use!\n"); insertUndefDebugValue(inst->getNextInstruction()); continue; } auto *insertPt = inst->getNextInstruction(); insertDestroyBeforeInstruction(addressUseState, insertPt, liveness.getRootValue(), bits, consumes); // We insert the debug_value undef /after/ the last use since we want // the value to be around when we stop at the last use instruction. insertUndefDebugValue(insertPt); continue; } } } for (auto pair : boundary.getBoundaryEdges()) { auto *insertPt = &*pair.first->begin(); insertDestroyBeforeInstruction(addressUseState, insertPt, liveness.getRootValue(), pair.second, consumes); insertUndefDebugValue(insertPt); LLVM_DEBUG(llvm::dbgs() << " Inserting destroy on edge bb" << pair.first->getDebugID() << "\n"); } for (auto defPair : boundary.getDeadDefs()) { LLVM_DEBUG(llvm::dbgs() << " Inserting destroy on dead def" << *defPair.first); if (auto *arg = dyn_cast(defPair.first)) { auto *insertPt = &*arg->getParent()->begin(); insertDestroyBeforeInstruction(addressUseState, insertPt, liveness.getRootValue(), defPair.second, consumes); insertUndefDebugValue(insertPt); } else { auto *inst = cast(defPair.first); // If we have a dead def that is our mark must check and that mark must // check was an init but not consumable, then do not destroy that // def. This is b/c we are in some sort of class initialization and we are // looking at the initial part of the live range before the initialization // has occured. This is our way of makinmg this fit the model that the // checker expects (which is that values are always initialized at the def // point). if (markedValue && markedValue->getCheckKind() == MarkUnresolvedNonCopyableValueInst::CheckKind:: InitableButNotConsumable) continue; SILInstruction *insertPt; if (auto tryApply = dyn_cast(inst)) { // The dead def is only valid on the success return path. insertPt = &tryApply->getNormalBB()->front(); } else { insertPt = inst->getNextInstruction(); assert(insertPt && "instruction is a terminator that wasn't handled?"); } insertDestroyBeforeInstruction(addressUseState, insertPt, liveness.getRootValue(), defPair.second, consumes); insertUndefDebugValue(insertPt); } } consumes.finishRecordingFinalConsumes(); } void MoveOnlyAddressCheckerPImpl::rewriteUses( MarkUnresolvedNonCopyableValueInst *markedValue, FieldSensitiveMultiDefPrunedLiveRange &liveness, const FieldSensitivePrunedLivenessBoundary &boundary) { LLVM_DEBUG(llvm::dbgs() << "MoveOnlyAddressChecker Rewrite Uses!\n"); /// Whether the marked value appeared in a discard statement. const bool isDiscardingContext = !addressUseState.dropDeinitInsts.empty(); // Process destroys for (auto destroyPair : addressUseState.destroys) { /// Is this destroy instruction a final consuming use? SmallBitVector bits(liveness.getNumSubElements()); destroyPair.second.setBits(bits); bool isFinalConsume = consumes.claimConsume(destroyPair.first, bits); // Remove destroys that are not the final consuming use. // TODO: for C++ types we do not want to remove destroys as the caller is // still responsible for invoking the dtor for the moved-from object. // See GH Issue #77894. if (!isFinalConsume) { destroyPair.first->eraseFromParent(); continue; } // Otherwise, if we're in a discarding context, flag this final destroy_addr // as a point where we're missing an explicit `consume self`. The reasoning // here is that if a destroy of self is the final consuming use, // then these are the points where we implicitly destroy self to clean-up // that self var before exiting the scope. An explicit 'consume self' // that is thrown away is a consume of this // mark_unresolved_non_copyable_value'd var and not a destroy of it, // according to the use classifier. if (isDiscardingContext) { // Since the boundary computations treat a newly-added destroy prior to // a reinit within that same block as a "final consuming use", exclude // such destroys-before-reinit. We are only interested in the final // destroy of a var, not intermediate destroys of the var. if (addressUseState.precedesReinitInSameBlock(destroyPair.first)) continue; auto *dropDeinit = addressUseState.dropDeinitInsts.front(); diagnosticEmitter.emitMissingConsumeInDiscardingContext(destroyPair.first, dropDeinit); } } auto debugVar = DebugVarCarryingInst::getFromValue( stripAccessMarkers(markedValue->getOperand())); // Then convert all claimed reinits to inits. for (auto reinitPair : addressUseState.reinitInsts) { if (!isReinitToInitConvertibleInst(reinitPair.first)) continue; if (!consumes.claimConsume(reinitPair.first, reinitPair.second)) convertMemoryReinitToInitForm(reinitPair.first, debugVar); } // Check all takes. for (auto takeInst : addressUseState.takeInsts) { auto &bits = takeInst.second; bool claimedConsume = consumes.claimConsume(takeInst.first, bits); (void)claimedConsume; if (!claimedConsume) { llvm::errs() << "Found consume that was not recorded as a 'claimed consume'!\n"; llvm::errs() << "Unrecorded consume: " << *takeInst.first; llvm_unreachable("Standard compiler abort?!"); } } // Then rewrite all copy insts to be takes and claim them. for (auto copyInst : addressUseState.copyInsts) { auto &bits = copyInst.second; bool claimedConsume = consumes.claimConsume(copyInst.first, bits); if (!claimedConsume) { llvm::errs() << "Found consume that was not recorded as a 'claimed consume'!\n"; llvm::errs() << "Unrecorded consume: " << *copyInst.first; llvm_unreachable("Standard compiler abort?!"); } if (auto *li = dyn_cast(copyInst.first)) { // Convert this to its take form. auto accessPath = AccessPathWithBase::computeInScope(li->getOperand()); if (auto *access = dyn_cast_or_null(accessPath.base)) access->setAccessKind(SILAccessKind::Modify); li->setOwnershipQualifier(LoadOwnershipQualifier::Take); changed = true; continue; } if (auto *copy = dyn_cast(copyInst.first)) { // Convert this to its take form. auto accessPath = AccessPathWithBase::computeInScope(copy->getSrc()); if (auto *access = dyn_cast_or_null(accessPath.base)) access->setAccessKind(SILAccessKind::Modify); if (auto *oeai = dyn_cast_or_null(copy->getSrc())) { oeai->setAccessKind(OpenedExistentialAccess::Mutable); } copy->setIsTakeOfSrc(IsTake); continue; } llvm::dbgs() << "Unhandled copy user: " << *copyInst.first; llvm_unreachable("Unhandled case?!"); } // Finally now that we have placed all of our destroys in the appropriate // places, convert any copies that we know are borrows into begin_borrow. We // do not need to worry about expanding scopes since if we needed to expand a // scope, we would have emitted the scope expansion error during diagnostics. for (auto pair : addressUseState.borrows) { if (auto *li = dyn_cast(pair.first)) { // If we had a load -> load_borrow then we know that all of its destroys // must have been destroy_value. So we can just gather up those // destroy_value and use then to create a new load_borrow scope. SILBuilderWithScope builder(li); auto *lbi = builder.createLoadBorrow(li->getLoc(), li->getOperand()); // We use this auxillary list to avoid iterator invalidation of // li->getConsumingUse(); StackList toDelete(lbi->getFunction()); for (auto *consumeUse : li->getConsumingUses()) { auto *dvi = cast(consumeUse->getUser()); SILBuilderWithScope destroyBuilder(dvi); destroyBuilder.createEndBorrow(dvi->getLoc(), lbi); toDelete.push_back(dvi); changed = true; } while (!toDelete.empty()) toDelete.pop_back_val()->eraseFromParent(); li->replaceAllUsesWith(lbi); li->eraseFromParent(); continue; } llvm::dbgs() << "Borrow: " << *pair.first; llvm_unreachable("Unhandled case?!"); } #ifndef NDEBUG if (consumes.hasUnclaimedConsumes()) { llvm::errs() << "Found unclaimed consumes?!\n"; consumes.print(llvm::errs()); llvm_unreachable("Standard error?!"); } #endif } void MoveOnlyAddressCheckerPImpl::checkForReinitAfterDiscard() { auto const &dropDeinits = addressUseState.dropDeinitInsts; auto const &reinits = addressUseState.reinitInsts; if (dropDeinits.empty() || reinits.empty()) return; using BasicBlockMap = llvm::DenseMap>; BasicBlockMap blocksWithReinit; for (auto const &info : reinits) { auto *reinit = info.first; blocksWithReinit[reinit->getParent()].insert(reinit); } // Starting from each drop_deinit instruction, can we reach a reinit of self? for (auto *dropInst : dropDeinits) { auto *dropBB = dropInst->getParent(); // First, if the block containing this drop_deinit also contains a reinit, // check if that reinit happens after this drop_deinit. auto result = blocksWithReinit.find(dropBB); if (result != blocksWithReinit.end()) { auto &blockReinits = result->second; for (auto ii = std::next(dropInst->getIterator()); ii != dropBB->end(); ++ii) { SILInstruction *current = &*ii; if (blockReinits.contains(current)) { // Then the drop_deinit can reach a reinit immediately after it in the // same block. diagnosticEmitter.emitReinitAfterDiscardError(current, dropInst); return; } } } BasicBlockWorklist worklist(fn); // Seed the search with the successors of the drop_init block, so that if we // visit the drop_deinit block again, we'll know the reinits _before_ the // drop_deinit are reachable via some back-edge / cycle. for (auto *succ : dropBB->getSuccessorBlocks()) worklist.pushIfNotVisited(succ); // Determine reachability across blocks. while (auto *bb = worklist.pop()) { // Set-up next iteration. for (auto *succ : bb->getSuccessorBlocks()) worklist.pushIfNotVisited(succ); auto result = blocksWithReinit.find(bb); if (result == blocksWithReinit.end()) continue; // We found a reachable reinit! Identify the earliest reinit in this block // for diagnosis. auto &blockReinits = result->second; SILInstruction *firstBadReinit = nullptr; for (auto &inst : *bb) { if (blockReinits.contains(&inst)) { firstBadReinit = &inst; break; } } if (!firstBadReinit) llvm_unreachable("bug"); diagnosticEmitter.emitReinitAfterDiscardError(firstBadReinit, dropInst); return; } } } void ExtendUnconsumedLiveness::run() { ConsumingBlocksCollection consumingBlocks; DestroysCollection destroys; for (unsigned element = 0, count = liveness.getNumSubElements(); element < count; ++element) { for (auto pair : addressUseState.consumingBlocks) { if (pair.second.test(element)) { consumingBlocks.insert(pair.first); } } for (auto pair : addressUseState.destroys) { if (pair.second.contains(element)) { destroys[pair.first] = DestroyKind::Destroy; } } for (auto pair : addressUseState.takeInsts) { if (pair.second.test(element)) { destroys[pair.first] = DestroyKind::Take; } } for (auto pair : addressUseState.reinitInsts) { if (pair.second.test(element)) { destroys[pair.first] = DestroyKind::Reinit; } } runOnField(element, destroys, consumingBlocks); consumingBlocks.clear(); destroys.clear(); } } /// Extend liveness of each field as far as possible within the original live /// range as far as possible without incurring any copies. /// /// The strategy has two parts. /// /// (1) The global analysis: /// - Collect the blocks in which the field was live before canonicalization. /// These are the "original" live blocks (originalLiveBlocks). /// [Color these blocks green.] /// - From within that collection, collect the blocks which contain a _final_ /// consuming, non-destroy use, and their iterative successors. /// These are the "consumed" blocks (consumedAtExitBlocks). /// [Color these blocks red.] /// - Extend liveness down to the boundary between originalLiveBlocks and /// consumedAtExitBlocks blocks. /// [Extend liveness down to the boundary between green blocks and red.] /// - In particular, in regions of originalLiveBlocks which have no boundary /// with consumedAtExitBlocks, liveness should be extended to its original /// extent. /// [Extend liveness down to the boundary between green blocks and uncolored.] /// /// (2) The local analysis: /// - For in-block lifetimes, extend liveness forward from non-consuming uses /// and dead defs to the original destroy. void ExtendUnconsumedLiveness::runOnField( unsigned element, DestroysCollection &destroys, ConsumingBlocksCollection &consumingBlocks) { SILValue currentDef = addressUseState.address; // First, collect the blocks that were _originally_ live. We can't use // liveness here because it doesn't include blocks that occur before a // destroy_addr. BasicBlockSet originalLiveBlocks(currentDef->getFunction()); { // Some of the work here was already done by initializeLiveness. // Specifically, it already discovered all blocks containing (transitive) // uses and blocks that appear between them and the def. // // Seed the set with what it already discovered. for (auto *discoveredBlock : liveness.getDiscoveredBlocks()) originalLiveBlocks.insert(discoveredBlock); // Start the walk from the consuming blocks (which includes destroys as well // as the other consuming uses). BasicBlockWorklist worklist(currentDef->getFunction()); for (auto *consumingBlock : consumingBlocks) { if (!originalLiveBlocks.insert(consumingBlock) // Don't walk into the predecessors of blocks which kill liveness. && !isLiveAtBegin(consumingBlock, element, /*isLiveAtEnd=*/true, destroys)) { continue; } for (auto *predecessor : consumingBlock->getPredecessorBlocks()) { worklist.pushIfNotVisited(predecessor); } } // Walk backwards from consuming blocks. while (auto *block = worklist.pop()) { if (!originalLiveBlocks.insert(block)) { continue; } for (auto *predecessor : block->getPredecessorBlocks()) { worklist.pushIfNotVisited(predecessor); } } } // Second, collect the blocks which occur after a consuming use. BasicBlockSet consumedAtExitBlocks(currentDef->getFunction()); BasicBlockSetVector consumedAtEntryBlocks(currentDef->getFunction()); { // Start the forward walk from blocks which contain non-destroy consumes not // followed by defs. // // Because they contain a consume not followed by a def, these are // consumed-at-exit. BasicBlockWorklist worklist(currentDef->getFunction()); for (auto iterator : boundary.getLastUsers()) { if (!iterator.second.test(element)) continue; auto *instruction = iterator.first; // Skip over destroys on the boundary. auto iter = destroys.find(instruction); if (iter != destroys.end() && iter->second != DestroyKind::Take) { continue; } // Skip over non-consuming users. auto interestingUser = liveness.isInterestingUser(instruction, element); assert(interestingUser != FieldSensitivePrunedLiveness::IsInterestingUser::NonUser); if (interestingUser != FieldSensitivePrunedLiveness::IsInterestingUser::LifetimeEndingUse) { continue; } // A consume with a subsequent def doesn't cause the block to be // consumed-at-exit. if (hasDefAfter(instruction, element)) continue; worklist.push(instruction->getParent()); } while (auto *block = worklist.pop()) { consumedAtExitBlocks.insert(block); for (auto *successor : block->getSuccessorBlocks()) { if (!originalLiveBlocks.contains(successor)) continue; worklist.pushIfNotVisited(successor); consumedAtEntryBlocks.insert(successor); } } } // Third, find the blocks on the boundary between the originally-live blocks // and the originally-live-but-consumed blocks. Extend liveness "to the end" // of these blocks. for (auto *block : consumedAtEntryBlocks) { for (auto *predecessor : block->getPredecessorBlocks()) { if (consumedAtExitBlocks.contains(predecessor)) continue; // Add "the instruction(s) before the terminator" of the predecessor to // liveness. addPreviousInstructionToLiveness(predecessor->getTerminator(), element); } } // Finally, preserve the destroys which weren't in the consumed region in // place: hoisting such destroys would not avoid copies. for (auto pair : destroys) { auto *destroy = pair.first; if (!shouldAddDestroyToLiveness(destroy, element, consumedAtExitBlocks, consumedAtEntryBlocks)) continue; addPreviousInstructionToLiveness(destroy, element); } } bool ExtendUnconsumedLiveness::shouldAddDestroyToLiveness( SILInstruction *destroy, unsigned element, BasicBlockSet const &consumedAtExitBlocks, BasicBlockSetVector const &consumedAtEntryBlocks) { auto *block = destroy->getParent(); bool followedByDef = hasDefAfter(destroy, element); if (!followedByDef) { // This destroy is the last write to the field in the block. // // If the block is consumed-at-exit, then there is some other consuming use // before this destroy. Liveness can't be extended. return !consumedAtExitBlocks.contains(block); } for (auto *inst = destroy->getPreviousInstruction(); inst; inst = inst->getPreviousInstruction()) { if (liveness.isDef(inst, element)) { // Found the corresponding def with no intervening users. Liveness // can be extended to the destroy. return true; } auto interestingUser = liveness.isInterestingUser(inst, element); switch (interestingUser) { case FieldSensitivePrunedLiveness::IsInterestingUser::NonUser: break; case FieldSensitivePrunedLiveness::IsInterestingUser::NonLifetimeEndingUse: // The first use seen is non-consuming. Liveness can be extended to the // destroy. return true; break; case FieldSensitivePrunedLiveness::IsInterestingUser::LifetimeEndingUse: // Found a consuming use. Liveness can't be extended to the destroy // (without creating a copy and triggering a diagnostic). return false; break; } } // Found no uses or defs between the destroy and the top of the block. If the // block was not consumed at entry, liveness can be extended to the destroy. return !consumedAtEntryBlocks.contains(block); } /// Compute the block's effect on liveness and apply it to \p isLiveAtEnd. bool ExtendUnconsumedLiveness::isLiveAtBegin(SILBasicBlock *block, unsigned element, bool isLiveAtEnd, DestroysCollection const &destroys) { enum class Effect { None, // 0 Kill, // 1 Gen, // 2 }; auto effect = Effect::None; for (auto &instruction : llvm::reverse(*block)) { // An instruction can be both a destroy and a def. If it is, its // behavior is first to destroy and then to init. So when walking // backwards, its last action is to destroy, so its effect is that of any // destroy. if (destroys.find(&instruction) != destroys.end()) { effect = Effect::Gen; } else if (liveness.isDef(&instruction, element)) { effect = Effect::Kill; } } switch (effect) { case Effect::None: return isLiveAtEnd; case Effect::Kill: return false; case Effect::Gen: return true; } } bool ExtendUnconsumedLiveness::hasDefAfter(SILInstruction *start, unsigned element) { // NOTE: Start iteration at \p start, not its sequel, because // it might be both a consuming use and a def. for (auto *inst = start; inst; inst = inst->getNextInstruction()) { if (liveness.isDef(inst, element)) return true; } return false; } void ExtendUnconsumedLiveness::addPreviousInstructionToLiveness( SILInstruction *inst, unsigned element) { inst->visitPriorInstructions([&](auto *prior) { if (liveness.isDef(prior, element)) { return true; } auto range = TypeTreeLeafTypeRange(element, element + 1); liveness.extendToNonUse(prior, range); return true; }); } bool MoveOnlyAddressCheckerPImpl::performSingleCheck( MarkUnresolvedNonCopyableValueInst *markedAddress) { SWIFT_DEFER { diagnosticEmitter.clearUsesWithDiagnostic(); }; unsigned diagCount = diagnosticEmitter.getDiagnosticCount(); // Before we do anything, canonicalize load_borrow + copy_value into load // [copy] + begin_borrow for further processing. This just eliminates a case // that the checker doesn't need to know about. { RAIILLVMDebug l("CopiedLoadBorrowEliminationVisitor"); CopiedLoadBorrowEliminationState state(markedAddress->getFunction()); CopiedLoadBorrowEliminationVisitor copiedLoadBorrowEliminator(state); // FIXME: should check AddressUseKind::NonEscaping != walk() to handle // PointerEscape. if (AddressUseKind::Unknown == std::move(copiedLoadBorrowEliminator).walk(markedAddress)) { LLVM_DEBUG(llvm::dbgs() << "Failed copied load borrow eliminator visit: " << *markedAddress); return false; } state.process(); } // Then if we have a let allocation, see if we have any copy_addr on our // markedAddress that form temporary allocation chains. This occurs when we // emit SIL for code like: // // let x: AddressOnlyType = ... // let _ = x.y.z // // SILGen will treat y as a separate rvalue from x and will create a temporary // allocation. In contrast if we have a var, we treat x like an lvalue and // just create GEPs appropriately. { RAIILLVMDebug l("temporary allocations from rvalue accesses"); if (eliminateTemporaryAllocationsFromLet(markedAddress)) { LLVM_DEBUG( llvm::dbgs() << "Succeeded in eliminating temporary allocations! Fn after:\n"; markedAddress->getFunction()->dump()); changed = true; } } // Then gather all uses of our address by walking from def->uses. We use this // to categorize the uses of this address into their ownership behavior (e.g.: // init, reinit, take, destroy, etc.). GatherUsesVisitor visitor(*this, addressUseState, markedAddress, diagnosticEmitter); SWIFT_DEFER { visitor.clear(); }; { RAIILLVMDebug l("main use gathering visitor"); visitor.reset(markedAddress); // FIXME: should check walkResult != AddressUseKind::NonEscaping to handle // PointerEscape. if (AddressUseKind::Unknown == std::move(visitor).walk(markedAddress)) { LLVM_DEBUG(llvm::dbgs() << "Failed access path visit: " << *markedAddress); return false; } } // If we found a load [copy] or copy_addr that requires multiple copies or an // exclusivity error, then we emitted an early error. Bail now and allow the // user to fix those errors and recompile to get further errors. // // DISCUSSION: The reason why we do this is in the dataflow below we want to // be able to assume that the load [copy] or copy_addr/copy_addr [init] are // actual last uses, but the frontend that emitted the code for simplicity // emitted a copy from the base address + a destroy_addr of the use. By // bailing here, we can make that assumption since we would have errored // earlier otherwise. if (diagCount != diagnosticEmitter.getDiagnosticCount()) return true; // Now that we know that we have run our visitor and did not emit any errors // and successfully visited everything, see if have any // assignable_but_not_consumable of address only types that are consumed. // // DISCUSSION: For non address only types, this is not an issue since we // eagerly load addressUseState.initializeImplicitEndOfLifetimeLivenessUses(); //===--- // Liveness Checking // SmallVector discoveredBlocks; FieldSensitiveMultiDefPrunedLiveRange liveness(fn, markedAddress, &discoveredBlocks); { RAIILLVMDebug logger("liveness initialization"); addressUseState.initializeLiveness(liveness); } // Now freeze our multimaps. addressUseState.freezeMultiMaps(); { RAIILLVMDebug l("checking for partial reinits"); PartialReinitChecker checker(addressUseState, diagnosticEmitter); unsigned count = diagnosticEmitter.getDiagnosticCount(); checker.performPartialReinitChecking(liveness); if (count != diagnosticEmitter.getDiagnosticCount()) { LLVM_DEBUG(llvm::dbgs() << "Found a partial reinit error! Ending early!\n"); return true; } } { RAIILLVMDebug l("performing global liveness checks"); // Then compute the takes that are within the cumulative boundary of // liveness that we have computed. If we find any, they are the errors // ones. GlobalLivenessChecker emitter(addressUseState, diagnosticEmitter, liveness); // If we had any errors, we do not want to modify the SIL... just bail. if (emitter.compute()) { return true; } } // First add any debug_values that we saw as liveness uses. This is important // since the debugger wants to see live values when we define a debug_value, // but we do not want to use them earlier when emitting diagnostic errors. if (auto *di = addressUseState.debugValue) { // Move the debug_value to right after the markedAddress to ensure that we // do not actually change our liveness computation. // // NOTE: The author is not sure if this can ever happen with SILGen output, // but this is being put just to be safe. di->moveAfter(markedAddress); liveness.updateForUse(di, TypeTreeLeafTypeRange(markedAddress), false /*lifetime ending*/); } // Compute our initial boundary. FieldSensitivePrunedLivenessBoundary boundary(liveness.getNumSubElements()); liveness.computeBoundary(boundary); LLVM_DEBUG(llvm::dbgs() << "Initial use based boundary:\n"; boundary.dump()); if (!DisableMoveOnlyAddressCheckerLifetimeExtension) { ExtendUnconsumedLiveness extension(addressUseState, liveness, boundary); extension.run(); } boundary.clear(); liveness.computeBoundary(boundary); LLVM_DEBUG(llvm::dbgs() << "Final maximized boundary:\n"; boundary.dump()); //=== // Final Transformation // // Ok, we now know that we fit our model since we did not emit errors and thus // can begin the transformation. SWIFT_DEFER { consumes.clear(); }; insertDestroysOnBoundary(markedAddress, liveness, boundary); checkForReinitAfterDiscard(); rewriteUses(markedAddress, liveness, boundary); return true; } //===----------------------------------------------------------------------===// // MARK: Top Level Entrypoint //===----------------------------------------------------------------------===// #ifndef NDEBUG static llvm::cl::opt NumTopLevelToProcess( "sil-move-only-address-checker-num-top-level-to-process", llvm::cl::desc("Allows for bisecting on move introducer that causes an " "error. Only meant for debugging!"), llvm::cl::init(UINT64_MAX)); #endif static llvm::cl::opt DumpSILBeforeRemovingMarkUnresolvedNonCopyableValueInst( "sil-move-only-address-checker-dump-before-removing-mark-must-check", llvm::cl::desc( "When bisecting it is useful to dump the SIL before the " "rest of the checker removes " "mark_unresolved_non_copyable_value. This lets one " "grab the SIL of a bad variable after all of the rest have " "been processed to work with further in sil-opt."), llvm::cl::init(false)); bool MoveOnlyAddressChecker::check( llvm::SmallSetVector &moveIntroducersToProcess) { assert(moveIntroducersToProcess.size() && "Must have checks to process to call this function"); MoveOnlyAddressCheckerPImpl pimpl(fn, diagnosticEmitter, domTree, poa, deadEndBlocksAnalysis, allocator); #ifndef NDEBUG static uint64_t numProcessed = 0; #endif for (auto *markedValue : moveIntroducersToProcess) { #ifndef NDEBUG ++numProcessed; if (NumTopLevelToProcess <= numProcessed) break; #endif LLVM_DEBUG(llvm::dbgs() << "======>>> Visiting top level: " << *markedValue); // Perform our address check. unsigned diagnosticEmittedByEarlierPassCount = diagnosticEmitter.getDiagnosticEmittedByEarlierPassCount(); if (!pimpl.performSingleCheck(markedValue)) { if (diagnosticEmittedByEarlierPassCount != diagnosticEmitter.getDiagnosticEmittedByEarlierPassCount()) { LLVM_DEBUG( llvm::dbgs() << "Failed to perform single check but found earlier emitted " "error. Not emitting checker doesn't understand diagnostic!\n"); continue; } LLVM_DEBUG(llvm::dbgs() << "Failed to perform single check! Emitting " "compiler doesn't understand diagnostic!\n"); // If we fail the address check in some way, set the diagnose! diagnosticEmitter.emitCheckerDoesntUnderstandDiagnostic(markedValue); } markedValue->replaceAllUsesWith(markedValue->getOperand()); markedValue->eraseFromParent(); } if (DumpSILBeforeRemovingMarkUnresolvedNonCopyableValueInst) { LLVM_DEBUG(llvm::dbgs() << "Dumping SIL before removing mark must checks!\n"; fn->dump()); } return pimpl.changed; } bool MoveOnlyAddressChecker::completeLifetimes() { // TODO: Delete once OSSACompleteLifetime is run as part of SILGenCleanup bool changed = false; // Lifetimes must be completed inside out (bottom-up in the CFG). PostOrderFunctionInfo *postOrder = poa->get(fn); OSSACompleteLifetime completion(fn, domTree, *deadEndBlocksAnalysis->get(fn)); for (auto *block : postOrder->getPostOrder()) { for (SILInstruction &inst : reverse(*block)) { for (auto result : inst.getResults()) { if (llvm::any_of(result->getUsers(), [](auto *user) { return isa(user); })) { continue; } if (completion.completeOSSALifetime( result, OSSACompleteLifetime::Boundary::Availability) == LifetimeCompletion::WasCompleted) { changed = true; } } } for (SILArgument *arg : block->getArguments()) { if (arg->isReborrow()) { continue; } if (completion.completeOSSALifetime( arg, OSSACompleteLifetime::Boundary::Availability) == LifetimeCompletion::WasCompleted) { changed = true; } } } return changed; }