Skip to content

Commit b3bbaec

Browse files
authored
Merge pull request #64366 from gottesmm/pr-63fcd3307c126048533239ab77552bb5acdc728c
[move-only] Treat mark_must_check assignable_but_not_consumable as ending initialized
2 parents c71cdcc + 02a12d4 commit b3bbaec

File tree

6 files changed

+184
-46
lines changed

6 files changed

+184
-46
lines changed

lib/SILGen/SILGenLValue.cpp

+14-11
Original file line numberDiff line numberDiff line change
@@ -790,17 +790,6 @@ namespace {
790790
SGF.B.createRefElementAddr(loc, base.getUnmanagedValue(),
791791
Field, SubstFieldType);
792792

793-
// If we have a move only type...
794-
if (result->getType().isMoveOnly()) {
795-
auto checkKind =
796-
MarkMustCheckInst::CheckKind::AssignableButNotConsumable;
797-
if (isReadAccess(getAccessKind())) {
798-
// Add a mark_must_check [no_consume_or_assign].
799-
checkKind = MarkMustCheckInst::CheckKind::NoConsumeOrAssign;
800-
}
801-
result = SGF.B.createMarkMustCheckInst(loc, result, checkKind);
802-
}
803-
804793
// Avoid emitting access markers completely for non-accesses or immutable
805794
// declarations. Access marker verification is aware of these cases.
806795
if (!IsNonAccessing && !Field->isLet()) {
@@ -811,6 +800,20 @@ namespace {
811800
}
812801
}
813802

803+
// If we have a move only type, add a marker.
804+
//
805+
// NOTE: We purposely do this on the access itself to ensure that when we
806+
// hoist destroy_addr, they stay within the access scope.
807+
if (result->getType().isMoveOnly()) {
808+
auto checkKind =
809+
MarkMustCheckInst::CheckKind::AssignableButNotConsumable;
810+
if (isReadAccess(getAccessKind())) {
811+
// Add a mark_must_check [no_consume_or_assign].
812+
checkKind = MarkMustCheckInst::CheckKind::NoConsumeOrAssign;
813+
}
814+
result = SGF.B.createMarkMustCheckInst(loc, result, checkKind);
815+
}
816+
814817
return ManagedValue::forLValue(result);
815818
}
816819

lib/SILOptimizer/Mandatory/MoveOnlyAddressCheckerUtils.cpp

