Skip to content

Commit 7028381

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 fb487bf commit 7028381

File tree

3 files changed

+107
-41
lines changed

3 files changed

+107
-41
lines changed

lib/SILOptimizer/Mandatory/MoveOnlyAddressCheckerUtils.cpp

Lines changed: 60 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -401,17 +401,6 @@ static bool isInOutDefThatNeedsEndOfFunctionLiveness(MarkMustCheckInst *markedAd
401401
}
402402
}
403403

404-
// See if we have an assignable_but_not_consumable from a formal access.
405-
// In this case, the value must be live at the end of the
406-
// access, similar to an inout parameter.
407-
if (markedAddr->getCheckKind() ==
408-
MarkMustCheckInst::CheckKind::AssignableButNotConsumable) {
409-
if (isa<BeginAccessInst>(operand)) {
410-
return true;
411-
}
412-
}
413-
414-
415404
return false;
416405
}
417406

@@ -529,7 +518,7 @@ struct UseState {
529518
/// terminator user and so that we can use the set to quickly identify later
530519
/// while emitting diagnostics that a liveness use is a terminator user and
531520
/// emit a specific diagnostic message.
532-
SmallSetVector<SILInstruction *, 2> inoutTermUsers;
521+
SmallSetVector<SILInstruction *, 2> implicitEndOfLifetimeLivenessUses;
533522

534523
/// We add debug_values to liveness late after we diagnose, but before we
535524
/// hoist destroys to ensure that we do not hoist destroys out of access
@@ -581,11 +570,17 @@ struct UseState {
581570
setAffectedBits(inst, range, initInsts);
582571
}
583572

584-
/// Returns true if this is a terminator instruction that although it doesn't
585-
/// use our inout argument directly is used by the pass to ensure that we
586-
/// reinit said argument if we consumed it in the body of the function.
587-
bool isInOutTermUser(SILInstruction *inst) const {
588-
return inoutTermUsers.count(inst);
573+
/// Returns true if this is an instruction that is used by the pass to ensure
574+
/// that we reinit said argument if we consumed it in a region of code.
575+
///
576+
/// Example:
577+
///
578+
/// 1. In the case of an inout argument, this will contain the terminator
579+
/// instruction.
580+
/// 2. In the case of a ref_element_addr or a global, this will contain the
581+
/// end_access.
582+
bool isImplicitEndOfLifetimeLivenessUses(SILInstruction *inst) const {
583+
return implicitEndOfLifetimeLivenessUses.count(inst);
589584
}
590585

591586
/// Returns true if the given instruction is within the same block as a reinit
@@ -625,7 +620,7 @@ struct UseState {
625620
initInsts.clear();
626621
reinitInsts.clear();
627622
dropDeinitInsts.clear();
628-
inoutTermUsers.clear();
623+
implicitEndOfLifetimeLivenessUses.clear();
629624
debugValue = nullptr;
630625
}
631626

@@ -663,8 +658,8 @@ struct UseState {
663658
for (auto *inst : dropDeinitInsts) {
664659
llvm::dbgs() << *inst;
665660
}
666-
llvm::dbgs() << "InOut Term Users:\n";
667-
for (auto *inst : inoutTermUsers) {
661+
llvm::dbgs() << "Implicit End Of Lifetime Liveness Users:\n";
662+
for (auto *inst : implicitEndOfLifetimeLivenessUses) {
668663
llvm::dbgs() << *inst;
669664
}
670665
llvm::dbgs() << "Debug Value User:\n";
@@ -696,16 +691,28 @@ struct UseState {
696691
void
697692
initializeLiveness(FieldSensitiveMultiDefPrunedLiveRange &prunedLiveness);
698693

699-
void initializeInOutTermUsers() {
700-
if (!isInOutDefThatNeedsEndOfFunctionLiveness(address))
694+
void initializeImplicitEndOfLifetimeLivenessUses() {
695+
if (isInOutDefThatNeedsEndOfFunctionLiveness(address)) {
696+
SmallVector<SILBasicBlock *, 8> exitBlocks;
697+
address->getFunction()->findExitingBlocks(exitBlocks);
698+
for (auto *block : exitBlocks) {
699+
LLVM_DEBUG(llvm::dbgs() << " Adding term as liveness user: "
700+
<< *block->getTerminator());
701+
implicitEndOfLifetimeLivenessUses.insert(block->getTerminator());
702+
}
701703
return;
704+
}
702705

703-
SmallVector<SILBasicBlock *, 8> exitBlocks;
704-
address->getFunction()->findExitingBlocks(exitBlocks);
705-
for (auto *block : exitBlocks) {
706-
LLVM_DEBUG(llvm::dbgs() << " Adding term as liveness user: "
707-
<< *block->getTerminator());
708-
inoutTermUsers.insert(block->getTerminator());
706+
if (address->getCheckKind() ==
707+
MarkMustCheckInst::CheckKind::AssignableButNotConsumable) {
708+
if (auto *bai = dyn_cast<BeginAccessInst>(address->getOperand())) {
709+
for (auto *eai : bai->getEndAccesses()) {
710+
LLVM_DEBUG(llvm::dbgs() << " Adding end_access as implicit end of "
711+
"lifetime liveness user: "
712+
<< *eai);
713+
implicitEndOfLifetimeLivenessUses.insert(eai);
714+
}
715+
}
709716
}
710717
}
711718

@@ -765,7 +772,7 @@ struct UseState {
765772
// An "inout terminator use" is an implicit liveness use of the entire
766773
// value. This is because we need to ensure that our inout value is
767774
// reinitialized along exit paths.
768-
if (inoutTermUsers.count(inst))
775+
if (implicitEndOfLifetimeLivenessUses.count(inst))
769776
return true;
770777

771778
return false;
@@ -1080,13 +1087,11 @@ void UseState::initializeLiveness(
10801087
liveness.print(llvm::dbgs()));
10811088
}
10821089

1083-
// Finally, if we have an inout argument, add a liveness use of the entire
1084-
// value on terminators in blocks that are exits from the function. This
1085-
// ensures that along all paths, if our inout is not reinitialized before we
1086-
// exit the function, we will get an error. We also stash these users into
1087-
// inoutTermUser so we can quickly recognize them later and emit a better
1088-
// error msg.
1089-
for (auto *inst : inoutTermUsers) {
1090+
// Finally, if we have an inout argument or an access scope associated with a
1091+
// ref_element_addr or global_addr, add a liveness use of the entire value on
1092+
// the implicit end lifetime instruction. For inout this is terminators for
1093+
// ref_element_addr, global_addr it is the end_access instruction.
1094+
for (auto *inst : implicitEndOfLifetimeLivenessUses) {
10901095
liveness.updateForUse(inst, TypeTreeLeafTypeRange(address),
10911096
false /*lifetime ending*/);
10921097
LLVM_DEBUG(llvm::dbgs() << "Added liveness for inoutTermUser: " << *inst;
@@ -2245,7 +2250,7 @@ bool GlobalLivenessChecker::testInstVectorLiveness(
22452250
if (addressUseState.isLivenessUse(&*ii, errorSpan)) {
22462251
diagnosticEmitter.emitAddressDiagnostic(
22472252
addressUseState.address, &*ii, errorUser, false /*is consuming*/,
2248-
addressUseState.isInOutTermUser(&*ii));
2253+
addressUseState.isImplicitEndOfLifetimeLivenessUses(&*ii));
22492254
foundSingleBlockError = true;
22502255
emittedDiagnostic = true;
22512256
break;
@@ -2265,8 +2270,9 @@ bool GlobalLivenessChecker::testInstVectorLiveness(
22652270
&*ii, FieldSensitivePrunedLiveness::NonLifetimeEndingUse,
22662271
errorSpan)) {
22672272
diagnosticEmitter.emitAddressDiagnostic(
2268-
addressUseState.address, &*ii, errorUser, false /*is consuming*/,
2269-
addressUseState.isInOutTermUser(&*ii));
2273+
addressUseState.address, &*ii, errorUser,
2274+
false /*is consuming*/,
2275+
addressUseState.isImplicitEndOfLifetimeLivenessUses(&*ii));
22702276
foundSingleBlockError = true;
22712277
emittedDiagnostic = true;
22722278
break;
@@ -2356,7 +2362,8 @@ bool GlobalLivenessChecker::testInstVectorLiveness(
23562362
diagnosticEmitter.emitAddressDiagnostic(
23572363
addressUseState.address, &blockInst, errorUser,
23582364
false /*is consuming*/,
2359-
addressUseState.isInOutTermUser(&blockInst));
2365+
addressUseState.isImplicitEndOfLifetimeLivenessUses(
2366+
&blockInst));
23602367
foundSingleBlockError = true;
23612368
emittedDiagnostic = true;
23622369
break;
@@ -2583,6 +2590,18 @@ void MoveOnlyAddressCheckerPImpl::insertDestroysOnBoundary(
25832590
continue;
25842591
}
25852592

2593+
// If we have an implicit end of lifetime use, we do not insert a
2594+
// destroy_addr. Instead, we insert an undef debug value after the
2595+
// use. This occurs if we have an end_access associated with a
2596+
// global_addr or a ref_element_addr field access.
2597+
if (addressUseState.isImplicitEndOfLifetimeLivenessUses(inst)) {
2598+
LLVM_DEBUG(
2599+
llvm::dbgs()
2600+
<< " Use was an implicit end of lifetime liveness use!\n");
2601+
insertUndefDebugValue(inst->getNextInstruction());
2602+
continue;
2603+
}
2604+
25862605
auto *insertPt = inst->getNextInstruction();
25872606
insertDestroyBeforeInstruction(addressUseState, insertPt,
25882607
liveness.getRootValue(), bits, consumes);
@@ -3210,7 +3229,7 @@ bool MoveOnlyAddressCheckerPImpl::performSingleCheck(
32103229
// DISCUSSION: For non address only types, this is not an issue since we
32113230
// eagerly load
32123231

3213-
addressUseState.initializeInOutTermUsers();
3232+
addressUseState.initializeImplicitEndOfLifetimeLivenessUses();
32143233

32153234
//===---
32163235
// Liveness Checking

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)