Skip to content

Commit 20479c9

Browse files
committed
[move-only] Refactor CanonicalizeOSSALifetime::canonicalizeValueLifetime into an API that computes liveness and a second API that rewrites copies/destroys and fix up MoveOnly checkers to use it.
For those who are unaware, CanonicalizeOSSALifetime::canonicalizeValueLifetime() is really a high level driver routine for the functionality of CanonicalizeOSSALifetime that computes liveness and then rewrites copies using boundary information. This change introduces splits the implementation of canonicalizeValueLifetime into two parts: a first part called computeLiveness and a second part called rewriteLifetimes. Internally canonicalizeValueLifetime still just calls these two methods. The reason why I am doing this is that it lets the move only object checker use the raw liveness information computed before the rewriting mucks with the analysis information. This information is used by the checker to compute the raw liveness boundary of a value and use that information to determine the list of consuming uses not on the boundary, consuming uses on the boundary, and non-consuming uses on the boundary. This is then used by later parts of the checker to emit our errors. Some additional benefits of doing this are: 1. I was able to eliminate callbacks in the rewriting stage of CanonicalOSSALifetimes which previously gave the checker this information. 2. Previously the move checker did not have access to the non-consuming boundary uses causing us to always fail appropriately, but sadly not emit a note showing the non-consuming use. I am going to wire this up in a subsequent commit. The other change to the implementation of the move checker that this caused is that I needed to add an extra diagnostic check for instructions that consume the value twice or consume the value and use the value. The reason why this must be done is that liveness does not distinguish in between different operands on the same instruction meaning such an error would be lost.
1 parent c6942ee commit 20479c9

11 files changed

+403
-106
lines changed

include/swift/SIL/PrunedLiveness.h

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -132,8 +132,10 @@
132132
#include "swift/SIL/OwnershipUtils.h"
133133
#include "swift/SIL/SILBasicBlock.h"
134134
#include "swift/SIL/SILFunction.h"
135+
#include "swift/SIL/SILInstruction.h"
135136
#include "llvm/ADT/MapVector.h"
136137
#include "llvm/ADT/PointerIntPair.h"
138+
#include "llvm/ADT/STLExtras.h"
137139
#include "llvm/ADT/SmallVector.h"
138140

139141
namespace swift {
@@ -542,6 +544,47 @@ class PrunedLiveness {
542544
return useIter->second ? LifetimeEndingUse : NonLifetimeEndingUse;
543545
}
544546

547+
using ConstUserRange =
548+
iterator_range<const std::pair<SILInstruction *, bool> *>;
549+
ConstUserRange getAllUsers() const {
550+
return llvm::make_range(users.begin(), users.end());
551+
}
552+
553+
/// A namespace containing helper functors for use with various mapped
554+
/// ranges. Intended to be used to hide these noise types when working in an
555+
/// IDE.
556+
struct LifetimeEndingUserIteratorHelpers {
557+
struct MapFunctor {
558+
SILInstruction *
559+
operator()(const std::pair<SILInstruction *, bool> &pair) const {
560+
// Strip off the const to ease use with other APIs.
561+
return const_cast<SILInstruction *>(pair.first);
562+
}
563+
};
564+
565+
struct FilterFunctor {
566+
bool operator()(const std::pair<SILInstruction *, bool> &pair) const {
567+
return pair.second;
568+
}
569+
};
570+
571+
using MapFilterIter = llvm::mapped_iterator<
572+
llvm::filter_iterator<const std::pair<SILInstruction *, bool> *,
573+
FilterFunctor>,
574+
MapFunctor>;
575+
};
576+
using LifetimeEndingUserRange =
577+
llvm::iterator_range<LifetimeEndingUserIteratorHelpers::MapFilterIter>;
578+
579+
/// Return a range consisting of the current set of consuming users fed into
580+
/// this PrunedLiveness instance.
581+
LifetimeEndingUserRange getLifetimeEndingUsers() const {
582+
return map_range(
583+
llvm::make_filter_range(
584+
getAllUsers(), LifetimeEndingUserIteratorHelpers::FilterFunctor()),
585+
LifetimeEndingUserIteratorHelpers::MapFunctor());
586+
}
587+
545588
void print(llvm::raw_ostream &OS) const;
546589
void dump() const;
547590
};

