-
Notifications
You must be signed in to change notification settings - Fork 10.5k
/
Copy pathLinearLifetimeChecker.cpp
819 lines (707 loc) · 31.2 KB
/
LinearLifetimeChecker.cpp
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
//===--- LinearLifetimeChecker.cpp ----------------------------------------===//
//
// This source file is part of the Swift.org open source project
//
// Copyright (c) 2014 - 2018 Apple Inc. and the Swift project authors
// Licensed under Apache License v2.0 with Runtime Library Exception
//
// See https://swift.org/LICENSE.txt for license information
// See https://swift.org/CONTRIBUTORS.txt for the list of Swift project authors
//
//===----------------------------------------------------------------------===//
///
/// \file
///
/// This file defines a linear lifetime checker for SIL. A value with a linear
/// lifetime is defined as a value that is guaranteed to be consuming exactly
/// once along any path through the program and has a guarantee that all
/// non-consuming uses and the initial value are joint-postdominated by the set
/// of consuming uses.
///
//===----------------------------------------------------------------------===//
#define DEBUG_TYPE "sil-linear-lifetime-checker"
#include "LinearLifetimeCheckerPrivate.h"
#include "swift/Basic/Assertions.h"
#include "swift/Basic/BlotMapVector.h"
#include "swift/Basic/Defer.h"
#include "swift/Basic/FrozenMultiMap.h"
#include "swift/SIL/BasicBlockUtils.h"
#include "swift/SIL/OwnershipUtils.h"
#include "swift/SIL/SILBasicBlock.h"
#include "swift/SIL/SILFunction.h"
#include "swift/SIL/SILUndef.h"
#include "llvm/ADT/DenseMap.h"
#include "llvm/ADT/MapVector.h"
#include "llvm/ADT/STLExtras.h"
#include "llvm/Support/Debug.h"
using namespace swift;
/// Return true if \p operand can legally be consumed by another operand of the
/// same instruction (in parallel).
bool isParallelOperand(Operand *operand) {
return isa<MarkDependenceInst>(operand->getUser())
|| operand->getOperandOwnership() == OperandOwnership::Reborrow
|| operand->getOperandOwnership() == OperandOwnership::GuaranteedForwarding;
}
//===----------------------------------------------------------------------===//
// Declarations
//===----------------------------------------------------------------------===//
namespace {
struct State {
/// If we are checking for a specific value, this is that value. This is only
/// used for diagnostic purposes. The algorithm if this is set works on the
/// parent block of the value.
std::optional<SILValue> value;
/// The insertion point where the live range begins. If the field value is not
/// None, then:
//
// 1. If this value is defined by an instruction, it will be
// value->getDefiningInstruction().
//
// 2. If this is an argument, this is the first instruction in the argument's
// defining block.
SILInstruction *beginInst;
/// A builder object that we use to build a LinearLifetimeChecker::Error
/// object that describes exhaustively the set of errors that we encountered.
///
/// It also handles any asserts/messages that need to be emitted if we are
/// supposed to fail hard.
LinearLifetimeChecker::ErrorBuilder &errorBuilder;
/// The blocks that we have already visited.
BasicBlockSet visitedBlocks;
/// If non-null a callback that we should pass any detected leaking blocks for
/// our caller. The intention is that this can be used in a failing case to
/// put in missing destroys.
std::optional<function_ref<void(SILBasicBlock *)>> leakingBlockCallback;
/// If non-null a callback that we should pass all uses that we detect are not
/// within the linear lifetime we are checking.
std::optional<function_ref<void(Operand *)>>
nonConsumingUseOutsideLifetimeCallback;
/// The list of passed in consuming uses.
ArrayRef<Operand *> consumingUses;
/// extend_lifetime uses.
ArrayRef<Operand *> extendLifetimeUses;
/// The list of passed in non consuming uses.
ArrayRef<Operand *> nonConsumingUses;
/// The set of blocks with consuming uses.
BasicBlockSet blocksWithConsumingUses;
/// The set of blocks with non-consuming uses and the associated
/// non-consuming use SILInstruction.
///
/// NOTE: This is initialized in initializeAllNonConsumingUses after which it
/// is frozen. Before this is frozen, one can only add new (Key, Value) pairs
/// to the map. Once frozen, map operations and blot-erase operations can be
/// performed. Additionally it provides a getRange() operation that can be
/// used to iterate over all (Key, [Value]) pairs ignoring erased keys.
SmallFrozenMultiMap<SILBasicBlock *, Operand *, 8> blocksWithNonConsumingUses;
/// The worklist that we use when performing our block dataflow.
SmallVector<SILBasicBlock *, 32> worklist;
/// A list of successor blocks that we must visit by the time the algorithm
/// terminates.
llvm::SmallSetVector<SILBasicBlock *, 8> successorBlocksThatMustBeVisited;
State(SILValue value, LinearLifetimeChecker::ErrorBuilder &errorBuilder,
std::optional<function_ref<void(SILBasicBlock *)>> leakingBlockCallback,
std::optional<function_ref<void(Operand *)>>
nonConsumingUseOutsideLifetimeCallback,
ArrayRef<Operand *> consumingUses,
ArrayRef<Operand *> extendLifetimeUses,
ArrayRef<Operand *> nonConsumingUses)
: value(value), beginInst(value->getDefiningInsertionPoint()),
errorBuilder(errorBuilder), visitedBlocks(value->getFunction()),
leakingBlockCallback(leakingBlockCallback),
nonConsumingUseOutsideLifetimeCallback(
nonConsumingUseOutsideLifetimeCallback),
consumingUses(consumingUses), extendLifetimeUses(extendLifetimeUses),
nonConsumingUses(nonConsumingUses),
blocksWithConsumingUses(value->getFunction()) {}
State(SILBasicBlock *beginBlock,
LinearLifetimeChecker::ErrorBuilder &errorBuilder,
std::optional<function_ref<void(SILBasicBlock *)>> leakingBlockCallback,
std::optional<function_ref<void(Operand *)>>
nonConsumingUseOutsideLifetimeCallback,
ArrayRef<Operand *> consumingUses,
ArrayRef<Operand *> extendLifetimeUses,
ArrayRef<Operand *> nonConsumingUses)
: value(), beginInst(&*beginBlock->begin()), errorBuilder(errorBuilder),
visitedBlocks(beginBlock->getParent()),
leakingBlockCallback(leakingBlockCallback),
nonConsumingUseOutsideLifetimeCallback(
nonConsumingUseOutsideLifetimeCallback),
consumingUses(consumingUses), extendLifetimeUses(extendLifetimeUses),
nonConsumingUses(nonConsumingUses),
blocksWithConsumingUses(beginBlock->getParent()) {}
SILBasicBlock *getBeginBlock() const { return beginInst->getParent(); }
void initializeAllNonConsumingUses(ArrayRef<Operand *> nonConsumingUsers);
void initializeAllExtendLifetimeUses(
ArrayRef<Operand *> extendLifetimeUses,
SmallVectorImpl<std::pair<Operand *, SILBasicBlock *>>
&predsToAddToWorklist);
void initializeAllConsumingUses(
ArrayRef<Operand *> consumingUsers,
SmallVectorImpl<std::pair<Operand *, SILBasicBlock *>>
&predsToAddToWorklist);
/// Initializes state for a consuming use.
///
/// If we are already tracking a consuming use for the block, this emits a
/// double consume checker error.
void initializeConsumingUse(Operand *consumingUse, SILBasicBlock *userBlock);
/// Check that this newly initialized consuming user does not have any
/// non-consuming uses after it. If the checker finds one, it emits a checker
/// error.
void checkForSameBlockUseAfterFree(Operand *consumingUse,
SILBasicBlock *userBlock);
/// Once we have marked all of our producing blocks.
void checkPredsForDoubleConsume(Operand *consumingUse,
SILBasicBlock *userBlock);
void checkPredsForDoubleConsume(SILBasicBlock *userBlock);
/// Once we have setup all of our consuming/non-consuming blocks and have
/// validated that all intra-block dataflow is safe, perform the inter-block
/// dataflow.
void performDataflow(DeadEndBlocks *deBlocks);
/// After we have performed the dataflow, check the end state of our dataflow
/// for validity. If this is a linear typed value, return true. Return false
/// otherwise.
void checkDataflowEndState(DeadEndBlocks *deBlocks);
void dumpConsumingUsers() const {
llvm::errs() << "Consuming Users:\n";
for (auto *use : consumingUses) {
llvm::errs() << *use->getUser();
}
llvm::errs() << "\n";
}
void dumpNonConsumingUsers() const {
llvm::errs() << "Non Consuming Users:\n";
for (auto *use : nonConsumingUses) {
llvm::errs() << *use->getUser();
}
llvm::errs() << "\n";
}
};
} // end anonymous namespace
//===----------------------------------------------------------------------===//
// Non Consuming Use Initialization
//===----------------------------------------------------------------------===//
void State::initializeAllNonConsumingUses(
ArrayRef<Operand *> nonConsumingUsers) {
SWIFT_DEFER {
// Once we have finished initializing blocksWithNonConsumingUses, we need to
// freeze it. By using a defer here, we can make sure we don't forget to do
// this below.
blocksWithNonConsumingUses.setFrozen();
};
for (Operand *use : nonConsumingUsers) {
auto *userBlock = use->getUser()->getParent();
// Make sure that this non consuming user is in either not in our definition
// block or is strictly after our defining instruction. If so, stash the use
// and continue.
if (userBlock != getBeginBlock() ||
std::find_if(beginInst->getIterator(), userBlock->end(),
[&use](const SILInstruction &inst) -> bool {
return use->getUser() == &inst;
}) != userBlock->end()) {
blocksWithNonConsumingUses.insert(userBlock, use);
continue;
}
if (nonConsumingUseOutsideLifetimeCallback) {
(*nonConsumingUseOutsideLifetimeCallback)(use);
}
// Otherwise, we emit an error since we found a use before our def. We do
// not bail early here since we want to gather up /all/ that we find.
errorBuilder.handleUseOutsideOfLifetime([&] {
llvm::errs() << "Found use outside of lifetime?!\n"
<< "Value: ";
if (auto v = value) {
llvm::errs() << *v;
} else {
llvm::errs() << "N/A. \n";
}
llvm::errs() << "User: " << use->getUser();
});
}
}
//===----------------------------------------------------------------------===//
// Consuming Use Initialization
//===----------------------------------------------------------------------===//
void State::initializeAllExtendLifetimeUses(
ArrayRef<Operand *> extendLifetimeUses,
SmallVectorImpl<std::pair<Operand *, SILBasicBlock *>>
&predsToAddToWorklist) {
// TODO: Efficiency.
for (auto *use : extendLifetimeUses) {
auto *user = use->getUser();
auto *block = user->getParent();
auto iter = blocksWithNonConsumingUses.find(block);
if (!iter.has_value()) {
continue;
}
// Remove the block from `blocksWithNonConsumingUses` if all non-consuming
// uses within the block occur before `user`.
bool allBefore = true;
for (auto *nonConsumingUse : *iter) {
auto *nonConsumingUser = nonConsumingUse->getUser();
assert(nonConsumingUser != user);
bool sawNonConsumingUser = false;
for (auto *inst = user->getNextInstruction(); inst;
inst = inst->getNextInstruction()) {
if (inst == nonConsumingUser) {
sawNonConsumingUser = true;
break;
}
}
if (sawNonConsumingUser) {
allBefore = false;
break;
}
}
if (allBefore) {
blocksWithNonConsumingUses.erase(block);
}
// Add relevant predecessors to the worklist.
if (block == getBeginBlock())
continue;
for (auto *predecessor : block->getPredecessorBlocks()) {
predsToAddToWorklist.push_back({use, predecessor});
}
}
}
void State::initializeAllConsumingUses(
ArrayRef<Operand *> consumingUses,
SmallVectorImpl<std::pair<Operand *, SILBasicBlock *>>
&predsToAddToWorklist) {
for (Operand *use : consumingUses) {
SILBasicBlock *userBlock = use->getUser()->getParent();
// First initialize our state for the consuming user.
//
// If we find another consuming instruction associated with userBlock this
// will emit a checker error.
initializeConsumingUse(use, userBlock);
// Then check if the given block has a use after free and emit an error if
// we find one.
checkForSameBlockUseAfterFree(use, userBlock);
// If this user is in the same block as the value, do not visit
// predecessors. We must be extra tolerant here since we allow for
// unreachable code.
if (userBlock == getBeginBlock())
continue;
// Then for each predecessor of this block...
for (auto *pred : userBlock->getPredecessorBlocks()) {
// If this block is not a block that we have already put on the list, add
// it to the worklist.
predsToAddToWorklist.push_back({use, pred});
}
}
}
void State::initializeConsumingUse(Operand *consumingUse,
SILBasicBlock *userBlock) {
// Map this user to the block. If we already have a value for the block, then
// we have a double consume and need to fail.
if (blocksWithConsumingUses.insert(userBlock))
return;
errorBuilder.handleOverConsume([&] {
llvm::errs() << "Found over consume?!\n";
if (auto v = value) {
llvm::errs() << "Value: " << *v;
} else {
llvm::errs() << "Value: N/A\n";
}
llvm::errs() << "User: " << *consumingUse->getUser() << "Block: bb"
<< userBlock->getDebugID() << "\n";
dumpConsumingUsers();
});
}
void State::checkForSameBlockUseAfterFree(Operand *consumingUse,
SILBasicBlock *userBlock) {
// If we do not have any consuming uses in the same block as our
// consuming user, then we can not have a same block use-after-free.
auto iter = blocksWithNonConsumingUses.find(userBlock);
if (!iter.has_value()) {
return;
}
auto nonConsumingUsesInBlock = *iter;
// Make sure that all of our non-consuming uses are before the consuming
// use. Otherwise, we have a use after free. Since we do not allow for cond_br
// anymore, we know that both of our users are non-cond branch users and thus
// must be instructions in the given block. Make sure that the non consuming
// user is strictly before the consuming user.
for (auto *nonConsumingUse : nonConsumingUsesInBlock) {
if (nonConsumingUse->getUser() != consumingUse->getUser()) {
if (std::find_if(consumingUse->getUser()->getIterator(),
userBlock->end(),
[&nonConsumingUse](const SILInstruction &i) -> bool {
return nonConsumingUse->getUser() == &i;
}) == userBlock->end()) {
continue;
}
} else if (isParallelOperand(nonConsumingUse)) {
continue;
}
if (nonConsumingUseOutsideLifetimeCallback) {
(*nonConsumingUseOutsideLifetimeCallback)(nonConsumingUse);
}
// NOTE: We do not exit here since we want to catch /all/ errors that we can
// find.
errorBuilder.handleUseOutsideOfLifetime([&] {
llvm::errs() << "Found outside of lifetime use?!\n"
<< "Value: ";
if (auto v = value) {
llvm::errs() << *v;
} else {
llvm::errs() << "N/A. \n";
}
llvm::errs() << "Consuming User: " << *consumingUse->getUser()
<< "Non Consuming User: " << *nonConsumingUse->getUser()
<< "Block: bb" << userBlock->getDebugID() << "\n\n";
});
}
// Erase the use since we know that it is either properly joint post-dominated
// or it was not and we emitted use after free errors.
blocksWithNonConsumingUses.erase(userBlock);
}
void State::checkPredsForDoubleConsume(Operand *consumingUse,
SILBasicBlock *userBlock) {
if (!blocksWithConsumingUses.contains(userBlock))
return;
// Check if this is a block that we have already visited. This means that we
// had a back edge of some sort. Double check that we haven't missed any
// successors.
if (visitedBlocks.contains(userBlock)) {
for (auto *succ : userBlock->getSuccessorBlocks()) {
if (!visitedBlocks.contains(succ)) {
successorBlocksThatMustBeVisited.insert(succ);
}
}
}
errorBuilder.handleOverConsume([&] {
llvm::errs() << "Found over consume?!\n"
<< "Value: ";
if (auto v = value) {
llvm::errs() << *v;
} else {
llvm::errs() << "N/A. \n";
}
llvm::errs() << "User: " << *consumingUse->getUser() << "Block: bb"
<< userBlock->getDebugID() << "\n";
dumpConsumingUsers();
});
}
void State::checkPredsForDoubleConsume(SILBasicBlock *userBlock) {
if (!blocksWithConsumingUses.contains(userBlock))
return;
// Check if this is a block that we have already visited. This means that we
// had a back edge of some sort. Double check that we haven't missed any
// successors.
if (visitedBlocks.contains(userBlock)) {
for (auto *succ : userBlock->getSuccessorBlocks()) {
if (!visitedBlocks.contains(succ)) {
successorBlocksThatMustBeVisited.insert(succ);
}
}
}
errorBuilder.handleOverConsume([&] {
llvm::errs() << "Found over consume?!\n"
<< "Value: ";
if (auto v = value) {
llvm::errs() << *v;
} else {
llvm::errs() << "N/A. \n";
}
llvm::errs() << "Block: bb" << userBlock->getDebugID() << "\n";
dumpConsumingUsers();
});
}
//===----------------------------------------------------------------------===//
// Dataflow
//===----------------------------------------------------------------------===//
void State::performDataflow(DeadEndBlocks *deBlocks) {
LLVM_DEBUG(llvm::dbgs() << " Beginning to check dataflow constraints\n");
// Until the worklist is empty...
while (!worklist.empty()) {
// Grab the next block to visit.
SILBasicBlock *block = worklist.pop_back_val();
LLVM_DEBUG(llvm::dbgs() << " Visiting Block: bb" << block->getDebugID()
<< '\n');
// Since the block is on our worklist, we know already that it is not a
// block with lifetime ending uses, due to the invariants of our loop.
// First remove BB from the SuccessorBlocksThatMustBeVisited list. This
// ensures that when the algorithm terminates, we know that BB was not the
// beginning of a non-covered path to the exit.
successorBlocksThatMustBeVisited.remove(block);
// Then remove BB from BlocksWithNonLifetimeEndingUses so we know that
// this block was properly joint post-dominated by our lifetime ending
// users.
blocksWithNonConsumingUses.erase(block);
// Ok, now we know that we do not have an overconsume. If this block does
// not end in a no return function, we need to update our state for our
// successors to make sure by the end of the traversal we visit them.
//
// We must consider such no-return blocks since we may be running during
// SILGen before NoReturn folding has run.
for (auto *succBlock : block->getSuccessorBlocks()) {
// If we already visited the successor, there is nothing to do since we
// already visited the successor.
if (visitedBlocks.contains(succBlock))
continue;
// Then check if the successor is a transitively unreachable block. In
// such a case, we ignore it since we are going to leak along that path.
if (deBlocks && deBlocks->isDeadEnd(succBlock))
continue;
// Otherwise, add the successor to our SuccessorBlocksThatMustBeVisited
// set to ensure that we assert if we do not visit it by the end of the
// algorithm.
successorBlocksThatMustBeVisited.insert(succBlock);
}
// If we are at the dominating block of our walk, continue. There is nothing
// further to do since we do not want to visit the predecessors of our
// dominating block. On the other hand, we do want to add its successors to
// the successorBlocksThatMustBeVisited set.
if (block == getBeginBlock())
continue;
// Then for each predecessor of this block:
//
// 1. If we have visited the predecessor already, then it is not a block
// with lifetime ending uses. If it is a block with uses, then we have a
// double release... so assert. If not, we continue.
//
// 2. We add the predecessor to the worklist if we have not visited it yet.
for (auto *predBlock : block->getPredecessorBlocks()) {
// Check if we have an over consume.
checkPredsForDoubleConsume(predBlock);
if (visitedBlocks.contains(predBlock)) {
continue;
}
visitedBlocks.insert(predBlock);
worklist.push_back(predBlock);
}
}
}
void State::checkDataflowEndState(DeadEndBlocks *deBlocks) {
if (!successorBlocksThatMustBeVisited.empty()) {
// If we are asked to store any leaking blocks, put them in the leaking
// blocks array.
if (leakingBlockCallback) {
for (auto *block : successorBlocksThatMustBeVisited) {
(*leakingBlockCallback)(block);
}
}
// If we are supposed to error on leaks, do so now.
errorBuilder.handleLeak([&] {
llvm::errs() << "Error! Found a leak due to a consuming post-dominance "
"failure!\n";
if (auto v = value) {
llvm::errs() << "Value: " << *value;
} else {
llvm::errs() << "Value: N/A\n";
}
llvm::errs() << "Post Dominating Failure Blocks:\n";
for (auto *succBlock : successorBlocksThatMustBeVisited) {
llvm::errs() << " bb" << succBlock->getDebugID();
}
llvm::errs() << '\n';
});
// Otherwise... see if we have any other failures. This signals the user
// wants us to tell it where to insert compensating destroys.
}
// If we do have remaining blocks, then these non lifetime ending uses must be
// outside of our "alive" blocks implying an outside of lifetime use. It could
// be a use-before-def or a use-after-free.
for (auto pair : blocksWithNonConsumingUses.getRange()) {
auto *block = pair.first;
if (deBlocks && deBlocks->isDeadEnd(block)) {
continue;
}
auto useList = pair.second;
for (auto *use : useList) {
if (nonConsumingUseOutsideLifetimeCallback) {
(*nonConsumingUseOutsideLifetimeCallback)(use);
}
errorBuilder.handleUseOutsideOfLifetime([&] {
llvm::errs() << "Found outside of lifetime use!\n"
<< "Value: ";
if (auto v = value) {
llvm::errs() << *v;
} else {
llvm::errs() << "N/A. \n";
}
llvm::errs() << "User:" << *use->getUser() << "Block: bb"
<< block->getDebugID() << "\n";
});
}
}
}
//===----------------------------------------------------------------------===//
// Top Level Entrypoints
//===----------------------------------------------------------------------===//
LinearLifetimeChecker::Error LinearLifetimeChecker::checkValueImpl(
SILValue value, ArrayRef<Operand *> consumingUses,
ArrayRef<Operand *> regularUses, ErrorBuilder &errorBuilder,
std::optional<function_ref<void(SILBasicBlock *)>> leakingBlockCallback,
std::optional<function_ref<void(Operand *)>>
nonConsumingUseOutsideLifetimeCallback) {
// FIXME: rdar://71240363. This assert does not make sense because
// consumingUses in some cases only contains the destroying uses. Owned values
// may not be destroyed because they may be converted to
// ValueOwnershipKind::None on all paths reaching a return. Instead, this
// utility needs to find liveness first considering all uses (or at least all
// uses that may be on a lifetime boundary). We probably then won't need this
// assert, but I'm leaving the FIXME as a placeholder for that work.
//
// assert((!consumingUses.empty()
// || deadEndBlocks.isDeadEnd(value->getParentBlock())) &&
// "Must have at least one consuming user?!");
SmallVector<Operand *, 32> nonConsumingUses;
SmallVector<Operand *, 32> extendLifetimeUses;
for (auto *use : regularUses) {
if (isa<ExtendLifetimeInst>(use->getUser())) {
extendLifetimeUses.push_back(use);
} else {
nonConsumingUses.push_back(use);
}
}
State state(value, errorBuilder, leakingBlockCallback,
nonConsumingUseOutsideLifetimeCallback, consumingUses,
extendLifetimeUses, nonConsumingUses);
// First add our non-consuming uses and their blocks to the
// blocksWithNonConsumingUses map. While we do this, if we have multiple uses
// in the same block, we only accept the last use since from a liveness
// perspective that is all we care about.
state.initializeAllNonConsumingUses(nonConsumingUses);
// Then, we go through each one of our consuming users performing the
// following operation:
//
// 1. Verifying that no two consuming users are in the same block. This
// is accomplished by adding the user blocks to the blocksWithConsumingUsers
// list. This avoids double consumes.
//
// 2. Verifying that no predecessor is a block with a consuming use. The
// reason why this is necessary is because we wish to not add elements to the
// worklist twice. Thus we want to check if we have already visited a
// predecessor.
SmallVector<std::pair<Operand *, SILBasicBlock *>, 32> predsToAddToWorklist;
state.initializeAllConsumingUses(consumingUses, predsToAddToWorklist);
state.initializeAllExtendLifetimeUses(extendLifetimeUses,
predsToAddToWorklist);
// If we have a singular consuming use and it is in the same block as value's
// def, we bail early. Any use-after-frees due to non-consuming uses would
// have been detected by initializing our consuming uses. So we are done.
if (consumingUses.size() == 1 &&
consumingUses[0]->getUser()->getParent() == value->getParentBlock()) {
// Check if any of our non consuming uses are not in the parent block and
// are reachable. We flag those as additional use after frees. Any in the
// same block, we would have flagged.
for (auto *use : nonConsumingUses) {
auto *useParent = use->getUser()->getParent();
if (useParent == value->getParentBlock() ||
(deadEndBlocks && deadEndBlocks->isDeadEnd(useParent))) {
continue;
}
if (nonConsumingUseOutsideLifetimeCallback) {
(*nonConsumingUseOutsideLifetimeCallback)(use);
}
state.errorBuilder.handleUseOutsideOfLifetime([&] {
llvm::errs() << "Function: '" << value->getFunction()->getName()
<< "'\n"
<< "Found non consuming use outside of the lifetime being "
"verified.\n"
<< "Value: " << *value << "User: " << *use->getUser();
});
}
return std::move(state.errorBuilder).consumeAndGetFinalError();
}
// Ok, we may have multiple consuming uses. Add the user block of each of our
// consuming users to the visited list since we do not want them to be added
// to the successors to visit set.
for (const auto &i : consumingUses) {
state.visitedBlocks.insert(i->getUser()->getParent());
}
for (const auto &i : extendLifetimeUses) {
state.visitedBlocks.insert(i->getUser()->getParent());
}
// Now that we have marked all of our producing blocks, we go through our
// predsToAddToWorklist list and add our preds, making sure that none of these
// preds are in blocksWithConsumingUses. This is important so that we do not
// need to re-process.
for (auto pair : predsToAddToWorklist) {
Operand *use = pair.first;
SILBasicBlock *predBlock = pair.second;
// Make sure that the predecessor is not in our blocksWithConsumingUses
// list.
state.checkPredsForDoubleConsume(use, predBlock);
if (!state.visitedBlocks.insert(predBlock))
continue;
state.worklist.push_back(predBlock);
}
// Now that our algorithm is completely prepared, run the
// dataflow... If we find a failure, return false.
state.performDataflow(deadEndBlocks);
// ...and then check that the end state shows that we have a valid linear
// typed value.
state.checkDataflowEndState(deadEndBlocks);
return std::move(state.errorBuilder).consumeAndGetFinalError();
}
LinearLifetimeChecker::Error LinearLifetimeChecker::checkValue(
SILValue value, ArrayRef<Operand *> consumingUses,
ArrayRef<Operand *> nonConsumingUses, ErrorBuilder &errorBuilder) {
return checkValueImpl(value, consumingUses, nonConsumingUses, errorBuilder,
std::nullopt, std::nullopt);
}
LinearLifetimeChecker::Error LinearLifetimeChecker::checkValue(
SILValue value, ArrayRef<Operand *> consumingUses,
ArrayRef<Operand *> nonConsumingUses, ErrorBuilder &errorBuilder,
function_ref<void(SILBasicBlock *)> leakingBlocksCallback) {
return checkValueImpl(value, consumingUses, nonConsumingUses, errorBuilder,
leakingBlocksCallback, std::nullopt);
}
bool LinearLifetimeChecker::completeConsumingUseSet(
SILValue value, Operand *consumingUse,
function_ref<void(SILBasicBlock::iterator)> visitor) {
ErrorBuilder errorBuilder(*value->getFunction(),
ErrorBehaviorKind::ReturnFalse);
auto error =
checkValue(value, {consumingUse}, {}, errorBuilder,
[&](SILBasicBlock *block) { return visitor(block->begin()); });
if (!error.getFoundError()) {
return false;
}
// Return true if we found an over consume (meaning our use is in a loop).
return error.getFoundOverConsume();
}
bool LinearLifetimeChecker::validateLifetime(
SILValue value, ArrayRef<Operand *> consumingUses,
ArrayRef<Operand *> nonConsumingUses) {
ErrorBuilder errorBuilder(*value->getFunction(),
ErrorBehaviorKind::ReturnFalse);
return !checkValue(value, consumingUses, nonConsumingUses, errorBuilder)
.getFoundError();
}
bool LinearLifetimeChecker::usesNotContainedWithinLifetime(
SILValue value, ArrayRef<Operand *> consumingUses,
ArrayRef<Operand *> usesToTest) {
auto errorBehavior = ErrorBehaviorKind(
ErrorBehaviorKind::ReturnFalse |
ErrorBehaviorKind::StoreNonConsumingUsesOutsideLifetime);
ErrorBuilder errorBuilder(*value->getFunction(), errorBehavior);
using OptType = std::optional<function_ref<void(Operand *)>>;
#ifndef NDEBUG
SmallVector<Operand *, 32> uniqueUsers;
#endif
unsigned numFoundUses = 0;
auto error = checkValueImpl(value, consumingUses, usesToTest, errorBuilder,
std::nullopt, OptType([&](Operand *use) {
#ifndef NDEBUG
uniqueUsers.push_back(use);
#endif
++numFoundUses;
}));
#ifndef NDEBUG
// Make sure in assert builds that we did not double count any operands.
sortUnique(uniqueUsers);
assert(numFoundUses == uniqueUsers.size());
#endif
// If we found any error except for uses outside of our lifetime, bail.
if (error.getFoundLeak() || error.getFoundOverConsume() ||
error.getFoundUseAfterFree())
return false;
// Return true if we /did/ find an error and when emitting that error, we
// found /all/ uses we were looking for.
return error.getFoundUseOutsideOfLifetime() &&
numFoundUses == usesToTest.size();
}