Skip to content

Commit ff627e5

Browse files
committed
DI: Place cleanups for partially-initialized non-Copyable values on the mark_unresolved_noncopyable marker.
This ensures that move checking sees the cleanups when determining a value's lifetime and doesn't try to insert its own cleanup leading to a double destroy. Fixes #85063 | rdar://163194098.
1 parent 4c9a9ca commit ff627e5

File tree

4 files changed

+177
-13
lines changed

4 files changed

+177
-13
lines changed

lib/SILOptimizer/Mandatory/DIMemoryUseCollector.cpp

Lines changed: 24 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -39,17 +39,22 @@ static bool isVariableOrResult(MarkUninitializedInst *MUI) {
3939
static void gatherDestroysOfContainer(const DIMemoryObjectInfo &memoryInfo,
4040
DIElementUseInfo &useInfo) {
4141
auto *uninitMemory = memoryInfo.getUninitializedValue();
42+
auto origUninitMemory = uninitMemory;
43+
44+
if (auto mui = dyn_cast<MarkUnresolvedNonCopyableValueInst>(uninitMemory)) {
45+
origUninitMemory = cast<SingleValueInstruction>(mui->getOperand());
46+
}
4247

4348
// The uninitMemory must be used on an alloc_box, alloc_stack, or global_addr.
4449
// If we have an alloc_stack or a global_addr, there is nothing further to do.
45-
if (isa<AllocStackInst>(uninitMemory->getOperand(0)) ||
46-
isa<GlobalAddrInst>(uninitMemory->getOperand(0)) ||
47-
isa<SILArgument>(uninitMemory->getOperand(0)) ||
50+
if (isa<AllocStackInst>(origUninitMemory->getOperand(0)) ||
51+
isa<GlobalAddrInst>(origUninitMemory->getOperand(0)) ||
52+
isa<SILArgument>(origUninitMemory->getOperand(0)) ||
4853
// FIXME: We only support pointer to address here to not break LLDB. It is
4954
// important that long term we get rid of this since this is a situation
5055
// where LLDB is breaking SILGen/DI invariants by not creating a new
5156
// independent stack location for the pointer to address.
52-
isa<PointerToAddressInst>(uninitMemory->getOperand(0))) {
57+
isa<PointerToAddressInst>(origUninitMemory->getOperand(0))) {
5358
return;
5459
}
5560

@@ -58,8 +63,8 @@ static void gatherDestroysOfContainer(const DIMemoryObjectInfo &memoryInfo,
5863
//
5964
// TODO: This should really be tracked separately from other destroys so that
6065
// we distinguish the lifetime of the container from the value itself.
61-
assert(isa<ProjectBoxInst>(uninitMemory));
62-
auto value = uninitMemory->getOperand(0);
66+
assert(isa<ProjectBoxInst>(origUninitMemory));
67+
auto value = origUninitMemory->getOperand(0);
6368
if (auto *bbi = dyn_cast<BeginBorrowInst>(value)) {
6469
value = bbi->getOperand();
6570
}
@@ -181,17 +186,23 @@ SILInstruction *DIMemoryObjectInfo::getFunctionEntryPoint() const {
181186

182187
static SingleValueInstruction *
183188
getUninitializedValue(MarkUninitializedInst *MemoryInst) {
184-
SILValue inst = MemoryInst;
185-
if (auto *bbi = MemoryInst->getSingleUserOfType<BeginBorrowInst>()) {
186-
inst = bbi;
189+
SingleValueInstruction *inst = MemoryInst;
190+
SILValue projectBoxUser = inst;
191+
192+
// Check for a project_box instruction (possibly via a borrow).
193+
if (auto *bbi = projectBoxUser->getSingleUserOfType<BeginBorrowInst>()) {
194+
projectBoxUser = bbi;
195+
}
196+
if (auto pbi = projectBoxUser->getSingleUserOfType<ProjectBoxInst>()) {
197+
inst = pbi;
187198
}
188199

189-
if (SingleValueInstruction *svi =
190-
inst->getSingleUserOfType<ProjectBoxInst>()) {
191-
return svi;
200+
// Access move-only values through their marker.
201+
if (auto mui = inst->getSingleUserOfType<MarkUnresolvedNonCopyableValueInst>()) {
202+
inst = mui;
192203
}
193204

194-
return MemoryInst;
205+
return inst;
195206
}
196207

197208
SingleValueInstruction *DIMemoryObjectInfo::getUninitializedValue() const {
Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
// RUN: %target-run-simple-swift
2+
// REQUIRES: executable_test
3+
4+
struct Butt: Error {}
5+
6+
struct Inside: ~Copyable {
7+
init() throws { throw Butt() }
8+
}
9+
10+
class C {
11+
deinit { print("destroying") }
12+
}
13+
14+
struct Outside: ~Copyable {
15+
let instance: Inside
16+
var c: C
17+
18+
init() throws {
19+
c = C()
20+
let instance = try Inside()
21+
self.instance = instance
22+
}
23+
}
24+
25+
// CHECK: begin
26+
// CHECK-NEXT: destroying
27+
// CHECK-NEXT: caught
28+
// CHECK-NEXT: end
29+
30+
func test() {
31+
print("begin")
32+
do {
33+
_ = try Outside()
34+
} catch {
35+
print("caught")
36+
}
37+
print("end")
38+
}
39+
test()
40+
Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
// RUN: %target-sil-opt -module-name definite_init_moveonly -enable-sil-verify-all -definite-init %s | %FileCheck %s
2+
3+
import Swift
4+
5+
class C {
6+
init()
7+
}
8+
9+
struct S: ~Copyable {
10+
var c1: C
11+
var c2: C
12+
}
13+
14+
// Ensure that, when DI introduces cleanups for partially initialized move-
15+
// only values, the cleanups are applied under the `mark_unresolved`
16+
// instruction, so that the move checker properly accounts for the cleanups.
17+
// #85063 | rdar://163194098
18+
19+
// CHECK-LABEL: sil [ossa] @$test
20+
sil [ossa] @$test : $@convention(thin) (@owned C) -> () {
21+
entry(%c : @owned $C):
22+
%s = alloc_stack [lexical] [var_decl] $S, var, name "butts", type $S
23+
%s_marked = mark_uninitialized [rootself] %s
24+
// CHECK: [[S_MOVEONLY:%.*]] = mark_unresolved_non_copyable_value
25+
%s_moveonly = mark_unresolved_non_copyable_value [consumable_and_assignable] %s_marked
26+
%s_access = begin_access [modify] [static] %s_moveonly
27+
%c1 = struct_element_addr %s_access, #S.c1
28+
assign %c to %c1
29+
end_access %s_access
30+
// CHECK: br [[NEXT:bb[0-9]+]]
31+
br next
32+
33+
// CHECK: [[NEXT]]:
34+
// CHECK: [[FIELD_TO_DESTROY:%.*]] = struct_element_addr [[S_MOVEONLY]], #S.c1
35+
// CHECK: destroy_addr [[FIELD_TO_DESTROY]]
36+
next:
37+
destroy_addr %s_moveonly
38+
dealloc_stack %s
39+
return undef : $()
40+
}
Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,73 @@
1+
// RUN: %target-swift-frontend -enable-library-evolution -emit-sil -verify %s
2+
3+
public struct Consumable: ~Copyable {}
4+
5+
public func consume(_: consuming Consumable) {}
6+
7+
public struct NotFrozen: ~Copyable {
8+
public var field: Consumable
9+
10+
public consuming func notInlinable() {
11+
consume(field)
12+
}
13+
14+
@inlinable
15+
public consuming func inlinable() {
16+
// expected-error @+1 {{consumed but not reinitialized}}
17+
consume(field) // expected-note{{}}
18+
}
19+
20+
@_alwaysEmitIntoClient
21+
public consuming func aeic() {
22+
// expected-error @+1 {{consumed but not reinitialized}}
23+
consume(field) // expected-note{{}}
24+
}
25+
26+
@_transparent
27+
public consuming func transparent() {
28+
// expected-error @+1 {{consumed but not reinitialized}}
29+
consume(field) // expected-note{{}}
30+
}
31+
}
32+
33+
@frozen
34+
public struct Frozen: ~Copyable {
35+
public var field: Consumable
36+
37+
public consuming func notInlinable() {
38+
consume(field)
39+
}
40+
41+
@inlinable
42+
public consuming func inlinable() {
43+
consume(field)
44+
}
45+
}
46+
47+
@usableFromInline
48+
internal struct NotFrozenUFI: ~Copyable {
49+
public var field: Consumable
50+
51+
public consuming func notInlinable() {
52+
consume(field)
53+
}
54+
55+
@inlinable
56+
public consuming func inlinable() {
57+
// expected-error @+1 {{consumed but not reinitialized}}
58+
consume(field) // expected-note{{}}
59+
}
60+
61+
@_alwaysEmitIntoClient
62+
public consuming func aeic() {
63+
// expected-error @+1 {{consumed but not reinitialized}}
64+
consume(field) // expected-note{{}}
65+
}
66+
67+
@_transparent
68+
public consuming func transparent() {
69+
// expected-error @+1 {{consumed but not reinitialized}}
70+
consume(field) // expected-note{{}}
71+
}
72+
}
73+

0 commit comments

Comments
 (0)