include/swift/SILOptimizer/Utils/CanonicalizeOSSALifetime.h

Lines changed: 42 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -213,15 +213,6 @@ class CanonicalizeOSSALifetime final {
213213
/// copies.
214214
bool maximizeLifetime;
215215

216-
/// If true and we are processing a value of move_only type, emit a diagnostic
217-
/// when-ever we need to insert a copy_value.
218-
std::function<void(Operand *)> moveOnlyCopyValueNotification;
219-
220-
/// If true and we are processing a value of move_only type, pass back to the
221-
/// caller any consuming uses that are going to be used as part of the final
222-
/// lifetime boundary in case we need to emit diagnostics.
223-
std::function<void(Operand *)> moveOnlyFinalConsumingUse;
224-
225216
// If present, will be used to ensure that the lifetime is not shortened to
226217
// end inside an access scope which it previously enclosed. (Note that ending
227218
// before such an access scope is fine regardless.)
@@ -289,27 +280,10 @@ class CanonicalizeOSSALifetime final {
289280
}
290281
}
291282

292-
void maybeNotifyMoveOnlyCopy(Operand *use) {
293-
if (!moveOnlyCopyValueNotification)
294-
return;
295-
moveOnlyCopyValueNotification(use);
296-
}
297-
298-
void maybeNotifyFinalConsumingUse(Operand *use) {
299-
if (!moveOnlyFinalConsumingUse)
300-
return;
301-
moveOnlyFinalConsumingUse(use);
302-
}
303-
304-
CanonicalizeOSSALifetime(
305-
bool pruneDebugMode, bool maximizeLifetime,
306-
NonLocalAccessBlockAnalysis *accessBlockAnalysis, DominanceInfo *domTree,
307-
InstructionDeleter &deleter,
308-
std::function<void(Operand *)> moveOnlyCopyValueNotification = nullptr,
309-
std::function<void(Operand *)> moveOnlyFinalConsumingUse = nullptr)
283+
CanonicalizeOSSALifetime(bool pruneDebugMode, bool maximizeLifetime,
284+
NonLocalAccessBlockAnalysis *accessBlockAnalysis,
285+
DominanceInfo *domTree, InstructionDeleter &deleter)
310286
: pruneDebugMode(pruneDebugMode), maximizeLifetime(maximizeLifetime),
311-
moveOnlyCopyValueNotification(moveOnlyCopyValueNotification),
312-
moveOnlyFinalConsumingUse(moveOnlyFinalConsumingUse),
313287
accessBlockAnalysis(accessBlockAnalysis), domTree(domTree),
314288
deleter(deleter),
315289
liveness(maximizeLifetime ? &discoveredBlocks : nullptr) {}
@@ -348,8 +322,47 @@ class CanonicalizeOSSALifetime final {
348322
/// operands.
349323
bool canonicalizeValueLifetime(SILValue def);
350324

325+
/// Compute the liveness information for \p def. But do not do any rewriting
326+
/// or computation of boundaries.
327+
///
328+
/// The intention is that this is used if one wants to emit diagnostics using
329+
/// the liveness information before doing any rewriting.
330+
bool computeLiveness(SILValue def);
331+
332+
/// Given the already computed liveness boundary for the given def, rewrite
333+
/// copies of def as appropriate.
334+
///
335+
/// NOTE: It is assumed that one passes the extended boundary from \see
336+
/// computeLiveness.
337+
///
338+
/// NOTE: It is assumed that one has emitted any diagnostics.
339+
void rewriteLifetimes();
340+
341+
/// Return the pure original boundary just based off of liveness information
342+
/// without maximizing or extending liveness.
343+
void findOriginalBoundary(PrunedLivenessBoundary &resultingOriginalBoundary);
344+
351345
InstModCallbacks &getCallbacks() { return deleter.getCallbacks(); }
352346

347+
using IsInterestingUser = PrunedLiveness::IsInterestingUser;
348+
349+
/// Helper method that returns the isInterestingUser status of \p user in the
350+
/// passed in Liveness.
351+
///
352+
/// NOTE: Only call this after calling computeLivenessBoundary or the results
353+
/// will not be initialized.
354+
IsInterestingUser isInterestingUser(SILInstruction *user) const {
355+
return liveness.isInterestingUser(user);
356+
}
357+
358+
using LifetimeEndingUserRange = PrunedLiveness::LifetimeEndingUserRange;
359+
LifetimeEndingUserRange getLifetimeEndingUsers() const {
360+
return liveness.getLifetimeEndingUsers();
361+
}
362+
363+
using UserRange = PrunedLiveness::ConstUserRange;
364+
UserRange getUsers() const { return liveness.getAllUsers(); }
365+
353366
private:
354367
void recordDebugValue(DebugValueInst *dvi) { debugValues.insert(dvi); }
355368

@@ -362,8 +375,6 @@ class CanonicalizeOSSALifetime final {
362375

363376
void extendLivenessThroughOverlappingAccess();
364377

365-
void findOriginalBoundary(PrunedLivenessBoundary &boundary);
366-
367378
void findExtendedBoundary(PrunedLivenessBoundary const &originalBoundary,
368379
PrunedLivenessBoundary &boundary);
369380

lib/SILOptimizer/Mandatory/MoveOnlyAddressChecker.cpp

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1012,6 +1012,8 @@ bool GatherUsesVisitor::visitUse(Operand *op, AccessUseType useTy) {
10121012
// For convenience, grab the user of op.
10131013
auto *user = op->getUser();
10141014

1015+
LLVM_DEBUG(llvm::dbgs() << "Visiting user: " << *user);
1016+
10151017
// First check if we have init/reinit. These are quick/simple.
10161018
if (::memInstMustInitialize(op)) {
10171019
LLVM_DEBUG(llvm::dbgs() << "Found init: " << *user);

lib/SILOptimizer/Mandatory/MoveOnlyBorrowToDestructureTransform.cpp

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -294,11 +294,11 @@ void BorrowToDestructureTransform::checkForErrorsOnSameInstruction() {
294294
continue;
295295

296296
if (badOperand->isConsuming())
297-
diagnosticEmitter.emitObjectConsumesDestructuredValueTwice(
298-
mmci, use, badOperand);
297+
diagnosticEmitter.emitObjectInstConsumesValueTwice(mmci, use,
298+
badOperand);
299299
else
300-
diagnosticEmitter.emitObjectConsumesAndUsesDestructuredValue(
301-
mmci, use, badOperand);
300+
diagnosticEmitter.emitObjectInstConsumesAndUsesValue(mmci, use,
301+
badOperand);
302302
emittedError = true;
303303
}
304304

@@ -359,7 +359,9 @@ bool BorrowToDestructureTransform::gatherBorrows(
359359
return true;
360360
}
361361

362-
LLVM_DEBUG(llvm::dbgs() << "Searching for borrows for inst: " << *mmci);
362+
LLVM_DEBUG(llvm::dbgs() << "Performing BorrowToDestructureTramsform!\n"
363+
"Searching for borrows for inst: "
364+
<< *mmci);
363365

364366
StackList<Operand *> worklist(mmci->getFunction());
365367
for (auto *op : mmci->getUses())

lib/SILOptimizer/Mandatory/MoveOnlyDiagnostics.cpp

Lines changed: 15 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -153,41 +153,38 @@ void DiagnosticEmitter::emitObjectDiagnosticsForFoundUses(
153153
bool ignorePartialApplyUses) const {
154154
auto &astContext = fn->getASTContext();
155155

156-
for (auto *consumingUse : getCanonicalizer().consumingUsesNeedingCopy) {
156+
for (auto *consumingUser : getCanonicalizer().consumingUsesNeedingCopy) {
157157
// See if the consuming use is an owned moveonly_to_copyable whose only
158158
// user is a return. In that case, use the return loc instead. We do this
159159
// b/c it is illegal to put a return value location on a non-return value
160160
// instruction... so we have to hack around this slightly.
161-
auto *user = consumingUse->getUser();
162-
auto loc = user->getLoc();
163-
if (auto *mtc = dyn_cast<MoveOnlyWrapperToCopyableValueInst>(user)) {
161+
auto loc = consumingUser->getLoc();
162+
if (auto *mtc =
163+
dyn_cast<MoveOnlyWrapperToCopyableValueInst>(consumingUser)) {
164164
if (auto *ri = mtc->getSingleUserOfType<ReturnInst>()) {
165165
loc = ri->getLoc();
166166
}
167167
}
168168

169-
if (ignorePartialApplyUses &&
170-
isa<PartialApplyInst>(consumingUse->getUser()))
169+
if (ignorePartialApplyUses && isa<PartialApplyInst>(consumingUser))
171170
continue;
172171
diagnose(astContext, loc.getSourceLoc(),
173172
diag::sil_moveonlychecker_consuming_use_here);
174173
}
175174

176-
for (auto *consumingUse : getCanonicalizer().finalConsumingUses) {
175+
for (auto *user : getCanonicalizer().consumingBoundaryUsers) {
177176
// See if the consuming use is an owned moveonly_to_copyable whose only
178177
// user is a return. In that case, use the return loc instead. We do this
179178
// b/c it is illegal to put a return value location on a non-return value
180179
// instruction... so we have to hack around this slightly.
181-
auto *user = consumingUse->getUser();
182180
auto loc = user->getLoc();
183181
if (auto *mtc = dyn_cast<MoveOnlyWrapperToCopyableValueInst>(user)) {
184182
if (auto *ri = mtc->getSingleUserOfType<ReturnInst>()) {
185183
loc = ri->getLoc();
186184
}
187185
}
188186

189-
if (ignorePartialApplyUses &&
190-
isa<PartialApplyInst>(consumingUse->getUser()))
187+
if (ignorePartialApplyUses && isa<PartialApplyInst>(user))
191188
continue;
192189

193190
diagnose(astContext, loc.getSourceLoc(),
@@ -198,39 +195,37 @@ void DiagnosticEmitter::emitObjectDiagnosticsForFoundUses(
198195
void DiagnosticEmitter::emitObjectDiagnosticsForPartialApplyUses() const {
199196
auto &astContext = fn->getASTContext();
200197

201-
for (auto *consumingUse : getCanonicalizer().consumingUsesNeedingCopy) {
198+
for (auto *user : getCanonicalizer().consumingUsesNeedingCopy) {
202199
// See if the consuming use is an owned moveonly_to_copyable whose only
203200
// user is a return. In that case, use the return loc instead. We do this
204201
// b/c it is illegal to put a return value location on a non-return value
205202
// instruction... so we have to hack around this slightly.
206-
auto *user = consumingUse->getUser();
207203
auto loc = user->getLoc();
208204
if (auto *mtc = dyn_cast<MoveOnlyWrapperToCopyableValueInst>(user)) {
209205
if (auto *ri = mtc->getSingleUserOfType<ReturnInst>()) {
210206
loc = ri->getLoc();
211207
}
212208
}
213209

214-
if (!isa<PartialApplyInst>(consumingUse->getUser()))
210+
if (!isa<PartialApplyInst>(user))
215211
continue;
216212
diagnose(astContext, loc.getSourceLoc(),
217213
diag::sil_moveonlychecker_consuming_closure_use_here);
218214
}
219215

220-
for (auto *consumingUse : getCanonicalizer().finalConsumingUses) {
216+
for (auto *user : getCanonicalizer().consumingBoundaryUsers) {
221217
// See if the consuming use is an owned moveonly_to_copyable whose only
222218
// user is a return. In that case, use the return loc instead. We do this
223219
// b/c it is illegal to put a return value location on a non-return value
224220
// instruction... so we have to hack around this slightly.
225-
auto *user = consumingUse->getUser();
226221
auto loc = user->getLoc();
227222
if (auto *mtc = dyn_cast<MoveOnlyWrapperToCopyableValueInst>(user)) {
228223
if (auto *ri = mtc->getSingleUserOfType<ReturnInst>()) {
229224
loc = ri->getLoc();
230225
}
231226
}
232227

233-
if (!isa<PartialApplyInst>(consumingUse->getUser()))
228+
if (!isa<PartialApplyInst>(user))
234229
continue;
235230

236231
diagnose(astContext, loc.getSourceLoc(),
@@ -454,14 +449,13 @@ void DiagnosticEmitter::emitObjectDestructureNeededWithinBorrowBoundary(
454449
registerDiagnosticEmitted(markedValue);
455450
}
456451

457-
void DiagnosticEmitter::emitObjectConsumesDestructuredValueTwice(
452+
void DiagnosticEmitter::emitObjectInstConsumesValueTwice(
458453
MarkMustCheckInst *markedValue, Operand *firstUse, Operand *secondUse) {
459454
assert(firstUse->getUser() == secondUse->getUser());
460455
assert(firstUse->isConsuming());
461456
assert(secondUse->isConsuming());
462457

463-
LLVM_DEBUG(
464-
llvm::dbgs() << "Emitting object consumes destructure twice error!\n");
458+
LLVM_DEBUG(llvm::dbgs() << "Emitting object consumes value twice error!\n");
465459
LLVM_DEBUG(llvm::dbgs() << " Mark: " << *markedValue);
466460
LLVM_DEBUG(llvm::dbgs() << " User: " << *firstUse->getUser());
467461
LLVM_DEBUG(llvm::dbgs() << " First Conflicting Operand: "
@@ -480,15 +474,14 @@ void DiagnosticEmitter::emitObjectConsumesDestructuredValueTwice(
480474
registerDiagnosticEmitted(markedValue);
481475
}
482476

483-
void DiagnosticEmitter::emitObjectConsumesAndUsesDestructuredValue(
477+
void DiagnosticEmitter::emitObjectInstConsumesAndUsesValue(
484478
MarkMustCheckInst *markedValue, Operand *consumingUse,
485479
Operand *nonConsumingUse) {
486480
assert(consumingUse->getUser() == nonConsumingUse->getUser());
487481
assert(consumingUse->isConsuming());
488482
assert(!nonConsumingUse->isConsuming());
489483

490-
LLVM_DEBUG(
491-
llvm::dbgs() << "Emitting object consumes destructure twice error!\n");
484+
LLVM_DEBUG(llvm::dbgs() << "Emitting object consumeed and used error!\n");
492485
LLVM_DEBUG(llvm::dbgs() << " Mark: " << *markedValue);
493486
LLVM_DEBUG(llvm::dbgs() << " User: " << *consumingUse->getUser());
494487
LLVM_DEBUG(llvm::dbgs() << " Consuming Operand: "

lib/SILOptimizer/Mandatory/MoveOnlyDiagnostics.h

Lines changed: 6 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -91,13 +91,12 @@ class DiagnosticEmitter {
9191
TypeTreeLeafTypeRange destructureNeededBits,
9292
FieldSensitivePrunedLivenessBoundary &boundary);
9393

94-
void emitObjectConsumesDestructuredValueTwice(MarkMustCheckInst *markedValue,
95-
Operand *firstConsumingUse,
96-
Operand *secondConsumingUse);
97-
void
98-
emitObjectConsumesAndUsesDestructuredValue(MarkMustCheckInst *markedValue,
99-
Operand *consumingUse,
100-
Operand *nonConsumingUse);
94+
void emitObjectInstConsumesValueTwice(MarkMustCheckInst *markedValue,
95+
Operand *firstConsumingUse,
96+
Operand *secondConsumingUse);
97+
void emitObjectInstConsumesAndUsesValue(MarkMustCheckInst *markedValue,
98+
Operand *consumingUse,
99+
Operand *nonConsumingUse);
101100

102101
private:
103102
/// Emit diagnostics for the final consuming uses and consuming uses needing

0 commit comments

Comments
 (0)