Skip to content

Commit 43d8ab2

Browse files
gottesmmjckarter
authored andcommitted
[move-only] Add a new type of mark_must_check initable_but_not_consumable.
This is used to teach the checker that the thing being checked is supposed to be uninitialized at the mark_must_check point so that we don't put a destroy_addr there. The way this is implemented is that we always initially add assignable_but_not_consumable but in DI once we discover that the assign we are guarding is an init, we convert the assignable to its initable variant. rdar://106525988
1 parent 69f769f commit 43d8ab2

File tree

9 files changed

+161
-37
lines changed

9 files changed

+161
-37
lines changed

include/swift/SIL/SILInstruction.h

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8318,8 +8318,8 @@ class MarkMustCheckInst
83188318
ConsumableAndAssignable,
83198319

83208320
/// A signal to the move only checker to perform no consume or assign
8321-
/// checking. This forces the result of this instruction owned value to never
8322-
/// be consumed (for let/var semantics) or assigned over (for var
8321+
/// checking. This forces the result of this instruction owned value to
8322+
/// never be consumed (for let/var semantics) or assigned over (for var
83238323
/// semantics). Of course, we still allow for non-consuming uses.
83248324
NoConsumeOrAssign,
83258325

@@ -8330,6 +8330,11 @@ class MarkMustCheckInst
83308330
/// uninitialized state), but we are ok with the user assigning a new value,
83318331
/// completely assigning over the value at once.
83328332
AssignableButNotConsumable,
8333+
8334+
/// A signal to the move checker that the given value cannot be consumed or
8335+
/// assigned, but is allowed to be initialized. This is used for situations
8336+
/// like class initializers.
8337+
InitableButNotConsumable,
83338338
};
83348339

83358340
private:
@@ -8348,13 +8353,16 @@ class MarkMustCheckInst
83488353
public:
83498354
CheckKind getCheckKind() const { return kind; }
83508355

8356+
void setCheckKind(CheckKind newKind) { kind = newKind; }
8357+
83518358
bool hasMoveCheckerKind() const {
83528359
switch (kind) {
83538360
case CheckKind::Invalid:
83548361
return false;
83558362
case CheckKind::ConsumableAndAssignable:
83568363
case CheckKind::NoConsumeOrAssign:
83578364
case CheckKind::AssignableButNotConsumable:
8365+
case CheckKind::InitableButNotConsumable:
83588366
return true;
83598367
}
83608368
}

lib/SIL/IR/SILPrinter.cpp

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2010,6 +2010,9 @@ class SILPrinter : public SILInstructionVisitor<SILPrinter> {
20102010
case CheckKind::AssignableButNotConsumable:
20112011
*this << "[assignable_but_not_consumable] ";
20122012
break;
2013+
case CheckKind::InitableButNotConsumable:
2014+
*this << "[initable_but_not_consumable] ";
2015+
break;
20132016
}
20142017
*this << getIDAndType(I->getOperand());
20152018
}

