1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 495 496 497 498 499 500 501 502 503 504 505 506 507 508 509 510 511 512 513 514 515 516 517 518 519 520 521 522 523 524 525 526 527 528 529 530 531 532 533 534 535 536 537 538 539 540 541 542 543 544 545 546 547 548 549 550 551 552 553 554 555 556 557 558 559 560 561 562 563 564 565 566 567 568 569 570 571 572 573 574 575 576 577 578 579 580 581 582 583 584 585 586 587 588 589 590 591 592 593 594 595 596 597 598 599 600 601 602 603 604 605 606 607 608 609 610 611 612 613 614 615 616 617 618 619 620 621 622 623 624 625 626 627 628 629 630 631 632 633 634 635 636 637 638 639 640 641 642 643 644 645 646 647 648 649 650 651 652 653 654 655 656 657 658 659 660 661 662 663 664 665 666 667 668 669 670 671 672 673 674 675 676 677 678 679 680 681 682 683 684 685 686 687 688 689 690 691 692 693 694 695 696 697 698 699 700 701 702 703 704 705 706 707 708 709 710 711 712 713 714 715 716 717 718 719 720 721 722 723 724 725 726 727 728 729 730 731 732 733 734 735 736 737 738 739 740 741 742 743 744 745 746 747 748 749 750 751 752 753 754 755 756 757 758 759 760 761 762 763 764 765 766 767 768 769 770 771 772 773 774 775 776 777 778 779 780 781 782 783 784 785 786 787 788 789 790 791 792 793 794 795 796 797 798 799 800 801 802 803 804 805 806 807 808 809 810 811 812 813 814 815 816 817 818 819 820 821 822 823 824 825 826 827 828 829 830 831 832 833 834 835 836 837 838 839 840 841 842 843 844 845 846 847 848 849 850 851 852 853 854 855 856 857 858 859 860 861 862 863 864 865 866 867 868 869 870 871 872 873 874 875 876 877 878 879 880 881 882 883 884 885 886 887 888 889 890 891 892 893 894 895 896 897 898 899 900 901 902 903 904 905 906 907 908 909 910 911 912 913 914 915 916 917 918 919 920 921 922 923 924 925 926 927 928 929 930 931 932 933 934 935 936 937 938 939 940 941 942 943 944 945 946 947 948 949 950 951 952 953 954 955 956 957 958 959 960 961 962 963 964 965 966 967 968 969 970 971
|
//===--- TempRValueElimination.cpp ----------------------------------------===//
//
// This source file is part of the Swift.org open source project
//
// Copyright (c) 2014 - 2020 Apple Inc. and the Swift project authors
// Licensed under Apache License v2.0 with Runtime Library Exception
//
// See https://swift.org/LICENSE.txt for license information
// See https://swift.org/CONTRIBUTORS.txt for the list of Swift project authors
//
//===----------------------------------------------------------------------===//
///
/// Eliminate temporary RValues inserted as a result of materialization by
/// SILGen. The key pattern here is that we are looking for alloc_stack that are
/// only written to once and are eventually either destroyed/taken from.
///
//===----------------------------------------------------------------------===//
#define DEBUG_TYPE "sil-temp-rvalue-opt"
#include "swift/SIL/BasicBlockUtils.h"
#include "swift/SIL/DebugUtils.h"
#include "swift/SIL/MemAccessUtils.h"
#include "swift/SIL/NodeBits.h"
#include "swift/SIL/OSSALifetimeCompletion.h"
#include "swift/SIL/OwnershipUtils.h"
#include "swift/SIL/SILArgument.h"
#include "swift/SIL/SILBuilder.h"
#include "swift/SIL/SILVisitor.h"
#include "swift/SILOptimizer/Analysis/AliasAnalysis.h"
#include "swift/SILOptimizer/Analysis/DominanceAnalysis.h"
#include "swift/SILOptimizer/Analysis/PostOrderAnalysis.h"
#include "swift/SILOptimizer/Analysis/RCIdentityAnalysis.h"
#include "swift/SILOptimizer/Analysis/SimplifyInstruction.h"
#include "swift/SILOptimizer/PassManager/Passes.h"
#include "swift/SILOptimizer/PassManager/Transforms.h"
#include "swift/SILOptimizer/Utils/CFGOptUtils.h"
#include "swift/SILOptimizer/Utils/ValueLifetime.h"
#include "llvm/ADT/SetVector.h"
#include "llvm/ADT/Statistic.h"
#include "llvm/Support/CommandLine.h"
#include "llvm/Support/Debug.h"
using namespace swift;
//===----------------------------------------------------------------------===//
// Interface
//===----------------------------------------------------------------------===//
namespace {
/// Temporary RValue Optimization
///
/// Peephole optimization to eliminate short-lived immutable temporary copies.
/// This handles a common pattern generated by SILGen where temporary RValues
/// are emitted as copies...
///
/// %temp = alloc_stack $T
/// copy_addr %src to [init] %temp : $*T
/// // no writes to %src or %temp
/// destroy_addr %temp : $*T
/// dealloc_stack %temp : $*T
///
/// This differs from the copy forwarding algorithm because it handles
/// copy source and dest lifetimes that are unavoidably overlapping. Instead,
/// it finds cases in which it is easy to determine that the source is
/// unmodified during the copy destination's lifetime. Thus, the destination can
/// be viewed as a short-lived "rvalue".
///
/// As a second optimization, also stores into temporaries are handled. This is
/// a simple form of redundant-load-elimination (RLE).
///
/// %temp = alloc_stack $T
/// store %src to [init] %temp : $*T
/// // no writes to %temp
/// %v = load [take] %temp : $*T
/// dealloc_stack %temp : $*T
///
/// TODO: Check if we still need to handle stores when RLE supports OSSA.
class TempRValueOptPass : public SILFunctionTransform {
bool collectLoads(Operand *addressUse, CopyAddrInst *originalCopy,
InstructionSetWithSize &loadInsts);
bool collectLoadsFromProjection(SingleValueInstruction *projection,
CopyAddrInst *originalCopy,
InstructionSetWithSize &loadInsts);
SILInstruction *getLastUseWhileSourceIsNotModified(
CopyAddrInst *copyInst, const InstructionSetWithSize &useInsts,
AliasAnalysis *aa);
bool
checkTempObjectDestroy(AllocStackInst *tempObj, CopyAddrInst *copyInst);
bool extendAccessScopes(CopyAddrInst *copyInst, SILInstruction *lastUseInst,
AliasAnalysis *aa);
void tryOptimizeCopyIntoTemp(CopyAddrInst *copyInst);
SILBasicBlock::iterator tryOptimizeStoreIntoTemp(StoreInst *si);
void run() override;
};
} // anonymous namespace
bool TempRValueOptPass::collectLoadsFromProjection(
SingleValueInstruction *projection, CopyAddrInst *originalCopy,
InstructionSetWithSize &loadInsts) {
// Transitively look through projections on stack addresses.
for (auto *projUseOper : projection->getUses()) {
auto *user = projUseOper->getUser();
if (user->isTypeDependentOperand(*projUseOper))
continue;
if (!collectLoads(projUseOper, originalCopy, loadInsts))
return false;
}
return true;
}
/// Transitively explore all data flow uses of the given \p address until
/// reaching a load or returning false.
///
/// Any user opcode recognized by collectLoads must be replaced correctly later
/// during tryOptimizeCopyIntoTemp. If it is possible for any use to destroy the
/// value in \p address, then that use must be removed or made non-destructive
/// after the copy is removed and its operand is replaced.
///
/// Warning: To preserve the original object lifetime, tryOptimizeCopyIntoTemp
/// must assume that there are no holes in lifetime of the temporary stack
/// location at \address. The temporary must be initialized by the original copy
/// and never written to again. Therefore, collectLoads disallows any operation
/// that may write to memory at \p address.
bool TempRValueOptPass::
collectLoads(Operand *addressUse, CopyAddrInst *originalCopy,
InstructionSetWithSize &loadInsts) {
SILInstruction *user = addressUse->getUser();
SILValue address = addressUse->get();
// All normal uses (loads) must be in the initialization block.
// (The destroy and dealloc are commonly in a different block though.)
SILBasicBlock *block = originalCopy->getParent();
if (user->getParent() != block)
return false;
// Only allow uses that cannot destroy their operand. We need to be sure
// that replacing all this temporary's uses with the copy source doesn't
// destroy the source. This way, we know that the destroy_addr instructions
// that we recorded cover all the temporary's lifetime termination points.
//
// Currently this includes address projections, loads, and in_guaranteed uses
// by an apply.
//
// TODO: handle non-destructive projections of enums
// (unchecked_take_enum_data_addr of Optional is nondestructive.)
switch (user->getKind()) {
default:
LLVM_DEBUG(llvm::dbgs()
<< " Temp use may write/destroy its source" << *user);
return false;
case SILInstructionKind::BeginAccessInst: {
auto *beginAccess = cast<BeginAccessInst>(user);
if (beginAccess->getAccessKind() != SILAccessKind::Read)
return false;
// We don't have to recursively call collectLoads for the beginAccess
// result, because a SILAccessKind::Read already guarantees that there are
// no writes to the beginAccess result address (or any projection from it).
// But we have to register the end-accesses as loads to correctly mark the
// end-of-lifetime of the tempObj.
//
// %addr = begin_access [read]
// ... // there can be no writes to %addr here
// end_access %addr // <- This is where the use actually ends.
for (EndAccessInst *endAccess : beginAccess->getEndAccesses()) {
if (endAccess->getParent() != block)
return false;
loadInsts.insert(endAccess);
}
return true;
}
case SILInstructionKind::MarkDependenceInst: {
auto mdi = cast<MarkDependenceInst>(user);
// If the user is the base operand of the MarkDependenceInst we can return
// true, because this would be the end of this dataflow chain
if (mdi->getBase() == address) {
return true;
}
// If the user is the value operand of the MarkDependenceInst we have to
// transitively explore its uses until we reach a load or return false
for (auto *mdiUseOper : mdi->getUses()) {
if (!collectLoads(mdiUseOper, originalCopy, loadInsts))
return false;
}
return true;
}
case SILInstructionKind::PartialApplyInst:
if (!cast<PartialApplyInst>(user)->isOnStack())
return false;
LLVM_FALLTHROUGH;
case SILInstructionKind::ApplyInst:
case SILInstructionKind::TryApplyInst:
case SILInstructionKind::BeginApplyInst: {
auto convention = ApplySite(user).getArgumentConvention(*addressUse);
if (!convention.isGuaranteedConvention())
return false;
loadInsts.insert(user);
if (auto *beginApply = dyn_cast<BeginApplyInst>(user)) {
// Register 'end_apply'/'abort_apply' as loads as well
// 'checkNoSourceModification' should check instructions until
// 'end_apply'/'abort_apply'.
for (auto tokenUse : beginApply->getTokenResult()->getUses()) {
SILInstruction *tokenUser = tokenUse->getUser();
if (tokenUser->getParent() != block)
return false;
loadInsts.insert(tokenUser);
}
}
return true;
}
case SILInstructionKind::YieldInst: {
auto *yield = cast<YieldInst>(user);
auto convention = yield->getArgumentConventionForOperand(*addressUse);
if (!convention.isGuaranteedConvention())
return false;
loadInsts.insert(user);
return true;
}
case SILInstructionKind::OpenExistentialAddrInst: {
// We only support open existential addr if the access is immutable.
auto *oeai = cast<OpenExistentialAddrInst>(user);
if (oeai->getAccessKind() != OpenedExistentialAccess::Immutable) {
LLVM_DEBUG(llvm::dbgs() << " Temp consuming use may write/destroy "
"its source"
<< *user);
return false;
}
return collectLoadsFromProjection(oeai, originalCopy, loadInsts);
}
case SILInstructionKind::UncheckedTakeEnumDataAddrInst: {
// In certain cases, unchecked_take_enum_data_addr invalidates the
// underlying memory, so by default we can not look through it... but this
// is not true in the case of Optional. This is an important case for us to
// handle, so handle it here.
auto *utedai = cast<UncheckedTakeEnumDataAddrInst>(user);
if (!utedai->getOperand()->getType().getOptionalObjectType()) {
LLVM_DEBUG(llvm::dbgs()
<< " Temp use may write/destroy its source" << *utedai);
return false;
}
return collectLoadsFromProjection(utedai, originalCopy, loadInsts);
}
case SILInstructionKind::StructElementAddrInst:
case SILInstructionKind::TupleElementAddrInst:
case SILInstructionKind::UncheckedAddrCastInst:
return collectLoadsFromProjection(cast<SingleValueInstruction>(user),
originalCopy, loadInsts);
case SILInstructionKind::LoadInst: {
// Loads are the end of the data flow chain. The users of the load can't
// access the temporary storage.
//
// That being said, if we see a load [take] here then we must have had a
// load [take] of a projection of our temporary stack location since we skip
// all the load [take] of the top level allocation in the caller of this
// function. So if we have such a load [take], we /must/ have a
// reinitialization or an alloc_stack that does not fit the pattern we are
// expecting from SILGen. Be conservative and return false.
auto *li = cast<LoadInst>(user);
if (li->getOwnershipQualifier() == LoadOwnershipQualifier::Take &&
// Only accept load [take] if it takes the whole temporary object.
// load [take] from a projection would destroy only a part of the
// temporary and we don't handle this.
address != originalCopy->getDest()) {
return false;
}
loadInsts.insert(user);
return true;
}
case SILInstructionKind::LoadBorrowInst: {
loadInsts.insert(user);
BorrowedValue borrow(cast<LoadBorrowInst>(user));
auto visitEndScope = [&](Operand *op) -> bool {
auto *opUser = op->getUser();
if (auto *endBorrow = dyn_cast<EndBorrowInst>(opUser)) {
if (endBorrow->getParent() != block)
return false;
loadInsts.insert(endBorrow);
return true;
}
// Don't look further if we see a reborrow.
assert(cast<BranchInst>(opUser));
return false;
};
auto res = borrow.visitLocalScopeEndingUses(visitEndScope);
return res;
}
case SILInstructionKind::FixLifetimeInst:
// If we have a fixed lifetime on our alloc_stack, we can just treat it like
// a load and re-write it so that it is on the old memory or old src object.
loadInsts.insert(user);
return true;
case SILInstructionKind::CopyAddrInst: {
// copy_addr which read from the temporary are like loads.
auto *copyFromTmp = cast<CopyAddrInst>(user);
if (copyFromTmp->getDest() == address) {
LLVM_DEBUG(llvm::dbgs() << " Temp written or taken" << *user);
return false;
}
// As with load [take], only accept copy_addr [take] if it takes the whole
// temporary object.
if (copyFromTmp->isTakeOfSrc() && address != originalCopy->getDest())
return false;
loadInsts.insert(copyFromTmp);
return true;
}
}
}
/// Checks if the source of \p copyInst is not modified within the temporary's
/// lifetime, i.e. is not modified before the last use of \p useInsts.
///
/// If there are no source modifications with the lifetime, returns the last
/// user (or copyInst if there are no uses at all).
/// Otherwise, returns a nullptr.
///
/// Unfortunately, we cannot simply use the destroy points as the lifetime end,
/// because they can be in a different basic block (that's what SILGen
/// generates). Instead we guarantee that all normal uses are within the block
/// of the temporary and look for the last use, which effectively ends the
/// lifetime.
SILInstruction *TempRValueOptPass::getLastUseWhileSourceIsNotModified(
CopyAddrInst *copyInst, const InstructionSetWithSize &useInsts,
AliasAnalysis *aa) {
if (useInsts.empty())
return copyInst;
unsigned numLoadsFound = 0;
SILValue copySrc = copyInst->getSrc();
// We already checked that the useful lifetime of the temporary ends in
// the initialization block. Iterate over the instructions of the block,
// starting at copyInst, until we get to the last user.
auto iter = std::next(copyInst->getIterator());
auto iterEnd = copyInst->getParent()->end();
for (; iter != iterEnd; ++iter) {
SILInstruction *inst = &*iter;
if (useInsts.contains(inst))
++numLoadsFound;
// If this is the last use of the temp we are ok. After this point,
// modifications to the source don't matter anymore.
// Note that we are assuming here that if an instruction loads and writes
// to copySrc at the same time (like a copy_addr could do), the write
// takes effect after the load.
if (numLoadsFound == useInsts.size()) {
// Function calls are an exception: in a called function a potential
// modification of copySrc could occur _before_ the read of the temporary.
if ((FullApplySite::isa(inst) || isa<YieldInst>(inst)) &&
aa->mayWriteToMemory(inst, copySrc)) {
return nullptr;
}
return inst;
}
if (aa->mayWriteToMemory(inst, copySrc)) {
LLVM_DEBUG(llvm::dbgs() << " Source modified by" << *iter);
return nullptr;
}
}
// For some reason, not all normal uses have been seen between the copy and
// the end of the initialization block. We should never reach here.
return nullptr;
}
/// Tries to move an end_access down to extend the access scope over all uses
/// of the temporary. For example:
///
/// %a = begin_access %src
/// copy_addr %a to [init] %temp : $*T
/// end_access %a
/// use %temp
///
/// We must not replace %temp with %a after the end_access. Instead we try to
/// move the end_access after "use %temp".
bool TempRValueOptPass::extendAccessScopes(
CopyAddrInst *copyInst, SILInstruction *lastUseInst, AliasAnalysis *aa) {
SILValue copySrc = copyInst->getSrc();
EndAccessInst *endAccessToMove = nullptr;
auto begin = std::next(copyInst->getIterator());
auto end = std::next(lastUseInst->getIterator());
for (SILInstruction &inst : make_range(begin, end)) {
if (auto *endAccess = dyn_cast<EndAccessInst>(&inst)) {
// To keep things simple, we can just move a single end_access. Also, we
// cannot move an end_access over a (non-aliasing) end_access.
if (endAccessToMove)
return false;
// Is this the end of an access scope of the copy-source?
if (!aa->isNoAlias(copySrc, endAccess->getSource()) &&
// There cannot be any aliasing modifying accesses within the liferange
// of the temporary, because we would have cought this in
// `getLastUseWhileSourceIsNotModified`.
// But there are cases where `AliasAnalysis::isNoAlias` is less precise
// than `AliasAnalysis::mayWriteToMemory`. Therefore, just ignore any
// non-read accesses.
endAccess->getBeginAccess()->getAccessKind() == SILAccessKind::Read) {
// Don't move instructions beyond the block's terminator.
if (isa<TermInst>(lastUseInst))
return false;
endAccessToMove = endAccess;
}
} else if (endAccessToMove) {
// We cannot move an end_access over a begin_access. This would destroy
// the proper nesting of accesses.
if (isa<BeginAccessInst>(&inst) || isa<BeginUnpairedAccessInst>(inst))
return false;
// Don't extend a read-access scope over a (potential) write.
// Note that inst can be a function call containing other access scopes.
// But doing the mayWriteToMemory check, we know that the function can
// only contain read accesses (to the same memory location). So it's fine
// to move endAccessToMove even over such a function call.
if (aa->mayWriteToMemory(&inst, endAccessToMove->getSource()))
return false;
}
}
if (endAccessToMove)
endAccessToMove->moveAfter(lastUseInst);
return true;
}
/// Return true if the \p tempObj, which is initialized by \p copyInst, is
/// destroyed in an orthodox way.
///
/// When tryOptimizeCopyIntoTemp replaces all of tempObj's uses, it assumes that
/// the object is initialized by the original copy and directly destroyed on all
/// paths by one of the recognized 'destroy_addr' or 'copy_addr [take]'
/// operations. This assumption must be checked. For example, in non-OSSA,
/// it is legal to destroy an in-memory object by loading the value and
/// releasing it. Rather than detecting unbalanced load releases, simply check
/// that tempObj is destroyed directly on all paths.
bool TempRValueOptPass::checkTempObjectDestroy(
AllocStackInst *tempObj, CopyAddrInst *copyInst) {
// ValueLifetimeAnalysis is not normally used for address types. It does not
// reason about the lifetime of the in-memory object. However the utility can
// be abused here to check that the address is directly destroyed on all
// paths. collectLoads has already guaranteed that tempObj's lifetime has no
// holes/reinitializations.
SmallVector<SILInstruction *, 8> users;
for (auto result : tempObj->getResults()) {
for (Operand *operand : result->getUses()) {
SILInstruction *user = operand->getUser();
if (user == copyInst)
continue;
if (isa<DeallocStackInst>(user))
continue;
users.push_back(user);
}
}
// Find the boundary of tempObj's address lifetime, starting at copyInst.
ValueLifetimeAnalysis vla(copyInst, users);
ValueLifetimeAnalysis::Frontier tempAddressFrontier;
if (!vla.computeFrontier(tempAddressFrontier,
ValueLifetimeAnalysis::DontModifyCFG)) {
return false;
}
// Check that the lifetime boundary ends at direct destroy points.
for (SILInstruction *frontierInst : tempAddressFrontier) {
auto pos = frontierInst->getIterator();
// If the frontier is at the head of a block, then either it is an
// unexpected lifetime exit, or the lifetime ended at a
// terminator. TempRValueOptPass does not handle either case.
if (pos == frontierInst->getParent()->begin())
return false;
// Look for a known destroy point as described in the function level
// comment. This allowlist can be expanded as more cases are handled in
// tryOptimizeCopyIntoTemp during copy replacement.
SILInstruction *lastUser = &*std::prev(pos);
if (isa<DestroyAddrInst>(lastUser))
continue;
if (auto *cai = dyn_cast<CopyAddrInst>(lastUser)) {
assert(cai->getSrc() == tempObj && "collectLoads checks for writes");
if (cai->isTakeOfSrc())
continue;
}
return false;
}
return true;
}
/// Tries to perform the temporary rvalue copy elimination for \p copyInst
void TempRValueOptPass::tryOptimizeCopyIntoTemp(CopyAddrInst *copyInst) {
if (!copyInst->isInitializationOfDest())
return;
auto *tempObj = dyn_cast<AllocStackInst>(copyInst->getDest());
if (!tempObj)
return;
// If the temporary storage is lexical, it either came from a source-level var
// or was marked lexical because it was passed to a function that has been
// inlined.
// TODO: [begin_borrow_addr] Once we can mark addresses as being borrowed, we
// won't need to mark alloc_stacks lexical during inlining. At that
// point, the above comment should change, but the implementation
// remains the same.
//
// In either case, we can eliminate the temporary if the source of the copy is
// lexical and it is live for longer than the temporary.
if (tempObj->isLexical()) {
// TODO: Determine whether the base of the copy_addr's source is lexical and
// its live range contains the range in which the alloc_stack
// contains the value copied into it via the copy_addr.
//
// For now, only look for guaranteed arguments.
auto storage = AccessStorageWithBase::compute(copyInst->getSrc());
if (!storage.base)
return;
if (auto *arg = dyn_cast<SILFunctionArgument>(storage.base))
if (arg->getOwnershipKind() != OwnershipKind::Guaranteed)
return;
}
bool isOSSA = copyInst->getFunction()->hasOwnership();
SILValue copySrc = copyInst->getSrc();
assert(tempObj != copySrc && "can't initialize temporary with itself");
// If the source of the copyInst is taken, it must be deinitialized (via
// destroy_addr, load [take], copy_addr [take]). This must be done at the
// right spot: after the last use tempObj, but before any (potential)
// re-initialization of the source.
bool needFinalDeinit = copyInst->isTakeOfSrc();
// Scan all uses of the temporary storage (tempObj) to verify they all refer
// to the value initialized by this copy. It is sufficient to check that the
// only users that modify memory are the copy_addr [initialization] and
// destroy_addr.
InstructionSetWithSize loadInsts(getFunction());
// Set of tempObj users
InstructionSet userSet(getFunction());
for (auto *useOper : tempObj->getUses()) {
SILInstruction *user = useOper->getUser();
userSet.insert(user);
if (user == copyInst)
continue;
// Deallocations are allowed to be in a different block.
if (isa<DeallocStackInst>(user))
continue;
// Also, destroys are allowed to be in a different block.
if (isa<DestroyAddrInst>(user)) {
if (!isOSSA && needFinalDeinit) {
// In non-OSSA mode, for the purpose of inserting the destroy of
// copySrc, we have to be conservative and assume that the lifetime of
// tempObj goes beyond it's last use - until the final destroy_addr.
// Otherwise we would risk of inserting the destroy too early.
// So we just treat the destroy_addr as any other use of tempObj.
if (user->getParent() != copyInst->getParent())
return;
loadInsts.insert(user);
}
continue;
}
if (!collectLoads(useOper, copyInst, loadInsts))
return;
}
// Check and return without optimization if we have any users of tempObj that
// precede the copyInst.
// This can happen with projections.
// TODO: We can enable this case if we clone the projections at "load" uses
// All instructions in userSet are in the same block as copyInst. collectLoads
// ensures of this.
for (SILInstruction &inst : llvm::make_range(copyInst->getParent()->begin(),
copyInst->getIterator())) {
if (userSet.contains(&inst)) {
return;
}
}
AliasAnalysis *aa = getPassManager()->getAnalysis<AliasAnalysis>(getFunction());
// Check if the source is modified within the lifetime of the temporary.
SILInstruction *lastLoadInst =
getLastUseWhileSourceIsNotModified(copyInst, loadInsts, aa);
if (!lastLoadInst)
return;
// We cannot insert the destroy of copySrc after lastLoadInst if copySrc is
// re-initialized by exactly this instruction.
// This is a corner case, but can happen if lastLoadInst is a copy_addr.
// Example:
// copy_addr [take] %copySrc to [init] %tempObj // copyInst
// copy_addr [take] %tempObj to [init] %copySrc // lastLoadInst
if (needFinalDeinit && lastLoadInst != copyInst &&
!isa<DestroyAddrInst>(lastLoadInst) &&
aa->mayWriteToMemory(lastLoadInst, copySrc))
return;
if (!isOSSA && !checkTempObjectDestroy(tempObj, copyInst))
return;
if (!extendAccessScopes(copyInst, lastLoadInst, aa))
return;
LLVM_DEBUG(llvm::dbgs() << " Success: replace temp" << *tempObj);
// If copyInst's source must be deinitialized, whether that must be done via
// a newly created destroy_addr.
//
// If lastLoadInst is a load or a copy_addr, then the deinitialization can be
// done in that instruction.
//
// This is necessary for correctness: otherwise, copies of move-only values
// would be introduced.
bool needToInsertDestroy = [&]() {
if (!needFinalDeinit)
return false;
if (lastLoadInst == copyInst)
return true;
if (auto *cai = dyn_cast<CopyAddrInst>(lastLoadInst)) {
if (cai->getSrc() == tempObj && cai->isTakeOfSrc()) {
// This copy_addr [take] will perform the final deinitialization.
return false;
}
assert(!tempObj->getType().isMoveOnly() &&
"introducing copy of move-only value!?");
return true;
}
if (auto *li = dyn_cast<LoadInst>(lastLoadInst)) {
if (li->getOperand() == tempObj &&
li->getOwnershipQualifier() == LoadOwnershipQualifier::Take) {
// This load [take] will perform the final deinitialization.
return false;
}
assert(!tempObj->getType().isMoveOnly() &&
"introducing copy of move-only value!?");
return true;
}
return true;
}();
if (needToInsertDestroy) {
// Compensate the [take] of the original copyInst.
SILBuilderWithScope::insertAfter(lastLoadInst, [&] (SILBuilder &builder) {
builder.createDestroyAddr(builder.getInsertionPoint()->getLoc(), copySrc);
});
}
// * Replace all uses of the tempObj with the copySrc.
//
// * Delete the destroy(s) of tempObj (to compensate the removal of the
// original copyInst): either by erasing the destroy_addr or by converting
// load/copy_addr [take] into copying instructions.
//
// Note: we must not delete the original copyInst because it would crash the
// instruction iteration in run(). Instead the copyInst gets identical Src and
// Dest operands.
while (!tempObj->use_empty()) {
Operand *use = *tempObj->use_begin();
SILInstruction *user = use->getUser();
switch (user->getKind()) {
case SILInstructionKind::DestroyAddrInst:
case SILInstructionKind::DeallocStackInst:
user->eraseFromParent();
break;
case SILInstructionKind::CopyAddrInst: {
auto *cai = cast<CopyAddrInst>(user);
if (cai != copyInst) {
assert(cai->getSrc() == tempObj);
if (cai->isTakeOfSrc() && (!needFinalDeinit || lastLoadInst != cai)) {
cai->setIsTakeOfSrc(IsNotTake);
}
}
use->set(copySrc);
break;
}
case SILInstructionKind::LoadInst: {
auto *li = cast<LoadInst>(user);
if (li->getOwnershipQualifier() == LoadOwnershipQualifier::Take &&
(!needFinalDeinit || li != lastLoadInst)) {
li->setOwnershipQualifier(LoadOwnershipQualifier::Copy);
}
use->set(copySrc);
break;
}
// ASSUMPTION: no operations that may be handled by this default clause can
// destroy tempObj. This includes operations that load the value from memory
// and release it or cast the address before destroying it.
default:
use->set(copySrc);
break;
}
}
tempObj->eraseFromParent();
invalidateAnalysis(SILAnalysis::InvalidationKind::Instructions);
}
SILBasicBlock::iterator
TempRValueOptPass::tryOptimizeStoreIntoTemp(StoreInst *si) {
// If our store is an assign, bail.
if (si->getOwnershipQualifier() == StoreOwnershipQualifier::Assign)
return std::next(si->getIterator());
auto *tempObj = dyn_cast<AllocStackInst>(si->getDest());
if (!tempObj) {
return std::next(si->getIterator());
}
// If the temporary storage is lexical, it either came from a source-level var
// or was marked lexical because it was passed to a function that has been
// inlined.
// TODO: [begin_borrow_addr] Once we can mark addresses as being borrowed, we
// won't need to mark alloc_stacks lexical during inlining. At that
// point, the above comment should change, but the implementation
// remains the same.
//
// In either case, we can eliminate the temporary if the source of the store
// is lexical and it is live for longer than the temporary.
if (tempObj->isLexical()) {
// TODO: Find the lexical root of the source, if any, and allow optimization
// if its live range contains the range in which the alloc_stack
// contains the value stored into it.
return std::next(si->getIterator());
}
// If our tempObj has a dynamic lifetime (meaning it is conditionally
// initialized, conditionally taken, etc), we can not convert its uses to SSA
// while eliminating it simply. So bail.
if (tempObj->hasDynamicLifetime()) {
return std::next(si->getIterator());
}
// Scan all uses of the temporary storage (tempObj) to verify they all refer
// to the value initialized by this copy. It is sufficient to check that the
// only users that modify memory are the copy_addr [initialization] and
// destroy_addr.
for (auto *useOper : tempObj->getUses()) {
SILInstruction *user = useOper->getUser();
if (user == si)
continue;
// Bail if there is any kind of user which is not handled in the code below.
switch (user->getKind()) {
case SILInstructionKind::DestroyAddrInst:
case SILInstructionKind::DeallocStackInst:
case SILInstructionKind::LoadInst:
case SILInstructionKind::FixLifetimeInst:
break;
case SILInstructionKind::CopyAddrInst:
if (cast<CopyAddrInst>(user)->getDest() == tempObj)
return std::next(si->getIterator());
break;
case SILInstructionKind::MarkDependenceInst:
if (cast<MarkDependenceInst>(user)->getValue() == tempObj)
return std::next(si->getIterator());
break;
default:
return std::next(si->getIterator());
}
}
// Since store is always a consuming operation, we do not need to worry about
// any lifetime constraints and can just replace all of the uses here. This
// contrasts with the copy_addr implementation where we need to consider the
// possibility that the source address is written to.
LLVM_DEBUG(llvm::dbgs() << " Success: replace temp" << *tempObj);
// Do a "replaceAllUses" by either deleting the users or replacing them with
// the appropriate operation on the source value.
SmallVector<SILInstruction *, 4> toDelete;
for (auto *use : tempObj->getUses()) {
// If our store is the user, just skip it.
if (use->getUser() == si) {
continue;
}
SILInstruction *user = use->getUser();
switch (user->getKind()) {
case SILInstructionKind::DestroyAddrInst: {
SILBuilderWithScope builder(user);
builder.emitDestroyValueOperation(user->getLoc(), si->getSrc());
toDelete.push_back(user);
break;
}
case SILInstructionKind::DeallocStackInst:
toDelete.push_back(user);
break;
case SILInstructionKind::CopyAddrInst: {
auto *cai = cast<CopyAddrInst>(user);
assert(cai->getSrc() == tempObj);
SILBuilderWithScope builder(user);
auto qualifier = cai->isInitializationOfDest()
? StoreOwnershipQualifier::Init
: StoreOwnershipQualifier::Assign;
SILValue src = si->getSrc();
if (!cai->isTakeOfSrc()) {
src = builder.emitCopyValueOperation(cai->getLoc(), src);
}
builder.emitStoreValueOperation(cai->getLoc(), src, cai->getDest(),
qualifier);
toDelete.push_back(cai);
break;
}
case SILInstructionKind::LoadInst: {
// Since store is always forwarding, we know that we should have our own
// value here. So, we should be able to just RAUW any load [take] and
// insert a copy + RAUW for any load [copy].
auto *li = cast<LoadInst>(user);
SILValue srcObject = si->getSrc();
if (li->getOwnershipQualifier() == LoadOwnershipQualifier::Copy) {
SILBuilderWithScope builder(li);
srcObject = builder.emitCopyValueOperation(li->getLoc(), srcObject);
}
li->replaceAllUsesWith(srcObject);
toDelete.push_back(li);
break;
}
case SILInstructionKind::FixLifetimeInst: {
auto *fli = cast<FixLifetimeInst>(user);
SILBuilderWithScope builder(fli);
builder.createFixLifetime(fli->getLoc(), si->getSrc());
toDelete.push_back(fli);
break;
}
case SILInstructionKind::MarkDependenceInst: {
auto mdi = cast<MarkDependenceInst>(user);
assert(mdi->getBase() == tempObj);
SILBuilderWithScope builder(user);
auto newInst = builder.createMarkDependence(user->getLoc(),
mdi->getValue(),
si->getSrc(),
mdi->dependenceKind());
mdi->replaceAllUsesWith(newInst);
toDelete.push_back(mdi);
break;
}
// ASSUMPTION: no operations that may be handled by this default clause can
// destroy tempObj. This includes operations that load the value from memory
// and release it.
default:
llvm::errs() << "Unhandled user: " << *user;
llvm_unreachable("Unhandled case?!");
break;
}
}
while (!toDelete.empty()) {
auto *inst = toDelete.pop_back_val();
inst->dropAllReferences();
inst->eraseFromParent();
}
auto nextIter = std::next(si->getIterator());
si->eraseFromParent();
tempObj->eraseFromParent();
invalidateAnalysis(SILAnalysis::InvalidationKind::Instructions);
return nextIter;
}
//===----------------------------------------------------------------------===//
// High Level Entrypoint
//===----------------------------------------------------------------------===//
/// The main entry point of the pass.
void TempRValueOptPass::run() {
auto *function = getFunction();
auto *da = PM->getAnalysis<DominanceAnalysis>();
LLVM_DEBUG(llvm::dbgs() << "Copy Peephole in Func " << function->getName()
<< "\n");
SmallVector<SILValue> valuesToComplete;
// Find all copy_addr instructions.
llvm::SmallSetVector<CopyAddrInst *, 8> deadCopies;
for (auto &block : *function) {
// Increment the instruction iterator only after calling
// tryOptimizeCopyIntoTemp because the instruction after CopyInst might be
// deleted, but copyInst itself won't be deleted until later.
for (auto ii = block.begin(); ii != block.end();) {
if (auto *copyInst = dyn_cast<CopyAddrInst>(&*ii)) {
// In case of success, this may delete instructions, but not the
// CopyInst itself.
tryOptimizeCopyIntoTemp(copyInst);
// Remove identity copies which either directly result from successfully
// calling tryOptimizeCopyIntoTemp or was created by an earlier
// iteration, where another copy_addr copied the temporary back to the
// source location.
if (copyInst->getSrc() == copyInst->getDest()) {
deadCopies.insert(copyInst);
}
++ii;
continue;
}
if (auto *si = dyn_cast<StoreInst>(&*ii)) {
auto stored = si->getSrc();
bool isOrHasEnum = stored->getType().isOrHasEnum();
auto nextIter = std::next(si->getIterator());
ii = tryOptimizeStoreIntoTemp(si);
// If the optimization was successful, and the stack loc was an enum
// type, collect the stored value for lifetime completion.
// This is needed because we can have incomplete address lifetimes on
// none/trivial paths for an enum type. Once we convert to value form,
// this will cause incomplete value lifetimes which can raise ownership
// verification errors, because we rely on linear lifetimes in OSSA.
if (ii == nextIter && isOrHasEnum) {
valuesToComplete.push_back(stored);
}
continue;
}
++ii;
}
}
auto callbacks = InstModCallbacks().onDelete(
[](SILInstruction *instToKill) {
// SimplifyInstruction is not in the business of removing
// copy_addr. If it were, then we would need to update deadCopies.
assert(!isa<CopyAddrInst>(instToKill));
instToKill->eraseFromParent();
}
);
DeadEndBlocks deBlocks(function);
for (auto *deadCopy : deadCopies) {
auto *srcInst = deadCopy->getSrc()->getDefiningInstruction();
deadCopy->eraseFromParent();
// Simplify any access scope markers that were only used by the dead
// copy_addr and other potentially unused addresses.
if (srcInst) {
simplifyAndReplaceAllSimplifiedUsesAndErase(srcInst, callbacks, &deBlocks);
}
}
if (!deadCopies.empty()) {
invalidateAnalysis(SILAnalysis::InvalidationKind::Instructions);
}
// Call the utlity to complete ossa lifetime.
OSSALifetimeCompletion completion(function, da->get(function));
for (auto it : valuesToComplete) {
completion.completeOSSALifetime(it, /* forceBoundaryCompletion */ true);
}
}
SILTransform *swift::createTempRValueOpt() { return new TempRValueOptPass(); }
|