Skip to content

Commit b770650

Browse files
authored
Merge pull request swiftlang#28971 from atrick/escape-aliasanalysis
Rewrite AliasAnalysis may-release/may-decrement queries.
2 parents 3fa5011 + 659a37c commit b770650

File tree

7 files changed

+467
-133
lines changed

7 files changed

+467
-133
lines changed

include/swift/SILOptimizer/Analysis/AliasAnalysis.h

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -194,10 +194,10 @@ class AliasAnalysis : public SILAnalysis {
194194
return alias(V1, V2, TBAAType1, TBAAType2) == AliasResult::MayAlias;
195195
}
196196

197-
/// \returns True if the release of the \p Ptr can access memory accessed by
198-
/// \p User.
197+
/// \returns True if the release of the \p releasedReference can access or
198+
/// free memory accessed by \p User.
199199
bool mayValueReleaseInterfereWithInstruction(SILInstruction *User,
200-
SILValue Ptr);
200+
SILValue releasedReference);
201201

202202
/// Use the alias analysis to determine the memory behavior of Inst with
203203
/// respect to V.

include/swift/SILOptimizer/Analysis/EscapeAnalysis.h

Lines changed: 8 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -672,10 +672,10 @@ class EscapeAnalysis : public BottomUpIPAnalysis {
672672
/// isInterior is always false for non-content nodes and is set for content
673673
/// nodes based on the type and origin of the pointer.
674674
CGNode *allocNode(ValueBase *V, NodeType Type, bool isInterior,
675-
bool isReference) {
675+
bool hasReferenceOnly) {
676676
assert((Type == NodeType::Content) || !isInterior);
677677
CGNode *Node = new (NodeAllocator.Allocate())
678-
CGNode(V, Type, isInterior, isReference);
678+
CGNode(V, Type, isInterior, hasReferenceOnly);
679679
Nodes.push_back(Node);
680680
return Node;
681681
}
@@ -871,10 +871,6 @@ class EscapeAnalysis : public BottomUpIPAnalysis {
871871
template <typename CGNodeVisitor>
872872
bool forwardTraverseDefer(CGNode *startNode, CGNodeVisitor &&visitor);
873873

874-
/// Return true if \p pointer may indirectly point to \pointee via pointers
875-
/// and object references.
876-
bool mayReach(CGNode *pointer, CGNode *pointee);
877-
878874
public:
879875
/// Gets or creates a node for a value \p V.
880876
/// If V is a projection(-path) then the base of the projection(-path) is
@@ -1164,16 +1160,16 @@ class EscapeAnalysis : public BottomUpIPAnalysis {
11641160
/// Note that if \p RI is a retain-instruction always false is returned.
11651161
bool canEscapeTo(SILValue V, RefCountingInst *RI);
11661162

1167-
/// Returns true if the value \p V can escape to any other pointer \p To.
1168-
/// This means that either \p To is the same as \p V or contains a reference
1169-
/// to \p V.
1170-
bool canEscapeToValue(SILValue V, SILValue To);
1163+
/// Return true if \p releasedReference deinitialization may release memory
1164+
/// pointed to by \p accessedAddress.
1165+
bool mayReleaseContent(SILValue releasedReference, SILValue accessedAddress);
11711166

11721167
/// Returns true if the pointers \p V1 and \p V2 can possibly point to the
11731168
/// same memory.
11741169
///
1175-
/// If at least one of the pointers refers to a local object and the
1176-
/// connection-graph-nodes of both pointers do not point to the same content
1170+
/// First checks that the pointers are known not to alias outside this
1171+
/// function, then checks the connection graph to determine that their content
1172+
/// is not in the same points-to chain based on access inside this function.
11771173
bool canPointToSameMemory(SILValue V1, SILValue V2);
11781174

11791175
/// Invalidate all information in this analysis.

lib/SILOptimizer/Analysis/AliasAnalysis.cpp

Lines changed: 34 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -678,7 +678,7 @@ bool AliasAnalysis::canApplyDecrementRefCount(FullApplySite FAS, SILValue Ptr) {
678678
if (ArgEffect.mayRelease()) {
679679
// The function may release this argument, so check if the pointer can
680680
// escape to it.
681-
if (EA->canEscapeToValue(Ptr, FAS.getArgument(Idx)))
681+
if (EA->mayReleaseContent(FAS.getArgument(Idx), Ptr))
682682
return true;
683683
}
684684
}
@@ -696,54 +696,51 @@ bool AliasAnalysis::canBuiltinDecrementRefCount(BuiltinInst *BI, SILValue Ptr) {
696696

697697
// A builtin can only release an object if it can escape to one of the
698698
// builtin's arguments.
699-
if (EA->canEscapeToValue(Ptr, Arg))
699+
if (EA->mayReleaseContent(Arg, Ptr))
700700
return true;
701701
}
702702
return false;
703703
}
704704

705+
// If the deinit for releasedReference can release any values used by User, then
706+
// this is an interference. (The retains that originally forced liveness of
707+
// those values may have already been eliminated). Note that we only care about
708+
// avoiding a dangling pointer. The memory side affects of Release are
709+
// unordered.
710+
//
711+
// \p releasedReference must be a value that directly contains the references
712+
// being released. It cannot be an address or other kind of pointer that
713+
// indirectly releases a reference. Otherwise, the escape analysis query is
714+
// invalid.
715+
bool AliasAnalysis::mayValueReleaseInterfereWithInstruction(
716+
SILInstruction *User, SILValue releasedReference) {
717+
assert(!releasedReference->getType().isAddress()
718+
&& "an address is never a reference");
705719

706-
bool AliasAnalysis::mayValueReleaseInterfereWithInstruction(SILInstruction *User,
707-
SILValue Ptr) {
708-
// TODO: Its important to make this as precise as possible.
709-
//
710-
// TODO: Eventually we can plug in some analysis on the what the release of
711-
// the Ptr can do, i.e. be more precise about Ptr's deinit.
712-
//
713-
// TODO: If we know the specific release instruction, we can potentially do
714-
// more.
715-
//
716720
// If this instruction can not read or write any memory. Its OK.
717721
if (!User->mayReadOrWriteMemory())
718722
return false;
719723

720-
// These instructions do read or write memory, get memory directly
721-
// accessed. 'V' must be the only memory accessed by User, and it must be
722-
// directly accessed. Any memory indirectly accessed via 'User' may have
723-
// escaped.
724-
SILValue V = getDirectlyAccessedMemory(User);
725-
if (!V)
726-
return true;
727-
728-
// If the 'User' instruction's memory is uniquely identified and does not
729-
// escape in the local scope, then it can't be accessed by a deinit in the
730-
// local scope. Note that an exclusive argument's content may have escaped in
731-
// the caller, but the argument value itself can't be accessed via aliasing
732-
// references and we know that User doesn't see through any indirection.
733-
if (!isUniquelyIdentified(V))
724+
// Get a pointer to the memory directly accessed by 'Users' (either via an
725+
// address or heap reference operand). If additional memory may be indirectly
726+
// accessed by 'User', such as via an inout argument, then stop here because
727+
// mayReleaseContent can only reason about one level of memory access.
728+
//
729+
// TODO: Handle @inout arguments by iterating over the apply arguments. For
730+
// each argument find out if any reachable content can be released. This is
731+
// slightly more involved than mayReleaseContent because it needs to check all
732+
// connection graph nodes reachable from accessedPointer that don't pass
733+
// through another stored reference.
734+
SILValue accessedPointer = getDirectlyAccessedMemory(User);
735+
if (!accessedPointer)
734736
return true;
735737

736-
// This is a scoped allocation.
737-
// The most important check: does the object escape the current function?
738-
auto LO = getUnderlyingObject(V);
739-
auto *ConGraph = EA->getConnectionGraph(User->getFunction());
740-
auto *Content = ConGraph->getValueContent(LO);
741-
if (Content && !Content->escapes())
742-
return false;
743-
744-
// This is either a non-local allocation or a scoped allocation that escapes.
745-
// We failed to prove anything, it could be read or written by the deinit.
746-
return true;
738+
// If releasedReference can reach the first refcounted object reachable from
739+
// accessedPointer, then releasing it early may destroy the object accessed by
740+
// accessedPointer. Access to any objects beyond the first released refcounted
741+
// object are irrelevant--they must already have sufficient refcount that they
742+
// won't be released when releasing Ptr.
743+
return EA->mayReleaseContent(releasedReference, accessedPointer);
747744
}
748745

749746
bool swift::isLetPointer(SILValue V) {

lib/SILOptimizer/Analysis/EscapeAnalysis.cpp

Lines changed: 110 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -72,7 +72,8 @@ EscapeAnalysis::findRecursivePointerKind(SILType Ty,
7272
};
7373
if (auto *Str = Ty.getStructOrBoundGenericStruct()) {
7474
for (auto *Field : Str->getStoredProperties()) {
75-
SILType fieldTy = Ty.getFieldType(Field, M, F.getTypeExpansionContext());
75+
SILType fieldTy = Ty.getFieldType(Field, M, F.getTypeExpansionContext())
76+
.getObjectType();
7677
meetAggregateKind(findCachedPointerKind(fieldTy, F));
7778
}
7879
return aggregateKind;
@@ -925,8 +926,10 @@ EscapeAnalysis::ConnectionGraph::getOrCreateReferenceContent(SILValue refVal,
925926
if (auto *C = refType.getClassOrBoundGenericClass()) {
926927
PointerKind aggregateKind = NoPointer;
927928
for (auto *field : C->getStoredProperties()) {
928-
SILType fieldType = refType.getFieldType(field, F->getModule(),
929-
F->getTypeExpansionContext());
929+
SILType fieldType = refType
930+
.getFieldType(field, F->getModule(),
931+
F->getTypeExpansionContext())
932+
.getObjectType();
930933
PointerKind fieldKind = EA->findCachedPointerKind(fieldType, *F);
931934
if (fieldKind > aggregateKind)
932935
aggregateKind = fieldKind;
@@ -1161,19 +1164,6 @@ bool EscapeAnalysis::ConnectionGraph::forwardTraverseDefer(
11611164
return true;
11621165
}
11631166

1164-
bool EscapeAnalysis::ConnectionGraph::mayReach(CGNode *pointer,
1165-
CGNode *pointee) {
1166-
if (pointer == pointee)
1167-
return true;
1168-
1169-
// This query is successful when the traversal halts and returns false.
1170-
return !backwardTraverse(pointee, [pointer](Predecessor pred) {
1171-
if (pred.getPredNode() == pointer)
1172-
return Traversal::Halt;
1173-
return Traversal::Follow;
1174-
});
1175-
}
1176-
11771167
void EscapeAnalysis::ConnectionGraph::removeFromGraph(ValueBase *V) {
11781168
CGNode *node = Values2Nodes.lookup(V);
11791169
if (!node)
@@ -1193,10 +1183,7 @@ void EscapeAnalysis::ConnectionGraph::removeFromGraph(ValueBase *V) {
11931183
/// This makes iterating over the edges easier.
11941184
struct CGForDotView {
11951185

1196-
enum EdgeTypes {
1197-
PointsTo,
1198-
Deferred
1199-
};
1186+
enum EdgeTypes { PointsTo, Reference, Deferred };
12001187

12011188
struct Node {
12021189
EscapeAnalysis::CGNode *OrigNode;
@@ -1248,7 +1235,10 @@ CGForDotView::CGForDotView(const EscapeAnalysis::ConnectionGraph *CG) :
12481235
Nd.OrigNode = OrigNode;
12491236
if (auto *PT = OrigNode->getPointsToEdge()) {
12501237
Nd.Children.push_back(Orig2Node[PT]);
1251-
Nd.ChildrenTypes.push_back(PointsTo);
1238+
if (OrigNode->hasReferenceOnly())
1239+
Nd.ChildrenTypes.push_back(Reference);
1240+
else
1241+
Nd.ChildrenTypes.push_back(PointsTo);
12521242
}
12531243
for (auto *Def : OrigNode->defersTo) {
12541244
Nd.Children.push_back(Orig2Node[Def]);
@@ -1396,8 +1386,12 @@ namespace llvm {
13961386
const CGForDotView *Graph) {
13971387
unsigned ChildIdx = I - Node->Children.begin();
13981388
switch (Node->ChildrenTypes[ChildIdx]) {
1399-
case CGForDotView::PointsTo: return "";
1400-
case CGForDotView::Deferred: return "color=\"gray\"";
1389+
case CGForDotView::PointsTo:
1390+
return "";
1391+
case CGForDotView::Reference:
1392+
return "color=\"green\"";
1393+
case CGForDotView::Deferred:
1394+
return "color=\"gray\"";
14011395
}
14021396

14031397
llvm_unreachable("Unhandled CGForDotView in switch.");
@@ -2570,24 +2564,6 @@ static SILFunction *getCommonFunction(SILValue V1, SILValue V2) {
25702564
return F;
25712565
}
25722566

2573-
bool EscapeAnalysis::canEscapeToValue(SILValue V, SILValue To) {
2574-
if (!isUniquelyIdentified(V))
2575-
return true;
2576-
2577-
SILFunction *F = getCommonFunction(V, To);
2578-
if (!F)
2579-
return true;
2580-
auto *ConGraph = getConnectionGraph(F);
2581-
2582-
CGNode *valueContent = ConGraph->getValueContent(V);
2583-
if (!valueContent)
2584-
return true;
2585-
CGNode *userContent = ConGraph->getValueContent(To);
2586-
if (!userContent)
2587-
return true;
2588-
return ConGraph->mayReach(userContent, valueContent);
2589-
}
2590-
25912567
bool EscapeAnalysis::canPointToSameMemory(SILValue V1, SILValue V2) {
25922568
// At least one of the values must be a non-escaping local object.
25932569
bool isUniq1 = isUniquelyIdentified(V1);
@@ -2642,6 +2618,99 @@ bool EscapeAnalysis::canPointToSameMemory(SILValue V1, SILValue V2) {
26422618
return true;
26432619
}
26442620

2621+
// Return true if deinitialization of \p releasedReference may release memory
2622+
// directly pointed to by \p accessAddress.
2623+
//
2624+
// Note that \p accessedAddress could be a reference itself, an address of a
2625+
// local/argument that contains a reference, or even a pointer to the middle of
2626+
// an object (even if it is an exclusive argument).
2627+
//
2628+
// This is almost the same as asking "is the content node for accessedAddress
2629+
// reachable via releasedReference", with three subtle differences:
2630+
//
2631+
// (1) A locally referenced object can only be freed when deinitializing
2632+
// releasedReference if it is the same object. Indirect references will be kept
2633+
// alive by their distinct local references--ARC can't remove those without
2634+
// inserting a mark_dependence/end_dependence scope.
2635+
//
2636+
// (2) the content of exclusive arguments may be indirectly reachable via
2637+
// releasedReference, but the exclusive argument must have it's own reference
2638+
// count, so cannot be freed via the locally released reference.
2639+
//
2640+
// (3) Objects may contain raw pointers into themselves or into other
2641+
// objects. Any access to the raw pointer is not considered a use of the object
2642+
// because that access must be "guarded" by a fix_lifetime or
2643+
// mark_dependence/end_dependence that acts as a placeholder.
2644+
//
2645+
// There are two interesting cases in which a connection graph query can
2646+
// determine that the accessed memory cannot be released:
2647+
//
2648+
// Case #1: accessedAddress points to a uniquely identified object that does not
2649+
// escape within this function.
2650+
//
2651+
// Note: A "uniquely identified object" is either a locally allocated object,
2652+
// which is obviously not reachable outside this function, or an exclusive
2653+
// address argument, which *is* reachable outside this function, but must
2654+
// have its own reference count so cannot be released locally.
2655+
//
2656+
// Case #2: The released reference points to a local object and no connection
2657+
// graph path exists from the referenced object to a global-escaping or
2658+
// argument-escaping node without traversing a non-interior edge.
2659+
//
2660+
// In both cases, the connection graph is sufficient to determine if the
2661+
// accessed content may be released. To prove that the accessed memory is
2662+
// distinct from any released memory it is now sufficient to check that no
2663+
// connection graph path exists from the released object's node to the accessed
2664+
// content node without traversing a non-interior edge.
2665+
bool EscapeAnalysis::mayReleaseContent(SILValue releasedReference,
2666+
SILValue accessedAddress) {
2667+
assert(!releasedReference->getType().isAddress()
2668+
&& "an address is never a reference");
2669+
2670+
SILFunction *f = getCommonFunction(releasedReference, accessedAddress);
2671+
if (!f)
2672+
return true;
2673+
2674+
auto *conGraph = getConnectionGraph(f);
2675+
2676+
CGNode *addrContentNode = conGraph->getValueContent(accessedAddress);
2677+
if (!addrContentNode)
2678+
return true;
2679+
2680+
// Case #1: Unique accessedAddress whose content does not escape.
2681+
bool isAccessUniq =
2682+
isUniquelyIdentified(accessedAddress)
2683+
&& !addrContentNode->valueEscapesInsideFunction(accessedAddress);
2684+
2685+
// Case #2: releasedReference points to a local object.
2686+
if (!isAccessUniq && !pointsToLocalObject(releasedReference))
2687+
return true;
2688+
2689+
CGNode *releasedObjNode = conGraph->getValueContent(releasedReference);
2690+
// Make sure we have at least one value CGNode for releasedReference.
2691+
if (!releasedObjNode)
2692+
return true;
2693+
2694+
// Check for reachability from releasedObjNode to addrContentNode.
2695+
// A pointsTo cycle is equivalent to a null pointsTo.
2696+
CGNodeWorklist worklist(conGraph);
2697+
for (CGNode *releasedNode = releasedObjNode;
2698+
releasedNode && worklist.tryPush(releasedNode);
2699+
releasedNode = releasedNode->getContentNodeOrNull()) {
2700+
// A path exists from released content to accessed content.
2701+
if (releasedNode == addrContentNode)
2702+
return true;
2703+
2704+
// A path exists to an escaping node.
2705+
if (!isAccessUniq && releasedNode->escapesInsideFunction())
2706+
return true;
2707+
2708+
if (!releasedNode->isInterior())
2709+
break;
2710+
}
2711+
return false; // no path to escaping memory that may be freed.
2712+
}
2713+
26452714
void EscapeAnalysis::invalidate() {
26462715
Function2Info.clear();
26472716
Allocator.DestroyAll();

0 commit comments

Comments
 (0)