lib/SIL/Parser/ParseSIL.cpp

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3712,7 +3712,10 @@ bool SILParser::parseSpecificSILInstruction(SILBuilder &B,
37123712
.Case("consumable_and_assignable",
37133713
CheckKind::ConsumableAndAssignable)
37143714
.Case("no_consume_or_assign", CheckKind::NoConsumeOrAssign)
3715-
.Case("assignable_but_not_consumable", CheckKind::AssignableButNotConsumable)
3715+
.Case("assignable_but_not_consumable",
3716+
CheckKind::AssignableButNotConsumable)
3717+
.Case("initable_but_not_consumable",
3718+
CheckKind::InitableButNotConsumable)
37163719
.Default(CheckKind::Invalid);
37173720

37183721
if (CKind == CheckKind::Invalid) {

lib/SILOptimizer/Mandatory/DefiniteInitialization.cpp

Lines changed: 18 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2312,7 +2312,6 @@ void LifetimeChecker::updateInstructionForInitState(unsigned UseID) {
23122312
// If this is an assign, rewrite it based on whether it is an initialization
23132313
// or not.
23142314
if (auto *AI = dyn_cast<AssignInst>(Inst)) {
2315-
23162315
// Remove this instruction from our data structures, since we will be
23172316
// removing it.
23182317
Use.Inst = nullptr;
@@ -2328,6 +2327,24 @@ void LifetimeChecker::updateInstructionForInitState(unsigned UseID) {
23282327
: AssignOwnershipQualifier::Reassign));
23292328
}
23302329

2330+
// Look and see if we are assigning a moveonly type into a mark_must_check
2331+
// [assignable_but_not_consumable]. If we are, then we need to transition
2332+
// its flag to initable_but_not_assignable.
2333+
//
2334+
// NOTE: We should only ever have to do this for a single level since SILGen
2335+
// always initializes values completely and we enforce that invariant.
2336+
if (InitKind == IsInitialization) {
2337+
if (auto *mmci =
2338+
dyn_cast<MarkMustCheckInst>(stripAccessMarkers(AI->getDest()))) {
2339+
if (mmci->getCheckKind() ==
2340+
MarkMustCheckInst::CheckKind::AssignableButNotConsumable &&
2341+
isa<RefElementAddrInst>(stripAccessMarkers(mmci->getOperand()))) {
2342+
mmci->setCheckKind(
2343+
MarkMustCheckInst::CheckKind::InitableButNotConsumable);
2344+
}
2345+
}
2346+
}
2347+
23312348
return;
23322349
}
23332350
if (auto *AI = dyn_cast<AssignByWrapperInst>(Inst)) {

lib/SILOptimizer/Mandatory/MoveOnlyAddressCheckerUtils.cpp

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -500,8 +500,12 @@ static bool isInOutDefThatNeedsEndOfFunctionLiveness(MarkMustCheckInst *markedAd
500500

501501
if (isa<GlobalAddrInst>(stripAccessMarkers(operand)))
502502
return true;
503+
504+
if (auto *rei = dyn_cast<RefElementAddrInst>(stripAccessMarkers(operand)))
505+
return true;
503506
}
504507

508+
505509
return false;
506510
}
507511

@@ -2159,6 +2163,19 @@ void MoveOnlyAddressCheckerPImpl::insertDestroysOnBoundary(
21592163
insertUndefDebugValue(insertPt);
21602164
} else {
21612165
auto *inst = cast<SILInstruction>(defPair.first);
2166+
2167+
// If we have a dead def that is our mark must check and that mark must
2168+
// check was an init but not consumable, then do not destroy that
2169+
// def. This is b/c we are in some sort of class initialization and we are
2170+
// looking at the initial part of the live range before the initialization
2171+
// has occured. This is our way of makinmg this fit the model that the
2172+
// checker expects (which is that values are always initialized at the def
2173+
// point).
2174+
if (markedValue &&
2175+
markedValue->getCheckKind() ==
2176+
MarkMustCheckInst::CheckKind::InitableButNotConsumable)
2177+
continue;
2178+
21622179
auto *insertPt = inst->getNextInstruction();
21632180
assert(insertPt && "def instruction was a terminator");
21642181
insertDestroyBeforeInstruction(addressUseState, insertPt,

lib/Serialization/DeserializeSIL.cpp

Lines changed: 10 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -2200,17 +2200,6 @@ bool SILDeserializer::readSILInstruction(SILFunction *Fn,
22002200
break;
22012201
}
22022202

2203-
case SILInstructionKind::MarkMustCheckInst: {
2204-
using CheckKind = MarkMustCheckInst::CheckKind;
2205-
auto Ty = MF->getType(TyID);
2206-
auto CKind = CheckKind(Attr);
2207-
ResultInst = Builder.createMarkMustCheckInst(
2208-
Loc,
2209-
getLocalValue(ValID, getSILType(Ty, (SILValueCategory)TyCategory, Fn)),
2210-
CKind);
2211-
break;
2212-
}
2213-
22142203
case SILInstructionKind::MarkUnresolvedReferenceBindingInst: {
22152204
using Kind = MarkUnresolvedReferenceBindingInst::Kind;
22162205
auto ty = MF->getType(TyID);
@@ -2288,6 +2277,16 @@ bool SILDeserializer::readSILInstruction(SILFunction *Fn,
22882277
ResultInst = Builder.createMarkUninitialized(Loc, Val, Kind);
22892278
break;
22902279
}
2280+
case SILInstructionKind::MarkMustCheckInst: {
2281+
using CheckKind = MarkMustCheckInst::CheckKind;
2282+
auto Ty = MF->getType(TyID);
2283+
auto CKind = CheckKind(Attr);
2284+
ResultInst = Builder.createMarkMustCheckInst(
2285+
Loc,
2286+
getLocalValue(ValID, getSILType(Ty, (SILValueCategory)TyCategory, Fn)),
2287+
CKind);
2288+
break;
2289+
}
22912290
case SILInstructionKind::StoreInst: {
22922291
auto Ty = MF->getType(TyID);
22932292
SILType addrType = getSILType(Ty, (SILValueCategory)TyCategory, Fn);

lib/Serialization/SerializeSIL.cpp

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1482,7 +1482,6 @@ void SILSerializer::writeSILInstruction(const SILInstruction &SI) {
14821482
case SILInstructionKind::CopyValueInst:
14831483
case SILInstructionKind::ExplicitCopyValueInst:
14841484
case SILInstructionKind::MoveValueInst:
1485-
case SILInstructionKind::MarkMustCheckInst:
14861485
case SILInstructionKind::MarkUnresolvedReferenceBindingInst:
14871486
case SILInstructionKind::MoveOnlyWrapperToCopyableValueInst:
14881487
case SILInstructionKind::CopyableToMoveOnlyWrapperValueInst:
@@ -1561,6 +1560,11 @@ void SILSerializer::writeSILInstruction(const SILInstruction &SI) {
15611560
writeOneOperandLayout(SI.getKind(), Attr, SI.getOperand(0));
15621561
break;
15631562
}
1563+
case SILInstructionKind::MarkMustCheckInst: {
1564+
unsigned Attr = unsigned(cast<MarkMustCheckInst>(&SI)->getCheckKind());
1565+
writeOneOperandExtraAttributeLayout(SI.getKind(), Attr, SI.getOperand(0));
1566+
break;
1567+
}
15641568
case SILInstructionKind::MarkUninitializedInst: {
15651569
unsigned Attr =
15661570
(unsigned)cast<MarkUninitializedInst>(&SI)->getMarkUninitializedKind();

test/Interpreter/moveonly.swift

Lines changed: 63 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -10,40 +10,82 @@ var Tests = TestSuite("MoveOnlyTests")
1010

1111
@_moveOnly
1212
struct FD {
13-
var a = LifetimeTracked(0)
13+
var a = LifetimeTracked(0)
1414

15-
deinit {
16-
}
15+
deinit {
16+
}
1717
}
1818

1919
Tests.test("simple deinit called once") {
20-
do {
21-
let s = FD()
22-
}
23-
expectEqual(0, LifetimeTracked.instances)
20+
do {
21+
let s = FD()
22+
}
23+
expectEqual(0, LifetimeTracked.instances)
2424
}
2525

2626
Tests.test("ref element addr destroyed once") {
27-
class CopyableKlass {
28-
var fd = FD()
29-
}
27+
class CopyableKlass {
28+
var fd = FD()
29+
}
3030

31-
func assignCopyableKlass(_ x: CopyableKlass) {
32-
x.fd = FD()
33-
}
31+
func assignCopyableKlass(_ x: CopyableKlass) {
32+
x.fd = FD()
33+
}
3434

35-
do {
36-
let x = CopyableKlass()
37-
assignCopyableKlass(x)
38-
}
39-
expectEqual(0, LifetimeTracked.instances)
35+
do {
36+
let x = CopyableKlass()
37+
assignCopyableKlass(x)
38+
}
39+
expectEqual(0, LifetimeTracked.instances)
4040
}
4141

4242
var global = FD()
4343

4444
Tests.test("global destroyed once") {
45-
do {
46-
global = FD()
45+
do {
46+
global = FD()
47+
}
48+
expectEqual(0, LifetimeTracked.instances)
49+
}
50+
51+
// TODO (rdar://107494072): Move-only types with deinits declared inside
52+
// functions sometimes lose their deinit function.
53+
// When that's fixed, FD2 can be moved back inside the test closure below.
54+
@_moveOnly
55+
struct FD2 {
56+
var field = 5
57+
static var count = 0
58+
init() { FD2.count += 1 }
59+
deinit {
60+
FD2.count -= 1
61+
print("In deinit!")
62+
}
63+
func use() {}
64+
}
65+
66+
Tests.test("deinit not called in init when assigned") {
67+
class FDHaver {
68+
var fd: FD2
69+
70+
init() {
71+
self.fd = FD2()
4772
}
48-
expectEqual(0, LifetimeTracked.instances)
73+
}
74+
75+
class FDHaver2 {
76+
var fd: FD2
77+
78+
init() {
79+
self.fd = FD2()
80+
self.fd = FD2()
81+
self.fd = FD2()
82+
self.fd.use()
83+
}
84+
}
85+
86+
do {
87+
let haver = FDHaver2()
88+
let _ = haver
89+
}
90+
expectEqual(0, FD2.count)
4991
}
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
// RUN: %target-swift-frontend -module-name moveonly_class_inits_end_to_end %s -emit-sil -o - | %FileCheck %s
2+
3+
// A test that makes sure end to end in a copyable class containing a
4+
// non-copyable type, in the init, we only have a single destroy_addr.
5+
6+
@_moveOnly
7+
public struct MO {
8+
var x: Int8 = 0
9+
deinit { print("destroyed MO") }
10+
}
11+
12+
public class MOHaver {
13+
var mo: MO
14+
15+
// CHECK-LABEL: sil hidden @$s028moveonly_class_inits_end_to_D07MOHaverCACycfc : $@convention(method) (@owned MOHaver) -> @owned MOHaver {
16+
// CHECK: bb0([[ARG:%.*]] :
17+
// CHECK-NEXT: debug_value [[ARG]]
18+
// CHECK-NEXT: [[META:%.*]] = metatype
19+
// CHECK-NEXT: function_ref MO.init()
20+
// CHECK-NEXT: [[FUNC:%.*]] = function_ref @$s028moveonly_class_inits_end_to_D02MOVACycfC :
21+
// CHECK-NEXT: [[RESULT:%.*]] = apply [[FUNC]]([[META]])
22+
// CHECK-NEXT: [[REF:%.*]] = ref_element_addr [[ARG]]
23+
// CHECK-NEXT: [[REF_ACCESS:%.*]] = begin_access [modify] [dynamic] [[REF]]
24+
// CHECK-NEXT: store [[RESULT]] to [[REF_ACCESS]]
25+
// CHECK-NEXT: end_access [[REF_ACCESS]]
26+
// CHECK-NEXT: return [[ARG]]
27+
// CHECK-NEXT: } // end sil function '$s028moveonly_class_inits_end_to_D07MOHaverCACycfc'
28+
init() {
29+
self.mo = MO()
30+
}
31+
}

0 commit comments

Comments
 (0)