Skip to content

Commit 3bf1f0d

Browse files
authored
Merge pull request #8591 from gottesmm/semantic_arc_opts_copy_of_copy
2 parents 3c43909 + 2224584 commit 3bf1f0d

File tree

3 files changed

+173
-9
lines changed

3 files changed

+173
-9
lines changed

lib/SILGen/SILGenApply.cpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1953,7 +1953,7 @@ static void beginInOutFormalAccesses(SILGenFunction &SGF,
19531953

19541954
llvm_unreachable("ran out of null arguments before we ran out of inouts");
19551955

1956-
done:
1956+
done:
19571957

19581958
// Check to see if we have multiple inout arguments which obviously
19591959
// alias. Note that we could do this in a later SILDiagnostics pass

lib/SILOptimizer/Mandatory/SemanticARCOpts.cpp

Lines changed: 91 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,8 @@
1414
#include "swift/SIL/OwnershipChecker.h"
1515
#include "swift/SIL/SILArgument.h"
1616
#include "swift/SIL/SILInstruction.h"
17+
#include "swift/SIL/TransitivelyUnreachableBlocks.h"
18+
#include "swift/SILOptimizer/Analysis/PostOrderAnalysis.h"
1719
#include "swift/SILOptimizer/PassManager/Passes.h"
1820
#include "swift/SILOptimizer/PassManager/Transforms.h"
1921
#include "llvm/ADT/SmallPtrSet.h"
@@ -24,20 +26,21 @@ using namespace swift;
2426

2527
STATISTIC(NumEliminatedInsts, "number of removed instructions");
2628

27-
static bool optimizeGuaranteedArgument(SILArgument *Arg) {
29+
static bool optimizeGuaranteedArgument(SILArgument *Arg,
30+
OwnershipChecker &Checker) {
2831
bool MadeChange = false;
2932

3033
// Gather all copy_value users of Arg.
31-
llvm::SmallVector<CopyValueInst *, 4> Copies;
34+
llvm::SmallVector<CopyValueInst *, 4> Worklist;
3235
for (auto *Op : Arg->getUses()) {
3336
if (auto *CVI = dyn_cast<CopyValueInst>(Op->getUser())) {
34-
Copies.push_back(CVI);
37+
Worklist.push_back(CVI);
3538
}
3639
}
3740

3841
// Then until we run out of copies...
39-
while (!Copies.empty()) {
40-
auto *CVI = Copies.pop_back_val();
42+
while (!Worklist.empty()) {
43+
auto *CVI = Worklist.pop_back_val();
4144

4245
// Quickly see if copy has only one use and that use is a destroy_value. In
4346
// such a case, we can always eliminate both the copy and the destroy.
@@ -49,6 +52,84 @@ static bool optimizeGuaranteedArgument(SILArgument *Arg) {
4952
continue;
5053
}
5154
}
55+
56+
// Ok, now run the checker on the copy value. If it fails, then we just
57+
// continue.
58+
if (!Checker.checkValue(CVI))
59+
continue;
60+
61+
// Otherwise, lets do a quick check on what the checker thinks the lifetime
62+
// ending and non-lifetime ending users. To be conservative, we bail unless
63+
// each lifetime ending use is a destroy_value and if each non-lifetime
64+
// ending use is one of the following instructions:
65+
//
66+
// 1. copy_value.
67+
// 2. begin_borrow.
68+
// 3. end_borrow.
69+
if (!all_of(Checker.LifetimeEndingUsers, [](SILInstruction *I) -> bool {
70+
return isa<DestroyValueInst>(I);
71+
}))
72+
continue;
73+
74+
// Extra copy values that we should visit recursively.
75+
llvm::SmallVector<CopyValueInst *, 8> NewCopyInsts;
76+
llvm::SmallVector<SILInstruction *, 8> NewBorrowInsts;
77+
if (!all_of(Checker.RegularUsers, [&](SILInstruction *I) -> bool {
78+
if (auto *CVI = dyn_cast<CopyValueInst>(I)) {
79+
NewCopyInsts.push_back(CVI);
80+
return true;
81+
}
82+
83+
if (!isa<BeginBorrowInst>(I) && !isa<EndBorrowInst>(I))
84+
return false;
85+
86+
NewBorrowInsts.push_back(I);
87+
return true;
88+
}))
89+
continue;
90+
91+
// Ok! we can remove the copy_value, destroy_values!
92+
MadeChange = true;
93+
CVI->replaceAllUsesWith(CVI->getOperand());
94+
CVI->eraseFromParent();
95+
++NumEliminatedInsts;
96+
97+
while (!Checker.LifetimeEndingUsers.empty()) {
98+
Checker.LifetimeEndingUsers.pop_back_val()->eraseFromParent();
99+
++NumEliminatedInsts;
100+
}
101+
102+
// Then add the copy_values that were users of our original copy value to
103+
// the worklist.
104+
while (!NewCopyInsts.empty()) {
105+
Worklist.push_back(NewCopyInsts.pop_back_val());
106+
}
107+
108+
// Then remove any begin/end borrow that we found. These are unneeded since
109+
// the lifetime guarantee from the argument exists above and beyond said
110+
// scope.
111+
while (!NewBorrowInsts.empty()) {
112+
SILInstruction *I = NewBorrowInsts.pop_back_val();
113+
if (auto *BBI = dyn_cast<BeginBorrowInst>(I)) {
114+
// Any copy_value that is used by the begin borrow is added to the
115+
// worklist.
116+
for (auto *BBIUse : BBI->getUses()) {
117+
if (auto *BBIUseCopyValue =
118+
dyn_cast<CopyValueInst>(BBIUse->getUser())) {
119+
Worklist.push_back(BBIUseCopyValue);
120+
}
121+
}
122+
BBI->replaceAllUsesWith(BBI->getOperand());
123+
BBI->eraseFromParent();
124+
++NumEliminatedInsts;
125+
continue;
126+
}
127+
128+
// This is not necessary, but it does add a check.
129+
auto *EBI = cast<EndBorrowInst>(I);
130+
EBI->eraseFromParent();
131+
++NumEliminatedInsts;
132+
}
52133
}
53134

54135
return MadeChange;
@@ -65,6 +146,10 @@ struct SemanticARCOpts : SILFunctionTransform {
65146
bool MadeChange = false;
66147
SILFunction *F = getFunction();
67148

149+
auto *PO = PM->getAnalysis<PostOrderAnalysis>()->get(F);
150+
TransitivelyUnreachableBlocksInfo TUB(*PO);
151+
OwnershipChecker Checker{F->getModule(), TUB, {}, {}, {}};
152+
68153
// First as a special case, handle guaranteed SIL function arguments.
69154
//
70155
// The reason that this is special is that we do not need to consider the
@@ -73,7 +158,7 @@ struct SemanticARCOpts : SILFunctionTransform {
73158
for (auto *Arg : F->getArguments()) {
74159
if (Arg->getOwnershipKind() != ValueOwnershipKind::Guaranteed)
75160
continue;
76-
MadeChange |= optimizeGuaranteedArgument(Arg);
161+
MadeChange |= optimizeGuaranteedArgument(Arg, Checker);
77162
}
78163

79164
if (MadeChange) {

test/SILOptimizer/semantic-arc-opts.sil

Lines changed: 81 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,14 +4,93 @@ sil_stage canonical
44

55
import Builtin
66

7-
// CHECK-LABEL: sil @only_destroy_user_test : $@convention(thin) (@guaranteed Builtin.NativeObject) -> () {
7+
//////////////////
8+
// Declarations //
9+
//////////////////
10+
11+
sil @guaranteed_user : $@convention(thin) (@guaranteed Builtin.NativeObject) -> ()
12+
13+
///////////
14+
// Tests //
15+
///////////
16+
17+
// CHECK-LABEL: sil @argument_only_destroy_user_test : $@convention(thin) (@guaranteed Builtin.NativeObject) -> () {
818
// CHECK-NOT: copy_value
919
// CHECK-NOT: destroy_value
10-
sil @only_destroy_user_test : $@convention(thin) (@guaranteed Builtin.NativeObject) -> () {
20+
sil @argument_only_destroy_user_test : $@convention(thin) (@guaranteed Builtin.NativeObject) -> () {
1121
bb0(%0 : @guaranteed $Builtin.NativeObject):
1222
%1 = copy_value %0 : $Builtin.NativeObject
1323
destroy_value %1 : $Builtin.NativeObject
1424
%9999 = tuple()
1525
return %9999 : $()
1626
}
1727

28+
// CHECK-LABEL: sil @argument_diamond_test_case : $@convention(thin) (@guaranteed Builtin.NativeObject) -> @owned Builtin.NativeObject {
29+
// CHECK: bb0([[ARG:%.*]] : @guaranteed $Builtin.NativeObject):
30+
// CHECK-NEXT: [[RESULT:%.*]] = copy_value [[ARG]]
31+
// CHECK-NEXT: cond_br undef, [[LHSBB:bb[0-9]+]], [[RHSBB:bb[0-9]+]]
32+
//
33+
// CHECK: [[LHSBB]]:
34+
// CHECK-NEXT: br [[EPILOGBB:bb[0-9]+]]
35+
//
36+
// CHECK: [[RHSBB]]:
37+
// CHECK-NEXT: br [[EPILOGBB]]
38+
//
39+
// CHECK: [[EPILOGBB]]:
40+
// CHECK-NEXT: return [[RESULT]]
41+
sil @argument_diamond_test_case : $@convention(thin) (@guaranteed Builtin.NativeObject) -> @owned Builtin.NativeObject {
42+
bb0(%0 : @guaranteed $Builtin.NativeObject):
43+
%1 = copy_value %0 : $Builtin.NativeObject
44+
%2 = copy_value %1 : $Builtin.NativeObject
45+
cond_br undef, bb1, bb2
46+
47+
bb1:
48+
destroy_value %1 : $Builtin.NativeObject
49+
br bb3
50+
51+
bb2:
52+
destroy_value %1 : $Builtin.NativeObject
53+
br bb3
54+
55+
bb3:
56+
return %2 : $Builtin.NativeObject
57+
}
58+
59+
// CHECK-LABEL: sil @argument_copy_borrow_test_case : $@convention(thin) (@guaranteed Builtin.NativeObject) -> () {
60+
// CHECK: bb0([[ARG:%.*]] : @guaranteed $Builtin.NativeObject
61+
// CHECK-NOT: copy_value
62+
// CHECK-NOT: begin_borrow
63+
// CHECK: apply {{%.*}}([[ARG]])
64+
// CHECK-NOT: end_borrow
65+
// CHECK-NOT: destroy_value
66+
// CHECK: } // end sil function 'argument_copy_borrow_test_case'
67+
sil @argument_copy_borrow_test_case : $@convention(thin) (@guaranteed Builtin.NativeObject) -> () {
68+
bb0(%0 : @guaranteed $Builtin.NativeObject):
69+
%1 = copy_value %0 : $Builtin.NativeObject
70+
%2 = function_ref @guaranteed_user : $@convention(thin) (@guaranteed Builtin.NativeObject) -> ()
71+
%3 = begin_borrow %1 : $Builtin.NativeObject
72+
apply %2(%3) : $@convention(thin) (@guaranteed Builtin.NativeObject) -> ()
73+
%4 = end_borrow %3 from %1 : $Builtin.NativeObject, $Builtin.NativeObject
74+
destroy_value %1 : $Builtin.NativeObject
75+
%9999 = tuple()
76+
return %9999 : $()
77+
}
78+
79+
// CHECK-LABEL: sil @argument_copy_of_copy : $@convention(thin) (@guaranteed Builtin.NativeObject) -> () {
80+
// CHECK: bb0
81+
// CHECK-NEXT: tuple
82+
// CHECK-NEXT: return
83+
// CHECK-NEXT: } // end sil function 'argument_copy_of_copy'
84+
sil @argument_copy_of_copy : $@convention(thin) (@guaranteed Builtin.NativeObject) -> () {
85+
bb0(%0 : @guaranteed $Builtin.NativeObject):
86+
%1 = copy_value %0 : $Builtin.NativeObject
87+
%2 = begin_borrow %1 : $Builtin.NativeObject
88+
%3 = copy_value %2 : $Builtin.NativeObject
89+
%4 = begin_borrow %3 : $Builtin.NativeObject
90+
end_borrow %4 from %3 : $Builtin.NativeObject, $Builtin.NativeObject
91+
destroy_value %3 : $Builtin.NativeObject
92+
end_borrow %2 from %1 : $Builtin.NativeObject, $Builtin.NativeObject
93+
destroy_value %1 : $Builtin.NativeObject
94+
%9999 = tuple()
95+
return %9999 : $()
96+
}

0 commit comments

Comments
 (0)