Skip to content

Commit efc193f

Browse files
authored
Merge pull request #74154 from jckarter/treat-reinit-attempts-in-multi-block-defers-as-errors
Turn mishandled reinitialize-in-`defer`-after-`consume` cases into errors.
2 parents 69d2006 + 74aaf88 commit efc193f

File tree

4 files changed

+97
-23
lines changed

4 files changed

+97
-23
lines changed

include/swift/Basic/Features.def

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -399,6 +399,8 @@ EXPERIMENTAL_FEATURE(Sensitive, true)
399399
// Enable the stdlib @DebugDescription macro.
400400
EXPERIMENTAL_FEATURE(DebugDescriptionMacro, true)
401401

402+
EXPERIMENTAL_FEATURE(ReinitializeConsumeInMultiBlockDefer, false)
403+
402404
#undef EXPERIMENTAL_FEATURE_EXCLUDED_FROM_MODULE_INTERFACE
403405
#undef EXPERIMENTAL_FEATURE
404406
#undef UPCOMING_FEATURE

lib/AST/FeatureSet.cpp

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -778,6 +778,7 @@ static bool usesFeatureSensitive(Decl *decl) {
778778
}
779779

780780
UNINTERESTING_FEATURE(DebugDescriptionMacro)
781+
UNINTERESTING_FEATURE(ReinitializeConsumeInMultiBlockDefer)
781782

782783
// ----------------------------------------------------------------------------
783784
// MARK: - FeatureSet

lib/SILOptimizer/Mandatory/ConsumeOperatorCopyableAddressesChecker.cpp

