| //===--- COWArrayOpt.cpp - Optimize Copy-On-Write Array Checks ------------===// |
| // |
| // This source file is part of the Swift.org open source project |
| // |
| // Copyright (c) 2014 - 2017 Apple Inc. and the Swift project authors |
| // Licensed under Apache License v2.0 with Runtime Library Exception |
| // |
| // See https://swift.org/LICENSE.txt for license information |
| // See https://swift.org/CONTRIBUTORS.txt for the list of Swift project authors |
| // |
| //===----------------------------------------------------------------------===// |
| /// |
| /// Optimize CoW array access by hoisting uniqueness checks. |
| /// |
| //===----------------------------------------------------------------------===// |
| |
| #define DEBUG_TYPE "cowarray-opts" |
| |
| #include "ArrayOpt.h" |
| #include "swift/SIL/CFG.h" |
| #include "swift/SIL/DebugUtils.h" |
| #include "swift/SIL/InstructionUtils.h" |
| #include "swift/SIL/LoopInfo.h" |
| #include "swift/SIL/Projection.h" |
| #include "swift/SIL/SILArgument.h" |
| #include "swift/SIL/SILBuilder.h" |
| #include "swift/SIL/SILInstruction.h" |
| #include "swift/SILOptimizer/Analysis/ARCAnalysis.h" |
| #include "swift/SILOptimizer/Analysis/AliasAnalysis.h" |
| #include "swift/SILOptimizer/Analysis/ArraySemantic.h" |
| #include "swift/SILOptimizer/Analysis/ColdBlockInfo.h" |
| #include "swift/SILOptimizer/Analysis/DominanceAnalysis.h" |
| #include "swift/SILOptimizer/Analysis/LoopAnalysis.h" |
| #include "swift/SILOptimizer/Analysis/RCIdentityAnalysis.h" |
| #include "swift/SILOptimizer/Analysis/ValueTracking.h" |
| #include "swift/SILOptimizer/PassManager/Passes.h" |
| #include "swift/SILOptimizer/PassManager/Transforms.h" |
| #include "swift/SILOptimizer/Utils/InstOptUtils.h" |
| #include "llvm/ADT/MapVector.h" |
| #include "llvm/ADT/StringExtras.h" |
| #include "llvm/Support/CommandLine.h" |
| #include "llvm/Support/Debug.h" |
| using namespace swift; |
| |
| // Do the two values \p A and \p B reference the same 'array' after potentially |
| // looking through a load. To identify a common array address this functions |
| // strips struct projections until it hits \p ArrayAddress. |
| bool areArraysEqual(RCIdentityFunctionInfo *RCIA, SILValue A, SILValue B, |
| SILValue ArrayAddress) { |
| A = RCIA->getRCIdentityRoot(A); |
| B = RCIA->getRCIdentityRoot(B); |
| if (A == B) |
| return true; |
| // We have stripped off struct_extracts. Remove the load to look at the |
| // address we are loading from. |
| if (auto *ALoad = dyn_cast<LoadInst>(A)) |
| A = ALoad->getOperand(); |
| if (auto *BLoad = dyn_cast<LoadInst>(B)) |
| B = BLoad->getOperand(); |
| // Strip off struct_extract_refs until we hit array address. |
| if (ArrayAddress) { |
| StructElementAddrInst *SEAI = nullptr; |
| while (A != ArrayAddress && (SEAI = dyn_cast<StructElementAddrInst>(A))) |
| A = SEAI->getOperand(); |
| while (B != ArrayAddress && (SEAI = dyn_cast<StructElementAddrInst>(B))) |
| B = SEAI->getOperand(); |
| } |
| return A == B; |
| } |
| |
| /// \return true if the given instruction releases the given value. |
| static bool isRelease(SILInstruction *Inst, SILValue RetainedValue, |
| SILValue ArrayAddress, RCIdentityFunctionInfo *RCIA, |
| SmallPtrSetImpl<Operand *> &MatchedReleases) { |
| |
| // Before we can match a release with a retain we need to check that we have |
| // not already matched the release with a retain we processed earlier. |
| // We don't want to match the release with both retains in the example below. |
| // |
| // retain %a <--| |
| // retain %a | Match. <-| Don't match. |
| // release %a <--| <-| |
| // |
| if (auto *R = dyn_cast<ReleaseValueInst>(Inst)) |
| if (!MatchedReleases.count(&R->getOperandRef())) |
| if (areArraysEqual(RCIA, Inst->getOperand(0), RetainedValue, |
| ArrayAddress)) { |
| LLVM_DEBUG(llvm::dbgs() << " matching with release " << *Inst); |
| MatchedReleases.insert(&R->getOperandRef()); |
| return true; |
| } |
| |
| if (auto *R = dyn_cast<StrongReleaseInst>(Inst)) |
| if (!MatchedReleases.count(&R->getOperandRef())) |
| if (areArraysEqual(RCIA, Inst->getOperand(0), RetainedValue, |
| ArrayAddress)) { |
| LLVM_DEBUG(llvm::dbgs() << " matching with release " << *Inst); |
| MatchedReleases.insert(&R->getOperandRef()); |
| return true; |
| } |
| |
| LLVM_DEBUG(llvm::dbgs() << " not a matching release " << *Inst); |
| return false; |
| } |
| |
| namespace { |
| |
| /// Optimize Copy-On-Write array checks based on high-level semantics. |
| /// |
| /// Performs an analysis on all Array users to ensure they do not interfere |
| /// with make_mutable hoisting. Ultimately, the only thing that can interfere |
| /// with make_mutable is a retain of the array. To ensure no retains occur |
| /// within the loop, it is necessary to check that the array does not escape on |
| /// any path reaching the loop, and that it is not directly retained within the |
| /// loop itself. |
| /// |
| /// In some cases, a retain does exist within the loop, but is balanced by a |
| /// release or call to @owned. The analysis must determine whether any array |
| /// mutation can occur between the retain and release. To accomplish this it |
| /// relies on knowledge of all array operations within the loop. If the array |
| /// escapes in some way that cannot be tracked, the analysis must fail. |
| /// |
| /// TODO: Handle this pattern: |
| /// retain(array) |
| /// call(array) |
| /// release(array) |
| /// Whenever the call is readonly, has balanced retain/release for the array, |
| /// and does not capture the array. Under these conditions, the call can neither |
| /// mutate the array nor save an alias for later mutation. |
| /// |
| /// TODO: Completely eliminate make_mutable calls if all operations that the |
| /// guard are already guarded by either "init" or "mutate_unknown". |
| class COWArrayOpt { |
| typedef StructUseCollector::UserList UserList; |
| typedef StructUseCollector::UserOperList UserOperList; |
| |
| RCIdentityFunctionInfo *RCIA; |
| SILFunction *Function; |
| SILLoop *Loop; |
| SILBasicBlock *Preheader; |
| DominanceInfo *DomTree; |
| bool HasChanged = false; |
| |
| // Keep track of cold blocks. |
| ColdBlockInfo ColdBlocks; |
| |
| // Cache of the analysis whether a loop is safe wrt.std::make_unique hoisting by |
| // looking at the operations (no uniquely identified objects). |
| std::pair<bool, bool> CachedSafeLoop; |
| |
| // Set of all blocks that may reach the loop, not including loop blocks. |
| llvm::SmallPtrSet<SILBasicBlock*,32> ReachingBlocks; |
| |
| /// Transient per-Array user set. |
| /// |
| /// Track all known array users with the exception of struct_extract users |
| /// (checkSafeArrayElementUse prohibits struct_extract users from mutating the |
| /// array). During analysis of retains/releases within the loop body, the |
| /// users in this set are assumed to cover all possible mutating operations on |
| /// the array. If the array escaped through an unknown use, the analysis must |
| /// abort earlier. |
| SmallPtrSet<SILInstruction*, 8> ArrayUserSet; |
| |
| /// Array loads which can be hoisted because a make_mutable of that array |
| /// was hoisted previously. |
| /// This is important to handle the two dimensional array case. |
| SmallPtrSet<LoadInst *, 4> HoistableLoads; |
| |
| // When matching retains to releases we must not match the same release twice. |
| // |
| // For example we could have: |
| // retain %a // id %1 |
| // retain %a // id %2 |
| // release %a // id %3 |
| // When we match %1 with %3, we can't match %3 again when we look for a |
| // matching release for %2. |
| // The set refers to operands instead of instructions because an apply could |
| // have several operands with release semantics. |
| SmallPtrSet<Operand*, 8> MatchedReleases; |
| |
| // The address of the array passed to the current make_mutable we are |
| // analyzing. |
| SILValue CurrentArrayAddr; |
| public: |
| COWArrayOpt(RCIdentityFunctionInfo *RCIA, SILLoop *L, DominanceAnalysis *DA) |
| : RCIA(RCIA), Function(L->getHeader()->getParent()), Loop(L), |
| Preheader(L->getLoopPreheader()), DomTree(DA->get(Function)), |
| ColdBlocks(DA), CachedSafeLoop(false, false) {} |
| |
| bool run(); |
| |
| protected: |
| bool checkUniqueArrayContainer(SILValue ArrayContainer); |
| SmallPtrSetImpl<SILBasicBlock*> &getReachingBlocks(); |
| bool isRetainReleasedBeforeMutate(SILInstruction *RetainInst, |
| bool IsUniquelyIdentifiedArray = true); |
| bool checkSafeArrayAddressUses(UserList &AddressUsers); |
| bool checkSafeArrayValueUses(UserList &ArrayValueUsers); |
| bool checkSafeArrayElementUse(SILInstruction *UseInst, SILValue ArrayVal); |
| bool checkSafeElementValueUses(UserOperList &ElementValueUsers); |
| bool hoistMakeMutable(ArraySemanticsCall MakeMutable, bool dominatesExits); |
| bool dominatesExitingBlocks(SILBasicBlock *BB); |
| void hoistAddressProjections(Operand &ArrayOp); |
| bool hasLoopOnlyDestructorSafeArrayOperations(); |
| SILValue getArrayAddressBase(SILValue V); |
| }; |
| } // end anonymous namespace |
| |
| /// \return true of the given container is known to be a unique copy of the |
| /// array with no aliases. Cases we check: |
| /// |
| /// (1) An @inout argument. |
| /// |
| /// (2) A local variable, which may be copied from a by-val argument, |
| /// initialized directly, or copied from a function return value. We don't |
| /// need to check how it is initialized here, because that will show up as a |
| /// store to the local's address. checkSafeArrayAddressUses will check that the |
| /// store is a simple initialization outside the loop. |
| bool COWArrayOpt::checkUniqueArrayContainer(SILValue ArrayContainer) { |
| if (auto *Arg = dyn_cast<SILArgument>(ArrayContainer)) { |
| // Check that the argument is passed as an inout type. This means there are |
| // no aliases accessible within this function scope. |
| auto Params = Function->getLoweredFunctionType()->getParameters(); |
| ArrayRef<SILArgument *> FunctionArgs = Function->begin()->getArguments(); |
| for (unsigned ArgIdx = 0, ArgEnd = Params.size(); |
| ArgIdx != ArgEnd; ++ArgIdx) { |
| if (FunctionArgs[ArgIdx] != Arg) |
| continue; |
| |
| if (!Params[ArgIdx].isIndirectInOut()) { |
| LLVM_DEBUG(llvm::dbgs() |
| << " Skipping Array: Not an inout argument!\n"); |
| return false; |
| } |
| } |
| return true; |
| } |
| else if (isa<AllocStackInst>(ArrayContainer)) |
| return true; |
| |
| if (auto *LI = dyn_cast<LoadInst>(ArrayContainer)) { |
| // A load of another array, which follows a make_mutable, also guarantees |
| // a unique container. This is the case if the current array is an element |
| // of the outer array in nested arrays. |
| if (HoistableLoads.count(LI) != 0) |
| return true; |
| } |
| |
| // TODO: we should also take advantage of access markers to identify |
| // unique arrays. |
| |
| LLVM_DEBUG(llvm::dbgs() |
| << " Skipping Array: Not an argument or local variable!\n"); |
| return false; |
| } |
| |
| /// Lazily compute blocks that may reach the loop. |
| SmallPtrSetImpl<SILBasicBlock*> &COWArrayOpt::getReachingBlocks() { |
| if (ReachingBlocks.empty()) { |
| SmallVector<SILBasicBlock*, 8> Worklist; |
| ReachingBlocks.insert(Preheader); |
| Worklist.push_back(Preheader); |
| while (!Worklist.empty()) { |
| SILBasicBlock *BB = Worklist.pop_back_val(); |
| for (auto PI = BB->pred_begin(), PE = BB->pred_end(); PI != PE; ++PI) { |
| if (ReachingBlocks.insert(*PI).second) |
| Worklist.push_back(*PI); |
| } |
| } |
| } |
| return ReachingBlocks; |
| } |
| |
| |
| /// \return true if the instruction is a call to a non-mutating array semantic |
| /// function. |
| static bool isNonMutatingArraySemanticCall(SILInstruction *Inst) { |
| ArraySemanticsCall Call(Inst); |
| if (!Call) |
| return false; |
| |
| switch (Call.getKind()) { |
| case ArrayCallKind::kNone: |
| case ArrayCallKind::kArrayPropsIsNativeTypeChecked: |
| case ArrayCallKind::kCheckSubscript: |
| case ArrayCallKind::kCheckIndex: |
| case ArrayCallKind::kGetCount: |
| case ArrayCallKind::kGetCapacity: |
| case ArrayCallKind::kGetElement: |
| case ArrayCallKind::kGetElementAddress: |
| case ArrayCallKind::kEndMutation: |
| return true; |
| case ArrayCallKind::kMakeMutable: |
| case ArrayCallKind::kMutateUnknown: |
| case ArrayCallKind::kReserveCapacityForAppend: |
| case ArrayCallKind::kWithUnsafeMutableBufferPointer: |
| case ArrayCallKind::kArrayInit: |
| case ArrayCallKind::kArrayInitEmpty: |
| case ArrayCallKind::kArrayUninitialized: |
| case ArrayCallKind::kArrayUninitializedIntrinsic: |
| case ArrayCallKind::kArrayFinalizeIntrinsic: |
| case ArrayCallKind::kAppendContentsOf: |
| case ArrayCallKind::kAppendElement: |
| return false; |
| } |
| |
| llvm_unreachable("Unhandled ArrayCallKind in switch."); |
| } |
| |
| /// \return true if the given retain instruction is followed by a release on the |
| /// same object prior to any potential mutating operation. |
| bool COWArrayOpt::isRetainReleasedBeforeMutate(SILInstruction *RetainInst, |
| bool IsUniquelyIdentifiedArray) { |
| // If a retain is found outside the loop ignore it. Otherwise, it must |
| // have a matching @owned call. |
| if (!Loop->contains(RetainInst)) |
| return true; |
| |
| LLVM_DEBUG(llvm::dbgs() << " Looking at retain " << *RetainInst); |
| |
| // Walk forward looking for a release of ArrayLoad or element of |
| // ArrayUserSet. Note that ArrayUserSet does not included uses of elements |
| // within the Array. Consequently, checkSafeArrayElementUse must prove that |
| // no uses of the Array value, or projections of it can lead to mutation |
| // (element uses may only be retained/released). |
| for (auto II = std::next(SILBasicBlock::iterator(RetainInst)), |
| IE = RetainInst->getParent()->end(); II != IE; ++II) { |
| if (isRelease(&*II, RetainInst->getOperand(0), CurrentArrayAddr, RCIA, |
| MatchedReleases)) |
| return true; |
| |
| if (isa<RetainValueInst>(II) || isa<StrongRetainInst>(II)) |
| continue; |
| |
| // A side effect free instruction cannot mutate the array. |
| if (!II->mayHaveSideEffects()) |
| continue; |
| |
| // Non mutating array calls are safe. |
| if (isNonMutatingArraySemanticCall(&*II)) |
| continue; |
| |
| if (IsUniquelyIdentifiedArray) { |
| // It is okay for an identified loop to have releases in between a retain |
| // and a release. We can end up here if we have two retains in a row and |
| // then a release. The second retain cannot be matched with the release |
| // but must be matched by a follow up instruction. |
| // retain %ptr |
| // retain %ptr |
| // release %ptr |
| // array_operation(..., @owned %ptr) |
| // |
| // This is not the case for a potentially aliased array because a release |
| // can cause a destructor to run. The destructor in turn can cause |
| // arbitrary side effects. |
| if (isa<ReleaseValueInst>(II) || isa<StrongReleaseInst>(II)) |
| continue; |
| |
| if (ArrayUserSet.count(&*II)) // May be an array mutation. |
| break; |
| } else { |
| // Not safe. |
| break; |
| } |
| } |
| LLVM_DEBUG(llvm::dbgs() << " Skipping Array: retained in loop!\n" |
| << " " << *RetainInst); |
| return false; |
| } |
| |
| /// \return true if all given users of an array address are safe to hoist |
| /// make_mutable across. |
| /// |
| /// General calls are unsafe because they may copy the array struct which in |
| /// turn bumps the reference count of the array storage. |
| /// |
| /// The same logic currently applies to both uses of the array struct itself and |
| /// uses of an aggregate containing the array. |
| /// |
| /// This does not apply to addresses of elements within the array. e.g. it is |
| /// not safe to store to an element in the array because we may be storing an |
| /// alias to the array storage. |
| bool COWArrayOpt::checkSafeArrayAddressUses(UserList &AddressUsers) { |
| |
| for (auto *UseInst : AddressUsers) { |
| |
| if (auto *AI = dyn_cast<ApplyInst>(UseInst)) { |
| if (ArraySemanticsCall(AI)) |
| continue; |
| |
| // Check of this escape can reach the current loop. |
| if (!Loop->contains(UseInst->getParent()) && |
| !getReachingBlocks().count(UseInst->getParent())) { |
| continue; |
| } |
| |
| LLVM_DEBUG(llvm::dbgs() << " Skipping Array: may escape " |
| "through call!\n" |
| << " " << *UseInst); |
| return false; |
| } |
| |
| if (auto *StInst = dyn_cast<StoreInst>(UseInst)) { |
| // Allow a local array to be initialized outside the loop via a by-value |
| // argument or return value. The array value may be returned by its |
| // initializer or some other factory function. |
| if (Loop->contains(StInst->getParent())) { |
| LLVM_DEBUG(llvm::dbgs() << " Skipping Array: store inside loop!\n" |
| << " " << *StInst); |
| return false; |
| } |
| |
| SILValue InitArray = StInst->getSrc(); |
| if (isa<SILArgument>(InitArray) || isa<ApplyInst>(InitArray)) |
| continue; |
| |
| LLVM_DEBUG(llvm::dbgs() << " Skipping Array: may escape " |
| "through store!\n" |
| << " " << *UseInst); |
| return false; |
| } |
| |
| if (isa<DeallocStackInst>(UseInst)) { |
| // Handle destruction of a local array. |
| continue; |
| } |
| |
| if (isa<MarkDependenceInst>(UseInst)) { |
| continue; |
| } |
| |
| LLVM_DEBUG(llvm::dbgs() << " Skipping Array: unknown Array use!\n" |
| << " " << *UseInst); |
| // Found an unsafe or unknown user. The Array may escape here. |
| return false; |
| } |
| return true; |
| } |
| |
| template <typename UserRange> |
| ArraySemanticsCall getEndMutationCall(const UserRange &AddressUsers) { |
| for (auto *UseInst : AddressUsers) { |
| if (auto *AI = dyn_cast<ApplyInst>(UseInst)) { |
| ArraySemanticsCall ASC(AI); |
| if (ASC.getKind() == ArrayCallKind::kEndMutation) |
| return ASC; |
| } |
| } |
| return ArraySemanticsCall(); |
| } |
| |
| /// Returns true if this instruction is a safe array use if all of its users are |
| /// also safe array users. |
| static SILValue isTransitiveSafeUser(SILInstruction *I) { |
| switch (I->getKind()) { |
| case SILInstructionKind::StructExtractInst: |
| case SILInstructionKind::TupleExtractInst: |
| case SILInstructionKind::UncheckedEnumDataInst: |
| case SILInstructionKind::StructInst: |
| case SILInstructionKind::TupleInst: |
| case SILInstructionKind::EnumInst: |
| case SILInstructionKind::UncheckedRefCastInst: |
| case SILInstructionKind::UncheckedBitwiseCastInst: |
| return cast<SingleValueInstruction>(I); |
| default: |
| return nullptr; |
| } |
| } |
| |
| /// Check that the use of an Array value, the value of an aggregate containing |
| /// an array, or the value of an element within the array, is safe w.r.t |
| /// make_mutable hoisting. Retains are safe as long as they are not inside the |
| /// Loop. |
| bool COWArrayOpt::checkSafeArrayValueUses(UserList &ArrayValueUsers) { |
| for (auto *UseInst : ArrayValueUsers) { |
| if (auto *AI = dyn_cast<ApplyInst>(UseInst)) { |
| if (ArraySemanticsCall(AI)) |
| continue; |
| |
| // Found an unsafe or unknown user. The Array may escape here. |
| LLVM_DEBUG(llvm::dbgs() << " Skipping Array: unsafe call!\n" |
| << " " << *UseInst); |
| return false; |
| } |
| |
| /// Is this a unary transitive safe user instruction. This means that the |
| /// instruction is safe only if all of its users are safe. Check this |
| /// recursively. |
| if (auto inst = isTransitiveSafeUser(UseInst)) { |
| if (std::all_of(inst->use_begin(), inst->use_end(), |
| [this](Operand *Op) -> bool { |
| return checkSafeArrayElementUse(Op->getUser(), |
| Op->get()); |
| })) |
| continue; |
| return false; |
| } |
| |
| if (isa<RetainValueInst>(UseInst)) { |
| if (isRetainReleasedBeforeMutate(UseInst)) |
| continue; |
| // Found an unsafe or unknown user. The Array may escape here. |
| LLVM_DEBUG(llvm::dbgs() << " Skipping Array: found unmatched retain " |
| "value!\n" |
| << " " << *UseInst); |
| return false; |
| } |
| |
| if (isa<ReleaseValueInst>(UseInst)) { |
| // Releases are always safe. This case handles the release of an array |
| // buffer that is loaded from a local array struct. |
| continue; |
| } |
| |
| if (isa<MarkDependenceInst>(UseInst)) |
| continue; |
| |
| if (UseInst->isDebugInstruction()) |
| continue; |
| |
| // Found an unsafe or unknown user. The Array may escape here. |
| LLVM_DEBUG(llvm::dbgs() << " Skipping Array: unsafe Array value use!\n" |
| << " " << *UseInst); |
| return false; |
| } |
| return true; |
| } |
| |
| /// Given an array value, recursively check that uses of elements within the |
| /// array are safe. |
| /// |
| /// Consider any potentially mutating operation unsafe. Mutation would not |
| /// prevent make_mutable hoisting, but it would interfere with |
| /// isRetainReleasedBeforeMutate. Since struct_extract users are not visited by |
| /// StructUseCollector, they are never added to ArrayUserSet. Thus we check here |
| /// that no mutating struct_extract users exist. |
| /// |
| /// After the lower aggregates pass, SIL contains chains of struct_extract and |
| /// retain_value instructions. e.g. |
| /// %a = load %0 : $*Array<Int> |
| /// %b = struct_extract %a : $Array<Int>, #Array._buffer |
| /// %s = struct_extract %b : $_ArrayBuffer<Int>, #_ArrayBuffer.storage |
| /// retain_value %s : $Optional<Builtin.NativeObject> |
| /// |
| /// SILCombine typically simplifies this by bypassing the |
| /// struct_extract. However, for completeness this analysis has the ability to |
| /// follow struct_extract users. |
| /// |
| /// Since this does not recurse through multi-operand instructions, no visited |
| /// set is necessary. |
| bool COWArrayOpt::checkSafeArrayElementUse(SILInstruction *UseInst, |
| SILValue ArrayVal) { |
| if ((isa<RetainValueInst>(UseInst) || isa<StrongRetainInst>(UseInst)) && |
| isRetainReleasedBeforeMutate(UseInst)) |
| return true; |
| |
| if (isa<ReleaseValueInst>(UseInst) || isa<StrongReleaseInst>(UseInst)) |
| // Releases are always safe. This case handles the release of an array |
| // buffer that is loaded from a local array struct. |
| return true; |
| |
| if (isa<RefTailAddrInst>(UseInst)) { |
| return true; |
| } |
| |
| // Look for a safe mark_dependence instruction use. |
| // |
| // This use looks something like: |
| // |
| // %57 = load %56 : $*Builtin.BridgeObject from Array<Int> |
| // %58 = unchecked_ref_cast %57 : $Builtin.BridgeObject to |
| // $_ContiguousArray |
| // %59 = unchecked_ref_cast %58 : $__ContiguousArrayStorageBase to |
| // $Builtin.NativeObject |
| // %60 = struct_extract %53 : $UnsafeMutablePointer<Int>, |
| // #UnsafeMutablePointer |
| // %61 = pointer_to_address %60 : $Builtin.RawPointer to strict $*Int |
| // %62 = mark_dependence %61 : $*Int on %59 : $Builtin.NativeObject |
| // |
| // The struct_extract, unchecked_ref_cast is handled below in the |
| // "Transitive SafeArrayElementUse" code. |
| if (isa<MarkDependenceInst>(UseInst)) |
| return true; |
| |
| if (UseInst->isDebugInstruction()) |
| return true; |
| |
| // If this is an instruction which is a safe array element use if and only if |
| // all of its users are safe array element uses, recursively check its uses |
| // and return false if any of them are not transitive escape array element |
| // uses. |
| if (auto result = isTransitiveSafeUser(UseInst)) { |
| return std::all_of(result->use_begin(), result->use_end(), |
| [this, &ArrayVal](Operand *UI) -> bool { |
| return checkSafeArrayElementUse(UI->getUser(), |
| ArrayVal); |
| }); |
| } |
| |
| // Found an unsafe or unknown user. The Array may escape here. |
| LLVM_DEBUG(llvm::dbgs() << " Skipping Array: unknown Element use!\n" |
| << *UseInst); |
| return false; |
| } |
| |
| /// Check that the use of an Array element is safe w.r.t. make_mutable hoisting. |
| /// |
| /// This logic should be similar to checkSafeArrayElementUse |
| bool COWArrayOpt::checkSafeElementValueUses(UserOperList &ElementValueUsers) { |
| for (auto &Pair : ElementValueUsers) { |
| SILInstruction *UseInst = Pair.first; |
| Operand *ArrayValOper = Pair.second; |
| if (!checkSafeArrayElementUse(UseInst, ArrayValOper->get())) |
| return false; |
| } |
| return true; |
| } |
| |
| /// Check if a loop has only 'safe' array operations such that we can hoist the |
| /// uniqueness check even without having an 'identified' object. |
| /// |
| /// 'Safe' array operations are: |
| /// * all array semantic functions |
| /// * stores to array elements |
| /// * any instruction that does not have side effects. |
| /// * any retain must be matched by a release before we hit astd::make_unique. |
| /// |
| /// Note, that a release in this modus (we don't have a uniquely identified |
| /// object) is not safe because the destructor of what we are releasing might |
| /// be unsafe (creating a reference). |
| /// |
| bool COWArrayOpt::hasLoopOnlyDestructorSafeArrayOperations() { |
| if (CachedSafeLoop.first) |
| return CachedSafeLoop.second; |
| |
| assert(!CachedSafeLoop.second && |
| "We only move to a true state below"); |
| |
| // We will compute the state of this loop now. |
| CachedSafeLoop.first = true; |
| |
| // We need to cleanup the MatchedRelease on return. |
| auto ReturnWithCleanup = [&] (bool LoopHasSafeOperations) { |
| MatchedReleases.clear(); |
| return LoopHasSafeOperations; |
| }; |
| |
| LLVM_DEBUG(llvm::dbgs() << " checking whether loop only has safe array " |
| "operations ...\n"); |
| CanType SameTy; |
| for (auto *BB : Loop->getBlocks()) { |
| for (auto &It : *BB) { |
| auto *Inst = &It; |
| LLVM_DEBUG(llvm::dbgs() << " visiting: " << *Inst); |
| |
| // Semantic calls are safe. |
| ArraySemanticsCall Sem(Inst); |
| if (Sem && Sem.hasSelf()) { |
| auto Kind = Sem.getKind(); |
| // Safe because they create new arrays. |
| if (Kind == ArrayCallKind::kArrayInit || |
| Kind == ArrayCallKind::kArrayInitEmpty || |
| Kind == ArrayCallKind::kArrayUninitialized || |
| Kind == ArrayCallKind::kArrayUninitializedIntrinsic) |
| continue; |
| // All array types must be the same. This is a stronger guaranteed than |
| // we actually need. The requirement is that we can't create another |
| // reference to the array by performing an array operation: for example, |
| // storing or appending one array into a two-dimensional array. |
| // Checking |
| // that all types are the same make guarantees that this cannot happen. |
| if (SameTy.isNull()) { |
| SameTy = Sem.getSelf()->getType().getASTType(); |
| continue; |
| } |
| |
| if (Sem.getSelf()->getType().getASTType() != SameTy) { |
| LLVM_DEBUG(llvm::dbgs() << " (NO) mismatching array types\n"); |
| return ReturnWithCleanup(false); |
| } |
| |
| // Safe array semantics operation. |
| continue; |
| } |
| |
| // Stores to array elements. |
| if (auto *SI = dyn_cast<StoreInst>(Inst)) { |
| if (isAddressOfArrayElement(SI->getDest())) |
| continue; |
| LLVM_DEBUG(llvm::dbgs() << " (NO) unknown store " << *SI); |
| return ReturnWithCleanup(false); |
| } |
| |
| // Instructions without side effects are safe. |
| if (!Inst->mayHaveSideEffects()) |
| continue; |
| if (isa<CondFailInst>(Inst)) |
| continue; |
| if (isa<AllocationInst>(Inst) || isa<DeallocStackInst>(Inst)) |
| continue; |
| |
| if (isa<RetainValueInst>(Inst) || isa<StrongRetainInst>(Inst)) |
| if (isRetainReleasedBeforeMutate(Inst, false)) |
| continue; |
| |
| // If the instruction is a matched release we can ignore it. |
| if (auto SRI = dyn_cast<StrongReleaseInst>(Inst)) |
| if (MatchedReleases.count(&SRI->getOperandRef())) |
| continue; |
| if (auto RVI = dyn_cast<ReleaseValueInst>(Inst)) |
| if (MatchedReleases.count(&RVI->getOperandRef())) |
| continue; |
| |
| // Ignore fix_lifetime. It cannot increment ref counts. |
| if (isa<FixLifetimeInst>(Inst)) |
| continue; |
| |
| LLVM_DEBUG(llvm::dbgs() << " (NO) unknown operation " << *Inst); |
| return ReturnWithCleanup(false); |
| } |
| } |
| |
| LLVM_DEBUG(llvm::dbgs() << " (YES)\n"); |
| CachedSafeLoop.second = true; |
| return ReturnWithCleanup(true); |
| } |
| |
| /// Return the underlying Array address after stripping off all address |
| /// projections. Returns an invalid SILValue if the array base does not dominate |
| /// the loop. |
| SILValue COWArrayOpt::getArrayAddressBase(SILValue V) { |
| while (true) { |
| V = stripSinglePredecessorArgs(V); |
| if (auto *RefCast = dyn_cast<UncheckedRefCastInst>(V)) { |
| V = RefCast->getOperand(); |
| continue; |
| } |
| if (auto *SE = dyn_cast<StructExtractInst>(V)) { |
| V = SE->getOperand(); |
| continue; |
| } |
| if (auto *IA = dyn_cast<IndexAddrInst>(V)) { |
| // index_addr is the only projection which has a second operand: the index. |
| // Check if the index is loop invariant. |
| SILBasicBlock *IndexBlock = IA->getIndex()->getParentBlock(); |
| if (IndexBlock && !DomTree->dominates(IndexBlock, Preheader)) |
| return SILValue(); |
| V = IA->getBase(); |
| continue; |
| } |
| if (!Projection::isAddressProjection(V)) |
| break; |
| auto *Inst = cast<SingleValueInstruction>(V); |
| if (Inst->getNumOperands() > 1) |
| break; |
| V = Inst->getOperand(0); |
| } |
| if (auto *LI = dyn_cast<LoadInst>(V)) { |
| if (HoistableLoads.count(LI) != 0) |
| return V; |
| } |
| SILBasicBlock *ArrayAddrBaseBB = V->getParentBlock(); |
| if (ArrayAddrBaseBB && !DomTree->dominates(ArrayAddrBaseBB, Preheader)) |
| return SILValue(); |
| |
| return V; |
| } |
| |
| /// Hoist the address projection rooted in \p Op to \p InsertBefore. |
| /// Requires the projected value to dominate the insertion point. |
| /// |
| /// Will look through single basic block predecessor arguments. |
| void COWArrayOpt::hoistAddressProjections(Operand &ArrayOp) { |
| SILValue V = ArrayOp.get(); |
| SILInstruction *Prev = nullptr; |
| SILInstruction *InsertPt = Preheader->getTerminator(); |
| while (true) { |
| SILValue Incoming = stripSinglePredecessorArgs(V); |
| |
| // Forward the incoming arg from a single predecessor. |
| if (V != Incoming) { |
| if (V == ArrayOp.get()) { |
| // If we are the operand itself set the operand to the incoming |
| // argument. |
| ArrayOp.set(Incoming); |
| V = Incoming; |
| } else { |
| // Otherwise, set the previous projections operand to the incoming |
| // argument. |
| assert(Prev && "Must have seen a projection"); |
| Prev->setOperand(0, Incoming); |
| V = Incoming; |
| } |
| } |
| |
| switch (V->getKind()) { |
| case ValueKind::LoadInst: |
| case ValueKind::StructElementAddrInst: |
| case ValueKind::TupleElementAddrInst: |
| case ValueKind::RefElementAddrInst: |
| case ValueKind::RefTailAddrInst: |
| case ValueKind::UncheckedRefCastInst: |
| case ValueKind::StructExtractInst: |
| case ValueKind::IndexAddrInst: |
| case ValueKind::UncheckedTakeEnumDataAddrInst: { |
| auto *Inst = cast<SingleValueInstruction>(V); |
| // We are done once the current projection dominates the insert point. |
| if (DomTree->dominates(Inst->getParent(), Preheader)) |
| return; |
| |
| assert(!isa<LoadInst>(V) || HoistableLoads.count(cast<LoadInst>(V)) != 0); |
| |
| // Move the current projection and memorize it for the next iteration. |
| Prev = Inst; |
| Inst->moveBefore(InsertPt); |
| InsertPt = Inst; |
| V = Inst->getOperand(0); |
| continue; |
| } |
| default: |
| assert(DomTree->dominates(V->getParentBlock(), Preheader) && |
| "The projected value must dominate the insertion point"); |
| return; |
| } |
| } |
| } |
| |
| /// Check if this call to "make_mutable" is hoistable, and copy it, along with |
| /// the corresponding end_mutation call, to the loop pre-header. |
| /// |
| /// The origial make_mutable/end_mutation calls remain in the loop, because |
| /// removing them would violate the COW representation rules. |
| /// Having those calls in the pre-header will then enable COWOpts (after |
| /// inlining) to constant fold the uniqueness check of the begin_cow_mutation |
| /// in the loop. |
| bool COWArrayOpt::hoistMakeMutable(ArraySemanticsCall MakeMutable, |
| bool dominatesExits) { |
| LLVM_DEBUG(llvm::dbgs() << " Checking mutable array: " <<CurrentArrayAddr); |
| |
| // We can hoist address projections (even if they are only conditionally |
| // executed). |
| SILValue ArrayAddrBase = getArrayAddressBase(CurrentArrayAddr); |
| if (!ArrayAddrBase) { |
| LLVM_DEBUG(llvm::dbgs() << " Skipping Array: does not dominate loop!\n"); |
| return false; |
| } |
| |
| SmallVector<int, 4> AccessPath; |
| SILValue ArrayContainer = |
| StructUseCollector::getAccessPath(CurrentArrayAddr, AccessPath); |
| bool arrayContainerIsUnique = checkUniqueArrayContainer(ArrayContainer); |
| |
| StructUseCollector StructUses; |
| |
| // Check whether we can hoist make_mutable based on the operations that are |
| // in the loop. |
| // |
| // Hoisting make_mutable releases the original array storage. If an alias of |
| // that storage is accessed on any path reachable from the loop header that |
| // doesn't already pass through the make_mutable, then hoisting is |
| // illegal. hasLoopOnlyDestructorSafeArrayOperations checks that the array |
| // storage is not accessed within the loop. However, this does not include |
| // paths exiting the loop. Rather than analyzing code outside the loop, simply |
| // check that the original make_mutable dominates all exits. The test |
| // SILOptimizer/cowarray_opt.sil: dont_hoist_if_executed_conditionally shows |
| // the problem. |
| if (hasLoopOnlyDestructorSafeArrayOperations() && dominatesExits) { |
| // Done. We can hoist the make_mutable. |
| // We still need the array uses later to check if we can add loads to |
| // HoistableLoads. |
| StructUses.collectUses(ArrayContainer, AccessPath); |
| } else { |
| // There are some unsafe operations in the loop. If the array is uniquely |
| // identifyable and not escaping, then we are good if all the array uses |
| // are safe. |
| if (!arrayContainerIsUnique) { |
| LLVM_DEBUG(llvm::dbgs() << " Skipping Array: is not unique!\n"); |
| return false; |
| } |
| |
| // Check that the Array is not retained with this loop and it's address does |
| // not escape within this function. |
| StructUses.collectUses(ArrayContainer, AccessPath); |
| for (auto *Oper : StructUses.Visited) |
| ArrayUserSet.insert(Oper->getUser()); |
| |
| if (!checkSafeArrayAddressUses(StructUses.AggregateAddressUsers) || |
| !checkSafeArrayAddressUses(StructUses.StructAddressUsers) || |
| !checkSafeArrayValueUses(StructUses.StructValueUsers) || |
| !checkSafeElementValueUses(StructUses.ElementValueUsers) || |
| !StructUses.ElementAddressUsers.empty()) |
| return false; |
| } |
| |
| auto ArrayUsers = llvm::map_range(MakeMutable.getSelf()->getUses(), |
| ValueBase::UseToUser()); |
| |
| // There should be a call to end_mutation. Find it so that we can copy it to |
| // the pre-header. |
| ArraySemanticsCall EndMutation = getEndMutationCall(ArrayUsers); |
| if (!EndMutation) { |
| EndMutation = getEndMutationCall(StructUses.StructAddressUsers); |
| if (!EndMutation) |
| return false; |
| } |
| |
| // Hoist the make_mutable. |
| LLVM_DEBUG(llvm::dbgs() << " Hoisting make_mutable: " << *MakeMutable); |
| |
| hoistAddressProjections(MakeMutable.getSelfOperand()); |
| |
| assert(MakeMutable.canHoist(Preheader->getTerminator(), DomTree) && |
| "Should be able to hoist make_mutable"); |
| |
| // Copy the make_mutable and end_mutation calls to the pre-header. |
| TermInst *insertionPoint = Preheader->getTerminator(); |
| ApplyInst *hoistedMM = MakeMutable.copyTo(insertionPoint, DomTree); |
| ApplyInst *EMInst = EndMutation; |
| ApplyInst *hoistedEM = cast<ApplyInst>(EMInst->clone(insertionPoint)); |
| hoistedEM->setArgument(0, hoistedMM->getArgument(0)); |
| placeFuncRef(hoistedEM, DomTree); |
| |
| // Register array loads. This is needed for hoisting make_mutable calls of |
| // inner arrays in the two-dimensional case. |
| if (arrayContainerIsUnique && |
| StructUses.hasOnlyAddressUses((ApplyInst *)MakeMutable, EMInst)) { |
| for (auto use : MakeMutable.getSelf()->getUses()) { |
| if (auto *LI = dyn_cast<LoadInst>(use->getUser())) |
| HoistableLoads.insert(LI); |
| } |
| } |
| return true; |
| } |
| |
| bool COWArrayOpt::dominatesExitingBlocks(SILBasicBlock *BB) { |
| llvm::SmallVector<SILBasicBlock *, 8> ExitingBlocks; |
| Loop->getExitingBlocks(ExitingBlocks); |
| for (SILBasicBlock *Exiting : ExitingBlocks) { |
| if (!DomTree->dominates(BB, Exiting)) |
| return false; |
| } |
| return true; |
| } |
| |
| bool COWArrayOpt::run() { |
| LLVM_DEBUG(llvm::dbgs() << " Array Opts in Loop " << *Loop); |
| |
| Preheader = Loop->getLoopPreheader(); |
| if (!Preheader) { |
| LLVM_DEBUG(llvm::dbgs() << " Skipping Loop: No Preheader!\n"); |
| return false; |
| } |
| |
| // Map an array to a hoisted make_mutable call for the current loop. An array |
| // is only mapped to a call once the analysis has determined that no |
| // make_mutable calls are required within the loop body for that array. |
| llvm::SmallDenseMap<SILValue, ApplyInst*> ArrayMakeMutableMap; |
| |
| llvm::SmallVector<ArraySemanticsCall, 8> makeMutableCalls; |
| |
| for (auto *BB : Loop->getBlocks()) { |
| if (ColdBlocks.isCold(BB)) |
| continue; |
| |
| // Instructions are getting moved around. To not mess with iterator |
| // invalidation, first collect all calls, and then do the transformation. |
| for (SILInstruction &I : *BB) { |
| ArraySemanticsCall MakeMutableCall(&I, "array.make_mutable"); |
| if (MakeMutableCall) |
| makeMutableCalls.push_back(MakeMutableCall); |
| } |
| |
| bool dominatesExits = dominatesExitingBlocks(BB); |
| for (ArraySemanticsCall MakeMutableCall : makeMutableCalls) { |
| CurrentArrayAddr = MakeMutableCall.getSelf(); |
| auto HoistedCallEntry = ArrayMakeMutableMap.find(CurrentArrayAddr); |
| if (HoistedCallEntry == ArrayMakeMutableMap.end()) { |
| if (hoistMakeMutable(MakeMutableCall, dominatesExits)) { |
| ArrayMakeMutableMap[CurrentArrayAddr] = MakeMutableCall; |
| HasChanged = true; |
| } else { |
| ArrayMakeMutableMap[CurrentArrayAddr] = nullptr; |
| } |
| } |
| } |
| } |
| return HasChanged; |
| } |
| |
| namespace { |
| |
| class COWArrayOptPass : public SILFunctionTransform { |
| void run() override { |
| // FIXME: Update for ownership. |
| if (getFunction()->hasOwnership()) |
| return; |
| |
| LLVM_DEBUG(llvm::dbgs() << "COW Array Opts in Func " |
| << getFunction()->getName() << "\n"); |
| |
| auto *DA = PM->getAnalysis<DominanceAnalysis>(); |
| auto *LA = PM->getAnalysis<SILLoopAnalysis>(); |
| auto *RCIA = |
| PM->getAnalysis<RCIdentityAnalysis>()->get(getFunction()); |
| SILLoopInfo *LI = LA->get(getFunction()); |
| if (LI->empty()) { |
| LLVM_DEBUG(llvm::dbgs() << " Skipping Function: No loops.\n"); |
| return; |
| } |
| |
| // Create a flat list of loops in loop-tree postorder (bottom-up). |
| llvm::SmallVector<SILLoop *, 16> Loops; |
| std::function<void (SILLoop*)> pushChildren = [&](SILLoop *L) { |
| for (auto *SubLoop : *L) |
| pushChildren(SubLoop); |
| Loops.push_back(L); |
| }; |
| for (auto *L : *LI) |
| pushChildren(L); |
| |
| bool HasChanged = false; |
| for (auto *L : Loops) |
| HasChanged |= COWArrayOpt(RCIA, L, DA).run(); |
| |
| if (HasChanged) |
| invalidateAnalysis(SILAnalysis::InvalidationKind::CallsAndInstructions); |
| } |
| |
| }; |
| } // end anonymous namespace |
| |
| SILTransform *swift::createCOWArrayOpts() { |
| return new COWArrayOptPass(); |
| } |