Skip to content

Commit 29ca777

Browse files
committed
[move-only] When adding implicit liveness uses for class field/global accesses, do not use the terminator, use the end access.
Previously, when doing this I could just use the terminator both in cases of inout and for class field/global accesses... but after some recent changes to field sensitive pruned liveness, this seems to no longer work. So just do the right thing and use the field access. The specific bug was that we would introduce a destroy_addr along one of the paths where the value wasn't defined resulting in a dominance error. I added a SIL test that shows this fixes the issue, a swift test, and also an Interpreter test that validates the correctness. rdar://111659649
1 parent 7c07f12 commit 29ca777

File tree

4 files changed

+243
-41
lines changed

4 files changed

+243
-41
lines changed

lib/SILOptimizer/Mandatory/MoveOnlyAddressCheckerUtils.cpp

Lines changed: 60 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -385,17 +385,6 @@ static bool isInOutDefThatNeedsEndOfFunctionLiveness(MarkMustCheckInst *markedAd
385385
}
386386
}
387387

388-
// See if we have an assignable_but_not_consumable from a formal access.
389-
// In this case, the value must be live at the end of the
390-
// access, similar to an inout parameter.
391-
if (markedAddr->getCheckKind() ==
392-
MarkMustCheckInst::CheckKind::AssignableButNotConsumable) {
393-
if (isa<BeginAccessInst>(operand)) {
394-
return true;
395-
}
396-
}
397-
398-
399388
return false;
400389
}
401390

