Skip to content

Commit 7b00925

Browse files
authored
Merge pull request #59491 from gottesmm/pr-ca943b37d72fe7d242326b81483ea03ed749e4ae
[move-function-value] When searching for uses to emit notes about in a sub-loop from the move, skip destroy_value.
2 parents 933ae41 + 568cdba commit 7b00925

File tree

2 files changed

+129
-50
lines changed

2 files changed

+129
-50
lines changed

lib/SILOptimizer/Mandatory/MoveKillsCopyableValuesChecker.cpp

Lines changed: 82 additions & 50 deletions
Original file line numberDiff line numberDiff line change
@@ -250,24 +250,32 @@ void MoveKillsCopyableValuesChecker::emitDiagnosticForMove(
250250

251251
// Then we do a bit of work to figure out where /all/ of the later uses than
252252
// mvi are so we can emit notes to the user telling them this is a problem
253-
// use. We can do a little more work here since we are going to be emitting a
254-
// fatalError ending the program.
253+
// use. We can do a little more work here since we already know that we are
254+
// going to be emitting a diagnostic and thus later parts of the compiler are
255+
// not going to run. First we look for uses in the same block as our move.
255256
auto *mviBlock = mvi->getParent();
256257
auto mviBlockLiveness = livenessInfo.liveness.getBlockLiveness(mviBlock);
257258
switch (mviBlockLiveness) {
258259
case PrunedLiveBlocks::Dead:
259260
llvm_unreachable("We should never see this");
260261
case PrunedLiveBlocks::LiveWithin: {
261262
// The boundary was within our block. We need to search for uses later than
262-
// us and emit a diagnostic upon them. Then we return. We leave the rest of
263+
// us and emit a diagnostic upon them and then return. We leave the rest of
263264
// the function for the implementation of the LiveOutCase.
265+
//
266+
// NOTE: This does mean that once the user fixes this use, they will get
267+
// additional errors that we did not diagnose before. We do this to simplify
268+
// the implementation noting that the program in either case will not
269+
// compile meaning correctness will be maintained despite this
270+
// implementation choice.
264271
for (SILInstruction &inst :
265272
make_range(std::next(mvi->getIterator()), mviBlock->end())) {
266273
switch (livenessInfo.liveness.isInterestingUser(&inst)) {
267274
case PrunedLiveness::NonUser:
268275
break;
269276
case PrunedLiveness::NonLifetimeEndingUse:
270277
case PrunedLiveness::LifetimeEndingUse:
278+
LLVM_DEBUG(llvm::dbgs() << "Emitting note for in block use: " << inst);
271279
diagnose(astContext, inst.getLoc().getSourceLoc(),
272280
diag::sil_movekillscopyablevalue_use_here);
273281
break;
@@ -287,73 +295,97 @@ void MoveKillsCopyableValuesChecker::emitDiagnosticForMove(
287295
"We are handling only the live out case here. The rest of the cases "
288296
"were handled in the switch above and return early upon success");
289297

298+
// Ok, our boundary was later, so we need to search the CFG along successor
299+
// edges starting at the successors's of our move function block
290300
BasicBlockWorklist worklist(mvi->getFunction());
291301
for (auto *succBlock : mvi->getParent()->getSuccessorBlocks()) {
292302
worklist.pushIfNotVisited(succBlock);
293303
}
294304

295305
// In order to make sure that we do not miss uses that are within loops, we
296-
// maintain a list of all user sets. The issue is that a block at a deeper
297-
// loop level than our def, even if it contained the use that triggered the
298-
// issue will be LiveOut. So when we see a live out block, we perform this
299-
// extra check and emit a diagnostic if needed.
306+
// maintain a list of all user sets.
307+
//
308+
// DISCUSSION: The issue is that a block at a deeper loop level than our def,
309+
// even if it contained the use that triggered the issue will be LiveOut. So
310+
// when we see a live out block, we perform this extra check and emit a
311+
// diagnostic if needed.
300312
BasicBlockSet usesToCheckForInLiveOutBlocks(mvi->getFunction());
301313
for (auto *user : livenessInfo.nonLifetimeEndingUsesInLiveOut)
302314
usesToCheckForInLiveOutBlocks.insert(user->getParent());
303-
for (auto *consumingUse : livenessInfo.consumingUse)
304-
usesToCheckForInLiveOutBlocks.insert(consumingUse->getParentBlock());
315+
for (auto *consumingUse : livenessInfo.consumingUse) {
316+
// We ignore consuming uses that are destroy_value since in our model they
317+
// do not provide liveness.
318+
if (!isa<DestroyValueInst>(consumingUse->getUser()))
319+
usesToCheckForInLiveOutBlocks.insert(consumingUse->getParentBlock());
320+
}
305321

306322
while (auto *block = worklist.pop()) {
307-
if (PrunedLiveBlocks::LiveOut ==
323+
// First do a quick check if we are not a live out block. If so, the
324+
// boundary was within the block. We need to search for interesting uses in
325+
// the block and then emit diagnostics upon them. We then continue without
326+
// adding successors since we do not need to look further than the pruned
327+
// liveness boundary for uses.
328+
if (PrunedLiveBlocks::LiveOut !=
308329
livenessInfo.liveness.getBlockLiveness(block)) {
309-
// Make sure that if we have a liveout block that is at a lower level in
310-
// the loop nest than our def and we have a use in that block, that we
311-
// emit an error. We know it is after the move since we are visiting
312-
// instructions in successors of move.
313-
if (usesToCheckForInLiveOutBlocks.contains(block)) {
314-
for (SILInstruction &inst : *block) {
315-
if (livenessInfo.nonLifetimeEndingUsesInLiveOut.contains(&inst)) {
316-
diagnose(astContext, inst.getLoc().getSourceLoc(),
317-
diag::sil_movekillscopyablevalue_use_here);
318-
continue;
319-
}
320-
for (auto &op : inst.getAllOperands()) {
321-
if (livenessInfo.consumingUse.contains(&op)) {
322-
// If one of our in loop moves is ourselves, then we know that our
323-
// original value is outside of the loop and thus we have a loop
324-
// carry dataflow violation.
325-
if (mvi == &inst) {
326-
diagnose(
327-
astContext, inst.getLoc().getSourceLoc(),
328-
diag::sil_movekillscopyablevalue_value_consumed_in_loop);
329-
continue;
330-
}
331-
332-
diagnose(astContext, inst.getLoc().getSourceLoc(),
333-
diag::sil_movekillscopyablevalue_use_here);
334-
continue;
335-
}
336-
}
330+
for (SILInstruction &inst : *block) {
331+
switch (livenessInfo.liveness.isInterestingUser(&inst)) {
332+
case PrunedLiveness::NonUser:
333+
break;
334+
case PrunedLiveness::NonLifetimeEndingUse:
335+
case PrunedLiveness::LifetimeEndingUse:
336+
LLVM_DEBUG(llvm::dbgs()
337+
<< "(3) Emitting diagnostic for user: " << inst);
338+
diagnose(astContext, inst.getLoc().getSourceLoc(),
339+
diag::sil_movekillscopyablevalue_use_here);
340+
break;
337341
}
338342
}
339-
340-
for (auto *succBlock : block->getSuccessorBlocks()) {
341-
worklist.pushIfNotVisited(succBlock);
342-
}
343343
continue;
344344
}
345345

346-
// The boundary was within the block. We need to search for interesting uses
347-
// in the block and then emit diagnostics upon them.
346+
// Otherwise, we have a live out block. First before we do anything, add the
347+
// successors of this block to the worklist.
348+
for (auto *succBlock : block->getSuccessorBlocks())
349+
worklist.pushIfNotVisited(succBlock);
350+
351+
// Then check if we have any of those deeper loop nest uses. If not, we are
352+
// done with this block and continue...
353+
if (!usesToCheckForInLiveOutBlocks.contains(block))
354+
continue;
355+
356+
// Ok! This is a live out block with a use we need to emit an error for . We
357+
// know it is reachable from the move since we are walking successors from
358+
// the move block. Of course, if we do not have any such uses... just
359+
// continue.
348360
for (SILInstruction &inst : *block) {
349-
switch (livenessInfo.liveness.isInterestingUser(&inst)) {
350-
case PrunedLiveness::NonUser:
351-
break;
352-
case PrunedLiveness::NonLifetimeEndingUse:
353-
case PrunedLiveness::LifetimeEndingUse:
361+
if (livenessInfo.nonLifetimeEndingUsesInLiveOut.contains(&inst)) {
362+
LLVM_DEBUG(llvm::dbgs()
363+
<< "(1) Emitting diagnostic for user: " << inst);
354364
diagnose(astContext, inst.getLoc().getSourceLoc(),
355365
diag::sil_movekillscopyablevalue_use_here);
356-
break;
366+
continue;
367+
}
368+
369+
for (auto &op : inst.getAllOperands()) {
370+
if (livenessInfo.consumingUse.contains(&op)) {
371+
// If one of our in loop moves is ourselves, then we know that our
372+
// original value is outside of the loop and thus we have a loop
373+
// carry dataflow violation.
374+
if (mvi == &inst) {
375+
diagnose(astContext, inst.getLoc().getSourceLoc(),
376+
diag::sil_movekillscopyablevalue_value_consumed_in_loop);
377+
continue;
378+
}
379+
// We ignore consuming uses that are destroy_value since in our model
380+
// they do not provide liveness.
381+
if (isa<DestroyValueInst>(inst))
382+
continue;
383+
384+
LLVM_DEBUG(llvm::dbgs()
385+
<< "(2) Emitting diagnostic for user: " << inst);
386+
diagnose(astContext, inst.getLoc().getSourceLoc(),
387+
diag::sil_movekillscopyablevalue_use_here);
388+
}
357389
}
358390
}
359391
}
Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
// RUN: %target-sil-opt -enable-sil-verify-all -o - -sil-move-kills-copyable-values-checker -verify %s
2+
3+
// This file is meant for specific SIL patterns that may be hard to produce with
4+
// the current swift frontend but have reproduced before and we want to make
5+
// sure keep on working.
6+
7+
import Builtin
8+
9+
class Klass {}
10+
11+
struct Int {
12+
var _value: Builtin.Int64
13+
}
14+
15+
enum Optional<T> {
16+
case none
17+
case some(T)
18+
}
19+
20+
// Make sure that we only emit an error on the use within the loop and not on
21+
// the destroy_value along the exit edge of the loop.
22+
sil [ossa] @useInLoopWithDestroyOutOfLoop : $@convention(thin) (@guaranteed Klass) -> () {
23+
bb0(%0 : @guaranteed $Klass):
24+
debug_value %0 : $Klass, let, name "x", argno 1
25+
%2 = begin_borrow [lexical] %0 : $Klass // expected-error {{'y' used after being moved}}
26+
debug_value %2 : $Klass, let, name "y"
27+
%4 = copy_value %2 : $Klass
28+
%5 = move_value [allows_diagnostics] %4 : $Klass // expected-note {{move here}}
29+
destroy_value %5 : $Klass
30+
br bb1
31+
32+
bb1:
33+
switch_enum undef : $Optional<Int>, case #Optional.some!enumelt: bb2, case #Optional.none!enumelt: bb3
34+
35+
bb2(%58 : $Int):
36+
%59 = copy_value %2 : $Klass
37+
%60 = begin_borrow [lexical] %59 : $Klass // expected-note {{use here}}
38+
debug_value %60 : $Klass, let, name "m"
39+
end_borrow %60 : $Klass
40+
destroy_value %59 : $Klass
41+
br bb1
42+
43+
bb3:
44+
end_borrow %2 : $Klass
45+
%67 = tuple ()
46+
return %67 : $()
47+
}

0 commit comments

Comments
 (0)