@@ -70,7 +70,9 @@ class TempRValueOptPass : public SILFunctionTransform {
70
70
checkNoSourceModification (CopyAddrInst *copyInst,
71
71
const SmallPtrSetImpl<SILInstruction *> &useInsts);
72
72
73
- bool checkTempObjectDestroy (AllocStackInst *tempObj, CopyAddrInst *copyInst);
73
+ bool
74
+ checkTempObjectDestroy (AllocStackInst *tempObj, CopyAddrInst *copyInst,
75
+ ValueLifetimeAnalysis::Frontier &tempAddressFrontier);
74
76
75
77
bool tryOptimizeCopyIntoTemp (CopyAddrInst *copyInst);
76
78
@@ -189,6 +191,18 @@ bool TempRValueOptPass::collectLoads(
189
191
case SILInstructionKind::LoadBorrowInst:
190
192
// Loads are the end of the data flow chain. The users of the load can't
191
193
// access the temporary storage.
194
+ //
195
+ // That being said, if we see a load [take] here then we must have had a
196
+ // load [take] of a projection of our temporary stack location since we skip
197
+ // all the load [take] of the top level allocation in the caller of this
198
+ // function. So if we have such a load [take], we /must/ have a
199
+ // reinitialization or an alloc_stack that does not fit the pattern we are
200
+ // expecting from SILGen. Be conservative and return false.
201
+ if (auto *li = dyn_cast<LoadInst>(user)) {
202
+ if (li->getOwnershipQualifier () == LoadOwnershipQualifier::Take) {
203
+ return false ;
204
+ }
205
+ }
192
206
loadInsts.insert (user);
193
207
return true ;
194
208
@@ -250,8 +264,9 @@ bool TempRValueOptPass::checkNoSourceModification(
250
264
// / it is legal to destroy an in-memory object by loading the value and
251
265
// / releasing it. Rather than detecting unbalanced load releases, simply check
252
266
// / that tempObj is destroyed directly on all paths.
253
- bool TempRValueOptPass::checkTempObjectDestroy (AllocStackInst *tempObj,
254
- CopyAddrInst *copyInst) {
267
+ bool TempRValueOptPass::checkTempObjectDestroy (
268
+ AllocStackInst *tempObj, CopyAddrInst *copyInst,
269
+ ValueLifetimeAnalysis::Frontier &tempAddressFrontier) {
255
270
// If the original copy was a take, then replacing all uses cannot affect
256
271
// the lifetime.
257
272
if (copyInst->isTakeOfSrc ())
@@ -275,7 +290,6 @@ bool TempRValueOptPass::checkTempObjectDestroy(AllocStackInst *tempObj,
275
290
}
276
291
// Find the boundary of tempObj's address lifetime, starting at copyInst.
277
292
ValueLifetimeAnalysis vla (copyInst, users);
278
- ValueLifetimeAnalysis::Frontier tempAddressFrontier;
279
293
if (!vla.computeFrontier (tempAddressFrontier,
280
294
ValueLifetimeAnalysis::DontModifyCFG)) {
281
295
return false ;
@@ -289,13 +303,19 @@ bool TempRValueOptPass::checkTempObjectDestroy(AllocStackInst *tempObj,
289
303
if (pos == frontierInst->getParent ()->begin ())
290
304
return false ;
291
305
292
- // Look for a known destroy point as described in the funciton level
306
+ // Look for a known destroy point as described in the function level
293
307
// comment. This whitelist can be expanded as more cases are handled in
294
308
// tryOptimizeCopyIntoTemp during copy replacement.
295
309
SILInstruction *lastUser = &*std::prev (pos);
296
310
if (isa<DestroyAddrInst>(lastUser))
297
311
continue ;
298
312
313
+ if (auto *li = dyn_cast<LoadInst>(lastUser)) {
314
+ if (li->getOwnershipQualifier () == LoadOwnershipQualifier::Take) {
315
+ continue ;
316
+ }
317
+ }
318
+
299
319
if (auto *cai = dyn_cast<CopyAddrInst>(lastUser)) {
300
320
assert (cai->getSrc () == tempObj && " collectLoads checks for writes" );
301
321
assert (!copyInst->isTakeOfSrc () && " checked above" );
@@ -334,6 +354,15 @@ bool TempRValueOptPass::tryOptimizeCopyIntoTemp(CopyAddrInst *copyInst) {
334
354
if (isa<DestroyAddrInst>(user) || isa<DeallocStackInst>(user))
335
355
continue ;
336
356
357
+ // Same for load [take] on the top level temp object. SILGen always takes
358
+ // whole values from temporaries. If we have load [take] on projections from
359
+ // our base, we fail since those would be re-initializations.
360
+ if (auto *li = dyn_cast<LoadInst>(user)) {
361
+ if (li->getOwnershipQualifier () == LoadOwnershipQualifier::Take) {
362
+ continue ;
363
+ }
364
+ }
365
+
337
366
if (!collectLoads (useOper, user, tempObj, copyInst->getSrc (), loadInsts))
338
367
return false ;
339
368
}
@@ -342,7 +371,8 @@ bool TempRValueOptPass::tryOptimizeCopyIntoTemp(CopyAddrInst *copyInst) {
342
371
if (!checkNoSourceModification (copyInst, loadInsts))
343
372
return false ;
344
373
345
- if (!checkTempObjectDestroy (tempObj, copyInst))
374
+ ValueLifetimeAnalysis::Frontier tempAddressFrontier;
375
+ if (!checkTempObjectDestroy (tempObj, copyInst, tempAddressFrontier))
346
376
return false ;
347
377
348
378
LLVM_DEBUG (llvm::dbgs () << " Success: replace temp" << *tempObj);
@@ -351,6 +381,10 @@ bool TempRValueOptPass::tryOptimizeCopyIntoTemp(CopyAddrInst *copyInst) {
351
381
// the source address. Note: we must not delete the original copyInst because
352
382
// it would crash the instruction iteration in run(). Instead the copyInst
353
383
// gets identical Src and Dest operands.
384
+ //
385
+ // NOTE: We delete instructions at the end to allow us to use
386
+ // tempAddressFrontier to insert compensating destroys for load [take].
387
+ SmallVector<SILInstruction *, 4 > toDelete;
354
388
while (!tempObj->use_empty ()) {
355
389
Operand *use = *tempObj->use_begin ();
356
390
SILInstruction *user = use->getUser ();
@@ -359,11 +393,13 @@ bool TempRValueOptPass::tryOptimizeCopyIntoTemp(CopyAddrInst *copyInst) {
359
393
if (copyInst->isTakeOfSrc ()) {
360
394
use->set (copyInst->getSrc ());
361
395
} else {
362
- user->eraseFromParent ();
396
+ user->dropAllReferences ();
397
+ toDelete.push_back (user);
363
398
}
364
399
break ;
365
400
case SILInstructionKind::DeallocStackInst:
366
- user->eraseFromParent ();
401
+ user->dropAllReferences ();
402
+ toDelete.push_back (user);
367
403
break ;
368
404
case SILInstructionKind::CopyAddrInst: {
369
405
auto *cai = cast<CopyAddrInst>(user);
@@ -375,6 +411,40 @@ bool TempRValueOptPass::tryOptimizeCopyIntoTemp(CopyAddrInst *copyInst) {
375
411
use->set (copyInst->getSrc ());
376
412
break ;
377
413
}
414
+ case SILInstructionKind::LoadInst: {
415
+ // If we do not have a load [take] or we have a load [take] and our
416
+ // copy_addr takes the source, just do the normal thing of setting the
417
+ // load to use the copyInst's source.
418
+ auto *li = cast<LoadInst>(user);
419
+ if (li->getOwnershipQualifier () != LoadOwnershipQualifier::Take ||
420
+ copyInst->isTakeOfSrc ()) {
421
+ use->set (copyInst->getSrc ());
422
+ break ;
423
+ }
424
+
425
+ // Otherwise, since copy_addr is not taking src, we need to ensure that we
426
+ // insert a copy of our value. We do that by creating a load [copy] at the
427
+ // copy_addr inst and RAUWing the load [take] with that. We then insert
428
+ // destroy_value for the load [copy] at all points where we had destroys
429
+ // that are not the specific take that we were optimizing.
430
+ SILBuilderWithScope builder (copyInst);
431
+ SILValue newLoad = builder.emitLoadValueOperation (
432
+ copyInst->getLoc (), copyInst->getSrc (), LoadOwnershipQualifier::Copy);
433
+ for (auto *inst : tempAddressFrontier) {
434
+ assert (inst->getIterator () != inst->getParent ()->begin () &&
435
+ " Should have caught this when checking destructor" );
436
+ auto prevInst = std::prev (inst->getIterator ());
437
+ if (&*prevInst == li)
438
+ continue ;
439
+ SILBuilderWithScope builder (prevInst);
440
+ builder.emitDestroyValueOperation (prevInst->getLoc (), newLoad);
441
+ }
442
+ li->replaceAllUsesWith (newLoad);
443
+ li->dropAllReferences ();
444
+ toDelete.push_back (li);
445
+ break ;
446
+ }
447
+
378
448
// ASSUMPTION: no operations that may be handled by this default clause can
379
449
// destroy tempObj. This includes operations that load the value from memory
380
450
// and release it.
@@ -383,6 +453,10 @@ bool TempRValueOptPass::tryOptimizeCopyIntoTemp(CopyAddrInst *copyInst) {
383
453
break ;
384
454
}
385
455
}
456
+
457
+ while (!toDelete.empty ()) {
458
+ toDelete.pop_back_val ()->eraseFromParent ();
459
+ }
386
460
tempObj->eraseFromParent ();
387
461
return true ;
388
462
}
0 commit comments