@@ -513,7 +502,7 @@ struct UseState {
513502
/// terminator user and so that we can use the set to quickly identify later
514503
/// while emitting diagnostics that a liveness use is a terminator user and
515504
/// emit a specific diagnostic message.
516-
SmallSetVector<SILInstruction *, 2> inoutTermUsers;
505+
SmallSetVector<SILInstruction *, 2> implicitEndOfLifetimeLivenessUses;
517506

518507
/// We add debug_values to liveness late after we diagnose, but before we
519508
/// hoist destroys to ensure that we do not hoist destroys out of access
@@ -565,11 +554,17 @@ struct UseState {
565554
setAffectedBits(inst, range, initInsts);
566555
}
567556

568-
/// Returns true if this is a terminator instruction that although it doesn't
569-
/// use our inout argument directly is used by the pass to ensure that we
570-
/// reinit said argument if we consumed it in the body of the function.
571-
bool isInOutTermUser(SILInstruction *inst) const {
572-
return inoutTermUsers.count(inst);
557+
/// Returns true if this is an instruction that is used by the pass to ensure
558+
/// that we reinit said argument if we consumed it in a region of code.
559+
///
560+
/// Example:
561+
///
562+
/// 1. In the case of an inout argument, this will contain the terminator
563+
/// instruction.
564+
/// 2. In the case of a ref_element_addr or a global, this will contain the
565+
/// end_access.
566+
bool isImplicitEndOfLifetimeLivenessUses(SILInstruction *inst) const {
567+
return implicitEndOfLifetimeLivenessUses.count(inst);
573568
}
574569

575570
/// Returns true if the given instruction is within the same block as a reinit
@@ -609,7 +604,7 @@ struct UseState {
609604
initInsts.clear();
610605
reinitInsts.clear();
611606
dropDeinitInsts.clear();
612-
inoutTermUsers.clear();
607+
implicitEndOfLifetimeLivenessUses.clear();
613608
debugValue = nullptr;
614609
}
615610

@@ -647,8 +642,8 @@ struct UseState {
647642
for (auto *inst : dropDeinitInsts) {
648643
llvm::dbgs() << *inst;
649644
}
650-
llvm::dbgs() << "InOut Term Users:\n";
651-
for (auto *inst : inoutTermUsers) {
645+
llvm::dbgs() << "Implicit End Of Lifetime Liveness Users:\n";
646+
for (auto *inst : implicitEndOfLifetimeLivenessUses) {
652647
llvm::dbgs() << *inst;
653648
}
654649
llvm::dbgs() << "Debug Value User:\n";
@@ -680,16 +675,28 @@ struct UseState {
680675
void
681676
initializeLiveness(FieldSensitiveMultiDefPrunedLiveRange &prunedLiveness);
682677

683-
void initializeInOutTermUsers() {
684-
if (!isInOutDefThatNeedsEndOfFunctionLiveness(address))
678+
void initializeImplicitEndOfLifetimeLivenessUses() {
679+
if (isInOutDefThatNeedsEndOfFunctionLiveness(address)) {
680+
SmallVector<SILBasicBlock *, 8> exitBlocks;
681+
address->getFunction()->findExitingBlocks(exitBlocks);
682+
for (auto *block : exitBlocks) {
683+
LLVM_DEBUG(llvm::dbgs() << " Adding term as liveness user: "
684+
<< *block->getTerminator());
685+
implicitEndOfLifetimeLivenessUses.insert(block->getTerminator());
686+
}
685687
return;
688+
}
686689

687-
SmallVector<SILBasicBlock *, 8> exitBlocks;
688-
address->getFunction()->findExitingBlocks(exitBlocks);
689-
for (auto *block : exitBlocks) {
690-
LLVM_DEBUG(llvm::dbgs() << " Adding term as liveness user: "
691-
<< *block->getTerminator());
692-
inoutTermUsers.insert(block->getTerminator());
690+
if (address->getCheckKind() ==
691+
MarkMustCheckInst::CheckKind::AssignableButNotConsumable) {
692+
if (auto *bai = dyn_cast<BeginAccessInst>(address->getOperand())) {
693+
for (auto *eai : bai->getEndAccesses()) {
694+
LLVM_DEBUG(llvm::dbgs() << " Adding end_access as implicit end of "
695+
"lifetime liveness user: "
696+
<< *eai);
697+
implicitEndOfLifetimeLivenessUses.insert(eai);
698+
}
699+
}
693700
}
694701
}
695702

@@ -749,7 +756,7 @@ struct UseState {
749756
// An "inout terminator use" is an implicit liveness use of the entire
750757
// value. This is because we need to ensure that our inout value is
751758
// reinitialized along exit paths.
752-
if (inoutTermUsers.count(inst))
759+
if (implicitEndOfLifetimeLivenessUses.count(inst))
753760
return true;
754761

755762
return false;
@@ -1064,13 +1071,11 @@ void UseState::initializeLiveness(
10641071
liveness.print(llvm::dbgs()));
10651072
}
10661073

1067-
// Finally, if we have an inout argument, add a liveness use of the entire
1068-
// value on terminators in blocks that are exits from the function. This
1069-
// ensures that along all paths, if our inout is not reinitialized before we
1070-
// exit the function, we will get an error. We also stash these users into
1071-
// inoutTermUser so we can quickly recognize them later and emit a better
1072-
// error msg.
1073-
for (auto *inst : inoutTermUsers) {
1074+
// Finally, if we have an inout argument or an access scope associated with a
1075+
// ref_element_addr or global_addr, add a liveness use of the entire value on
1076+
// the implicit end lifetime instruction. For inout this is terminators for
1077+
// ref_element_addr, global_addr it is the end_access instruction.
1078+
for (auto *inst : implicitEndOfLifetimeLivenessUses) {
10741079
liveness.updateForUse(inst, TypeTreeLeafTypeRange(address),
10751080
false /*lifetime ending*/);
10761081
LLVM_DEBUG(llvm::dbgs() << "Added liveness for inoutTermUser: " << *inst;
@@ -2229,7 +2234,7 @@ bool GlobalLivenessChecker::testInstVectorLiveness(
22292234
if (addressUseState.isLivenessUse(&*ii, errorSpan)) {
22302235
diagnosticEmitter.emitAddressDiagnostic(
22312236
addressUseState.address, &*ii, errorUser, false /*is consuming*/,
2232-
addressUseState.isInOutTermUser(&*ii));
2237+
addressUseState.isImplicitEndOfLifetimeLivenessUses(&*ii));
22332238
foundSingleBlockError = true;
22342239
emittedDiagnostic = true;
22352240
break;
@@ -2249,8 +2254,9 @@ bool GlobalLivenessChecker::testInstVectorLiveness(
22492254
&*ii, FieldSensitivePrunedLiveness::NonLifetimeEndingUse,
22502255
errorSpan)) {
22512256
diagnosticEmitter.emitAddressDiagnostic(
2252-
addressUseState.address, &*ii, errorUser, false /*is consuming*/,
2253-
addressUseState.isInOutTermUser(&*ii));
2257+
addressUseState.address, &*ii, errorUser,
2258+
false /*is consuming*/,
2259+
addressUseState.isImplicitEndOfLifetimeLivenessUses(&*ii));
22542260
foundSingleBlockError = true;
22552261
emittedDiagnostic = true;
22562262
break;
@@ -2340,7 +2346,8 @@ bool GlobalLivenessChecker::testInstVectorLiveness(
23402346
diagnosticEmitter.emitAddressDiagnostic(
23412347
addressUseState.address, &blockInst, errorUser,
23422348
false /*is consuming*/,
2343-
addressUseState.isInOutTermUser(&blockInst));
2349+
addressUseState.isImplicitEndOfLifetimeLivenessUses(
2350+
&blockInst));
23442351
foundSingleBlockError = true;
23452352
emittedDiagnostic = true;
23462353
break;
@@ -2567,6 +2574,18 @@ void MoveOnlyAddressCheckerPImpl::insertDestroysOnBoundary(
25672574
continue;
25682575
}
25692576

2577+
// If we have an implicit end of lifetime use, we do not insert a
2578+
// destroy_addr. Instead, we insert an undef debug value after the
2579+
// use. This occurs if we have an end_access associated with a
2580+
// global_addr or a ref_element_addr field access.
2581+
if (addressUseState.isImplicitEndOfLifetimeLivenessUses(inst)) {
2582+
LLVM_DEBUG(
2583+
llvm::dbgs()
2584+
<< " Use was an implicit end of lifetime liveness use!\n");
2585+
insertUndefDebugValue(inst->getNextInstruction());
2586+
continue;
2587+
}
2588+
25702589
auto *insertPt = inst->getNextInstruction();
25712590
insertDestroyBeforeInstruction(addressUseState, insertPt,
25722591
liveness.getRootValue(), bits, consumes);
@@ -3207,7 +3226,7 @@ bool MoveOnlyAddressCheckerPImpl::performSingleCheck(
32073226
// DISCUSSION: For non address only types, this is not an issue since we
32083227
// eagerly load
32093228

3210-
addressUseState.initializeInOutTermUsers();
3229+
addressUseState.initializeImplicitEndOfLifetimeLivenessUses();
32113230

32123231
//===---
32133232
// Liveness Checking
Lines changed: 136 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,136 @@
1+
// RUN: %target-run-simple-swift(-Xfrontend -sil-verify-all)
2+
// RUN: %target-run-simple-swift(-O -Xfrontend -sil-verify-all)
3+
4+
// REQUIRES: executable_test
5+
6+
/// A class that we use as a box to store the memory for one of our linked list
7+
/// nodes. It is on purpose fileprivate since it is an implementation detail of
8+
/// \p NodeBox.
9+
fileprivate final class _Box<T> {
10+
var value: _Node<T>
11+
12+
init(_ value: consuming _Node<T>) { self.value = value }
13+
}
14+
15+
struct _Node<T> : ~Copyable {
16+
var value: T
17+
var _next: ListEntry<T> = ListEntry<T>()
18+
19+
init(_ newValue: T) {
20+
value = newValue
21+
}
22+
}
23+
24+
/// A noncopyable box that contains the memory for a linked list node. Can be
25+
/// embedded within other noncopyable data structures to point at a Node data
26+
/// structure.
27+
///
28+
/// Internally uses a class as the actual box.
29+
struct ListEntry<T> : ~Copyable {
30+
private var innerBox: _Box<T>?
31+
32+
init() { innerBox = nil }
33+
init(initialValue value: consuming T) {
34+
innerBox = _Box<T>(_Node(value))
35+
}
36+
37+
mutating func push(value newValue: consuming T) {
38+
if innerBox == nil {
39+
// If we do not already have a head, just take on this value and return.
40+
innerBox = _Box<T>(_Node(newValue))
41+
return
42+
}
43+
44+
// Otherwise, we need to create a new node and fix things up.
45+
var nodeEntry = ListEntry<T>(initialValue: newValue)
46+
nodeEntry.next = self
47+
self = nodeEntry
48+
}
49+
50+
mutating func pop() -> T? {
51+
guard let innerBox = innerBox else {
52+
return nil
53+
}
54+
55+
let result = innerBox.value.value
56+
func fixNext(_ lhs: inout ListEntry<T>, _ rhs: inout ListEntry<T>) {
57+
lhs = rhs
58+
rhs = ListEntry<T>()
59+
}
60+
fixNext(&self, &innerBox.value._next)
61+
return result
62+
}
63+
64+
var hasNext: Bool {
65+
return innerBox != nil
66+
}
67+
var next: ListEntry<T> {
68+
_modify {
69+
yield &innerBox!.value._next
70+
}
71+
_read {
72+
yield innerBox!.value._next
73+
}
74+
}
75+
76+
var value: T? {
77+
return innerBox?.value.value
78+
}
79+
}
80+
81+
let target = "ogyfbssvlh"
82+
let strings = [
83+
"nulbhqylps",
84+
"hpdovhuybl",
85+
"bjjvpakqbm",
86+
"rqyozjzkyz",
87+
"qpzghmdcag",
88+
"lqefxvulvn",
89+
"wtokfqarxm",
90+
"acdcrzxpdg",
91+
"bxgfacpjic",
92+
"acblrvoego",
93+
"msevhriohn",
94+
"bamfcnbqvx",
95+
"wimkkqhryd",
96+
"dounctqkiw",
97+
"zxmyxcabhq",
98+
"ljerkuhlgy",
99+
"cettadahue",
100+
"cuummvmwly",
101+
"kdebludzsh",
102+
"ogyfbssvlh",
103+
"lrowrxwufj",
104+
"rftifkqggr",
105+
"ktjeeeobca",
106+
"xqlbnswmjr",
107+
"zpuxfbtmip",
108+
"rljcxrvdgh",
109+
"twkgardobr",
110+
"zrogczpzem",
111+
"bkuzjugksg",
112+
"eqanimdywo"
113+
]
114+
115+
func buildList() -> ListEntry<String> {
116+
var head = ListEntry<String>()
117+
118+
for i in strings {
119+
head.push(value: i)
120+
}
121+
122+
return head
123+
}
124+
125+
func main() {
126+
var head = buildList()
127+
var count = 0
128+
129+
var strCount = strings.count
130+
while let x = head.pop() {
131+
assert(x == strings[strCount - count - 1])
132+
count = count + 1
133+
}
134+
}
135+
136+
main()

test/SILOptimizer/moveonly_addresschecker.sil

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -732,3 +732,39 @@ bb0(%0 : @owned $FixedSizeQueue):
732732
dealloc_stack %1 : $*FixedSizeQueue
733733
return %11 : $Builtin.Int32
734734
}
735+
736+
// Make sure that we set the end_access as the implicit end lifetime use instead
737+
// of the terminator.
738+
// CHECK-LABEL: sil [ossa] @assignableButNotConsumableEndAccessImplicitLifetimeTest : $@convention(thin) (@guaranteed ClassContainingMoveOnly, @owned NonTrivialStruct2) -> () {
739+
// CHECK: bb0([[ARG0:%.*]] : @guaranteed $ClassContainingMoveOnly, [[ARG1:%.*]] : @owned
740+
// CHECK: bb2:
741+
// CHECK-NEXT: [[ADDR:%.*]] = ref_element_addr [[ARG0]]
742+
// CHECK-NEXT: [[ACCESS:%.*]] = begin_access [modify] [dynamic] [[ADDR]]
743+
// CHECK-NEXT: [[GEP1:%.*]] = struct_element_addr [[ACCESS]]
744+
// CHECK-NEXT: [[GEP2:%.*]] = struct_element_addr [[ACCESS]]
745+
// CHECK-NEXT: destroy_addr [[GEP2]]
746+
// CHECK-NEXT: store [[ARG1]] to [init] [[GEP1]]
747+
// CHECK-NEXT: end_access [[ACCESS]]
748+
// CHECK-NEXT: br bb3
749+
// CHECK: } // end sil function 'assignableButNotConsumableEndAccessImplicitLifetimeTest'
750+
sil [ossa] @assignableButNotConsumableEndAccessImplicitLifetimeTest : $@convention(thin) (@guaranteed ClassContainingMoveOnly, @owned NonTrivialStruct2) -> () {
751+
bb0(%0 : @guaranteed $ClassContainingMoveOnly, %1 : @owned $NonTrivialStruct2):
752+
cond_br undef, bb1, bb2
753+
754+
bb1:
755+
destroy_value %1 : $NonTrivialStruct2
756+
br bb3
757+
758+
bb2:
759+
%2 = ref_element_addr %0 : $ClassContainingMoveOnly, #ClassContainingMoveOnly.value
760+
%3 = begin_access [modify] [dynamic] %2 : $*NonTrivialStruct
761+
%4 = mark_must_check [assignable_but_not_consumable] %3 : $*NonTrivialStruct
762+
%4a = struct_element_addr %4 : $*NonTrivialStruct, #NonTrivialStruct.nonTrivialStruct2
763+
store %1 to [assign] %4a : $*NonTrivialStruct2
764+
end_access %3 : $*NonTrivialStruct
765+
br bb3
766+
767+
bb3:
768+
%9999 = tuple ()
769+
return %9999 : $()
770+
}

test/SILOptimizer/moveonly_addresschecker_diagnostics.swift

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4551,3 +4551,14 @@ public class NonFinalCopyableKlassWithMoveOnlyField {
45514551
var moveOnlyVarProt = AddressOnlyProtocol()
45524552
let moveOnlyLetProt = AddressOnlyProtocol()
45534553
}
4554+
4555+
//////////////////////
4556+
// MARK: Misc Tests //
4557+
//////////////////////
4558+
4559+
// For misc tests associated with specific radars.
4560+
func assignableButNotConsumableEndAccessImplicitLifetimeTest(_ x: CopyableKlassWithMoveOnlyField) {
4561+
if boolValue {
4562+
x.moveOnlyVarStruct.nonTrivialStruct2 = NonTrivialStruct2()
4563+
}
4564+
}

0 commit comments

Comments
 (0)