+45-25
Original file line numberDiff line numberDiff line change
@@ -449,34 +449,54 @@ static bool memInstMustConsume(Operand *memOper) {
449449
/// These are cases where we want to treat the end of the function as a liveness
450450
/// use to ensure that we reinitialize \p value before the end of the function
451451
/// if we consume \p value in the function body.
452-
static bool isInOutDefThatNeedsEndOfFunctionLiveness(SILValue value) {
453-
if (auto *fArg = dyn_cast<SILFunctionArgument>(value)) {
454-
switch (fArg->getArgumentConvention()) {
455-
case SILArgumentConvention::Indirect_In:
456-
case SILArgumentConvention::Indirect_Out:
457-
case SILArgumentConvention::Indirect_In_Guaranteed:
458-
case SILArgumentConvention::Direct_Guaranteed:
459-
case SILArgumentConvention::Direct_Owned:
460-
case SILArgumentConvention::Direct_Unowned:
461-
case SILArgumentConvention::Pack_Guaranteed:
462-
case SILArgumentConvention::Pack_Owned:
463-
case SILArgumentConvention::Pack_Out:
464-
return false;
465-
case SILArgumentConvention::Indirect_Inout:
466-
case SILArgumentConvention::Indirect_InoutAliasable:
467-
case SILArgumentConvention::Pack_Inout:
468-
LLVM_DEBUG(llvm::dbgs() << "Found inout arg: " << *fArg);
469-
return true;
452+
static bool isInOutDefThatNeedsEndOfFunctionLiveness(MarkMustCheckInst *markedAddr) {
453+
SILValue operand = markedAddr->getOperand();
454+
455+
// Check for inout types of arguments that are marked with consumable and
456+
// assignable.
457+
if (markedAddr->getCheckKind() ==
458+
MarkMustCheckInst::CheckKind::ConsumableAndAssignable) {
459+
if (auto *fArg = dyn_cast<SILFunctionArgument>(operand)) {
460+
switch (fArg->getArgumentConvention()) {
461+
case SILArgumentConvention::Indirect_In:
462+
case SILArgumentConvention::Indirect_Out:
463+
case SILArgumentConvention::Indirect_In_Guaranteed:
464+
case SILArgumentConvention::Direct_Guaranteed:
465+
case SILArgumentConvention::Direct_Owned:
466+
case SILArgumentConvention::Direct_Unowned:
467+
case SILArgumentConvention::Pack_Guaranteed:
468+
case SILArgumentConvention::Pack_Owned:
469+
case SILArgumentConvention::Pack_Out:
470+
return false;
471+
case SILArgumentConvention::Indirect_Inout:
472+
case SILArgumentConvention::Indirect_InoutAliasable:
473+
case SILArgumentConvention::Pack_Inout:
474+
LLVM_DEBUG(llvm::dbgs() << "Found inout arg: " << *fArg);
475+
return true;
476+
}
470477
}
471478
}
472479

473-
if (auto *pbi = dyn_cast<ProjectBoxInst>(value)) {
474-
if (auto *fArg = dyn_cast<SILFunctionArgument>(pbi->getOperand())) {
475-
if (!fArg->isClosureCapture())
476-
return false;
477-
LLVM_DEBUG(llvm::dbgs() << "Found inout arg: " << *fArg);
478-
return true;
480+
// See if we have an assignable_but_not_consumable from a project_box +
481+
// function_argument. In this case, the value must be live at the end of the
482+
// use, similar to an inout parameter.
483+
//
484+
// TODO: Rather than using a terminator, we might be able to use the
485+
// end_access of the access marker instead. That would slightly change the
486+
// model and this is semantically ok today.
487+
if (markedAddr->getCheckKind() ==
488+
MarkMustCheckInst::CheckKind::AssignableButNotConsumable) {
489+
if (auto *pbi = dyn_cast<ProjectBoxInst>(stripAccessMarkers(operand))) {
490+
if (auto *fArg = dyn_cast<SILFunctionArgument>(pbi->getOperand())) {
491+
if (!fArg->isClosureCapture())
492+
return false;
493+
LLVM_DEBUG(llvm::dbgs() << "Found inout arg: " << *fArg);
494+
return true;
495+
}
479496
}
497+
498+
if (isa<RefElementAddrInst>(stripAccessMarkers(operand)))
499+
return true;
480500
}
481501

482502
return false;
@@ -592,7 +612,7 @@ struct UseState {
592612
initializeLiveness(FieldSensitiveMultiDefPrunedLiveRange &prunedLiveness);
593613

594614
void initializeInOutTermUsers() {
595-
if (!isInOutDefThatNeedsEndOfFunctionLiveness(address->getOperand()))
615+
if (!isInOutDefThatNeedsEndOfFunctionLiveness(address))
596616
return;
597617

598618
SmallVector<SILBasicBlock *, 8> exitBlocks;
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
// RUN: %target-run-simple-swift(-Xfrontend -enable-experimental-move-only) | %FileCheck %s
2+
3+
// REQUIRES: executable_test
4+
5+
@_moveOnly
6+
struct MO {
7+
var x: Int
8+
deinit { print("dying \(x)") }
9+
}
10+
11+
var f: () -> () = {}
12+
13+
func foo(x: consuming MO) {
14+
var counter = 42
15+
f = {
16+
x = MO(x: counter)
17+
counter += 1
18+
}
19+
}
20+
21+
func main() {
22+
let x = MO(x: 69)
23+
24+
// CHECK: a
25+
print("a")
26+
foo(x: x)
27+
// CHECK-NEXT: b
28+
print("b")
29+
// CHECK-NEXT: dying 69
30+
// CHECK-NEXT: c
31+
f()
32+
print("c")
33+
// CHECK-NEXT: dying 42
34+
// CHECK-NEXT: d
35+
f()
36+
print("d")
37+
// CHECK-NEXT: dying 43
38+
// CHECK-NEXT: e
39+
f()
40+
print("e")
41+
// CHECK-NEXT: dying 44
42+
// CHECK-NEXT: f
43+
f = {}
44+
print("f")
45+
}
46+
main()
47+
print("done")

test/SILGen/moveonly.swift

+19-3
Original file line numberDiff line numberDiff line change
@@ -537,9 +537,9 @@ func moveOnlyStructCopyableStructCopyableStructCopyableKlassNonConsumingUse() {
537537
// CHECK: end_access [[ACCESS]]
538538
// CHECK: [[BORROWED_COPYABLE_KLASS:%.*]] = begin_borrow [[COPYABLE_KLASS]]
539539
// CHECK: [[FIELD:%.*]] = ref_element_addr [[BORROWED_COPYABLE_KLASS]]
540-
// CHECK: [[FIELD_MARK:%.*]] = mark_must_check [no_consume_or_assign] [[FIELD]]
541-
// CHECK: [[ACCESS:%.*]] = begin_access [read] [dynamic] [[FIELD_MARK]]
542-
// CHECK: [[BORROWED_MOVEONLY_KLASS:%.*]] = load_borrow [[ACCESS]]
540+
// CHECK: [[ACCESS:%.*]] = begin_access [read] [dynamic] [[FIELD]]
541+
// CHECK: [[ACCESS_MARK:%.*]] = mark_must_check [no_consume_or_assign] [[ACCESS]]
542+
// CHECK: [[BORROWED_MOVEONLY_KLASS:%.*]] = load_borrow [[ACCESS_MARK]]
543543
// CHECK: [[FN:%.*]] = function_ref @$s8moveonly9borrowValyyAA2FDVhF :
544544
// CHECK: apply [[FN]]([[BORROWED_MOVEONLY_KLASS]])
545545
// CHECK: end_borrow [[BORROWED_MOVEONLY_KLASS]]
@@ -551,6 +551,22 @@ func moveOnlyStructCopyableStructCopyableStructCopyableKlassMoveOnlyKlassNonCons
551551
borrowVal(k.nonTrivialCopyableStruct.nonTrivialCopyableStruct2.copyableKlass.fd)
552552
}
553553

554+
//////////////////////
555+
// Assignment Tests //
556+
//////////////////////
557+
558+
// CHECK-LABEL: sil hidden [ossa] @$s8moveonly19assignCopyableKlassyyAA0cD0CF : $@convention(thin) (@guaranteed CopyableKlass) -> () {
559+
// CHECK: bb0([[ARG:%.*]] : @guaranteed
560+
// CHECK: [[REF:%.*]] = ref_element_addr [[ARG]]
561+
// CHECK: [[ACCESS:%.*]] = begin_access [modify] [dynamic] [[REF]]
562+
// CHECK: [[MARKED_ACCESS:%.*]] = mark_must_check [assignable_but_not_consumable] [[ACCESS]]
563+
// CHECK: assign {{%.*}} to [[MARKED_ACCESS]]
564+
// CHECK: end_access [[ACCESS]]
565+
// CHECK: } // end sil function '$s8moveonly19assignCopyableKlassyyAA0cD0CF'
566+
func assignCopyableKlass(_ x: CopyableKlass) {
567+
x.fd = FD()
568+
}
569+
554570
///////////////////////
555571
// Enum Switch Tests //
556572
///////////////////////

test/SILOptimizer/moveonly_addresschecker.sil

+56
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ public struct NonTrivialStruct {
2727
}
2828

2929
sil @useNonTrivialStruct : $@convention(thin) (@guaranteed NonTrivialStruct) -> ()
30+
sil @getNonTrivialStruct : $@convention(thin) () -> @owned NonTrivialStruct
3031

3132
@_moveOnly
3233
public struct NonTrivialStructPair {
@@ -437,3 +438,58 @@ bb0(%0 : $*Klass):
437438
%27 = tuple ()
438439
return %27 : $()
439440
}
441+
442+
// Make sure that we treat the move only access below like an inout parameter
443+
// that is live at the end of its lifetime, rather than like a var.
444+
//
445+
// CHECK: sil [ossa] @closure_assignable_but_not_consumable_treated_like_inout : $@convention(thin) (@guaranteed { var NonTrivialStruct }) -> () {
446+
// CHECK: bb0([[ARG:%.*]] : @closureCapture @guaranteed
447+
// CHECK-NEXT: project_box
448+
// CHECK-NEXT: // function_ref
449+
// CHECK-NEXT: function_ref
450+
// CHECK-NEXT: apply
451+
// CHECK-NEXT: begin_access
452+
// CHECK-NEXT: destroy_addr
453+
// CHECK-NEXT: store
454+
// CHECK-NEXT: end_access
455+
// CHECK-NEXT: tuple ()
456+
// CHECK-NEXT: return
457+
// CHECK-NEXT: } // end sil function 'closure_assignable_but_not_consumable_treated_like_inout'
458+
sil [ossa] @closure_assignable_but_not_consumable_treated_like_inout : $@convention(thin) (@guaranteed { var NonTrivialStruct }) -> () {
459+
bb0(%0 : @closureCapture @guaranteed ${ var NonTrivialStruct }):
460+
%4 = project_box %0 : ${ var NonTrivialStruct }, 0
461+
%9 = function_ref @getNonTrivialStruct : $@convention(thin) () -> @owned NonTrivialStruct
462+
%10 = apply %9() : $@convention(thin) () -> @owned NonTrivialStruct
463+
%11 = begin_access [modify] [dynamic] %4 : $*NonTrivialStruct
464+
%12 = mark_must_check [assignable_but_not_consumable] %11 : $*NonTrivialStruct
465+
store %10 to [assign] %12 : $*NonTrivialStruct
466+
end_access %11 : $*NonTrivialStruct
467+
%24 = tuple ()
468+
return %24 : $()
469+
}
470+
471+
// CHECK: sil hidden [ossa] @ref_element_addr_treated_like_inout : $@convention(method) (@guaranteed ClassContainingMoveOnly) -> () {
472+
// CHECK: bb0(
473+
// CHECK-NEXT: // function_ref
474+
// CHECK-NEXT: function_ref
475+
// CHECK-NEXT: apply
476+
// CHECK-NEXT: ref_element_addr
477+
// CHECK-NEXT: begin_access
478+
// CHECK-NEXT: destroy_addr
479+
// CHECK-NEXT: store
480+
// CHECK-NEXT: end_access
481+
// CHECK-NEXT: tuple ()
482+
// CHECK-NEXT: return
483+
// CHECK: } // end sil function 'ref_element_addr_treated_like_inout'
484+
sil hidden [ossa] @ref_element_addr_treated_like_inout : $@convention(method) (@guaranteed ClassContainingMoveOnly) -> () {
485+
bb0(%0 : @guaranteed $ClassContainingMoveOnly):
486+
%9 = function_ref @getNonTrivialStruct : $@convention(thin) () -> @owned NonTrivialStruct
487+
%10 = apply %9() : $@convention(thin) () -> @owned NonTrivialStruct
488+
%5 = ref_element_addr %0 : $ClassContainingMoveOnly, #ClassContainingMoveOnly.value
489+
%6 = begin_access [modify] [dynamic] %5 : $*NonTrivialStruct
490+
%7 = mark_must_check [assignable_but_not_consumable] %6 : $*NonTrivialStruct
491+
store %10 to [assign] %7 : $*NonTrivialStruct
492+
end_access %6 : $*NonTrivialStruct
493+
%11 = tuple ()
494+
return %11 : $()
495+
}

test/SILOptimizer/moveonly_coro_accessor.swift

+3-7
Original file line numberDiff line numberDiff line change
@@ -43,12 +43,10 @@ public class ListOfFiles {
4343
// CHECK: [[NEW_VAL_RELOADED:%.*]] = load [[NEW_VAL_STACK]] : $*File
4444
// >> destroy the element currently in the field
4545
// CHECK: [[FIELD:%.*]] = ref_element_addr [[SELF]] : $ListOfFiles, #ListOfFiles.file
46-
// CHECK: destroy_addr [[FIELD]] : $*File
4746
// >> write the new value in its place
4847
// CHECK: [[FIELD_ACCESS:%.*]] = begin_access [modify] [dynamic] [[FIELD]] : $*File
48+
// CHECK: destroy_addr [[FIELD_ACCESS]] : $*File
4949
// CHECK: store [[NEW_VAL_RELOADED]] to [[FIELD_ACCESS]] : $*File
50-
// >> FIXME: we should not be destroying the field here rdar://105910066
51-
// CHECK: destroy_addr [[FIELD]] : $*File
5250
//
5351
// CHECK: end_access [[FIELD_ACCESS]] : $*File
5452
// CHECK-NOT: begin_access
@@ -65,15 +63,13 @@ public class ListOfFiles {
6563
// CHECK: yield [[ACCESS]] : $*File, resume bb1, unwind bb2
6664
//
6765
// CHECK: bb1:
68-
// >> FIXME: we should not be destroying the field here rdar://105910066
69-
// CHECK: destroy_addr [[FIELD]] : $*File
7066
//
7167
// CHECK: end_access [[ACCESS]] : $*File
7268
// CHECK-NOT: begin_access
69+
// CHECK-NOT: destroy_addr [[FIELD]] : $*File
7370
//
7471
// CHECK: bb2:
75-
// >> FIXME: we should not be destroying the field here rdar://105910066
76-
// CHECK: destroy_addr [[FIELD]] : $*File
72+
// CHECK-NOT: destroy_addr [[FIELD]] : $*File
7773
//
7874
// CHECK: end_access [[ACCESS]] : $*File
7975
// CHECK-NOT: begin_access

0 commit comments

Comments
 (0)