@@ -81,9 +81,8 @@ class TempRValueOptPass : public SILFunctionTransform {
81
81
CopyAddrInst *originalCopy,
82
82
SmallPtrSetImpl<SILInstruction *> &loadInsts);
83
83
84
- bool
85
- checkNoSourceModification (CopyAddrInst *copyInst, SILValue copySrc,
86
- const SmallPtrSetImpl<SILInstruction *> &useInsts);
84
+ SILInstruction *getLastUseWhileSourceIsNotModified (
85
+ CopyAddrInst *copyInst, const SmallPtrSetImpl<SILInstruction *> &useInsts);
87
86
88
87
bool
89
88
checkTempObjectDestroy (AllocStackInst *tempObj, CopyAddrInst *copyInst,
@@ -157,11 +156,16 @@ collectLoads(Operand *addressUse, CopyAddrInst *originalCopy,
157
156
auto *beginAccess = cast<BeginAccessInst>(user);
158
157
if (beginAccess->getAccessKind () != SILAccessKind::Read)
159
158
return false ;
159
+
160
160
// We don't have to recursively call collectLoads for the beginAccess
161
161
// result, because a SILAccessKind::Read already guarantees that there are
162
162
// no writes to the beginAccess result address (or any projection from it).
163
163
// 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.
165
169
for (Operand *accessUse : beginAccess->getUses ()) {
166
170
if (auto *endAccess = dyn_cast<EndAccessInst>(accessUse->getUser ())) {
167
171
if (endAccess->getParent () != block)
@@ -284,20 +288,29 @@ collectLoads(Operand *addressUse, CopyAddrInst *originalCopy,
284
288
}
285
289
}
286
290
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.
288
297
// /
289
298
// / Unfortunately, we cannot simply use the destroy points as the lifetime end,
290
299
// / because they can be in a different basic block (that's what SILGen
291
300
// / generates). Instead we guarantee that all normal uses are within the block
292
301
// / of the temporary and look for the last use, which effectively ends the
293
302
// / 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;
297
307
unsigned numLoadsFound = 0 ;
298
- auto iter = std::next (copyInst->getIterator ());
308
+ SILValue copySrc = copyInst->getSrc ();
309
+
299
310
// 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 ());
301
314
auto iterEnd = copyInst->getParent ()->end ();
302
315
for (; iter != iterEnd; ++iter) {
303
316
SILInstruction *inst = &*iter;
@@ -314,19 +327,19 @@ bool TempRValueOptPass::checkNoSourceModification(
314
327
// Function calls are an exception: in a called function a potential
315
328
// modification of copySrc could occur _before_ the read of the temporary.
316
329
if (FullApplySite::isa (inst) && aa->mayWriteToMemory (inst, copySrc))
317
- return false ;
330
+ return nullptr ;
318
331
319
- return true ;
332
+ return inst ;
320
333
}
321
334
322
335
if (aa->mayWriteToMemory (inst, copySrc)) {
323
336
LLVM_DEBUG (llvm::dbgs () << " Source modified by" << *iter);
324
- return false ;
337
+ return nullptr ;
325
338
}
326
339
}
327
340
// For some reason, not all normal uses have been seen between the copy and
328
341
// the end of the initialization block. We should never reach here.
329
- return false ;
342
+ return nullptr ;
330
343
}
331
344
332
345
// / Return true if the \p tempObj, which is initialized by \p copyInst, is
@@ -342,11 +355,6 @@ bool TempRValueOptPass::checkNoSourceModification(
342
355
bool TempRValueOptPass::checkTempObjectDestroy (
343
356
AllocStackInst *tempObj, CopyAddrInst *copyInst,
344
357
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
-
350
358
// ValueLifetimeAnalysis is not normally used for address types. It does not
351
359
// reason about the lifetime of the in-memory object. However the utility can
352
360
// be abused here to check that the address is directly destroyed on all
@@ -385,15 +393,8 @@ bool TempRValueOptPass::checkTempObjectDestroy(
385
393
if (isa<DestroyAddrInst>(lastUser))
386
394
continue ;
387
395
388
- if (auto *li = dyn_cast<LoadInst>(lastUser)) {
389
- if (li->getOwnershipQualifier () == LoadOwnershipQualifier::Take) {
390
- continue ;
391
- }
392
- }
393
-
394
396
if (auto *cai = dyn_cast<CopyAddrInst>(lastUser)) {
395
397
assert (cai->getSrc () == tempObj && " collectLoads checks for writes" );
396
- assert (!copyInst->isTakeOfSrc () && " checked above" );
397
398
if (cai->isTakeOfSrc ())
398
399
continue ;
399
400
}
@@ -411,16 +412,22 @@ bool TempRValueOptPass::tryOptimizeCopyIntoTemp(CopyAddrInst *copyInst) {
411
412
if (!tempObj)
412
413
return false ;
413
414
415
+ bool isOSSA = copyInst->getFunction ()->hasOwnership ();
416
+
414
417
// The copy's source address must not be a scoped instruction, like
415
418
// begin_borrow. When the temporary object is eliminated, it's uses are
416
419
// replaced with the copy's source. Therefore, the source address must be
417
420
// valid at least until the next instruction that may write to or destroy the
418
421
// source. End-of-scope markers, such as end_borrow, do not write to or
419
422
// destroy memory, so scoped addresses are not valid replacements.
420
423
SILValue copySrc = stripAccessMarkers (copyInst->getSrc ());
421
-
422
424
assert (tempObj != copySrc && " can't initialize temporary with itself" );
423
425
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
+
424
431
// Scan all uses of the temporary storage (tempObj) to verify they all refer
425
432
// to the value initialized by this copy. It is sufficient to check that the
426
433
// only users that modify memory are the copy_addr [initialization] and
@@ -432,16 +439,44 @@ bool TempRValueOptPass::tryOptimizeCopyIntoTemp(CopyAddrInst *copyInst) {
432
439
if (user == copyInst)
433
440
continue ;
434
441
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
+ }
437
458
continue ;
459
+ }
438
460
439
461
if (!collectLoads (useOper, copyInst, loadInsts))
440
462
return false ;
441
463
}
442
464
443
465
// 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))
445
480
return false ;
446
481
447
482
ValueLifetimeAnalysis::Frontier tempAddressFrontier;
@@ -450,71 +485,45 @@ bool TempRValueOptPass::tryOptimizeCopyIntoTemp(CopyAddrInst *copyInst) {
450
485
451
486
LLVM_DEBUG (llvm::dbgs () << " Success: replace temp" << *tempObj);
452
487
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.
457
496
//
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.
461
504
while (!tempObj->use_empty ()) {
462
505
Operand *use = *tempObj->use_begin ();
463
506
SILInstruction *user = use->getUser ();
464
507
switch (user->getKind ()) {
465
508
case SILInstructionKind::DestroyAddrInst:
466
- if (copyInst->isTakeOfSrc ()) {
467
- use->set (copySrc);
468
- } else {
469
- user->dropAllReferences ();
470
- toDelete.push_back (user);
471
- }
472
- break ;
473
509
case SILInstructionKind::DeallocStackInst:
474
- user->dropAllReferences ();
475
- toDelete.push_back (user);
510
+ user->eraseFromParent ();
476
511
break ;
477
512
case SILInstructionKind::CopyAddrInst: {
478
513
auto *cai = cast<CopyAddrInst>(user);
479
514
if (cai != copyInst) {
480
515
assert (cai->getSrc () == tempObj);
481
- if (cai->isTakeOfSrc () && !copyInst-> isTakeOfSrc () )
516
+ if (cai->isTakeOfSrc ())
482
517
cai->setIsTakeOfSrc (IsNotTake);
483
518
}
484
519
use->set (copySrc);
485
520
break ;
486
521
}
487
522
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.
491
523
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 ());
518
527
break ;
519
528
}
520
529
@@ -527,9 +536,6 @@ bool TempRValueOptPass::tryOptimizeCopyIntoTemp(CopyAddrInst *copyInst) {
527
536
}
528
537
}
529
538
530
- while (!toDelete.empty ()) {
531
- toDelete.pop_back_val ()->eraseFromParent ();
532
- }
533
539
tempObj->eraseFromParent ();
534
540
return true ;
535
541
}
0 commit comments