Skip to content

Commit 0e383cd

Browse files
committed
[move-only] Fix a place in DI where we were not converting an assignable_but_not_consumable -> initable_but_not_consumable.
I fixed this for assign but missed a place where we needed to do the same thing for copy_addr. rdar://111709236
1 parent 7028381 commit 0e383cd

File tree

3 files changed

+103
-0
lines changed

3 files changed

+103
-0
lines changed

lib/SILOptimizer/Mandatory/DefiniteInitialization.cpp

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2381,6 +2381,21 @@ void LifetimeChecker::updateInstructionForInitState(unsigned UseID) {
23812381
CA->setIsInitializationOfDest(InitKind);
23822382
if (InitKind == IsInitialization)
23832383
setStaticInitAccess(CA->getDest());
2384+
2385+
// If we had an initialization and had an assignable_but_not_consumable
2386+
// noncopyable type, convert it to be an initable_but_not_consumable so that
2387+
// we do not consume an uninitialized value.
2388+
if (InitKind == IsInitialization) {
2389+
if (auto *mmci =
2390+
dyn_cast<MarkMustCheckInst>(stripAccessMarkers(CA->getDest()))) {
2391+
if (mmci->getCheckKind() ==
2392+
MarkMustCheckInst::CheckKind::AssignableButNotConsumable) {
2393+
mmci->setCheckKind(
2394+
MarkMustCheckInst::CheckKind::InitableButNotConsumable);
2395+
}
2396+
}
2397+
}
2398+
23842399
return;
23852400
}
23862401

Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
// RUN: %target-sil-opt -definite-init %s | %FileCheck %s
2+
3+
// Make sure that DI properly converts mark_must_check
4+
// [assignable_but_not_consumable] -> mark_must_check
5+
// [initable_but_not_consumable].
6+
7+
sil_stage raw
8+
9+
fileprivate final class _Box<T> {
10+
var value: _Node<T>
11+
12+
init(_ value: consuming _Node<T>)
13+
}
14+
15+
struct _Node<T> : ~Copyable {
16+
var value: T
17+
var _next: ListEntry<T>
18+
19+
init(_ newValue: T)
20+
}
21+
22+
struct ListEntry<T> : ~Copyable {
23+
private var innerBox: _Box<T>
24+
}
25+
26+
// CHECK-LABEL: sil private [ossa] @boxInit : $@convention(method) <T> (@in _Node<T>, @owned _Box<T>) -> @owned _Box<T> {
27+
// CHECK: bb0([[INPUT:%.*]] : $*_Node<T>, [[SELF:%.*]] : @owned
28+
// CHECK: [[BORROW_SELF:%.*]] = begin_borrow [[SELF]]
29+
// CHECK: [[REF:%.*]] = ref_element_addr [[BORROW_SELF]]
30+
// CHECK: [[ACCESS:%.*]] = begin_access [modify] [dynamic] [[REF]]
31+
// CHECK: [[MARK:%.*]] = mark_must_check [initable_but_not_consumable] [[ACCESS]]
32+
// CHECK: copy_addr [take] {{%.*}} to [init] [[MARK]]
33+
// CHECK: end_access [[ACCESS]]
34+
// CHECK: } // end sil function 'boxInit'
35+
sil private [ossa] @boxInit : $@convention(method) <T> (@in _Node<T>, @owned _Box<T>) -> @owned _Box<T> {
36+
bb0(%0 : $*_Node<T>, %1 : @owned $_Box<T>):
37+
%2 = alloc_stack [lexical] $_Node<T>, var, name "value"
38+
%3 = mark_must_check [consumable_and_assignable] %2 : $*_Node<T>
39+
copy_addr [take] %0 to [init] %3 : $*_Node<T>
40+
debug_value %1 : $_Box<T>, let, name "self", argno 2, implicit
41+
%6 = mark_uninitialized [rootself] %1 : $_Box<T>
42+
%7 = begin_borrow %6 : $_Box<T>
43+
%8 = begin_access [read] [static] %3 : $*_Node<T>
44+
%9 = alloc_stack $_Node<T>
45+
copy_addr %8 to [init] %9 : $*_Node<T>
46+
end_access %8 : $*_Node<T>
47+
%12 = ref_element_addr %7 : $_Box<T>, #_Box.value
48+
%13 = begin_access [modify] [dynamic] %12 : $*_Node<T>
49+
%14 = mark_must_check [assignable_but_not_consumable] %13 : $*_Node<T>
50+
copy_addr [take] %9 to %14 : $*_Node<T>
51+
end_access %13 : $*_Node<T>
52+
dealloc_stack %9 : $*_Node<T>
53+
end_borrow %7 : $_Box<T>
54+
%19 = copy_value %6 : $_Box<T>
55+
destroy_value %6 : $_Box<T>
56+
destroy_addr %3 : $*_Node<T>
57+
dealloc_stack %2 : $*_Node<T>
58+
return %19 : $_Box<T>
59+
}

test/SILOptimizer/moveonly_addresschecker_di_interactions.swift

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -86,3 +86,32 @@ func testEnumNoInitWithGenericPayload() {
8686
} // expected-error {{'self.init' isn't called on all paths before returning from initializer}}
8787
}
8888
}
89+
90+
// This test doesn't actually test anything today... but once the memory
91+
// lifetime verifier runs on ref_element_addr, this would assert in _Box<T>.init
92+
// since we were not setting the mark_must_check to initable_but_not_assignable.
93+
fileprivate final class _Box<T> {
94+
var value: _Node<T>
95+
96+
init(_ value: consuming _Node<T>) { self.value = value }
97+
}
98+
99+
struct _Node<T> : ~Copyable {
100+
var value: T
101+
var _next: ListEntry<T> = ListEntry<T>()
102+
103+
init(_ newValue: T) {
104+
value = newValue
105+
}
106+
}
107+
108+
/// A noncopyable box that contains the memory for a linked list node. Can be
109+
/// embedded within other noncopyable data structures to point at a Node data
110+
/// structure.
111+
///
112+
/// Internally uses a class as the actual box.
113+
struct ListEntry<T> : ~Copyable {
114+
private var innerBox: _Box<T>?
115+
116+
init() { innerBox = nil }
117+
}

0 commit comments

Comments
 (0)