Skip to content

Commit 1cb6ab5

Browse files
authored
Merge pull request swiftlang#83763 from atrick/fix-dce-struct-deinit
SIL: fix miscompiles of non-Copyable struct/enum with deinits
2 parents faad7c7 + 9d40198 commit 1cb6ab5

File tree

5 files changed

+96
-5
lines changed

5 files changed

+96
-5
lines changed

include/swift/SIL/SILInstruction.h

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1293,7 +1293,9 @@ class ForwardingInstruction {
12931293
/// The resulting forwarded value's ownership, returned by getOwnershipKind(),
12941294
/// is not identical to the forwarding ownership. It differs when the result
12951295
/// is trivial type. e.g. an owned or guaranteed value can be cast to a
1296-
/// trivial type using owned or guaranteed forwarding.
1296+
/// trivial type using owned or guaranteed forwarding. Similarly, if a trivial
1297+
/// value is forwarded into an owned non-Copyable struct or enum, forwarding
1298+
/// ownership is 'none' while value ownerhip is 'owned'.
12971299
ValueOwnershipKind getForwardingOwnershipKind() const {
12981300
return ownershipKind;
12991301
}
@@ -7009,8 +7011,9 @@ class EnumInst
70097011
EnumInst(SILDebugLocation DebugLoc, SILValue Operand,
70107012
EnumElementDecl *Element, SILType ResultTy,
70117013
ValueOwnershipKind forwardingOwnershipKind)
7012-
: InstructionBase(DebugLoc, ResultTy, forwardingOwnershipKind),
7013-
Element(Element) {
7014+
: InstructionBase(DebugLoc, ResultTy,
7015+
forwardingOwnershipKind.forwardToInit(ResultTy)),
7016+
Element(Element) {
70147017
sharedUInt32().EnumInst.caseIndex = InvalidCaseIndex;
70157018

70167019
if (Operand) {

include/swift/SIL/SILValue.h

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -353,6 +353,30 @@ struct ValueOwnershipKind {
353353
});
354354
}
355355

356+
// An initialized value whose nominal type has a deinit() must be 'owned'. For
357+
// example, an owned struct/enum-with-deinit may be initialized by
358+
// "forwarding" a trivial value. A struct/enum-with-deinit must be prevented
359+
// from forwarding a guaranteed value.
360+
//
361+
// Simply consider all non-Copyable types to be 'owned'. This could instead be
362+
// limited to isValueTypeWithDeinit(). However, forcing non-Copyable types to
363+
// be owned is consistent with the fact that their type is non-Trivial and
364+
// simplifies reasoning about non-Copyable ownership.
365+
ValueOwnershipKind forwardToInit(SILType nominalType) {
366+
if (nominalType.isMoveOnly()) {
367+
switch (value) {
368+
case OwnershipKind::Any:
369+
case OwnershipKind::None:
370+
case OwnershipKind::Owned:
371+
return OwnershipKind::Owned;
372+
case OwnershipKind::Guaranteed:
373+
case OwnershipKind::Unowned:
374+
ABORT("Cannot initialize a nonCopyable type with a guaranteed value");
375+
}
376+
}
377+
return *this;
378+
}
379+
356380
StringRef asString() const;
357381
};
358382

lib/SIL/IR/SILInstructions.cpp

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1509,8 +1509,9 @@ StructInst *StructInst::create(SILDebugLocation Loc, SILType Ty,
15091509
StructInst::StructInst(SILDebugLocation Loc, SILType Ty,
15101510
ArrayRef<SILValue> Elems,
15111511
ValueOwnershipKind forwardingOwnershipKind)
1512-
: InstructionBaseWithTrailingOperands(Elems, Loc, Ty,
1513-
forwardingOwnershipKind) {
1512+
: InstructionBaseWithTrailingOperands(
1513+
Elems, Loc, Ty, forwardingOwnershipKind.forwardToInit(Ty))
1514+
{
15141515
assert(!Ty.getStructOrBoundGenericStruct()->hasUnreferenceableStorage());
15151516
}
15161517

lib/SILOptimizer/Utils/CanonicalizeInstruction.cpp

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -548,6 +548,10 @@ static SILBasicBlock::iterator
548548
eliminateUnneededForwardingUnarySingleValueInst(SingleValueInstruction *inst,
549549
CanonicalizeInstruction &pass) {
550550
auto next = std::next(inst->getIterator());
551+
if (inst->getType().isValueTypeWithDeinit()) {
552+
// Avoid bypassing non-Copyable struct/enum deinitializers.
553+
return next;
554+
}
551555
if (isa<DropDeinitInst>(inst))
552556
return next;
553557
if (auto *uedi = dyn_cast<UncheckedEnumDataInst>(inst)) {

test/SILOptimizer/sil_combine_moveonly.sil

Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,17 @@ struct Wrapper<T>: ~Copyable {
3131
var t: T
3232
}
3333

34+
struct NonCopyableForwardingStruct : ~Copyable {
35+
var some: Int
36+
deinit
37+
}
38+
39+
enum NonCopyableForwardingEnum : ~Copyable {
40+
case some(Int)
41+
case nothing
42+
deinit
43+
}
44+
3445
sil @getWrappedValue : $@convention(thin) <T> (@in_guaranteed Wrapper<T>) -> @out T
3546

3647
// Test that a release_value is not removed for a struct-with-deinit.
@@ -110,3 +121,51 @@ bb0(%0 : @owned $S):
110121
%64 = tuple ()
111122
return %64 : $()
112123
}
124+
125+
// Test a "dead" non-Copyable struct-with-deinit that forwards a
126+
// single trivial value. The destroy_value cannot be eliminated
127+
// without first devirtualizing the deinit.
128+
//
129+
// CHECK-LABEL: sil [ossa] @testNonCopyableForwardingStructDeinit : $@convention(thin) (Int) -> () {
130+
// CHECK: [[STRUCT:%[0-9]+]] = struct $NonCopyableForwardingStruct (%0 : $Int)
131+
// CHECK: destroy_value [[STRUCT]]
132+
// CHECK-LABEL: } // end sil function 'testNonCopyableForwardingStructDeinit'
133+
sil [ossa] @testNonCopyableForwardingStructDeinit : $@convention(thin) (Int) -> () {
134+
bb0(%0: $Int):
135+
%1 = struct $NonCopyableForwardingStruct(%0 : $Int)
136+
destroy_value %1
137+
%13 = tuple ()
138+
return %13
139+
}
140+
141+
// Test a "dead" non-Copyable enum-with-deinit that forwards a single
142+
// trivial value. The destroy_value cannot be eliminated without first
143+
// devirtualizing the deinit.
144+
//
145+
// CHECK-LABEL: sil [ossa] @testNonCopyableForwardingEnumDeinit : $@convention(thin) (Int) -> () {
146+
// CHECK: [[ENUM:%[0-9]+]] = enum $NonCopyableForwardingEnum, #NonCopyableForwardingEnum.some!enumelt, %0
147+
// CHECK: destroy_value [[ENUM]]
148+
// CHECK-LABEL: } // end sil function 'testNonCopyableForwardingEnumDeinit'
149+
sil [ossa] @testNonCopyableForwardingEnumDeinit : $@convention(thin) (Int) -> () {
150+
bb0(%0 : $Int):
151+
%1 = enum $NonCopyableForwardingEnum, #NonCopyableForwardingEnum.some!enumelt, %0
152+
destroy_value %1
153+
%13 = tuple ()
154+
return %13
155+
}
156+
157+
// Test a "dead" non-Copyable enum-with-deinit and an empty case. The
158+
// destroy_value cannot be eliminated without first devirtualizing the
159+
// deinit.
160+
//
161+
// CHECK-LABEL: sil [ossa] @testNonCopyableEmptyEnumDeinit : $@convention(thin) () -> () {
162+
// CHECK: [[ENUM:%[0-9]+]] = enum $NonCopyableForwardingEnum, #NonCopyableForwardingEnum.nothing
163+
// CHECK: destroy_value [[ENUM]]
164+
// CHECK-LABEL: } // end sil function 'testNonCopyableEmptyEnumDeinit'
165+
sil [ossa] @testNonCopyableEmptyEnumDeinit : $@convention(thin) () -> () {
166+
bb0:
167+
%0 = enum $NonCopyableForwardingEnum, #NonCopyableForwardingEnum.nothing
168+
destroy_value %0
169+
%13 = tuple ()
170+
return %13
171+
}

0 commit comments

Comments
 (0)