//===--- GlobalARCSequenceDataflow.cpp - ARC Sequence Dataflow Analysis ---===// // // This source file is part of the Swift.org open source project // // Copyright (c) 2014 - 2015 Apple Inc. and the Swift project authors // Licensed under Apache License v2.0 with Runtime Library Exception // // See http://swift.org/LICENSE.txt for license information // See http://swift.org/CONTRIBUTORS.txt for the list of Swift project authors // //===----------------------------------------------------------------------===// #define DEBUG_TYPE "sil-global-arc-opts" #include "GlobalARCSequenceDataflow.h" #include "ARCBBState.h" #include "swift/SILAnalysis/ARCAnalysis.h" #include "swift/SILAnalysis/PostOrderAnalysis.h" #include "swift/SILAnalysis/RCIdentityAnalysis.h" #include "swift/SIL/SILInstruction.h" #include "swift/SIL/SILFunction.h" #include "swift/SIL/SILSuccessor.h" #include "swift/SIL/CFG.h" #include "swift/SIL/SILModule.h" #include "llvm/ADT/PostOrderIterator.h" #include "llvm/ADT/Statistic.h" #include "llvm/ADT/StringSwitch.h" #include "llvm/Support/Debug.h" using namespace swift; //===----------------------------------------------------------------------===// // Utilities //===----------------------------------------------------------------------===// static bool isAutoreleasePoolCall(SILInstruction &I) { ApplyInst *AI = dyn_cast(&I); if (!AI) return false; FunctionRefInst *FRI = dyn_cast(AI->getCallee()); if (!FRI) return false; return llvm::StringSwitch(FRI->getReferencedFunction()->getName()) .Case("objc_autoreleasePoolPush", true) .Case("objc_autoreleasePoolPop", true) .Default(false); } namespace { using ARCBBState = ARCSequenceDataflowEvaluator::ARCBBState; using ARCBBStateInfo = ARCSequenceDataflowEvaluator::ARCBBStateInfo; using ARCBBStateInfoHandle = ARCSequenceDataflowEvaluator::ARCBBStateInfoHandle; } // end anonymous namespace //===----------------------------------------------------------------------===// // Top Down Dataflow //===----------------------------------------------------------------------===// /// Analyze a single BB for refcount inc/dec instructions. /// /// If anything was found it will be added to DecToIncStateMap. /// /// NestingDetected will be set to indicate that the block needs to be /// reanalyzed if code motion occurs. static bool processBBTopDown( ARCBBState &BBState, BlotMapVector &DecToIncStateMap, AliasAnalysis *AA, RCIdentityAnalysis *RCIA) { DEBUG(llvm::dbgs() << ">>>> Top Down!\n"); SILBasicBlock &BB = BBState.getBB(); bool NestingDetected = false; // If the current BB is the entry BB, initialize a state corresponding to each // of its owned parameters. This enables us to know that if we see a retain // before any decrements that the retain is known safe. // // We do not handle guaranteed parameters here since those are handled in the // code in GlobalARCPairingAnalysis. This is because even if we don't do // anything, we will still pair the retain, releases and then the guaranteed // parameter will ensure it is known safe to remove them. if (&BB == &*BB.getParent()->begin()) { auto Args = BB.getBBArgs(); auto SignatureParams = BB.getParent()->getLoweredFunctionType()->getParameters(); for (unsigned i = 0, e = Args.size(); i != e; ++i) { SILArgument *A = Args[i]; ParameterConvention P = SignatureParams[i].getConvention(); DEBUG(llvm::dbgs() << "VISITING ARGUMENT: " << *A); if (P != ParameterConvention::Direct_Owned) continue; TopDownRefCountState &State = BBState.getTopDownRefCountState(Args[i]); State.initWithArg(A); } } // For each instruction I in BB... for (auto &I : BB) { DEBUG(llvm::dbgs() << "VISITING:\n " << I); // If we see an autorelease pool call, be conservative and clear all state // that we are currently tracking in the BB. if (isAutoreleasePoolCall(I)) { BBState.clear(); continue; } SILValue Op; // If I is a ref count increment instruction... if (isRefCountIncrement(I)) { // map its operand to a newly initialized or reinitialized ref count // state and continue... Op = RCIA->getRCIdentityRoot(I.getOperand(0)); TopDownRefCountState &State = BBState.getTopDownRefCountState(Op); NestingDetected |= State.initWithInst(&I); DEBUG(llvm::dbgs() << " REF COUNT INCREMENT! Known Safe: " << (State.isKnownSafe() ? "yes" : "no") << "\n"); // Continue processing in case this increment could be a CanUse for a // different pointer. } // If we have a reference count decrement... if (isRefCountDecrement(I)) { // Look up the state associated with its operand... Op = RCIA->getRCIdentityRoot(I.getOperand(0)); TopDownRefCountState &RefCountState = BBState.getTopDownRefCountState(Op); DEBUG(llvm::dbgs() << " REF COUNT DECREMENT!\n"); // If we are tracking an increment on the ref count root associated with // the decrement and the decrement matches, pair this decrement with a // copy of the increment state and then clear the original increment state // so that we are ready to process further values. if (RefCountState.isRefCountInstMatchedToTrackedInstruction(&I)) { // Copy the current value of ref count state into the result map. DecToIncStateMap[&I] = RefCountState; DEBUG(llvm::dbgs() << " MATCHING INCREMENT:\n" << RefCountState.getValue()); // Clear the ref count state in preparation for more pairs. RefCountState.clear(); } #if NDEBUG else { if (RefCountState.isTrackingRefCountInst()) { DEBUG(llvm::dbgs() << " FAILED MATCH INCREMENT:\n" << RefCountState.getValue()); } else { DEBUG(llvm::dbgs() << " FAILED MATCH. NO INCREMENT.\n"); } } #endif // Otherwise we continue processing the reference count decrement to // see if the decrement can affect any other pointers that we are // tracking. } // For all other [(SILValue, TopDownState)] we are tracking... for (auto &OtherState : BBState.getTopDownStates()) { // If we visited an increment or decrement successfully (and thus Op is // set), if this is the state for this operand, skip it. We already // processed it. if (Op && OtherState.first == Op) continue; // If the other state's value is blotted, skip it. if (!OtherState.first) continue; // If the other state is not tracking anything, bail. if (!OtherState.second.isTrackingRefCount()) continue; // Check if the instruction we are visiting could potentially use our // instruction in a way that requires us to guarantee the lifetime of the // pointer up to this point. This has the effect of performing a use and a // decrement. if (OtherState.second.handlePotentialGuaranteedUser(&I, AA)) { DEBUG(llvm::dbgs() << " Found Potential Guaranteed Use:\n " << OtherState.second.getValue()); continue; } // Check if the instruction we are visiting could potentially decrement // the reference counted value we are tracking in a manner that could // cause us to change states. If we do change states continue... if (OtherState.second.handlePotentialDecrement(&I, AA)) { DEBUG(llvm::dbgs() << " Found Potential Decrement:\n " << OtherState.second.getValue()); continue; } // Otherwise check if the reference counted value we are tracking // could be used by the given instruction. if (OtherState.second.handlePotentialUser(&I, AA)) DEBUG(llvm::dbgs() << " Found Potential Use:\n " << OtherState.second.getValue()); } } return NestingDetected; } void ARCSequenceDataflowEvaluator::mergePredecessors( ARCBBStateInfoHandle &DataHandle) { bool HasAtLeastOnePred = false; llvm::SmallVector BBThatNeedInsertPts; SILBasicBlock *BB = DataHandle.getBB(); ARCBBState &BBState = DataHandle.getState(); // For each successor of BB... for (SILBasicBlock *PredBB : BB->getPreds()) { // Try to look up the data handle for it. If we don't have any such state, // then the predecessor must be unreachable from the entrance and thus is // uninteresting to us. auto PredDataHandle = getTopDownBBState(PredBB); if (!PredDataHandle) continue; DEBUG(llvm::dbgs() << " Merging Pred: " << PredDataHandle->getID() << "\n"); // If the predecessor is the head of a backedge in our traversal, clear any // state we are tracking now and clear the state of the basic block. There // is some sort of control flow here that we do not understand. if (PredDataHandle->isBackedge(BB)) { BBState.clear(); break; } ARCBBState &PredBBState = PredDataHandle->getState(); // If we found the state but the state is for a trap BB, skip it. Trap BBs // leak all reference counts and do not reference reference semantic objects // in any manner. // // TODO: I think this is a copy paste error, since we a trap BB should have // an unreachable at its end. See if this can be removed. if (PredBBState.isTrapBB()) continue; if (HasAtLeastOnePred) { BBState.mergePredTopDown(PredBBState); continue; } BBState.initPredTopDown(PredBBState); HasAtLeastOnePred = true; } } bool ARCSequenceDataflowEvaluator::processTopDown() { bool NestingDetected = false; DEBUG(llvm::dbgs() << "<<<< Processing Top Down! >>>>\n"); // For each BB in our reverse post order... for (auto *BB : POA->getReversePostOrder(&F)) { // We should always have a value here. auto BBDataHandle = getTopDownBBState(BB).getValue(); // This will always succeed since we have an entry for each BB in our RPOT. // // TODO: When data handles are introduced, print that instead. This code // should not be touching BBIDs directly. DEBUG(llvm::dbgs() << "Processing BB#: " << BBDataHandle.getID() << "\n"); DEBUG(llvm::dbgs() << "Merging Predecessors!\n"); mergePredecessors(BBDataHandle); // Then perform the basic block optimization. NestingDetected |= processBBTopDown(BBDataHandle.getState(), DecToIncStateMap, AA, RCIA); } return NestingDetected; } //===----------------------------------------------------------------------===// // Bottom Up Dataflow //===----------------------------------------------------------------------===// /// Analyze a single BB for refcount inc/dec instructions. /// /// If anything was found it will be added to DecToIncStateMap. /// /// NestingDetected will be set to indicate that the block needs to be /// reanalyzed if code motion occurs. /// /// An epilogue release is a release that post dominates all other uses of a /// pointer in a function that implies that the pointer is alive up to that /// point. We "freeze" (i.e. do not attempt to remove or move) such releases if /// FreezeOwnedArgEpilogueReleases is set. This is useful since in certain cases /// due to dataflow issues, we can not properly propagate the last use /// information. Instead we run an extra iteration of the ARC optimizer with /// this enabled in a side table so the information gets propgated everywhere in /// the CFG. bool ARCSequenceDataflowEvaluator::processBBBottomUp( ARCBBState &BBState, bool FreezeOwnedArgEpilogueReleases) { DEBUG(llvm::dbgs() << ">>>> Bottom Up!\n"); SILBasicBlock &BB = BBState.getBB(); bool NestingDetected = false; // For each non terminator instruction I in BB visited in reverse... for (auto II = std::next(BB.rbegin()), IE = BB.rend(); II != IE;) { SILInstruction &I = *II; ++II; DEBUG(llvm::dbgs() << "VISITING:\n " << I); // If we see an autorelease pool, be conservative and clear *everything*. if (isAutoreleasePoolCall(I)) { BBState.clear(); continue; } // If this instruction is a post dominating release, skip it so we don't // pair it up with anything. if (FreezeOwnedArgEpilogueReleases && ConsumedArgToReleaseMap.isReleaseMatchedToArgument(&I)) continue; SILValue Op; // If I is a ref count decrement instruction... if (isRefCountDecrement(I)) { // map its operand to a newly initialized or reinitialized ref count // state and continue... Op = RCIA->getRCIdentityRoot(I.getOperand(0)); BottomUpRefCountState &State = BBState.getBottomUpRefCountState(Op); NestingDetected |= State.initWithInst(&I); // If we are running with 'frozen' owned arg releases, check if we have a // frozen use in the side table. If so, this release must be known safe. if (FreezeOwnedArgEpilogueReleases) { State.KnownSafe |= ConsumedArgToReleaseMap.argumentHasRelease(Op); } DEBUG(llvm::dbgs() << " REF COUNT DECREMENT! Known Safe: " << (State.isKnownSafe() ? "yes" : "no") << "\n"); // Continue on to see if our reference decrement could potentially affect // any other pointers via a use or a decrement. } // If we have a reference count decrement... if (isRefCountIncrement(I)) { // Look up the state associated with its operand... Op = RCIA->getRCIdentityRoot(I.getOperand(0)); BottomUpRefCountState &RefCountState = BBState.getBottomUpRefCountState(Op); DEBUG(llvm::dbgs() << " REF COUNT INCREMENT!\n"); // If we find a state initialized with a matching increment, pair this // decrement with a copy of the ref count state and then clear the ref // count state in preparation for any future pairs we may see on the same // pointer. if (RefCountState.isRefCountInstMatchedToTrackedInstruction(&I)) { // Copy the current value of ref count state into the result map. IncToDecStateMap[&I] = RefCountState; DEBUG(llvm::dbgs() << " MATCHING DECREMENT:" << RefCountState.getValue()); // Clear the ref count state so it can be used for future pairs we may // see. RefCountState.clear(); } #ifndef NDEBUG else { if (RefCountState.isTrackingRefCountInst()) { DEBUG(llvm::dbgs() << " FAILED MATCH DECREMENT:" << RefCountState.getValue()); } else { DEBUG(llvm::dbgs() << " FAILED MATCH DECREMENT. Not tracking a " "decrement.\n"); } } #endif // Otherwise we continue processing the reference count decrement to // see if the increment can act as a use for other values. } // For all other (reference counted value, ref count state) we are // tracking... for (auto &OtherState : BBState.getBottomupStates()) { // If this is the state associated with the instruction that we are // currently visiting, bail. if (Op && OtherState.first == Op) continue; // If the other state's value is blotted, skip it. if (!OtherState.first) continue; // If this state is not tracking anything, skip it. if (!OtherState.second.isTrackingRefCount()) continue; // Check if the instruction we are visiting could potentially use our // instruction in a way that requires us to guarantee the lifetime of the // pointer up to this point. This has the effect of performing a use and a // decrement. if (OtherState.second.handlePotentialGuaranteedUser(&I, AA)) { DEBUG(llvm::dbgs() << " Found Potential Guaranteed Use:\n " << OtherState.second.getValue()); continue; } // Check if the instruction we are visiting could potentially decrement // the reference counted value we are tracking... in a manner that could // cause us to change states. If we do change states continue... if (OtherState.second.handlePotentialDecrement(&I, AA)) { DEBUG(llvm::dbgs() << " Found Potential Decrement:\n " << OtherState.second.getValue()); continue; } // Otherwise check if the reference counted value we are tracking // could be used by the given instruction. if (OtherState.second.handlePotentialUser(&I, AA)) DEBUG(llvm::dbgs() << " Found Potential Use:\n " << OtherState.second.getValue()); } } return NestingDetected; } void ARCSequenceDataflowEvaluator::mergeSuccessors( ARCBBStateInfoHandle &DataHandle) { SILBasicBlock *BB = DataHandle.getBB(); ARCBBState &BBState = DataHandle.getState(); // For each successor of BB... ArrayRef Succs = BB->getSuccessors(); bool HasAtLeastOneSucc = false; for (unsigned i = 0, e = Succs.size(); i != e; ++i) { // If it does not have a basic block associated with it... auto *SuccBB = Succs[i].getBB(); // Skip it. if (!SuccBB) continue; // If the BB is the head of a backedge in our traversal, we have // hit a loop boundary. In that case, add any instructions we are // tracking or instructions that we have seen to the banned // instruction list. Clear the instructions we are tracking // currently, but leave that we saw a release on them. In a post // order, we know that all of a BB's successors will always be // visited before the BB, implying we will know if conservatively // we saw a release on the pointer going down all paths. if (DataHandle.isBackedge(SuccBB)) { BBState.clear(); break; } // Otherwise, lookup the BBState associated with the successor and merge // the successor in. We know this will always succeed. auto SuccDataHandle = *getBottomUpBBState(SuccBB); ARCBBState &SuccBBState = SuccDataHandle.getState(); if (SuccBBState.isTrapBB()) continue; if (HasAtLeastOneSucc) { BBState.mergeSuccBottomUp(SuccBBState); continue; } BBState.initSuccBottomUp(SuccBBState); HasAtLeastOneSucc = true; } } bool ARCSequenceDataflowEvaluator::processBottomUp( bool FreezeOwnedArgEpilogueReleases) { bool NestingDetected = false; DEBUG(llvm::dbgs() << "<<<< Processing Bottom Up! >>>>\n"); // For each BB in our post order... for (auto *BB : POA->getPostOrder(&F)) { // Grab the BBState associated with it and set it to be the current BB. auto BBDataHandle = *getBottomUpBBState(BB); // This will always succeed since we have an entry for each BB in our post // order. DEBUG(llvm::dbgs() << "Processing BB#: " << BBDataHandle.getID() << "\n"); DEBUG(llvm::dbgs() << "Merging Successors!\n"); mergeSuccessors(BBDataHandle); // Then perform the basic block optimization. NestingDetected |= processBBBottomUp(BBDataHandle.getState(), FreezeOwnedArgEpilogueReleases); } return NestingDetected; } //===----------------------------------------------------------------------===// // Top Level ARC Sequence Dataflow Evaluator //===----------------------------------------------------------------------===// ARCSequenceDataflowEvaluator::ARCSequenceDataflowEvaluator( SILFunction &F, AliasAnalysis *AA, PostOrderAnalysis *POA, RCIdentityAnalysis *RCIA, BlotMapVector &DecToIncStateMap, BlotMapVector &IncToDecStateMap) : F(F), AA(AA), POA(POA), RCIA(RCIA), DecToIncStateMap(DecToIncStateMap), IncToDecStateMap(IncToDecStateMap), // We use a malloced pointer here so we don't need to expose // ARCBBStateInfo in the header. BBStateInfo(new ARCBBStateInfo(&F, POA)), ConsumedArgToReleaseMap(RCIA, &F) {} bool ARCSequenceDataflowEvaluator::run(bool FreezeOwnedReleases) { bool NestingDetected = processBottomUp(FreezeOwnedReleases); NestingDetected |= processTopDown(); return NestingDetected; } ARCSequenceDataflowEvaluator::~ARCSequenceDataflowEvaluator() { // We use a malloced pointer here so we don't need to expose the type to the // outside world. delete BBStateInfo; } void ARCSequenceDataflowEvaluator::clear() { BBStateInfo->clear(); } llvm::Optional ARCSequenceDataflowEvaluator::getBottomUpBBState(SILBasicBlock *BB) { return BBStateInfo->getBottomUpBBHandle(BB); } llvm::Optional ARCSequenceDataflowEvaluator::getTopDownBBState(SILBasicBlock *BB) { return BBStateInfo->getTopDownBBHandle(BB); }