Skip to content

EscapeAnalysis: make the use-point analysis more precise #28502

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 0 additions & 3 deletions include/swift/SILOptimizer/Analysis/EscapeAnalysis.h
Original file line number Diff line number Diff line change
Expand Up @@ -701,9 +701,6 @@ class EscapeAnalysis : public BottomUpIPAnalysis {

/// Adds an argument/instruction in which the node's value is used.
int addUsePoint(CGNode *Node, SILNode *User) {
if (Node->getEscapeState() >= EscapeState::Global)
return -1;

User = User->getRepresentativeSILNodeInObject();
int Idx = (int)UsePoints.size();
assert(UsePoints.count(User) == 0 && "value is already a use-point");
Expand Down
46 changes: 27 additions & 19 deletions lib/SILOptimizer/Analysis/EscapeAnalysis.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -856,8 +856,11 @@ void EscapeAnalysis::ConnectionGraph::computeUsePoints() {
Changed = false;
for (CGNode *Node : Nodes) {
// Propagate the bits to all successor nodes.
Node->visitSuccessors([&Changed, Node](CGNode *succ) {
Changed |= succ->mergeUsePoints(Node);
if (Node->pointsTo) {
Changed |= Node->pointsTo->mergeUsePoints(Node);
}
Node->visitDefers([&Changed, Node](CGNode *defer, bool) {
Changed |= defer->mergeUsePoints(Node);
return true;
});
}
Expand Down Expand Up @@ -2357,24 +2360,29 @@ bool EscapeAnalysis::canEscapeToUsePoint(SILValue V, SILNode *UsePoint,
if (ConGraph->isUsePoint(UsePoint, Node))
return true;

assert(isPointer(V) && "should not have a node for a non-pointer");

// Check if the object "content" can escape to the called function.
// This will catch cases where V is a reference and a pointer to a stored
// property escapes.
// It's also important in case of a pointer assignment, e.g.
// V = V1
// apply(V1)
// In this case the apply is only a use-point for V1 and V1's content node.
// As V1's content node is the same as V's content node, we also make the
// check for the content node.
CGNode *ContentNode = getValueContent(ConGraph, V);
if (ContentNode->escapesInsideFunction(V))
return true;

if (ConGraph->isUsePoint(UsePoint, ContentNode))
return true;
// TODO: should we just check the Node->hasRC flag here? Currently it is
// not safe because this flag may be conservatively false.
if (V->getType().hasReferenceSemantics()) {
// In case V is the result of getUnderlyingObject, we also have to check
// the content node. Example:
//
// %a = ref_element %r
// apply %f(%a)
//
// The reference %r does not actually escape to the function call. But in
// case this API is called like
//
// canEscapeToUsePoint(getUnderlyingObject(applyArgument))
//
// it would yield false, although the projected object address is passed to
// the function.
CGNode *ContentNode = getValueContent(ConGraph, V);
if (ContentNode->escapesInsideFunction(V))
return true;

if (ConGraph->isUsePoint(UsePoint, ContentNode))
return true;
}
return false;
}

Expand Down
5 changes: 3 additions & 2 deletions lib/SILOptimizer/Transforms/StackPromotion.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -127,8 +127,9 @@ bool StackPromotion::tryPromoteAlloc(AllocRefInst *ARI, EscapeAnalysis *EA,
if (SILInstruction *I = dyn_cast<SILInstruction>(UsePoint)) {
UsePoints.push_back(I);
} else {
// Also block arguments can be use points.
SILBasicBlock *UseBB = cast<SILPhiArgument>(UsePoint)->getParent();
// Also block arguments can be use points (even function arguments, as
// use-points are propagated backwards along defer edges).
SILBasicBlock *UseBB = cast<SILArgument>(UsePoint)->getParent();
// For simplicity we just add the first instruction of the block as use
// point.
UsePoints.push_back(&UseBB->front());
Expand Down
44 changes: 22 additions & 22 deletions test/SILOptimizer/escape_analysis.sil
Original file line number Diff line number Diff line change
Expand Up @@ -119,7 +119,7 @@ bb0(%0 : $*Y, %1 : $X):
// CHECK-LABEL: CG of deferringEdge
// CHECK-NEXT: Arg %0 Esc: A, Succ: ([rc] %4)
// CHECK-NEXT: Arg %1 Esc: A, Succ:
// CHECK-NEXT: Val %3 Esc: %3, Succ: ([rc] %4), %0
// CHECK-NEXT: Val %3 Esc: %0,%3, Succ: ([rc] %4), %0
// CHECK-NEXT: Con [rc] %4 Esc: A, Succ: (%4.1)
// CHECK-NEXT: Con %4.1 Esc: A, Succ: %1
// CHECK-NEXT: Ret return Esc: R, Succ: %0
Expand Down Expand Up @@ -153,8 +153,8 @@ bb0:
// CHECK-NEXT: Val %1 Esc: A, Succ: ([rc] %2)
// CHECK-NEXT: Con [rc] %2 Esc: A, Succ: (%13)
// CHECK-NEXT: Val %4 Esc: A, Succ: ([rc] %2)
// CHECK-NEXT: Val %7 Esc: %11, Succ: ([rc] %2)
// CHECK-NEXT: Val %11 Esc: %11, Succ: ([rc] %2), %7, %13
// CHECK-NEXT: Val %7 Esc: %0,%11, Succ: ([rc] %2)
// CHECK-NEXT: Val %11 Esc: %0,%11, Succ: ([rc] %2), %7, %13
// CHECK-NEXT: Con %13 Esc: A, Succ: %0, %1, %4
// CHECK-NEXT: Ret return Esc: R, Succ: %13
// CHECK-NEXT: End
Expand Down Expand Up @@ -212,7 +212,7 @@ bb0(%0 : $LinkedNode):

// CHECK-LABEL: CG of loadNext
// CHECK-NEXT: Arg %0 Esc: A, Succ: ([rc] %3)
// CHECK-NEXT: Val %2 Esc: %2, Succ: ([rc] %3), %0, %4
// CHECK-NEXT: Val %2 Esc: %0,%2, Succ: ([rc] %3), %0, %4
// CHECK-NEXT: Con [rc] %3 Esc: A, Succ: (%4)
// CHECK-NEXT: Con %4 Esc: A, Succ: ([rc] %3)
// CHECK-NEXT: Ret return Esc: R, Succ: %4
Expand Down Expand Up @@ -407,11 +407,11 @@ sil @call_copy_addr_content : $@convention(thin) () -> () {
// CHECK-NEXT: Arg %1 Esc: G, Succ:
// CHECK-NEXT: Arg %2 Esc: A, Succ: (%2.1)
// CHECK-NEXT: Con %2.1 Esc: G, Succ:
// CHECK-NEXT: Val %3 Esc: %14,%15,%17, Succ: ([rc] %7)
// CHECK-NEXT: Val %6 Esc: %14,%15,%16, Succ: ([rc] %7)
// CHECK-NEXT: Val %3 Esc: %14,%15,%16,%17, Succ: ([rc] %7)
// CHECK-NEXT: Val %6 Esc: %14,%15,%16,%17, Succ: ([rc] %7)
// CHECK-NEXT: Con [rc] %7 Esc: %14,%15,%16,%17, Succ: (%7.1)
// CHECK-NEXT: Con %7.1 Esc: %14,%15,%16,%17, Succ: %2
// CHECK-NEXT: Val %12 Esc: %14,%15, Succ: %3, %6
// CHECK-NEXT: Con %7.1 Esc: %2,%14,%15,%16,%17, Succ: %2
// CHECK-NEXT: Val %12 Esc: %14,%15,%16,%17, Succ: %3, %6
// CHECK-NEXT: End
sil @test_partial_apply : $@convention(thin) (Int64, @owned X, @owned Y) -> Int64 {
bb0(%0 : $Int64, %1 : $X, %2 : $Y):
Expand Down Expand Up @@ -443,7 +443,7 @@ bb0(%0 : $Int64, %1 : $X, %2 : $Y):
// CHECK-NEXT: Con [rc] %4 Esc: A, Succ: (%4.1)
// CHECK-NEXT: Con %4.1 Esc: A, Succ: ([rc] %4.2)
// CHECK-NEXT: Con [rc] %4.2 Esc: G, Succ:
// CHECK-NEXT: Val %7 Esc: %8, Succ: %2
// CHECK-NEXT: Val %7 Esc: %2,%8, Succ: %2
// CHECK-NEXT: End
sil @closure1 : $@convention(thin) (@owned X, @owned <τ_0_0> { var τ_0_0 } <Int64>, @owned <τ_0_0> { var τ_0_0 } <Y>) -> Int64 {
bb0(%0 : $X, %1 : $<τ_0_0> { var τ_0_0 } <Int64>, %2 : $<τ_0_0> { var τ_0_0 } <Y>):
Expand Down Expand Up @@ -762,7 +762,7 @@ sil @unknown_throwing_func : $@convention(thin) (@owned X) -> (@owned X, @error
// CHECK-NEXT: Con %0.1 Esc: G, Succ:
// CHECK-NEXT: Val %1 Esc: %6, Succ: ([rc] %2)
// CHECK-NEXT: Con [rc] %2 Esc: %6, Succ: (%2.1)
// CHECK-NEXT: Con %2.1 Esc: %6, Succ: %0
// CHECK-NEXT: Con %2.1 Esc: %0,%6, Succ: %0
// CHECK-NEXT: Val %5 Esc: %6, Succ: %1
// CHECK-NEXT: End
sil @test_release_of_partial_apply_with_box : $@convention(thin) (@owned Y) -> () {
Expand Down Expand Up @@ -970,7 +970,7 @@ bb0(%0 : $Builtin.Int64, %1 : $X, %2 : $X, %3 : $X):
// CHECK-NEXT: Arg %0 Esc: A, Succ:
// CHECK-NEXT: Val %1 Esc: , Succ: (%2)
// CHECK-NEXT: Con %2 Esc: , Succ: (%2.1)
// CHECK-NEXT: Con %2.1 Esc: , Succ: %0
// CHECK-NEXT: Con %2.1 Esc: %0, Succ: %0
// CHECK-NEXT: End
sil @test_existential_addr : $@convention(thin) (@owned Pointer) -> () {
bb0(%0 : $Pointer):
Expand Down Expand Up @@ -1076,9 +1076,9 @@ sil_global @global_y : $SomeData
// CHECK-LABEL: CG of test_node_merge_during_struct_inst
// CHECK-NEXT: Arg %0 Esc: A, Succ: (%8)
// CHECK-NEXT: Val %1 Esc: G, Succ: (%8)
// CHECK-NEXT: Val %4 Esc: , Succ: (%8)
// CHECK-NEXT: Val %4 Esc: %0, Succ: (%8)
// CHECK-NEXT: Con %8 Esc: G, Succ: (%8), %1
// CHECK-NEXT: Val %10 Esc: , Succ: %0, %4, %8
// CHECK-NEXT: Val %10 Esc: %0, Succ: %0, %4, %8
// CHECK-NEXT: End
sil @test_node_merge_during_struct_inst : $@convention(thin) (Y) -> () {
bb0(%0 : $Y):
Expand Down Expand Up @@ -1183,7 +1183,7 @@ bb0(%0 : $*Array<X>):
// CHECK-LABEL: CG of arraysemantics_get_element_address
// CHECK-NEXT: Arg %0 Esc: A, Succ: ([rc] %0.1)
// CHECK-NEXT: Con [rc] %0.1 Esc: A, Succ:
// CHECK-NEXT: Val %4 Esc: , Succ: [rc] %0.1
// CHECK-NEXT: Val %4 Esc: %0,%4, Succ: [rc] %0.1
// CHECK-NEXT: End
sil @arraysemantics_get_element_address : $@convention(thin) (Array<X>) -> () {
bb0(%0 : $Array<X>):
Expand Down Expand Up @@ -1540,10 +1540,10 @@ bb0:
// CHECK-NEXT: Arg %0 Esc: A, Succ: ([rc] %0.1)
// CHECK-NEXT: Con [rc] %0.1 Esc: A, Succ: (%0.2)
// CHECK-NEXT: Con %0.2 Esc: A, Succ: ([rc] %13)
// CHECK-NEXT: Val %2 Esc: %4, Succ: %0.2
// CHECK-NEXT: Val %4 Esc: %4, Succ: %2
// CHECK-NEXT: Val %7 Esc: %12, Succ: ([rc] %13), %0.2
// CHECK-NEXT: Val %12 Esc: %12, Succ: ([rc] %13), %7
// CHECK-NEXT: Val %2 Esc: %0,%2,%4,%7,%12, Succ: %0.2
// CHECK-NEXT: Val %4 Esc: %0,%2,%4,%7,%12, Succ: %2
// CHECK-NEXT: Val %7 Esc: %0,%2,%4,%7,%12, Succ: ([rc] %13), %0.2
// CHECK-NEXT: Val %12 Esc: %0,%2,%4,%7,%12, Succ: ([rc] %13), %7
// CHECK-NEXT: Con [rc] %13 Esc: A, Succ: (%14)
// CHECK-NEXT: Con %14 Esc: A, Succ:
// CHECK-LABEL: End
Expand Down Expand Up @@ -1597,10 +1597,10 @@ bb10(%arg2 : $LinkedNode):
// CHECK-NEXT: Arg %1 Esc: A, Succ: ([rc] %2)
// CHECK-NEXT: Con [rc] %2 Esc: A, Succ: (%3)
// CHECK-NEXT: Con %3 Esc: A, Succ:
// CHECK-NEXT: Val %7 Esc: %7,%18, Succ: %0
// CHECK-NEXT: Val %12 Esc: %12,%14,%18, Succ: %1
// CHECK-NEXT: Val %14 Esc: %18, Succ: ([rc] %2), %1, %12
// CHECK-NEXT: Val %18 Esc: %18, Succ: %7, %14
// CHECK-NEXT: Val %7 Esc: %0,%1,%7,%12,%14,%18, Succ: %0
// CHECK-NEXT: Val %12 Esc: %0,%1,%7,%12,%14,%18, Succ: %1
// CHECK-NEXT: Val %14 Esc: %0,%1,%7,%12,%14,%18, Succ: ([rc] %2), %1, %12
// CHECK-NEXT: Val %18 Esc: %0,%1,%7,%12,%14,%18, Succ: %7, %14
// CHECK-LABEL: End
sil @testInitializePointsToMerge : $@convention(method) (@guaranteed C, @guaranteed C) -> C {
bb0(%0: $C, %1 : $C):
Expand Down
30 changes: 30 additions & 0 deletions test/SILOptimizer/redundant_load_elim.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
// RUN: %target-swift-frontend -O -parse-as-library -module-name=test -emit-sil %s | %FileCheck %s

final class X {}

public struct S {
var x: X
var i: Int
}

var gg = X()

@inline(never)
func takex(_ x: X) {
gg = x
}

// Test if escape analysis is able to handle inout parameters, containing
// references, correctly.

// CHECK-LABEL: sil @$s4test6testityyAA1SVz_ADtF
// CHECK-NOT: store
// CHECK: apply
// CHECK: store
// CHECK-NOT: store
// CHECK: } // end sil function '$s4test6testityyAA1SVz_ADtF'
public func testit(_ s: inout S, _ s1: S) {
s = s1 // this store should be eliminated, even if s.x escapes.
takex(s.x)
s = s1
}