Skip to content

Commit 6ff00e5

Browse files
committed
[move-only] Ban partial reinitialization after consuming a value.
This is similar to our ban on partial consuming a value for this release. The reason for this is that, one can achieve a similar affect as partial consumption via a consumption of the entire value and then a partial reinitialization. Example: ```swift struct X : ~Copyable { var i = 5, var i2 = Klass() } var x = X() _ = consume x x.i = 5 ``` in the case above, we now have a value that is in a partially initialized state. We still allow for move only types to have their fields initialized as long as there is an intervening init. rdar://111498740
1 parent d282ce1 commit 6ff00e5

File tree

7 files changed

+552
-41
lines changed

7 files changed

+552
-41
lines changed

include/swift/AST/DiagnosticsSIL.def

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -788,6 +788,12 @@ ERROR(sil_movechecking_cannot_destructure_has_deinit, none,
788788
ERROR(sil_movechecking_cannot_destructure, none,
789789
"cannot partially consume '%0'",
790790
(StringRef))
791+
ERROR(sil_movechecking_cannot_partially_reinit_has_deinit, none,
792+
"cannot partially reinitialize '%0' when it has a deinitializer; only full reinitialization is allowed",
793+
(StringRef))
794+
ERROR(sil_movechecking_cannot_partially_reinit, none,
795+
"cannot partially reinitialize '%0' after it has been consumed; only full reinitialization is allowed",
796+
(StringRef))
791797
ERROR(sil_movechecking_discard_missing_consume_self, none,
792798
"must consume 'self' before exiting method that discards self", ())
793799
ERROR(sil_movechecking_reinit_after_discard, none,

include/swift/SIL/FieldSensitivePrunedLiveness.h

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@
2424
#include "swift/Basic/Debug.h"
2525
#include "swift/Basic/FrozenMultiMap.h"
2626
#include "swift/Basic/STLExtras.h"
27+
#include "swift/SIL/BasicBlockDatastructures.h"
2728
#include "swift/SIL/SILFunction.h"
2829
#include "swift/SIL/SILInstruction.h"
2930
#include "swift/SIL/SILValue.h"
@@ -1037,6 +1038,7 @@ class FieldSensitivePrunedLivenessBoundary {
10371038
}
10381039

10391040
SmallBitVector &getDeadDefsBits(SILNode *def) {
1041+
assert(def->getParentBlock() && "Always expect to have a parent block!\n");
10401042
auto iter = deadDefs.insert({def, SmallBitVector()});
10411043
if (iter.second) {
10421044
iter.first->second.resize(numBits);
@@ -1359,6 +1361,16 @@ class FieldSensitiveMultiDefPrunedLiveRange
13591361
void
13601362
findBoundariesInBlock(SILBasicBlock *block, unsigned bitNo, bool isLiveOut,
13611363
FieldSensitivePrunedLivenessBoundary &boundary) const;
1364+
1365+
/// Walk from \p inst until we find a def for \p index. If we see a consuming
1366+
/// use, call \p callback. If \p callback returns true, then this is not the
1367+
/// consuming use we are looking for and we should keep on
1368+
/// searching. Otherwise, if it returns false, we bail early and return
1369+
/// false. If we find a def, we return true. If we stopped due to a consuming
1370+
/// use, we return false.
1371+
bool findEarlierConsumingUse(
1372+
SILInstruction *inst, unsigned index,
1373+
llvm::function_ref<bool(SILInstruction *)> callback) const;
13621374
};
13631375

13641376
} // namespace swift

lib/SIL/Utils/FieldSensitivePrunedLiveness.cpp

Lines changed: 103 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1107,7 +1107,107 @@ void FieldSensitiveMultiDefPrunedLiveRange::findBoundariesInBlock(
11071107
<< " Live at beginning of block! No dead args!\n");
11081108
}
11091109

1110-
assert((isLiveOut ||
1111-
prevCount < boundary.getNumLastUsersAndDeadDefs(bitNo)) &&
1112-
"findBoundariesInBlock must be called on a live block");
1110+
assert(
1111+
(isLiveOut || prevCount < boundary.getNumLastUsersAndDeadDefs(bitNo)) &&
1112+
"findBoundariesInBlock must be called on a live block");
1113+
}
1114+
1115+
bool FieldSensitiveMultiDefPrunedLiveRange::findEarlierConsumingUse(
1116+
SILInstruction *inst, unsigned index,
1117+
llvm::function_ref<bool(SILInstruction *)> callback) const {
1118+
PRUNED_LIVENESS_LOG(
1119+
llvm::dbgs()
1120+
<< "Performing single block search for consuming use for bit: " << index
1121+
<< "!\n");
1122+
1123+
// Walk our block back from inst looking for defs or a consuming use. If we
1124+
// see a def, return true. If we see a use, we keep processing if the callback
1125+
// returns true... and return false early if the callback returns false.
1126+
for (auto ii = std::next(inst->getReverseIterator()),
1127+
ie = inst->getParent()->rend();
1128+
ii != ie; ++ii) {
1129+
PRUNED_LIVENESS_LOG(llvm::dbgs() << "Visiting: " << *ii);
1130+
// If we have a def, then we are automatically done.
1131+
if (isDef(&*ii, index)) {
1132+
PRUNED_LIVENESS_LOG(llvm::dbgs() << " Is Def! Returning true!\n");
1133+
return true;
1134+
}
1135+
1136+
// If we have a consuming use, emit the error.
1137+
if (isInterestingUser(&*ii, index) ==
1138+
IsInterestingUser::LifetimeEndingUse) {
1139+
PRUNED_LIVENESS_LOG(llvm::dbgs() << " Is Lifetime Ending Use!\n");
1140+
if (!callback(&*ii)) {
1141+
PRUNED_LIVENESS_LOG(llvm::dbgs()
1142+
<< " Callback returned false... exiting!\n");
1143+
return false;
1144+
}
1145+
PRUNED_LIVENESS_LOG(llvm::dbgs()
1146+
<< " Callback returned true... continuing!\n");
1147+
}
1148+
1149+
// Otherwise, keep going.
1150+
}
1151+
1152+
// Then check our argument defs.
1153+
for (auto *arg : inst->getParent()->getArguments()) {
1154+
PRUNED_LIVENESS_LOG(llvm::dbgs() << "Visiting arg: " << *arg);
1155+
if (isDef(arg, index)) {
1156+
PRUNED_LIVENESS_LOG(llvm::dbgs() << " Found def. Returning true!\n");
1157+
return true;
1158+
}
1159+
}
1160+
1161+
PRUNED_LIVENESS_LOG(llvm::dbgs() << "Finished single block. Didn't find "
1162+
"anything... Performing interprocedural");
1163+
1164+
// Ok, we now know that we need to look further back.
1165+
BasicBlockWorklist worklist(inst->getFunction());
1166+
for (auto *predBlock : inst->getParent()->getPredecessorBlocks()) {
1167+
worklist.pushIfNotVisited(predBlock);
1168+
}
1169+
1170+
while (auto *next = worklist.pop()) {
1171+
PRUNED_LIVENESS_LOG(llvm::dbgs()
1172+
<< "Checking block bb" << next->getDebugID() << '\n');
1173+
for (auto ii = next->rbegin(), ie = next->rend(); ii != ie; ++ii) {
1174+
PRUNED_LIVENESS_LOG(llvm::dbgs() << "Visiting: " << *ii);
1175+
// If we have a def, then we are automatically done.
1176+
if (isDef(&*ii, index)) {
1177+
PRUNED_LIVENESS_LOG(llvm::dbgs() << " Is Def! Returning true!\n");
1178+
return true;
1179+
}
1180+
1181+
// If we have a consuming use, emit the error.
1182+
if (isInterestingUser(&*ii, index) ==
1183+
IsInterestingUser::LifetimeEndingUse) {
1184+
PRUNED_LIVENESS_LOG(llvm::dbgs() << " Is Lifetime Ending Use!\n");
1185+
if (!callback(&*ii)) {
1186+
PRUNED_LIVENESS_LOG(llvm::dbgs()
1187+
<< " Callback returned false... exiting!\n");
1188+
return false;
1189+
}
1190+
PRUNED_LIVENESS_LOG(llvm::dbgs()
1191+
<< " Callback returned true... continuing!\n");
1192+
}
1193+
1194+
// Otherwise, keep going.
1195+
}
1196+
1197+
for (auto *arg : next->getArguments()) {
1198+
PRUNED_LIVENESS_LOG(llvm::dbgs() << "Visiting arg: " << *arg);
1199+
if (isDef(arg, index)) {
1200+
PRUNED_LIVENESS_LOG(llvm::dbgs() << " Found def. Returning true!\n");
1201+
return true;
1202+
}
1203+
}
1204+
1205+
PRUNED_LIVENESS_LOG(llvm::dbgs()
1206+
<< "Didn't find anything... visiting predecessors!\n");
1207+
for (auto *predBlock : next->getPredecessorBlocks()) {
1208+
worklist.pushIfNotVisited(predBlock);
1209+
}
1210+
}
1211+
1212+
return true;
11131213
}

0 commit comments

Comments
 (0)