@@ -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(
342355bool 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}
0 commit comments