Skip to content

Commit e80df24

Browse files
authored
Merge pull request #71762 from jckarter/borrowing-switch-10
Consume switch subjects under an opaque sub-access.
2 parents 93d48b5 + 0491755 commit e80df24

File tree

6 files changed

+157
-30
lines changed

6 files changed

+157
-30
lines changed

lib/SILGen/SILGenPattern.cpp

Lines changed: 19 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1289,8 +1289,8 @@ void PatternMatchEmission::bindIrrefutablePatterns(const ClauseRow &row,
12891289

12901290
void PatternMatchEmission::bindIrrefutableBorrows(const ClauseRow &row,
12911291
ArgArray args,
1292-
bool forIrrefutableRow,
1293-
bool hasMultipleItems) {
1292+
bool forIrrefutableRow,
1293+
bool hasMultipleItems) {
12941294
assert(row.columns() == args.size());
12951295
for (unsigned i = 0, e = args.size(); i != e; ++i) {
12961296
if (!row[i]) // We use null patterns to mean artificial AnyPatterns
@@ -1446,7 +1446,7 @@ void PatternMatchEmission::bindBorrow(Pattern *pattern, VarDecl *var,
14461446
auto access = SGF.B.createBeginAccess(pattern, bindValue.getValue(),
14471447
SILAccessKind::Read,
14481448
SILAccessEnforcement::Static,
1449-
false, false);
1449+
/*no nested conflict*/ true, false);
14501450
SGF.Cleanups.pushCleanup<EndAccessCleanup>(access);
14511451
bindValue = ManagedValue::forBorrowedAddressRValue(access);
14521452
}
@@ -2797,11 +2797,23 @@ void PatternMatchEmission::emitDestructiveCaseBlocks() {
27972797
// Create a scope to break down the subject value.
27982798
Scope caseScope(SGF, pattern);
27992799

2800+
// If the subject value is in memory, enter a deinit access for the memory.
2801+
// This saves the move-only-checker from trying to analyze the payload
2802+
// decomposition as a potential partial consume. We always fully consume
2803+
// the subject on this path.
2804+
auto origSubject = NoncopyableConsumableValue.forward(SGF);
2805+
if (origSubject->getType().isAddress()) {
2806+
origSubject = SGF.B.createBeginAccess(pattern, origSubject,
2807+
SILAccessKind::Deinit,
2808+
SILAccessEnforcement::Static,
2809+
/*no nested conflict*/ true, false);
2810+
SGF.Cleanups.pushCleanup<EndAccessCleanup>(origSubject);
2811+
}
2812+
28002813
// Clone the original subject's cleanup state so that it will be reliably
28012814
// consumed in this scope, while leaving the original for other case
28022815
// blocks to re-consume.
2803-
ManagedValue subject = SGF.emitManagedRValueWithCleanup(
2804-
NoncopyableConsumableValue.forward(SGF));
2816+
ManagedValue subject = SGF.emitManagedRValueWithCleanup(origSubject);
28052817

28062818
// TODO: handle fallthroughs and multiple cases bindings
28072819
// In those cases we'd need to forward bindings through the shared case
@@ -3428,7 +3440,8 @@ void SILGenFunction::emitSwitchStmt(SwitchStmt *S) {
34283440
// match is not allowed to modify it.
34293441
auto access = B.createBeginAccess(S, subjectMV.getValue(),
34303442
SILAccessKind::Read,
3431-
SILAccessEnforcement::Static, false, false);
3443+
SILAccessEnforcement::Static,
3444+
/*no nested conflict*/ true, false);
34323445
Cleanups.pushCleanup<EndAccessCleanup>(access);
34333446
subjectMV = ManagedValue::forBorrowedAddressRValue(access);
34343447
}

lib/SILOptimizer/Mandatory/MoveOnlyAddressCheckerUtils.cpp

Lines changed: 27 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -1978,17 +1978,13 @@ struct GatherUsesVisitor : public TransitiveAddressWalker<GatherUsesVisitor> {
19781978
} // end anonymous namespace
19791979

19801980
bool GatherUsesVisitor::visitTransitiveUseAsEndPointUse(Operand *op) {
1981-
// If an access is checked by its own strict mark_unresolved_noncopyable
1982-
// instruction, then treat the access as an opaque borrowing use from the
1983-
// outside.
1981+
// If an access is static and marked as "no nested conflict", we use that
1982+
// in switch codegen to mark an opaque sub-access that move-only checking
1983+
// should not look through.
19841984
if (auto ba = dyn_cast<BeginAccessInst>(op->getUser())) {
1985-
for (auto accessUse : ba->getUses()) {
1986-
if (auto mark
1987-
= dyn_cast<MarkUnresolvedNonCopyableValueInst>(accessUse->getUser())) {
1988-
if (mark->isStrict()) {
1989-
return true;
1990-
}
1991-
}
1985+
if (ba->getEnforcement() == SILAccessEnforcement::Static
1986+
&& ba->hasNoNestedConflict()) {
1987+
return true;
19921988
}
19931989
}
19941990
return false;
@@ -2548,20 +2544,29 @@ bool GatherUsesVisitor::visitUse(Operand *op) {
25482544
return true;
25492545
}
25502546

2551-
// Treat an opaque read access as a borrow liveness use for the duration
2552-
// of the access.
25532547
if (auto *access = dyn_cast<BeginAccessInst>(op->getUser())) {
2554-
assert(access->getAccessKind() == SILAccessKind::Read);
2555-
LLVM_DEBUG(llvm::dbgs() << "begin_access use\n");
2556-
2557-
auto leafRange = TypeTreeLeafTypeRange::get(op->get(), getRootAddress());
2558-
if (!leafRange) {
2559-
LLVM_DEBUG(llvm::dbgs() << "Failed to form leaf type range!\n");
2560-
return false;
2561-
}
2548+
switch (access->getAccessKind()) {
2549+
// Treat an opaque read access as a borrow liveness use for the duration
2550+
// of the access.
2551+
case SILAccessKind::Read: {
2552+
LLVM_DEBUG(llvm::dbgs() << "begin_access [read]\n");
2553+
2554+
auto leafRange = TypeTreeLeafTypeRange::get(op->get(), getRootAddress());
2555+
if (!leafRange) {
2556+
LLVM_DEBUG(llvm::dbgs() << "Failed to form leaf type range!\n");
2557+
return false;
2558+
}
25622559

2563-
useState.recordLivenessUse(user, *leafRange);
2564-
return true;
2560+
useState.recordLivenessUse(user, *leafRange);
2561+
return true;
2562+
}
2563+
// Treat a deinit access as a consume of the entire value.
2564+
case SILAccessKind::Deinit:
2565+
llvm_unreachable("should have been handled by `memInstMustConsume`");
2566+
case SILAccessKind::Init:
2567+
case SILAccessKind::Modify:
2568+
llvm_unreachable("should look through these kinds of accesses currently");
2569+
}
25652570
}
25662571

25672572
// If we don't fit into any of those categories, just track as a liveness

lib/SILOptimizer/Mandatory/MoveOnlyUtils.cpp

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -313,6 +313,8 @@ bool noncopyable::memInstMustConsume(Operand *memOper) {
313313
case SILInstructionKind::LoadInst:
314314
return cast<LoadInst>(memInst)->getOwnershipQualifier() ==
315315
LoadOwnershipQualifier::Take;
316+
case SILInstructionKind::BeginAccessInst:
317+
return cast<BeginAccessInst>(memInst)->getAccessKind() == SILAccessKind::Deinit;
316318
}
317319
}
318320

test/SILOptimizer/moveonly_borrowing_switch.swift

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -237,8 +237,8 @@ func testOuterAO(consuming bas: consuming AOBas) { // expected-error{{'bas' used
237237
break
238238
}
239239

240-
switch bas {
241-
case _borrowing x: // expected-warning{{}} expected-note{{used here}}
240+
switch bas { // expected-note{{used here}}
241+
case _borrowing x: // expected-warning{{}}
242242
break
243243
}
244244
}
Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,80 @@
1+
// RUN: %target-swift-frontend -emit-sil -enable-experimental-feature NoncopyableGenerics -enable-experimental-feature BorrowingSwitch -enable-experimental-feature MoveOnlyPartialConsumption -verify %s
2+
struct MyPointer<Wrapped: ~Copyable>: Copyable {
3+
var v: UnsafeMutablePointer<Int>
4+
5+
static func allocate(capacity: Int) -> Self {
6+
fatalError()
7+
}
8+
9+
func initialize(to: consuming Wrapped) {
10+
}
11+
func deinitialize(count: Int) {
12+
}
13+
func deallocate() {
14+
}
15+
func move() -> Wrapped {
16+
fatalError()
17+
}
18+
}
19+
20+
struct Box<Wrapped: ~Copyable>: ~Copyable {
21+
private let _pointer: MyPointer<Wrapped>
22+
23+
init(_ element: consuming Wrapped) {
24+
_pointer = .allocate(capacity: 1)
25+
print("allocating",_pointer)
26+
_pointer.initialize(to: element)
27+
}
28+
29+
deinit {
30+
print("deallocating",_pointer)
31+
_pointer.deinitialize(count: 1)
32+
_pointer.deallocate()
33+
}
34+
35+
consuming func move() -> Wrapped {
36+
let wrapped = _pointer.move()
37+
print("deallocating", _pointer)
38+
_pointer.deallocate()
39+
discard self
40+
return wrapped
41+
}
42+
}
43+
44+
45+
46+
47+
48+
49+
50+
51+
52+
enum List<Element: ~Copyable>: ~Copyable {
53+
case empty
54+
case node(Element, Box<List>)
55+
}
56+
57+
extension List {
58+
init() { self = .empty }
59+
60+
mutating func push(_ element: consuming Element) {
61+
self = .node(element, Box(self))
62+
}
63+
64+
mutating func pop() -> Element {
65+
switch self {
66+
case .node(let element, let box):
67+
self = box.move()
68+
return element
69+
case .empty:
70+
fatalError()
71+
}
72+
}
73+
74+
var isEmpty: Bool {
75+
switch self {
76+
case .empty: true
77+
case .node: false
78+
}
79+
}
80+
}
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
// RUN: %target-swift-frontend -emit-sil -enable-experimental-feature BorrowingSwitch -enable-experimental-feature MoveOnlyPartialConsumption -verify %s
2+
3+
struct Box: ~Copyable {
4+
let ptr: UnsafeMutablePointer<Int>
5+
deinit { print("butt") }
6+
}
7+
8+
enum List<Element>: ~Copyable {
9+
case end
10+
case more(Element, Box)
11+
}
12+
13+
extension List {
14+
init() {
15+
self = .end
16+
}
17+
18+
mutating func pop() -> Element {
19+
switch consume self {
20+
case .more(let element, let box): // expected-warning{{}}
21+
self = .end
22+
return element
23+
case .end: fatalError()
24+
}
25+
}
26+
}
27+

0 commit comments

Comments
 (0)