Skip to content

Commit e5d6451

Browse files
committed
[move-only] If we have a guaranteed forwarding instruction with only trivial results, treat it as a liveness use.
The reason why we want to do this is that if we treat it as a true forwarding use, we will visit the uses of the trivial value and treat those as liveness uses. Since the trivial value is not tied to the lifetime of the underlying noncopyable value, this can be outside of the lifetime of said value causing a memory lifetime error. By just treating the guaranteed forwarding instruction with all trivial values as a liveness use, we avoid this problem. I added a SIL test, a Swift test, and an Interpreter test that validates this behavior. rdar://111497657 (cherry picked from commit 9b59588)
1 parent 1dcd4c0 commit e5d6451

File tree

5 files changed

+179
-12
lines changed

5 files changed

+179
-12
lines changed

lib/SILOptimizer/Mandatory/MoveOnlyBorrowToDestructureUtils.cpp

Lines changed: 36 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -369,16 +369,46 @@ bool Implementation::gatherUses(SILValue value) {
369369
continue;
370370
}
371371

372-
case OperandOwnership::GuaranteedForwarding:
373-
// Look through guaranteed forwarding.
372+
case OperandOwnership::GuaranteedForwarding: {
373+
// Look through guaranteed forwarding if we have at least one non-trivial
374+
// value. If we have all non-trivial values, treat this as a liveness use.
375+
SmallVector<SILValue, 8> forwardedValues;
376+
auto *fn = nextUse->getUser()->getFunction();
374377
ForwardingOperand(nextUse).visitForwardedValues([&](SILValue value) {
375-
for (auto *use : value->getUses()) {
376-
useWorklist.push_back(use);
377-
}
378+
if (value->getType().isTrivial(fn))
379+
return true;
380+
forwardedValues.push_back(value);
378381
return true;
379382
});
380-
continue;
381383

384+
if (forwardedValues.empty()) {
385+
auto leafRange =
386+
TypeTreeLeafTypeRange::get(nextUse->get(), getRootValue());
387+
if (!leafRange) {
388+
LLVM_DEBUG(llvm::dbgs() << " Failed to compute leaf range?!\n");
389+
return false;
390+
}
391+
392+
LLVM_DEBUG(llvm::dbgs() << " Found non lifetime ending use!\n");
393+
blocksToUses.insert(nextUse->getParentBlock(),
394+
{nextUse,
395+
{liveness.getNumSubElements(), *leafRange,
396+
false /*is lifetime ending*/}});
397+
liveness.updateForUse(nextUse->getUser(), *leafRange,
398+
false /*is lifetime ending*/);
399+
instToInterestingOperandIndexMap.insert(nextUse->getUser(), nextUse);
400+
continue;
401+
}
402+
403+
// If we had at least one forwarded value that is non-trivial, we need to
404+
// visit those uses.
405+
while (!forwardedValues.empty()) {
406+
for (auto *use : forwardedValues.pop_back_val()->getUses()) {
407+
useWorklist.push_back(use);
408+
}
409+
}
410+
continue;
411+
}
382412
case OperandOwnership::Borrow: {
383413
// Look through borrows.
384414
if (auto *bbi = dyn_cast<BeginBorrowInst>(nextUse->getUser())) {
Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
// RUN: %target-run-simple-swift(-Xfrontend -sil-verify-all)
2+
// RUN: %target-run-simple-swift(-O -Xfrontend -sil-verify-all)
3+
4+
enum FixedSizeQueueError : Error {
5+
case outOfSpace
6+
}
7+
8+
struct FixedSizeQueue<T> : ~Copyable {
9+
private var readOffset: Int = 0
10+
private var writeOffset: Int = 0
11+
private var ptr: UnsafeMutableBufferPointer<T>
12+
13+
init(size: Int) {
14+
ptr = UnsafeMutableBufferPointer.allocate(capacity: size)
15+
}
16+
17+
deinit {
18+
ptr.deinitialize().deallocate()
19+
}
20+
21+
mutating func read() -> T? {
22+
if readOffset == writeOffset {
23+
return nil
24+
}
25+
26+
let x = ptr[readOffset]
27+
readOffset = (readOffset + 1) % ptr.count
28+
return x
29+
}
30+
31+
mutating func write(_ element: T) throws {
32+
if ((writeOffset + 1) % ptr.count) == readOffset {
33+
throw FixedSizeQueueError.outOfSpace
34+
}
35+
ptr[writeOffset] = element
36+
writeOffset = (writeOffset + 1) % ptr.count
37+
}
38+
39+
// Drain the queue... returning the value.
40+
consuming func drain() -> UnsafeMutableBufferPointer<T> {
41+
let buffer = self.ptr
42+
discard self
43+
return buffer
44+
}
45+
}
46+
47+
func main() throws {
48+
var x = FixedSizeQueue<Int>(size: 10)
49+
precondition(x.read() == nil)
50+
try x.write(0)
51+
try x.write(9)
52+
try x.write(1)
53+
try x.write(3)
54+
precondition(x.read() == 0)
55+
precondition(x.read() == 9)
56+
precondition(x.read() == 1)
57+
precondition(x.read() == 3)
58+
59+
let d = x.drain()
60+
precondition(d[0] == 0)
61+
precondition(d[1] == 9)
62+
precondition(d[2] == 1)
63+
precondition(d[3] == 3)
64+
}
65+
66+
try main()

test/SILOptimizer/moveonly_addresschecker.sil

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -677,3 +677,58 @@ bb0:
677677
%21 = tuple ()
678678
return %21 : $()
679679
}
680+
681+
struct FixedSizeQueue : ~Copyable {
682+
var ptr: Builtin.Int32
683+
}
684+
685+
sil @get_fixedsize_queue : $@convention(thin) () -> @owned FixedSizeQueue
686+
687+
// CHECK-LABEL: sil [ossa] @test_return_trivial_type : $@convention(thin) (@owned FixedSizeQueue) -> Builtin.Int32 {
688+
// CHECK: bb0([[ARG:%.*]] : @owned $
689+
// CHECK: [[ALLOC:%.*]] = alloc_stack [lexical] $FixedSizeQueue
690+
// CHECK: store [[ARG]] to [init] [[ALLOC]]
691+
// CHECK: [[ACCESS:%.*]] = begin_access [modify] [static] [[ALLOC]]
692+
// CHECK: [[ALLOC2:%.*]] = alloc_stack $FixedSizeQueue
693+
// CHECK: copy_addr [take] [[ACCESS]] to [init] [[ALLOC2]]
694+
// CHECK: [[LOADED:%.*]] = load_borrow [[ALLOC2]]
695+
// CHECK: end_access [[ACCESS]]
696+
// CHECK: [[BORROW:%.*]] = begin_borrow [[LOADED]]
697+
// CHECK: [[TRIVIAL_VALUE:%.*]] = struct_extract [[BORROW]]
698+
// CHECK: end_borrow [[BORROW]]
699+
// CHECK: end_borrow [[LOADED]]
700+
// CHECK: destroy_addr [[ALLOC2]]
701+
// CHECK: dealloc_stack [[ALLOC2]]
702+
// CHECK: [[NEW_VALUE:%.*]] = apply {{%.*}}()
703+
// CHECK: [[ACCESS:%.*]] = begin_access [modify] [static] [[ALLOC]]
704+
// CHECK: store [[NEW_VALUE]] to [init] [[ACCESS]]
705+
// CHECK: end_access [[ACCESS]]
706+
// CHECK: destroy_addr [[ALLOC]]
707+
// CHECK: dealloc_stack [[ALLOC]]
708+
// CHECK: return [[TRIVIAL_VALUE]]
709+
// CHECK: } // end sil function 'test_return_trivial_type'
710+
sil [ossa] @test_return_trivial_type : $@convention(thin) (@owned FixedSizeQueue) -> Builtin.Int32 {
711+
bb0(%0 : @owned $FixedSizeQueue):
712+
%1 = alloc_stack [lexical] $FixedSizeQueue, var, name "self", implicit
713+
%2 = mark_must_check [consumable_and_assignable] %1 : $*FixedSizeQueue
714+
store %0 to [init] %2 : $*FixedSizeQueue
715+
%4 = begin_access [modify] [static] %2 : $*FixedSizeQueue
716+
%5 = alloc_stack $FixedSizeQueue
717+
%6 = mark_must_check [consumable_and_assignable] %5 : $*FixedSizeQueue
718+
copy_addr %4 to [init] %6 : $*FixedSizeQueue
719+
%8 = load [take] %6 : $*FixedSizeQueue
720+
end_access %4 : $*FixedSizeQueue
721+
%10 = begin_borrow %8 : $FixedSizeQueue
722+
%11 = struct_extract %10 : $FixedSizeQueue, #FixedSizeQueue.ptr
723+
end_borrow %10 : $FixedSizeQueue
724+
destroy_value %8 : $FixedSizeQueue
725+
dealloc_stack %5 : $*FixedSizeQueue
726+
%21 = function_ref @get_fixedsize_queue : $@convention(thin) () -> @owned FixedSizeQueue
727+
%22 = apply %21() : $@convention(thin) () -> @owned FixedSizeQueue
728+
%23 = begin_access [modify] [static] %2 : $*FixedSizeQueue
729+
store %22 to [assign] %23 : $*FixedSizeQueue
730+
end_access %23 : $*FixedSizeQueue
731+
destroy_addr %2 : $*FixedSizeQueue
732+
dealloc_stack %1 : $*FixedSizeQueue
733+
return %11 : $Builtin.Int32
734+
}
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
// RUN: %target-swift-emit-sil -O -sil-verify-all -verify -enable-experimental-feature MoveOnlyPartialConsumption -enable-experimental-feature NoImplicitCopy -enable-experimental-feature MoveOnlyClasses %s
2+
3+
// This file contains tests that used to crash due to verifier errors. It must
4+
// be separate from moveonly_addresschecker_diagnostics since when we fail on
5+
// the diagnostics in that file, we do not actually run the verifier.
6+
7+
struct TestTrivialReturnValue : ~Copyable {
8+
var i: Int = 5
9+
10+
// We used to error on return buffer.
11+
consuming func drain() -> Int {
12+
let buffer = (consume self).i
13+
self = .init(i: 5)
14+
return buffer
15+
}
16+
}

test/SILOptimizer/moveonly_borrow_to_destructure_transform.sil

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -147,12 +147,12 @@ bb0(%0 : @owned $MoveOnlyKlass):
147147
// CHECK: [[ARG_COPY:%.*]] = copy_value [[ARG]]
148148
// CHECK: [[MARKED_ARG:%.*]] = mark_must_check [no_consume_or_assign] [[ARG_COPY]]
149149
// CHECK: [[MARKED_ARG_COPY:%.*]] = copy_value [[MARKED_ARG]]
150-
// CHECK: [[MISC_USE:%.*]] = function_ref @misc_use : $@convention(thin) () -> ()
151-
// CHECK: apply [[MISC_USE]]()
152150
// CHECK: [[BORROW:%.*]] = begin_borrow [[MARKED_ARG_COPY]]
153151
// CHECK: [[EXT:%.*]] = struct_extract [[BORROW]]
154152
// CHECK: end_borrow [[BORROW]]
155-
// CHECK: destroy_value [[MARKED_ARG_COPY]]
153+
// CHECK: [[MISC_USE:%.*]] = function_ref @misc_use : $@convention(thin) () -> ()
154+
// CHECK: apply [[MISC_USE]]()
155+
// CHECK: destroy_value [[MARKED_ARG]]
156156
// CHECK: return [[EXT]]
157157
// CHECK: } // end sil function 'test_return_of_extracted_trivial_type'
158158
sil [ossa] @test_return_of_extracted_trivial_type : $@convention(thin) (@guaranteed SingleIntContainingStruct) -> Builtin.Int32 {
@@ -862,9 +862,9 @@ bbCont:
862862
// CHECK: [[BB_E_SECOND]]([[BBARG:%.*]] : @owned
863863
// CHECK: [[BBARG_BORROW:%.*]] = begin_borrow [[BBARG]]
864864
// CHECK: [[BBARG_BORROW_EXT:%.*]] = struct_extract [[BBARG_BORROW]]
865-
// CHECK: apply {{%.*}}([[BBARG_BORROW_EXT]])
866865
// CHECK: end_borrow [[BBARG_BORROW]]
867866
// CHECK: destroy_value [[BBARG]]
867+
// CHECK: apply {{%.*}}([[BBARG_BORROW_EXT]])
868868
// CHECK: br [[BB_CONT]]
869869
//
870870
// CHECK: [[BB_E_THIRD]]([[BBARG:%.*]] : @owned
@@ -955,9 +955,9 @@ bbCont:
955955
// CHECK: [[BB_E_SECOND]]([[BBARG:%.*]] : @owned
956956
// CHECK: [[BBARG_BORROW:%.*]] = begin_borrow [[BBARG]]
957957
// CHECK: [[BBARG_BORROW_EXT:%.*]] = struct_extract [[BBARG_BORROW]]
958-
// CHECK: apply {{%.*}}([[BBARG_BORROW_EXT]])
959958
// CHECK: end_borrow [[BBARG_BORROW]]
960959
// CHECK: destroy_value [[BBARG]]
960+
// CHECK: apply {{%.*}}([[BBARG_BORROW_EXT]])
961961
// CHECK: br [[BB_CONT]]
962962
//
963963
// CHECK: [[BB_E_THIRD]]([[BBARG:%.*]] : @owned
@@ -1076,4 +1076,4 @@ bb2:
10761076
end_borrow %3 : $KlassPair
10771077
destroy_value %2 : $KlassPair
10781078
unwind
1079-
}
1079+
}

0 commit comments

Comments
 (0)