Skip to content

Commit 659a37c

Browse files
committed
Rewrite AliasAnalysis may-release/may-decrement queries.
Use the new EscapeAnalysis infrastructure to make ARC code motion and ARC sequence opts much more powerful and fix a latent bug in AliasAnalysis. Adds a new API `EscapeAnalysis::mayReleaseContent()`. This replaces all uses if `EscapeAnalysis::canEscapeValueTo()`, which affects `AliasAnalysis::can[Apply|Builtin]DecrementRefCount()`. Also rewrite `AliasAnalysis::mayValueReleaseInterferWithInstruction` to directly use `EscapeAnalysis::mayReleaseContent`. The new implementation in `EscapeAnalysis::mayReleaseContent()` generalizes the logic to handle more cases while avoiding an incorrect assumption in the prior code. In particular, it adds support for disambiguating local references from accessed addresses. This helps handle cases in which inlining was defeating ARC optimization. The incorrect assumption was that a non-escaping address is never reachable via a reference. However, if a reference does not escape, then an address into its object also does not escape. The bug in `AliasAnalysis::mayValueReleaseInterfereWithInstruction()` appears not to have broken anything yet because it is always called by `AliasAnalysis::mayHaveSymmetricInteference()`, which later checks whether the accessed address may alias with the released reference using a separate query, `EscapeAnalysis::canPointToSameMemory()`. This happens to work because an address into memory that is directly released when destroying a reference necesasarilly points to the same memory object. For this reason, I couldn't figure out a simple way to hand-code SIL tests to expose this bug. The changes in diff order: Replace EscapeAnalysis `canEscapeToValue` with `mayReleaseContent` to make the semantics clear. It queries: "Can the given reference release the content pointed to the given address". Change `AliasAnalysis::canApplyDecrementRefCount` to use `mayReleaseContent` instead if 'canEscapeToValue'. Change `AliasAnalysis::mayValueReleaseInterferWithInstruction`: after getting the memory address accessed by the instruction, simply call `EscapeAnalysis::mayReleaseContent`, which now implements all the logic. This avoids the bad assumption made by AliasAnalysis. Handle two cases in mayReleaseContent: non-escaping instruction addresses and non-escaping referenecs. Fix the non-escaping address case by following all content nodes to determine whether the address is reachable from the released reference. Introduce a new optimization for the case in which the reference being released is allocated locally. The following test case is now optimized in arcsequenceopts.sil: remove_as_local_object_indirectly_escapes_to_callee. It was trying to test that ARC optimization was not too aggressive when it removed a retain/release of a child object whose parent container is still in use. But the retain/release should be removed. The original example already over-releases the parent object. Add new unit tests to late_release_hoisting.sil.
1 parent c5a22e1 commit 659a37c

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)