-
Notifications
You must be signed in to change notification settings - Fork 10.4k
/
Copy pathTempRValueElimination.cpp
972 lines (881 loc) · 38 KB
/
TempRValueElimination.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
//===--- TempRValueElimination.cpp ----------------------------------------===//
//
// This source file is part of the Swift.org open source project
//
// Copyright (c) 2014 - 2020 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
//
//===----------------------------------------------------------------------===//
///
/// Eliminate temporary RValues inserted as a result of materialization by
/// SILGen. The key pattern here is that we are looking for alloc_stack that are
/// only written to once and are eventually either destroyed/taken from.
///
//===----------------------------------------------------------------------===//
#define DEBUG_TYPE "sil-temp-rvalue-opt"
#include "swift/Basic/Assertions.h"
#include "swift/SIL/BasicBlockUtils.h"
#include "swift/SIL/DebugUtils.h"
#include "swift/SIL/MemAccessUtils.h"
#include "swift/SIL/NodeBits.h"
#include "swift/SIL/OSSALifetimeCompletion.h"
#include "swift/SIL/OwnershipUtils.h"
#include "swift/SIL/SILArgument.h"
#include "swift/SIL/SILBuilder.h"
#include "swift/SIL/SILVisitor.h"
#include "swift/SILOptimizer/Analysis/AliasAnalysis.h"
#include "swift/SILOptimizer/Analysis/DominanceAnalysis.h"
#include "swift/SILOptimizer/Analysis/PostOrderAnalysis.h"
#include "swift/SILOptimizer/Analysis/RCIdentityAnalysis.h"
#include "swift/SILOptimizer/Analysis/SimplifyInstruction.h"
#include "swift/SILOptimizer/PassManager/Passes.h"
#include "swift/SILOptimizer/PassManager/Transforms.h"
#include "swift/SILOptimizer/Utils/CFGOptUtils.h"
#include "swift/SILOptimizer/Utils/ValueLifetime.h"
#include "llvm/ADT/SetVector.h"
#include "llvm/ADT/Statistic.h"
#include "llvm/Support/CommandLine.h"
#include "llvm/Support/Debug.h"
using namespace swift;
//===----------------------------------------------------------------------===//
// Interface
//===----------------------------------------------------------------------===//
namespace {
/// Temporary RValue Optimization
///
/// Peephole optimization to eliminate short-lived immutable temporary copies.
/// This handles a common pattern generated by SILGen where temporary RValues
/// are emitted as copies...
///
/// %temp = alloc_stack $T
/// copy_addr %src to [init] %temp : $*T
/// // no writes to %src or %temp
/// destroy_addr %temp : $*T
/// dealloc_stack %temp : $*T
///
/// This differs from the copy forwarding algorithm because it handles
/// copy source and dest lifetimes that are unavoidably overlapping. Instead,
/// it finds cases in which it is easy to determine that the source is
/// unmodified during the copy destination's lifetime. Thus, the destination can
/// be viewed as a short-lived "rvalue".
///
/// As a second optimization, also stores into temporaries are handled. This is
/// a simple form of redundant-load-elimination (RLE).
///
/// %temp = alloc_stack $T
/// store %src to [init] %temp : $*T
/// // no writes to %temp
/// %v = load [take] %temp : $*T
/// dealloc_stack %temp : $*T
///
/// TODO: Check if we still need to handle stores when RLE supports OSSA.
class TempRValueOptPass : public SILFunctionTransform {
bool collectLoads(Operand *addressUse, CopyAddrInst *originalCopy,
InstructionSetWithSize &loadInsts);
bool collectLoadsFromProjection(SingleValueInstruction *projection,
CopyAddrInst *originalCopy,
InstructionSetWithSize &loadInsts);
SILInstruction *getLastUseWhileSourceIsNotModified(
CopyAddrInst *copyInst, const InstructionSetWithSize &useInsts,
AliasAnalysis *aa);
bool
checkTempObjectDestroy(AllocStackInst *tempObj, CopyAddrInst *copyInst);
bool extendAccessScopes(CopyAddrInst *copyInst, SILInstruction *lastUseInst,
AliasAnalysis *aa);
void tryOptimizeCopyIntoTemp(CopyAddrInst *copyInst);
SILBasicBlock::iterator tryOptimizeStoreIntoTemp(StoreInst *si);
void run() override;
};
} // anonymous namespace
bool TempRValueOptPass::collectLoadsFromProjection(
SingleValueInstruction *projection, CopyAddrInst *originalCopy,
InstructionSetWithSize &loadInsts) {
// Transitively look through projections on stack addresses.
for (auto *projUseOper : projection->getUses()) {
auto *user = projUseOper->getUser();
if (user->isTypeDependentOperand(*projUseOper))
continue;
if (!collectLoads(projUseOper, originalCopy, loadInsts))
return false;
}
return true;
}
/// Transitively explore all data flow uses of the given \p address until
/// reaching a load or returning false.
///
/// Any user opcode recognized by collectLoads must be replaced correctly later
/// during tryOptimizeCopyIntoTemp. If it is possible for any use to destroy the
/// value in \p address, then that use must be removed or made non-destructive
/// after the copy is removed and its operand is replaced.
///
/// Warning: To preserve the original object lifetime, tryOptimizeCopyIntoTemp
/// must assume that there are no holes in lifetime of the temporary stack
/// location at \address. The temporary must be initialized by the original copy
/// and never written to again. Therefore, collectLoads disallows any operation
/// that may write to memory at \p address.
bool TempRValueOptPass::
collectLoads(Operand *addressUse, CopyAddrInst *originalCopy,
InstructionSetWithSize &loadInsts) {
SILInstruction *user = addressUse->getUser();
SILValue address = addressUse->get();
// All normal uses (loads) must be in the initialization block.
// (The destroy and dealloc are commonly in a different block though.)
SILBasicBlock *block = originalCopy->getParent();
if (user->getParent() != block)
return false;
// Only allow uses that cannot destroy their operand. We need to be sure
// that replacing all this temporary's uses with the copy source doesn't
// destroy the source. This way, we know that the destroy_addr instructions
// that we recorded cover all the temporary's lifetime termination points.
//
// Currently this includes address projections, loads, and in_guaranteed uses
// by an apply.
//
// TODO: handle non-destructive projections of enums
// (unchecked_take_enum_data_addr of Optional is nondestructive.)
switch (user->getKind()) {
default:
LLVM_DEBUG(llvm::dbgs()
<< " Temp use may write/destroy its source" << *user);
return false;
case SILInstructionKind::BeginAccessInst: {
auto *beginAccess = cast<BeginAccessInst>(user);
if (beginAccess->getAccessKind() != SILAccessKind::Read)
return false;
// We don't have to recursively call collectLoads for the beginAccess
// result, because a SILAccessKind::Read already guarantees that there are
// no writes to the beginAccess result address (or any projection from it).
// But we have to register the end-accesses as loads to correctly mark the
// end-of-lifetime of the tempObj.
//
// %addr = begin_access [read]
// ... // there can be no writes to %addr here
// end_access %addr // <- This is where the use actually ends.
for (EndAccessInst *endAccess : beginAccess->getEndAccesses()) {
if (endAccess->getParent() != block)
return false;
loadInsts.insert(endAccess);
}
return true;
}
case SILInstructionKind::MarkDependenceInst: {
auto mdi = cast<MarkDependenceInst>(user);
if (mdi->getBase() == address) {
// We want to keep the original lifetime of the base. If we would eliminate
// the base alloc_stack, we risk to insert a destroy_addr too early.
return false;
}
// If the user is the value operand of the MarkDependenceInst we have to
// transitively explore its uses until we reach a load or return false
for (auto *mdiUseOper : mdi->getUses()) {
if (!collectLoads(mdiUseOper, originalCopy, loadInsts))
return false;
}
return true;
}
case SILInstructionKind::PartialApplyInst:
if (!cast<PartialApplyInst>(user)->isOnStack())
return false;
LLVM_FALLTHROUGH;
case SILInstructionKind::ApplyInst:
case SILInstructionKind::TryApplyInst:
case SILInstructionKind::BeginApplyInst: {
auto convention = ApplySite(user).getArgumentConvention(*addressUse);
if (!convention.isGuaranteedConventionInCaller())
return false;
loadInsts.insert(user);
if (auto *beginApply = dyn_cast<BeginApplyInst>(user)) {
// Register 'end_apply'/'abort_apply' as loads as well
// 'checkNoSourceModification' should check instructions until
// 'end_apply'/'abort_apply'.
for (auto *tokenUse : beginApply->getEndApplyUses()) {
auto *tokenUser = tokenUse->getUser();
if (tokenUser->getParent() != block)
return false;
loadInsts.insert(tokenUser);
}
}
return true;
}
case SILInstructionKind::YieldInst: {
auto *yield = cast<YieldInst>(user);
auto convention = yield->getArgumentConventionForOperand(*addressUse);
if (!convention.isGuaranteedConventionInCaller())
return false;
loadInsts.insert(user);
return true;
}
case SILInstructionKind::OpenExistentialAddrInst: {
// We only support open existential addr if the access is immutable.
auto *oeai = cast<OpenExistentialAddrInst>(user);
if (oeai->getAccessKind() != OpenedExistentialAccess::Immutable) {
LLVM_DEBUG(llvm::dbgs() << " Temp consuming use may write/destroy "
"its source"
<< *user);
return false;
}
return collectLoadsFromProjection(oeai, originalCopy, loadInsts);
}
case SILInstructionKind::UncheckedTakeEnumDataAddrInst: {
// In certain cases, unchecked_take_enum_data_addr invalidates the
// underlying memory, so by default we can not look through it... but this
// is not true in the case of Optional. This is an important case for us to
// handle, so handle it here.
auto *utedai = cast<UncheckedTakeEnumDataAddrInst>(user);
if (!utedai->getOperand()->getType().getOptionalObjectType()) {
LLVM_DEBUG(llvm::dbgs()
<< " Temp use may write/destroy its source" << *utedai);
return false;
}
return collectLoadsFromProjection(utedai, originalCopy, loadInsts);
}
case SILInstructionKind::StructElementAddrInst:
case SILInstructionKind::TupleElementAddrInst:
case SILInstructionKind::UncheckedAddrCastInst:
return collectLoadsFromProjection(cast<SingleValueInstruction>(user),
originalCopy, loadInsts);
case SILInstructionKind::LoadInst: {
// Loads are the end of the data flow chain. The users of the load can't
// access the temporary storage.
//
// That being said, if we see a load [take] here then we must have had a
// load [take] of a projection of our temporary stack location since we skip
// all the load [take] of the top level allocation in the caller of this
// function. So if we have such a load [take], we /must/ have a
// reinitialization or an alloc_stack that does not fit the pattern we are
// expecting from SILGen. Be conservative and return false.
auto *li = cast<LoadInst>(user);
if (li->getOwnershipQualifier() == LoadOwnershipQualifier::Take &&
// Only accept load [take] if it takes the whole temporary object.
// load [take] from a projection would destroy only a part of the
// temporary and we don't handle this.
address != originalCopy->getDest()) {
return false;
}
loadInsts.insert(user);
return true;
}
case SILInstructionKind::LoadBorrowInst: {
loadInsts.insert(user);
BorrowedValue borrow(cast<LoadBorrowInst>(user));
auto visitEndScope = [&](Operand *op) -> bool {
auto *opUser = op->getUser();
if (auto *endBorrow = dyn_cast<EndBorrowInst>(opUser)) {
if (endBorrow->getParent() != block)
return false;
loadInsts.insert(endBorrow);
return true;
}
// Don't look further if we see a reborrow.
assert(cast<BranchInst>(opUser));
return false;
};
auto res = borrow.visitLocalScopeEndingUses(visitEndScope);
return res;
}
case SILInstructionKind::FixLifetimeInst:
// If we have a fixed lifetime on our alloc_stack, we can just treat it like
// a load and re-write it so that it is on the old memory or old src object.
loadInsts.insert(user);
return true;
case SILInstructionKind::CopyAddrInst: {
// copy_addr which read from the temporary are like loads.
auto *copyFromTmp = cast<CopyAddrInst>(user);
if (copyFromTmp->getDest() == address) {
LLVM_DEBUG(llvm::dbgs() << " Temp written or taken" << *user);
return false;
}
// As with load [take], only accept copy_addr [take] if it takes the whole
// temporary object.
if (copyFromTmp->isTakeOfSrc() && address != originalCopy->getDest())
return false;
loadInsts.insert(copyFromTmp);
return true;
}
}
}
/// Checks if the source of \p copyInst is not modified within the temporary's
/// lifetime, i.e. is not modified before the last use of \p useInsts.
///
/// If there are no source modifications with the lifetime, returns the last
/// user (or copyInst if there are no uses at all).
/// Otherwise, returns a nullptr.
///
/// Unfortunately, we cannot simply use the destroy points as the lifetime end,
/// because they can be in a different basic block (that's what SILGen
/// generates). Instead we guarantee that all normal uses are within the block
/// of the temporary and look for the last use, which effectively ends the
/// lifetime.
SILInstruction *TempRValueOptPass::getLastUseWhileSourceIsNotModified(
CopyAddrInst *copyInst, const InstructionSetWithSize &useInsts,
AliasAnalysis *aa) {
if (useInsts.empty())
return copyInst;
unsigned numLoadsFound = 0;
SILValue copySrc = copyInst->getSrc();
// We already checked that the useful lifetime of the temporary ends in
// the initialization block. Iterate over the instructions of the block,
// starting at copyInst, until we get to the last user.
auto iter = std::next(copyInst->getIterator());
auto iterEnd = copyInst->getParent()->end();
for (; iter != iterEnd; ++iter) {
SILInstruction *inst = &*iter;
if (useInsts.contains(inst))
++numLoadsFound;
// If this is the last use of the temp we are ok. After this point,
// modifications to the source don't matter anymore.
// Note that we are assuming here that if an instruction loads and writes
// to copySrc at the same time (like a copy_addr could do), the write
// takes effect after the load.
if (numLoadsFound == useInsts.size()) {
// Function calls are an exception: in a called function a potential
// modification of copySrc could occur _before_ the read of the temporary.
if ((FullApplySite::isa(inst) || isa<YieldInst>(inst)) &&
aa->mayWriteToMemory(inst, copySrc)) {
return nullptr;
}
return inst;
}
if (aa->mayWriteToMemory(inst, copySrc)) {
LLVM_DEBUG(llvm::dbgs() << " Source modified by" << *iter);
return nullptr;
}
}
// For some reason, not all normal uses have been seen between the copy and
// the end of the initialization block. We should never reach here.
return nullptr;
}
/// Tries to move an end_access down to extend the access scope over all uses
/// of the temporary. For example:
///
/// %a = begin_access %src
/// copy_addr %a to [init] %temp : $*T
/// end_access %a
/// use %temp
///
/// We must not replace %temp with %a after the end_access. Instead we try to
/// move the end_access after "use %temp".
bool TempRValueOptPass::extendAccessScopes(
CopyAddrInst *copyInst, SILInstruction *lastUseInst, AliasAnalysis *aa) {
SILValue copySrc = copyInst->getSrc();
EndAccessInst *endAccessToMove = nullptr;
auto begin = std::next(copyInst->getIterator());
auto end = std::next(lastUseInst->getIterator());
for (SILInstruction &inst : make_range(begin, end)) {
if (auto *endAccess = dyn_cast<EndAccessInst>(&inst)) {
// To keep things simple, we can just move a single end_access. Also, we
// cannot move an end_access over a (non-aliasing) end_access.
if (endAccessToMove)
return false;
// Is this the end of an access scope of the copy-source?
if (aa->mayAlias(copySrc, endAccess->getSource()) &&
// There cannot be any aliasing modifying accesses within the
// liverange of the temporary, because we would have cought this in
// `getLastUseWhileSourceIsNotModified`.
// But there are cases where `AliasAnalysis::isNoAlias` is less
// precise than `AliasAnalysis::mayWriteToMemory`. Therefore, just
// ignore any non-read accesses.
endAccess->getBeginAccess()->getAccessKind() == SILAccessKind::Read) {
// Don't move instructions beyond the block's terminator.
if (isa<TermInst>(lastUseInst))
return false;
endAccessToMove = endAccess;
}
} else if (endAccessToMove) {
// We cannot move an end_access over a begin_access. This would destroy
// the proper nesting of accesses.
if (isa<BeginAccessInst>(&inst) || isa<BeginUnpairedAccessInst>(inst))
return false;
// Don't extend a read-access scope over a (potential) write.
// Note that inst can be a function call containing other access scopes.
// But doing the mayWriteToMemory check, we know that the function can
// only contain read accesses (to the same memory location). So it's fine
// to move endAccessToMove even over such a function call.
if (aa->mayWriteToMemory(&inst, endAccessToMove->getSource()))
return false;
}
}
if (endAccessToMove)
endAccessToMove->moveAfter(lastUseInst);
return true;
}
/// Return true if the \p tempObj, which is initialized by \p copyInst, is
/// destroyed in an orthodox way.
///
/// When tryOptimizeCopyIntoTemp replaces all of tempObj's uses, it assumes that
/// the object is initialized by the original copy and directly destroyed on all
/// paths by one of the recognized 'destroy_addr' or 'copy_addr [take]'
/// operations. This assumption must be checked. For example, in non-OSSA,
/// it is legal to destroy an in-memory object by loading the value and
/// releasing it. Rather than detecting unbalanced load releases, simply check
/// that tempObj is destroyed directly on all paths.
bool TempRValueOptPass::checkTempObjectDestroy(
AllocStackInst *tempObj, CopyAddrInst *copyInst) {
// ValueLifetimeAnalysis is not normally used for address types. It does not
// reason about the lifetime of the in-memory object. However the utility can
// be abused here to check that the address is directly destroyed on all
// paths. collectLoads has already guaranteed that tempObj's lifetime has no
// holes/reinitializations.
SmallVector<SILInstruction *, 8> users;
for (auto result : tempObj->getResults()) {
for (Operand *operand : result->getUses()) {
SILInstruction *user = operand->getUser();
if (user == copyInst)
continue;
if (isa<DeallocStackInst>(user))
continue;
users.push_back(user);
}
}
// Find the boundary of tempObj's address lifetime, starting at copyInst.
ValueLifetimeAnalysis vla(copyInst, users);
ValueLifetimeAnalysis::Frontier tempAddressFrontier;
if (!vla.computeFrontier(tempAddressFrontier,
ValueLifetimeAnalysis::DontModifyCFG)) {
return false;
}
// Check that the lifetime boundary ends at direct destroy points.
for (SILInstruction *frontierInst : tempAddressFrontier) {
auto pos = frontierInst->getIterator();
// If the frontier is at the head of a block, then either it is an
// unexpected lifetime exit, or the lifetime ended at a
// terminator. TempRValueOptPass does not handle either case.
if (pos == frontierInst->getParent()->begin())
return false;
// Look for a known destroy point as described in the function level
// comment. This allowlist can be expanded as more cases are handled in
// tryOptimizeCopyIntoTemp during copy replacement.
SILInstruction *lastUser = &*std::prev(pos);
if (isa<DestroyAddrInst>(lastUser))
continue;
if (auto *cai = dyn_cast<CopyAddrInst>(lastUser)) {
assert(cai->getSrc() == tempObj && "collectLoads checks for writes");
if (cai->isTakeOfSrc())
continue;
}
return false;
}
return true;
}
/// Tries to perform the temporary rvalue copy elimination for \p copyInst
void TempRValueOptPass::tryOptimizeCopyIntoTemp(CopyAddrInst *copyInst) {
if (!copyInst->isInitializationOfDest())
return;
auto *tempObj = dyn_cast<AllocStackInst>(copyInst->getDest());
if (!tempObj)
return;
// If the temporary storage is lexical, it either came from a source-level var
// or was marked lexical because it was passed to a function that has been
// inlined.
// TODO: [begin_borrow_addr] Once we can mark addresses as being borrowed, we
// won't need to mark alloc_stacks lexical during inlining. At that
// point, the above comment should change, but the implementation
// remains the same.
//
// In either case, we can eliminate the temporary if the source of the copy is
// lexical and it is live for longer than the temporary.
if (tempObj->isLexical()) {
// TODO: Determine whether the base of the copy_addr's source is lexical and
// its live range contains the range in which the alloc_stack
// contains the value copied into it via the copy_addr.
//
// For now, only look for guaranteed arguments.
auto storage = AccessStorageWithBase::compute(copyInst->getSrc());
if (!storage.base)
return;
if (auto *arg = dyn_cast<SILFunctionArgument>(storage.base))
if (!arg->getArgumentConvention().isGuaranteedConventionInCallee())
return;
}
bool isOSSA = copyInst->getFunction()->hasOwnership();
SILValue copySrc = copyInst->getSrc();
assert(tempObj != copySrc && "can't initialize temporary with itself");
// If the source of the copyInst is taken, it must be deinitialized (via
// destroy_addr, load [take], copy_addr [take]). This must be done at the
// right spot: after the last use tempObj, but before any (potential)
// re-initialization of the source.
bool needFinalDeinit = copyInst->isTakeOfSrc();
// Scan all uses of the temporary storage (tempObj) to verify they all refer
// to the value initialized by this copy. It is sufficient to check that the
// only users that modify memory are the copy_addr [initialization] and
// destroy_addr.
InstructionSetWithSize loadInsts(getFunction());
// Set of tempObj users
InstructionSet userSet(getFunction());
for (auto *useOper : tempObj->getUses()) {
SILInstruction *user = useOper->getUser();
userSet.insert(user);
if (user == copyInst)
continue;
// Deallocations are allowed to be in a different block.
if (isa<DeallocStackInst>(user))
continue;
// Also, destroys are allowed to be in a different block.
if (isa<DestroyAddrInst>(user)) {
if (!isOSSA && needFinalDeinit) {
// In non-OSSA mode, for the purpose of inserting the destroy of
// copySrc, we have to be conservative and assume that the lifetime of
// tempObj goes beyond it's last use - until the final destroy_addr.
// Otherwise we would risk of inserting the destroy too early.
// So we just treat the destroy_addr as any other use of tempObj.
if (user->getParent() != copyInst->getParent())
return;
loadInsts.insert(user);
}
continue;
}
if (!collectLoads(useOper, copyInst, loadInsts))
return;
}
// Check and return without optimization if we have any users of tempObj that
// precede the copyInst.
// This can happen with projections.
// TODO: We can enable this case if we clone the projections at "load" uses
// All instructions in userSet are in the same block as copyInst. collectLoads
// ensures of this.
for (SILInstruction &inst : llvm::make_range(copyInst->getParent()->begin(),
copyInst->getIterator())) {
if (userSet.contains(&inst)) {
return;
}
}
AliasAnalysis *aa = getPassManager()->getAnalysis<AliasAnalysis>(getFunction());
// Check if the source is modified within the lifetime of the temporary.
SILInstruction *lastLoadInst =
getLastUseWhileSourceIsNotModified(copyInst, loadInsts, aa);
if (!lastLoadInst)
return;
// We cannot insert the destroy of copySrc after lastLoadInst if copySrc is
// re-initialized by exactly this instruction.
// This is a corner case, but can happen if lastLoadInst is a copy_addr.
// Example:
// copy_addr [take] %copySrc to [init] %tempObj // copyInst
// copy_addr [take] %tempObj to [init] %copySrc // lastLoadInst
if (needFinalDeinit && lastLoadInst != copyInst &&
!isa<DestroyAddrInst>(lastLoadInst) &&
aa->mayWriteToMemory(lastLoadInst, copySrc))
return;
if (!isOSSA && !checkTempObjectDestroy(tempObj, copyInst))
return;
if (!extendAccessScopes(copyInst, lastLoadInst, aa))
return;
LLVM_DEBUG(llvm::dbgs() << " Success: replace temp" << *tempObj);
// If copyInst's source must be deinitialized, whether that must be done via
// a newly created destroy_addr.
//
// If lastLoadInst is a load or a copy_addr, then the deinitialization can be
// done in that instruction.
//
// This is necessary for correctness: otherwise, copies of move-only values
// would be introduced.
bool needToInsertDestroy = [&]() {
if (!needFinalDeinit)
return false;
if (lastLoadInst == copyInst)
return true;
if (auto *cai = dyn_cast<CopyAddrInst>(lastLoadInst)) {
if (cai->getSrc() == tempObj && cai->isTakeOfSrc()) {
// This copy_addr [take] will perform the final deinitialization.
return false;
}
return true;
}
if (auto *li = dyn_cast<LoadInst>(lastLoadInst)) {
if (li->getOperand() == tempObj &&
li->getOwnershipQualifier() == LoadOwnershipQualifier::Take) {
// This load [take] will perform the final deinitialization.
return false;
}
return true;
}
return true;
}();
if (needToInsertDestroy) {
// Compensate the [take] of the original copyInst.
SILBuilderWithScope::insertAfter(lastLoadInst, [&] (SILBuilder &builder) {
builder.createDestroyAddr(builder.getInsertionPoint()->getLoc(), copySrc);
});
}
// * Replace all uses of the tempObj with the copySrc.
//
// * Delete the destroy(s) of tempObj (to compensate the removal of the
// original copyInst): either by erasing the destroy_addr or by converting
// load/copy_addr [take] into copying instructions.
//
// Note: we must not delete the original copyInst because it would crash the
// instruction iteration in run(). Instead the copyInst gets identical Src and
// Dest operands.
while (!tempObj->use_empty()) {
Operand *use = *tempObj->use_begin();
SILInstruction *user = use->getUser();
switch (user->getKind()) {
case SILInstructionKind::DestroyAddrInst:
case SILInstructionKind::DeallocStackInst:
user->eraseFromParent();
break;
case SILInstructionKind::CopyAddrInst: {
auto *cai = cast<CopyAddrInst>(user);
if (cai != copyInst) {
assert(cai->getSrc() == tempObj);
if (cai->isTakeOfSrc() && (!needFinalDeinit || lastLoadInst != cai)) {
cai->setIsTakeOfSrc(IsNotTake);
}
}
use->set(copySrc);
break;
}
case SILInstructionKind::LoadInst: {
auto *li = cast<LoadInst>(user);
if (li->getOwnershipQualifier() == LoadOwnershipQualifier::Take &&
(!needFinalDeinit || li != lastLoadInst)) {
li->setOwnershipQualifier(LoadOwnershipQualifier::Copy);
}
use->set(copySrc);
break;
}
// ASSUMPTION: no operations that may be handled by this default clause can
// destroy tempObj. This includes operations that load the value from memory
// and release it or cast the address before destroying it.
default:
use->set(copySrc);
break;
}
}
tempObj->eraseFromParent();
invalidateAnalysis(SILAnalysis::InvalidationKind::Instructions);
}
SILBasicBlock::iterator
TempRValueOptPass::tryOptimizeStoreIntoTemp(StoreInst *si) {
// If our store is an assign, bail.
if (si->getOwnershipQualifier() == StoreOwnershipQualifier::Assign)
return std::next(si->getIterator());
auto *tempObj = dyn_cast<AllocStackInst>(si->getDest());
if (!tempObj) {
return std::next(si->getIterator());
}
// If the temporary storage is lexical, it either came from a source-level var
// or was marked lexical because it was passed to a function that has been
// inlined.
// TODO: [begin_borrow_addr] Once we can mark addresses as being borrowed, we
// won't need to mark alloc_stacks lexical during inlining. At that
// point, the above comment should change, but the implementation
// remains the same.
//
// In either case, we can eliminate the temporary if the source of the store
// is lexical and it is live for longer than the temporary.
if (tempObj->isLexical()) {
// TODO: Find the lexical root of the source, if any, and allow optimization
// if its live range contains the range in which the alloc_stack
// contains the value stored into it.
return std::next(si->getIterator());
}
// If our tempObj has a dynamic lifetime (meaning it is conditionally
// initialized, conditionally taken, etc), we can not convert its uses to SSA
// while eliminating it simply. So bail.
if (tempObj->hasDynamicLifetime()) {
return std::next(si->getIterator());
}
// Scan all uses of the temporary storage (tempObj) to verify they all refer
// to the value initialized by this copy. It is sufficient to check that the
// only users that modify memory are the copy_addr [initialization] and
// destroy_addr.
for (auto *useOper : tempObj->getUses()) {
SILInstruction *user = useOper->getUser();
if (user == si)
continue;
// Bail if there is any kind of user which is not handled in the code below.
switch (user->getKind()) {
case SILInstructionKind::DestroyAddrInst:
case SILInstructionKind::DeallocStackInst:
case SILInstructionKind::LoadInst:
case SILInstructionKind::FixLifetimeInst:
break;
case SILInstructionKind::CopyAddrInst:
if (cast<CopyAddrInst>(user)->getDest() == tempObj)
return std::next(si->getIterator());
break;
case SILInstructionKind::MarkDependenceInst:
if (cast<MarkDependenceInst>(user)->getValue() == tempObj)
return std::next(si->getIterator());
break;
default:
return std::next(si->getIterator());
}
}
// Since store is always a consuming operation, we do not need to worry about
// any lifetime constraints and can just replace all of the uses here. This
// contrasts with the copy_addr implementation where we need to consider the
// possibility that the source address is written to.
LLVM_DEBUG(llvm::dbgs() << " Success: replace temp" << *tempObj);
// Do a "replaceAllUses" by either deleting the users or replacing them with
// the appropriate operation on the source value.
SmallVector<SILInstruction *, 4> toDelete;
for (auto *use : tempObj->getUses()) {
// If our store is the user, just skip it.
if (use->getUser() == si) {
continue;
}
SILInstruction *user = use->getUser();
switch (user->getKind()) {
case SILInstructionKind::DestroyAddrInst: {
SILBuilderWithScope builder(user);
builder.emitDestroyValueOperation(user->getLoc(), si->getSrc());
toDelete.push_back(user);
break;
}
case SILInstructionKind::DeallocStackInst:
toDelete.push_back(user);
break;
case SILInstructionKind::CopyAddrInst: {
auto *cai = cast<CopyAddrInst>(user);
assert(cai->getSrc() == tempObj);
SILBuilderWithScope builder(user);
auto qualifier = cai->isInitializationOfDest()
? StoreOwnershipQualifier::Init
: StoreOwnershipQualifier::Assign;
SILValue src = si->getSrc();
if (!cai->isTakeOfSrc()) {
src = builder.emitCopyValueOperation(cai->getLoc(), src);
}
builder.emitStoreValueOperation(cai->getLoc(), src, cai->getDest(),
qualifier);
toDelete.push_back(cai);
break;
}
case SILInstructionKind::LoadInst: {
// Since store is always forwarding, we know that we should have our own
// value here. So, we should be able to just RAUW any load [take] and
// insert a copy + RAUW for any load [copy].
auto *li = cast<LoadInst>(user);
SILValue srcObject = si->getSrc();
if (li->getOwnershipQualifier() == LoadOwnershipQualifier::Copy) {
SILBuilderWithScope builder(li);
srcObject = builder.emitCopyValueOperation(li->getLoc(), srcObject);
}
li->replaceAllUsesWith(srcObject);
toDelete.push_back(li);
break;
}
case SILInstructionKind::FixLifetimeInst: {
auto *fli = cast<FixLifetimeInst>(user);
SILBuilderWithScope builder(fli);
builder.createFixLifetime(fli->getLoc(), si->getSrc());
toDelete.push_back(fli);
break;
}
case SILInstructionKind::MarkDependenceInst: {
auto mdi = cast<MarkDependenceInst>(user);
assert(mdi->getBase() == tempObj);
SILBuilderWithScope builder(user);
auto newInst = builder.createMarkDependence(user->getLoc(),
mdi->getValue(),
si->getSrc(),
mdi->dependenceKind());
mdi->replaceAllUsesWith(newInst);
toDelete.push_back(mdi);
break;
}
// ASSUMPTION: no operations that may be handled by this default clause can
// destroy tempObj. This includes operations that load the value from memory
// and release it.
default:
llvm::errs() << "Unhandled user: " << *user;
llvm_unreachable("Unhandled case?!");
break;
}
}
while (!toDelete.empty()) {
auto *inst = toDelete.pop_back_val();
inst->dropAllReferences();
inst->eraseFromParent();
}
auto nextIter = std::next(si->getIterator());
si->eraseFromParent();
tempObj->eraseFromParent();
invalidateAnalysis(SILAnalysis::InvalidationKind::Instructions);
return nextIter;
}
//===----------------------------------------------------------------------===//
// High Level Entrypoint
//===----------------------------------------------------------------------===//
/// The main entry point of the pass.
void TempRValueOptPass::run() {
auto *function = getFunction();
auto *da = PM->getAnalysis<DominanceAnalysis>();
LLVM_DEBUG(llvm::dbgs() << "Copy Peephole in Func " << function->getName()
<< "\n");
SmallVector<SILValue> valuesToComplete;
// Find all copy_addr instructions.
llvm::SmallSetVector<CopyAddrInst *, 8> deadCopies;
for (auto &block : *function) {
// Increment the instruction iterator only after calling
// tryOptimizeCopyIntoTemp because the instruction after CopyInst might be
// deleted, but copyInst itself won't be deleted until later.
for (auto ii = block.begin(); ii != block.end();) {
if (auto *copyInst = dyn_cast<CopyAddrInst>(&*ii)) {
// In case of success, this may delete instructions, but not the
// CopyInst itself.
tryOptimizeCopyIntoTemp(copyInst);
// Remove identity copies which either directly result from successfully
// calling tryOptimizeCopyIntoTemp or was created by an earlier
// iteration, where another copy_addr copied the temporary back to the
// source location.
if (copyInst->getSrc() == copyInst->getDest()) {
deadCopies.insert(copyInst);
}
++ii;
continue;
}
if (auto *si = dyn_cast<StoreInst>(&*ii)) {
auto stored = si->getSrc();
bool isOrHasEnum = stored->getType().isOrHasEnum();
auto nextIter = std::next(si->getIterator());
ii = tryOptimizeStoreIntoTemp(si);
// If the optimization was successful, and the stack loc was an enum
// type, collect the stored value for lifetime completion.
// This is needed because we can have incomplete address lifetimes on
// none/trivial paths for an enum type. Once we convert to value form,
// this will cause incomplete value lifetimes which can raise ownership
// verification errors, because we rely on linear lifetimes in OSSA.
if (ii == nextIter && isOrHasEnum) {
valuesToComplete.push_back(stored);
}
continue;
}
++ii;
}
}
auto callbacks = InstModCallbacks().onDelete(
[](SILInstruction *instToKill) {
// SimplifyInstruction is not in the business of removing
// copy_addr. If it were, then we would need to update deadCopies.
assert(!isa<CopyAddrInst>(instToKill));
instToKill->eraseFromParent();
}
);
DeadEndBlocks deBlocks(function);
for (auto *deadCopy : deadCopies) {
auto *srcInst = deadCopy->getSrc()->getDefiningInstruction();
deadCopy->eraseFromParent();
// Simplify any access scope markers that were only used by the dead
// copy_addr and other potentially unused addresses.
if (srcInst) {
simplifyAndReplaceAllSimplifiedUsesAndErase(srcInst, callbacks, &deBlocks);
}
}
// Call the utlity to complete ossa lifetime.
bool completedAny = false;
OSSALifetimeCompletion completion(function, da->get(function), deBlocks);
for (auto it : valuesToComplete) {
auto completed = completion.completeOSSALifetime(
it, OSSALifetimeCompletion::Boundary::Liveness);
completedAny = (completed == LifetimeCompletion::WasCompleted);
}
if (!deadCopies.empty() || completedAny) {
invalidateAnalysis(SILAnalysis::InvalidationKind::Instructions);
}
}
SILTransform *swift::createTempRValueOpt() { return new TempRValueOptPass(); }