Skip to content

Commit 4557f15

Browse files
committed
TempRValueOpt: fix the placement of destroy_addr in case of a copy_addr [take]
Instead of reusing the existing destroy_addr (or load/copy_addr [take]) of the temporary, insert a new destroy_addr - at the correct location. This fixes a memory lifetime failure in case the copy-source is modified (actually: re-initialized) after the last use of the temporary: E.g. (before copy elimination): copy_addr [take] %src to [initialization] %temp %x = load %temp // last use of %temp store %y to [init] %src destroy_addr %temp The correct location for the destroy_addr is after the last use (after copy elimination): %x = load %src destroy_addr %src store %y to [init] %src rdar://problem/69757314
1 parent 9a10ec7 commit 4557f15

File tree

3 files changed

+176
-84
lines changed

3 files changed

+176
-84
lines changed

lib/SILOptimizer/Transforms/TempRValueElimination.cpp

Lines changed: 85 additions & 79 deletions
Original file line numberDiff line numberDiff line change
@@ -81,9 +81,8 @@ class TempRValueOptPass : public SILFunctionTransform {
8181
CopyAddrInst *originalCopy,
8282
SmallPtrSetImpl<SILInstruction *> &loadInsts);
8383

84-
bool
85-
checkNoSourceModification(CopyAddrInst *copyInst, SILValue copySrc,
86-
const SmallPtrSetImpl<SILInstruction *> &useInsts);
84+
SILInstruction *getLastUseWhileSourceIsNotModified(
85+
CopyAddrInst *copyInst, const SmallPtrSetImpl<SILInstruction *> &useInsts);
8786

