Skip to content

Commit d428d76

Browse files
authored
Merge pull request swiftlang#30225 from gottesmm/pr-3ca0f543e9c626572476d6c0ffb789ef45166e54
[temp-rvalue] Add support for eliminating simple temp rvalues inited with a store when in ossa.
2 parents 8b6f66b + 0e19ae4 commit d428d76

File tree

2 files changed

+520
-28
lines changed

2 files changed

+520
-28
lines changed

lib/SILOptimizer/Transforms/TempRValueElimination.cpp

Lines changed: 193 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -77,6 +77,8 @@ class TempRValueOptPass : public SILFunctionTransform {
7777
ValueLifetimeAnalysis::Frontier &tempAddressFrontier);
7878

7979
bool tryOptimizeCopyIntoTemp(CopyAddrInst *copyInst);
80+
std::pair<SILBasicBlock::iterator, bool>
81+
tryOptimizeStoreIntoTemp(StoreInst *si);
8082

8183
void run() override;
8284
};
@@ -98,7 +100,7 @@ class TempRValueOptPass : public SILFunctionTransform {
98100
/// that may write to memory at \p address.
99101
bool TempRValueOptPass::collectLoads(
100102
Operand *userOp, SILInstruction *user, SingleValueInstruction *address,
101-
SILValue srcObject, SmallPtrSetImpl<SILInstruction *> &loadInsts) {
103+
SILValue srcAddr, SmallPtrSetImpl<SILInstruction *> &loadInsts) {
102104
// All normal uses (loads) must be in the initialization block.
103105
// (The destroy and dealloc are commonly in a different block though.)
104106
if (user->getParent() != address->getParent())
@@ -136,8 +138,20 @@ bool TempRValueOptPass::collectLoads(
136138
return false;
137139
}
138140

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+
139153
// 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.
141155
//
142156
// When a use of the temporary is an apply, then we need to prove that the
143157
// function called by the apply cannot modify the temporary's source
@@ -150,23 +164,33 @@ bool TempRValueOptPass::collectLoads(
150164
// alias with `src` because the `src` value must be initialized at the point
151165
// of the call. Hence, it is sufficient to check specifically for another
152166
// @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+
}
160176
}
177+
++calleeArgIdx;
161178
}
162-
++calleeArgIdx;
163179
}
164180

165181
// Everything is okay with the function call. Register it as a "load".
166182
loadInsts.insert(user);
167183
return true;
168184
}
169185
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+
170194
// We only support open existential addr if the access is immutable.
171195
auto *oeai = cast<OpenExistentialAddrInst>(user);
172196
if (oeai->getAccessKind() != OpenedExistentialAccess::Immutable) {
@@ -179,21 +203,29 @@ bool TempRValueOptPass::collectLoads(
179203
}
180204
case SILInstructionKind::StructElementAddrInst:
181205
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+
182216
// Transitively look through projections on stack addresses.
183-
auto proj = cast<SingleValueInstruction>(user);
184217
for (auto *projUseOper : proj->getUses()) {
185218
auto *user = projUseOper->getUser();
186219
if (user->isTypeDependentOperand(*projUseOper))
187220
continue;
188221

189-
if (!collectLoads(projUseOper, user, proj, srcObject, loadInsts))
222+
if (!collectLoads(projUseOper, user, proj, srcAddr, loadInsts))
190223
return false;
191224
}
192225
return true;
193226
}
194227

195228
case SILInstructionKind::LoadInst:
196-
case SILInstructionKind::LoadBorrowInst:
197229
// Loads are the end of the data flow chain. The users of the load can't
198230
// access the temporary storage.
199231
//
@@ -211,6 +243,15 @@ bool TempRValueOptPass::collectLoads(
211243
loadInsts.insert(user);
212244
return true;
213245

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+
214255
case SILInstructionKind::CopyAddrInst: {
215256
// copy_addr which read from the temporary are like loads.
216257
auto *copyFromTmp = cast<CopyAddrInst>(user);
@@ -474,6 +515,131 @@ bool TempRValueOptPass::tryOptimizeCopyIntoTemp(CopyAddrInst *copyInst) {
474515
return true;
475516
}
476517

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+
477643
//===----------------------------------------------------------------------===//
478644
// High Level Entrypoint
479645
//===----------------------------------------------------------------------===//
@@ -492,10 +658,8 @@ void TempRValueOptPass::run() {
492658
// Increment the instruction iterator only after calling
493659
// tryOptimizeCopyIntoTemp because the instruction after CopyInst might be
494660
// 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)) {
499663
// In case of success, this may delete instructions, but not the
500664
// CopyInst itself.
501665
changed |= tryOptimizeCopyIntoTemp(copyInst);
@@ -507,9 +671,21 @@ void TempRValueOptPass::run() {
507671
changed = true;
508672
deadCopies.push_back(copyInst);
509673
}
674+
++ii;
675+
continue;
510676
}
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;
511686
}
512687
}
688+
513689
// Delete the copies and any unused address operands.
514690
// The same copy may have been added multiple times.
515691
sortUnique(deadCopies);

0 commit comments

Comments
 (0)