@@ -77,6 +77,8 @@ class TempRValueOptPass : public SILFunctionTransform {
77
77
ValueLifetimeAnalysis::Frontier &tempAddressFrontier);
78
78
79
79
bool tryOptimizeCopyIntoTemp (CopyAddrInst *copyInst);
80
+ std::pair<SILBasicBlock::iterator, bool >
81
+ tryOptimizeStoreIntoTemp (StoreInst *si);
80
82
81
83
void run () override ;
82
84
};
@@ -98,7 +100,7 @@ class TempRValueOptPass : public SILFunctionTransform {
98
100
// / that may write to memory at \p address.
99
101
bool TempRValueOptPass::collectLoads (
100
102
Operand *userOp, SILInstruction *user, SingleValueInstruction *address,
101
- SILValue srcObject , SmallPtrSetImpl<SILInstruction *> &loadInsts) {
103
+ SILValue srcAddr , SmallPtrSetImpl<SILInstruction *> &loadInsts) {
102
104
// All normal uses (loads) must be in the initialization block.
103
105
// (The destroy and dealloc are commonly in a different block though.)
104
106
if (user->getParent () != address->getParent ())
@@ -136,8 +138,20 @@ bool TempRValueOptPass::collectLoads(
136
138
return false ;
137
139
}
138
140
141
+ // If we do not have an src address, but are indirect, bail. We would need
142
+ // to perform function signature specialization to change the functions
143
+ // signature to pass something direct.
144
+ if (!srcAddr && convention.isIndirectConvention ()) {
145
+ LLVM_DEBUG (
146
+ llvm::dbgs ()
147
+ << " Temp used to materialize value for indirect convention?! Can "
148
+ " not remove temporary without func sig opts"
149
+ << *user);
150
+ return false ;
151
+ }
152
+
139
153
// Check if there is another function argument, which is inout which might
140
- // modify the source of the copy_addr .
154
+ // modify the source address if we have one .
141
155
//
142
156
// When a use of the temporary is an apply, then we need to prove that the
143
157
// function called by the apply cannot modify the temporary's source
@@ -150,23 +164,33 @@ bool TempRValueOptPass::collectLoads(
150
164
// alias with `src` because the `src` value must be initialized at the point
151
165
// of the call. Hence, it is sufficient to check specifically for another
152
166
// @inout that might alias with `src`.
153
- auto calleeConv = apply.getSubstCalleeConv ();
154
- unsigned calleeArgIdx = apply.getCalleeArgIndexOfFirstAppliedArg ();
155
- for (const auto &operand : apply.getArgumentOperands ()) {
156
- auto argConv = calleeConv.getSILArgumentConvention (calleeArgIdx);
157
- if (argConv.isInoutConvention ()) {
158
- if (!aa->isNoAlias (operand.get (), srcObject)) {
159
- return false ;
167
+ if (srcAddr) {
168
+ auto calleeConv = apply.getSubstCalleeConv ();
169
+ unsigned calleeArgIdx = apply.getCalleeArgIndexOfFirstAppliedArg ();
170
+ for (const auto &operand : apply.getArgumentOperands ()) {
171
+ auto argConv = calleeConv.getSILArgumentConvention (calleeArgIdx);
172
+ if (argConv.isInoutConvention ()) {
173
+ if (!aa->isNoAlias (operand.get (), srcAddr)) {
174
+ return false ;
175
+ }
160
176
}
177
+ ++calleeArgIdx;
161
178
}
162
- ++calleeArgIdx;
163
179
}
164
180
165
181
// Everything is okay with the function call. Register it as a "load".
166
182
loadInsts.insert (user);
167
183
return true ;
168
184
}
169
185
case SILInstructionKind::OpenExistentialAddrInst: {
186
+ // If we do not have an srcAddr, bail. We do not support promoting this yet.
187
+ if (!srcAddr) {
188
+ LLVM_DEBUG (llvm::dbgs () << " Temp has open_existential_addr use?! Can "
189
+ " not yet promote to value"
190
+ << *user);
191
+ return false ;
192
+ }
193
+
170
194
// We only support open existential addr if the access is immutable.
171
195
auto *oeai = cast<OpenExistentialAddrInst>(user);
172
196
if (oeai->getAccessKind () != OpenedExistentialAccess::Immutable) {
@@ -179,21 +203,29 @@ bool TempRValueOptPass::collectLoads(
179
203
}
180
204
case SILInstructionKind::StructElementAddrInst:
181
205
case SILInstructionKind::TupleElementAddrInst: {
206
+ auto *proj = cast<SingleValueInstruction>(user);
207
+
208
+ if (!srcAddr) {
209
+ LLVM_DEBUG (
210
+ llvm::dbgs ()
211
+ << " Temp has addr_projection use?! Can not yet promote to value"
212
+ << *user);
213
+ return false ;
214
+ }
215
+
182
216
// Transitively look through projections on stack addresses.
183
- auto proj = cast<SingleValueInstruction>(user);
184
217
for (auto *projUseOper : proj->getUses ()) {
185
218
auto *user = projUseOper->getUser ();
186
219
if (user->isTypeDependentOperand (*projUseOper))
187
220
continue ;
188
221
189
- if (!collectLoads (projUseOper, user, proj, srcObject , loadInsts))
222
+ if (!collectLoads (projUseOper, user, proj, srcAddr , loadInsts))
190
223
return false ;
191
224
}
192
225
return true ;
193
226
}
194
227
195
228
case SILInstructionKind::LoadInst:
196
- case SILInstructionKind::LoadBorrowInst:
197
229
// Loads are the end of the data flow chain. The users of the load can't
198
230
// access the temporary storage.
199
231
//
@@ -211,6 +243,15 @@ bool TempRValueOptPass::collectLoads(
211
243
loadInsts.insert (user);
212
244
return true ;
213
245
246
+ case SILInstructionKind::LoadBorrowInst:
247
+ // If we do not have a source addr, we must be trying to eliminate a
248
+ // store. Until we check that the source object is not destroyed within the
249
+ // given range, we need bail.
250
+ if (!srcAddr)
251
+ return false ;
252
+ loadInsts.insert (user);
253
+ return true ;
254
+
214
255
case SILInstructionKind::CopyAddrInst: {
215
256
// copy_addr which read from the temporary are like loads.
216
257
auto *copyFromTmp = cast<CopyAddrInst>(user);
@@ -474,6 +515,131 @@ bool TempRValueOptPass::tryOptimizeCopyIntoTemp(CopyAddrInst *copyInst) {
474
515
return true ;
475
516
}
476
517
518
+ std::pair<SILBasicBlock::iterator, bool >
519
+ TempRValueOptPass::tryOptimizeStoreIntoTemp (StoreInst *si) {
520
+ // If our store is an assign, bail.
521
+ if (si->getOwnershipQualifier () == StoreOwnershipQualifier::Assign)
522
+ return {std::next (si->getIterator ()), false };
523
+
524
+ auto *tempObj = dyn_cast<AllocStackInst>(si->getDest ());
525
+ if (!tempObj) {
526
+ return {std::next (si->getIterator ()), false };
527
+ }
528
+
529
+ // If our tempObj has a dynamic lifetime (meaning it is conditionally
530
+ // initialized, conditionally taken, etc), we can not convert its uses to SSA
531
+ // while eliminating it simply. So bail.
532
+ if (tempObj->hasDynamicLifetime ()) {
533
+ return {std::next (si->getIterator ()), false };
534
+ }
535
+
536
+ // Scan all uses of the temporary storage (tempObj) to verify they all refer
537
+ // to the value initialized by this copy. It is sufficient to check that the
538
+ // only users that modify memory are the copy_addr [initialization] and
539
+ // destroy_addr.
540
+ SmallPtrSet<SILInstruction *, 8 > loadInsts;
541
+ for (auto *useOper : tempObj->getUses ()) {
542
+ SILInstruction *user = useOper->getUser ();
543
+
544
+ if (user == si)
545
+ continue ;
546
+
547
+ // Destroys and deallocations are allowed to be in a different block.
548
+ if (isa<DestroyAddrInst>(user) || isa<DeallocStackInst>(user))
549
+ continue ;
550
+
551
+ // Same for load [take] on the top level temp object. SILGen always takes
552
+ // whole values from temporaries. If we have load [take] on projections from
553
+ // our base, we fail since those would be re-initializations.
554
+ if (auto *li = dyn_cast<LoadInst>(user)) {
555
+ if (li->getOwnershipQualifier () == LoadOwnershipQualifier::Take) {
556
+ continue ;
557
+ }
558
+ }
559
+
560
+ // We pass in SILValue() since we do not have a source address.
561
+ if (!collectLoads (useOper, user, tempObj, SILValue (), loadInsts))
562
+ return {std::next (si->getIterator ()), false };
563
+ }
564
+
565
+ // Since store is always a consuming operation, we do not need to worry about
566
+ // any lifetime constraints and can just replace all of the uses here. This
567
+ // contrasts with the copy_addr implementation where we need to consider the
568
+ // possibility that the source address is written to.
569
+ LLVM_DEBUG (llvm::dbgs () << " Success: replace temp" << *tempObj);
570
+
571
+ // Do a "replaceAllUses" by either deleting the users or replacing them with
572
+ // the appropriate operation on the source value.
573
+ SmallVector<SILInstruction *, 4 > toDelete;
574
+ for (auto *use : tempObj->getUses ()) {
575
+ // If our store is the user, just skip it.
576
+ if (use->getUser () == si) {
577
+ continue ;
578
+ }
579
+
580
+ SILInstruction *user = use->getUser ();
581
+ switch (user->getKind ()) {
582
+ case SILInstructionKind::DestroyAddrInst: {
583
+ SILBuilderWithScope builder (user);
584
+ builder.emitDestroyValueOperation (user->getLoc (), si->getSrc ());
585
+ toDelete.push_back (user);
586
+ break ;
587
+ }
588
+ case SILInstructionKind::DeallocStackInst:
589
+ toDelete.push_back (user);
590
+ break ;
591
+ case SILInstructionKind::CopyAddrInst: {
592
+ auto *cai = cast<CopyAddrInst>(user);
593
+ assert (cai->getSrc () == tempObj);
594
+ SILBuilderWithScope builder (user);
595
+ auto qualifier = cai->isInitializationOfDest ()
596
+ ? StoreOwnershipQualifier::Init
597
+ : StoreOwnershipQualifier::Assign;
598
+ SILValue src = si->getSrc ();
599
+ if (!cai->isTakeOfSrc ()) {
600
+ src = builder.emitCopyValueOperation (cai->getLoc (), src);
601
+ }
602
+ builder.emitStoreValueOperation (cai->getLoc (), src, cai->getDest (),
603
+ qualifier);
604
+ toDelete.push_back (cai);
605
+ break ;
606
+ }
607
+ case SILInstructionKind::LoadInst: {
608
+ // Since store is always forwarding, we know that we should have our own
609
+ // value here. So, we should be able to just RAUW any load [take] and
610
+ // insert a copy + RAUW for any load [copy].
611
+ auto *li = cast<LoadInst>(user);
612
+ SILValue srcObject = si->getSrc ();
613
+ if (li->getOwnershipQualifier () == LoadOwnershipQualifier::Copy) {
614
+ SILBuilderWithScope builder (li);
615
+ srcObject = builder.emitCopyValueOperation (li->getLoc (), srcObject);
616
+ }
617
+ li->replaceAllUsesWith (srcObject);
618
+ toDelete.push_back (li);
619
+ break ;
620
+ }
621
+
622
+ // ASSUMPTION: no operations that may be handled by this default clause can
623
+ // destroy tempObj. This includes operations that load the value from memory
624
+ // and release it.
625
+ default :
626
+ llvm::errs () << " Unhandled user: " << *user;
627
+ llvm_unreachable (" Unhandled case?!" );
628
+ break ;
629
+ }
630
+ }
631
+
632
+ while (!toDelete.empty ()) {
633
+ auto *inst = toDelete.pop_back_val ();
634
+ inst->dropAllReferences ();
635
+ inst->eraseFromParent ();
636
+ }
637
+ auto nextIter = std::next (si->getIterator ());
638
+ si->eraseFromParent ();
639
+ tempObj->eraseFromParent ();
640
+ return {nextIter, true };
641
+ }
642
+
477
643
// ===----------------------------------------------------------------------===//
478
644
// High Level Entrypoint
479
645
// ===----------------------------------------------------------------------===//
@@ -492,10 +658,8 @@ void TempRValueOptPass::run() {
492
658
// Increment the instruction iterator only after calling
493
659
// tryOptimizeCopyIntoTemp because the instruction after CopyInst might be
494
660
// deleted, but copyInst itself won't be deleted until later.
495
- for (auto ii = block.begin (); ii != block.end (); ++ii) {
496
- auto *copyInst = dyn_cast<CopyAddrInst>(&*ii);
497
-
498
- if (copyInst) {
661
+ for (auto ii = block.begin (); ii != block.end ();) {
662
+ if (auto *copyInst = dyn_cast<CopyAddrInst>(&*ii)) {
499
663
// In case of success, this may delete instructions, but not the
500
664
// CopyInst itself.
501
665
changed |= tryOptimizeCopyIntoTemp (copyInst);
@@ -507,9 +671,21 @@ void TempRValueOptPass::run() {
507
671
changed = true ;
508
672
deadCopies.push_back (copyInst);
509
673
}
674
+ ++ii;
675
+ continue ;
510
676
}
677
+
678
+ if (auto *si = dyn_cast<StoreInst>(&*ii)) {
679
+ bool madeSingleChange;
680
+ std::tie (ii, madeSingleChange) = tryOptimizeStoreIntoTemp (si);
681
+ changed |= madeSingleChange;
682
+ continue ;
683
+ }
684
+
685
+ ++ii;
511
686
}
512
687
}
688
+
513
689
// Delete the copies and any unused address operands.
514
690
// The same copy may have been added multiple times.
515
691
sortUnique (deadCopies);
0 commit comments