diff --git a/include/swift/SILOptimizer/Analysis/EscapeAnalysis.h b/include/swift/SILOptimizer/Analysis/EscapeAnalysis.h index 6586956d3c2..a21e8687735 100644 --- a/include/swift/SILOptimizer/Analysis/EscapeAnalysis.h +++ b/include/swift/SILOptimizer/Analysis/EscapeAnalysis.h @@ -286,8 +286,16 @@ private: /// the node's value. /// Note that in the false-case the node's value can still escape via /// the return instruction. - bool escapesInsideFunction() const { - return getEscapeState() > EscapeState::Return; + bool escapesInsideFunction(bool isNotAliasingArgument) const { + switch (getEscapeState()) { + case EscapeState::None: + case EscapeState::Return: + return false; + case EscapeState::Arguments: + return !isNotAliasingArgument; + case EscapeState::Global: + return true; + } } }; @@ -423,7 +431,12 @@ public: /// taken. This means the node is always created for the "outermost" value /// where V is contained. /// Returns null, if V is not a "pointer". - CGNode *getOrCreateNode(ValueBase *V); + CGNode *getNode(ValueBase *V, EscapeAnalysis *EA, bool createIfNeeded = true); + + /// Gets or creates a node for a SILValue (same as above). + CGNode *getNode(SILValue V, EscapeAnalysis *EA) { + return getNode(V.getDef(), EA, true); + } /// Gets or creates a content node to which \a AddrNode points to. CGNode *getContentNode(CGNode *AddrNode); @@ -444,7 +457,7 @@ public: /// Returns the node of the "exact" value \p V (no projections are skipped) /// if one exists. - CGNode *getNodeOrNull(ValueBase *V) { + CGNode *lookupNode(ValueBase *V) { CGNode *Node = Values2Nodes.lookup(V); if (Node) return Node->getMergeTarget(); @@ -502,6 +515,9 @@ public: /// lookup-up with getNode() anymore. void removeFromGraph(ValueBase *V) { Values2Nodes.erase(V); } + /// Returns true if there is a path from \p From to \p To. + bool isReachable(CGNode *From, CGNode *To); + public: /// Gets or creates a node for a value \p V. @@ -509,11 +525,13 @@ public: /// taken. This means the node is always created for the "outermost" value /// where V is contained. /// Returns null, if V is not a "pointer". - CGNode *getNode(ValueBase *V, EscapeAnalysis *EA); + CGNode *getNodeOrNull(ValueBase *V, EscapeAnalysis *EA) { + return getNode(V, EA, false); + } /// Gets or creates a node for a SILValue (same as above). - CGNode *getNode(SILValue V, EscapeAnalysis *EA) { - return getNode(V.getDef(), EA); + CGNode *getNodeOrNull(SILValue V, EscapeAnalysis *EA) { + return getNode(V.getDef(), EA, false); } /// Returns the number of use-points of a node. @@ -529,9 +547,6 @@ public: /// e.g. release or apply instructions. bool isUsePoint(ValueBase *V, CGNode *Node); - /// Returns true if there is a path from \p From to \p To. - bool canEscapeTo(CGNode *From, CGNode *To); - /// Computes the use point information. void computeUsePoints(); @@ -627,7 +642,7 @@ private: bool isPointer(ValueBase *V); /// If V is a pointer, set it to global escaping. - void setEscapesGlobal(ConnectionGraph *ConGraph, SILValue V) { + void setEscapesGlobal(ConnectionGraph *ConGraph, ValueBase *V) { if (CGNode *Node = ConGraph->getNode(V, this)) ConGraph->setEscapesGlobal(Node); } @@ -704,19 +719,30 @@ public: /// Returns true if the value \p V can escape to the function call \p FAS. /// This means that the called function may access the value \p V. - bool canEscapeTo(SILValue V, FullApplySite FAS, ConnectionGraph *ConGraph) { - return canEscapeToUsePoint(V, FAS.getInstruction(), ConGraph); - } + /// If \p V has reference semantics, this function returns false if only the + /// address of a contained property escapes, but not the object itself. + bool canEscapeTo(SILValue V, FullApplySite FAS); + + /// Returns true if the value \p V or its content can escape to the + /// function call \p FAS. + /// This is the same as above, execpt that it returns true if an address of + /// a contained property escapes. + bool canObjectOrContentEscapeTo(SILValue V, FullApplySite FAS); /// Returns true if the value \p V can escape to the release-instruction \p /// RI. This means that \p RI may release \p V or any called destructor may /// access (or release) \p V. /// Note that if \p RI is a retain-instruction always false is returned. - bool canEscapeTo(SILValue V, RefCountingInst *RI, ConnectionGraph *ConGraph) { - return canEscapeToUsePoint(V, RI, ConGraph); - } + bool canEscapeTo(SILValue V, RefCountingInst *RI); - bool canPointToSameMemory(SILValue V1, SILValue V2, ConnectionGraph *ConGraph); + /// Returns true if the value \p V can escape to any other pointer \p To. + /// This means that either \p To is the same as \p V or containes a reference + /// to \p V. + bool canEscapeToValue(SILValue V, SILValue To); + + /// Returns true if the pointers \p V1 and \p V2 can possibly point to the + /// same memory. + bool canPointToSameMemory(SILValue V1, SILValue V2); virtual void invalidate(InvalidationKind K) override; diff --git a/lib/SILOptimizer/Analysis/EscapeAnalysis.cpp b/lib/SILOptimizer/Analysis/EscapeAnalysis.cpp index 261b8a16643..4d6334c6eb8 100644 --- a/lib/SILOptimizer/Analysis/EscapeAnalysis.cpp +++ b/lib/SILOptimizer/Analysis/EscapeAnalysis.cpp @@ -80,7 +80,21 @@ void EscapeAnalysis::ConnectionGraph::clear() { } EscapeAnalysis::CGNode *EscapeAnalysis::ConnectionGraph:: -getOrCreateNode(ValueBase *V) { +getNode(ValueBase *V, EscapeAnalysis *EA, bool createIfNeeded) { + if (isa(V)) + return nullptr; + + if (!V->hasValue()) + return nullptr; + + if (!EA->isPointer(V)) + return nullptr; + + V = skipProjections(V); + + if (!createIfNeeded) + return lookupNode(V); + CGNode * &Node = Values2Nodes[V]; if (!Node) { if (SILArgument *Arg = dyn_cast(V)) { @@ -296,7 +310,7 @@ void EscapeAnalysis::ConnectionGraph::computeUsePoints() { /// 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)) { + if (CGNode *ArgNode = lookupNode(BBArg)) { addUsePoint(ArgNode, BBArg); } } @@ -314,7 +328,7 @@ void EscapeAnalysis::ConnectionGraph::computeUsePoints() { int ValueIdx = -1; for (const Operand &Op : I.getAllOperands()) { ValueBase *OpV = Op.get().getDef(); - if (CGNode *OpNd = getNodeOrNull(skipProjections(OpV))) { + if (CGNode *OpNd = lookupNode(skipProjections(OpV))) { if (ValueIdx < 0) { ValueIdx = addUsePoint(OpNd, &I); } else { @@ -438,22 +452,6 @@ bool EscapeAnalysis::ConnectionGraph::mergeFrom(ConnectionGraph *SourceGraph, return Changed; } -EscapeAnalysis::CGNode *EscapeAnalysis::ConnectionGraph:: -getNode(ValueBase *V, EscapeAnalysis *EA) { - if (isa(V)) - return nullptr; - - if (!V->hasValue()) - return nullptr; - - if (!EA->isPointer(V)) - return nullptr; - - V = skipProjections(V); - - return getOrCreateNode(V); -} - /// Returns true if \p V is a use of \p Node, i.e. V may (indirectly) /// somehow refer to the Node's value. /// Use-points are only values which are relevant for lifeness computation, @@ -470,7 +468,7 @@ bool EscapeAnalysis::ConnectionGraph::isUsePoint(ValueBase *V, CGNode *Node) { return Node->UsePoints.test(Idx); } -bool EscapeAnalysis::ConnectionGraph::canEscapeTo(CGNode *From, CGNode *To) { +bool EscapeAnalysis::ConnectionGraph::isReachable(CGNode *From, CGNode *To) { // See if we can reach the From-node by transitively visiting the // predecessor nodes of the To-node. // Usually nodes have few predecessor nodes and the graph depth is small. @@ -1121,6 +1119,9 @@ void EscapeAnalysis::analyzeInstruction(SILInstruction *I, case ValueKind::AllocStackInst: case ValueKind::AllocRefInst: case ValueKind::AllocBoxInst: + ConGraph->getNode(I, this); + return; + case ValueKind::DeallocStackInst: case ValueKind::StrongRetainInst: case ValueKind::StrongRetainUnownedInst: @@ -1318,7 +1319,7 @@ void EscapeAnalysis::setAllEscaping(SILInstruction *I, for (const Operand &Op : I->getAllOperands()) { SILValue OpVal = Op.get(); if (!isNonWritableMemoryAddress(OpVal.getDef())) - setEscapesGlobal(ConGraph, OpVal); + setEscapesGlobal(ConGraph, OpVal.getDef()); } // 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. @@ -1469,42 +1470,134 @@ bool EscapeAnalysis::mergeSummaryGraph(ConnectionGraph *SummaryGraph, return SummaryGraph->mergeFrom(Graph, Mapping); } - bool EscapeAnalysis::canEscapeToUsePoint(SILValue V, ValueBase *UsePoint, ConnectionGraph *ConGraph) { - CGNode *Node = ConGraph->getNode(V, this); + + assert((FullApplySite::isa(UsePoint) || isa(UsePoint)) && + "use points are only created for calls and refcount instructions"); + + CGNode *Node = ConGraph->getNodeOrNull(V, this); if (!Node) - return false; + return true; // First check if there are escape pathes which we don't explicitly see // in the graph. - switch (Node->getEscapeState()) { - case EscapeState::None: - case EscapeState::Return: - break; - case EscapeState::Arguments: - if (!isNotAliasingArgument(V)) - return true; - break; - case EscapeState::Global: - return true; - } + if (Node->escapesInsideFunction(isNotAliasingArgument(V))) + return true; + // No hidden escapes: check if the Node is reachable from the UsePoint. return ConGraph->isUsePoint(UsePoint, Node); } -bool EscapeAnalysis::canPointToSameMemory(SILValue V1, SILValue V2, - ConnectionGraph *ConGraph) { - CGNode *Node1 = ConGraph->getNode(V1, this); - assert(Node1 && "value is not a pointer"); - CGNode *Node2 = ConGraph->getNode(V2, this); - assert(Node2 && "value is not a pointer"); +bool EscapeAnalysis::canEscapeTo(SILValue V, FullApplySite FAS) { + // If it's not a local object we don't know anything about the value. + if (!pointsToLocalObject(V)) + return true; + auto *ConGraph = getConnectionGraph(FAS.getFunction()); + return canEscapeToUsePoint(V, FAS.getInstruction(), ConGraph); +} - // If both nodes escape, the relation of the nodes may not be explicitly - // represented in the graph. - if (Node1->escapesInsideFunction() && Node2->escapesInsideFunction()) +bool EscapeAnalysis::canObjectOrContentEscapeTo(SILValue V, FullApplySite FAS) { + // If it's not a local object we don't know anything about the value. + if (!pointsToLocalObject(V)) return true; + auto *ConGraph = getConnectionGraph(FAS.getFunction()); + CGNode *Node = ConGraph->getNodeOrNull(V, this); + if (!Node) + return true; + + // First check if there are escape pathes which we don't explicitly see + // in the graph. + if (Node->escapesInsideFunction(isNotAliasingArgument(V))) + return true; + + // Check if the object itself can escape to the called function. + SILInstruction *UsePoint = FAS.getInstruction(); + if (ConGraph->isUsePoint(UsePoint, Node)) + return true; + + if (V.getType().hasReferenceSemantics()) { + // Check if the object "content", i.e. a pointer to one of its stored + // properties, can escape to the called function. + CGNode *ContentNode = ConGraph->getContentNode(Node); + if (ContentNode->escapesInsideFunction(false)) + return true; + + if (ConGraph->isUsePoint(UsePoint, ContentNode)) + return true; + } + return false; +} + +bool EscapeAnalysis::canEscapeTo(SILValue V, RefCountingInst *RI) { + // If it's not a local object we don't know anything about the value. + if (!pointsToLocalObject(V)) + return true; + auto *ConGraph = getConnectionGraph(RI->getFunction()); + return canEscapeToUsePoint(V, RI, ConGraph); +} + +/// Utility to get the function which contains both values \p V1 and \p V2. +static SILFunction *getCommonFunction(SILValue V1, SILValue V2) { + SILBasicBlock *BB1 = V1->getParentBB(); + SILBasicBlock *BB2 = V2->getParentBB(); + if (!BB1 || !BB2) + return nullptr; + + SILFunction *F = BB1->getParent(); + assert(BB2->getParent() == F && "values not in same function"); + return F; +} + +bool EscapeAnalysis::canEscapeToValue(SILValue V, SILValue To) { + if (!pointsToLocalObject(V)) + return true; + + SILFunction *F = getCommonFunction(V, To); + if (!F) + return true; + auto *ConGraph = getConnectionGraph(F); + + CGNode *Node = ConGraph->getNodeOrNull(V, this); + if (!Node) + return true; + CGNode *ToNode = ConGraph->getNodeOrNull(To, this); + if (!ToNode) + return true; + return ConGraph->isReachable(Node, ToNode); +} + +bool EscapeAnalysis::canPointToSameMemory(SILValue V1, SILValue V2) { + // At least one of the values must be a non-escaping local object. + bool isLocal1 = pointsToLocalObject(V1); + bool isLocal2 = pointsToLocalObject(V2); + if (!isLocal1 && !isLocal2) + return true; + + SILFunction *F = getCommonFunction(V1, V2); + if (!F) + return true; + auto *ConGraph = getConnectionGraph(F); + + CGNode *Node1 = ConGraph->getNodeOrNull(V1, this); + if (!Node1) + return true; + CGNode *Node2 = ConGraph->getNodeOrNull(V2, this); + if (!Node2) + return true; + + // Finish the check for one value being a non-escaping local object. + if (isLocal1 && Node1->escapesInsideFunction(isNotAliasingArgument(V1))) + isLocal1 = false; + + if (isLocal2 && Node2->escapesInsideFunction(isNotAliasingArgument(V2))) + isLocal2 = false; + + if (!isLocal1 && !isLocal2) + return true; + + // Check if both nodes may point to the same content. CGNode *Content1 = ConGraph->getContentNode(Node1); CGNode *Content2 = ConGraph->getContentNode(Node2); return Content1 == Content2; diff --git a/lib/SILOptimizer/Transforms/StackPromotion.cpp b/lib/SILOptimizer/Transforms/StackPromotion.cpp index d7e465847cf..247a03eefe3 100644 --- a/lib/SILOptimizer/Transforms/StackPromotion.cpp +++ b/lib/SILOptimizer/Transforms/StackPromotion.cpp @@ -290,7 +290,7 @@ bool StackPromoter::canPromoteAlloc(SILInstruction *AI, SILInstruction *&DeallocInsertionPoint) { AllocInsertionPoint = nullptr; DeallocInsertionPoint = nullptr; - auto *Node = ConGraph->getNode(AI, EA); + auto *Node = ConGraph->getNodeOrNull(AI, EA); if (!Node) return false;