Lines changed: 42 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -544,20 +544,23 @@ namespace {
544544
/// and the compiler emits assigns when it reinitializes vars this early in the
545545
/// pipeline.
546546
struct ClosureArgDataflowState {
547+
ASTContext &C;
547548
SmallVector<SILInstruction *, 32> livenessWorklist;
548549
SmallVector<SILInstruction *, 32> consumingWorklist;
549550
MultiDefPrunedLiveness livenessForConsumes;
550551
UseState &useState;
551552

552553
public:
553554
ClosureArgDataflowState(SILFunction *function, UseState &useState)
554-
: livenessForConsumes(function), useState(useState) {}
555+
: C(function->getASTContext()),
556+
livenessForConsumes(function), useState(useState) {}
555557

556558
bool process(
557559
SILArgument *arg, ClosureOperandState &state,
558560
SmallBlotSetVector<SILInstruction *, 8> &postDominatingConsumingUsers);
559561

560562
private:
563+
561564
/// Perform our liveness dataflow. Returns true if we found any liveness uses
562565
/// at all. These we will need to error upon.
563566
bool performLivenessDataflow(const BasicBlockSet &initBlocks,
@@ -719,15 +722,15 @@ void ClosureArgDataflowState::classifyUses(BasicBlockSet &initBlocks,
719722

720723
for (auto *user : useState.inits) {
721724
if (upwardScanForInit(user, useState)) {
722-
LLVM_DEBUG(llvm::dbgs() << " Found init block at: " << *user);
725+
LLVM_DEBUG(llvm::dbgs() << " Found init block during classifyUses at: " << *user);
723726
livenessForConsumes.initializeDef(user);
724727
initBlocks.insert(user->getParent());
725728
}
726729
}
727730

728731
for (auto *user : useState.livenessUses) {
729732
if (upwardScanForUseOut(user, useState)) {
730-
LLVM_DEBUG(llvm::dbgs() << " Found use block at: " << *user);
733+
LLVM_DEBUG(llvm::dbgs() << " Found use block during classifyUses at: " << *user);
731734
livenessBlocks.insert(user->getParent());
732735
livenessWorklist.push_back(user);
733736
}
@@ -742,7 +745,7 @@ void ClosureArgDataflowState::classifyUses(BasicBlockSet &initBlocks,
742745
assert(iter != useState.destroyToIndexMap.end());
743746

744747
if (upwardScanForDestroys(destroy, useState)) {
745-
LLVM_DEBUG(llvm::dbgs() << " Found destroy block at: " << *destroy);
748+
LLVM_DEBUG(llvm::dbgs() << " Found destroy block during classifyUses at: " << *destroy);
746749
consumingBlocks.insert(destroy->getParent());
747750
consumingWorklist.push_back(destroy);
748751
}
@@ -755,8 +758,17 @@ void ClosureArgDataflowState::classifyUses(BasicBlockSet &initBlocks,
755758
auto iter = useState.reinitToIndexMap.find(reinit);
756759
assert(iter != useState.reinitToIndexMap.end());
757760

761+
// TODO: Reinitialization analysis is currently incomplete and leads
762+
// to miscompiles. Treat reinitializations as regular uses for now.
763+
if (!C.LangOpts.hasFeature(Feature::ReinitializeConsumeInMultiBlockDefer)) {
764+
LLVM_DEBUG(llvm::dbgs() << " Treating reinit as use block during classifyUses at: " << *reinit);
765+
livenessBlocks.insert(reinit->getParent());
766+
livenessWorklist.push_back(reinit);
767+
continue;
768+
}
769+
758770
if (upwardScanForDestroys(reinit, useState)) {
759-
LLVM_DEBUG(llvm::dbgs() << " Found reinit block at: " << *reinit);
771+
LLVM_DEBUG(llvm::dbgs() << " Found reinit block during classifyUses at: " << *reinit);
760772
consumingBlocks.insert(reinit->getParent());
761773
consumingWorklist.push_back(reinit);
762774
}
@@ -823,31 +835,38 @@ bool ClosureArgDataflowState::process(
823835
// parameter. We are going to change it to be an out parameter and eliminate
824836
// these when we clone the closure.
825837
if (performConsumingDataflow(initBlocks, consumingBlocks)) {
838+
LLVM_DEBUG(llvm::dbgs() << "found single consuming use!\n");
839+
826840
// Before we do anything, make sure our argument has at least one single
827841
// debug_value user. If we have many we can't handle it since something in
828842
// SILGen is emitting weird code. Our tests will ensure that SILGen does not
829843
// diverge by mistake. So we are really just being careful.
830844
if (hasMoreThanOneDebugUse(address)) {
831845
// Failing b/c more than one debug use!
846+
LLVM_DEBUG(llvm::dbgs() << "...but argument has more than one debug use!\n");
832847
return false;
833848
}
834849

835850
//!!! FIXME: Why?
836-
// auto *frontBlock = &*fn->begin();
837-
// livenessForConsumes.initializeDefBlock(frontBlock);
851+
//auto *frontBlock = &*fn->begin();
852+
//livenessForConsumes.initializeDef(address);
838853

839-
for (unsigned i : indices(livenessWorklist)) {
840-
if (auto *ptr = livenessWorklist[i]) {
854+
for (unsigned i : indices(consumingWorklist)) {
855+
if (auto *ptr = consumingWorklist[i]) {
856+
LLVM_DEBUG(llvm::dbgs() << "liveness for consume: " << *ptr);
841857
state.pairedConsumingInsts.push_back(ptr);
842-
livenessForConsumes.updateForUse(ptr, true /*is lifetime ending*/);
858+
//livenessForConsumes.updateForUse(ptr, true /*is lifetime ending*/);
843859
}
844860
}
845861

846862
// If our consumes do not have a linear lifetime, bail. We will error on the
847863
// move being unknown.
848864
for (auto *ptr : state.pairedConsumingInsts) {
849-
if (livenessForConsumes.isWithinBoundary(ptr))
865+
/*if (livenessForConsumes.isWithinBoundary(ptr)) {
866+
LLVM_DEBUG(llvm::dbgs() << "consuming inst within boundary; bailing: "
867+
<< *ptr);
850868
return false;
869+
}*/
851870
postDominatingConsumingUsers.insert(ptr);
852871
}
853872
state.result = DownwardScanResult::ClosureConsume;
@@ -1835,15 +1854,15 @@ void DataflowState::init() {
18351854
// mark this as an "init block".
18361855
for (auto *init : useState.inits) {
18371856
if (upwardScanForInit(init, useState)) {
1838-
LLVM_DEBUG(llvm::dbgs() << " Found use block at: " << *init);
1857+
LLVM_DEBUG(llvm::dbgs() << " Found use block during DataflowState::init at: " << *init);
18391858
initBlocks.insert(init->getParent());
18401859
}
18411860
}
18421861

18431862
// Then go through all normal uses and do upwardScanForUseOut.
18441863
for (auto *user : useState.livenessUses) {
18451864
if (upwardScanForUseOut(user, useState)) {
1846-
LLVM_DEBUG(llvm::dbgs() << " Found liveness block at: " << *user);
1865+
LLVM_DEBUG(llvm::dbgs() << " Found liveness block during DataflowState::init at: " << *user);
18471866
useBlocks[user->getParent()] = user;
18481867
}
18491868
}
@@ -1860,7 +1879,7 @@ void DataflowState::init() {
18601879
assert(iter != useState.destroyToIndexMap.end());
18611880

18621881
if (upwardScanForDestroys(destroy, useState)) {
1863-
LLVM_DEBUG(llvm::dbgs() << " Found destroy block at: " << *destroy);
1882+
LLVM_DEBUG(llvm::dbgs() << " Found destroy block during DataflowState::init at: " << *destroy);
18641883
destroyBlocks[destroy->getParent()] = destroy;
18651884
}
18661885
}
@@ -1876,7 +1895,7 @@ void DataflowState::init() {
18761895
assert(iter != useState.reinitToIndexMap.end());
18771896

18781897
if (upwardScanForDestroys(reinit, useState)) {
1879-
LLVM_DEBUG(llvm::dbgs() << " Found reinit block at: " << *reinit);
1898+
LLVM_DEBUG(llvm::dbgs() << " Found reinit block during DataflowState::init at: " << *reinit);
18801899
reinitBlocks[reinit->getParent()] = reinit;
18811900
}
18821901
}
@@ -1896,14 +1915,14 @@ void DataflowState::init() {
18961915
case DownwardScanResult::ClosureUse:
18971916
if (upwardScanForUseOut(user, useState)) {
18981917
LLVM_DEBUG(llvm::dbgs()
1899-
<< " Found closure liveness block at: " << *user);
1918+
<< " Found closure liveness block during DataflowState::init at: " << *user);
19001919
closureUseBlocks[user->getParent()] = &state;
19011920
}
19021921
break;
19031922
case DownwardScanResult::ClosureConsume:
19041923
if (upwardScanForDestroys(user, useState)) {
19051924
LLVM_DEBUG(llvm::dbgs()
1906-
<< " Found closure consuming block at: " << *user);
1925+
<< " Found closure consuming block during DataflowState::init at: " << *user);
19071926
closureConsumeBlocks[user->getParent()] = use;
19081927
}
19091928
break;
@@ -2019,20 +2038,20 @@ void ConsumeOperatorCopyableAddressesChecker::cloneDeferCalleeAndRewriteUses(
20192038
bool ConsumeOperatorCopyableAddressesChecker::performClosureDataflow(
20202039
Operand *callerOperand, ClosureOperandState &calleeOperandState) {
20212040
auto fas = FullApplySite::isa(callerOperand->getUser());
2022-
auto *func = fas.getCalleeFunction();
2041+
auto *callee = fas.getCalleeFunction();
20232042
auto *address =
2024-
func->begin()->getArgument(fas.getCalleeArgIndex(*callerOperand));
2043+
callee->begin()->getArgument(fas.getCalleeArgIndex(*callerOperand));
20252044

20262045
LLVM_DEBUG(llvm::dbgs() << "Performing closure dataflow on caller use: "
20272046
<< *callerOperand->getUser());
2028-
LLVM_DEBUG(llvm::dbgs() << " Callee: " << func->getName() << '\n');
2047+
LLVM_DEBUG(llvm::dbgs() << " Callee: " << callee->getName() << '\n');
20292048
LLVM_DEBUG(llvm::dbgs() << " Callee Argument: " << *address);
20302049
// We emit an end closure dataflow to make it easier when reading debug output
20312050
// to make it easy to see when we have returned to analyzing the caller.
20322051
SWIFT_DEFER {
20332052
LLVM_DEBUG(llvm::dbgs()
20342053
<< "Finished performing closure dataflow on Callee: "
2035-
<< func->getName() << '\n';);
2054+
<< callee->getName() << '\n';);
20362055
};
20372056
auto accessPathWithBase = AccessPathWithBase::compute(address);
20382057
auto accessPath = accessPathWithBase.accessPath;
@@ -2053,10 +2072,10 @@ bool ConsumeOperatorCopyableAddressesChecker::performClosureDataflow(
20532072
GatherClosureUseVisitor visitor(closureUseState);
20542073
SWIFT_DEFER { visitor.clear(); };
20552074
visitor.reset(address);
2056-
if (!visitAccessPathUses(visitor, accessPath, fn))
2075+
if (!visitAccessPathUses(visitor, accessPath, callee))
20572076
return false;
20582077

2059-
ClosureArgDataflowState closureUseDataflowState(fn, closureUseState);
2078+
ClosureArgDataflowState closureUseDataflowState(callee, closureUseState);
20602079
return closureUseDataflowState.process(address, calleeOperandState,
20612080
closureConsumes);
20622081
}
Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
// RUN: %target-swift-frontend -emit-sil -verify %s
2+
3+
func consume<T>(_: consuming T) {}
4+
5+
func testSingleBlock<T>(x: inout T, y: T) {
6+
defer { x = y }
7+
consume(consume x)
8+
}
9+
10+
func cond() -> Bool { fatalError() }
11+
12+
// TODO: should be accepted
13+
func testAlwaysReinitAfterConditional<T>(x: inout T, y: T) { // not-really expected-error{{used after consume}}
14+
defer {
15+
if cond() { }
16+
x = y // not-really expected-note{{}}
17+
}
18+
consume(consume x) // not-really expected-note{{}}
19+
}
20+
21+
// TODO: should be accepted
22+
func testAlwaysReinitBeforeConditional<T>(x: inout T, y: T) { // not-really expected-error{{used after consume}}
23+
defer {
24+
x = y // not-really expected-note{{}}
25+
if cond() { }
26+
}
27+
consume(consume x) // not-really expected-note{{}}
28+
}
29+
30+
// TODO: should be accepted
31+
func testAlwaysReinitInBothBranchesOfConditional<T>(x: inout T, y: T) { // not-really expected-error{{used after consume}}
32+
defer {
33+
if cond() {
34+
x = y // not-really expected-note{{}}
35+
} else {
36+
x = y
37+
}
38+
}
39+
consume(consume x) // not-really expected-note{{}}
40+
}
41+
42+
// TODO: should raise an error about inout not being reinitialized on all paths
43+
func testSometimesReinitInConditional<T>(x: inout T, y: T) { // not-really expected-error{{used after consume}}
44+
defer {
45+
if cond() {
46+
x = y // not-really expected-note{{}}
47+
} else {
48+
// ex/pected-note {{not initialized on this path}}
49+
}
50+
}
51+
consume(consume x) // not-really expected-note{{}}
52+
}

0 commit comments

Comments
 (0)