| //===-------------- EscapeAnalysis.cpp - SIL Escape 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-escape" |
| #include "swift/SILAnalysis/EscapeAnalysis.h" |
| #include "swift/SILAnalysis/CallGraphAnalysis.h" |
| #include "swift/SILAnalysis/ArraySemantic.h" |
| #include "swift/SILPasses/PassManager.h" |
| #include "swift/SIL/SILArgument.h" |
| #include "llvm/Support/GraphWriter.h" |
| #include "llvm/Support/raw_ostream.h" |
| |
| using namespace swift; |
| |
| static bool isProjection(ValueBase *V) { |
| switch (V->getKind()) { |
| case ValueKind::IndexAddrInst: |
| case ValueKind::IndexRawPointerInst: |
| case ValueKind::StructElementAddrInst: |
| case ValueKind::TupleElementAddrInst: |
| case ValueKind::UncheckedTakeEnumDataAddrInst: |
| case ValueKind::StructExtractInst: |
| case ValueKind::TupleExtractInst: |
| case ValueKind::UncheckedEnumDataInst: |
| case ValueKind::MarkDependenceInst: |
| case ValueKind::PointerToAddressInst: |
| return true; |
| default: |
| return false; |
| } |
| } |
| |
| static bool isNonWritableMemoryAddress(ValueBase *V) { |
| switch (V->getKind()) { |
| case ValueKind::FunctionRefInst: |
| case ValueKind::WitnessMethodInst: |
| case ValueKind::ClassMethodInst: |
| case ValueKind::SuperMethodInst: |
| case ValueKind::StringLiteralInst: |
| // These instructions return pointers to memory which can't be a |
| // destination of a store. |
| return true; |
| default: |
| return false; |
| } |
| } |
| |
| static ValueBase *skipProjections(ValueBase *V) { |
| for (;;) { |
| if (!isProjection(V)) |
| return V; |
| V = cast<SILInstruction>(V)->getOperand(0).getDef(); |
| } |
| llvm_unreachable("there is no escape from an infinite loop"); |
| } |
| |
| void EscapeAnalysis::ConnectionGraph::invalidate() { |
| Values2Nodes.clear(); |
| Nodes.clear(); |
| ReturnNode = nullptr; |
| UsePoints.clear(); |
| KnownCallees.clear(); |
| Valid = false; |
| UsePointsComputed = false; |
| NodeAllocator.DestroyAll(); |
| assert(ToMerge.empty()); |
| assert(!NeedMergeCallees); |
| } |
| |
| EscapeAnalysis::CGNode *EscapeAnalysis::ConnectionGraph::getNode(ValueBase *V) { |
| if (isa<FunctionRefInst>(V)) |
| return nullptr; |
| |
| if (!V->hasValue()) |
| return nullptr; |
| |
| if (!EA->isPointer(V)) |
| return nullptr; |
| |
| V = skipProjections(V); |
| |
| CGNode * &Node = Values2Nodes[V]; |
| if (!Node) { |
| if (auto *REAI = dyn_cast<RefElementAddrInst>(V)) { |
| /// Create a separate node for ref_element_addr. |
| CGNode *BasedNode = getNode(REAI->getOperand()); |
| assert(BasedNode && "operand of ref_element_addr must always have a node"); |
| return getRefElementNode(BasedNode); |
| } |
| if (SILArgument *Arg = dyn_cast<SILArgument>(V)) { |
| if (Arg->isFunctionArg()) { |
| Node = allocNode(V, NodeType::Argument); |
| Node->mergeEscapeState(EscapeState::Arguments); |
| } else { |
| Node = allocNode(V, NodeType::Value); |
| } |
| } else { |
| Node = allocNode(V, NodeType::Value); |
| } |
| } |
| return Node->getMergeTarget(); |
| } |
| |
| EscapeAnalysis::CGNode *EscapeAnalysis::ConnectionGraph:: |
| getRefElementNode(CGNode *RefNode) { |
| /// All ref_element_addr of a reference share a single node. |
| if (CGNode *ExistingRENode = RefNode->getRefElementNode()) |
| return ExistingRENode; |
| |
| CGNode *Node = allocNode(RefNode->V, NodeType::RefElement); |
| RefNode->addDefered(Node); |
| if (RefNode->pointsTo) |
| Node->setPointsTo(RefNode->pointsTo); |
| return Node; |
| } |
| |
| EscapeAnalysis::CGNode *EscapeAnalysis::ConnectionGraph::getContentNode( |
| CGNode *AddrNode) { |
| // Do we already have a content node (which is not necessarliy an immediate |
| // successor of AddrNode)? |
| if (AddrNode->pointsTo) |
| return AddrNode->pointsTo; |
| |
| CGNode *Node = allocNode(AddrNode->V, NodeType::Content); |
| updatePointsTo(AddrNode, Node); |
| assert(ToMerge.empty() && |
| "Initially setting pointsTo should not require any node merges"); |
| return Node; |
| } |
| |
| bool EscapeAnalysis::ConnectionGraph::addDeferEdge(CGNode *From, CGNode *To) { |
| if (!From->addDefered(To)) |
| return false; |
| |
| CGNode *FromPointsTo = From->pointsTo; |
| CGNode *ToPointsTo = To->pointsTo; |
| if (FromPointsTo != ToPointsTo) { |
| if (!ToPointsTo) { |
| updatePointsTo(To, FromPointsTo->getMergeTarget()); |
| assert(ToMerge.empty() && |
| "Initially setting pointsTo should not require any node merges"); |
| } else { |
| // We are adding an edge between two pointers which point to different |
| // content nodes. This will require to merge the content nodes (and maybe |
| // other content nodes as well), because of the graph invariance 4). |
| updatePointsTo(From, ToPointsTo->getMergeTarget()); |
| } |
| } |
| return true; |
| } |
| |
| void EscapeAnalysis::ConnectionGraph::mergeAllScheduledNodes() { |
| while (!ToMerge.empty()) { |
| CGNode *From = ToMerge.pop_back_val(); |
| CGNode *To = From->mergeTo; |
| assert(To && "Node scheduled to merge but no merge target set"); |
| assert(!From->isMerged && "Merge source is already merged"); |
| assert(From->Type == NodeType::Content && "Can only merge content nodes"); |
| assert(To->Type == NodeType::Content && "Can only merge content nodes"); |
| |
| // Unlink the predecessors and redirect the incoming pointsTo edge. |
| // Note: we don't redirect the defer-edges because we don't want to trigger |
| // updatePointsTo (which is called by addDeferEdge) right now. |
| for (Predecessor Pred : From->Preds) { |
| CGNode *PredNode = Pred.getPointer(); |
| if (Pred.getInt() == EdgeType::PointsTo) { |
| assert(PredNode->getPointsToEdge() == From && |
| "Incoming pointsTo edge not set in predecessor"); |
| if (PredNode != From) |
| PredNode->setPointsTo(To); |
| } else { |
| assert(PredNode != From); |
| auto Iter = PredNode->findDefered(From); |
| assert(Iter != PredNode->defersTo.end() && |
| "Incoming defer-edge not found in predecessor's defer list"); |
| PredNode->defersTo.erase(Iter); |
| } |
| } |
| // Unlink and redirect the outgoing pointsTo edge. |
| if (CGNode *PT = From->getPointsToEdge()) { |
| if (PT != From) { |
| PT->removeFromPreds(Predecessor(From, EdgeType::PointsTo)); |
| } else { |
| PT = To; |
| } |
| if (CGNode *ExistingPT = To->getPointsToEdge()) { |
| // The To node already has an outgoing pointsTo edge, so the only thing |
| // we can do is to merge both content nodes. |
| scheduleToMerge(ExistingPT, PT); |
| } else { |
| To->setPointsTo(PT); |
| } |
| } |
| // Unlink the outgoing defer edges. |
| for (CGNode *Defers : From->defersTo) { |
| assert(Defers != From && "defer edge may not form a self-cycle"); |
| Defers->removeFromPreds(Predecessor(From, EdgeType::Defer)); |
| } |
| // Redirect the incoming defer edges. This may trigger other node merges. |
| for (Predecessor Pred : From->Preds) { |
| CGNode *PredNode = Pred.getPointer(); |
| if (Pred.getInt() == EdgeType::Defer) { |
| assert(PredNode != From && "defer edge may not form a self-cycle"); |
| addDeferEdge(PredNode, To); |
| } |
| } |
| // Redirect the outgoing defer edges, which may also trigger other node |
| // merges. |
| for (CGNode *Defers : From->defersTo) { |
| addDeferEdge(To, Defers); |
| } |
| |
| // Ensure that graph invariance 4) is kept. At this point there may be still |
| // some violations because of the new adjacent edges of the To node. |
| for (Predecessor Pred : To->Preds) { |
| if (Pred.getInt() == EdgeType::PointsTo) { |
| CGNode *PredNode = Pred.getPointer(); |
| for (Predecessor PredOfPred : PredNode->Preds) { |
| if (PredOfPred.getInt() == EdgeType::Defer) |
| updatePointsTo(PredOfPred.getPointer(), To); |
| } |
| } |
| } |
| if (CGNode *ToPT = To->getPointsToEdge()) { |
| for (CGNode *ToDef : To->defersTo) { |
| updatePointsTo(ToDef, ToPT); |
| } |
| for (Predecessor Pred : To->Preds) { |
| if (Pred.getInt() == EdgeType::Defer) |
| updatePointsTo(Pred.getPointer(), ToPT); |
| } |
| } |
| To->mergeEscapeState(From->State); |
| |
| // Cleanup the merged node. |
| From->isMerged = true; |
| From->Preds.clear(); |
| From->defersTo.clear(); |
| From->pointsTo = nullptr; |
| } |
| } |
| |
| void EscapeAnalysis::ConnectionGraph:: |
| updatePointsTo(CGNode *InitialNode, CGNode *pointsTo) { |
| // Visit all nodes in the defer web, which don't have the right pointsTo set. |
| llvm::SmallVector<CGNode *, 8> WorkList; |
| WorkList.push_back(InitialNode); |
| InitialNode->isInWorkList = true; |
| for (unsigned Idx = 0; Idx < WorkList.size(); ++Idx) { |
| auto *Node = WorkList[Idx]; |
| if (Node->pointsTo == pointsTo) |
| continue; |
| |
| if (Node->pointsTo) { |
| // Mismatching: we need to merge! |
| scheduleToMerge(Node->pointsTo, pointsTo); |
| } |
| |
| // If the node already has a pointsTo _edge_ we don't change it (we don't |
| // want to change the structure of the graph at this point). |
| if (!Node->pointsToIsEdge) { |
| if (Node->defersTo.empty()) { |
| // This node is the end of a defer-edge path with no pointsTo connected. |
| // We create an edge to pointsTo (agreed, this changes the structure of |
| // the graph but adding this edge is harmless). |
| Node->setPointsTo(pointsTo); |
| } else { |
| Node->pointsTo = pointsTo; |
| } |
| } |
| |
| // Add all adjacent nodes to the WorkList. |
| for (auto *Defered : Node->defersTo) { |
| if (!Defered->isInWorkList) { |
| WorkList.push_back(Defered); |
| Defered->isInWorkList = true; |
| } |
| } |
| for (Predecessor Pred : Node->Preds) { |
| if (Pred.getInt() == EdgeType::Defer) { |
| CGNode *PredNode = Pred.getPointer(); |
| if (!PredNode->isInWorkList) { |
| WorkList.push_back(PredNode); |
| PredNode->isInWorkList = true; |
| } |
| } |
| } |
| } |
| for (CGNode *Node : WorkList) { |
| Node->isInWorkList = false; |
| } |
| } |
| |
| void EscapeAnalysis::ConnectionGraph::propagateEscapeStates() { |
| bool Changed = false; |
| do { |
| Changed = false; |
| |
| for (CGNode *Node : Nodes) { |
| // Propagate the state to all successor nodes. |
| if (Node->pointsTo) { |
| Changed |= Node->pointsTo->mergeEscapeState(Node->State); |
| } |
| for (CGNode *Def : Node->defersTo) { |
| Changed |= Def->mergeEscapeState(Node->State); |
| } |
| } |
| } while (Changed); |
| } |
| |
| void EscapeAnalysis::ConnectionGraph::computeUsePoints() { |
| if (UsePointsComputed) |
| return; |
| |
| // First scan the whole function and add relevant instructions as use-points. |
| for (auto &BB : *F) { |
| for (SILArgument *BBArg : BB.getBBArgs()) { |
| /// In addition to releasing instructions (see below) we also add block |
| /// arguments as use points. In case of loops, block arguments can |
| /// "extend" the liferange of a reference in upward direction. |
| if (CGNode *ArgNode = getNodeOrNull(BBArg)) { |
| addUsePoint(ArgNode, BBArg); |
| } |
| } |
| |
| for (auto &I : BB) { |
| switch (I.getKind()) { |
| case ValueKind::StrongReleaseInst: |
| case ValueKind::ReleaseValueInst: |
| case ValueKind::UnownedReleaseInst: |
| case ValueKind::ApplyInst: |
| case ValueKind::TryApplyInst: { |
| /// Actually we only add instructions which may release a reference. |
| /// We need the use points only for getting the end of a reference's |
| /// liferange. And that must be a releaseing instruction. |
| int ValueIdx = -1; |
| for (const Operand &Op : I.getAllOperands()) { |
| ValueBase *OpV = Op.get().getDef(); |
| if (CGNode *OpNd = getNodeOrNull(skipProjections(OpV))) { |
| if (ValueIdx < 0) { |
| ValueIdx = addUsePoint(OpNd, &I); |
| } else { |
| OpNd->setUsePointBit(ValueIdx); |
| } |
| } |
| } |
| break; |
| } |
| default: |
| break; |
| } |
| } |
| } |
| |
| // Second, we propagate the use-point information through the graph. |
| bool Changed = false; |
| do { |
| Changed = false; |
| |
| for (CGNode *Node : Nodes) { |
| // Propagate the bits to all successor nodes. |
| if (Node->pointsTo) { |
| Changed |= Node->pointsTo->mergeUsePoints(Node); |
| } |
| for (CGNode *Def : Node->defersTo) { |
| Changed |= Def->mergeUsePoints(Node); |
| } |
| } |
| } while (Changed); |
| |
| UsePointsComputed = true; |
| } |
| |
| void EscapeAnalysis::ConnectionGraph:: |
| getUsePoints(llvm::SmallPtrSetImpl<ValueBase *> &Values, CGNode *Node) { |
| assert(!Node->escapes() && "Use points are only valid for non-escaping nodes"); |
| |
| computeUsePoints(); |
| |
| for (unsigned VIdx = Node->UsePoints.find_first(); VIdx != -1u; |
| VIdx = Node->UsePoints.find_next(VIdx)) { |
| Values.insert(UsePoints[VIdx]); |
| } |
| } |
| |
| bool EscapeAnalysis::ConnectionGraph::mergeCalleeGraph(FullApplySite FAS, |
| ConnectionGraph *CalleeGraph) { |
| // The main point of the merging algorithm is to map each content node in the |
| // callee graph to a content node in this (caller) graph. This may require to |
| // create new nodes or to merge existing nodes in the caller graph. |
| CGNodeMap Callee2CallerMapping; |
| llvm::SmallVector<CGNode *, 8> MappedNodes; |
| |
| // First map the callee parameters to the caller arguments. |
| SILFunction *Callee = CalleeGraph->F; |
| unsigned numCallerArgs = FAS.getNumArguments(); |
| unsigned numCalleeArgs = Callee->getArguments().size(); |
| assert(numCalleeArgs >= numCallerArgs); |
| for (unsigned Idx = 0; Idx < numCalleeArgs; ++Idx) { |
| // If there are more callee parameters than arguments it means that the |
| // callee is the result of a partial_apply - a thick function. A thick |
| // thick function also references the boxed partially applied arguments. |
| // Therefore we map all the extra callee paramters to the callee operand |
| // of the apply site. |
| SILValue CallerArg = (Idx < numCallerArgs ? FAS.getArgument(Idx) : |
| FAS.getCallee()); |
| if (CGNode *CalleeNd = CalleeGraph->getNode(Callee->getArgument(Idx))) { |
| CGNode *CallerNd = getNode(CallerArg); |
| assert(CallerNd); |
| Callee2CallerMapping.insert(CalleeNd, CallerNd); |
| MappedNodes.push_back(CalleeNd); |
| CalleeNd->isInWorkList = true; |
| } |
| } |
| |
| // Map the return value. |
| if (CalleeGraph->ReturnNode) { |
| ValueBase *CallerReturnVal = nullptr; |
| if (auto *TAI = dyn_cast<TryApplyInst>(FAS.getInstruction())) { |
| CallerReturnVal = TAI->getNormalBB()->getBBArg(0); |
| } else { |
| CallerReturnVal = FAS.getInstruction(); |
| } |
| CGNode *CallerRetNd = getNode(CallerReturnVal); |
| assert(CallerRetNd); |
| Callee2CallerMapping.insert(CalleeGraph->ReturnNode, CallerRetNd); |
| MappedNodes.push_back(CalleeGraph->ReturnNode); |
| CalleeGraph->ReturnNode->isInWorkList = true; |
| } |
| |
| // First step: replicate the points-to edges and the content nodes in the |
| // caller graph. |
| bool Changed = false; |
| bool NodesMerged; |
| do { |
| NodesMerged = false; |
| for (unsigned Idx = 0; Idx < MappedNodes.size(); ++Idx) { |
| CGNode *CalleeNd = MappedNodes[Idx]; |
| CGNode *CallerNd = Callee2CallerMapping.get(CalleeNd); |
| assert(CallerNd); |
| |
| if (CalleeNd->getEscapeState() >= EscapeState::Global) { |
| // We don't need to merge the callee subgraph of nodes which have the |
| // global escaping state set. |
| // Just set global escaping in the caller node and that's it. |
| Changed |= CallerNd->mergeEscapeState(EscapeState::Global); |
| continue; |
| } |
| |
| CGNode *CalleePT = CalleeNd->pointsTo; |
| if (!CalleePT) |
| continue; |
| |
| CGNode *MappedCallerPT = Callee2CallerMapping.get(CalleePT); |
| if (!CallerNd->pointsTo) { |
| // The following getContentNode() will create a new content node. |
| Changed = true; |
| } |
| CGNode *CallerPT = getContentNode(CallerNd); |
| if (MappedCallerPT) { |
| // We already found the caller node through another path. |
| if (CallerPT != MappedCallerPT) { |
| // There are two content nodes in the caller which map to the same |
| // content node in the callee -> we have to merge the caller nodes. |
| scheduleToMerge(CallerPT, MappedCallerPT); |
| mergeAllScheduledNodes(); |
| Changed = true; |
| NodesMerged = true; |
| } |
| } else { |
| // It's the first time we see the caller node, so we add it to the |
| // mapping. |
| Callee2CallerMapping.insert(CalleePT, CallerPT); |
| } |
| if (!CalleePT->isInWorkList) { |
| MappedNodes.push_back(CalleePT); |
| CalleePT->isInWorkList = true; |
| } |
| } |
| } while (NodesMerged); |
| |
| for (CGNode *CalleeNd : MappedNodes) { |
| CalleeNd->isInWorkList = false; |
| } |
| |
| // Second step: add the callee's graph defer edges to the caller graph. |
| for (CGNode *CalleeNd : MappedNodes) { |
| Changed |= addDeferEdgesFromCallee(CalleeNd, Callee2CallerMapping); |
| } |
| return Changed; |
| } |
| |
| bool EscapeAnalysis::ConnectionGraph::addDeferEdgesFromCallee( |
| CGNode *CalleeSource, const CGNodeMap &Callee2CallerMapping) { |
| llvm::SmallVector<CGNode *, 8> WorkList; |
| WorkList.push_back(CalleeSource); |
| CalleeSource->isInWorkList = true; |
| CGNode *Source = Callee2CallerMapping.get(CalleeSource); |
| assert(Source && "node should have been merged to caller graph"); |
| |
| // Collect all nodes which are reachable from the CalleeSource via a path |
| // which only contains defer-edges. |
| bool Changed = false; |
| for (unsigned Idx = 0; Idx < WorkList.size(); ++Idx) { |
| CGNode *CalleeNode = WorkList[Idx]; |
| CGNode *Node = Callee2CallerMapping.get(CalleeNode); |
| // Create the edge in the caller graph. Note: this may trigger merging of |
| // content nodes in the caller graph. |
| if (Node) |
| Changed |= defer(Source, Node); |
| |
| for (auto *Defered : CalleeNode->defersTo) { |
| if (!Defered->isInWorkList) { |
| WorkList.push_back(Defered); |
| Defered->isInWorkList = true; |
| } |
| } |
| } |
| for (CGNode *CalleeNode : WorkList) { |
| CalleeNode->isInWorkList = false; |
| } |
| return Changed; |
| } |
| |
| |
| //===----------------------------------------------------------------------===// |
| // Dumping, Viewing and Verification |
| //===----------------------------------------------------------------------===// |
| |
| #ifndef NDEBUG |
| |
| /// For the llvm's GraphWriter we copy the connection graph into CGForDotView. |
| /// This makes iterating over the edges easier. |
| struct CGForDotView { |
| |
| enum EdgeTypes { |
| PointsTo, |
| Defered |
| }; |
| |
| struct Node { |
| EscapeAnalysis::CGNode *OrigNode; |
| CGForDotView *Graph; |
| SmallVector<Node *, 8> Children; |
| SmallVector<EdgeTypes, 8> ChildrenTypes; |
| }; |
| |
| CGForDotView(const EscapeAnalysis::ConnectionGraph *CG); |
| |
| std::string getNodeLabel(const Node *Node) const; |
| |
| std::string getNodeAttributes(const Node *Node) const; |
| |
| std::vector<Node> Nodes; |
| |
| const EscapeAnalysis::ConnectionGraph *OrigGraph; |
| |
| // The same IDs as the SILPrinter uses. |
| llvm::DenseMap<const ValueBase *, unsigned> InstToIDMap; |
| |
| typedef std::vector<Node>::iterator iterator; |
| typedef SmallVectorImpl<Node *>::iterator child_iterator; |
| }; |
| |
| CGForDotView::CGForDotView(const EscapeAnalysis::ConnectionGraph *CG) : |
| OrigGraph(CG) { |
| Nodes.resize(CG->Nodes.size()); |
| llvm::DenseMap<EscapeAnalysis::CGNode *, Node *> Orig2Node; |
| int idx = 0; |
| for (auto *OrigNode : CG->Nodes) { |
| if (OrigNode->isMerged) |
| continue; |
| |
| Orig2Node[OrigNode] = &Nodes[idx++]; |
| } |
| Nodes.resize(idx); |
| CG->F->numberValues(InstToIDMap); |
| |
| idx = 0; |
| for (auto *OrigNode : CG->Nodes) { |
| if (OrigNode->isMerged) |
| continue; |
| |
| auto &Nd = Nodes[idx++]; |
| Nd.Graph = this; |
| Nd.OrigNode = OrigNode; |
| if (auto *PT = OrigNode->getPointsToEdge()) { |
| Nd.Children.push_back(Orig2Node[PT]); |
| Nd.ChildrenTypes.push_back(PointsTo); |
| } |
| for (auto *Def : OrigNode->defersTo) { |
| Nd.Children.push_back(Orig2Node[Def]); |
| Nd.ChildrenTypes.push_back(Defered); |
| } |
| } |
| } |
| |
| std::string CGForDotView::getNodeLabel(const Node *Node) const { |
| std::string Label; |
| llvm::raw_string_ostream O(Label); |
| O << '%' << InstToIDMap.lookup(Node->OrigNode->V) << '\n'; |
| |
| switch (Node->OrigNode->Type) { |
| case swift::EscapeAnalysis::NodeType::Content: |
| O << "content"; |
| break; |
| case swift::EscapeAnalysis::NodeType::Return: |
| O << "return"; |
| break; |
| case swift::EscapeAnalysis::NodeType::RefElement: |
| O << "refelement"; |
| break; |
| default: { |
| std::string Inst; |
| llvm::raw_string_ostream OI(Inst); |
| SILValue(Node->OrigNode->V).print(OI); |
| size_t start = Inst.find(" = "); |
| if (start != std::string::npos) { |
| start += 3; |
| } else { |
| start = 2; |
| } |
| O << Inst.substr(start, 20); |
| break; |
| } |
| } |
| if (!Node->OrigNode->matchPointToOfDefers()) { |
| O << "\nPT mismatch: "; |
| if (Node->OrigNode->pointsTo) { |
| O << '%' << Node->Graph->InstToIDMap[Node->OrigNode->pointsTo->V]; |
| } else { |
| O << "null"; |
| } |
| } |
| O.flush(); |
| return Label; |
| } |
| |
| std::string CGForDotView::getNodeAttributes(const Node *Node) const { |
| auto *Orig = Node->OrigNode; |
| std::string attr; |
| switch (Orig->Type) { |
| case swift::EscapeAnalysis::NodeType::Content: |
| attr = "style=\"rounded\""; |
| break; |
| case swift::EscapeAnalysis::NodeType::Argument: |
| case swift::EscapeAnalysis::NodeType::Return: |
| attr = "style=\"bold\""; |
| break; |
| default: |
| break; |
| } |
| switch (Orig->getEscapeState()) { |
| case swift::EscapeAnalysis::EscapeState::None: |
| break; |
| case swift::EscapeAnalysis::EscapeState::Arguments: |
| if (!attr.empty()) |
| attr += ','; |
| attr += "color=\"blue\""; |
| break; |
| case swift::EscapeAnalysis::EscapeState::Global: |
| if (!attr.empty()) |
| attr += ','; |
| attr += "color=\"red\""; |
| break; |
| } |
| return attr; |
| } |
| |
| namespace llvm { |
| |
| |
| /// GraphTraits specialization so the CGForDotView can be |
| /// iterable by generic graph iterators. |
| template <> struct GraphTraits<CGForDotView::Node *> { |
| typedef CGForDotView::Node NodeType; |
| typedef CGForDotView::child_iterator ChildIteratorType; |
| |
| static NodeType *getEntryNode(NodeType *N) { return N; } |
| static inline ChildIteratorType child_begin(NodeType *N) { |
| return N->Children.begin(); |
| } |
| static inline ChildIteratorType child_end(NodeType *N) { |
| return N->Children.end(); |
| } |
| }; |
| |
| template <> struct GraphTraits<CGForDotView *> |
| : public GraphTraits<CGForDotView::Node *> { |
| typedef CGForDotView *GraphType; |
| |
| static NodeType *getEntryNode(GraphType F) { return nullptr; } |
| |
| typedef CGForDotView::iterator nodes_iterator; |
| static nodes_iterator nodes_begin(GraphType OCG) { |
| return OCG->Nodes.begin(); |
| } |
| static nodes_iterator nodes_end(GraphType OCG) { return OCG->Nodes.end(); } |
| static unsigned size(GraphType CG) { return CG->Nodes.size(); } |
| }; |
| |
| /// This is everything the llvm::GraphWriter needs to write the call graph in |
| /// a dot file. |
| template <> |
| struct DOTGraphTraits<CGForDotView *> : public DefaultDOTGraphTraits { |
| |
| DOTGraphTraits(bool isSimple = false) : DefaultDOTGraphTraits(isSimple) {} |
| |
| static std::string getGraphName(const CGForDotView *Graph) { |
| return "CG for " + Graph->OrigGraph->getFunction()->getName().str(); |
| } |
| |
| std::string getNodeLabel(const CGForDotView::Node *Node, |
| const CGForDotView *Graph) { |
| return Graph->getNodeLabel(Node); |
| } |
| |
| static std::string getNodeAttributes(const CGForDotView::Node *Node, |
| const CGForDotView *Graph) { |
| return Graph->getNodeAttributes(Node); |
| } |
| |
| static std::string getEdgeAttributes(const CGForDotView::Node *Node, |
| CGForDotView::child_iterator I, |
| const CGForDotView *Graph) { |
| unsigned ChildIdx = I - Node->Children.begin(); |
| switch (Node->ChildrenTypes[ChildIdx]) { |
| case CGForDotView::PointsTo: return ""; |
| case CGForDotView::Defered: return "color=\"gray\""; |
| } |
| } |
| }; |
| } // end llvm namespace |
| |
| #endif |
| |
| void EscapeAnalysis::ConnectionGraph::viewCG() const { |
| /// When asserts are disabled, this should be a NoOp. |
| #ifndef NDEBUG |
| CGForDotView CGDot(this); |
| llvm::ViewGraph(&CGDot, "connection-graph"); |
| #endif |
| } |
| |
| void EscapeAnalysis::CGNode::dump() const { |
| llvm::errs() << getTypeStr() << ": " << *V; |
| if (mergeTo) { |
| llvm::errs() << " -> merged to "; |
| mergeTo->dump(); |
| } |
| } |
| |
| const char *EscapeAnalysis::CGNode::getTypeStr() const { |
| switch (Type) { |
| case NodeType::Value: return "Val"; |
| case NodeType::Content: return "Con"; |
| case NodeType::Argument: return "Arg"; |
| case NodeType::Return: return "Ret"; |
| case NodeType::RefElement: return "REl"; |
| } |
| } |
| |
| void EscapeAnalysis::ConnectionGraph::dump() const { |
| print(llvm::errs()); |
| } |
| |
| void EscapeAnalysis::ConnectionGraph::print(llvm::raw_ostream &OS) const { |
| #ifndef NDEBUG |
| OS << "CG of " << F->getName() << '\n'; |
| |
| if (!Valid) { |
| OS << " <invalid>\n"; |
| return; |
| } |
| |
| // Assign the same IDs to SILValues as the SILPrinter does. |
| llvm::DenseMap<const ValueBase *, unsigned> InstToIDMap; |
| F->numberValues(InstToIDMap); |
| |
| // Assign consecutive subindices for nodes which map to the same value. |
| llvm::DenseMap<const ValueBase *, unsigned> NumSubindicesPerValue; |
| llvm::DenseMap<CGNode *, unsigned> Node2Subindex; |
| |
| // Sort by SILValue ID+Subindex. To make the output somehow consistent with |
| // the output of the function's SIL. |
| auto sortNodes = [&](llvm::SmallVectorImpl<CGNode *> &Nodes) { |
| std::sort(Nodes.begin(), Nodes.end(), |
| [&](CGNode *Nd1, CGNode *Nd2) -> bool { |
| unsigned VIdx1 = InstToIDMap[Nd1->V]; |
| unsigned VIdx2 = InstToIDMap[Nd2->V]; |
| if (VIdx1 != VIdx2) |
| return VIdx1 < VIdx2; |
| return Node2Subindex[Nd1] < Node2Subindex[Nd2]; |
| }); |
| }; |
| |
| auto NodeStr = [&](CGNode *Nd) -> std::string { |
| std::string Str; |
| llvm::raw_string_ostream OS(Str); |
| OS << '%' << InstToIDMap[Nd->V]; |
| unsigned Idx = Node2Subindex[Nd]; |
| if (Idx != 0) |
| OS << '.' << Idx; |
| OS.flush(); |
| return Str; |
| }; |
| |
| llvm::SmallVector<CGNode *, 8> SortedNodes; |
| for (CGNode *Nd : Nodes) { |
| if (!Nd->isMerged) { |
| unsigned &Idx = NumSubindicesPerValue[Nd->V]; |
| Node2Subindex[Nd] = Idx++; |
| SortedNodes.push_back(Nd); |
| } |
| } |
| sortNodes(SortedNodes); |
| |
| for (CGNode *Nd : SortedNodes) { |
| OS << " " << Nd->getTypeStr() << ' ' << NodeStr(Nd) << " Esc: "; |
| switch (Nd->getEscapeState()) { |
| case EscapeState::None: { |
| const char *Separator = ""; |
| for (unsigned VIdx = Nd->UsePoints.find_first(); VIdx != -1u; |
| VIdx = Nd->UsePoints.find_next(VIdx)) { |
| ValueBase *V = UsePoints[VIdx]; |
| OS << Separator << '%' << InstToIDMap[V]; |
| Separator = ","; |
| } |
| break; |
| } |
| case EscapeState::Arguments: |
| OS << 'A'; |
| break; |
| case EscapeState::Global: |
| OS << 'G'; |
| break; |
| } |
| OS << ", Succ: "; |
| const char *Separator = ""; |
| if (CGNode *PT = Nd->getPointsToEdge()) { |
| OS << '(' << NodeStr(PT) << ')'; |
| Separator = ", "; |
| } |
| llvm::SmallVector<CGNode *, 8> SortedDefers = Nd->defersTo; |
| sortNodes(SortedDefers); |
| for (CGNode *Def : SortedDefers) { |
| OS << Separator << NodeStr(Def); |
| Separator = ", "; |
| } |
| OS << '\n'; |
| } |
| OS << "End\n"; |
| #endif |
| } |
| |
| void EscapeAnalysis::ConnectionGraph::verify() const { |
| #ifndef NDEBUG |
| verifyStructure(); |
| |
| // Check graph invariance 4) |
| for (CGNode *Nd : Nodes) { |
| assert(Nd->matchPointToOfDefers()); |
| } |
| #endif |
| } |
| |
| void EscapeAnalysis::ConnectionGraph::verifyStructure() const { |
| #ifndef NDEBUG |
| if (!Valid) { |
| assert(Nodes.empty()); |
| assert(Values2Nodes.empty()); |
| assert(!ReturnNode); |
| return; |
| } |
| for (CGNode *Nd : Nodes) { |
| if (Nd->isMerged) { |
| assert(Nd->mergeTo); |
| assert(!Nd->pointsTo); |
| assert(Nd->defersTo.empty()); |
| assert(Nd->Preds.empty()); |
| assert(Nd->Type == NodeType::Content); |
| continue; |
| } |
| // Check if predecessor and successor edges are linked correctly. |
| for (Predecessor Pred : Nd->Preds) { |
| CGNode *PredNode = Pred.getPointer(); |
| if (Pred.getInt() == EdgeType::Defer) { |
| assert(PredNode->findDefered(Nd) != PredNode->defersTo.end()); |
| } else { |
| assert(Pred.getInt() == EdgeType::PointsTo); |
| assert(PredNode->getPointsToEdge() == Nd); |
| } |
| } |
| bool InRefElements = true; |
| for (CGNode *Def : Nd->defersTo) { |
| if (Def->Type == NodeType::RefElement) { |
| assert(InRefElements && "RefElement node is not first in defersTo"); |
| } else { |
| InRefElements = false; |
| } |
| assert(Def->findPred(Predecessor(Nd, EdgeType::Defer)) != Def->Preds.end()); |
| assert(Def != Nd); |
| } |
| if (CGNode *PT = Nd->getPointsToEdge()) { |
| assert(PT->Type == NodeType::Content); |
| assert(PT->findPred(Predecessor(Nd, EdgeType::PointsTo)) != PT->Preds.end()); |
| } |
| } |
| #endif |
| } |
| |
| //===----------------------------------------------------------------------===// |
| // EscapeAnalysis |
| //===----------------------------------------------------------------------===// |
| |
| EscapeAnalysis::EscapeAnalysis(SILModule *M) : |
| SILAnalysis(AnalysisKind::Escape), M(M), |
| ArrayType(M->getASTContext().getArrayDecl()), |
| CGA(nullptr), shouldRecompute(true) { |
| } |
| |
| |
| void EscapeAnalysis::initialize(SILPassManager *PM) { |
| CGA = PM->getAnalysis<CallGraphAnalysis>(); |
| } |
| |
| /// Returns true if we need to add defer edges for the arguments of a block. |
| static bool linkBBArgs(SILBasicBlock *BB) { |
| // Don't need to handle function arguments. |
| if (BB == &BB->getParent()->front()) |
| return false; |
| // We don't need to link to the try_apply's normal result argument, because |
| // we handle it separatly in setAllEscaping() and mergeCalleeGraph(). |
| if (SILBasicBlock *SinglePred = BB->getSinglePredecessor()) { |
| auto *TAI = dyn_cast<TryApplyInst>(SinglePred->getTerminator()); |
| if (TAI && BB == TAI->getNormalBB()) |
| return false; |
| } |
| return true; |
| } |
| |
| /// Returns true if the type \p Ty is a reference or transitively contains |
| /// a reference, i.e. if it is a "pointer" type. |
| static bool isOrContainsReference(SILType Ty, SILModule *Mod) { |
| if (Ty.hasReferenceSemantics()) |
| return true; |
| |
| if (Ty.getSwiftType() == Mod->getASTContext().TheRawPointerType) |
| return true; |
| |
| if (auto *Str = Ty.getStructOrBoundGenericStruct()) { |
| for (auto *Field : Str->getStoredProperties()) { |
| if (isOrContainsReference(Ty.getFieldType(Field, *Mod), Mod)) |
| return true; |
| } |
| return false; |
| } |
| if (auto TT = Ty.getAs<TupleType>()) { |
| for (unsigned i = 0, e = TT->getNumElements(); i != e; ++i) { |
| if (isOrContainsReference(Ty.getTupleElementType(i), Mod)) |
| return true; |
| } |
| return false; |
| } |
| if (auto En = Ty.getEnumOrBoundGenericEnum()) { |
| for (auto *ElemDecl : En->getAllElements()) { |
| if (ElemDecl->hasArgumentType() && |
| isOrContainsReference(Ty.getEnumElementType(ElemDecl, *Mod), Mod)) |
| return true; |
| } |
| return false; |
| } |
| return false; |
| } |
| |
| bool EscapeAnalysis::isPointer(ValueBase *V) { |
| assert(V->hasValue()); |
| SILType Ty = V->getType(0); |
| auto Iter = isPointerCache.find(Ty); |
| if (Iter != isPointerCache.end()) |
| return Iter->second; |
| |
| bool IP = (Ty.isAddress() || Ty.isLocalStorage() || |
| isOrContainsReference(Ty, M)); |
| isPointerCache[Ty] = IP; |
| return IP; |
| } |
| |
| void EscapeAnalysis::buildConnectionGraph(SILFunction *F, |
| ConnectionGraph *ConGraph) { |
| // We use a worklist for iteration to visit the blocks in dominance order. |
| llvm::SmallPtrSet<SILBasicBlock*, 32> VisitedBlocks; |
| llvm::SmallVector<SILBasicBlock *, 16> WorkList; |
| VisitedBlocks.insert(&*F->begin()); |
| WorkList.push_back(&*F->begin()); |
| |
| while (!WorkList.empty()) { |
| SILBasicBlock *BB = WorkList.pop_back_val(); |
| |
| // Create edges for the instructions. |
| for (auto &I : *BB) { |
| analyzeInstruction(&I, ConGraph); |
| } |
| for (auto &Succ : BB->getSuccessors()) { |
| if (VisitedBlocks.insert(Succ.getBB()).second) |
| WorkList.push_back(Succ.getBB()); |
| } |
| } |
| |
| // Second step: create defer-edges for block arguments. |
| for (SILBasicBlock &BB : *F) { |
| if (!linkBBArgs(&BB)) |
| continue; |
| |
| // Create defer-edges from the block arguments to it's values in the |
| // predecessor's terminator instructions. |
| for (SILArgument *BBArg : BB.getBBArgs()) { |
| CGNode *ArgNode = ConGraph->getNode(BBArg); |
| if (!ArgNode) |
| continue; |
| |
| llvm::SmallVector<SILValue,4> Incoming; |
| if (!BBArg->getIncomingValues(Incoming)) { |
| // We don't know where the block argument comes from -> treat it |
| // conservatively. |
| ConGraph->setEscapesGlobal(ArgNode); |
| continue; |
| } |
| |
| for (SILValue Src : Incoming) { |
| CGNode *SrcArg = ConGraph->getNode(Src); |
| if (SrcArg) { |
| ConGraph->defer(ArgNode, SrcArg); |
| } else { |
| ConGraph->setEscapesGlobal(ArgNode); |
| break; |
| } |
| } |
| } |
| } |
| |
| ConGraph->propagateEscapeStates(); |
| } |
| |
| /// Returns true if all called functions from an apply site are known and not |
| /// external. |
| static bool allCalleeFunctionsVisible(FullApplySite FAS, CallGraph &CG) { |
| if (CG.canCallUnknownFunction(FAS.getInstruction())) |
| return false; |
| |
| SILFunction *Caller = FAS.getInstruction()->getFunction(); |
| auto Callees = CG.getCallees(FAS.getInstruction()); |
| for (SILFunction *Callee : Callees) { |
| if (Callee->isExternalDeclaration()) |
| return false; |
| // TODO: Currently we can't self-cycles in the call graph, because we can't |
| // merge a graph to itself. |
| if (Callee == Caller) |
| return false; |
| } |
| return true; |
| } |
| |
| void EscapeAnalysis::analyzeInstruction(SILInstruction *I, |
| ConnectionGraph *ConGraph) { |
| FullApplySite FAS = FullApplySite::isa(I); |
| if (FAS) { |
| ArraySemanticsCall ASC(FAS.getInstruction()); |
| switch (ASC.getKind()) { |
| case ArrayCallKind::kArrayPropsIsNative: |
| case ArrayCallKind::kArrayPropsIsNativeTypeChecked: |
| case ArrayCallKind::kCheckSubscript: |
| case ArrayCallKind::kCheckIndex: |
| case ArrayCallKind::kGetCount: |
| case ArrayCallKind::kGetCapacity: |
| case ArrayCallKind::kMakeMutable: |
| // These array semantics calls do not capture anything. |
| return; |
| case ArrayCallKind::kArrayUninitialized: |
| // array.uninitialized may have a first argument which is the |
| // allocated array buffer. The call is like a struct(buffer) |
| // instruction. |
| if (CGNode *BufferNode = ConGraph->getNode(FAS.getArgument(0))) { |
| ConGraph->defer(ConGraph->getNode(I), BufferNode); |
| } |
| return; |
| case ArrayCallKind::kGetArrayOwner: |
| if (CGNode *BufferNode = ConGraph->getNode(ASC.getSelf())) { |
| ConGraph->defer(ConGraph->getNode(I), BufferNode); |
| } |
| return; |
| case ArrayCallKind::kGetElement: |
| // This is like a load. |
| if (FAS.getArgument(0).getType().isAddress()) { |
| if (CGNode *AddrNode = ConGraph->getNode(ASC.getSelf())) { |
| if (CGNode *DestNode = ConGraph->getNode(FAS.getArgument(0))) { |
| CGNode *ArrayContent = ConGraph->getContentNode(AddrNode); |
| CGNode *DestContent = ConGraph->getContentNode(DestNode); |
| ConGraph->defer(DestContent, ArrayContent); |
| return; |
| } |
| } |
| } |
| break; |
| case ArrayCallKind::kGetElementAddress: |
| // This is like a ref_element_addr. |
| if (CGNode *SelfNode = ConGraph->getNode(ASC.getSelf())) { |
| ConGraph->defer(ConGraph->getNode(I), |
| ConGraph->getRefElementNode(SelfNode)); |
| } |
| return; |
| default: |
| break; |
| } |
| |
| if (allCalleeFunctionsVisible(FAS, CGA->getCallGraph())) { |
| // We handle this apply site afterwards by merging the callee graph(s) into |
| // the caller graph. |
| ConGraph->addKnownCallee(FAS); |
| return; |
| } |
| |
| if (auto *FRI = dyn_cast<FunctionRefInst>(FAS.getCallee())) { |
| if (FRI->getReferencedFunction()->getName() == "swift_bufferAllocate") |
| // The call is a buffer allocation, e.g. for Array. |
| return; |
| } |
| } |
| if (isProjection(I)) |
| return; |
| |
| // Instructions which return the address of non-writable memory cannot have |
| // an effect on escaping. |
| if (isNonWritableMemoryAddress(I)) |
| return; |
| |
| switch (I->getKind()) { |
| case ValueKind::AllocStackInst: |
| case ValueKind::AllocRefInst: |
| case ValueKind::AllocBoxInst: |
| case ValueKind::DeallocStackInst: |
| case ValueKind::StrongRetainInst: |
| case ValueKind::StrongRetainUnownedInst: |
| case ValueKind::RetainValueInst: |
| case ValueKind::UnownedRetainInst: |
| case ValueKind::BranchInst: |
| case ValueKind::CondBranchInst: |
| case ValueKind::SwitchEnumInst: |
| case ValueKind::DebugValueInst: |
| case ValueKind::DebugValueAddrInst: |
| case ValueKind::RefElementAddrInst: |
| // These instructions don't have any effect on escaping. |
| return; |
| case ValueKind::StrongReleaseInst: |
| case ValueKind::ReleaseValueInst: |
| case ValueKind::UnownedReleaseInst: { |
| SILValue OpV = I->getOperand(0); |
| if (CGNode *AddrNode = ConGraph->getNode(OpV)) { |
| // A release instruction may deallocate the pointer operand. This may |
| // capture any content of the released object, but not the pointer to |
| // the object itself (because it will be a dangling pointer after |
| // deallocation). |
| CGNode *CapturedByDeinit = ConGraph->getContentNode(AddrNode); |
| SILType ReleasedType = OpV.getType(); |
| // The deinit of a box or thick function does not capture anything, |
| // but may call the deinit of the boxed value. So we can go down |
| // one pointsTo-level again. |
| if (ReleasedType.is<SILBoxType>() || |
| ReleasedType.is<SILFunctionType>() || |
| // Similarly, the deinit of an array does not capture its elements. |
| isArrayOrArrayStorage(OpV)) { |
| CapturedByDeinit = ConGraph->getContentNode(CapturedByDeinit); |
| } |
| ConGraph->setEscapesGlobal(CapturedByDeinit); |
| } |
| return; |
| } |
| case ValueKind::LoadInst: |
| case ValueKind::LoadWeakInst: |
| if (isPointer(I)) { |
| CGNode *AddrNode = ConGraph->getNode(I->getOperand(0)); |
| if (!AddrNode) { |
| // A load from an address we don't handle -> be conservative. |
| CGNode *ValueNode = ConGraph->getNode(I); |
| ConGraph->setEscapesGlobal(ValueNode); |
| return; |
| } |
| CGNode *PointsTo = ConGraph->getContentNode(AddrNode); |
| // No need for a separate node for the load instruction: |
| // just reuse the content node. |
| ConGraph->setNode(I, PointsTo); |
| } |
| return; |
| case ValueKind::StoreInst: |
| case ValueKind::StoreWeakInst: |
| if (CGNode *ValueNode = ConGraph->getNode(I->getOperand(StoreInst::Src))) { |
| CGNode *AddrNode = ConGraph->getNode(I->getOperand(StoreInst::Dest)); |
| if (AddrNode) { |
| // Create a defer-edge from the content to the stored value. |
| CGNode *PointsTo = ConGraph->getContentNode(AddrNode); |
| ConGraph->defer(PointsTo, ValueNode); |
| } else { |
| // A store to an address we don't handle -> be conservative. |
| ConGraph->setEscapesGlobal(ValueNode); |
| } |
| } |
| return; |
| case ValueKind::PartialApplyInst: { |
| // The result of a partial_apply is a thick function which stores the |
| // boxed partial applied arguments. We create defer-edges from the |
| // partial_apply values to the arguments. |
| CGNode *ResultNode = ConGraph->getNode(I); |
| assert(ResultNode && "thick functions must have a CG node"); |
| for (const Operand &Op : I->getAllOperands()) { |
| if (CGNode *ArgNode = ConGraph->getNode(Op.get())) { |
| ConGraph->defer(ResultNode, ArgNode); |
| } |
| } |
| return; |
| } |
| case ValueKind::StructInst: |
| case ValueKind::TupleInst: |
| case ValueKind::EnumInst: { |
| // Aggregate composition is like assigning the aggregate fields to the |
| // resulting aggregate value. |
| CGNode *ResultNode = nullptr; |
| for (const Operand &Op : I->getAllOperands()) { |
| if (CGNode *FieldNode = ConGraph->getNode(Op.get())) { |
| if (!ResultNode) { |
| // A small optimization to reduce the graph size: we re-use the |
| // first field node as result node. |
| ConGraph->setNode(I, FieldNode); |
| ResultNode = FieldNode; |
| assert(isPointer(I)); |
| } else { |
| ConGraph->defer(ResultNode, FieldNode); |
| } |
| } |
| } |
| return; |
| } |
| case ValueKind::UncheckedRefCastInst: |
| // A cast is almost like a projection. |
| if (CGNode *OpNode = ConGraph->getNode(I->getOperand(0))) { |
| ConGraph->setNode(I, OpNode); |
| } |
| break; |
| case ValueKind::ReturnInst: |
| if (CGNode *ValueNd = ConGraph->getNode(cast<ReturnInst>(I)->getOperand())) { |
| ConGraph->defer(ConGraph->getReturnNode(cast<ReturnInst>(I)), |
| ValueNd); |
| } |
| return; |
| default: |
| // We handle all other instructions conservatively. |
| setAllEscaping(I, ConGraph); |
| return; |
| } |
| } |
| |
| bool EscapeAnalysis::isArrayOrArrayStorage(SILValue V) { |
| for (;;) { |
| if (V.getType().getNominalOrBoundGenericNominal() == ArrayType) |
| return true; |
| |
| if (!isProjection(V.getDef())) |
| return false; |
| |
| V = dyn_cast<SILInstruction>(V.getDef())->getOperand(0); |
| } |
| } |
| |
| void EscapeAnalysis::setAllEscaping(SILInstruction *I, |
| ConnectionGraph *ConGraph) { |
| if (auto *TAI = dyn_cast<TryApplyInst>(I)) { |
| ConGraph->setEscapesGlobal(TAI->getNormalBB()->getBBArg(0)); |
| ConGraph->setEscapesGlobal(TAI->getErrorBB()->getBBArg(0)); |
| } |
| // Even if the instruction does not write memory we conservatively set all |
| // operands to escaping, because they may "escape" to the result value in |
| // an unspecified way. For example consider bit-casting a pointer to an int. |
| // In this case we don't even create a node for the resulting int value. |
| for (const Operand &Op : I->getAllOperands()) { |
| SILValue OpVal = Op.get(); |
| if (!isNonWritableMemoryAddress(OpVal.getDef())) |
| ConGraph->setEscapesGlobal(OpVal); |
| } |
| // Even if the instruction does not write memory it could e.g. return the |
| // address of global memory. Therefore we have to define it as escaping. |
| ConGraph->setEscapesGlobal(I); |
| } |
| |
| void EscapeAnalysis::recompute() { |
| |
| // Did anything change since the last recompuation? (Probably yes) |
| if (!shouldRecompute) |
| return; |
| |
| DEBUG(llvm::dbgs() << "recompute escape analysis\n"); |
| |
| Function2ConGraph.clear(); |
| Allocator.DestroyAll(); |
| shouldRecompute = false; |
| |
| CallGraph &CG = CGA->getOrBuildCallGraph(); |
| |
| // TODO: Remove this workaround when the bottom-up function order is |
| // updated automatically. |
| CG.invalidateBottomUpFunctionOrder(); |
| |
| auto BottomUpFunctions = CG.getBottomUpFunctionOrder(); |
| std::vector<ConnectionGraph *> Graphs; |
| Graphs.reserve(BottomUpFunctions.size()); |
| |
| // First step: create the initial connection graphs for all functions. |
| for (SILFunction *F : BottomUpFunctions) { |
| if (F->isExternalDeclaration()) |
| continue; |
| |
| DEBUG(llvm::dbgs() << " build initial graph for " << F->getName() << '\n'); |
| |
| auto *ConGraph = new (Allocator.Allocate()) ConnectionGraph(F, this); |
| Function2ConGraph[F] = ConGraph; |
| buildConnectionGraph(F, ConGraph); |
| |
| Graphs.push_back(ConGraph); |
| ConGraph->setNeedMergeCallees(); |
| } |
| |
| // Second step: propagate the connection graphs up the call tree until it |
| // stabalizes. |
| int Iteration = 0; |
| bool Changed; |
| do { |
| DEBUG(llvm::dbgs() << "iteration " << Iteration << '\n'); |
| Changed = false; |
| for (ConnectionGraph *ConGraph : Graphs) { |
| if (!ConGraph->handleMergeCallees()) |
| continue; |
| |
| // Limit the total number of iterations. First to limit compile time, |
| // second to make sure that the loop terminates. Theoretically this |
| // should alwasy be the case, but who knows? |
| if (Iteration < MaxGraphMerges) { |
| DEBUG(llvm::dbgs() << " merge into " << |
| ConGraph->getFunction()->getName() << '\n'); |
| |
| if (mergeAllCallees(ConGraph, CG)) { |
| // Trigger another graph merging action in all caller functions. |
| auto &CallerSet = CG.getCallerEdges(ConGraph->getFunction()); |
| for (CallGraphEdge *CallerEdge : CallerSet) { |
| if (!CallerEdge->canCallUnknownFunction()) { |
| SILFunction *Caller = CallerEdge->getInstruction()->getFunction(); |
| ConnectionGraph *CallerCG = getConnectionGraph(Caller); |
| assert(CallerCG); |
| CallerCG->setNeedMergeCallees(); |
| } |
| } |
| Changed = true; |
| } |
| } else { |
| DEBUG(llvm::dbgs() << " finalize " << |
| ConGraph->getFunction()->getName() << '\n'); |
| |
| finalizeGraphConservatively(ConGraph); |
| } |
| } |
| Iteration++; |
| } while (Changed); |
| |
| verify(); |
| } |
| |
| bool EscapeAnalysis::mergeAllCallees(ConnectionGraph *ConGraph, CallGraph &CG) { |
| bool Changed = false; |
| for (FullApplySite FAS : ConGraph->getKnownCallees()) { |
| auto Callees = CG.getCallees(FAS.getInstruction()); |
| assert(!Callees.canCallUnknownFunction() && |
| "knownCallees should not contain an unknown function"); |
| for (SILFunction *Callee : Callees) { |
| DEBUG(llvm::dbgs() << " callee " << Callee->getName() << '\n'); |
| ConnectionGraph *CalleeCG = getConnectionGraph(Callee); |
| assert(CalleeCG); |
| assert (CalleeCG != ConGraph && "no support for CG self cycles yet"); |
| Changed |= ConGraph->mergeCalleeGraph(FAS, CalleeCG); |
| } |
| } |
| |
| if (Changed) { |
| ConGraph->propagateEscapeStates(); |
| return true; |
| } |
| return false; |
| } |
| |
| void EscapeAnalysis::finalizeGraphConservatively(ConnectionGraph *ConGraph) { |
| for (FullApplySite FAS : ConGraph->getKnownCallees()) { |
| setAllEscaping(FAS.getInstruction(), ConGraph); |
| } |
| ConGraph->propagateEscapeStates(); |
| } |
| |
| SILAnalysis *swift::createEscapeAnalysis(SILModule *M) { |
| return new EscapeAnalysis(M); |
| } |