Skip to content

Commit 880db42

Browse files
committed
Fix TempRValueOpt with access markers.
- consistently operate on the address value of the variable via stripAccessMarkers. Never use address of the transient access which may not have sufficient lifetime. - Recognize begin_access [read] as a load. - delete dead access markers after deleting copies Fixes <rdar://59345115> Unnecessary copy_addr not eliminated with exclusivity checking
1 parent badc565 commit 880db42

File tree

2 files changed

+84
-29
lines changed

2 files changed

+84
-29
lines changed

lib/SILOptimizer/Transforms/TempRValueElimination.cpp

Lines changed: 58 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -18,13 +18,15 @@
1818

1919
#define DEBUG_TYPE "sil-temp-rvalue-opt"
2020
#include "swift/SIL/DebugUtils.h"
21+
#include "swift/SIL/MemAccessUtils.h"
2122
#include "swift/SIL/SILArgument.h"
2223
#include "swift/SIL/SILBuilder.h"
2324
#include "swift/SIL/SILVisitor.h"
2425
#include "swift/SILOptimizer/Analysis/AliasAnalysis.h"
2526
#include "swift/SILOptimizer/Analysis/DominanceAnalysis.h"
2627
#include "swift/SILOptimizer/Analysis/PostOrderAnalysis.h"
2728
#include "swift/SILOptimizer/Analysis/RCIdentityAnalysis.h"
29+
#include "swift/SILOptimizer/Analysis/SimplifyInstruction.h"
2830
#include "swift/SILOptimizer/PassManager/Passes.h"
2931
#include "swift/SILOptimizer/PassManager/Transforms.h"
3032
#include "swift/SILOptimizer/Utils/CFGOptUtils.h"
@@ -50,7 +52,7 @@ namespace {
5052
///
5153
/// %temp = alloc_stack $T
5254
/// copy_addr %src to [initialization] %temp : $*T
53-
/// // no writes to %src and %temp
55+
/// // no writes to %src or %temp
5456
/// destroy_addr %temp : $*T
5557
/// dealloc_stack %temp : $*T
5658
///
@@ -67,7 +69,7 @@ class TempRValueOptPass : public SILFunctionTransform {
6769
SmallPtrSetImpl<SILInstruction *> &loadInsts);
6870

6971
bool
70-
checkNoSourceModification(CopyAddrInst *copyInst,
72+
checkNoSourceModification(CopyAddrInst *copyInst, SILValue copySrc,
7173
const SmallPtrSetImpl<SILInstruction *> &useInsts);
7274

7375
bool
@@ -118,6 +120,9 @@ bool TempRValueOptPass::collectLoads(
118120
<< " Temp use may write/destroy its source" << *user);
119121
return false;
120122

123+
case SILInstructionKind::BeginAccessInst:
124+
return cast<BeginAccessInst>(user)->getAccessKind() == SILAccessKind::Read;
125+
121126
case SILInstructionKind::ApplyInst:
122127
case SILInstructionKind::TryApplyInst: {
123128
ApplySite apply(user);
@@ -227,7 +232,8 @@ bool TempRValueOptPass::collectLoads(
227232
/// of the temporary and look for the last use, which effectively ends the
228233
/// lifetime.
229234
bool TempRValueOptPass::checkNoSourceModification(
230-
CopyAddrInst *copyInst, const SmallPtrSetImpl<SILInstruction *> &useInsts) {
235+
CopyAddrInst *copyInst, SILValue copySrc,
236+
const SmallPtrSetImpl<SILInstruction *> &useInsts) {
231237
unsigned numLoadsFound = 0;
232238
auto iter = std::next(copyInst->getIterator());
233239
// We already checked that the useful lifetime of the temporary ends in
@@ -244,7 +250,7 @@ bool TempRValueOptPass::checkNoSourceModification(
244250
if (numLoadsFound == useInsts.size())
245251
return true;
246252

247-
if (aa->mayWriteToMemory(inst, copyInst->getSrc())) {
253+
if (aa->mayWriteToMemory(inst, copySrc)) {
248254
LLVM_DEBUG(llvm::dbgs() << " Source modified by" << *iter);
249255
return false;
250256
}
@@ -336,8 +342,15 @@ bool TempRValueOptPass::tryOptimizeCopyIntoTemp(CopyAddrInst *copyInst) {
336342
if (!tempObj)
337343
return false;
338344

339-
assert(tempObj != copyInst->getSrc() &&
340-
"can't initialize temporary with itself");
345+
// The copy's source address must not be a scoped instruction, like
346+
// begin_borrow. When the temporary object is eliminated, it's uses are
347+
// replaced with the copy's source. Therefore, the source address must be
348+
// valid at least until the next instruction that may write to or destroy the
349+
// source. End-of-scope markers, such as end_borrow, do not write to or
350+
// destroy memory, so scoped addresses are not valid replacements.
351+
SILValue copySrc = stripAccessMarkers(copyInst->getSrc());
352+
353+
assert(tempObj != copySrc && "can't initialize temporary with itself");
341354

342355
// Scan all uses of the temporary storage (tempObj) to verify they all refer
343356
// to the value initialized by this copy. It is sufficient to check that the
@@ -363,12 +376,12 @@ bool TempRValueOptPass::tryOptimizeCopyIntoTemp(CopyAddrInst *copyInst) {
363376
}
364377
}
365378

366-
if (!collectLoads(useOper, user, tempObj, copyInst->getSrc(), loadInsts))
379+
if (!collectLoads(useOper, user, tempObj, copySrc, loadInsts))
367380
return false;
368381
}
369382

370383
// Check if the source is modified within the lifetime of the temporary.
371-
if (!checkNoSourceModification(copyInst, loadInsts))
384+
if (!checkNoSourceModification(copyInst, copySrc, loadInsts))
372385
return false;
373386

374387
ValueLifetimeAnalysis::Frontier tempAddressFrontier;
@@ -391,7 +404,7 @@ bool TempRValueOptPass::tryOptimizeCopyIntoTemp(CopyAddrInst *copyInst) {
391404
switch (user->getKind()) {
392405
case SILInstructionKind::DestroyAddrInst:
393406
if (copyInst->isTakeOfSrc()) {
394-
use->set(copyInst->getSrc());
407+
use->set(copySrc);
395408
} else {
396409
user->dropAllReferences();
397410
toDelete.push_back(user);
@@ -408,7 +421,7 @@ bool TempRValueOptPass::tryOptimizeCopyIntoTemp(CopyAddrInst *copyInst) {
408421
if (cai->isTakeOfSrc() && !copyInst->isTakeOfSrc())
409422
cai->setIsTakeOfSrc(IsNotTake);
410423
}
411-
use->set(copyInst->getSrc());
424+
use->set(copySrc);
412425
break;
413426
}
414427
case SILInstructionKind::LoadInst: {
@@ -447,9 +460,9 @@ bool TempRValueOptPass::tryOptimizeCopyIntoTemp(CopyAddrInst *copyInst) {
447460

448461
// ASSUMPTION: no operations that may be handled by this default clause can
449462
// destroy tempObj. This includes operations that load the value from memory
450-
// and release it.
463+
// and release it or cast the address before destroying it.
451464
default:
452-
use->set(copyInst->getSrc());
465+
use->set(copySrc);
453466
break;
454467
}
455468
}
@@ -474,33 +487,50 @@ void TempRValueOptPass::run() {
474487
bool changed = false;
475488

476489
// Find all copy_addr instructions.
490+
llvm::SmallVector<CopyAddrInst *, 8> deadCopies;
477491
for (auto &block : *getFunction()) {
478-
auto ii = block.begin();
479-
while (ii != block.end()) {
492+
// Increment the instruction iterator only after calling
493+
// tryOptimizeCopyIntoTemp because the instruction after CopyInst might be
494+
// deleted, but copyInst itself won't be deleted until later.
495+
for (auto ii = block.begin(); ii != block.end(); ++ii) {
480496
auto *copyInst = dyn_cast<CopyAddrInst>(&*ii);
481497

482498
if (copyInst) {
483499
// In case of success, this may delete instructions, but not the
484500
// CopyInst itself.
485501
changed |= tryOptimizeCopyIntoTemp(copyInst);
502+
// Remove identity copies which either directly result from successfully
503+
// calling tryOptimizeCopyIntoTemp or was created by an earlier
504+
// iteration, where another copy_addr copied the temporary back to the
505+
// source location.
506+
if (stripAccessMarkers(copyInst->getSrc()) == copyInst->getDest()) {
507+
changed = true;
508+
deadCopies.push_back(copyInst);
509+
}
486510
}
487-
488-
// Increment the instruction iterator here. We can't do it at the begin of
489-
// the loop because the instruction after CopyInst might be deleted in
490-
// in tryOptimizeCopyIntoTemp. We can't do it at the end of the loop
491-
// because the CopyInst might be deleted in the following code.
492-
++ii;
493-
494-
// Remove identity copies which are a result of this optimization.
495-
if (copyInst && copyInst->getSrc() == copyInst->getDest()) {
496-
// This is either the CopyInst which just got optimized or it is a
497-
// follow-up from an earlier iteration, where another copy_addr copied
498-
// the temporary back to the source location.
499-
copyInst->eraseFromParent();
511+
}
512+
}
513+
// Delete the copies and any unused address operands.
514+
// The same copy may have been added multiple times.
515+
sortUnique(deadCopies);
516+
for (auto *deadCopy : deadCopies) {
517+
assert(changed);
518+
auto *srcInst = deadCopy->getSrc()->getDefiningInstruction();
519+
deadCopy->eraseFromParent();
520+
// Simplify any access scope markers that were only used by the dead
521+
// copy_addr and other potentially unused addresses.
522+
if (srcInst) {
523+
if (SILValue result = simplifyInstruction(srcInst)) {
524+
replaceAllSimplifiedUsesAndErase(
525+
srcInst, result, [](SILInstruction *instToKill) {
526+
// SimplifyInstruction is not in the business of removing
527+
// copy_addr. If it were, then we would need to update deadCopies.
528+
assert(!isa<CopyAddrInst>(instToKill));
529+
instToKill->eraseFromParent();
530+
});
500531
}
501532
}
502533
}
503-
504534
if (changed) {
505535
invalidateAnalysis(SILAnalysis::InvalidationKind::Instructions);
506536
}

test/SILOptimizer/temp_rvalue_opt_ossa.sil

Lines changed: 26 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -733,4 +733,29 @@ bb3(%obj3 : @owned $GS<Builtin.NativeObject>):
733733
dealloc_stack %stk : $*GS<Builtin.NativeObject>
734734
apply %f(%obj3) : $@convention(thin) (@guaranteed GS<Builtin.NativeObject>) -> ()
735735
return %obj3 : $GS<Builtin.NativeObject>
736-
}
736+
}
737+
738+
// Test copy elimination with access markers on both the source and dest.
739+
//
740+
// CHECK-LABEL: sil [ossa] @takeWithAccess : $@convention(thin) (@in Klass) -> () {
741+
// CHECK: bb0(%0 : $*Klass):
742+
// CHECK: [[ACCESS:%.*]] = begin_access [read] [static] %0 : $*Klass
743+
// CHECK: apply %{{.*}}([[ACCESS]]) : $@convention(thin) (@in_guaranteed Klass) -> ()
744+
// CHECK: end_access [[ACCESS]] : $*Klass
745+
// CHECK: destroy_addr %0 : $*Klass
746+
// CHECK-LABEL: } // end sil function 'takeWithAccess'
747+
sil [ossa] @takeWithAccess : $@convention(thin) (@in Klass) -> () {
748+
bb0(%0 : $*Klass):
749+
%stack = alloc_stack $Klass
750+
%access = begin_access [read] [static] %0 : $*Klass
751+
copy_addr [take] %access to [initialization] %stack : $*Klass
752+
end_access %access : $*Klass
753+
%f = function_ref @inguaranteed_user_without_result : $@convention(thin) (@in_guaranteed Klass) -> ()
754+
%access2 = begin_access [read] [static] %stack : $*Klass
755+
%call = apply %f(%access2) : $@convention(thin) (@in_guaranteed Klass) -> ()
756+
end_access %access2 : $*Klass
757+
destroy_addr %stack : $*Klass
758+
dealloc_stack %stack : $*Klass
759+
%9999 = tuple()
760+
return %9999 : $()
761+
}

0 commit comments

Comments
 (0)