8887
bool
8988
checkTempObjectDestroy(AllocStackInst *tempObj, CopyAddrInst *copyInst,
@@ -157,11 +156,16 @@ collectLoads(Operand *addressUse, CopyAddrInst *originalCopy,
157156
auto *beginAccess = cast<BeginAccessInst>(user);
158157
if (beginAccess->getAccessKind() != SILAccessKind::Read)
159158
return false;
159+
160160
// We don't have to recursively call collectLoads for the beginAccess
161161
// result, because a SILAccessKind::Read already guarantees that there are
162162
// no writes to the beginAccess result address (or any projection from it).
163163
// But we have to register the end-accesses as loads to correctly mark the
164-
// end-of-lifetime of the temporary object.
164+
// end-of-lifetime of the tempObj.
165+
//
166+
// %addr = begin_access [read]
167+
// ... // there can be no writes to %addr here
168+
// end_acess %addr // <- This is where the use actually ends.
165169
for (Operand *accessUse : beginAccess->getUses()) {
166170
if (auto *endAccess = dyn_cast<EndAccessInst>(accessUse->getUser())) {
167171
if (endAccess->getParent() != block)
@@ -284,20 +288,29 @@ collectLoads(Operand *addressUse, CopyAddrInst *originalCopy,
284288
}
285289
}
286290

287-
/// Checks if the copy's source can be modified within the temporary's lifetime.
291+
/// Checks if the source of \p copyInst is not modified within the temporary's
292+
/// lifetime, i.e. is not modified before the last use of \p useInsts.
293+
///
294+
/// If there are no source modifications with the lifetime, returns the last
295+
/// user (or copyInst if there are no uses at all).
296+
/// Otherwise, returns a nullptr.
288297
///
289298
/// Unfortunately, we cannot simply use the destroy points as the lifetime end,
290299
/// because they can be in a different basic block (that's what SILGen
291300
/// generates). Instead we guarantee that all normal uses are within the block
292301
/// of the temporary and look for the last use, which effectively ends the
293302
/// lifetime.
294-
bool TempRValueOptPass::checkNoSourceModification(
295-
CopyAddrInst *copyInst, SILValue copySrc,
296-
const SmallPtrSetImpl<SILInstruction *> &useInsts) {
303+
SILInstruction *TempRValueOptPass::getLastUseWhileSourceIsNotModified(
304+
CopyAddrInst *copyInst, const SmallPtrSetImpl<SILInstruction *> &useInsts) {
305+
if (useInsts.empty())
306+
return copyInst;
297307
unsigned numLoadsFound = 0;
298-
auto iter = std::next(copyInst->getIterator());
308+
SILValue copySrc = copyInst->getSrc();
309+
299310
// We already checked that the useful lifetime of the temporary ends in
300-
// the initialization block.
311+
// the initialization block. Iterate over the instructions of the block,
312+
// starting at copyInst, until we get to the last user.
313+
auto iter = std::next(copyInst->getIterator());
301314
auto iterEnd = copyInst->getParent()->end();
302315
for (; iter != iterEnd; ++iter) {
303316
SILInstruction *inst = &*iter;
@@ -314,19 +327,19 @@ bool TempRValueOptPass::checkNoSourceModification(
314327
// Function calls are an exception: in a called function a potential
315328
// modification of copySrc could occur _before_ the read of the temporary.
316329
if (FullApplySite::isa(inst) && aa->mayWriteToMemory(inst, copySrc))
317-
return false;
330+
return nullptr;
318331

319-
return true;
332+
return inst;
320333
}
321334

322335
if (aa->mayWriteToMemory(inst, copySrc)) {
323336
LLVM_DEBUG(llvm::dbgs() << " Source modified by" << *iter);
324-
return false;
337+
return nullptr;
325338
}
326339
}
327340
// For some reason, not all normal uses have been seen between the copy and
328341
// the end of the initialization block. We should never reach here.
329-
return false;
342+
return nullptr;
330343
}
331344

332345
/// Return true if the \p tempObj, which is initialized by \p copyInst, is
@@ -342,11 +355,6 @@ bool TempRValueOptPass::checkNoSourceModification(
342355
bool TempRValueOptPass::checkTempObjectDestroy(
343356
AllocStackInst *tempObj, CopyAddrInst *copyInst,
344357
ValueLifetimeAnalysis::Frontier &tempAddressFrontier) {
345-
// If the original copy was a take, then replacing all uses cannot affect
346-
// the lifetime.
347-
if (copyInst->isTakeOfSrc())
348-
return true;
349-
350358
// ValueLifetimeAnalysis is not normally used for address types. It does not
351359
// reason about the lifetime of the in-memory object. However the utility can
352360
// be abused here to check that the address is directly destroyed on all
@@ -385,15 +393,8 @@ bool TempRValueOptPass::checkTempObjectDestroy(
385393
if (isa<DestroyAddrInst>(lastUser))
386394
continue;
387395

388-
if (auto *li = dyn_cast<LoadInst>(lastUser)) {
389-
if (li->getOwnershipQualifier() == LoadOwnershipQualifier::Take) {
390-
continue;
391-
}
392-
}
393-
394396
if (auto *cai = dyn_cast<CopyAddrInst>(lastUser)) {
395397
assert(cai->getSrc() == tempObj && "collectLoads checks for writes");
396-
assert(!copyInst->isTakeOfSrc() && "checked above");
397398
if (cai->isTakeOfSrc())
398399
continue;
399400
}
@@ -411,16 +412,22 @@ bool TempRValueOptPass::tryOptimizeCopyIntoTemp(CopyAddrInst *copyInst) {
411412
if (!tempObj)
412413
return false;
413414

415+
bool isOSSA = copyInst->getFunction()->hasOwnership();
416+
414417
// The copy's source address must not be a scoped instruction, like
415418
// begin_borrow. When the temporary object is eliminated, it's uses are
416419
// replaced with the copy's source. Therefore, the source address must be
417420
// valid at least until the next instruction that may write to or destroy the
418421
// source. End-of-scope markers, such as end_borrow, do not write to or
419422
// destroy memory, so scoped addresses are not valid replacements.
420423
SILValue copySrc = stripAccessMarkers(copyInst->getSrc());
421-
422424
assert(tempObj != copySrc && "can't initialize temporary with itself");
423425

426+
// If the source of the copyInst is taken, we must insert a compensating
427+
// destroy_addr. This must be done at the right spot: after the last use
428+
// tempObj, but before any (potential) re-initialization of the source.
429+
bool needToInsertDestroy = copyInst->isTakeOfSrc();
430+
424431
// Scan all uses of the temporary storage (tempObj) to verify they all refer
425432
// to the value initialized by this copy. It is sufficient to check that the
426433
// only users that modify memory are the copy_addr [initialization] and
@@ -432,16 +439,44 @@ bool TempRValueOptPass::tryOptimizeCopyIntoTemp(CopyAddrInst *copyInst) {
432439
if (user == copyInst)
433440
continue;
434441

435-
// Destroys and deallocations are allowed to be in a different block.
436-
if (isa<DestroyAddrInst>(user) || isa<DeallocStackInst>(user))
442+
// Deallocations are allowed to be in a different block.
443+
if (isa<DeallocStackInst>(user))
444+
continue;
445+
446+
// Also, destroys are allowed to be in a different block.
447+
if (isa<DestroyAddrInst>(user)) {
448+
if (!isOSSA && needToInsertDestroy) {
449+
// In non-OSSA mode, for the purpose of inserting the destroy of
450+
// copySrc, we have to be conservative and assume that the lifetime of
451+
// tempObj goes beyond it's last use - until the final destroy_addr.
452+
// Otherwise we would risk of inserting the destroy too early.
453+
// So we just treat the destroy_addr as any other use of tempObj.
454+
if (user->getParent() != copyInst->getParent())
455+
return false;
456+
loadInsts.insert(user);
457+
}
437458
continue;
459+
}
438460

439461
if (!collectLoads(useOper, copyInst, loadInsts))
440462
return false;
441463
}
442464

443465
// Check if the source is modified within the lifetime of the temporary.
444-
if (!checkNoSourceModification(copyInst, copySrc, loadInsts))
466+
SILInstruction *lastLoadInst = getLastUseWhileSourceIsNotModified(copyInst,
467+
loadInsts);
468+
if (!lastLoadInst)
469+
return false;
470+
471+
// We cannot insert the destroy of copySrc after lastLoadInst if copySrc is
472+
// re-initialized by exactly this instruction.
473+
// This is a corner case, but can happen if lastLoadInst is a copy_addr.
474+
// Example:
475+
// copy_addr [take] %copySrc to [initialization] %tempObj // copyInst
476+
// copy_addr [take] %tempObj to [initialization] %copySrc // lastLoadInst
477+
if (needToInsertDestroy && lastLoadInst != copyInst &&
478+
!isa<DestroyAddrInst>(lastLoadInst) &&
479+
aa->mayWriteToMemory(lastLoadInst, copySrc))
445480
return false;
446481

447482
ValueLifetimeAnalysis::Frontier tempAddressFrontier;
@@ -450,71 +485,45 @@ bool TempRValueOptPass::tryOptimizeCopyIntoTemp(CopyAddrInst *copyInst) {
450485

451486
LLVM_DEBUG(llvm::dbgs() << " Success: replace temp" << *tempObj);
452487

453-
// Do a "replaceAllUses" by either deleting the users or replacing them with
454-
// the source address. Note: we must not delete the original copyInst because
455-
// it would crash the instruction iteration in run(). Instead the copyInst
456-
// gets identical Src and Dest operands.
488+
if (needToInsertDestroy) {
489+
// Compensate the [take] of the original copyInst.
490+
SILBuilderWithScope::insertAfter(lastLoadInst, [&] (SILBuilder &builder) {
491+
builder.createDestroyAddr(builder.getInsertionPoint()->getLoc(), copySrc);
492+
});
493+
}
494+
495+
// * Replace all uses of the tempObj with the copySrc.
457496
//
458-
// NOTE: We delete instructions at the end to allow us to use
459-
// tempAddressFrontier to insert compensating destroys for load [take].
460-
SmallVector<SILInstruction *, 4> toDelete;
497+
// * Delete the destroy(s) of tempObj (to compensate the removal of the
498+
// original copyInst): either by erasing the destroy_addr or by converting
499+
// load/copy_addr [take] into copying instructions.
500+
//
501+
// Note: we must not delete the original copyInst because it would crash the
502+
// instruction iteration in run(). Instead the copyInst gets identical Src and
503+
// Dest operands.
461504
while (!tempObj->use_empty()) {
462505
Operand *use = *tempObj->use_begin();
463506
SILInstruction *user = use->getUser();
464507
switch (user->getKind()) {
465508
case SILInstructionKind::DestroyAddrInst:
466-
if (copyInst->isTakeOfSrc()) {
467-
use->set(copySrc);
468-
} else {
469-
user->dropAllReferences();
470-
toDelete.push_back(user);
471-
}
472-
break;
473509
case SILInstructionKind::DeallocStackInst:
474-
user->dropAllReferences();
475-
toDelete.push_back(user);
510+
user->eraseFromParent();
476511
break;
477512
case SILInstructionKind::CopyAddrInst: {
478513
auto *cai = cast<CopyAddrInst>(user);
479514
if (cai != copyInst) {
480515
assert(cai->getSrc() == tempObj);
481-
if (cai->isTakeOfSrc() && !copyInst->isTakeOfSrc())
516+
if (cai->isTakeOfSrc())
482517
cai->setIsTakeOfSrc(IsNotTake);
483518
}
484519
use->set(copySrc);
485520
break;
486521
}
487522
case SILInstructionKind::LoadInst: {
488-
// If we do not have a load [take] or we have a load [take] and our
489-
// copy_addr takes the source, just do the normal thing of setting the
490-
// load to use the copyInst's source.
491523
auto *li = cast<LoadInst>(user);
492-
if (li->getOwnershipQualifier() != LoadOwnershipQualifier::Take ||
493-
copyInst->isTakeOfSrc()) {
494-
use->set(copyInst->getSrc());
495-
break;
496-
}
497-
498-
// Otherwise, since copy_addr is not taking src, we need to ensure that we
499-
// insert a copy of our value. We do that by creating a load [copy] at the
500-
// copy_addr inst and RAUWing the load [take] with that. We then insert
501-
// destroy_value for the load [copy] at all points where we had destroys
502-
// that are not the specific take that we were optimizing.
503-
SILBuilderWithScope builder(copyInst);
504-
SILValue newLoad = builder.emitLoadValueOperation(
505-
copyInst->getLoc(), copyInst->getSrc(), LoadOwnershipQualifier::Copy);
506-
for (auto *inst : tempAddressFrontier) {
507-
assert(inst->getIterator() != inst->getParent()->begin() &&
508-
"Should have caught this when checking destructor");
509-
auto prevInst = std::prev(inst->getIterator());
510-
if (&*prevInst == li)
511-
continue;
512-
SILBuilderWithScope builder(prevInst);
513-
builder.emitDestroyValueOperation(prevInst->getLoc(), newLoad);
514-
}
515-
li->replaceAllUsesWith(newLoad);
516-
li->dropAllReferences();
517-
toDelete.push_back(li);
524+
if (li->getOwnershipQualifier() == LoadOwnershipQualifier::Take)
525+
li->setOwnershipQualifier(LoadOwnershipQualifier::Copy);
526+
use->set(copyInst->getSrc());
518527
break;
519528
}
520529

@@ -527,9 +536,6 @@ bool TempRValueOptPass::tryOptimizeCopyIntoTemp(CopyAddrInst *copyInst) {
527536
}
528537
}
529538

530-
while (!toDelete.empty()) {
531-
toDelete.pop_back_val()->eraseFromParent();
532-
}
533539
tempObj->eraseFromParent();
534540
return true;
535541
}

test/SILOptimizer/temp_rvalue_opt.sil

Lines changed: 25 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ struct Str {
2525

2626
sil @unknown : $@convention(thin) () -> ()
2727
sil @load_string : $@convention(thin) (@in_guaranteed String) -> String
28+
sil @guaranteed_user : $@convention(thin) (@guaranteed Klass) -> ()
2829

2930
sil @inguaranteed_user_without_result : $@convention(thin) (@in_guaranteed Klass) -> () {
3031
bb0(%0 : $*Klass):
@@ -456,6 +457,25 @@ bb3:
456457
return %9 : $()
457458
}
458459

460+
// CHECK-LABEL: sil @lifetime_beyond_last_use :
461+
// CHECK-NOT: copy_addr
462+
// CHECK: [[V:%[0-9]+]] = load %0
463+
// CHECK: apply {{%[0-9]+}}([[V]])
464+
// CHECK: destroy_addr %0
465+
// CHECK: } // end sil function 'lifetime_beyond_last_use'
466+
sil @lifetime_beyond_last_use : $@convention(thin) (@in Klass) -> () {
467+
bb0(%0 : $*Klass):
468+
%2 = alloc_stack $Klass
469+
copy_addr [take] %0 to [initialization] %2 : $*Klass
470+
%4 = load %2 : $*Klass
471+
%5 = function_ref @guaranteed_user : $@convention(thin) (@guaranteed Klass) -> ()
472+
%6 = apply %5(%4) : $@convention(thin) (@guaranteed Klass) -> ()
473+
destroy_addr %2 : $*Klass
474+
dealloc_stack %2 : $*Klass
475+
%9 = tuple ()
476+
return %9 : $()
477+
}
478+
459479
// Make sure that we can eliminate temporaries passed via a temporary rvalue to
460480
// an @in_guaranteed function.
461481
//
@@ -683,12 +703,13 @@ bb0(%0 : $*Builtin.NativeObject):
683703
return %v : $()
684704
}
685705

686-
// Remove a copy that is released via a load as long as it was a copy [take].
687706
// CHECK-LABEL: sil @takeWithLoadRelease : $@convention(thin) (@in Builtin.NativeObject) -> () {
688707
// CHECK: bb0(%0 : $*Builtin.NativeObject):
689-
// CHECK: [[V:%.*]] = load %0 : $*Builtin.NativeObject
690-
// CHECK: apply %{{.*}}([[V]]) : $@convention(thin) (@guaranteed Builtin.NativeObject) -> ()
691-
// CHECK: release_value [[V]] : $Builtin.NativeObject
708+
// CHECK: [[STK:%.*]] = alloc_stack $Builtin.NativeObject
709+
// CHECK: copy_addr [take] %0 to [initialization] [[STK]] : $*Builtin.NativeObject
710+
// CHECK: [[VAL:%.*]] = load [[STK]] : $*Builtin.NativeObject
711+
// CHECK: apply %{{.*}}([[VAL]]) : $@convention(thin) (@guaranteed Builtin.NativeObject) -> ()
712+
// CHECK: release_value [[VAL]] : $Builtin.NativeObject
692713
// CHECK-LABEL: } // end sil function 'takeWithLoadRelease'
693714
sil @takeWithLoadRelease : $@convention(thin) (@in Builtin.NativeObject) -> () {
694715
bb0(%0 : $*Builtin.NativeObject):

0 commit comments

Comments
 (0)