| //===--- UnsafeGuaranteedPeephole.cpp - UnsafeGuaranteed Peephole ---------===// |
| // |
| // This source file is part of the Swift.org open source project |
| // |
| // Copyright (c) 2014 - 2017 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 |
| // |
| //===----------------------------------------------------------------------===// |
| // |
| // Optimize retain/release pairs based on Builtin.unsafeGuaranteed |
| // |
| // strong_retain %0 : $Foo |
| // %4 = builtin "unsafeGuaranteed"<Foo>(%0 : $Foo) : $(Foo, Builtin.Int8) |
| // %5 = tuple_extract %4 : $(Foo, Builtin.Int8), 0 |
| // %6 = tuple_extract %4 : $(Foo, Builtin.Int8), 1 |
| // %9 = function_ref @beep : $@convention(method) (@guaranteed Foo) -> () |
| // %10 = apply %9(%0) : $@convention(method) (@guaranteed Foo) -> () |
| // strong_release %5 : $Foo |
| // %12 = builtin "unsafeGuaranteedEnd"(%6 : $Builtin.Int8) : $() |
| // |
| // Based on the assertion that there is another reference to "%0" that keeps |
| // "%0" alive for the scope between the two builtin calls we can remove the |
| // retain/release pair and the builtins. |
| // |
| // %9 = function_ref @beep : $@convention(method) (@guaranteed Foo) -> () |
| // %10 = apply %9(%0) : $@convention(method) (@guaranteed Foo) -> () |
| // |
| //===----------------------------------------------------------------------===// |
| |
| #define DEBUG_TYPE "unsafe-guaranteed-peephole" |
| #include "swift/SILOptimizer/PassManager/Passes.h" |
| #include "swift/SIL/DebugUtils.h" |
| #include "swift/SIL/SILFunction.h" |
| #include "swift/SIL/SILInstruction.h" |
| #include "swift/SIL/SILModule.h" |
| #include "swift/SILOptimizer/Analysis/ARCAnalysis.h" |
| #include "swift/SILOptimizer/Analysis/DominanceAnalysis.h" |
| #include "swift/SILOptimizer/Analysis/RCIdentityAnalysis.h" |
| #include "swift/SILOptimizer/PassManager/Transforms.h" |
| #include "swift/SILOptimizer/Utils/Local.h" |
| |
| using namespace swift; |
| |
| /// Pattern match and remove "retain(self), apply(self), release(self)" calls |
| /// inbetween unsafeGuaranteed pairs and remove the retain/release pairs. |
| static void tryRemoveRetainReleasePairsBetween( |
| RCIdentityFunctionInfo &RCFI, SILInstruction *UnsafeGuaranteedI, |
| SILInstruction *Retain, SILInstruction *Release, |
| SILInstruction *UnsafeGuaranteedEndI) { |
| auto *BB = UnsafeGuaranteedI->getParent(); |
| if (BB != UnsafeGuaranteedEndI->getParent() || BB != Retain->getParent() || |
| BB != Release->getParent()) |
| return; |
| |
| SILInstruction *CandidateRetain = nullptr; |
| SmallVector<SILInstruction *, 8> InstsToDelete; |
| |
| SILBasicBlock::iterator It(UnsafeGuaranteedI); |
| while (It != BB->end() && It != SILBasicBlock::iterator(Release) && |
| It != SILBasicBlock::iterator(UnsafeGuaranteedEndI)) { |
| auto *CurInst = &*It++; |
| if (CurInst != Retain && |
| (isa<StrongRetainInst>(CurInst) || isa<RetainValueInst>(CurInst)) && |
| RCFI.getRCIdentityRoot(CurInst->getOperand(0)) == |
| SILValue(UnsafeGuaranteedI)) { |
| CandidateRetain = CurInst; |
| continue; |
| } |
| if (!CurInst->mayHaveSideEffects()) |
| continue; |
| |
| if (isa<DebugValueInst>(CurInst) || isa<DebugValueAddrInst>(CurInst)) |
| continue; |
| |
| if (isa<ApplyInst>(CurInst) || isa<PartialApplyInst>(CurInst)) |
| continue; |
| |
| if (CandidateRetain != nullptr && CurInst != Release && |
| (isa<StrongReleaseInst>(CurInst) || isa<ReleaseValueInst>(CurInst)) && |
| RCFI.getRCIdentityRoot(CurInst->getOperand(0)) == |
| SILValue(UnsafeGuaranteedI)) { |
| // Delete the retain/release pair. |
| InstsToDelete.push_back(CandidateRetain); |
| InstsToDelete.push_back(CurInst); |
| } |
| |
| // Otherwise, reset our scan. |
| CandidateRetain = nullptr; |
| } |
| for (auto *Inst: InstsToDelete) |
| Inst->eraseFromParent(); |
| } |
| |
| /// Remove retain/release pairs around builtin "unsafeGuaranteed" instruction |
| /// sequences. |
| static bool removeGuaranteedRetainReleasePairs(SILFunction &F, |
| RCIdentityFunctionInfo &RCIA, |
| PostDominanceAnalysis *PDA) { |
| DEBUG(llvm::dbgs() << "Running on function " << F.getName() << "\n"); |
| bool Changed = false; |
| |
| // Lazily compute post-dominance info only when we really need it. |
| PostDominanceInfo *PDI = nullptr; |
| |
| for (auto &BB : F) { |
| auto It = BB.begin(), End = BB.end(); |
| llvm::DenseMap<SILValue, SILInstruction *> LastRetain; |
| while (It != End) { |
| auto *CurInst = &*It; |
| ++It; |
| |
| // Memorize the last retain. |
| if (isa<StrongRetainInst>(CurInst) || isa<RetainValueInst>(CurInst)) { |
| LastRetain[RCIA.getRCIdentityRoot(CurInst->getOperand(0))] = CurInst; |
| continue; |
| } |
| |
| // Look for a builtin "unsafeGuaranteed" instruction. |
| auto *UnsafeGuaranteedI = dyn_cast<BuiltinInst>(CurInst); |
| if (!UnsafeGuaranteedI || !UnsafeGuaranteedI->getBuiltinKind() || |
| *UnsafeGuaranteedI->getBuiltinKind() != |
| BuiltinValueKind::UnsafeGuaranteed) |
| continue; |
| |
| auto Opd = UnsafeGuaranteedI->getOperand(0); |
| auto RCIdOpd = RCIA.getRCIdentityRoot(UnsafeGuaranteedI->getOperand(0)); |
| if (!LastRetain.count(RCIdOpd)) { |
| DEBUG(llvm::dbgs() << "LastRetain failed\n"); |
| continue; |
| } |
| |
| // This code is very conservative. Check that there is a matching retain |
| // before the unsafeGuaranteed builtin with only retains inbetween. |
| auto *LastRetainInst = LastRetain[RCIdOpd]; |
| auto NextInstIter = std::next(SILBasicBlock::iterator(LastRetainInst)); |
| while (NextInstIter != BB.end() && &*NextInstIter != CurInst && |
| (isa<RetainValueInst>(*NextInstIter) || |
| isa<StrongRetainInst>(*NextInstIter) || |
| !NextInstIter->mayHaveSideEffects() || |
| isa<DebugValueInst>(*NextInstIter) || |
| isa<DebugValueAddrInst>(*NextInstIter))) |
| ++NextInstIter; |
| if (&*NextInstIter != CurInst) { |
| DEBUG(llvm::dbgs() << "Last retain right before match failed\n"); |
| continue; |
| } |
| |
| DEBUG(llvm::dbgs() << "Saw " << *UnsafeGuaranteedI); |
| DEBUG(llvm::dbgs() << " with operand " << *Opd); |
| |
| // Match the reference and token result. |
| // %4 = builtin "unsafeGuaranteed"<Foo>(%0 : $Foo) |
| // %5 = tuple_extract %4 : $(Foo, Builtin.Int8), 0 |
| // %6 = tuple_extract %4 : $(Foo, Builtin.Int8), 1 |
| SILInstruction *UnsafeGuaranteedValue; |
| SILInstruction *UnsafeGuaranteedToken; |
| std::tie(UnsafeGuaranteedValue, UnsafeGuaranteedToken) = |
| getSingleUnsafeGuaranteedValueResult(UnsafeGuaranteedI); |
| |
| if (!UnsafeGuaranteedValue) { |
| DEBUG(llvm::dbgs() << " no single unsafeGuaranteed value use\n"); |
| continue; |
| } |
| |
| // Look for a builtin "unsafeGuaranteedEnd" instruction that uses the |
| // token. |
| // builtin "unsafeGuaranteedEnd"(%6 : $Builtin.Int8) : $() |
| auto *UnsafeGuaranteedEndI = |
| getUnsafeGuaranteedEndUser(UnsafeGuaranteedToken); |
| if (!UnsafeGuaranteedEndI) { |
| DEBUG(llvm::dbgs() << " no single unsafeGuaranteedEnd use found\n"); |
| continue; |
| } |
| |
| if (!PDI) |
| PDI = PDA->get(&F); |
| |
| // It needs to post-dominated the end instruction, since we need to remove |
| // the release along all paths to exit. |
| if (!PDI->properlyDominates(UnsafeGuaranteedEndI, UnsafeGuaranteedI)) |
| continue; |
| |
| |
| // Find the release to match with the unsafeGuaranteedValue. |
| auto &UnsafeGuaranteedEndBB = *UnsafeGuaranteedEndI->getParent(); |
| auto LastRelease = findReleaseToMatchUnsafeGuaranteedValue( |
| UnsafeGuaranteedEndI, UnsafeGuaranteedI, UnsafeGuaranteedValue, |
| UnsafeGuaranteedEndBB, RCIA); |
| if (!LastRelease) { |
| DEBUG(llvm::dbgs() << " no release before/after unsafeGuaranteedEnd found\n"); |
| continue; |
| } |
| |
| // Restart iteration before the earliest instruction we remove. |
| bool RestartAtBeginningOfBlock = false; |
| auto LastRetainIt = SILBasicBlock::iterator(LastRetainInst); |
| if (LastRetainIt != BB.begin()) { |
| It = std::prev(LastRetainIt); |
| } else RestartAtBeginningOfBlock = true; |
| |
| // Okay we found a post dominating release. Let's remove the |
| // retain/unsafeGuaranteed/release combo. |
| // |
| // Before we do this check whether there are any pairs of retain releases |
| // we can safely remove. |
| tryRemoveRetainReleasePairsBetween(RCIA, UnsafeGuaranteedI, |
| LastRetainInst, LastRelease, |
| UnsafeGuaranteedEndI); |
| |
| LastRetainInst->eraseFromParent(); |
| LastRelease->eraseFromParent(); |
| UnsafeGuaranteedEndI->eraseFromParent(); |
| deleteAllDebugUses(UnsafeGuaranteedValue); |
| deleteAllDebugUses(UnsafeGuaranteedToken); |
| deleteAllDebugUses(UnsafeGuaranteedI); |
| UnsafeGuaranteedValue->replaceAllUsesWith(Opd); |
| UnsafeGuaranteedValue->eraseFromParent(); |
| UnsafeGuaranteedToken->eraseFromParent(); |
| UnsafeGuaranteedI->replaceAllUsesWith(Opd); |
| UnsafeGuaranteedI->eraseFromParent(); |
| |
| if (RestartAtBeginningOfBlock) |
| It = BB.begin(); |
| |
| Changed = true; |
| } |
| } |
| return Changed; |
| } |
| |
| namespace { |
| class UnsafeGuaranteedPeephole : public swift::SILFunctionTransform { |
| |
| void run() override { |
| auto &RCIA = *getAnalysis<RCIdentityAnalysis>()->get(getFunction()); |
| auto *PostDominance = getAnalysis<PostDominanceAnalysis>(); |
| if (removeGuaranteedRetainReleasePairs(*getFunction(), RCIA, PostDominance)) |
| invalidateAnalysis(SILAnalysis::InvalidationKind::Instructions); |
| } |
| |
| }; |
| } // end anonymous namespace |
| |
| SILTransform *swift::createUnsafeGuaranteedPeephole() { |
| return new UnsafeGuaranteedPeephole(); |
| } |