Skip to content

Commit 75064b3

Browse files
committed
[move-only] Fix SILOptimizer code motion to preserve value deinits
Multiple code motion and ARC related passes were removing struct/enum deinits. Passes fixed include: - SILCombine - EarlyCodeMotion - ReleaseHoisting - *many* passes that rely on ARC analysis (RCIndentity) (cherry picked from commit 6756ed7)
1 parent fcb535f commit 75064b3

File tree

8 files changed

+174
-17
lines changed

8 files changed

+174
-17
lines changed

include/swift/SILOptimizer/Utils/InstOptUtils.h

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -141,6 +141,16 @@ void collectUsesOfValue(SILValue V,
141141
/// value itself)
142142
void eraseUsesOfValue(SILValue value);
143143

144+
/// Return true if \p type is a value type (struct/enum) that requires
145+
/// deinitialization beyond destruction of its members.
146+
bool hasValueDeinit(SILType type);
147+
148+
/// Return true if \p value has a value type (struct/enum) that requires
149+
/// deinitialization beyond destruction of its members.
150+
inline bool hasValueDeinit(SILValue value) {
151+
return hasValueDeinit(value->getType());
152+
}
153+
144154
/// Gets the concrete value which is stored in an existential box.
145155
/// Returns %value in following pattern:
146156
///

lib/SILOptimizer/Analysis/RCIdentityAnalysis.cpp

Lines changed: 21 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212

1313
#include "swift/SILOptimizer/Analysis/RCIdentityAnalysis.h"
1414
#include "swift/SILOptimizer/Analysis/DominanceAnalysis.h"
15+
#include "swift/SILOptimizer/Utils/InstOptUtils.h"
1516
#include "swift/SIL/SILInstruction.h"
1617
#include "swift/SIL/DynamicCasts.h"
1718
#include "llvm/Support/CommandLine.h"
@@ -79,26 +80,29 @@ static SILValue stripRCIdentityPreservingInsts(SILValue V) {
7980
// the struct is equivalent to a ref count operation on the extracted
8081
// member. Strip off the extract.
8182
if (auto *SEI = dyn_cast<StructExtractInst>(V))
82-
if (SEI->isFieldOnlyNonTrivialField())
83+
if (SEI->isFieldOnlyNonTrivialField() && !hasValueDeinit(SEI->getOperand()))
8384
return SEI->getOperand();
8485

8586
// If we have a struct instruction with only one non-trivial stored field, the
8687
// only reference count that can be modified is the non-trivial field. Return
8788
// the non-trivial field.
88-
if (auto *SI = dyn_cast<StructInst>(V))
89-
if (SILValue NewValue = SI->getUniqueNonTrivialFieldValue())
90-
return NewValue;
91-
89+
if (auto *SI = dyn_cast<StructInst>(V)) {
90+
if (!hasValueDeinit(SI)) {
91+
if (SILValue NewValue = SI->getUniqueNonTrivialFieldValue())
92+
return NewValue;
93+
}
94+
}
9295
// If we have an unchecked_enum_data, strip off the unchecked_enum_data.
93-
if (auto *UEDI = dyn_cast<UncheckedEnumDataInst>(V))
94-
return UEDI->getOperand();
95-
96+
if (auto *UEDI = dyn_cast<UncheckedEnumDataInst>(V)) {
97+
if (!hasValueDeinit(UEDI->getOperand()))
98+
return UEDI->getOperand();
99+
}
96100
// If we have an enum instruction with a payload, strip off the enum to
97101
// expose the enum's payload.
98-
if (auto *EI = dyn_cast<EnumInst>(V))
99-
if (EI->hasOperand())
102+
if (auto *EI = dyn_cast<EnumInst>(V)) {
103+
if (EI->hasOperand() && !hasValueDeinit(EI))
100104
return EI->getOperand();
101-
105+
}
102106
// If we have a tuple_extract that is extracting the only non trivial member
103107
// of a tuple, a retain_value on the tuple is equivalent to a retain_value on
104108
// the extracted value.
@@ -114,8 +118,10 @@ static SILValue stripRCIdentityPreservingInsts(SILValue V) {
114118
return NewValue;
115119

116120
if (auto *result = SILArgument::isTerminatorResult(V)) {
117-
if (auto *forwardedOper = result->forwardedTerminatorResultOperand())
118-
return forwardedOper->get();
121+
if (auto *forwardedOper = result->forwardedTerminatorResultOperand()) {
122+
if (!hasValueDeinit(forwardedOper->get()))
123+
return forwardedOper->get();
124+
}
119125
}
120126

121127
// Handle useless single-predecessor phis for legacy reasons. (Although these
@@ -307,7 +313,7 @@ static SILValue allIncomingValuesEqual(
307313
SILValue RCIdentityFunctionInfo::stripRCIdentityPreservingArgs(SILValue V,
308314
unsigned RecursionDepth) {
309315
auto *A = dyn_cast<SILPhiArgument>(V);
310-
if (!A) {
316+
if (!A || !A->isPhi()) {
311317
return SILValue();
312318
}
313319

@@ -503,7 +509,7 @@ static bool isNonOverlappingTrivialAccess(SILValue value) {
503509
if (auto *SEI = dyn_cast<StructExtractInst>(value)) {
504510
// If the struct we are extracting from only has one non trivial element and
505511
// we are not extracting from that element, this is an ARC escape.
506-
return SEI->isTrivialFieldOfOneRCIDStruct();
512+
return SEI->isTrivialFieldOfOneRCIDStruct() && !hasValueDeinit(SEI);
507513
}
508514

509515
return false;

lib/SILOptimizer/SILCombiner/SILCombinerMiscVisitors.cpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1092,7 +1092,7 @@ SILInstruction *SILCombiner::visitReleaseValueInst(ReleaseValueInst *RVI) {
10921092

10931093
// retain_value of an enum_inst where we know that it has a payload can be
10941094
// reduced to a retain_value on the payload.
1095-
if (EI->hasOperand()) {
1095+
if (EI->hasOperand() && !hasValueDeinit(EI)) {
10961096
return Builder.createReleaseValue(RVI->getLoc(), EI->getOperand(),
10971097
RVI->getAtomicity());
10981098
}

lib/SILOptimizer/Transforms/SILCodeMotion.cpp

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -564,6 +564,10 @@ bool BBEnumTagDataflowState::visitReleaseValueInst(ReleaseValueInst *RVI) {
564564
if (FindResult == ValueToCaseMap.end())
565565
return false;
566566

567+
// If the enum has a deinit, preserve the original release.
568+
if (hasValueDeinit(RVI->getOperand()))
569+
return false;
570+
567571
// If we do not have any argument, just delete the release value.
568572
if (!(*FindResult)->second->hasAssociatedValues()) {
569573
RVI->eraseFromParent();
@@ -621,6 +625,10 @@ bool BBEnumTagDataflowState::hoistDecrementsIntoSwitchRegions(
621625
continue;
622626
}
623627

628+
// If the enum has a deinit, preserve the original release.
629+
if (hasValueDeinit(Op))
630+
return false;
631+
624632
auto &EnumBBCaseList = (*R)->second;
625633
// If we don't have an enum tag for each predecessor of this BB, bail since
626634
// we do not know how to handle that BB.
@@ -1508,6 +1516,10 @@ static bool tryToSinkRefCountAcrossSwitch(SwitchEnumInst *Switch,
15081516
RCIA->getRCIdentityRoot(Switch->getOperand()))
15091517
return false;
15101518

1519+
// If the enum has a deinit, preserve the original release.
1520+
assert(!hasValueDeinit(Ptr) &&
1521+
"enum with deinit is not RC-identical to its payload");
1522+
15111523
// If S has a default case bail since the default case could represent
15121524
// multiple cases.
15131525
//
@@ -1578,6 +1590,10 @@ static bool tryToSinkRefCountAcrossSelectEnum(CondBranchInst *CondBr,
15781590
RCIA->getRCIdentityRoot(SEI->getEnumOperand()))
15791591
return false;
15801592

1593+
// If the enum has a deinit, preserve the original release.
1594+
assert(!hasValueDeinit(Ptr) &&
1595+
"enum with deinit is not RC-identical to its payload");
1596+
15811597
// Work out which enum element is the true branch, and which is false.
15821598
// If the enum only has 2 values and its tag isn't the true branch, then we
15831599
// know the true branch must be the other tag.

lib/SILOptimizer/Utils/InstOptUtils.cpp

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -309,6 +309,15 @@ void swift::eraseUsesOfValue(SILValue v) {
309309
}
310310
}
311311

312+
bool swift::hasValueDeinit(SILType type) {
313+
// Do not look inside an aggregate type that has a user-deinit, for which
314+
// memberwise-destruction is not equivalent to aggregate destruction.
315+
if (auto *nominal = type.getNominalOrBoundGenericNominal()) {
316+
return nominal->getValueTypeDestructor() != nullptr;
317+
}
318+
return false;
319+
}
320+
312321
SILValue swift::
313322
getConcreteValueOfExistentialBox(AllocExistentialBoxInst *existentialBox,
314323
SILInstruction *ignoreUser) {
Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
// RUN: %target-sil-opt -enable-sil-verify-all %s -early-codemotion -retain-sinking -enable-experimental-feature MoveOnlyEnumDeinits | %FileCheck %s
2+
3+
sil_stage canonical
4+
5+
import Builtin
6+
7+
struct FileDescriptor: ~Copyable {
8+
var fd: Builtin.Int64
9+
10+
init(fd: Builtin.Int64)
11+
deinit
12+
}
13+
14+
enum MaybeFileDescriptor: ~Copyable {
15+
case some(FileDescriptor)
16+
case nothing
17+
18+
consuming func discard() { discard self }
19+
20+
deinit
21+
}
22+
23+
24+
// Test that a release_value is not replaced for a enum-with-deinit.
25+
// Doing so would forget the deinit.
26+
//
27+
// CHECK-LABEL: sil hidden [noinline] @testEnumDeinit : $@convention(thin) () -> () {
28+
// CHECK: release_value %{{.*}} : $MaybeFileDescriptor
29+
// CHECK-LABEL: } // end sil function 'testEnumDeinit'
30+
sil hidden [noinline] @testEnumDeinit : $@convention(thin) () -> () {
31+
bb0:
32+
%0 = integer_literal $Builtin.Int64, 0
33+
%26 = struct $FileDescriptor (%0 : $Builtin.Int64)
34+
%38 = enum $MaybeFileDescriptor, #MaybeFileDescriptor.some!enumelt, %26 : $FileDescriptor
35+
release_value %38 : $MaybeFileDescriptor
36+
%42 = tuple ()
37+
return %42 : $()
38+
}

test/SILOptimizer/late_release_hoisting.sil

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,15 @@ class HasHasObj {
3131
var ho : HasObj
3232
}
3333

34+
public final class C {}
35+
36+
@_moveOnly
37+
struct FD {
38+
var c = C()
39+
40+
deinit {}
41+
}
42+
3443
sil @$s4test8HasInt64CfD : $@convention(method) (@owned HasInt64) -> () {
3544
[%0: noescape **]
3645
}
@@ -308,3 +317,16 @@ bb0(%0 : $Int64, %1 : $HasObj):
308317
strong_release %lo : $HasHasInt64
309318
return %o : $AnyObject
310319
}
320+
321+
// Test that a struct-with-deinit is not RC-identical to its single member.
322+
//
323+
// CHECK-LABEL: sil hidden @fdDeinit : $@convention(method) (@owned FD) -> () {
324+
// CHECK: strong_release %{{.*}} : $C
325+
// CHECK-LABEL: } // end sil function 'fdDeinit'
326+
sil hidden @fdDeinit : $@convention(method) (@owned FD) -> () {
327+
bb0(%0 : $FD):
328+
%2 = struct_extract %0 : $FD, #FD.c
329+
strong_release %2 : $C
330+
%4 = tuple ()
331+
return %4 : $()
332+
}

test/SILOptimizer/sil_combine_moveonly.sil

Lines changed: 57 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,8 @@
1+
<<<<<<< HEAD
12
// RUN: %target-sil-opt -enable-sil-verify-all -sil-combine %s | %FileCheck %s
3+
=======
4+
// RUN: %target-sil-opt -enable-experimental-feature MoveOnlyEnumDeinits -enable-sil-verify-all -sil-combine %s | %FileCheck %s
5+
>>>>>>> 536cba48be8 ([move-only] Fix SILOptimizer code motion to preserve value deinits)
26

37
sil_stage canonical
48

@@ -8,6 +12,7 @@
812
deinit {}
913
}
1014

15+
<<<<<<< HEAD
1116
// Test that a release_value is not removed for a struct-with-deinit.
1217
// Doing so would forget the deinit.
1318
//
@@ -25,4 +30,55 @@
2530
%5 = tuple ()
2631
return %5 : $()
2732
}
28-
33+
34+
=======
35+
struct FileDescriptor: ~Copyable {
36+
var fd: Builtin.Int64
37+
38+
init(fd: Builtin.Int64)
39+
deinit
40+
}
41+
42+
enum MaybeFileDescriptor: ~Copyable {
43+
case some(FileDescriptor)
44+
case nothing
45+
46+
consuming func discard() { discard self }
47+
48+
deinit
49+
}
50+
51+
// Test that a release_value is not removed for a struct-with-deinit.
52+
// Doing so would forget the deinit.
53+
//
54+
// public func testStructDeinit() {
55+
// var s = S()
56+
// }
57+
//
58+
// CHECK-LABEL: sil @testStructDeinit : $@convention(thin) () -> () {
59+
// CHECK: release_value %{{.*}} : $S
60+
// CHECK-LABEL: } // end sil function 'testStructDeinit'
61+
sil @testStructDeinit : $@convention(thin) () -> () {
62+
bb0:
63+
%0 = struct $S ()
64+
release_value %0 : $S
65+
%5 = tuple ()
66+
return %5 : $()
67+
}
68+
69+
// Test that a release_value is not replaced for a enum-with-deinit.
70+
// Doing so would forget the deinit.
71+
//
72+
// CHECK-LABEL: sil hidden [noinline] @testEnumDeinit : $@convention(thin) () -> () {
73+
// CHECK: release_value %{{.*}} : $MaybeFileDescriptor
74+
// CHECK-LABEL: } // end sil function 'testEnumDeinit'
75+
sil hidden [noinline] @testEnumDeinit : $@convention(thin) () -> () {
76+
bb0:
77+
%0 = integer_literal $Builtin.Int64, 0
78+
%26 = struct $FileDescriptor (%0 : $Builtin.Int64)
79+
%38 = enum $MaybeFileDescriptor, #MaybeFileDescriptor.some!enumelt, %26 : $FileDescriptor
80+
release_value %38 : $MaybeFileDescriptor
81+
%42 = tuple ()
82+
return %42 : $()
83+
}
84+
>>>>>>> 536cba48be8 ([move-only] Fix SILOptimizer code motion to preserve value deinits)

0 commit comments

Comments
 (0)