-
Notifications
You must be signed in to change notification settings - Fork 10.4k
/
Copy pathDefiniteInitialization.cpp
3923 lines (3353 loc) · 146 KB
/
DefiniteInitialization.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
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
//===--- DefiniteInitialization.cpp - Perform definite init analysis ------===//
//
// This source file is part of the Swift.org open source project
//
// Copyright (c) 2014 - 2017 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
//
//===----------------------------------------------------------------------===//
#define DEBUG_TYPE "definite-init"
#include "DIMemoryUseCollector.h"
#include "swift/AST/DiagnosticEngine.h"
#include "swift/AST/DiagnosticsSIL.h"
#include "swift/AST/DistributedDecl.h"
#include "swift/AST/Expr.h"
#include "swift/AST/Stmt.h"
#include "swift/Basic/Assertions.h"
#include "swift/ClangImporter/ClangModule.h"
#include "swift/SIL/BasicBlockBits.h"
#include "swift/AST/SemanticAttrs.h"
#include "swift/SIL/BasicBlockData.h"
#include "swift/SIL/InstructionUtils.h"
#include "swift/SIL/MemAccessUtils.h"
#include "swift/SIL/SILArgument.h"
#include "swift/SIL/SILBuilder.h"
#include "swift/SIL/SILValue.h"
#include "swift/SILOptimizer/PassManager/Passes.h"
#include "swift/SILOptimizer/PassManager/Transforms.h"
#include "swift/SILOptimizer/Utils/CFGOptUtils.h"
#include "swift/SILOptimizer/Utils/DistributedActor.h"
#include "swift/SILOptimizer/Utils/InstOptUtils.h"
#include "llvm/ADT/STLExtras.h"
#include "llvm/ADT/SmallBitVector.h"
#include "llvm/ADT/Statistic.h"
#include "llvm/ADT/StringExtras.h"
#include "llvm/Support/CommandLine.h"
#include "llvm/Support/Compiler.h"
#include "llvm/Support/Debug.h"
using namespace swift;
using namespace ownership;
llvm::cl::opt<bool> TriggerUnreachableOnFailure(
"sil-di-assert-on-failure", llvm::cl::init(false),
llvm::cl::desc("After emitting a DI error, assert instead of continuing. "
"Meant for debugging ONLY!"),
llvm::cl::Hidden);
template<typename ...ArgTypes>
static InFlightDiagnostic diagnose(SILModule &M, SILLocation loc,
ArgTypes... args) {
auto diag = M.getASTContext().Diags.diagnose(loc.getSourceLoc(),
Diagnostic(args...));
if (TriggerUnreachableOnFailure)
llvm_unreachable("Triggering standard assertion failure routine");
return diag;
}
/// Insert a CFG diamond at the position specified by the SILBuilder, with a
/// conditional branch based on "Cond".
///
/// This returns the true, false, and continuation block. The SILBuilder is left
/// at the start of the ContBB block.
static void InsertCFGDiamond(SILValue Cond, SILLocation Loc, SILBuilder &B,
SILBasicBlock *&TrueBB,
SILBasicBlock *&FalseBB,
SILBasicBlock *&ContBB) {
SILBasicBlock *StartBB = B.getInsertionBB();
// Start by splitting the current block.
ContBB = StartBB->split(B.getInsertionPoint());
TrueBB = StartBB->getParent()->createBasicBlock();
TrueBB->getParent()->moveBlockBefore(TrueBB, ContBB->getIterator());
B.setInsertionPoint(TrueBB);
B.createBranch(Loc, ContBB);
FalseBB = StartBB->getParent()->createBasicBlock();
FalseBB->getParent()->moveBlockBefore(FalseBB, ContBB->getIterator());
B.setInsertionPoint(FalseBB);
B.createBranch(Loc, ContBB);
// Now that we have our destinations, insert a conditional branch on the
// condition.
B.setInsertionPoint(StartBB);
B.createCondBranch(Loc, Cond, TrueBB, FalseBB);
B.setInsertionPoint(ContBB, ContBB->begin());
}
//===----------------------------------------------------------------------===//
// Per-Element Promotion Logic
//===----------------------------------------------------------------------===//
namespace {
enum class DIKind : uint8_t { No, Yes, Partial };
} // end anonymous namespace
/// This implements the lattice merge operation for 2 optional DIKinds.
static std::optional<DIKind> mergeKinds(std::optional<DIKind> OK1,
std::optional<DIKind> OK2) {
// If OK1 is unset, ignore it.
if (!OK1.has_value())
return OK2;
DIKind K1 = OK1.value();
// If "this" is already partial, we won't learn anything.
if (K1 == DIKind::Partial)
return K1;
// If OK2 is unset, take K1.
if (!OK2.has_value())
return K1;
DIKind K2 = OK2.value();
// If "K1" is yes, or no, then switch to partial if we find a different
// answer.
if (K1 != K2)
return DIKind::Partial;
// Otherwise, we're still consistently Yes or No.
return K1;
}
namespace {
/// AvailabilitySet - This class stores an array of lattice values for tuple
/// elements being analyzed for liveness computations. Each element is
/// represented with two bits in a bitvector, allowing this to represent the
/// lattice values corresponding to "Unknown" (bottom), "Live" or "Not Live",
/// which are the middle elements of the lattice, and "Partial" which is the
/// top element.
class AvailabilitySet {
// We store two bits per element, encoded in the following form:
// T,T -> Nothing/Unknown
// F,F -> No
// F,T -> Yes
// T,F -> Partial
SmallBitVector Data;
public:
AvailabilitySet() {}
AvailabilitySet(unsigned NumElts) { init(NumElts); }
void init(unsigned NumElts) {
Data.set();
Data.resize(NumElts*2, true);
}
bool empty() const { return Data.empty(); }
unsigned size() const { return Data.size()/2; }
DIKind get(unsigned Elt) const {
return getConditional(Elt).value();
}
std::optional<DIKind> getConditional(unsigned Elt) const {
bool V1 = Data[Elt*2], V2 = Data[Elt*2+1];
if (V1 == V2)
return V1 ? std::optional<DIKind>(std::nullopt) : DIKind::No;
return V2 ? DIKind::Yes : DIKind::Partial;
}
void set(unsigned Elt, DIKind K) {
switch (K) {
case DIKind::No: Data[Elt*2] = false; Data[Elt*2+1] = false; break;
case DIKind::Yes: Data[Elt*2] = false, Data[Elt*2+1] = true; break;
case DIKind::Partial: Data[Elt*2] = true, Data[Elt*2+1] = false; break;
}
}
void set(unsigned Elt, std::optional<DIKind> K) {
if (!K.has_value())
Data[Elt*2] = true, Data[Elt*2+1] = true;
else
set(Elt, K.value());
}
/// containsUnknownElements - Return true if there are any elements that are
/// unknown.
bool containsUnknownElements() const {
// Check that we didn't get any unknown values.
for (unsigned i = 0, e = size(); i != e; ++i)
if (!getConditional(i).has_value())
return true;
return false;
}
bool isAll(DIKind K) const {
for (unsigned i = 0, e = size(); i != e; ++i) {
auto Elt = getConditional(i);
if (!Elt.has_value() || Elt.value() != K)
return false;
}
return true;
}
bool hasAny(DIKind K) const {
for (unsigned i = 0, e = size(); i != e; ++i) {
auto Elt = getConditional(i);
if (Elt.has_value() && Elt.value() == K)
return true;
}
return false;
}
bool isAllYes() const { return isAll(DIKind::Yes); }
bool isAllNo() const { return isAll(DIKind::No); }
/// changeUnsetElementsTo - If any elements of this availability set are not
/// known yet, switch them to the specified value.
void changeUnsetElementsTo(DIKind K) {
for (unsigned i = 0, e = size(); i != e; ++i)
if (!getConditional(i).has_value())
set(i, K);
}
void mergeIn(const AvailabilitySet &RHS) {
// Logically, this is an elementwise "this = merge(this, RHS)" operation,
// using the lattice merge operation for each element.
for (unsigned i = 0, e = size(); i != e; ++i)
set(i, mergeKinds(getConditional(i), RHS.getConditional(i)));
}
void dump(llvm::raw_ostream &OS) const {
OS << '(';
for (unsigned i = 0, e = size(); i != e; ++i) {
if (std::optional<DIKind> Elt = getConditional(i)) {
switch (Elt.value()) {
case DIKind::No: OS << 'n'; break;
case DIKind::Yes: OS << 'y'; break;
case DIKind::Partial: OS << 'p'; break;
}
} else {
OS << '.';
}
}
OS << ')';
}
};
LLVM_ATTRIBUTE_USED
inline llvm::raw_ostream &operator<<(llvm::raw_ostream &OS,
const AvailabilitySet &AS) {
AS.dump(OS);
return OS;
}
} // end anonymous namespace
namespace {
/// LiveOutBlockState - Keep track of information about blocks that have
/// already been analyzed. Since this is a global analysis, we need this to
/// cache information about different paths through the CFG.
struct LiveOutBlockState {
/// Keep track of whether there is a Store, InOutUse, or Escape locally in
/// this block.
bool HasNonLoadUse : 1;
/// Helper flag used during building the worklist for the dataflow analysis.
bool isInWorkList : 1;
/// Availability of elements within the block.
/// Not "empty" for all blocks which have non-load uses or contain the
/// definition of the memory object.
AvailabilitySet LocalAvailability;
/// The live out information of the block. This is the LocalAvailability
/// plus the information merged-in from the predecessor blocks.
AvailabilitySet OutAvailability;
/// Keep track of blocks where the contents of the self box are stored to
/// as a result of a successful self.init or super.init call.
std::optional<DIKind> LocalSelfInitialized;
/// The live out information of the block. This is the LocalSelfInitialized
/// plus the information merged-in from the predecessor blocks.
std::optional<DIKind> OutSelfInitialized;
LiveOutBlockState() { init(0); }
void init(unsigned NumElements) {
HasNonLoadUse = false;
isInWorkList = false;
LocalAvailability.init(NumElements);
OutAvailability.init(NumElements);
LocalSelfInitialized = std::nullopt;
OutSelfInitialized = std::nullopt;
}
/// Sets all unknown elements to not-available.
void setUnknownToNotAvailable() {
LocalAvailability.changeUnsetElementsTo(DIKind::No);
OutAvailability.changeUnsetElementsTo(DIKind::No);
if (!LocalSelfInitialized.has_value())
LocalSelfInitialized = DIKind::No;
if (!OutSelfInitialized.has_value())
OutSelfInitialized = DIKind::No;
}
/// Transfer function for dataflow analysis.
///
/// \param pred Value from a predecessor block
/// \param out Current live-out
/// \param local Value from current block, overrides predecessor
/// \param result Out parameter
///
/// \return True if the result was different from the live-out
bool transferAvailability(const std::optional<DIKind> pred,
const std::optional<DIKind> out,
const std::optional<DIKind> local,
std::optional<DIKind> &result) {
if (local.has_value()) {
// A local availability overrides the incoming value.
result = local;
} else {
result = mergeKinds(out, pred);
}
if (result.has_value() &&
(!out.has_value() || result.value() != out.value())) {
return true;
}
return false;
}
/// Merge the state from a predecessor block into the OutAvailability.
/// Returns true if the live out set changed.
bool mergeFromPred(const LiveOutBlockState &Pred) {
bool changed = false;
for (unsigned i = 0, e = OutAvailability.size(); i != e; ++i) {
std::optional<DIKind> result;
if (transferAvailability(Pred.OutAvailability.getConditional(i),
OutAvailability.getConditional(i),
LocalAvailability.getConditional(i),
result)) {
changed = true;
OutAvailability.set(i, result);
}
}
std::optional<DIKind> result;
if (transferAvailability(Pred.OutSelfInitialized,
OutSelfInitialized,
LocalSelfInitialized,
result)) {
changed = true;
OutSelfInitialized = result;
}
return changed;
}
/// Sets the elements of a use to available.
void markAvailable(const DIMemoryUse &Use) {
// If the memory object has nothing in it (e.g., is an empty tuple)
// ignore.
if (LocalAvailability.empty()) return;
for (unsigned i = 0; i != Use.NumElements; ++i) {
LocalAvailability.set(Use.FirstElement+i, DIKind::Yes);
OutAvailability.set(Use.FirstElement+i, DIKind::Yes);
}
}
/// Mark the block as storing to self, indicating the self box has been
/// initialized.
void markStoreToSelf() {
LocalSelfInitialized = DIKind::Yes;
OutSelfInitialized = DIKind::Yes;
}
/// If true, we're not done with our dataflow analysis yet.
bool containsUndefinedValues() {
return (!OutSelfInitialized.has_value() ||
OutAvailability.containsUnknownElements());
}
};
struct ConditionalDestroy {
unsigned ReleaseID;
AvailabilitySet Availability;
DIKind SelfInitialized;
};
using BlockStates = BasicBlockData<LiveOutBlockState>;
/// LifetimeChecker - This is the main heavy lifting for definite
/// initialization checking of a memory object.
class LifetimeChecker {
SILFunction &F;
SILModule &Module;
/// TheMemory - This holds information about the memory object being
/// analyzed.
DIMemoryObjectInfo TheMemory;
SmallVectorImpl<DIMemoryUse> &Uses;
TinyPtrVector<SILInstruction *> &StoresToSelf;
SmallVectorImpl<SILInstruction *> &Destroys;
SmallVector<unsigned, 8> NeedsUpdateForInitState;
std::vector<ConditionalDestroy> ConditionalDestroys;
BlockStates &blockStates;
BasicBlockFlag blockStateInitialized;
/// This is a map of uses that are not loads (i.e., they are Stores,
/// InOutUses, and Escapes), to their entry in Uses.
llvm::SmallDenseMap<SILInstruction*, SmallVector<unsigned, 1>, 16> NonLoadUses;
/// This is true when there is an ambiguous store, which may be an init or
/// assign, depending on the CFG path.
bool HasConditionalInitAssign = false;
/// This is true when there is an ambiguous destroy, which may be a release
/// of a fully-initialized or a partially-initialized value.
bool HasConditionalDestroy = false;
/// This is true when there is a destroy on a path where the self value may
/// have been consumed, in which case there is nothing to do.
bool HasConditionalSelfInitialized = false;
/// This is true when the object being checked is a 'self' parameter for a
/// struct in a non-delegating cross-module initializer. In this case, the
/// initializer is not allowed to be fieldwise in Swift 5, so we produce a
/// warning in Swift 4 and earlier.
bool WantsCrossModuleStructInitializerDiagnostic = false;
/// This is true if any diagnostics have offered a fix-it to insert
/// `self.init()`. While the first diagnostic to offer this may not be
/// suggesting it in the best place, offering it more than once is clearly
/// wrong.
bool HasSuggestedNoArgSelfInit = false;
// Keep track of whether we've emitted an error. We only emit one error per
// location as a policy decision.
std::vector<SILLocation> EmittedErrorLocs;
SmallPtrSet<const SILBasicBlock *, 16> BlocksReachableFromEntry;
public:
LifetimeChecker(const DIMemoryObjectInfo &TheMemory,
DIElementUseInfo &UseInfo,
BlockStates &blockStates);
void doIt();
private:
/// Find all the points where \c TheMemory has been fully initialized
/// by a store to its element. If there are no elements then
/// initialization point is located right after the mark_uninitialized
/// instruction.
void
findFullInitializationPoints(SmallVectorImpl<SILInstruction *> &points);
/// Injects `hop_to_executor` instructions into the function after
/// `self` becomes fully initialized, only if the current function
/// is an actor initializer that requires this, and if TheMemory
/// corresponds to `self`.
void injectActorHops();
void emitSelfConsumedDiagnostic(SILInstruction *Inst);
LiveOutBlockState &getBlockInfo(SILBasicBlock *BB) {
auto &state = blockStates.get(BB, []() { return LiveOutBlockState(); });
if (!blockStateInitialized.testAndSet(BB))
state.init(TheMemory.getNumElements());
return state;
}
AvailabilitySet getLivenessAtInst(SILInstruction *Inst, unsigned FirstElt,
unsigned NumElts);
AvailabilitySet getLivenessAtNonTupleInst(SILInstruction *Inst,
SILBasicBlock *InstBB,
AvailabilitySet &CurrentSet);
int getAnyUninitializedMemberAtInst(SILInstruction *Inst, unsigned FirstElt,
unsigned NumElts);
DIKind getSelfInitializedAtInst(SILInstruction *Inst);
bool isInitializedAtUse(const DIMemoryUse &Use,
bool *SuperInitDone = nullptr,
bool *FailedSelfUse = nullptr,
bool *FullyUninitialized = nullptr);
void handleStoreUse(unsigned UseID);
void handleLoadUse(const DIMemoryUse &Use);
void handleLoadForTypeOfSelfUse(DIMemoryUse &Use);
void handleTypeOfSelfUse(DIMemoryUse &Use);
void handleInOutUse(const DIMemoryUse &Use);
void handleEscapeUse(const DIMemoryUse &Use);
void handleFlowSensitiveActorIsolationUse(const DIMemoryUse &Use);
bool diagnoseReturnWithoutInitializingStoredProperties(
const SILInstruction *Inst, SILLocation loc, const DIMemoryUse &Use);
void handleLoadUseFailure(const DIMemoryUse &Use,
bool SuperInitDone,
bool FailedSelfUse);
void handleSelfInitUse(unsigned UseID);
void updateInstructionForInitState(unsigned UseID);
void processUninitializedRelease(SILInstruction *Release,
bool consumed,
SILBasicBlock::iterator InsertPt);
/// Process a mark_uninitialized of an alloc_box that is uninitialized and
/// needs a dealloc_box.
void processUninitializedReleaseOfBox(MarkUninitializedInst *MUI,
SILInstruction *Release,
bool consumed,
SILBasicBlock::iterator InsertPt);
void deleteDeadRelease(unsigned ReleaseID);
void processNonTrivialRelease(unsigned ReleaseID);
SILValue handleConditionalInitAssign();
void handleConditionalDestroys(SILValue ControlVariableAddr);
typedef SmallVector<SILBasicBlock *, 16> WorkListType;
void putIntoWorkList(SILBasicBlock *BB, WorkListType &WorkList);
void computePredsLiveOut(SILBasicBlock *BB);
void getOutAvailability(SILBasicBlock *BB, AvailabilitySet &Result);
void getOutSelfInitialized(SILBasicBlock *BB,
std::optional<DIKind> &Result);
bool shouldEmitError(const SILInstruction *Inst);
std::string getUninitElementName(const DIMemoryUse &Use);
void noteUninitializedMembers(const DIMemoryUse &Use);
void diagnoseInitError(const DIMemoryUse &Use,
Diag<StringRef, bool> DiagMessage);
void diagnoseRefElementAddr(RefElementAddrInst *REI);
bool diagnoseMethodCall(const DIMemoryUse &Use,
bool SuperInitDone);
void diagnoseBadExplicitStore(SILInstruction *Inst);
bool isBlockIsReachableFromEntry(const SILBasicBlock *BB);
};
} // end anonymous namespace
LifetimeChecker::LifetimeChecker(const DIMemoryObjectInfo &TheMemory,
DIElementUseInfo &UseInfo,
BlockStates &blockStates)
: F(TheMemory.getFunction()), Module(TheMemory.getModule()),
TheMemory(TheMemory), Uses(UseInfo.Uses),
StoresToSelf(UseInfo.StoresToSelf), Destroys(UseInfo.Releases),
blockStates(blockStates), blockStateInitialized(&F) {
// The first step of processing an element is to collect information about the
// element into data structures we use later.
for (unsigned ui = 0, e = Uses.size(); ui != e; ++ui) {
auto &Use = Uses[ui];
assert(Use.Inst && "No instruction identified?");
// Keep track of all the uses that aren't loads or escapes. These are
// important uses that we'll visit, but we don't consider them definition
// points for liveness computation purposes.
switch (Use.Kind) {
case DIUseKind::Load:
case DIUseKind::LoadForTypeOfSelf:
case DIUseKind::TypeOfSelf:
case DIUseKind::Escape:
case DIUseKind::FlowSensitiveSelfIsolation:
continue;
case DIUseKind::Assign:
case DIUseKind::Set:
case DIUseKind::IndirectIn:
case DIUseKind::InitOrAssign:
case DIUseKind::InOutArgument:
case DIUseKind::Initialization:
case DIUseKind::InOutSelfArgument:
case DIUseKind::PartialStore:
case DIUseKind::SelfInit:
case DIUseKind::BadExplicitStore:
break;
}
NonLoadUses[Use.Inst].push_back(ui);
auto &BBInfo = getBlockInfo(Use.Inst->getParent());
BBInfo.HasNonLoadUse = true;
// Each of the non-load instructions will each be checked to make sure that
// they are live-in or a full element store. This means that the block they
// are in should be treated as a live out for cross-block analysis purposes.
BBInfo.markAvailable(Use);
}
// Mark blocks where the self box is initialized.
for (auto *I : StoresToSelf) {
// FIXME: critical edges?
auto *bb = I->getParent();
getBlockInfo(bb).markStoreToSelf();
}
// It isn't really a use, but we account for the mark_uninitialized or
// project_box as a use so we see it in our dataflow walks.
auto &MemBBInfo = getBlockInfo(TheMemory.getParentBlock());
MemBBInfo.HasNonLoadUse = true;
// There is no scanning required (or desired) for the block that defines the
// memory object itself. Its live-out properties are whatever are trivially
// locally inferred by the loop above. Mark any unset elements as not
// available.
MemBBInfo.setUnknownToNotAvailable();
// Finally, check if we need to emit compatibility diagnostics for cross-module
// non-delegating struct initializers.
if (TheMemory.isCrossModuleStructInitSelf())
WantsCrossModuleStructInitializerDiagnostic = true;
}
/// Determine whether the specified block is reachable from the entry of the
/// containing function's entrypoint. This allows us to avoid diagnosing DI
/// errors in synthesized code that turns out to be unreachable.
bool LifetimeChecker::isBlockIsReachableFromEntry(const SILBasicBlock *BB) {
// Lazily compute reachability, so we only have to do it in the case of an
// error.
if (BlocksReachableFromEntry.empty()) {
SmallVector<const SILBasicBlock*, 128> Worklist;
Worklist.push_back(&BB->getParent()->front());
BlocksReachableFromEntry.insert(Worklist.back());
// Collect all reachable blocks by walking the successors.
while (!Worklist.empty()) {
const SILBasicBlock *BB = Worklist.pop_back_val();
for (auto &Succ : BB->getSuccessors()) {
if (BlocksReachableFromEntry.insert(Succ).second)
Worklist.push_back(Succ);
}
}
}
return BlocksReachableFromEntry.count(BB);
}
/// shouldEmitError - Check to see if we've already emitted an error at the
/// specified instruction. If so, return false. If not, remember the
/// instruction and return true.
bool LifetimeChecker::shouldEmitError(const SILInstruction *Inst) {
// If this instruction is in a dead region, don't report the error. This can
// occur because we haven't run DCE before DI and this may be a synthesized
// statement. If it isn't synthesized, then DCE will report an error on the
// dead code.
if (!isBlockIsReachableFromEntry(Inst->getParent()))
return false;
// Check to see if we've already emitted an error at this location. If so,
// swallow the error.
SILLocation InstLoc = Inst->getLoc();
if (llvm::any_of(EmittedErrorLocs, [&](SILLocation L) -> bool {
return L.getSourceLoc() == InstLoc.getSourceLoc();
}))
return false;
// Ignore loads used only by an assign_by_wrapper or assign_or_init setter.
// This is safe to ignore because assign_by_wrapper/assign_or_init will
// only be re-written to use the setter if the value is fully initialized.
if (auto *load = dyn_cast<SingleValueInstruction>(Inst)) {
auto isOnlyUsedByPartialApply =
[&](const SingleValueInstruction *inst) -> PartialApplyInst * {
Operand *result = nullptr;
for (auto *op : inst->getUses()) {
auto *user = op->getUser();
// Ignore copies, destroys and borrows because they'd be
// erased together with the setter.
if (isa<DestroyValueInst>(user) || isa<CopyValueInst>(user) ||
isa<BeginBorrowInst>(user) || isa<EndBorrowInst>(user))
continue;
if (result)
return nullptr;
result = op;
}
return result ? dyn_cast<PartialApplyInst>(result->getUser()) : nullptr;
};
if (auto *PAI = isOnlyUsedByPartialApply(load)) {
if (std::find_if(PAI->use_begin(), PAI->use_end(), [](auto PAIUse) {
return isa<AssignByWrapperInst>(PAIUse->getUser()) ||
isa<AssignOrInitInst>(PAIUse->getUser());
}) != PAI->use_end()) {
return false;
}
}
}
EmittedErrorLocs.push_back(InstLoc);
return true;
}
/// Emit notes for each uninitialized stored property in a designated
/// initializer.
void LifetimeChecker::noteUninitializedMembers(const DIMemoryUse &Use) {
assert(TheMemory.isAnyInitSelf() && !TheMemory.isDelegatingInit() &&
"Not a designated initializer");
// Determine which members, specifically are uninitialized.
AvailabilitySet Liveness =
getLivenessAtInst(Use.Inst, Use.FirstElement, Use.NumElements);
SmallVector<std::function<void()>, 2> delayedNotes;
bool emittedNote = false;
for (unsigned i = Use.FirstElement, e = Use.FirstElement+Use.NumElements;
i != e; ++i) {
if (Liveness.get(i) == DIKind::Yes) continue;
// Ignore a failed super.init requirement.
if (i == TheMemory.getNumElements() - 1 && TheMemory.isDerivedClassSelf())
continue;
std::string Name;
auto *Decl = TheMemory.getPathStringToElement(i, Name);
SILLocation Loc = Use.Inst->getLoc();
auto propertyInitIsolation = ActorIsolation::forUnspecified();
if (Decl) {
if (auto *var = dyn_cast<VarDecl>(Decl)) {
propertyInitIsolation = var->getInitializerIsolation();
}
// If we found a non-implicit declaration, use its source location.
if (!Decl->isImplicit())
Loc = SILLocation(Decl);
// If it's marked @_compilerInitialized, delay emission of the note.
if (Decl->getAttrs().hasAttribute<CompilerInitializedAttr>()) {
delayedNotes.push_back([=](){
diagnose(Module, Loc, diag::stored_property_not_initialized,
StringRef(Name));
});
continue;
}
}
if (propertyInitIsolation.isGlobalActor()) {
auto *init =
dyn_cast<ConstructorDecl>(F.getDeclContext()->getAsDecl());
diagnose(Module, Loc, diag::isolated_property_initializer,
StringRef(Name), propertyInitIsolation,
getActorIsolation(init));
} else {
diagnose(Module, Loc, diag::stored_property_not_initialized,
StringRef(Name));
}
emittedNote = true;
}
// Drop the notes for @_compilerInitialized decls if we emitted a note for
// other ones that do not have that attr.
if (emittedNote)
return;
// otherwise, emit delayed notes.
for (auto &emitter : delayedNotes)
emitter();
}
/// Given a use that has at least one uninitialized element in it, produce a
/// nice symbolic name for the element being accessed.
std::string LifetimeChecker::getUninitElementName(const DIMemoryUse &Use) {
// If the overall memory allocation has multiple elements, then dive in to
// explain *which* element is being used uninitialized. Start by rerunning
// the query, to get a bitmask of exactly which elements are uninitialized.
// In a multi-element query, the first element may already be defined and
// we want to point to the second one.
unsigned firstUndefElement =
getAnyUninitializedMemberAtInst(Use.Inst, Use.FirstElement,Use.NumElements);
assert(firstUndefElement != ~0U && "No undef elements found?");
// Verify that it isn't the super.init marker that failed. The client should
// handle this, not pass it down to diagnoseInitError.
assert((!TheMemory.isDerivedClassSelf() ||
firstUndefElement != TheMemory.getNumElements() - 1) &&
"super.init failure not handled in the right place");
// If the definition is a declaration, try to reconstruct a name and
// optionally an access path to the uninitialized element.
//
// TODO: Given that we know the range of elements being accessed, we don't
// need to go all the way deep into a recursive tuple here. We could print
// an error about "v" instead of "v.0" when "v" has tuple type and the whole
// thing is accessed inappropriately.
std::string Name;
TheMemory.getPathStringToElement(firstUndefElement, Name);
return Name;
}
void LifetimeChecker::diagnoseInitError(const DIMemoryUse &Use,
Diag<StringRef, bool> DiagMessage) {
auto *Inst = Use.Inst;
if (!shouldEmitError(Inst))
return;
// If the definition is a declaration, try to reconstruct a name and
// optionally an access path to the uninitialized element.
std::string Name = getUninitElementName(Use);
// Figure out the source location to emit the diagnostic to. If this is null,
// it is probably implicitly generated code, so we'll adjust it.
SILLocation DiagLoc = Inst->getLoc();
if (DiagLoc.isNull() || DiagLoc.getSourceLoc().isInvalid())
DiagLoc = Inst->getFunction()->getLocation();
// Determine whether the field we're touching is a let property.
bool isLet = true;
for (unsigned i = 0, e = Use.NumElements; i != e; ++i)
isLet &= TheMemory.isElementLetProperty(i);
diagnose(Module, DiagLoc, DiagMessage, StringRef(Name), isLet);
// As a debugging hack, print the instruction itself if there is no location
// information. This should never happen.
if (Inst->getLoc().isNull())
llvm::dbgs() << " the instruction: " << *Inst << "\n";
// Provide context as note diagnostics.
// TODO: The QoI could be improved in many different ways here. For example,
// We could give some path information where the use was uninitialized, like
// the static analyzer.
if (!TheMemory.isAnyInitSelf())
diagnose(Module, TheMemory.getLoc(), diag::variable_defined_here, isLet);
}
void LifetimeChecker::diagnoseBadExplicitStore(SILInstruction *Inst) {
if (!shouldEmitError(Inst))
return;
diagnose(Module, Inst->getLoc(), diag::explicit_store_of_compilerinitialized);
}
/// Determines whether the given function is a constructor that belongs to a
/// distributed actor declaration.
/// \returns nullptr if false, and the class decl for the actor otherwise.
static ClassDecl* getDistributedActorOfCtor(SILFunction &F) {
auto *context = F.getDeclContext();
if (auto *ctor = dyn_cast_or_null<ConstructorDecl>(context->getAsDecl()))
if (auto *cls = dyn_cast<ClassDecl>(ctor->getDeclContext()->getAsDecl()))
if (cls->isDistributedActor())
return cls;
return nullptr;
}
static bool isFailableInitReturnUseOfEnum(EnumInst *EI);
void LifetimeChecker::findFullInitializationPoints(
SmallVectorImpl<SILInstruction *> &points) {
auto recordLocations = [&](SILInstruction *inst) {
// While insertAfter can handle terminators, it cannot handle ones that lead
// to a block with multiple predecessors. I don't expect that a terminator
// could initialize a stored property at all: a try_apply passed the
// property as an inout would not be a valid use until _after_ the property
// has been initialized.
assert(!isa<TermInst>(inst) && "unexpected terminator");
//////
// NOTE: We prefer to inject code outside of any access regions, so that
// the dynamic access-set is empty. This is a best-effort to avoid injecting
// it inside of a region, but does not account for overlapping accesses,
// etc. But, I cannot think of a way to create an overlapping access with a
// stored property when it is first initialized, because it's not valid to
// pass those inout or capture them in a closure. - kavon
BeginAccessInst *access = nullptr;
// Finds begin_access instructions that need hops placed after its
// end_access.
auto getBeginAccess = [](SILValue v) -> BeginAccessInst * {
return dyn_cast<BeginAccessInst>(getAccessScope(v));
};
// If this insertion-point is after a store-like instruction, look for a
// begin_access corresponding to the destination.
if (auto *store = dyn_cast<StoreInst>(inst)) {
access = getBeginAccess(store->getDest());
} else if (auto *assign = dyn_cast<AssignInst>(inst)) {
access = getBeginAccess(assign->getDest());
}
// If we found a begin_access, then we need to inject the hop after
// all of the corresponding end_accesses.
if (access) {
for (auto *endAccess : access->getEndAccesses())
points.push_back(endAccess);
} else {
points.push_back(inst);
}
};
// Even if there are no stored properties to initialize, we still need
// to mark full initialization point.
//
// We insert this directly after the mark_uninitialized instruction, so
// that it happens as early as `self` is available.
if (TheMemory.getNumElements() == 0) {
// FIXME: this might be wrong for convenience inits (rdar://87485045)
auto *selfDef = TheMemory.getUninitializedValue();
recordLocations(&*selfDef->getIterator());
return;
}
// Returns true iff a block returns normally from the initializer,
// which means that it returns `self` in some way (perhaps optional-wrapped).
auto returnsSelf = [](SILBasicBlock &block) -> bool {
auto term = block.getTerminator();
auto kind = term->getTermKind();
// Does this block return directly?
if (kind == TermKind::ReturnInst)
return true;
// Does this block return `self` wrapped in an Optional?
// The pattern would look like:
//
// thisBB:
// ...
// %x = enum $Optional<Dactor>, #Optional.some!enumelt
// br exitBB(%x : $Optional<Dactor>)
//
// exitBB(%y : $Optional<Dactor>):
// return %y : $Optional<Dactor>
//
if (kind == TermKind::BranchInst)
if (term->getNumOperands() == 1)
if (auto *passedVal = term->getOperand(0)->getDefiningInstruction())
if (auto *ei = dyn_cast<EnumInst>(passedVal))
if (isFailableInitReturnUseOfEnum(ei))
// Once we've reached this point, we know it's an Optional enum.
// To determine whether it's .some or .none, we can just check
// the number of operands.
return ei->getNumOperands() == 1; // is it .some ?
return false;
};
for (auto &block : F) {
/////
// Step 1: Find initializing blocks, which are blocks that contain a store
// to TheMemory that fully-initializes it, and build the Map.
// We determine whether a block is "initializing" by inspecting the "in" and
// "out" availability sets of the block. If the block goes from No / Partial
// "in" to Yes "out", then some instruction in the block caused TheMemory to
// become fully-initialized, so we record that block and its in-availability
// to scan the block more precisely later in the next Step.
auto &info = getBlockInfo(&block);
if (!info.HasNonLoadUse) {
LLVM_DEBUG(llvm::dbgs()
<< "full-init-finder: rejecting bb" << block.getDebugID()
<< " b/c no non-load uses.\n");
continue; // could not be an initializing block.
}
// Determine if this `block` is initializing, that is:
//
// InAvailability ≡ merge(OutAvailability(predecessors(block)))
// ≠ Yes
// AND
// OutAvailability(block) = Yes OR returnsSelf(block)
//
// A block with no predecessors has in-avail of non-Yes.
// A block with no successors has an out-avail of non-Yes, since
// availability is not computed for it.
auto outSet = info.OutAvailability;
if (!outSet.isAllYes() && !returnsSelf(block)) {
LLVM_DEBUG(llvm::dbgs()
<< "full-init-finder: rejecting bb" << block.getDebugID()
<< " b/c non-Yes OUT avail\n");
continue; // then this block never sees TheMemory initialized.
}
AvailabilitySet inSet(outSet.size());
auto const &predecessors = block.getPredecessorBlocks();
for (auto *pred : predecessors)
inSet.mergeIn(getBlockInfo(pred).OutAvailability);
if (inSet.isAllYes()) {
LLVM_DEBUG(llvm::dbgs()
<< "full-init-finder: rejecting bb" << block.getDebugID()
<< " b/c all-Yes IN avail\n");