From 9fbd318d36e618fb08fb53bb48b7c848e617a8a7 Mon Sep 17 00:00:00 2001 From: John McCall Date: Wed, 15 Jun 2011 23:37:01 +0000 Subject: [PATCH] The ARC language-specific optimizer. Credit to Dan Gohman. git-svn-id: https://llvm.org/svn/llvm-project/llvm/trunk@133108 91177308-0d34-0410-b5e6-96231b3b80d8 --- include/llvm/Analysis/Passes.h | 7 + include/llvm/InitializePasses.h | 4 + include/llvm/LinkAllPasses.h | 4 + include/llvm/Support/PassManagerBuilder.h | 2 + include/llvm/Transforms/Scalar.h | 18 + lib/CodeGen/LLVMTargetMachine.cpp | 4 + lib/Transforms/Scalar/CMakeLists.txt | 1 + lib/Transforms/Scalar/ObjCARC.cpp | 3520 +++++++++++++++++ lib/Transforms/Scalar/Scalar.cpp | 4 + test/Transforms/ObjCARC/basic.ll | 1898 +++++++++ test/Transforms/ObjCARC/cfg-hazards.ll | 86 + test/Transforms/ObjCARC/contract-marker.ll | 23 + .../ObjCARC/contract-storestrong-ivar.ll | 31 + .../ObjCARC/contract-storestrong.ll | 59 + test/Transforms/ObjCARC/contract-testcases.ll | 63 + test/Transforms/ObjCARC/contract.ll | 145 + test/Transforms/ObjCARC/dg.exp | 3 + test/Transforms/ObjCARC/expand.ll | 28 + test/Transforms/ObjCARC/gvn.ll | 21 + .../move-and-form-retain-autorelease.ll | 221 ++ .../ObjCARC/move-and-merge-autorelease.ll | 108 + test/Transforms/ObjCARC/post-inlining.ll | 48 + .../Transforms/ObjCARC/retain-not-declared.ll | 25 + test/Transforms/ObjCARC/rle-s2l.ll | 135 + test/Transforms/ObjCARC/rv.ll | 331 ++ test/Transforms/ObjCARC/weak-contract.ll | 14 + test/Transforms/ObjCARC/weak-copies.ll | 87 + test/Transforms/ObjCARC/weak.ll | 57 + 28 files changed, 6947 insertions(+) create mode 100644 lib/Transforms/Scalar/ObjCARC.cpp create mode 100644 test/Transforms/ObjCARC/basic.ll create mode 100644 test/Transforms/ObjCARC/cfg-hazards.ll create mode 100644 test/Transforms/ObjCARC/contract-marker.ll create mode 100644 test/Transforms/ObjCARC/contract-storestrong-ivar.ll create mode 100644 test/Transforms/ObjCARC/contract-storestrong.ll create mode 100644 test/Transforms/ObjCARC/contract-testcases.ll create mode 100644 test/Transforms/ObjCARC/contract.ll create mode 100644 test/Transforms/ObjCARC/dg.exp create mode 100644 test/Transforms/ObjCARC/expand.ll create mode 100644 test/Transforms/ObjCARC/gvn.ll create mode 100644 test/Transforms/ObjCARC/move-and-form-retain-autorelease.ll create mode 100644 test/Transforms/ObjCARC/move-and-merge-autorelease.ll create mode 100644 test/Transforms/ObjCARC/post-inlining.ll create mode 100644 test/Transforms/ObjCARC/retain-not-declared.ll create mode 100644 test/Transforms/ObjCARC/rle-s2l.ll create mode 100644 test/Transforms/ObjCARC/rv.ll create mode 100644 test/Transforms/ObjCARC/weak-contract.ll create mode 100644 test/Transforms/ObjCARC/weak-copies.ll create mode 100644 test/Transforms/ObjCARC/weak.ll diff --git a/include/llvm/Analysis/Passes.h b/include/llvm/Analysis/Passes.h index 0eff75fe2f8..a22bd12dec1 100644 --- a/include/llvm/Analysis/Passes.h +++ b/include/llvm/Analysis/Passes.h @@ -86,6 +86,13 @@ namespace llvm { // ImmutablePass *createTypeBasedAliasAnalysisPass(); + //===--------------------------------------------------------------------===// + // + // createObjCARCAliasAnalysisPass - This pass implements ObjC-ARC-based + // alias analysis. + // + ImmutablePass *createObjCARCAliasAnalysisPass(); + //===--------------------------------------------------------------------===// // // createProfileLoaderPass - This pass loads information from a profile dump diff --git a/include/llvm/InitializePasses.h b/include/llvm/InitializePasses.h index 372eaba74a7..de58252117c 100644 --- a/include/llvm/InitializePasses.h +++ b/include/llvm/InitializePasses.h @@ -160,6 +160,10 @@ void initializeModuleDebugInfoPrinterPass(PassRegistry&); void initializeNoAAPass(PassRegistry&); void initializeNoProfileInfoPass(PassRegistry&); void initializeNoPathProfileInfoPass(PassRegistry&); +void initializeObjCARCAliasAnalysisPass(PassRegistry&); +void initializeObjCARCExpandPass(PassRegistry&); +void initializeObjCARCContractPass(PassRegistry&); +void initializeObjCARCOptPass(PassRegistry&); void initializeOptimalEdgeProfilerPass(PassRegistry&); void initializeOptimizePHIsPass(PassRegistry&); void initializePEIPass(PassRegistry&); diff --git a/include/llvm/LinkAllPasses.h b/include/llvm/LinkAllPasses.h index c02e1615de1..c2ea8efb9a6 100644 --- a/include/llvm/LinkAllPasses.h +++ b/include/llvm/LinkAllPasses.h @@ -97,6 +97,10 @@ namespace { (void) llvm::createLowerSwitchPass(); (void) llvm::createNoAAPass(); (void) llvm::createNoProfileInfoPass(); + (void) llvm::createObjCARCAliasAnalysisPass(); + (void) llvm::createObjCARCExpandPass(); + (void) llvm::createObjCARCContractPass(); + (void) llvm::createObjCARCOptPass(); (void) llvm::createProfileEstimatorPass(); (void) llvm::createProfileVerifierPass(); (void) llvm::createPathProfileVerifierPass(); diff --git a/include/llvm/Support/PassManagerBuilder.h b/include/llvm/Support/PassManagerBuilder.h index 513bb887a04..31624db8139 100644 --- a/include/llvm/Support/PassManagerBuilder.h +++ b/include/llvm/Support/PassManagerBuilder.h @@ -188,6 +188,7 @@ public: MPM.add(createArgumentPromotionPass()); // Scalarize uninlined fn args // Start of function pass. + MPM.add(createObjCARCExpandPass()); // Canonicalize ObjC ARC code. // Break up aggregate allocas, using SSAUpdater. MPM.add(createScalarReplAggregatesPass(-1, false)); MPM.add(createEarlyCSEPass()); // Catch trivial redundancies @@ -223,6 +224,7 @@ public: MPM.add(createJumpThreadingPass()); // Thread jumps MPM.add(createCorrelatedValuePropagationPass()); MPM.add(createDeadStoreEliminationPass()); // Delete dead stores + MPM.add(createObjCARCOptPass()); // Objective-C ARC optimizations. MPM.add(createAggressiveDCEPass()); // Delete dead instructions MPM.add(createCFGSimplificationPass()); // Merge & remove BBs MPM.add(createInstructionCombiningPass()); // Clean up after everything. diff --git a/include/llvm/Transforms/Scalar.h b/include/llvm/Transforms/Scalar.h index de46a8d9840..e8304357518 100644 --- a/include/llvm/Transforms/Scalar.h +++ b/include/llvm/Transforms/Scalar.h @@ -336,6 +336,24 @@ Pass *createLowerAtomicPass(); // Pass *createCorrelatedValuePropagationPass(); +//===----------------------------------------------------------------------===// +// +// ObjCARCExpand - ObjC ARC preliminary simplifications. +// +Pass *createObjCARCExpandPass(); + +//===----------------------------------------------------------------------===// +// +// ObjCARCContract - Late ObjC ARC cleanups. +// +Pass *createObjCARCContractPass(); + +//===----------------------------------------------------------------------===// +// +// ObjCARCOpt - ObjC ARC optimization. +// +Pass *createObjCARCOptPass(); + //===----------------------------------------------------------------------===// // // InstructionSimplifier - Remove redundant instructions. diff --git a/lib/CodeGen/LLVMTargetMachine.cpp b/lib/CodeGen/LLVMTargetMachine.cpp index 589d0a9a671..b98fbed695b 100644 --- a/lib/CodeGen/LLVMTargetMachine.cpp +++ b/lib/CodeGen/LLVMTargetMachine.cpp @@ -303,6 +303,10 @@ bool LLVMTargetMachine::addCommonCodeGenPasses(PassManagerBase &PM, if (!DisableVerify) PM.add(createVerifierPass()); + // Simplify ObjC ARC code. This is done late because it makes re-optimization + // difficult. + PM.add(createObjCARCContractPass()); + // Run loop strength reduction before anything else. if (OptLevel != CodeGenOpt::None && !DisableLSR) { PM.add(createLoopStrengthReducePass(getTargetLowering())); diff --git a/lib/Transforms/Scalar/CMakeLists.txt b/lib/Transforms/Scalar/CMakeLists.txt index fcf914f8baa..c223da60e0f 100644 --- a/lib/Transforms/Scalar/CMakeLists.txt +++ b/lib/Transforms/Scalar/CMakeLists.txt @@ -20,6 +20,7 @@ add_llvm_library(LLVMScalarOpts LoopUnswitch.cpp LowerAtomic.cpp MemCpyOptimizer.cpp + ObjCARC.cpp Reassociate.cpp Reg2Mem.cpp SCCP.cpp diff --git a/lib/Transforms/Scalar/ObjCARC.cpp b/lib/Transforms/Scalar/ObjCARC.cpp new file mode 100644 index 00000000000..e65e2853c2a --- /dev/null +++ b/lib/Transforms/Scalar/ObjCARC.cpp @@ -0,0 +1,3520 @@ +//===- ObjCARC.cpp - ObjC ARC Optimization --------------------------------===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// +// +// This file defines ObjC ARC optimizations. ARC stands for +// Automatic Reference Counting and is a system for managing reference counts +// for objects in Objective C. +// +// The optimizations performed include elimination of redundant, partially +// redundant, and inconsequential reference count operations, elimination of +// redundant weak pointer operations, pattern-matching and replacement of +// low-level operations into higher-level operations, and numerous minor +// simplifications. +// +// This file also defines a simple ARC-aware AliasAnalysis. +// +// WARNING: This file knows about certain library functions. It recognizes them +// by name, and hardwires knowedge of their semantics. +// +// WARNING: This file knows about how certain Objective-C library functions are +// used. Naive LLVM IR transformations which would otherwise be +// behavior-preserving may break these assumptions. +// +//===----------------------------------------------------------------------===// + +#define DEBUG_TYPE "objc-arc" +#include "llvm/Function.h" +#include "llvm/Intrinsics.h" +#include "llvm/GlobalVariable.h" +#include "llvm/DerivedTypes.h" +#include "llvm/Analysis/ValueTracking.h" +#include "llvm/Transforms/Utils/Local.h" +#include "llvm/Support/CallSite.h" +#include "llvm/Support/CommandLine.h" +#include "llvm/ADT/StringSwitch.h" +#include "llvm/ADT/DenseMap.h" +#include "llvm/ADT/STLExtras.h" +using namespace llvm; + +// A handy option to enable/disable all optimizations in this file. +static cl::opt EnableARCOpts("enable-objc-arc-opts", cl::init(true)); + +//===----------------------------------------------------------------------===// +// Misc. Utilities +//===----------------------------------------------------------------------===// + +namespace { + /// MapVector - An associative container with fast insertion-order + /// (deterministic) iteration over its elements. Plus the special + /// blot operation. + template + class MapVector { + /// Map - Map keys to indices in Vector. + typedef DenseMap MapTy; + MapTy Map; + + /// Vector - Keys and values. + typedef std::vector > VectorTy; + VectorTy Vector; + + public: + typedef typename VectorTy::iterator iterator; + typedef typename VectorTy::const_iterator const_iterator; + iterator begin() { return Vector.begin(); } + iterator end() { return Vector.end(); } + const_iterator begin() const { return Vector.begin(); } + const_iterator end() const { return Vector.end(); } + +#ifdef XDEBUG + ~MapVector() { + assert(Vector.size() >= Map.size()); // May differ due to blotting. + for (typename MapTy::const_iterator I = Map.begin(), E = Map.end(); + I != E; ++I) { + assert(I->second < Vector.size()); + assert(Vector[I->second].first == I->first); + } + for (typename VectorTy::const_iterator I = Vector.begin(), + E = Vector.end(); I != E; ++I) + assert(!I->first || + (Map.count(I->first) && + Map[I->first] == size_t(I - Vector.begin()))); + } +#endif + + ValueT &operator[](KeyT Arg) { + std::pair Pair = + Map.insert(std::make_pair(Arg, size_t(0))); + if (Pair.second) { + Pair.first->second = Vector.size(); + Vector.push_back(std::make_pair(Arg, ValueT())); + return Vector.back().second; + } + return Vector[Pair.first->second].second; + } + + std::pair + insert(const std::pair &InsertPair) { + std::pair Pair = + Map.insert(std::make_pair(InsertPair.first, size_t(0))); + if (Pair.second) { + Pair.first->second = Vector.size(); + Vector.push_back(InsertPair); + return std::make_pair(llvm::prior(Vector.end()), true); + } + return std::make_pair(Vector.begin() + Pair.first->second, false); + } + + const_iterator find(KeyT Key) const { + typename MapTy::const_iterator It = Map.find(Key); + if (It == Map.end()) return Vector.end(); + return Vector.begin() + It->second; + } + + /// blot - This is similar to erase, but instead of removing the element + /// from the vector, it just zeros out the key in the vector. This leaves + /// iterators intact, but clients must be prepared for zeroed-out keys when + /// iterating. + void blot(KeyT Key) { + typename MapTy::iterator It = Map.find(Key); + if (It == Map.end()) return; + Vector[It->second].first = KeyT(); + Map.erase(It); + } + + void clear() { + Map.clear(); + Vector.clear(); + } + }; +} + +//===----------------------------------------------------------------------===// +// ARC Utilities. +//===----------------------------------------------------------------------===// + +namespace { + /// InstructionClass - A simple classification for instructions. + enum InstructionClass { + IC_Retain, ///< objc_retain + IC_RetainRV, ///< objc_retainAutoreleasedReturnValue + IC_RetainBlock, ///< objc_retainBlock + IC_Release, ///< objc_release + IC_Autorelease, ///< objc_autorelease + IC_AutoreleaseRV, ///< objc_autoreleaseReturnValue + IC_AutoreleasepoolPush, ///< objc_autoreleasePoolPush + IC_AutoreleasepoolPop, ///< objc_autoreleasePoolPop + IC_NoopCast, ///< objc_retainedObject, etc. + IC_FusedRetainAutorelease, ///< objc_retainAutorelease + IC_FusedRetainAutoreleaseRV, ///< objc_retainAutoreleaseReturnValue + IC_LoadWeakRetained, ///< objc_loadWeakRetained (primitive) + IC_StoreWeak, ///< objc_storeWeak (primitive) + IC_InitWeak, ///< objc_initWeak (derived) + IC_LoadWeak, ///< objc_loadWeak (derived) + IC_MoveWeak, ///< objc_moveWeak (derived) + IC_CopyWeak, ///< objc_copyWeak (derived) + IC_DestroyWeak, ///< objc_destroyWeak (derived) + IC_CallOrUser, ///< could call objc_release and/or "use" pointers + IC_Call, ///< could call objc_release + IC_User, ///< could "use" a pointer + IC_None ///< anything else + }; +} + +/// IsPotentialUse - Test whether the given value is possible a +/// reference-counted pointer. +static bool IsPotentialUse(const Value *Op) { + // Pointers to static or stack storage are not reference-counted pointers. + if (isa(Op) || isa(Op)) + return false; + // Special arguments are not reference-counted. + if (const Argument *Arg = dyn_cast(Op)) + if (Arg->hasByValAttr() || + Arg->hasNestAttr() || + Arg->hasStructRetAttr()) + return false; + // Only consider values with pointer types, and not function pointers. + const PointerType *Ty = dyn_cast(Op->getType()); + if (!Ty || isa(Ty->getElementType())) + return false; + // Conservatively assume anything else is a potential use. + return true; +} + +/// GetCallSiteClass - Helper for GetInstructionClass. Determines what kind +/// of construct CS is. +static InstructionClass GetCallSiteClass(ImmutableCallSite CS) { + for (ImmutableCallSite::arg_iterator I = CS.arg_begin(), E = CS.arg_end(); + I != E; ++I) + if (IsPotentialUse(*I)) + return CS.onlyReadsMemory() ? IC_User : IC_CallOrUser; + + return CS.onlyReadsMemory() ? IC_None : IC_Call; +} + +/// GetFunctionClass - Determine if F is one of the special known Functions. +/// If it isn't, return IC_CallOrUser. +static InstructionClass GetFunctionClass(const Function *F) { + Function::const_arg_iterator AI = F->arg_begin(), AE = F->arg_end(); + + // No arguments. + if (AI == AE) + return StringSwitch(F->getName()) + .Case("objc_autoreleasePoolPush", IC_AutoreleasepoolPush) + .Default(IC_CallOrUser); + + // One argument. + const Argument *A0 = AI++; + if (AI == AE) + // Argument is a pointer. + if (const PointerType *PTy = dyn_cast(A0->getType())) { + const Type *ETy = PTy->getElementType(); + // Argument is i8*. + if (ETy->isIntegerTy(8)) + return StringSwitch(F->getName()) + .Case("objc_retain", IC_Retain) + .Case("objc_retainAutoreleasedReturnValue", IC_RetainRV) + .Case("objc_retainBlock", IC_RetainBlock) + .Case("objc_release", IC_Release) + .Case("objc_autorelease", IC_Autorelease) + .Case("objc_autoreleaseReturnValue", IC_AutoreleaseRV) + .Case("objc_autoreleasePoolPop", IC_AutoreleasepoolPop) + .Case("objc_retainedObject", IC_NoopCast) + .Case("objc_unretainedObject", IC_NoopCast) + .Case("objc_unretainedPointer", IC_NoopCast) + .Case("objc_retain_autorelease", IC_FusedRetainAutorelease) + .Case("objc_retainAutorelease", IC_FusedRetainAutorelease) + .Case("objc_retainAutoreleaseReturnValue",IC_FusedRetainAutoreleaseRV) + .Default(IC_CallOrUser); + + // Argument is i8** + if (const PointerType *Pte = dyn_cast(ETy)) + if (Pte->getElementType()->isIntegerTy(8)) + return StringSwitch(F->getName()) + .Case("objc_loadWeakRetained", IC_LoadWeakRetained) + .Case("objc_loadWeak", IC_LoadWeak) + .Case("objc_destroyWeak", IC_DestroyWeak) + .Default(IC_CallOrUser); + } + + // Two arguments, first is i8**. + const Argument *A1 = AI++; + if (AI == AE) + if (const PointerType *PTy = dyn_cast(A0->getType())) + if (const PointerType *Pte = dyn_cast(PTy->getElementType())) + if (Pte->getElementType()->isIntegerTy(8)) + if (const PointerType *PTy1 = dyn_cast(A1->getType())) { + const Type *ETy1 = PTy1->getElementType(); + // Second argument is i8* + if (ETy1->isIntegerTy(8)) + return StringSwitch(F->getName()) + .Case("objc_storeWeak", IC_StoreWeak) + .Case("objc_initWeak", IC_InitWeak) + .Default(IC_CallOrUser); + // Second argument is i8**. + if (const PointerType *Pte1 = dyn_cast(ETy1)) + if (Pte1->getElementType()->isIntegerTy(8)) + return StringSwitch(F->getName()) + .Case("objc_moveWeak", IC_MoveWeak) + .Case("objc_copyWeak", IC_CopyWeak) + .Default(IC_CallOrUser); + } + + // Anything else. + return IC_CallOrUser; +} + +/// GetInstructionClass - Determine what kind of construct V is. +static InstructionClass GetInstructionClass(const Value *V) { + if (const Instruction *I = dyn_cast(V)) { + // Any instruction other than bitcast and gep with a pointer operand have a + // use of an objc pointer. Bitcasts, GEPs, Selects, PHIs transfer a pointer + // to a subsequent use, rather than using it themselves, in this sense. + // As a short cut, several other opcodes are known to have no pointer + // operands of interest. And ret is never followed by a release, so it's + // not interesting to examine. + switch (I->getOpcode()) { + case Instruction::Call: { + const CallInst *CI = cast(I); + // Check for calls to special functions. + if (const Function *F = CI->getCalledFunction()) { + InstructionClass Class = GetFunctionClass(F); + if (Class != IC_CallOrUser) + return Class; + + // None of the intrinsic functions do objc_release. For intrinsics, the + // only question is whether or not they may be users. + switch (F->getIntrinsicID()) { + case 0: break; + case Intrinsic::bswap: case Intrinsic::ctpop: + case Intrinsic::ctlz: case Intrinsic::cttz: + case Intrinsic::returnaddress: case Intrinsic::frameaddress: + case Intrinsic::stacksave: case Intrinsic::stackrestore: + case Intrinsic::vastart: case Intrinsic::vacopy: case Intrinsic::vaend: + // Don't let dbg info affect our results. + case Intrinsic::dbg_declare: case Intrinsic::dbg_value: + // Short cut: Some intrinsics obviously don't use ObjC pointers. + return IC_None; + default: + for (Function::const_arg_iterator AI = F->arg_begin(), + AE = F->arg_end(); AI != AE; ++AI) + if (IsPotentialUse(AI)) + return IC_User; + return IC_None; + } + } + return GetCallSiteClass(CI); + } + case Instruction::Invoke: + return GetCallSiteClass(cast(I)); + case Instruction::BitCast: + case Instruction::GetElementPtr: + case Instruction::Select: case Instruction::PHI: + case Instruction::Ret: case Instruction::Br: + case Instruction::Switch: case Instruction::IndirectBr: + case Instruction::Alloca: case Instruction::VAArg: + case Instruction::Add: case Instruction::FAdd: + case Instruction::Sub: case Instruction::FSub: + case Instruction::Mul: case Instruction::FMul: + case Instruction::SDiv: case Instruction::UDiv: case Instruction::FDiv: + case Instruction::SRem: case Instruction::URem: case Instruction::FRem: + case Instruction::Shl: case Instruction::LShr: case Instruction::AShr: + case Instruction::And: case Instruction::Or: case Instruction::Xor: + case Instruction::SExt: case Instruction::ZExt: case Instruction::Trunc: + case Instruction::IntToPtr: case Instruction::FCmp: + case Instruction::FPTrunc: case Instruction::FPExt: + case Instruction::FPToUI: case Instruction::FPToSI: + case Instruction::UIToFP: case Instruction::SIToFP: + case Instruction::InsertElement: case Instruction::ExtractElement: + case Instruction::ShuffleVector: + case Instruction::ExtractValue: + break; + case Instruction::ICmp: + // Comparing a pointer with null, or any other constant, isn't an + // interesting use, because we don't care what the pointer points to, or + // about the values of any other dynamic reference-counted pointers. + if (IsPotentialUse(I->getOperand(1))) + return IC_User; + break; + default: + // For anything else, check all the operands. + for (User::const_op_iterator OI = I->op_begin(), OE = I->op_end(); + OI != OE; ++OI) + if (IsPotentialUse(*OI)) + return IC_User; + } + } + + // Otherwise, it's totally inert for ARC purposes. + return IC_None; +} + +/// GetBasicInstructionClass - Determine what kind of construct V is. This is +/// similar to GetInstructionClass except that it only detects objc runtine +/// calls. This allows it to be faster. +static InstructionClass GetBasicInstructionClass(const Value *V) { + if (const CallInst *CI = dyn_cast(V)) { + if (const Function *F = CI->getCalledFunction()) + return GetFunctionClass(F); + // Otherwise, be conservative. + return IC_CallOrUser; + } + + // Otherwise, be conservative. + return IC_User; +} + +/// IsRetain - Test if the the given class is objc_retain or +/// equivalent. +static bool IsRetain(InstructionClass Class) { + return Class == IC_Retain || + Class == IC_RetainRV; +} + +/// IsAutorelease - Test if the the given class is objc_autorelease or +/// equivalent. +static bool IsAutorelease(InstructionClass Class) { + return Class == IC_Autorelease || + Class == IC_AutoreleaseRV; +} + +/// IsForwarding - Test if the given class represents instructions which return +/// their argument verbatim. +static bool IsForwarding(InstructionClass Class) { + // objc_retainBlock technically doesn't always return its argument + // verbatim, but it doesn't matter for our purposes here. + return Class == IC_Retain || + Class == IC_RetainRV || + Class == IC_Autorelease || + Class == IC_AutoreleaseRV || + Class == IC_RetainBlock || + Class == IC_NoopCast; +} + +/// IsNoopOnNull - Test if the given class represents instructions which do +/// nothing if passed a null pointer. +static bool IsNoopOnNull(InstructionClass Class) { + return Class == IC_Retain || + Class == IC_RetainRV || + Class == IC_Release || + Class == IC_Autorelease || + Class == IC_AutoreleaseRV || + Class == IC_RetainBlock; +} + +/// IsAlwaysTail - Test if the given class represents instructions which are +/// always safe to mark with the "tail" keyword. +static bool IsAlwaysTail(InstructionClass Class) { + // IC_RetainBlock may be given a stack argument. + return Class == IC_Retain || + Class == IC_RetainRV || + Class == IC_Autorelease || + Class == IC_AutoreleaseRV; +} + +/// IsNoThrow - Test if the given class represents instructions which are always +/// safe to mark with the nounwind attribute.. +static bool IsNoThrow(InstructionClass Class) { + return Class == IC_Retain || + Class == IC_RetainRV || + Class == IC_RetainBlock || + Class == IC_Release || + Class == IC_Autorelease || + Class == IC_AutoreleaseRV || + Class == IC_AutoreleasepoolPush || + Class == IC_AutoreleasepoolPop; +} + +/// EraseInstruction - Erase the given instruction. ObjC calls return their +/// argument verbatim, so if it's such a call and the return value has users, +/// replace them with the argument value. +static void EraseInstruction(Instruction *CI) { + Value *OldArg = cast(CI)->getArgOperand(0); + + bool Unused = CI->use_empty(); + + if (!Unused) { + // Replace the return value with the argument. + assert(IsForwarding(GetBasicInstructionClass(CI)) && + "Can't delete non-forwarding instruction with users!"); + CI->replaceAllUsesWith(OldArg); + } + + CI->eraseFromParent(); + + if (Unused) + RecursivelyDeleteTriviallyDeadInstructions(OldArg); +} + +/// GetUnderlyingObjCPtr - This is a wrapper around getUnderlyingObject which +/// also knows how to look through objc_retain and objc_autorelease calls, which +/// we know to return their argument verbatim. +static const Value *GetUnderlyingObjCPtr(const Value *V) { + for (;;) { + V = GetUnderlyingObject(V); + if (!IsForwarding(GetBasicInstructionClass(V))) + break; + V = cast(V)->getArgOperand(0); + } + + return V; +} + +/// StripPointerCastsAndObjCCalls - This is a wrapper around +/// Value::stripPointerCasts which also knows how to look through objc_retain +/// and objc_autorelease calls, which we know to return their argument verbatim. +static const Value *StripPointerCastsAndObjCCalls(const Value *V) { + for (;;) { + V = V->stripPointerCasts(); + if (!IsForwarding(GetBasicInstructionClass(V))) + break; + V = cast(V)->getArgOperand(0); + } + return V; +} + +/// StripPointerCastsAndObjCCalls - This is a wrapper around +/// Value::stripPointerCasts which also knows how to look through objc_retain +/// and objc_autorelease calls, which we know to return their argument verbatim. +static Value *StripPointerCastsAndObjCCalls(Value *V) { + for (;;) { + V = V->stripPointerCasts(); + if (!IsForwarding(GetBasicInstructionClass(V))) + break; + V = cast(V)->getArgOperand(0); + } + return V; +} + +/// GetObjCArg - Assuming the given instruction is one of the special calls such +/// as objc_retain or objc_release, return the argument value, stripped of no-op +/// casts and forwarding calls. +static Value *GetObjCArg(Value *Inst) { + return StripPointerCastsAndObjCCalls(cast(Inst)->getArgOperand(0)); +} + +/// IsObjCIdentifiedObject - This is similar to AliasAnalysis' +/// isObjCIdentifiedObject, except that it uses special knowledge of +/// ObjC conventions... +static bool IsObjCIdentifiedObject(const Value *V) { + // Assume that call results and arguments have their own "provenance". + // Constants (including GlobalVariables) and Allocas are never + // reference-counted. + if (isa(V) || isa(V) || + isa(V) || isa(V) || + isa(V)) + return true; + + if (const LoadInst *LI = dyn_cast(V)) { + const Value *Pointer = + StripPointerCastsAndObjCCalls(LI->getPointerOperand()); + if (const GlobalVariable *GV = dyn_cast(Pointer)) { + StringRef Name = GV->getName(); + // These special variables are known to hold values which are not + // reference-counted pointers. + if (Name.startswith("\01L_OBJC_SELECTOR_REFERENCES_") || + Name.startswith("\01L_OBJC_CLASSLIST_REFERENCES_") || + Name.startswith("\01L_OBJC_CLASSLIST_SUP_REFS_$_") || + Name.startswith("\01L_OBJC_METH_VAR_NAME_") || + Name.startswith("\01l_objc_msgSend_fixup_")) + return true; + } + } + + return false; +} + +/// FindSingleUseIdentifiedObject - This is similar to +/// StripPointerCastsAndObjCCalls but it stops as soon as it finds a value +/// with multiple uses. +static const Value *FindSingleUseIdentifiedObject(const Value *Arg) { + if (Arg->hasOneUse()) { + if (const BitCastInst *BC = dyn_cast(Arg)) + return FindSingleUseIdentifiedObject(BC->getOperand(0)); + if (const GetElementPtrInst *GEP = dyn_cast(Arg)) + if (GEP->hasAllZeroIndices()) + return FindSingleUseIdentifiedObject(GEP->getPointerOperand()); + if (IsForwarding(GetBasicInstructionClass(Arg))) + return FindSingleUseIdentifiedObject( + cast(Arg)->getArgOperand(0)); + if (!IsObjCIdentifiedObject(Arg)) + return 0; + return Arg; + } + + // If we found an identifiable object but it has multiple uses, but they + // are trivial uses, we can still consider this to be a single-use + // value. + if (IsObjCIdentifiedObject(Arg)) { + for (Value::const_use_iterator UI = Arg->use_begin(), UE = Arg->use_end(); + UI != UE; ++UI) { + const User *U = *UI; + if (!U->use_empty() || StripPointerCastsAndObjCCalls(U) != Arg) + return 0; + } + + return Arg; + } + + return 0; +} + +//===----------------------------------------------------------------------===// +// ARC AliasAnalysis. +//===----------------------------------------------------------------------===// + +#include "llvm/Pass.h" +#include "llvm/Analysis/AliasAnalysis.h" +#include "llvm/Analysis/Passes.h" + +namespace { + /// ObjCARCAliasAnalysis - This is a simple alias analysis + /// implementation that uses knowledge of ARC constructs to answer queries. + /// + /// TODO: This class could be generalized to know about other ObjC-specific + /// tricks. Such as knowing that ivars in the non-fragile ABI are non-aliasing + /// even though their offsets are dynamic. + class ObjCARCAliasAnalysis : public ImmutablePass, + public AliasAnalysis { + public: + static char ID; // Class identification, replacement for typeinfo + ObjCARCAliasAnalysis() : ImmutablePass(ID) { + initializeObjCARCAliasAnalysisPass(*PassRegistry::getPassRegistry()); + } + + private: + virtual void initializePass() { + InitializeAliasAnalysis(this); + } + + /// getAdjustedAnalysisPointer - This method is used when a pass implements + /// an analysis interface through multiple inheritance. If needed, it + /// should override this to adjust the this pointer as needed for the + /// specified pass info. + virtual void *getAdjustedAnalysisPointer(const void *PI) { + if (PI == &AliasAnalysis::ID) + return (AliasAnalysis*)this; + return this; + } + + virtual void getAnalysisUsage(AnalysisUsage &AU) const; + virtual AliasResult alias(const Location &LocA, const Location &LocB); + virtual bool pointsToConstantMemory(const Location &Loc, bool OrLocal); + virtual ModRefBehavior getModRefBehavior(ImmutableCallSite CS); + virtual ModRefBehavior getModRefBehavior(const Function *F); + virtual ModRefResult getModRefInfo(ImmutableCallSite CS, + const Location &Loc); + virtual ModRefResult getModRefInfo(ImmutableCallSite CS1, + ImmutableCallSite CS2); + }; +} // End of anonymous namespace + +// Register this pass... +char ObjCARCAliasAnalysis::ID = 0; +INITIALIZE_AG_PASS(ObjCARCAliasAnalysis, AliasAnalysis, "objc-arc-aa", + "ObjC-ARC-Based Alias Analysis", false, true, false) + +ImmutablePass *llvm::createObjCARCAliasAnalysisPass() { + return new ObjCARCAliasAnalysis(); +} + +void +ObjCARCAliasAnalysis::getAnalysisUsage(AnalysisUsage &AU) const { + AU.setPreservesAll(); + AliasAnalysis::getAnalysisUsage(AU); +} + +AliasAnalysis::AliasResult +ObjCARCAliasAnalysis::alias(const Location &LocA, const Location &LocB) { + if (!EnableARCOpts) + return AliasAnalysis::alias(LocA, LocB); + + // First, strip off no-ops, including ObjC-specific no-ops, and try making a + // precise alias query. + const Value *SA = StripPointerCastsAndObjCCalls(LocA.Ptr); + const Value *SB = StripPointerCastsAndObjCCalls(LocB.Ptr); + AliasResult Result = + AliasAnalysis::alias(Location(SA, LocA.Size, LocA.TBAATag), + Location(SB, LocB.Size, LocB.TBAATag)); + if (Result != MayAlias) + return Result; + + // If that failed, climb to the underlying object, including climbing through + // ObjC-specific no-ops, and try making an imprecise alias query. + const Value *UA = GetUnderlyingObjCPtr(SA); + const Value *UB = GetUnderlyingObjCPtr(SB); + if (UA != SA || UB != SB) { + Result = AliasAnalysis::alias(Location(UA), Location(UB)); + // We can't use MustAlias or PartialAlias results here because + // GetUnderlyingObjCPtr may return an offsetted pointer value. + if (Result == NoAlias) + return NoAlias; + } + + // If that failed, fail. We don't need to chain here, since that's covered + // by the earlier precise query. + return MayAlias; +} + +bool +ObjCARCAliasAnalysis::pointsToConstantMemory(const Location &Loc, + bool OrLocal) { + if (!EnableARCOpts) + return AliasAnalysis::pointsToConstantMemory(Loc, OrLocal); + + // First, strip off no-ops, including ObjC-specific no-ops, and try making + // a precise alias query. + const Value *S = StripPointerCastsAndObjCCalls(Loc.Ptr); + if (AliasAnalysis::pointsToConstantMemory(Location(S, Loc.Size, Loc.TBAATag), + OrLocal)) + return true; + + // If that failed, climb to the underlying object, including climbing through + // ObjC-specific no-ops, and try making an imprecise alias query. + const Value *U = GetUnderlyingObjCPtr(S); + if (U != S) + return AliasAnalysis::pointsToConstantMemory(Location(U), OrLocal); + + // If that failed, fail. We don't need to chain here, since that's covered + // by the earlier precise query. + return false; +} + +AliasAnalysis::ModRefBehavior +ObjCARCAliasAnalysis::getModRefBehavior(ImmutableCallSite CS) { + // We have nothing to do. Just chain to the next AliasAnalysis. + return AliasAnalysis::getModRefBehavior(CS); +} + +AliasAnalysis::ModRefBehavior +ObjCARCAliasAnalysis::getModRefBehavior(const Function *F) { + if (!EnableARCOpts) + return AliasAnalysis::getModRefBehavior(F); + + switch (GetFunctionClass(F)) { + case IC_NoopCast: + return DoesNotAccessMemory; + default: + break; + } + + return AliasAnalysis::getModRefBehavior(F); +} + +AliasAnalysis::ModRefResult +ObjCARCAliasAnalysis::getModRefInfo(ImmutableCallSite CS, const Location &Loc) { + if (!EnableARCOpts) + return AliasAnalysis::getModRefInfo(CS, Loc); + + switch (GetBasicInstructionClass(CS.getInstruction())) { + case IC_Retain: + case IC_RetainRV: + case IC_RetainBlock: + case IC_Autorelease: + case IC_AutoreleaseRV: + case IC_NoopCast: + case IC_AutoreleasepoolPush: + case IC_FusedRetainAutorelease: + case IC_FusedRetainAutoreleaseRV: + // These functions don't access any memory visible to the compiler. + return NoModRef; + default: + break; + } + + return AliasAnalysis::getModRefInfo(CS, Loc); +} + +AliasAnalysis::ModRefResult +ObjCARCAliasAnalysis::getModRefInfo(ImmutableCallSite CS1, + ImmutableCallSite CS2) { + // TODO: Theoretically we could check for dependencies between objc_* calls + // and OnlyAccessesArgumentPointees calls or other well-behaved calls. + return AliasAnalysis::getModRefInfo(CS1, CS2); +} + +//===----------------------------------------------------------------------===// +// ARC expansion. +//===----------------------------------------------------------------------===// + +#include "llvm/Support/InstIterator.h" +#include "llvm/Transforms/Scalar.h" + +namespace { + /// ObjCARCExpand - Early ARC transformations. + class ObjCARCExpand : public FunctionPass { + virtual void getAnalysisUsage(AnalysisUsage &AU) const; + virtual bool runOnFunction(Function &F); + + public: + static char ID; + ObjCARCExpand() : FunctionPass(ID) { + initializeObjCARCExpandPass(*PassRegistry::getPassRegistry()); + } + }; +} + +char ObjCARCExpand::ID = 0; +INITIALIZE_PASS(ObjCARCExpand, + "objc-arc-expand", "ObjC ARC expansion", false, false) + +Pass *llvm::createObjCARCExpandPass() { + return new ObjCARCExpand(); +} + +void ObjCARCExpand::getAnalysisUsage(AnalysisUsage &AU) const { + AU.setPreservesCFG(); +} + +bool ObjCARCExpand::runOnFunction(Function &F) { + if (!EnableARCOpts) + return false; + + bool Changed = false; + + for (inst_iterator I = inst_begin(&F), E = inst_end(&F); I != E; ++I) { + Instruction *Inst = &*I; + + switch (GetBasicInstructionClass(Inst)) { + case IC_Retain: + case IC_RetainRV: + case IC_Autorelease: + case IC_AutoreleaseRV: + case IC_FusedRetainAutorelease: + case IC_FusedRetainAutoreleaseRV: + // These calls return their argument verbatim, as a low-level + // optimization. However, this makes high-level optimizations + // harder. Undo any uses of this optimization that the front-end + // emitted here. We'll redo them in a later pass. + Changed = true; + Inst->replaceAllUsesWith(cast(Inst)->getArgOperand(0)); + break; + default: + break; + } + } + + return Changed; +} + +//===----------------------------------------------------------------------===// +// ARC optimization. +//===----------------------------------------------------------------------===// + +// TODO: On code like this: +// +// objc_retain(%x) +// stuff_that_cannot_release() +// objc_autorelease(%x) +// stuff_that_cannot_release() +// objc_retain(%x) +// stuff_that_cannot_release() +// objc_autorelease(%x) +// +// The second retain and autorelease can be deleted. + +// TODO: It should be possible to delete +// objc_autoreleasePoolPush and objc_autoreleasePoolPop +// pairs if nothing is actually autoreleased between them. Also, autorelease +// calls followed by objc_autoreleasePoolPop calls (perhaps in ObjC++ code +// after inlining) can be turned into plain release calls. + +// TODO: Critical-edge splitting. If the optimial insertion point is +// a critical edge, the current algorithm has to fail, because it doesn't +// know how to split edges. It should be possible to make the optimizer +// think in terms of edges, rather than blocks, and then split critical +// edges on demand. + +// TODO: OptimizeSequences could generalized to be Interprocedural. + +// TODO: Recognize that a bunch of other objc runtime calls have +// non-escaping arguments and non-releasing arguments, and may be +// non-autoreleasing. + +// TODO: Sink autorelease calls as far as possible. Unfortunately we +// usually can't sink them past other calls, which would be the main +// case where it would be useful. + +#include "llvm/GlobalAlias.h" +#include "llvm/Module.h" +#include "llvm/Constants.h" +#include "llvm/LLVMContext.h" +#include "llvm/Support/ErrorHandling.h" +#include "llvm/Support/CFG.h" +#include "llvm/ADT/PostOrderIterator.h" +#include "llvm/ADT/Statistic.h" + +STATISTIC(NumNoops, "Number of no-op objc calls eliminated"); +STATISTIC(NumPartialNoops, "Number of partially no-op objc calls eliminated"); +STATISTIC(NumAutoreleases,"Number of autoreleases converted to releases"); +STATISTIC(NumRets, "Number of return value forwarding " + "retain+autoreleaes eliminated"); +STATISTIC(NumRRs, "Number of retain+release paths eliminated"); +STATISTIC(NumPeeps, "Number of calls peephole-optimized"); + +namespace { + /// ProvenanceAnalysis - This is similar to BasicAliasAnalysis, and it + /// uses many of the same techniques, except it uses special ObjC-specific + /// reasoning about pointer relationships. + class ProvenanceAnalysis { + AliasAnalysis *AA; + + typedef std::pair ValuePairTy; + typedef DenseMap CachedResultsTy; + CachedResultsTy CachedResults; + + bool relatedCheck(const Value *A, const Value *B); + bool relatedSelect(const SelectInst *A, const Value *B); + bool relatedPHI(const PHINode *A, const Value *B); + + // Do not implement. + void operator=(const ProvenanceAnalysis &); + ProvenanceAnalysis(const ProvenanceAnalysis &); + + public: + ProvenanceAnalysis() {} + + void setAA(AliasAnalysis *aa) { AA = aa; } + + AliasAnalysis *getAA() const { return AA; } + + bool related(const Value *A, const Value *B); + + void clear() { + CachedResults.clear(); + } + }; +} + +bool ProvenanceAnalysis::relatedSelect(const SelectInst *A, const Value *B) { + // If the values are Selects with the same condition, we can do a more precise + // check: just check for relations between the values on corresponding arms. + if (const SelectInst *SB = dyn_cast(B)) + if (A->getCondition() == SB->getCondition()) { + if (related(A->getTrueValue(), SB->getTrueValue())) + return true; + if (related(A->getFalseValue(), SB->getFalseValue())) + return true; + return false; + } + + // Check both arms of the Select node individually. + if (related(A->getTrueValue(), B)) + return true; + if (related(A->getFalseValue(), B)) + return true; + + // The arms both checked out. + return false; +} + +bool ProvenanceAnalysis::relatedPHI(const PHINode *A, const Value *B) { + // If the values are PHIs in the same block, we can do a more precise as well + // as efficient check: just check for relations between the values on + // corresponding edges. + if (const PHINode *PNB = dyn_cast(B)) + if (PNB->getParent() == A->getParent()) { + for (unsigned i = 0, e = A->getNumIncomingValues(); i != e; ++i) + if (related(A->getIncomingValue(i), + PNB->getIncomingValueForBlock(A->getIncomingBlock(i)))) + return true; + return false; + } + + // Check each unique source of the PHI node against B. + SmallPtrSet UniqueSrc; + for (unsigned i = 0, e = A->getNumIncomingValues(); i != e; ++i) { + const Value *PV1 = A->getIncomingValue(i); + if (UniqueSrc.insert(PV1) && related(PV1, B)) + return true; + } + + // All of the arms checked out. + return false; +} + +/// isStoredObjCPointer - Test if the value of P, or any value covered by its +/// provenance, is ever stored within the function (not counting callees). +static bool isStoredObjCPointer(const Value *P) { + SmallPtrSet Visited; + SmallVector Worklist; + Worklist.push_back(P); + Visited.insert(P); + do { + P = Worklist.pop_back_val(); + for (Value::const_use_iterator UI = P->use_begin(), UE = P->use_end(); + UI != UE; ++UI) { + const User *Ur = *UI; + if (isa(Ur)) { + if (UI.getOperandNo() == 0) + // The pointer is stored. + return true; + // The pointed is stored through. + continue; + } + if (isa(Ur)) + // The pointer is passed as an argument, ignore this. + continue; + if (isa(P)) + // Assume the worst. + return true; + if (Visited.insert(Ur)) + Worklist.push_back(Ur); + } + } while (!Worklist.empty()); + + // Everything checked out. + return false; +} + +bool ProvenanceAnalysis::relatedCheck(const Value *A, const Value *B) { + // Skip past provenance pass-throughs. + A = GetUnderlyingObjCPtr(A); + B = GetUnderlyingObjCPtr(B); + + // Quick check. + if (A == B) + return true; + + // Ask regular AliasAnalysis, for a first approximation. + switch (AA->alias(A, B)) { + case AliasAnalysis::NoAlias: + return false; + case AliasAnalysis::MustAlias: + case AliasAnalysis::PartialAlias: + return true; + case AliasAnalysis::MayAlias: + break; + } + + bool AIsIdentified = IsObjCIdentifiedObject(A); + bool BIsIdentified = IsObjCIdentifiedObject(B); + + // An ObjC-Identified object can't alias a load if it is never locally stored. + if (AIsIdentified) { + if (BIsIdentified) { + // If both pointers have provenance, they can be directly compared. + if (A != B) + return false; + } else { + if (isa(B)) + return isStoredObjCPointer(A); + } + } else { + if (BIsIdentified && isa(A)) + return isStoredObjCPointer(B); + } + + // Special handling for PHI and Select. + if (const PHINode *PN = dyn_cast(A)) + return relatedPHI(PN, B); + if (const PHINode *PN = dyn_cast(B)) + return relatedPHI(PN, A); + if (const SelectInst *S = dyn_cast(A)) + return relatedSelect(S, B); + if (const SelectInst *S = dyn_cast(B)) + return relatedSelect(S, A); + + // Conservative. + return true; +} + +bool ProvenanceAnalysis::related(const Value *A, const Value *B) { + // Begin by inserting a conservative value into the map. If the insertion + // fails, we have the answer already. If it succeeds, leave it there until we + // compute the real answer to guard against recursive queries. + if (A > B) std::swap(A, B); + std::pair Pair = + CachedResults.insert(std::make_pair(ValuePairTy(A, B), true)); + if (!Pair.second) + return Pair.first->second; + + bool Result = relatedCheck(A, B); + CachedResults[ValuePairTy(A, B)] = Result; + return Result; +} + +namespace { + // Sequence - A sequence of states that a pointer may go through in which an + // objc_retain and objc_release are actually needed. + enum Sequence { + S_None, + S_Retain, ///< objc_retain(x) + S_CanRelease, ///< foo(x) -- x could possibly see a ref count decrement + S_Use, ///< any use of x + S_Stop, ///< like S_Release, but code motion is stopped + S_Release, ///< objc_release(x) + S_MovableRelease ///< objc_release(x), !clang.imprecise_release + }; +} + +static Sequence MergeSeqs(Sequence A, Sequence B, bool TopDown) { + // The easy cases. + if (A == B) + return A; + if (A == S_None || B == S_None) + return S_None; + + // Note that we can't merge S_CanRelease and S_Use. + if (A > B) std::swap(A, B); + if (TopDown) { + // Choose the side which is further along in the sequence. + if (A == S_Retain && (B == S_CanRelease || B == S_Use)) + return B; + } else { + // Choose the side which is further along in the sequence. + if ((A == S_Use || A == S_CanRelease) && + (B == S_Release || B == S_Stop || B == S_MovableRelease)) + return A; + // If both sides are releases, choose the more conservative one. + if (A == S_Stop && (B == S_Release || B == S_MovableRelease)) + return A; + if (A == S_Release && B == S_MovableRelease) + return A; + } + + return S_None; +} + +namespace { + /// RRInfo - Unidirectional information about either a + /// retain-decrement-use-release sequence or release-use-decrement-retain + /// reverese sequence. + struct RRInfo { + /// KnownIncremented - After an objc_retain, the reference count of the + /// referenced object is known to be positive. Similarly, before an + /// objc_release, the reference count of the referenced object is known to + /// be positive. If there are retain-release pairs in code regions where the + /// retain count is known to be positive, they can be eliminated, regardless + /// of any side effects between them. + bool KnownIncremented; + + /// IsRetainBlock - True if the Calls are objc_retainBlock calls (as + /// opposed to objc_retain calls). + bool IsRetainBlock; + + /// IsTailCallRelease - True of the objc_release calls are all marked + /// with the "tail" keyword. + bool IsTailCallRelease; + + /// ReleaseMetadata - If the Calls are objc_release calls and they all have + /// a clang.imprecise_release tag, this is the metadata tag. + MDNode *ReleaseMetadata; + + /// Calls - For a top-down sequence, the set of objc_retains or + /// objc_retainBlocks. For bottom-up, the set of objc_releases. + SmallPtrSet Calls; + + /// ReverseInsertPts - The set of optimal insert positions for + /// moving calls in the opposite sequence. + SmallPtrSet ReverseInsertPts; + + RRInfo() : + KnownIncremented(false), IsRetainBlock(false), IsTailCallRelease(false), + ReleaseMetadata(0) {} + + void clear(); + }; +} + +void RRInfo::clear() { + KnownIncremented = false; + IsRetainBlock = false; + IsTailCallRelease = false; + ReleaseMetadata = 0; + Calls.clear(); + ReverseInsertPts.clear(); +} + +namespace { + /// PtrState - This class summarizes several per-pointer runtime properties + /// which are propogated through the flow graph. + class PtrState { + /// RefCount - The known minimum number of reference count increments. + unsigned RefCount; + + /// Seq - The current position in the sequence. + Sequence Seq; + + public: + /// RRI - Unidirectional information about the current sequence. + /// TODO: Encapsulate this better. + RRInfo RRI; + + PtrState() : RefCount(0), Seq(S_None) {} + + void IncrementRefCount() { + if (RefCount != UINT_MAX) ++RefCount; + } + + void DecrementRefCount() { + if (RefCount != 0) --RefCount; + } + + void ClearRefCount() { + RefCount = 0; + } + + bool IsKnownIncremented() const { + return RefCount > 0; + } + + void SetSeq(Sequence NewSeq) { + Seq = NewSeq; + } + + void SetSeqToRelease(MDNode *M) { + if (Seq == S_None || Seq == S_Use) { + Seq = M ? S_MovableRelease : S_Release; + RRI.ReleaseMetadata = M; + } else if (Seq != S_MovableRelease || RRI.ReleaseMetadata != M) { + Seq = S_Release; + RRI.ReleaseMetadata = 0; + } + } + + Sequence GetSeq() const { + return Seq; + } + + void ClearSequenceProgress() { + Seq = S_None; + RRI.clear(); + } + + void Merge(const PtrState &Other, bool TopDown); + }; +} + +void +PtrState::Merge(const PtrState &Other, bool TopDown) { + Seq = MergeSeqs(Seq, Other.Seq, TopDown); + RefCount = std::min(RefCount, Other.RefCount); + + // We can't merge a plain objc_retain with an objc_retainBlock. + if (RRI.IsRetainBlock != Other.RRI.IsRetainBlock) + Seq = S_None; + + if (Seq == S_None) { + RRI.clear(); + } else { + // Conservatively merge the ReleaseMetadata information. + if (RRI.ReleaseMetadata != Other.RRI.ReleaseMetadata) + RRI.ReleaseMetadata = 0; + + RRI.KnownIncremented = RRI.KnownIncremented && Other.RRI.KnownIncremented; + RRI.IsTailCallRelease = RRI.IsTailCallRelease && Other.RRI.IsTailCallRelease; + RRI.Calls.insert(Other.RRI.Calls.begin(), Other.RRI.Calls.end()); + RRI.ReverseInsertPts.insert(Other.RRI.ReverseInsertPts.begin(), + Other.RRI.ReverseInsertPts.end()); + } +} + +namespace { + /// BBState - Per-BasicBlock state. + class BBState { + /// TopDownPathCount - The number of unique control paths from the entry + /// which can reach this block. + unsigned TopDownPathCount; + + /// BottomUpPathCount - The number of unique control paths to exits + /// from this block. + unsigned BottomUpPathCount; + + /// MapTy - A type for PerPtrTopDown and PerPtrBottomUp. + typedef MapVector MapTy; + + /// PerPtrTopDown - The top-down traversal uses this to record information + /// known about a pointer at the bottom of each block. + MapTy PerPtrTopDown; + + /// PerPtrBottomUp - The bottom-up traversal uses this to record information + /// known about a pointer at the top of each block. + MapTy PerPtrBottomUp; + + public: + BBState() : TopDownPathCount(0), BottomUpPathCount(0) {} + + typedef MapTy::iterator ptr_iterator; + typedef MapTy::const_iterator ptr_const_iterator; + + ptr_iterator top_down_ptr_begin() { return PerPtrTopDown.begin(); } + ptr_iterator top_down_ptr_end() { return PerPtrTopDown.end(); } + ptr_const_iterator top_down_ptr_begin() const { + return PerPtrTopDown.begin(); + } + ptr_const_iterator top_down_ptr_end() const { + return PerPtrTopDown.end(); + } + + ptr_iterator bottom_up_ptr_begin() { return PerPtrBottomUp.begin(); } + ptr_iterator bottom_up_ptr_end() { return PerPtrBottomUp.end(); } + ptr_const_iterator bottom_up_ptr_begin() const { + return PerPtrBottomUp.begin(); + } + ptr_const_iterator bottom_up_ptr_end() const { + return PerPtrBottomUp.end(); + } + + /// SetAsEntry - Mark this block as being an entry block, which has one + /// path from the entry by definition. + void SetAsEntry() { TopDownPathCount = 1; } + + /// SetAsExit - Mark this block as being an exit block, which has one + /// path to an exit by definition. + void SetAsExit() { BottomUpPathCount = 1; } + + PtrState &getPtrTopDownState(const Value *Arg) { + return PerPtrTopDown[Arg]; + } + + PtrState &getPtrBottomUpState(const Value *Arg) { + return PerPtrBottomUp[Arg]; + } + + void clearBottomUpPointers() { + PerPtrTopDown.clear(); + } + + void clearTopDownPointers() { + PerPtrTopDown.clear(); + } + + void InitFromPred(const BBState &Other); + void InitFromSucc(const BBState &Other); + void MergePred(const BBState &Other); + void MergeSucc(const BBState &Other); + + /// GetAllPathCount - Return the number of possible unique paths from an + /// entry to an exit which pass through this block. This is only valid + /// after both the top-down and bottom-up traversals are complete. + unsigned GetAllPathCount() const { + return TopDownPathCount * BottomUpPathCount; + } + }; +} + +void BBState::InitFromPred(const BBState &Other) { + PerPtrTopDown = Other.PerPtrTopDown; + TopDownPathCount = Other.TopDownPathCount; +} + +void BBState::InitFromSucc(const BBState &Other) { + PerPtrBottomUp = Other.PerPtrBottomUp; + BottomUpPathCount = Other.BottomUpPathCount; +} + +/// MergePred - The top-down traversal uses this to merge information about +/// predecessors to form the initial state for a new block. +void BBState::MergePred(const BBState &Other) { + // Other.TopDownPathCount can be 0, in which case it is either dead or a + // loop backedge. Loop backedges are special. + TopDownPathCount += Other.TopDownPathCount; + + // For each entry in the other set, if our set has an entry with the same key, + // merge the entries. Otherwise, copy the entry and merge it with an empty + // entry. + for (ptr_const_iterator MI = Other.top_down_ptr_begin(), + ME = Other.top_down_ptr_end(); MI != ME; ++MI) { + std::pair Pair = PerPtrTopDown.insert(*MI); + Pair.first->second.Merge(Pair.second ? PtrState() : MI->second, + /*TopDown=*/true); + } + + // For each entry in our set, if the other set doens't have an entry with the + // same key, force it to merge with an empty entry. + for (ptr_iterator MI = top_down_ptr_begin(), + ME = top_down_ptr_end(); MI != ME; ++MI) + if (Other.PerPtrTopDown.find(MI->first) == Other.PerPtrTopDown.end()) + MI->second.Merge(PtrState(), /*TopDown=*/true); +} + +/// MergeSucc - The bottom-up traversal uses this to merge information about +/// successors to form the initial state for a new block. +void BBState::MergeSucc(const BBState &Other) { + // Other.BottomUpPathCount can be 0, in which case it is either dead or a + // loop backedge. Loop backedges are special. + BottomUpPathCount += Other.BottomUpPathCount; + + // For each entry in the other set, if our set has an entry with the + // same key, merge the entries. Otherwise, copy the entry and merge + // it with an empty entry. + for (ptr_const_iterator MI = Other.bottom_up_ptr_begin(), + ME = Other.bottom_up_ptr_end(); MI != ME; ++MI) { + std::pair Pair = PerPtrBottomUp.insert(*MI); + Pair.first->second.Merge(Pair.second ? PtrState() : MI->second, + /*TopDown=*/false); + } + + // For each entry in our set, if the other set doens't have an entry + // with the same key, force it to merge with an empty entry. + for (ptr_iterator MI = bottom_up_ptr_begin(), + ME = bottom_up_ptr_end(); MI != ME; ++MI) + if (Other.PerPtrBottomUp.find(MI->first) == Other.PerPtrBottomUp.end()) + MI->second.Merge(PtrState(), /*TopDown=*/false); +} + +namespace { + /// ObjCARCOpt - The main ARC optimization pass. + class ObjCARCOpt : public FunctionPass { + bool Changed; + ProvenanceAnalysis PA; + + /// RetainFunc, RelaseFunc - Declarations for objc_retain, + /// objc_retainBlock, and objc_release. + Function *RetainFunc, *RetainBlockFunc, *RetainRVFunc, *ReleaseFunc, + *AutoreleaseFunc; + + /// RetainRVCallee, etc. - Declarations for ObjC runtime + /// functions, for use in creating calls to them. These are initialized + /// lazily to avoid cluttering up the Module with unused declarations. + Constant *RetainRVCallee, *AutoreleaseRVCallee, *ReleaseCallee, + *RetainCallee, *AutoreleaseCallee; + + /// UsedInThisFunciton - Flags which determine whether each of the + /// interesting runtine functions is in fact used in the current function. + unsigned UsedInThisFunction; + + /// ImpreciseReleaseMDKind - The Metadata Kind for clang.imprecise_release + /// metadata. + unsigned ImpreciseReleaseMDKind; + + Constant *getRetainRVCallee(Module *M); + Constant *getAutoreleaseRVCallee(Module *M); + Constant *getReleaseCallee(Module *M); + Constant *getRetainCallee(Module *M); + Constant *getAutoreleaseCallee(Module *M); + + void OptimizeRetainCall(Function &F, Instruction *Retain); + bool OptimizeRetainRVCall(Function &F, Instruction *RetainRV); + void OptimizeAutoreleaseRVCall(Function &F, Instruction *AutoreleaseRV); + void OptimizeIndividualCalls(Function &F); + + void CheckForCFGHazards(const BasicBlock *BB, + DenseMap &BBStates, + BBState &MyStates) const; + bool VisitBottomUp(BasicBlock *BB, + DenseMap &BBStates, + MapVector &Retains); + bool VisitTopDown(BasicBlock *BB, + DenseMap &BBStates, + DenseMap &Releases); + bool Visit(Function &F, + DenseMap &BBStates, + MapVector &Retains, + DenseMap &Releases); + + void MoveCalls(Value *Arg, RRInfo &RetainsToMove, RRInfo &ReleasesToMove, + MapVector &Retains, + DenseMap &Releases, + SmallVectorImpl &DeadInsts); + + bool PerformCodePlacement(DenseMap &BBStates, + MapVector &Retains, + DenseMap &Releases); + + void OptimizeWeakCalls(Function &F); + + bool OptimizeSequences(Function &F); + + void OptimizeReturns(Function &F); + + virtual void getAnalysisUsage(AnalysisUsage &AU) const; + virtual bool doInitialization(Module &M); + virtual bool runOnFunction(Function &F); + virtual void releaseMemory(); + + public: + static char ID; + ObjCARCOpt() : FunctionPass(ID) { + initializeObjCARCOptPass(*PassRegistry::getPassRegistry()); + } + }; +} + +char ObjCARCOpt::ID = 0; +INITIALIZE_PASS_BEGIN(ObjCARCOpt, + "objc-arc", "ObjC ARC optimization", false, false) +INITIALIZE_PASS_DEPENDENCY(ObjCARCAliasAnalysis) +INITIALIZE_PASS_END(ObjCARCOpt, + "objc-arc", "ObjC ARC optimization", false, false) + +Pass *llvm::createObjCARCOptPass() { + return new ObjCARCOpt(); +} + +void ObjCARCOpt::getAnalysisUsage(AnalysisUsage &AU) const { + AU.addRequired(); + AU.addRequired(); + // ARC optimization doesn't currently split critical edges. + AU.setPreservesCFG(); +} + +Constant *ObjCARCOpt::getRetainRVCallee(Module *M) { + if (!RetainRVCallee) { + LLVMContext &C = M->getContext(); + const Type *I8X = PointerType::getUnqual(Type::getInt8Ty(C)); + std::vector Params; + Params.push_back(I8X); + const FunctionType *FTy = + FunctionType::get(I8X, Params, /*isVarArg=*/false); + AttrListPtr Attributes; + Attributes.addAttr(~0u, Attribute::NoUnwind); + RetainRVCallee = + M->getOrInsertFunction("objc_retainAutoreleasedReturnValue", FTy, + Attributes); + } + return RetainRVCallee; +} + +Constant *ObjCARCOpt::getAutoreleaseRVCallee(Module *M) { + if (!AutoreleaseRVCallee) { + LLVMContext &C = M->getContext(); + const Type *I8X = PointerType::getUnqual(Type::getInt8Ty(C)); + std::vector Params; + Params.push_back(I8X); + const FunctionType *FTy = + FunctionType::get(I8X, Params, /*isVarArg=*/false); + AttrListPtr Attributes; + Attributes.addAttr(~0u, Attribute::NoUnwind); + AutoreleaseRVCallee = + M->getOrInsertFunction("objc_autoreleaseReturnValue", FTy, + Attributes); + } + return AutoreleaseRVCallee; +} + +Constant *ObjCARCOpt::getReleaseCallee(Module *M) { + if (!ReleaseCallee) { + LLVMContext &C = M->getContext(); + std::vector Params; + Params.push_back(PointerType::getUnqual(Type::getInt8Ty(C))); + AttrListPtr Attributes; + Attributes.addAttr(~0u, Attribute::NoUnwind); + ReleaseCallee = + M->getOrInsertFunction( + "objc_release", + FunctionType::get(Type::getVoidTy(C), Params, /*isVarArg=*/false), + Attributes); + } + return ReleaseCallee; +} + +Constant *ObjCARCOpt::getRetainCallee(Module *M) { + if (!RetainCallee) { + LLVMContext &C = M->getContext(); + std::vector Params; + Params.push_back(PointerType::getUnqual(Type::getInt8Ty(C))); + AttrListPtr Attributes; + Attributes.addAttr(~0u, Attribute::NoUnwind); + RetainCallee = + M->getOrInsertFunction( + "objc_retain", + FunctionType::get(Params[0], Params, /*isVarArg=*/false), + Attributes); + } + return RetainCallee; +} + +Constant *ObjCARCOpt::getAutoreleaseCallee(Module *M) { + if (!AutoreleaseCallee) { + LLVMContext &C = M->getContext(); + std::vector Params; + Params.push_back(PointerType::getUnqual(Type::getInt8Ty(C))); + AttrListPtr Attributes; + Attributes.addAttr(~0u, Attribute::NoUnwind); + AutoreleaseCallee = + M->getOrInsertFunction( + "objc_autorelease", + FunctionType::get(Params[0], Params, /*isVarArg=*/false), + Attributes); + } + return AutoreleaseCallee; +} + +/// CanAlterRefCount - Test whether the given instruction can result in a +/// reference count modification (positive or negative) for the pointer's +/// object. +static bool +CanAlterRefCount(const Instruction *Inst, const Value *Ptr, + ProvenanceAnalysis &PA, InstructionClass Class) { + switch (Class) { + case IC_Autorelease: + case IC_AutoreleaseRV: + case IC_User: + // These operations never directly modify a reference count. + return false; + default: break; + } + + ImmutableCallSite CS = static_cast(Inst); + assert(CS && "Only calls can alter reference counts!"); + + // See if AliasAnalysis can help us with the call. + AliasAnalysis::ModRefBehavior MRB = PA.getAA()->getModRefBehavior(CS); + if (AliasAnalysis::onlyReadsMemory(MRB)) + return false; + if (AliasAnalysis::onlyAccessesArgPointees(MRB)) { + for (ImmutableCallSite::arg_iterator I = CS.arg_begin(), E = CS.arg_end(); + I != E; ++I) { + const Value *Op = *I; + if (IsPotentialUse(Op) && PA.related(Ptr, Op)) + return true; + } + return false; + } + + // Assume the worst. + return true; +} + +/// CanUse - Test whether the given instruction can "use" the given pointer's +/// object in a way that requires the reference count to be positive. +static bool +CanUse(const Instruction *Inst, const Value *Ptr, ProvenanceAnalysis &PA, + InstructionClass Class) { + // IC_Call operations (as opposed to IC_CallOrUser) never "use" objc pointers. + if (Class == IC_Call) + return false; + + // Consider various instructions which may have pointer arguments which are + // not "uses". + if (const ICmpInst *ICI = dyn_cast(Inst)) { + // Comparing a pointer with null, or any other constant, isn't really a use, + // because we don't care what the pointer points to, or about the values + // of any other dynamic reference-counted pointers. + if (!IsPotentialUse(ICI->getOperand(1))) + return false; + } else if (ImmutableCallSite CS = static_cast(Inst)) { + // For calls, just check the arguments (and not the callee operand). + for (ImmutableCallSite::arg_iterator OI = CS.arg_begin(), + OE = CS.arg_end(); OI != OE; ++OI) { + const Value *Op = *OI; + if (IsPotentialUse(Op) && PA.related(Ptr, Op)) + return true; + } + return false; + } else if (const StoreInst *SI = dyn_cast(Inst)) { + // Special-case stores, because we don't care about the stored value, just + // the store address. + const Value *Op = GetUnderlyingObjCPtr(SI->getPointerOperand()); + // If we can't tell what the underlying object was, assume there is a + // dependence. + return IsPotentialUse(Op) && PA.related(Op, Ptr); + } + + // Check each operand for a match. + for (User::const_op_iterator OI = Inst->op_begin(), OE = Inst->op_end(); + OI != OE; ++OI) { + const Value *Op = *OI; + if (IsPotentialUse(Op) && PA.related(Ptr, Op)) + return true; + } + return false; +} + +/// CanInterruptRV - Test whether the given instruction can autorelease +/// any pointer or cause an autoreleasepool pop. +static bool +CanInterruptRV(InstructionClass Class) { + switch (Class) { + case IC_AutoreleasepoolPop: + case IC_CallOrUser: + case IC_Call: + case IC_Autorelease: + case IC_AutoreleaseRV: + case IC_FusedRetainAutorelease: + case IC_FusedRetainAutoreleaseRV: + return true; + default: + return false; + } +} + +namespace { + /// DependenceKind - There are several kinds of dependence-like concepts in + /// use here. + enum DependenceKind { + NeedsPositiveRetainCount, + CanChangeRetainCount, + RetainAutoreleaseDep, ///< Blocks objc_retainAutorelease. + RetainAutoreleaseRVDep, ///< Blocks objc_retainAutoreleaseReturnValue. + RetainRVDep ///< Blocks objc_retainAutoreleasedReturnValue. + }; +} + +/// Depends - Test if there can be dependencies on Inst through Arg. This +/// function only tests dependencies relevant for removing pairs of calls. +static bool +Depends(DependenceKind Flavor, Instruction *Inst, const Value *Arg, + ProvenanceAnalysis &PA) { + // If we've reached the definition of Arg, stop. + if (Inst == Arg) + return true; + + switch (Flavor) { + case NeedsPositiveRetainCount: { + InstructionClass Class = GetInstructionClass(Inst); + switch (Class) { + case IC_AutoreleasepoolPop: + case IC_AutoreleasepoolPush: + case IC_None: + return false; + default: + return CanUse(Inst, Arg, PA, Class); + } + } + + case CanChangeRetainCount: { + InstructionClass Class = GetInstructionClass(Inst); + switch (Class) { + case IC_AutoreleasepoolPop: + // Conservatively assume this can decrement any count. + return true; + case IC_AutoreleasepoolPush: + case IC_None: + return false; + default: + return CanAlterRefCount(Inst, Arg, PA, Class); + } + } + + case RetainAutoreleaseDep: + switch (GetBasicInstructionClass(Inst)) { + case IC_AutoreleasepoolPop: + // Don't merge an objc_autorelease with an objc_retain inside a different + // autoreleasepool scope. + return true; + case IC_Retain: + case IC_RetainRV: + // Check for a retain of the same pointer for merging. + return GetObjCArg(Inst) == Arg; + default: + // Nothing else matters for objc_retainAutorelease formation. + return false; + } + break; + + case RetainAutoreleaseRVDep: { + InstructionClass Class = GetBasicInstructionClass(Inst); + switch (Class) { + case IC_Retain: + case IC_RetainRV: + // Check for a retain of the same pointer for merging. + return GetObjCArg(Inst) == Arg; + default: + // Anything that can autorelease interrupts + // retainAutoreleaseReturnValue formation. + return CanInterruptRV(Class); + } + break; + } + + case RetainRVDep: + return CanInterruptRV(GetBasicInstructionClass(Inst)); + } + + llvm_unreachable("Invalid dependence flavor"); + return true; +} + +/// FindDependencies - Walk up the CFG from StartPos (which is in StartBB) and +/// find local and non-local dependencies on Arg. +/// TODO: Cache results? +static void +FindDependencies(DependenceKind Flavor, + const Value *Arg, + BasicBlock *StartBB, Instruction *StartInst, + SmallPtrSet &DependingInstructions, + SmallPtrSet &Visited, + ProvenanceAnalysis &PA) { + BasicBlock::iterator StartPos = StartInst; + + SmallVector, 4> Worklist; + Worklist.push_back(std::make_pair(StartBB, StartPos)); + do { + std::pair Pair = + Worklist.pop_back_val(); + BasicBlock *LocalStartBB = Pair.first; + BasicBlock::iterator LocalStartPos = Pair.second; + BasicBlock::iterator StartBBBegin = LocalStartBB->begin(); + for (;;) { + if (LocalStartPos == StartBBBegin) { + pred_iterator PI(LocalStartBB), PE(LocalStartBB, false); + if (PI == PE) + // If we've reached the function entry, produce a null dependence. + DependingInstructions.insert(0); + else + // Add the predecessors to the worklist. + do { + BasicBlock *PredBB = *PI; + if (Visited.insert(PredBB)) + Worklist.push_back(std::make_pair(PredBB, PredBB->end())); + } while (++PI != PE); + break; + } + + Instruction *Inst = --LocalStartPos; + if (Depends(Flavor, Inst, Arg, PA)) { + DependingInstructions.insert(Inst); + break; + } + } + } while (!Worklist.empty()); + + // Determine whether the original StartBB post-dominates all of the blocks we + // visited. If not, insert a sentinal indicating that most optimizations are + // not safe. + for (SmallPtrSet::const_iterator I = Visited.begin(), + E = Visited.end(); I != E; ++I) { + const BasicBlock *BB = *I; + if (BB == StartBB) + continue; + const TerminatorInst *TI = cast(&BB->back()); + for (succ_const_iterator SI(TI), SE(TI, false); SI != SE; ++SI) { + const BasicBlock *Succ = *SI; + if (Succ != StartBB && !Visited.count(Succ)) { + DependingInstructions.insert(reinterpret_cast(-1)); + return; + } + } + } +} + +static bool isNullOrUndef(const Value *V) { + return isa(V) || isa(V); +} + +static bool isNoopInstruction(const Instruction *I) { + return isa(I) || + (isa(I) && + cast(I)->hasAllZeroIndices()); +} + +/// OptimizeRetainCall - Turn objc_retain into +/// objc_retainAutoreleasedReturnValue if the operand is a return value. +void +ObjCARCOpt::OptimizeRetainCall(Function &F, Instruction *Retain) { + CallSite CS(GetObjCArg(Retain)); + Instruction *Call = CS.getInstruction(); + if (!Call) return; + if (Call->getParent() != Retain->getParent()) return; + + // Check that the call is next to the retain. + BasicBlock::iterator I = Call; + ++I; + while (isNoopInstruction(I)) ++I; + if (&*I != Retain) + return; + + // Turn it to an objc_retainAutoreleasedReturnValue.. + Changed = true; + ++NumPeeps; + cast(Retain)->setCalledFunction(getRetainRVCallee(F.getParent())); +} + +/// OptimizeRetainRVCall - Turn objc_retainAutoreleasedReturnValue into +/// objc_retain if the operand is not a return value. Or, if it can be +/// paired with an objc_autoreleaseReturnValue, delete the pair and +/// return true. +bool +ObjCARCOpt::OptimizeRetainRVCall(Function &F, Instruction *RetainRV) { + // Check for the argument being from an immediately preceding call. + Value *Arg = GetObjCArg(RetainRV); + CallSite CS(Arg); + if (Instruction *Call = CS.getInstruction()) + if (Call->getParent() == RetainRV->getParent()) { + BasicBlock::iterator I = Call; + ++I; + while (isNoopInstruction(I)) ++I; + if (&*I == RetainRV) + return false; + } + + // Check for being preceded by an objc_autoreleaseReturnValue on the same + // pointer. In this case, we can delete the pair. + BasicBlock::iterator I = RetainRV, Begin = RetainRV->getParent()->begin(); + if (I != Begin) { + do --I; while (I != Begin && isNoopInstruction(I)); + if (GetBasicInstructionClass(I) == IC_AutoreleaseRV && + GetObjCArg(I) == Arg) { + Changed = true; + ++NumPeeps; + EraseInstruction(I); + EraseInstruction(RetainRV); + return true; + } + } + + // Turn it to a plain objc_retain. + Changed = true; + ++NumPeeps; + cast(RetainRV)->setCalledFunction(getRetainCallee(F.getParent())); + return false; +} + +/// OptimizeAutoreleaseRVCall - Turn objc_autoreleaseReturnValue into +/// objc_autorelease if the result is not used as a return value. +void +ObjCARCOpt::OptimizeAutoreleaseRVCall(Function &F, Instruction *AutoreleaseRV) { + // Check for a return of the pointer value. + const Value *Ptr = GetObjCArg(AutoreleaseRV); + for (Value::const_use_iterator UI = Ptr->use_begin(), UE = Ptr->use_end(); + UI != UE; ++UI) { + const User *I = *UI; + if (isa(I) || GetBasicInstructionClass(I) == IC_RetainRV) + return; + } + + Changed = true; + ++NumPeeps; + cast(AutoreleaseRV)-> + setCalledFunction(getAutoreleaseCallee(F.getParent())); +} + +/// OptimizeIndividualCalls - Visit each call, one at a time, and make +/// simplifications without doing any additional analysis. +void ObjCARCOpt::OptimizeIndividualCalls(Function &F) { + // Reset all the flags in preparation for recomputing them. + UsedInThisFunction = 0; + + // Visit all objc_* calls in F. + for (inst_iterator I = inst_begin(&F), E = inst_end(&F); I != E; ) { + Instruction *Inst = &*I++; + InstructionClass Class = GetBasicInstructionClass(Inst); + + switch (Class) { + default: break; + + // Delete no-op casts. These function calls have special semantics, but + // the semantics are entirely implemented via lowering in the front-end, + // so by the time they reach the optimizer, they are just no-op calls + // which return their argument. + // + // There are gray areas here, as the ability to cast reference-counted + // pointers to raw void* and back allows code to break ARC assumptions, + // however these are currently considered to be unimportant. + case IC_NoopCast: + Changed = true; + ++NumNoops; + EraseInstruction(Inst); + continue; + + // If the pointer-to-weak-pointer is null, it's undefined behavior. + case IC_StoreWeak: + case IC_LoadWeak: + case IC_LoadWeakRetained: + case IC_InitWeak: + case IC_DestroyWeak: { + CallInst *CI = cast(Inst); + if (isNullOrUndef(CI->getArgOperand(0))) { + const Type *Ty = CI->getArgOperand(0)->getType(); + new StoreInst(UndefValue::get(cast(Ty)->getElementType()), + Constant::getNullValue(Ty), + CI); + CI->replaceAllUsesWith(UndefValue::get(CI->getType())); + CI->eraseFromParent(); + continue; + } + break; + } + case IC_CopyWeak: + case IC_MoveWeak: { + CallInst *CI = cast(Inst); + if (isNullOrUndef(CI->getArgOperand(0)) || + isNullOrUndef(CI->getArgOperand(1))) { + const Type *Ty = CI->getArgOperand(0)->getType(); + new StoreInst(UndefValue::get(cast(Ty)->getElementType()), + Constant::getNullValue(Ty), + CI); + CI->replaceAllUsesWith(UndefValue::get(CI->getType())); + CI->eraseFromParent(); + continue; + } + break; + } + case IC_Retain: + OptimizeRetainCall(F, Inst); + break; + case IC_RetainRV: + if (OptimizeRetainRVCall(F, Inst)) + continue; + break; + case IC_AutoreleaseRV: + OptimizeAutoreleaseRVCall(F, Inst); + break; + } + + // objc_autorelease(x) -> objc_release(x) if x is otherwise unused. + if (IsAutorelease(Class) && Inst->use_empty()) { + CallInst *Call = cast(Inst); + const Value *Arg = Call->getArgOperand(0); + Arg = FindSingleUseIdentifiedObject(Arg); + if (Arg) { + Changed = true; + ++NumAutoreleases; + + // Create the declaration lazily. + LLVMContext &C = Inst->getContext(); + CallInst *NewCall = + CallInst::Create(getReleaseCallee(F.getParent()), + Call->getArgOperand(0), "", Call); + NewCall->setMetadata(ImpreciseReleaseMDKind, + MDNode::get(C, ArrayRef())); + EraseInstruction(Call); + Inst = NewCall; + Class = IC_Release; + } + } + + // For functions which can never be passed stack arguments, add + // a tail keyword. + if (IsAlwaysTail(Class)) { + Changed = true; + cast(Inst)->setTailCall(); + } + + // Set nounwind as needed. + if (IsNoThrow(Class)) { + Changed = true; + cast(Inst)->setDoesNotThrow(); + } + + if (!IsNoopOnNull(Class)) { + UsedInThisFunction |= 1 << Class; + continue; + } + + const Value *Arg = GetObjCArg(Inst); + + // ARC calls with null are no-ops. Delete them. + if (isNullOrUndef(Arg)) { + Changed = true; + ++NumNoops; + EraseInstruction(Inst); + continue; + } + + // Keep track of which of retain, release, autorelease, and retain_block + // are actually present in this function. + UsedInThisFunction |= 1 << Class; + + // If Arg is a PHI, and one or more incoming values to the + // PHI are null, and the call is control-equivalent to the PHI, and there + // are no relevant side effects between the PHI and the call, the call + // could be pushed up to just those paths with non-null incoming values. + // For now, don't bother splitting critical edges for this. + SmallVector, 4> Worklist; + Worklist.push_back(std::make_pair(Inst, Arg)); + do { + std::pair Pair = Worklist.pop_back_val(); + Inst = Pair.first; + Arg = Pair.second; + + const PHINode *PN = dyn_cast(Arg); + if (!PN) continue; + + // Determine if the PHI has any null operands, or any incoming + // critical edges. + bool HasNull = false; + bool HasCriticalEdges = false; + for (unsigned i = 0, e = PN->getNumIncomingValues(); i != e; ++i) { + Value *Incoming = + StripPointerCastsAndObjCCalls(PN->getIncomingValue(i)); + if (isNullOrUndef(Incoming)) + HasNull = true; + else if (cast(PN->getIncomingBlock(i)->back()) + .getNumSuccessors() != 1) { + HasCriticalEdges = true; + break; + } + } + // If we have null operands and no critical edges, optimize. + if (!HasCriticalEdges && HasNull) { + SmallPtrSet DependingInstructions; + SmallPtrSet Visited; + + // Check that there is nothing that cares about the reference + // count between the call and the phi. + FindDependencies(NeedsPositiveRetainCount, Arg, + Inst->getParent(), Inst, + DependingInstructions, Visited, PA); + if (DependingInstructions.size() == 1 && + *DependingInstructions.begin() == PN) { + Changed = true; + ++NumPartialNoops; + // Clone the call into each predecessor that has a non-null value. + CallInst *CInst = cast(Inst); + const Type *ParamTy = CInst->getArgOperand(0)->getType(); + for (unsigned i = 0, e = PN->getNumIncomingValues(); i != e; ++i) { + Value *Incoming = + StripPointerCastsAndObjCCalls(PN->getIncomingValue(i)); + if (!isNullOrUndef(Incoming)) { + CallInst *Clone = cast(CInst->clone()); + Value *Op = PN->getIncomingValue(i); + Instruction *InsertPos = &PN->getIncomingBlock(i)->back(); + if (Op->getType() != ParamTy) + Op = new BitCastInst(Op, ParamTy, "", InsertPos); + Clone->setArgOperand(0, Op); + Clone->insertBefore(InsertPos); + Worklist.push_back(std::make_pair(Clone, Incoming)); + } + } + // Erase the original call. + EraseInstruction(CInst); + continue; + } + } + } while (!Worklist.empty()); + } +} + +/// CheckForCFGHazards - Check for critical edges, loop boundaries, irreducible +/// control flow, or other CFG structures where moving code across the edge +/// would result in it being executed more. +void +ObjCARCOpt::CheckForCFGHazards(const BasicBlock *BB, + DenseMap &BBStates, + BBState &MyStates) const { + // If any top-down local-use or possible-dec has a succ which is earlier in + // the sequence, forget it. + for (BBState::ptr_const_iterator I = MyStates.top_down_ptr_begin(), + E = MyStates.top_down_ptr_end(); I != E; ++I) + switch (I->second.GetSeq()) { + default: break; + case S_Use: { + const Value *Arg = I->first; + const TerminatorInst *TI = cast(&BB->back()); + bool SomeSuccHasSame = false; + bool AllSuccsHaveSame = true; + for (succ_const_iterator SI(TI), SE(TI, false); SI != SE; ++SI) + switch (BBStates[*SI].getPtrBottomUpState(Arg).GetSeq()) { + case S_None: + case S_CanRelease: + MyStates.getPtrTopDownState(Arg).ClearSequenceProgress(); + SomeSuccHasSame = false; + break; + case S_Use: + SomeSuccHasSame = true; + break; + case S_Stop: + case S_Release: + case S_MovableRelease: + AllSuccsHaveSame = false; + break; + case S_Retain: + llvm_unreachable("bottom-up pointer in retain state!"); + } + // If the state at the other end of any of the successor edges + // matches the current state, require all edges to match. This + // guards against loops in the middle of a sequence. + if (SomeSuccHasSame && !AllSuccsHaveSame) + MyStates.getPtrTopDownState(Arg).ClearSequenceProgress(); + } + case S_CanRelease: { + const Value *Arg = I->first; + const TerminatorInst *TI = cast(&BB->back()); + bool SomeSuccHasSame = false; + bool AllSuccsHaveSame = true; + for (succ_const_iterator SI(TI), SE(TI, false); SI != SE; ++SI) + switch (BBStates[*SI].getPtrBottomUpState(Arg).GetSeq()) { + case S_None: + MyStates.getPtrTopDownState(Arg).ClearSequenceProgress(); + SomeSuccHasSame = false; + break; + case S_CanRelease: + SomeSuccHasSame = true; + break; + case S_Stop: + case S_Release: + case S_MovableRelease: + case S_Use: + AllSuccsHaveSame = false; + break; + case S_Retain: + llvm_unreachable("bottom-up pointer in retain state!"); + } + // If the state at the other end of any of the successor edges + // matches the current state, require all edges to match. This + // guards against loops in the middle of a sequence. + if (SomeSuccHasSame && !AllSuccsHaveSame) + MyStates.getPtrTopDownState(Arg).ClearSequenceProgress(); + } + } +} + +bool +ObjCARCOpt::VisitBottomUp(BasicBlock *BB, + DenseMap &BBStates, + MapVector &Retains) { + bool NestingDetected = false; + BBState &MyStates = BBStates[BB]; + + // Merge the states from each successor to compute the initial state + // for the current block. + const TerminatorInst *TI = cast(&BB->back()); + succ_const_iterator SI(TI), SE(TI, false); + if (SI == SE) + MyStates.SetAsExit(); + else + do { + const BasicBlock *Succ = *SI++; + if (Succ == BB) + continue; + DenseMap::iterator I = BBStates.find(Succ); + if (I == BBStates.end()) + continue; + MyStates.InitFromSucc(I->second); + while (SI != SE) { + Succ = *SI++; + if (Succ != BB) { + I = BBStates.find(Succ); + if (I != BBStates.end()) + MyStates.MergeSucc(I->second); + } + } + break; + } while (SI != SE); + + // Visit all the instructions, bottom-up. + for (BasicBlock::iterator I = BB->end(), E = BB->begin(); I != E; --I) { + Instruction *Inst = llvm::prior(I); + InstructionClass Class = GetInstructionClass(Inst); + const Value *Arg = 0; + + switch (Class) { + case IC_Release: { + Arg = GetObjCArg(Inst); + + PtrState &S = MyStates.getPtrBottomUpState(Arg); + + // If we see two releases in a row on the same pointer. If so, make + // a note, and we'll cicle back to revisit it after we've + // hopefully eliminated the second release, which may allow us to + // eliminate the first release too. + // Theoretically we could implement removal of nested retain+release + // pairs by making PtrState hold a stack of states, but this is + // simple and avoids adding overhead for the non-nested case. + if (S.GetSeq() == S_Release || S.GetSeq() == S_MovableRelease) + NestingDetected = true; + + S.SetSeqToRelease(Inst->getMetadata(ImpreciseReleaseMDKind)); + S.RRI.clear(); + S.RRI.KnownIncremented = S.IsKnownIncremented(); + S.RRI.IsTailCallRelease = cast(Inst)->isTailCall(); + S.RRI.Calls.insert(Inst); + + S.IncrementRefCount(); + break; + } + case IC_RetainBlock: + case IC_Retain: + case IC_RetainRV: { + Arg = GetObjCArg(Inst); + + PtrState &S = MyStates.getPtrBottomUpState(Arg); + S.DecrementRefCount(); + + switch (S.GetSeq()) { + case S_Stop: + case S_Release: + case S_MovableRelease: + case S_Use: + S.RRI.ReverseInsertPts.clear(); + // FALL THROUGH + case S_CanRelease: + // Don't do retain+release tracking for IC_RetainRV, because it's + // better to let it remain as the first instruction after a call. + if (Class != IC_RetainRV) { + S.RRI.IsRetainBlock = Class == IC_RetainBlock; + Retains[Inst] = S.RRI; + } + S.ClearSequenceProgress(); + break; + case S_None: + break; + case S_Retain: + llvm_unreachable("bottom-up pointer in retain state!"); + } + break; + } + case IC_AutoreleasepoolPop: + // Conservatively, clear MyStates for all known pointers. + MyStates.clearBottomUpPointers(); + continue; + case IC_AutoreleasepoolPush: + case IC_None: + // These are irrelevant. + continue; + default: + break; + } + + // Consider any other possible effects of this instruction on each + // pointer being tracked. + for (BBState::ptr_iterator MI = MyStates.bottom_up_ptr_begin(), + ME = MyStates.bottom_up_ptr_end(); MI != ME; ++MI) { + const Value *Ptr = MI->first; + if (Ptr == Arg) + continue; // Handled above. + PtrState &S = MI->second; + Sequence Seq = S.GetSeq(); + + // Check for possible retains and releases. + if (CanAlterRefCount(Inst, Ptr, PA, Class)) { + // Check for a retain (we're going bottom-up here). + S.DecrementRefCount(); + + // Check for a release. + if (!IsRetain(Class) && Class != IC_RetainBlock) + switch (Seq) { + case S_Use: + S.SetSeq(S_CanRelease); + continue; + case S_CanRelease: + case S_Release: + case S_MovableRelease: + case S_Stop: + case S_None: + break; + case S_Retain: + llvm_unreachable("bottom-up pointer in retain state!"); + } + } + + // Check for possible direct uses. + switch (Seq) { + case S_Release: + case S_MovableRelease: + if (CanUse(Inst, Ptr, PA, Class)) { + S.RRI.ReverseInsertPts.clear(); + S.RRI.ReverseInsertPts.insert(Inst); + S.SetSeq(S_Use); + } else if (Seq == S_Release && + (Class == IC_User || Class == IC_CallOrUser)) { + // Non-movable releases depend on any possible objc pointer use. + S.SetSeq(S_Stop); + S.RRI.ReverseInsertPts.clear(); + S.RRI.ReverseInsertPts.insert(Inst); + } + break; + case S_Stop: + if (CanUse(Inst, Ptr, PA, Class)) + S.SetSeq(S_Use); + break; + case S_CanRelease: + case S_Use: + case S_None: + break; + case S_Retain: + llvm_unreachable("bottom-up pointer in retain state!"); + } + } + } + + return NestingDetected; +} + +bool +ObjCARCOpt::VisitTopDown(BasicBlock *BB, + DenseMap &BBStates, + DenseMap &Releases) { + bool NestingDetected = false; + BBState &MyStates = BBStates[BB]; + + // Merge the states from each predecessor to compute the initial state + // for the current block. + const_pred_iterator PI(BB), PE(BB, false); + if (PI == PE) + MyStates.SetAsEntry(); + else + do { + const BasicBlock *Pred = *PI++; + if (Pred == BB) + continue; + DenseMap::iterator I = BBStates.find(Pred); + if (I == BBStates.end()) + continue; + MyStates.InitFromPred(I->second); + while (PI != PE) { + Pred = *PI++; + if (Pred != BB) { + I = BBStates.find(Pred); + if (I != BBStates.end()) + MyStates.MergePred(I->second); + } + } + break; + } while (PI != PE); + + // Visit all the instructions, top-down. + for (BasicBlock::iterator I = BB->begin(), E = BB->end(); I != E; ++I) { + Instruction *Inst = I; + InstructionClass Class = GetInstructionClass(Inst); + const Value *Arg = 0; + + switch (Class) { + case IC_RetainBlock: + case IC_Retain: + case IC_RetainRV: { + Arg = GetObjCArg(Inst); + + PtrState &S = MyStates.getPtrTopDownState(Arg); + + // Don't do retain+release tracking for IC_RetainRV, because it's + // better to let it remain as the first instruction after a call. + if (Class != IC_RetainRV) { + // If we see two retains in a row on the same pointer. If so, make + // a note, and we'll cicle back to revisit it after we've + // hopefully eliminated the second retain, which may allow us to + // eliminate the first retain too. + // Theoretically we could implement removal of nested retain+release + // pairs by making PtrState hold a stack of states, but this is + // simple and avoids adding overhead for the non-nested case. + if (S.GetSeq() == S_Retain) + NestingDetected = true; + + S.SetSeq(S_Retain); + S.RRI.clear(); + S.RRI.IsRetainBlock = Class == IC_RetainBlock; + S.RRI.KnownIncremented = S.IsKnownIncremented(); + S.RRI.Calls.insert(Inst); + } + + S.IncrementRefCount(); + break; + } + case IC_Release: { + Arg = GetObjCArg(Inst); + + PtrState &S = MyStates.getPtrTopDownState(Arg); + S.DecrementRefCount(); + + switch (S.GetSeq()) { + case S_Retain: + case S_CanRelease: + S.RRI.ReverseInsertPts.clear(); + // FALL THROUGH + case S_Use: + S.RRI.ReleaseMetadata = Inst->getMetadata(ImpreciseReleaseMDKind); + S.RRI.IsTailCallRelease = cast(Inst)->isTailCall(); + Releases[Inst] = S.RRI; + S.ClearSequenceProgress(); + break; + case S_None: + break; + case S_Stop: + case S_Release: + case S_MovableRelease: + llvm_unreachable("top-down pointer in release state!"); + } + break; + } + case IC_AutoreleasepoolPop: + // Conservatively, clear MyStates for all known pointers. + MyStates.clearTopDownPointers(); + continue; + case IC_AutoreleasepoolPush: + case IC_None: + // These are irrelevant. + continue; + default: + break; + } + + // Consider any other possible effects of this instruction on each + // pointer being tracked. + for (BBState::ptr_iterator MI = MyStates.top_down_ptr_begin(), + ME = MyStates.top_down_ptr_end(); MI != ME; ++MI) { + const Value *Ptr = MI->first; + if (Ptr == Arg) + continue; // Handled above. + PtrState &S = MI->second; + Sequence Seq = S.GetSeq(); + + // Check for possible releases. + if (!IsRetain(Class) && Class != IC_RetainBlock && + CanAlterRefCount(Inst, Ptr, PA, Class)) { + // Check for a release. + S.DecrementRefCount(); + + // Check for a release. + switch (Seq) { + case S_Retain: + S.SetSeq(S_CanRelease); + S.RRI.ReverseInsertPts.clear(); + S.RRI.ReverseInsertPts.insert(Inst); + + // One call can't cause a transition from S_Retain to S_CanRelease + // and S_CanRelease to S_Use. If we've made the first transition, + // we're done. + continue; + case S_Use: + case S_CanRelease: + case S_None: + break; + case S_Stop: + case S_Release: + case S_MovableRelease: + llvm_unreachable("top-down pointer in release state!"); + } + } + + // Check for possible direct uses. + switch (Seq) { + case S_CanRelease: + if (CanUse(Inst, Ptr, PA, Class)) + S.SetSeq(S_Use); + break; + case S_Use: + case S_Retain: + case S_None: + break; + case S_Stop: + case S_Release: + case S_MovableRelease: + llvm_unreachable("top-down pointer in release state!"); + } + } + } + + CheckForCFGHazards(BB, BBStates, MyStates); + return NestingDetected; +} + +// Visit - Visit the function both top-down and bottom-up. +bool +ObjCARCOpt::Visit(Function &F, + DenseMap &BBStates, + MapVector &Retains, + DenseMap &Releases) { + // Use postorder for bottom-up, and reverse-postorder for top-down, because we + // magically know that loops will be well behaved, i.e. they won't repeatedly + // call retain on a single pointer without doing a release. + bool BottomUpNestingDetected = false; + SmallVector PostOrder; + for (po_iterator I = po_begin(&F), E = po_end(&F); I != E; ++I) { + BasicBlock *BB = *I; + PostOrder.push_back(BB); + + BottomUpNestingDetected |= VisitBottomUp(BB, BBStates, Retains); + } + + // Iterate through the post-order in reverse order, achieving a + // reverse-postorder traversal. We don't use the ReversePostOrderTraversal + // class here because it works by computing its own full postorder iteration, + // recording the sequence, and playing it back in reverse. Since we're already + // doing a full iteration above, we can just record the sequence manually and + // avoid the cost of having ReversePostOrderTraversal compute it. + bool TopDownNestingDetected = false; + for (SmallVectorImpl::const_reverse_iterator + RI = PostOrder.rbegin(), RE = PostOrder.rend(); RI != RE; ++RI) + TopDownNestingDetected |= VisitTopDown(*RI, BBStates, Releases); + + return TopDownNestingDetected && BottomUpNestingDetected; +} + +/// MoveCalls - Move the calls in RetainsToMove and ReleasesToMove. +void ObjCARCOpt::MoveCalls(Value *Arg, + RRInfo &RetainsToMove, + RRInfo &ReleasesToMove, + MapVector &Retains, + DenseMap &Releases, + SmallVectorImpl &DeadInsts) { + const Type *ArgTy = Arg->getType(); + const Type *ParamTy = + (RetainRVFunc ? RetainRVFunc : + RetainFunc ? RetainFunc : + RetainBlockFunc)->arg_begin()->getType(); + + // Insert the new retain and release calls. + for (SmallPtrSet::const_iterator + PI = ReleasesToMove.ReverseInsertPts.begin(), + PE = ReleasesToMove.ReverseInsertPts.end(); PI != PE; ++PI) { + Instruction *InsertPt = *PI; + Value *MyArg = ArgTy == ParamTy ? Arg : + new BitCastInst(Arg, ParamTy, "", InsertPt); + CallInst *Call = + CallInst::Create(RetainsToMove.IsRetainBlock ? + RetainBlockFunc : RetainFunc, + MyArg, "", InsertPt); + Call->setDoesNotThrow(); + if (!RetainsToMove.IsRetainBlock) + Call->setTailCall(); + } + for (SmallPtrSet::const_iterator + PI = RetainsToMove.ReverseInsertPts.begin(), + PE = RetainsToMove.ReverseInsertPts.end(); PI != PE; ++PI) { + Instruction *InsertPt = llvm::next(BasicBlock::iterator(*PI)); + Value *MyArg = ArgTy == ParamTy ? Arg : + new BitCastInst(Arg, ParamTy, "", InsertPt); + CallInst *Call = CallInst::Create(ReleaseFunc, MyArg, "", InsertPt); + // Attach a clang.imprecise_release metadata tag, if appropriate. + if (MDNode *M = ReleasesToMove.ReleaseMetadata) + Call->setMetadata(ImpreciseReleaseMDKind, M); + Call->setDoesNotThrow(); + if (ReleasesToMove.IsTailCallRelease) + Call->setTailCall(); + } + + // Delete the original retain and release calls. + for (SmallPtrSet::const_iterator + AI = RetainsToMove.Calls.begin(), + AE = RetainsToMove.Calls.end(); AI != AE; ++AI) { + Instruction *OrigRetain = *AI; + Retains.blot(OrigRetain); + DeadInsts.push_back(OrigRetain); + } + for (SmallPtrSet::const_iterator + AI = ReleasesToMove.Calls.begin(), + AE = ReleasesToMove.Calls.end(); AI != AE; ++AI) { + Instruction *OrigRelease = *AI; + Releases.erase(OrigRelease); + DeadInsts.push_back(OrigRelease); + } +} + +bool +ObjCARCOpt::PerformCodePlacement(DenseMap + &BBStates, + MapVector &Retains, + DenseMap &Releases) { + bool AnyPairsCompletelyEliminated = false; + RRInfo RetainsToMove; + RRInfo ReleasesToMove; + SmallVector NewRetains; + SmallVector NewReleases; + SmallVector DeadInsts; + + for (MapVector::const_iterator I = Retains.begin(), + E = Retains.end(); I != E; ) { + Value *V = (I++)->first; + if (!V) continue; // blotted + + Instruction *Retain = cast(V); + Value *Arg = GetObjCArg(Retain); + + // If the object being released is in static or stack storage, we know it's + // not being managed by ObjC reference counting, so we can delete pairs + // regardless of what possible decrements or uses lie between them. + bool KnownSafe = isa(Arg) || isa(Arg); + + // If a pair happens in a region where it is known that the reference count + // is already incremented, we can similarly ignore possible decrements. + bool KnownIncrementedTD = true, KnownIncrementedBU = true; + + // Connect the dots between the top-down-collected RetainsToMove and + // bottom-up-collected ReleasesToMove to form sets of related calls. + // This is an iterative process so that we connect multiple releases + // to multiple retains if needed. + unsigned OldDelta = 0; + unsigned NewDelta = 0; + unsigned OldCount = 0; + unsigned NewCount = 0; + bool FirstRelease = true; + bool FirstRetain = true; + NewRetains.push_back(Retain); + for (;;) { + for (SmallVectorImpl::const_iterator + NI = NewRetains.begin(), NE = NewRetains.end(); NI != NE; ++NI) { + Instruction *NewRetain = *NI; + MapVector::const_iterator It = Retains.find(NewRetain); + assert(It != Retains.end()); + const RRInfo &NewRetainRRI = It->second; + KnownIncrementedTD &= NewRetainRRI.KnownIncremented; + for (SmallPtrSet::const_iterator + LI = NewRetainRRI.Calls.begin(), + LE = NewRetainRRI.Calls.end(); LI != LE; ++LI) { + Instruction *NewRetainRelease = *LI; + DenseMap::const_iterator Jt = + Releases.find(NewRetainRelease); + if (Jt == Releases.end()) + goto next_retain; + const RRInfo &NewRetainReleaseRRI = Jt->second; + assert(NewRetainReleaseRRI.Calls.count(NewRetain)); + if (ReleasesToMove.Calls.insert(NewRetainRelease)) { + OldDelta -= + BBStates[NewRetainRelease->getParent()].GetAllPathCount(); + + // Merge the ReleaseMetadata and IsTailCallRelease values. + if (FirstRelease) { + ReleasesToMove.ReleaseMetadata = + NewRetainReleaseRRI.ReleaseMetadata; + ReleasesToMove.IsTailCallRelease = + NewRetainReleaseRRI.IsTailCallRelease; + FirstRelease = false; + } else { + if (ReleasesToMove.ReleaseMetadata != + NewRetainReleaseRRI.ReleaseMetadata) + ReleasesToMove.ReleaseMetadata = 0; + if (ReleasesToMove.IsTailCallRelease != + NewRetainReleaseRRI.IsTailCallRelease) + ReleasesToMove.IsTailCallRelease = false; + } + + // Collect the optimal insertion points. + if (!KnownSafe) + for (SmallPtrSet::const_iterator + RI = NewRetainReleaseRRI.ReverseInsertPts.begin(), + RE = NewRetainReleaseRRI.ReverseInsertPts.end(); + RI != RE; ++RI) { + Instruction *RIP = *RI; + if (ReleasesToMove.ReverseInsertPts.insert(RIP)) + NewDelta -= BBStates[RIP->getParent()].GetAllPathCount(); + } + NewReleases.push_back(NewRetainRelease); + } + } + } + NewRetains.clear(); + if (NewReleases.empty()) break; + + // Back the other way. + for (SmallVectorImpl::const_iterator + NI = NewReleases.begin(), NE = NewReleases.end(); NI != NE; ++NI) { + Instruction *NewRelease = *NI; + DenseMap::const_iterator It = + Releases.find(NewRelease); + assert(It != Releases.end()); + const RRInfo &NewReleaseRRI = It->second; + KnownIncrementedBU &= NewReleaseRRI.KnownIncremented; + for (SmallPtrSet::const_iterator + LI = NewReleaseRRI.Calls.begin(), + LE = NewReleaseRRI.Calls.end(); LI != LE; ++LI) { + Instruction *NewReleaseRetain = *LI; + MapVector::const_iterator Jt = + Retains.find(NewReleaseRetain); + if (Jt == Retains.end()) + goto next_retain; + const RRInfo &NewReleaseRetainRRI = Jt->second; + assert(NewReleaseRetainRRI.Calls.count(NewRelease)); + if (RetainsToMove.Calls.insert(NewReleaseRetain)) { + unsigned PathCount = + BBStates[NewReleaseRetain->getParent()].GetAllPathCount(); + OldDelta += PathCount; + OldCount += PathCount; + + // Merge the IsRetainBlock values. + if (FirstRetain) { + RetainsToMove.IsRetainBlock = NewReleaseRetainRRI.IsRetainBlock; + FirstRetain = false; + } else if (ReleasesToMove.IsRetainBlock != + NewReleaseRetainRRI.IsRetainBlock) + // It's not possible to merge the sequences if one uses + // objc_retain and the other uses objc_retainBlock. + goto next_retain; + + // Collect the optimal insertion points. + if (!KnownSafe) + for (SmallPtrSet::const_iterator + RI = NewReleaseRetainRRI.ReverseInsertPts.begin(), + RE = NewReleaseRetainRRI.ReverseInsertPts.end(); + RI != RE; ++RI) { + Instruction *RIP = *RI; + if (RetainsToMove.ReverseInsertPts.insert(RIP)) { + PathCount = BBStates[RIP->getParent()].GetAllPathCount(); + NewDelta += PathCount; + NewCount += PathCount; + } + } + NewRetains.push_back(NewReleaseRetain); + } + } + } + NewReleases.clear(); + if (NewRetains.empty()) break; + } + + // If the pointer is known incremented, we can safely delete the pair + // regardless of what's between them. + if (KnownIncrementedTD || KnownIncrementedBU) { + RetainsToMove.ReverseInsertPts.clear(); + ReleasesToMove.ReverseInsertPts.clear(); + NewCount = 0; + } + + // Determine whether the original call points are balanced in the retain and + // release calls through the program. If not, conservatively don't touch + // them. + // TODO: It's theoretically possible to do code motion in this case, as + // long as the existing imbalances are maintained. + if (OldDelta != 0) + goto next_retain; + + // Determine whether the new insertion points we computed preserve the + // balance of retain and release calls through the program. + // TODO: If the fully aggressive solution isn't valid, try to find a + // less aggressive solution which is. + if (NewDelta != 0) + goto next_retain; + + // Ok, everything checks out and we're all set. Let's move some code! + Changed = true; + AnyPairsCompletelyEliminated = NewCount == 0; + NumRRs += OldCount - NewCount; + MoveCalls(Arg, RetainsToMove, ReleasesToMove, Retains, Releases, DeadInsts); + + next_retain: + NewReleases.clear(); + NewRetains.clear(); + RetainsToMove.clear(); + ReleasesToMove.clear(); + } + + // Now that we're done moving everything, we can delete the newly dead + // instructions, as we no longer need them as insert points. + while (!DeadInsts.empty()) + EraseInstruction(DeadInsts.pop_back_val()); + + return AnyPairsCompletelyEliminated; +} + +/// OptimizeWeakCalls - Weak pointer optimizations. +void ObjCARCOpt::OptimizeWeakCalls(Function &F) { + // First, do memdep-style RLE and S2L optimizations. We can't use memdep + // itself because it uses AliasAnalysis and we need to do provenance + // queries instead. + for (inst_iterator I = inst_begin(&F), E = inst_end(&F); I != E; ) { + Instruction *Inst = &*I++; + InstructionClass Class = GetBasicInstructionClass(Inst); + if (Class != IC_LoadWeak && Class != IC_LoadWeakRetained) + continue; + + // Delete objc_loadWeak calls with no users. + if (Class == IC_LoadWeak && Inst->use_empty()) { + Inst->eraseFromParent(); + continue; + } + + // TODO: For now, just look for an earlier available version of this value + // within the same block. Theoretically, we could do memdep-style non-local + // analysis too, but that would want caching. A better approach would be to + // use the technique that EarlyCSE uses. + inst_iterator Current = llvm::prior(I); + BasicBlock *CurrentBB = Current.getBasicBlockIterator(); + for (BasicBlock::iterator B = CurrentBB->begin(), + J = Current.getInstructionIterator(); + J != B; --J) { + Instruction *EarlierInst = &*llvm::prior(J); + InstructionClass EarlierClass = GetInstructionClass(EarlierInst); + switch (EarlierClass) { + case IC_LoadWeak: + case IC_LoadWeakRetained: { + // If this is loading from the same pointer, replace this load's value + // with that one. + CallInst *Call = cast(Inst); + CallInst *EarlierCall = cast(EarlierInst); + Value *Arg = Call->getArgOperand(0); + Value *EarlierArg = EarlierCall->getArgOperand(0); + switch (PA.getAA()->alias(Arg, EarlierArg)) { + case AliasAnalysis::MustAlias: + Changed = true; + // If the load has a builtin retain, insert a plain retain for it. + if (Class == IC_LoadWeakRetained) { + CallInst *CI = + CallInst::Create(getRetainCallee(F.getParent()), EarlierCall, + "", Call); + CI->setTailCall(); + } + // Zap the fully redundant load. + Call->replaceAllUsesWith(EarlierCall); + Call->eraseFromParent(); + goto clobbered; + case AliasAnalysis::MayAlias: + case AliasAnalysis::PartialAlias: + goto clobbered; + case AliasAnalysis::NoAlias: + break; + } + break; + } + case IC_StoreWeak: + case IC_InitWeak: { + // If this is storing to the same pointer and has the same size etc. + // replace this load's value with the stored value. + CallInst *Call = cast(Inst); + CallInst *EarlierCall = cast(EarlierInst); + Value *Arg = Call->getArgOperand(0); + Value *EarlierArg = EarlierCall->getArgOperand(0); + switch (PA.getAA()->alias(Arg, EarlierArg)) { + case AliasAnalysis::MustAlias: + Changed = true; + // If the load has a builtin retain, insert a plain retain for it. + if (Class == IC_LoadWeakRetained) { + CallInst *CI = + CallInst::Create(getRetainCallee(F.getParent()), EarlierCall, + "", Call); + CI->setTailCall(); + } + // Zap the fully redundant load. + Call->replaceAllUsesWith(EarlierCall->getArgOperand(1)); + Call->eraseFromParent(); + goto clobbered; + case AliasAnalysis::MayAlias: + case AliasAnalysis::PartialAlias: + goto clobbered; + case AliasAnalysis::NoAlias: + break; + } + break; + } + case IC_MoveWeak: + case IC_CopyWeak: + // TOOD: Grab the copied value. + goto clobbered; + case IC_AutoreleasepoolPush: + case IC_None: + case IC_User: + // Weak pointers are only modified through the weak entry points + // (and arbitrary calls, which could call the weak entry points). + break; + default: + // Anything else could modify the weak pointer. + goto clobbered; + } + } + clobbered:; + } + + // Then, for each destroyWeak with an alloca operand, check to see if + // the alloca and all its users can be zapped. + for (inst_iterator I = inst_begin(&F), E = inst_end(&F); I != E; ) { + Instruction *Inst = &*I++; + InstructionClass Class = GetBasicInstructionClass(Inst); + if (Class != IC_DestroyWeak) + continue; + + CallInst *Call = cast(Inst); + Value *Arg = Call->getArgOperand(0); + if (AllocaInst *Alloca = dyn_cast(Arg)) { + for (Value::use_iterator UI = Alloca->use_begin(), + UE = Alloca->use_end(); UI != UE; ++UI) { + Instruction *UserInst = cast(*UI); + switch (GetBasicInstructionClass(UserInst)) { + case IC_InitWeak: + case IC_StoreWeak: + case IC_DestroyWeak: + continue; + default: + goto done; + } + } + Changed = true; + for (Value::use_iterator UI = Alloca->use_begin(), + UE = Alloca->use_end(); UI != UE; ) { + CallInst *UserInst = cast(*UI++); + if (!UserInst->use_empty()) + UserInst->replaceAllUsesWith(UserInst->getOperand(1)); + UserInst->eraseFromParent(); + } + Alloca->eraseFromParent(); + done:; + } + } +} + +/// OptimizeSequences - Identify program paths which execute sequences of +/// retains and releases which can be eliminated. +bool ObjCARCOpt::OptimizeSequences(Function &F) { + /// Releases, Retains - These are used to store the results of the main flow + /// analysis. These use Value* as the key instead of Instruction* so that the + /// map stays valid when we get around to rewriting code and calls get + /// replaced by arguments. + DenseMap Releases; + MapVector Retains; + + /// BBStates, This is used during the traversal of the function to track the + /// states for each identified object at each block. + DenseMap BBStates; + + // Analyze the CFG of the function, and all instructions. + bool NestingDetected = Visit(F, BBStates, Retains, Releases); + + // Transform. + return PerformCodePlacement(BBStates, Retains, Releases) && NestingDetected; +} + +/// OptimizeReturns - Look for this pattern: +/// +/// %call = call i8* @something(...) +/// %2 = call i8* @objc_retain(i8* %call) +/// %3 = call i8* @objc_autorelease(i8* %2) +/// ret i8* %3 +/// +/// And delete the retain and autorelease. +/// +/// Otherwise if it's just this: +/// +/// %3 = call i8* @objc_autorelease(i8* %2) +/// ret i8* %3 +/// +/// convert the autorelease to autoreleaseRV. +void ObjCARCOpt::OptimizeReturns(Function &F) { + if (!F.getReturnType()->isPointerTy()) + return; + + SmallPtrSet DependingInstructions; + SmallPtrSet Visited; + for (Function::iterator FI = F.begin(), FE = F.end(); FI != FE; ++FI) { + BasicBlock *BB = FI; + ReturnInst *Ret = dyn_cast(&BB->back()); + if (!Ret) continue; + + const Value *Arg = StripPointerCastsAndObjCCalls(Ret->getOperand(0)); + FindDependencies(NeedsPositiveRetainCount, Arg, + BB, Ret, DependingInstructions, Visited, PA); + if (DependingInstructions.size() != 1) + goto next_block; + + { + CallInst *Autorelease = + dyn_cast_or_null(*DependingInstructions.begin()); + if (!Autorelease) + goto next_block; + InstructionClass AutoreleaseClass = + GetBasicInstructionClass(Autorelease); + if (!IsAutorelease(AutoreleaseClass)) + goto next_block; + if (GetObjCArg(Autorelease) != Arg) + goto next_block; + + DependingInstructions.clear(); + Visited.clear(); + + // Check that there is nothing that can affect the reference + // count between the autorelease and the retain. + FindDependencies(CanChangeRetainCount, Arg, + BB, Autorelease, DependingInstructions, Visited, PA); + if (DependingInstructions.size() != 1) + goto next_block; + + { + CallInst *Retain = + dyn_cast_or_null(*DependingInstructions.begin()); + + // Check that we found a retain with the same argument. + if (!Retain || + !IsRetain(GetBasicInstructionClass(Retain)) || + GetObjCArg(Retain) != Arg) + goto next_block; + + DependingInstructions.clear(); + Visited.clear(); + + // Convert the autorelease to an autoreleaseRV, since it's + // returning the value. + if (AutoreleaseClass == IC_Autorelease) { + Autorelease->setCalledFunction(getAutoreleaseRVCallee(F.getParent())); + AutoreleaseClass = IC_AutoreleaseRV; + } + + // Check that there is nothing that can affect the reference + // count between the retain and the call. + FindDependencies(CanChangeRetainCount, Arg, BB, Retain, + DependingInstructions, Visited, PA); + if (DependingInstructions.size() != 1) + goto next_block; + + { + CallInst *Call = + dyn_cast_or_null(*DependingInstructions.begin()); + + // Check that the pointer is the return value of the call. + if (!Call || Arg != Call) + goto next_block; + + // Check that the call is a regular call. + InstructionClass Class = GetBasicInstructionClass(Call); + if (Class != IC_CallOrUser && Class != IC_Call) + goto next_block; + + // If so, we can zap the retain and autorelease. + Changed = true; + ++NumRets; + EraseInstruction(Retain); + EraseInstruction(Autorelease); + } + } + } + + next_block: + DependingInstructions.clear(); + Visited.clear(); + } +} + +bool ObjCARCOpt::doInitialization(Module &M) { + if (!EnableARCOpts) + return false; + + // Identify the imprecise release metadata kind. + ImpreciseReleaseMDKind = + M.getContext().getMDKindID("clang.imprecise_release"); + + // Identify the declarations for objc_retain and friends. + RetainFunc = M.getFunction("objc_retain"); + RetainBlockFunc = M.getFunction("objc_retainBlock"); + RetainRVFunc = M.getFunction("objc_retainAutoreleasedReturnValue"); + ReleaseFunc = M.getFunction("objc_release"); + AutoreleaseFunc = M.getFunction("objc_autorelease"); + + // Intuitively, objc_retain and others are nocapture, however in practice + // they are not, because they return their argument value. And objc_release + // calls finalizers. + + // These are initialized lazily. + RetainRVCallee = 0; + AutoreleaseRVCallee = 0; + ReleaseCallee = 0; + RetainCallee = 0; + AutoreleaseCallee = 0; + + return false; +} + +bool ObjCARCOpt::runOnFunction(Function &F) { + if (!EnableARCOpts) + return false; + + Changed = false; + + PA.setAA(&getAnalysis()); + + // This pass performs several distinct transformations. As a compile-time aid + // when compiling code that isn't ObjC, skip these if the relevant ObjC + // library functions aren't declared. + + // Preliminary optimizations. This also computs UsedInThisFunction. + OptimizeIndividualCalls(F); + + // Optimizations for weak pointers. + if (UsedInThisFunction & ((1 << IC_LoadWeak) | + (1 << IC_LoadWeakRetained) | + (1 << IC_StoreWeak) | + (1 << IC_InitWeak) | + (1 << IC_CopyWeak) | + (1 << IC_MoveWeak) | + (1 << IC_DestroyWeak))) + OptimizeWeakCalls(F); + + // Optimizations for retain+release pairs. + if (UsedInThisFunction & ((1 << IC_Retain) | + (1 << IC_RetainRV) | + (1 << IC_RetainBlock))) + if (UsedInThisFunction & (1 << IC_Release)) + // Run OptimizeSequences until it either stops making changes or + // no retain+release pair nesting is detected. + while (OptimizeSequences(F)) {} + + // Optimizations if objc_autorelease is used. + if (UsedInThisFunction & + ((1 << IC_Autorelease) | (1 << IC_AutoreleaseRV))) + OptimizeReturns(F); + + return Changed; +} + +void ObjCARCOpt::releaseMemory() { + PA.clear(); +} + +//===----------------------------------------------------------------------===// +// ARC contraction. +//===----------------------------------------------------------------------===// + +// TODO: ObjCARCContract could insert PHI nodes when uses aren't +// dominated by single calls. + +#include "llvm/Operator.h" +#include "llvm/InlineAsm.h" +#include "llvm/Analysis/Dominators.h" + +STATISTIC(NumStoreStrongs, "Number objc_storeStrong calls formed"); + +namespace { + /// ObjCARCContract - Late ARC optimizations. These change the IR in a way + /// that makes it difficult to be analyzed by ObjCARCOpt, so it's run late. + class ObjCARCContract : public FunctionPass { + bool Changed; + AliasAnalysis *AA; + DominatorTree *DT; + ProvenanceAnalysis PA; + + /// StoreStrongCallee, etc. - Declarations for ObjC runtime + /// functions, for use in creating calls to them. These are initialized + /// lazily to avoid cluttering up the Module with unused declarations. + Constant *StoreStrongCallee, + *RetainAutoreleaseCallee, *RetainAutoreleaseRVCallee; + + /// RetainRVMarker - The inline asm string to insert between calls and + /// RetainRV calls to make the optimization work on targets which need it. + const MDString *RetainRVMarker; + + Constant *getStoreStrongCallee(Module *M); + Constant *getRetainAutoreleaseCallee(Module *M); + Constant *getRetainAutoreleaseRVCallee(Module *M); + + bool ContractAutorelease(Function &F, Instruction *Autorelease, + InstructionClass Class, + SmallPtrSet + &DependingInstructions, + SmallPtrSet + &Visited); + + void ContractRelease(Instruction *Release, + inst_iterator &Iter); + + virtual void getAnalysisUsage(AnalysisUsage &AU) const; + virtual bool doInitialization(Module &M); + virtual bool runOnFunction(Function &F); + + public: + static char ID; + ObjCARCContract() : FunctionPass(ID) { + initializeObjCARCContractPass(*PassRegistry::getPassRegistry()); + } + }; +} + +char ObjCARCContract::ID = 0; +INITIALIZE_PASS_BEGIN(ObjCARCContract, + "objc-arc-contract", "ObjC ARC contraction", false, false) +INITIALIZE_AG_DEPENDENCY(AliasAnalysis) +INITIALIZE_PASS_DEPENDENCY(DominatorTree) +INITIALIZE_PASS_END(ObjCARCContract, + "objc-arc-contract", "ObjC ARC contraction", false, false) + +Pass *llvm::createObjCARCContractPass() { + return new ObjCARCContract(); +} + +void ObjCARCContract::getAnalysisUsage(AnalysisUsage &AU) const { + AU.addRequired(); + AU.addRequired(); + AU.setPreservesCFG(); +} + +Constant *ObjCARCContract::getStoreStrongCallee(Module *M) { + if (!StoreStrongCallee) { + LLVMContext &C = M->getContext(); + const Type *I8X = PointerType::getUnqual(Type::getInt8Ty(C)); + const Type *I8XX = PointerType::getUnqual(I8X); + std::vector Params; + Params.push_back(I8XX); + Params.push_back(I8X); + + AttrListPtr Attributes; + Attributes.addAttr(~0u, Attribute::NoUnwind); + Attributes.addAttr(1, Attribute::NoCapture); + + StoreStrongCallee = + M->getOrInsertFunction( + "objc_storeStrong", + FunctionType::get(Type::getVoidTy(C), Params, /*isVarArg=*/false), + Attributes); + } + return StoreStrongCallee; +} + +Constant *ObjCARCContract::getRetainAutoreleaseCallee(Module *M) { + if (!RetainAutoreleaseCallee) { + LLVMContext &C = M->getContext(); + const Type *I8X = PointerType::getUnqual(Type::getInt8Ty(C)); + std::vector Params; + Params.push_back(I8X); + const FunctionType *FTy = + FunctionType::get(I8X, Params, /*isVarArg=*/false); + AttrListPtr Attributes; + Attributes.addAttr(~0u, Attribute::NoUnwind); + RetainAutoreleaseCallee = + M->getOrInsertFunction("objc_retainAutorelease", FTy, Attributes); + } + return RetainAutoreleaseCallee; +} + +Constant *ObjCARCContract::getRetainAutoreleaseRVCallee(Module *M) { + if (!RetainAutoreleaseRVCallee) { + LLVMContext &C = M->getContext(); + const Type *I8X = PointerType::getUnqual(Type::getInt8Ty(C)); + std::vector Params; + Params.push_back(I8X); + const FunctionType *FTy = + FunctionType::get(I8X, Params, /*isVarArg=*/false); + AttrListPtr Attributes; + Attributes.addAttr(~0u, Attribute::NoUnwind); + RetainAutoreleaseRVCallee = + M->getOrInsertFunction("objc_retainAutoreleaseReturnValue", FTy, + Attributes); + } + return RetainAutoreleaseRVCallee; +} + +/// ContractAutorelease - Merge an autorelease with a retain into a fused +/// call. +bool +ObjCARCContract::ContractAutorelease(Function &F, Instruction *Autorelease, + InstructionClass Class, + SmallPtrSet + &DependingInstructions, + SmallPtrSet + &Visited) { + const Value *Arg = GetObjCArg(Autorelease); + + // Check that there are no instructions between the retain and the autorelease + // (such as an autorelease_pop) which may change the count. + CallInst *Retain = 0; + if (Class == IC_AutoreleaseRV) + FindDependencies(RetainAutoreleaseRVDep, Arg, + Autorelease->getParent(), Autorelease, + DependingInstructions, Visited, PA); + else + FindDependencies(RetainAutoreleaseDep, Arg, + Autorelease->getParent(), Autorelease, + DependingInstructions, Visited, PA); + + Visited.clear(); + if (DependingInstructions.size() != 1) { + DependingInstructions.clear(); + return false; + } + + Retain = dyn_cast_or_null(*DependingInstructions.begin()); + DependingInstructions.clear(); + + if (!Retain || + GetBasicInstructionClass(Retain) != IC_Retain || + GetObjCArg(Retain) != Arg) + return false; + + Changed = true; + ++NumPeeps; + + if (Class == IC_AutoreleaseRV) + Retain->setCalledFunction(getRetainAutoreleaseRVCallee(F.getParent())); + else + Retain->setCalledFunction(getRetainAutoreleaseCallee(F.getParent())); + + EraseInstruction(Autorelease); + return true; +} + +/// ContractRelease - Attempt to merge an objc_release with a store, load, and +/// objc_retain to form an objc_storeStrong. This can be a little tricky because +/// the instructions don't always appear in order, and there may be unrelated +/// intervening instructions. +void ObjCARCContract::ContractRelease(Instruction *Release, + inst_iterator &Iter) { + LoadInst *Load = dyn_cast(GetObjCArg(Release)); + if (!Load || Load->isVolatile()) return; + + // For now, require everything to be in one basic block. + BasicBlock *BB = Release->getParent(); + if (Load->getParent() != BB) return; + + // Walk down to find the store. + BasicBlock::iterator I = Load, End = BB->end(); + ++I; + AliasAnalysis::Location Loc = AA->getLocation(Load); + while (I != End && + (&*I == Release || + IsRetain(GetBasicInstructionClass(I)) || + !(AA->getModRefInfo(I, Loc) & AliasAnalysis::Mod))) + ++I; + StoreInst *Store = dyn_cast(I); + if (!Store || Store->isVolatile()) return; + if (Store->getPointerOperand() != Loc.Ptr) return; + + Value *New = StripPointerCastsAndObjCCalls(Store->getValueOperand()); + + // Walk up to find the retain. + I = Store; + BasicBlock::iterator Begin = BB->begin(); + while (I != Begin && GetBasicInstructionClass(I) != IC_Retain) + --I; + Instruction *Retain = I; + if (GetBasicInstructionClass(Retain) != IC_Retain) return; + if (GetObjCArg(Retain) != New) return; + + Changed = true; + ++NumStoreStrongs; + + LLVMContext &C = Release->getContext(); + const Type *I8X = PointerType::getUnqual(Type::getInt8Ty(C)); + const Type *I8XX = PointerType::getUnqual(I8X); + + Value *Args[] = { Load->getPointerOperand(), New }; + if (Args[0]->getType() != I8XX) + Args[0] = new BitCastInst(Args[0], I8XX, "", Store); + if (Args[1]->getType() != I8X) + Args[1] = new BitCastInst(Args[1], I8X, "", Store); + CallInst *StoreStrong = + CallInst::Create(getStoreStrongCallee(BB->getParent()->getParent()), + Args, array_endof(Args), "", Store); + StoreStrong->setDoesNotThrow(); + StoreStrong->setDebugLoc(Store->getDebugLoc()); + + if (&*Iter == Store) ++Iter; + Store->eraseFromParent(); + Release->eraseFromParent(); + EraseInstruction(Retain); + if (Load->use_empty()) + Load->eraseFromParent(); +} + +bool ObjCARCContract::doInitialization(Module &M) { + // These are initialized lazily. + StoreStrongCallee = 0; + RetainAutoreleaseCallee = 0; + RetainAutoreleaseRVCallee = 0; + + // Initialize RetainRVMarker. + RetainRVMarker = 0; + if (NamedMDNode *NMD = + M.getNamedMetadata("clang.arc.retainAutoreleasedReturnValueMarker")) + if (NMD->getNumOperands() == 1) { + const MDNode *N = NMD->getOperand(0); + if (N->getNumOperands() == 1) + if (const MDString *S = dyn_cast(N->getOperand(0))) + RetainRVMarker = S; + } + + return false; +} + +bool ObjCARCContract::runOnFunction(Function &F) { + if (!EnableARCOpts) + return false; + + Changed = false; + AA = &getAnalysis(); + DT = &getAnalysis(); + + PA.setAA(&getAnalysis()); + + // For ObjC library calls which return their argument, replace uses of the + // argument with uses of the call return value, if it dominates the use. This + // reduces register pressure. + SmallPtrSet DependingInstructions; + SmallPtrSet Visited; + for (inst_iterator I = inst_begin(&F), E = inst_end(&F); I != E; ) { + Instruction *Inst = &*I++; + + // Only these library routines return their argument. In particular, + // objc_retainBlock does not necessarily return its argument. + InstructionClass Class = GetBasicInstructionClass(Inst); + switch (Class) { + case IC_Retain: + case IC_FusedRetainAutorelease: + case IC_FusedRetainAutoreleaseRV: + break; + case IC_Autorelease: + case IC_AutoreleaseRV: + if (ContractAutorelease(F, Inst, Class, DependingInstructions, Visited)) + continue; + break; + case IC_RetainRV: { + // If we're compiling for a target which needs a special inline-asm + // marker to do the retainAutoreleasedReturnValue optimization, + // insert it now. + if (!RetainRVMarker) + break; + BasicBlock::iterator BBI = Inst; + --BBI; + while (isNoopInstruction(BBI)) --BBI; + if (&*BBI == GetObjCArg(Inst)) { + InlineAsm *IA = + InlineAsm::get(FunctionType::get(Type::getVoidTy(Inst->getContext()), + /*isVarArg=*/false), + RetainRVMarker->getString(), + /*Constraints=*/"", /*hasSideEffects=*/true); + CallInst::Create(IA, "", Inst); + } + break; + } + case IC_InitWeak: { + // objc_initWeak(p, null) => *p = null + CallInst *CI = cast(Inst); + if (isNullOrUndef(CI->getArgOperand(1))) { + Value *Null = + ConstantPointerNull::get(cast(CI->getType())); + Changed = true; + new StoreInst(Null, CI->getArgOperand(0), CI); + CI->replaceAllUsesWith(Null); + CI->eraseFromParent(); + } + continue; + } + case IC_Release: + ContractRelease(Inst, I); + continue; + default: + continue; + } + + // Don't use GetObjCArg because we don't want to look through bitcasts + // and such; to do the replacement, the argument must have type i8*. + const Value *Arg = cast(Inst)->getArgOperand(0); + for (;;) { + // If we're compiling bugpointed code, don't get in trouble. + if (!isa(Arg) && !isa(Arg)) + break; + // Look through the uses of the pointer. + for (Value::const_use_iterator UI = Arg->use_begin(), UE = Arg->use_end(); + UI != UE; ) { + Use &U = UI.getUse(); + unsigned OperandNo = UI.getOperandNo(); + ++UI; // Increment UI now, because we may unlink its element. + if (Instruction *UserInst = dyn_cast(U.getUser())) + if (Inst != UserInst && DT->dominates(Inst, UserInst)) { + Changed = true; + Instruction *Replacement = Inst; + const Type *UseTy = U.get()->getType(); + if (PHINode *PHI = dyn_cast(UserInst)) { + // For PHI nodes, insert the bitcast in the predecessor block. + unsigned ValNo = + PHINode::getIncomingValueNumForOperand(OperandNo); + BasicBlock *BB = + PHI->getIncomingBlock(ValNo); + if (Replacement->getType() != UseTy) + Replacement = new BitCastInst(Replacement, UseTy, "", + &BB->back()); + for (unsigned i = 0, e = PHI->getNumIncomingValues(); + i != e; ++i) + if (PHI->getIncomingBlock(i) == BB) { + // Keep the UI iterator valid. + if (&PHI->getOperandUse( + PHINode::getOperandNumForIncomingValue(i)) == + &UI.getUse()) + ++UI; + PHI->setIncomingValue(i, Replacement); + } + } else { + if (Replacement->getType() != UseTy) + Replacement = new BitCastInst(Replacement, UseTy, "", UserInst); + U.set(Replacement); + } + } + } + + // If Arg is a no-op casted pointer, strip one level of casts and + // iterate. + if (const BitCastInst *BI = dyn_cast(Arg)) + Arg = BI->getOperand(0); + else if (isa(Arg) && + cast(Arg)->hasAllZeroIndices()) + Arg = cast(Arg)->getPointerOperand(); + else if (isa(Arg) && + !cast(Arg)->mayBeOverridden()) + Arg = cast(Arg)->getAliasee(); + else + break; + } + } + + return Changed; +} diff --git a/lib/Transforms/Scalar/Scalar.cpp b/lib/Transforms/Scalar/Scalar.cpp index 32a05061743..158d65352f9 100644 --- a/lib/Transforms/Scalar/Scalar.cpp +++ b/lib/Transforms/Scalar/Scalar.cpp @@ -49,6 +49,10 @@ void llvm::initializeScalarOpts(PassRegistry &Registry) { initializeLoopIdiomRecognizePass(Registry); initializeLowerAtomicPass(Registry); initializeMemCpyOptPass(Registry); + initializeObjCARCAliasAnalysisPass(Registry); + initializeObjCARCExpandPass(Registry); + initializeObjCARCContractPass(Registry); + initializeObjCARCOptPass(Registry); initializeReassociatePass(Registry); initializeRegToMemPass(Registry); initializeSCCPPass(Registry); diff --git a/test/Transforms/ObjCARC/basic.ll b/test/Transforms/ObjCARC/basic.ll new file mode 100644 index 00000000000..a6bbf8674b4 --- /dev/null +++ b/test/Transforms/ObjCARC/basic.ll @@ -0,0 +1,1898 @@ +; RUN: opt -objc-arc -S < %s | FileCheck %s + +target datalayout = "e-p:64:64:64" + +declare i8* @objc_retain(i8*) +declare void @objc_release(i8*) +declare i8* @objc_autorelease(i8*) +declare void @objc_autoreleasePoolPop(i8*) +declare void @objc_autoreleasePoolPush() +declare i8* @objc_retainBlock(i8*) + +declare i8* @objc_retainedObject(i8*) +declare i8* @objc_unretainedObject(i8*) +declare i8* @objc_unretainedPointer(i8*) + +declare void @use_pointer(i8*) +declare void @callee() +declare void @callee_fnptr(void ()*) +declare void @invokee() +declare i8* @returner() + +declare void @llvm.dbg.value(metadata, i64, metadata) + +declare i8* @objc_msgSend(i8*, i8*, ...) + +; Simple retain+release pair deletion, with some intervening control +; flow and harmless instructions. + +; CHECK: define void @test0( +; CHECK-NOT: @objc_ +; CHECK: } +define void @test0(i32* %x, i1 %p) nounwind { +entry: + %a = bitcast i32* %x to i8* + %0 = call i8* @objc_retain(i8* %a) nounwind + br i1 %p, label %t, label %f + +t: + store i8 3, i8* %a + %b = bitcast i32* %x to float* + store float 2.0, float* %b + br label %return + +f: + store i32 7, i32* %x + br label %return + +return: + %c = bitcast i32* %x to i8* + call void @objc_release(i8* %c) nounwind + ret void +} + +; Like test0 but the release isn't always executed when the retain is, +; so the optimization is not safe. + +; TODO: Make the objc_release's argument be %0. + +; CHECK: define void @test1( +; CHECK: @objc_retain(i8* %a) +; CHECK: @objc_release +; CHECK: } +define void @test1(i32* %x, i1 %p, i1 %q) nounwind { +entry: + %a = bitcast i32* %x to i8* + %0 = call i8* @objc_retain(i8* %a) nounwind + br i1 %p, label %t, label %f + +t: + store i8 3, i8* %a + %b = bitcast i32* %x to float* + store float 2.0, float* %b + br label %return + +f: + store i32 7, i32* %x + call void @callee() + br i1 %q, label %return, label %alt_return + +return: + %c = bitcast i32* %x to i8* + call void @objc_release(i8* %c) nounwind + ret void + +alt_return: + ret void +} + +; Like test0 but the pointer is passed to an intervening call, +; so the optimization is not safe. + +; CHECK: define void @test2( +; CHECK: @objc_retain(i8* %a) +; CHECK: @objc_release +; CHECK: } +define void @test2(i32* %x, i1 %p) nounwind { +entry: + %a = bitcast i32* %x to i8* + %0 = call i8* @objc_retain(i8* %a) nounwind + br i1 %p, label %t, label %f + +t: + store i8 3, i8* %a + %b = bitcast i32* %x to float* + store float 2.0, float* %b + br label %return + +f: + store i32 7, i32* %x + call void @use_pointer(i8* %0) + %d = bitcast i32* %x to float* + store float 3.0, float* %d + br label %return + +return: + %c = bitcast i32* %x to i8* + call void @objc_release(i8* %c) nounwind + ret void +} + +; Like test0 but the release is in a loop, +; so the optimization is not safe. + +; TODO: For now, assume this can't happen. + +; CHECK: define void @test3( +; TODO: @objc_retain(i8* %a) +; TODO: @objc_release +; CHECK: } +define void @test3(i32* %x, i1* %q) nounwind { +entry: + %a = bitcast i32* %x to i8* + %0 = call i8* @objc_retain(i8* %a) nounwind + br label %loop + +loop: + %c = bitcast i32* %x to i8* + call void @objc_release(i8* %c) nounwind + %j = volatile load i1* %q + br i1 %j, label %loop, label %return + +return: + ret void +} + +; TODO: For now, assume this can't happen. + +; Like test0 but the retain is in a loop, +; so the optimization is not safe. + +; CHECK: define void @test4( +; TODO: @objc_retain(i8* %a) +; TODO: @objc_release +; CHECK: } +define void @test4(i32* %x, i1* %q) nounwind { +entry: + br label %loop + +loop: + %a = bitcast i32* %x to i8* + %0 = call i8* @objc_retain(i8* %a) nounwind + %j = volatile load i1* %q + br i1 %j, label %loop, label %return + +return: + %c = bitcast i32* %x to i8* + call void @objc_release(i8* %c) nounwind + ret void +} + +; Like test0 but the pointer is conditionally passed to an intervening call, +; so the optimization is not safe. + +; CHECK: define void @test5( +; CHECK: @objc_retain(i8* +; CHECK: @objc_release +; CHECK: } +define void @test5(i32* %x, i1 %q, i8* %y) nounwind { +entry: + %a = bitcast i32* %x to i8* + %0 = call i8* @objc_retain(i8* %a) nounwind + %s = select i1 %q, i8* %y, i8* %0 + call void @use_pointer(i8* %s) + store i32 7, i32* %x + %c = bitcast i32* %x to i8* + call void @objc_release(i8* %c) nounwind + ret void +} + +; retain+release pair deletion, where the release happens on two different +; flow paths. + +; CHECK: define void @test6( +; CHECK-NOT: @objc_ +; CHECK: } +define void @test6(i32* %x, i1 %p) nounwind { +entry: + %a = bitcast i32* %x to i8* + %0 = call i8* @objc_retain(i8* %a) nounwind + br i1 %p, label %t, label %f + +t: + store i8 3, i8* %a + %b = bitcast i32* %x to float* + store float 2.0, float* %b + %ct = bitcast i32* %x to i8* + call void @objc_release(i8* %ct) nounwind + br label %return + +f: + store i32 7, i32* %x + call void @callee() + %cf = bitcast i32* %x to i8* + call void @objc_release(i8* %cf) nounwind + br label %return + +return: + ret void +} + +; retain+release pair deletion, where the retain happens on two different +; flow paths. + +; CHECK: define void @test7( +; CHECK-NOT: @objc_ +; CHECK: } +define void @test7(i32* %x, i1 %p) nounwind { +entry: + %a = bitcast i32* %x to i8* + br i1 %p, label %t, label %f + +t: + %0 = call i8* @objc_retain(i8* %a) nounwind + store i8 3, i8* %a + %b = bitcast i32* %x to float* + store float 2.0, float* %b + br label %return + +f: + %1 = call i8* @objc_retain(i8* %a) nounwind + store i32 7, i32* %x + call void @callee() + br label %return + +return: + %c = bitcast i32* %x to i8* + call void @objc_release(i8* %c) nounwind + ret void +} + +; Like test7, but there's a retain/retainBlock mismatch. Don't delete! + +; CHECK: define void @test7b +; CHECK: t: +; CHECK: call i8* @objc_retainBlock +; CHECK: f: +; CHECK: call i8* @objc_retain +; CHECK: return: +; CHECK: call void @objc_release +; CHECK: } +define void @test7b(i32* %x, i1 %p) nounwind { +entry: + %a = bitcast i32* %x to i8* + br i1 %p, label %t, label %f + +t: + %0 = call i8* @objc_retainBlock(i8* %a) nounwind + store i8 3, i8* %a + %b = bitcast i32* %x to float* + store float 2.0, float* %b + br label %return + +f: + %1 = call i8* @objc_retain(i8* %a) nounwind + store i32 7, i32* %x + call void @callee() + br label %return + +return: + %c = bitcast i32* %x to i8* + call void @objc_release(i8* %c) nounwind + ret void +} + +; retain+release pair deletion, where the retain and release both happen on +; different flow paths. Wild! + +; CHECK: define void @test8( +; CHECK-NOT: @objc_ +; CHECK: } +define void @test8(i32* %x, i1 %p, i1 %q) nounwind { +entry: + %a = bitcast i32* %x to i8* + br i1 %p, label %t, label %f + +t: + %0 = call i8* @objc_retain(i8* %a) nounwind + store i8 3, i8* %a + %b = bitcast i32* %x to float* + store float 2.0, float* %b + br label %mid + +f: + %1 = call i8* @objc_retain(i8* %a) nounwind + store i32 7, i32* %x + br label %mid + +mid: + br i1 %q, label %u, label %g + +u: + call void @callee() + %cu = bitcast i32* %x to i8* + call void @objc_release(i8* %cu) nounwind + br label %return + +g: + %cg = bitcast i32* %x to i8* + call void @objc_release(i8* %cg) nounwind + br label %return + +return: + ret void +} + +; Trivial retain+release pair deletion. + +; CHECK: define void @test9( +; CHECK-NOT: @objc_ +; CHECK: } +define void @test9(i8* %x) nounwind { +entry: + %0 = call i8* @objc_retain(i8* %x) nounwind + call void @objc_release(i8* %0) nounwind + ret void +} + +; Retain+release pair, but on an unknown pointer relationship. Don't delete! + +; CHECK: define void @test9b +; CHECK: @objc_retain(i8* %x) +; CHECK: @objc_release(i8* %s) +; CHECK: } +define void @test9b(i8* %x, i1 %j, i8* %p) nounwind { +entry: + %0 = call i8* @objc_retain(i8* %x) nounwind + %s = select i1 %j, i8* %x, i8* %p + call void @objc_release(i8* %s) nounwind + ret void +} + +; Trivial retain+release pair with intervening calls - don't delete! + +; CHECK: define void @test10( +; CHECK: @objc_retain(i8* %x) +; CHECK: @use_pointer +; CHECK: @objc_release +; CHECK: } +define void @test10(i8* %x) nounwind { +entry: + %0 = call i8* @objc_retain(i8* %x) nounwind + call void @use_pointer(i8* %x) + call void @use_pointer(i8* %x) + call void @objc_release(i8* %0) nounwind + ret void +} + +; Trivial retain+autoreleaserelease pair. Don't delete! +; Also, add a tail keyword, since objc_retain can never be passed +; a stack argument. + +; CHECK: define void @test11( +; CHECK: tail call i8* @objc_retain(i8* %x) nounwind +; CHECK: tail call i8* @objc_autorelease(i8* %0) nounwind +; CHECK: } +define void @test11(i8* %x) nounwind { +entry: + %0 = call i8* @objc_retain(i8* %x) nounwind + call i8* @objc_autorelease(i8* %0) nounwind + call void @use_pointer(i8* %x) + ret void +} + +; Same as test11 but with no use_pointer call. Delete the pair! + +; CHECK: define void @test11a( +; CHECK: entry: +; CHECK-NEXT: ret void +; CHECK: } +define void @test11a(i8* %x) nounwind { +entry: + %0 = call i8* @objc_retain(i8* %x) nounwind + call i8* @objc_autorelease(i8* %0) nounwind + ret void +} + +; Same as test11 but the value is returned. Do an RV optimization. + +; CHECK: define i8* @test11b( +; CHECK: tail call i8* @objc_retain(i8* %x) nounwind +; CHECK: tail call i8* @objc_autoreleaseReturnValue(i8* %0) nounwind +; CHECK: } +define i8* @test11b(i8* %x) nounwind { +entry: + %0 = call i8* @objc_retain(i8* %x) nounwind + call i8* @objc_autorelease(i8* %0) nounwind + ret i8* %x +} + +; Trivial retain,release pair with intervening call, but it's dominated +; by another retain - delete! + +; CHECK: define void @test12( +; CHECK-NEXT: entry: +; CHECK-NEXT: @objc_retain(i8* %x) +; CHECK-NOT: @objc_ +; CHECK: } +define void @test12(i8* %x, i64 %n) { +entry: + call i8* @objc_retain(i8* %x) nounwind + call i8* @objc_retain(i8* %x) nounwind + call void @use_pointer(i8* %x) + call void @use_pointer(i8* %x) + call void @objc_release(i8* %x) nounwind + ret void +} + +; Trivial retain,autorelease pair. Don't delete! + +; CHECK: define void @test13( +; CHECK: tail call i8* @objc_retain(i8* %x) nounwind +; CHECK: tail call i8* @objc_retain(i8* %x) nounwind +; CHECK: @use_pointer(i8* %x) +; CHECK: tail call i8* @objc_autorelease(i8* %x) nounwind +; CHECK: } +define void @test13(i8* %x, i64 %n) { +entry: + call i8* @objc_retain(i8* %x) nounwind + call i8* @objc_retain(i8* %x) nounwind + call void @use_pointer(i8* %x) + call i8* @objc_autorelease(i8* %x) nounwind + ret void +} + +; Delete the retain+release pair. + +; CHECK: define void @test13b +; CHECK-NEXT: entry: +; CHECK-NEXT: @objc_retain(i8* %x) +; CHECK-NEXT: @use_pointer +; CHECK-NEXT: @use_pointer +; CHECK-NEXT: ret void +define void @test13b(i8* %x, i64 %n) { +entry: + call i8* @objc_retain(i8* %x) nounwind + call i8* @objc_retain(i8* %x) nounwind + call void @use_pointer(i8* %x) + call void @use_pointer(i8* %x) + call void @objc_release(i8* %x) nounwind + ret void +} + +; Don't delete the retain+release pair because there's an +; autoreleasePoolPop in the way. + +; CHECK: define void @test13c +; CHECK: @objc_retain(i8* %x) +; CHECK: @objc_autoreleasePoolPop +; CHECK: @objc_retain(i8* %x) +; CHECK: @use_pointer +; CHECK: @objc_release +; CHECK: } +define void @test13c(i8* %x, i64 %n) { +entry: + call i8* @objc_retain(i8* %x) nounwind + call void @objc_autoreleasePoolPop(i8* undef) + call i8* @objc_retain(i8* %x) nounwind + call void @use_pointer(i8* %x) + call void @use_pointer(i8* %x) + call void @objc_release(i8* %x) nounwind + ret void +} + +; Like test13c, but there's an autoreleasePoolPush in the way, but that +; doesn't matter. + +; CHECK: define void @test13d +; CHECK-NEXT: entry: +; CHECK-NEXT: @objc_retain(i8* %x) +; CHECK-NEXT: @objc_autoreleasePoolPush +; CHECK-NEXT: @use_pointer +; CHECK-NEXT: @use_pointer +; CHECK-NEXT: ret void +define void @test13d(i8* %x, i64 %n) { +entry: + call i8* @objc_retain(i8* %x) nounwind + call void @objc_autoreleasePoolPush() + call i8* @objc_retain(i8* %x) nounwind + call void @use_pointer(i8* %x) + call void @use_pointer(i8* %x) + call void @objc_release(i8* %x) nounwind + ret void +} + +; Trivial retain,release pair with intervening call, but it's post-dominated +; by another release - delete! + +; CHECK: define void @test14( +; CHECK-NEXT: entry: +; CHECK-NEXT: @use_pointer +; CHECK-NEXT: @use_pointer +; CHECK-NEXT: @objc_release +; CHECK-NEXT: ret void +; CHECK-NEXT: } +define void @test14(i8* %x, i64 %n) { +entry: + call i8* @objc_retain(i8* %x) nounwind + call void @use_pointer(i8* %x) + call void @use_pointer(i8* %x) + call void @objc_release(i8* %x) nounwind + call void @objc_release(i8* %x) nounwind + ret void +} + +; Trivial retain,autorelease pair with intervening call, but it's post-dominated +; by another release. Don't delete anything. + +; CHECK: define void @test15( +; CHECK-NEXT: entry: +; CHECK-NEXT: @objc_retain(i8* %x) +; CHECK-NEXT: @use_pointer +; CHECK-NEXT: @objc_autorelease(i8* %x) +; CHECK-NEXT: @objc_release +; CHECK-NEXT: ret void +; CHECK-NEXT: } +define void @test15(i8* %x, i64 %n) { +entry: + call i8* @objc_retain(i8* %x) nounwind + call void @use_pointer(i8* %x) + call i8* @objc_autorelease(i8* %x) nounwind + call void @objc_release(i8* %x) nounwind + ret void +} + +; Trivial retain,autorelease pair, post-dominated +; by another release. Delete the retain and release. + +; CHECK: define void @test15b +; CHECK-NEXT: entry: +; CHECK-NEXT: @objc_autorelease +; CHECK-NEXT: ret void +; CHECK-NEXT: } +define void @test15b(i8* %x, i64 %n) { +entry: + call i8* @objc_retain(i8* %x) nounwind + call i8* @objc_autorelease(i8* %x) nounwind + call void @objc_release(i8* %x) nounwind + ret void +} + +; Retain+release pairs in diamonds, all dominated by a retain. + +; CHECK: define void @test16( +; CHECK: @objc_retain(i8* %x) +; CHECK-NOT: @objc +; CHECK: } +define void @test16(i1 %a, i1 %b, i8* %x) { +entry: + call i8* @objc_retain(i8* %x) nounwind + br i1 %a, label %red, label %orange + +red: + call i8* @objc_retain(i8* %x) nounwind + br label %yellow + +orange: + call i8* @objc_retain(i8* %x) nounwind + br label %yellow + +yellow: + call void @use_pointer(i8* %x) + call void @use_pointer(i8* %x) + br i1 %b, label %green, label %blue + +green: + call void @objc_release(i8* %x) nounwind + br label %purple + +blue: + call void @objc_release(i8* %x) nounwind + br label %purple + +purple: + ret void +} + +; Retain+release pairs in diamonds, all post-dominated by a release. + +; CHECK: define void @test17( +; CHECK-NOT: @objc_ +; CHECK: purple: +; CHECK: @objc_release +; CHECK: } +define void @test17(i1 %a, i1 %b, i8* %x) { +entry: + br i1 %a, label %red, label %orange + +red: + call i8* @objc_retain(i8* %x) nounwind + br label %yellow + +orange: + call i8* @objc_retain(i8* %x) nounwind + br label %yellow + +yellow: + call void @use_pointer(i8* %x) + call void @use_pointer(i8* %x) + br i1 %b, label %green, label %blue + +green: + call void @objc_release(i8* %x) nounwind + br label %purple + +blue: + call void @objc_release(i8* %x) nounwind + br label %purple + +purple: + call void @objc_release(i8* %x) nounwind + ret void +} + +; Delete no-ops. + +; CHECK: define void @test18( +; CHECK-NOT: @objc_ +; CHECK: } +define void @test18() { + call i8* @objc_retain(i8* null) + call void @objc_release(i8* null) + call i8* @objc_autorelease(i8* null) + ret void +} + +; Delete no-ops where undef can be assumed to be null. + +; CHECK: define void @test18b +; CHECK-NOT: @objc_ +; CHECK: } +define void @test18b() { + call i8* @objc_retain(i8* undef) + call void @objc_release(i8* undef) + call i8* @objc_autorelease(i8* undef) + ret void +} + +; Replace uses of arguments with uses of return values, to reduce +; register pressure. + +; CHECK: define void @test19(i32* %y) { +; CHECK: %z = bitcast i32* %y to i8* +; CHECK: %0 = bitcast i32* %y to i8* +; CHECK: %1 = tail call i8* @objc_retain(i8* %0) +; CHECK: call void @use_pointer(i8* %z) +; CHECK: call void @use_pointer(i8* %z) +; CHECK: %2 = bitcast i32* %y to i8* +; CHECK: call void @objc_release(i8* %2) +; CHECK: ret void +; CHECK: } +define void @test19(i32* %y) { +entry: + %x = bitcast i32* %y to i8* + %0 = call i8* @objc_retain(i8* %x) nounwind + %z = bitcast i32* %y to i8* + call void @use_pointer(i8* %z) + call void @use_pointer(i8* %z) + call void @objc_release(i8* %x) + ret void +} + +; Bitcast insertion + +; CHECK: define void @test20( +; CHECK: %tmp1 = tail call i8* @objc_retain(i8* %tmp) nounwind +; CHECK-NEXT: invoke +define void @test20(double* %self) { +if.then12: + %tmp = bitcast double* %self to i8* + %tmp1 = call i8* @objc_retain(i8* %tmp) nounwind + invoke void @invokee() + to label %invoke.cont23 unwind label %lpad20 + +invoke.cont23: ; preds = %if.then12 + invoke void @invokee() + to label %if.end unwind label %lpad20 + +lpad20: ; preds = %invoke.cont23, %if.then12 + %tmp502 = phi double* [ undef, %invoke.cont23 ], [ %self, %if.then12 ] + unreachable + +if.end: ; preds = %invoke.cont23 + ret void +} + +; Delete a redundant retain,autorelease when forwaring a call result +; directly to a return value. + +; CHECK: define i8* @test21( +; CHECK: call i8* @returner() +; CHECK-NEXT: ret i8* %call +define i8* @test21() { +entry: + %call = call i8* @returner() + %0 = call i8* @objc_retain(i8* %call) nounwind + %1 = call i8* @objc_autorelease(i8* %0) nounwind + ret i8* %1 +} + +; Move an objc call up through a phi that has null operands. + +; CHECK: define void @test22( +; CHECK: B: +; CHECK: %1 = bitcast double* %p to i8* +; CHECK: call void @objc_release(i8* %1) +; CHECK: br label %C +; CHECK: C: ; preds = %B, %A +; CHECK-NOT: @objc_release +; CHECK: } +define void @test22(double* %p, i1 %a) { + br i1 %a, label %A, label %B +A: + br label %C +B: + br label %C +C: + %h = phi double* [ null, %A ], [ %p, %B ] + %c = bitcast double* %h to i8* + call void @objc_release(i8* %c) + ret void +} + +; Optimize objc_retainBlock. + +; CHECK: define void @test23( +; CHECK-NOT: @objc_ +; CHECK: } +%block0 = type { i64, i64, i8*, i8* } +%block1 = type { i8**, i32, i32, i32 (%struct.__block_literal_1*)*, %block0* } +%struct.__block_descriptor = type { i64, i64 } +%struct.__block_literal_1 = type { i8**, i32, i32, i8**, %struct.__block_descriptor* } +@__block_holder_tmp_1 = external constant %block1 +define void @test23() { +entry: + %0 = call i8* @objc_retainBlock(i8* bitcast (%block1* @__block_holder_tmp_1 to i8*)) nounwind + call void @bar(i32 ()* bitcast (%block1* @__block_holder_tmp_1 to i32 ()*)) + call void @bar(i32 ()* bitcast (%block1* @__block_holder_tmp_1 to i32 ()*)) + call void @objc_release(i8* bitcast (%block1* @__block_holder_tmp_1 to i8*)) nounwind + ret void +} + +; Don't optimize objc_retainBlock. + +; CHECK: define void @test23b +; CHECK: @objc_retainBlock +; CHECK: @objc_release +; CHECK: } +define void @test23b(i8* %p) { +entry: + %0 = call i8* @objc_retainBlock(i8* %p) nounwind + call void @use_pointer(i8* %p) + call void @use_pointer(i8* %p) + call void @objc_release(i8* %p) nounwind + ret void +} + +; Any call can decrement a retain count. + +; CHECK: define void @test24( +; CHECK: @objc_retain(i8* %a) +; CHECK: @objc_release +; CHECK: } +define void @test24(i8* %r, i8* %a) { + call i8* @objc_retain(i8* %a) + call void @use_pointer(i8* %r) + %q = load i8* %a + call void @objc_release(i8* %a) + ret void +} + +; Don't move a retain/release pair if the release can be moved +; but the retain can't be moved to balance it. + +; CHECK: define void @test25( +; CHECK: entry: +; CHECK: call i8* @objc_retain(i8* %p) +; CHECK: true: +; CHECK: done: +; CHECK: call void @objc_release(i8* %p) +; CHECK: } +define void @test25(i8* %p, i1 %x) { +entry: + %f0 = call i8* @objc_retain(i8* %p) + call void @callee() + br i1 %x, label %true, label %done + +true: + store i8 0, i8* %p + br label %done + +done: + call void @objc_release(i8* %p) + ret void +} + +; Don't move a retain/release pair if the retain can be moved +; but the release can't be moved to balance it. + +; CHECK: define void @test26( +; CHECK: entry: +; CHECK: call i8* @objc_retain(i8* %p) +; CHECK: true: +; CHECK: done: +; CHECK: call void @objc_release(i8* %p) +; CHECK: } +define void @test26(i8* %p, i1 %x) { +entry: + %f0 = call i8* @objc_retain(i8* %p) + br i1 %x, label %true, label %done + +true: + call void @callee() + br label %done + +done: + store i8 0, i8* %p + call void @objc_release(i8* %p) + ret void +} + +; Don't sink the retain,release into the loop. + +; CHECK: define void @test27( +; CHECK: entry: +; CHECK: call i8* @objc_retain(i8* %p) +; CHECK: loop: +; CHECK-NOT: @objc_ +; CHECK: done: +; CHECK: call void @objc_release +; CHECK: } +define void @test27(i8* %p, i1 %x, i1 %y) { +entry: + %f0 = call i8* @objc_retain(i8* %p) + br i1 %x, label %loop, label %done + +loop: + call void @callee() + store i8 0, i8* %p + br i1 %y, label %done, label %loop + +done: + call void @objc_release(i8* %p) + ret void +} + +; Trivial code motion case: Triangle. + +; CHECK: define void @test28( +; CHECK-NOT: @objc_ +; CHECK: true: +; CHECK: call i8* @objc_retain( +; CHECK: call void @callee() +; CHECK: store +; CHECK: call void @objc_release +; CHECK: done: +; CHECK-NOT: @objc_ +; CHECK: } +define void @test28(i8* %p, i1 %x) { +entry: + %f0 = call i8* @objc_retain(i8* %p) + br i1 %x, label %true, label %done + +true: + call void @callee() + store i8 0, i8* %p + br label %done + +done: + call void @objc_release(i8* %p), !clang.imprecise_release !0 + ret void +} + +; Trivial code motion case: Triangle, but no metadata. Don't move past +; unrelated memory references! + +; CHECK: define void @test28b +; CHECK: call i8* @objc_retain( +; CHECK: true: +; CHECK-NOT: @objc_ +; CHECK: call void @callee() +; CHECK-NOT: @objc_ +; CHECK: store +; CHECK-NOT: @objc_ +; CHECK: done: +; CHECK: @objc_release +; CHECK: } +define void @test28b(i8* %p, i1 %x, i8* noalias %t) { +entry: + %f0 = call i8* @objc_retain(i8* %p) + br i1 %x, label %true, label %done + +true: + call void @callee() + store i8 0, i8* %p + br label %done + +done: + store i8 0, i8* %t + call void @objc_release(i8* %p) + ret void +} + +; Trivial code motion case: Triangle, with metadata. Do move past +; unrelated memory references! And preserve the metadata. + +; CHECK: define void @test28c +; CHECK-NOT: @objc_ +; CHECK: true: +; CHECK: call i8* @objc_retain( +; CHECK: call void @callee() +; CHECK: store +; CHECK: call void @objc_release(i8* %p) nounwind, !clang.imprecise_release +; CHECK: done: +; CHECK-NOT: @objc_ +; CHECK: } +define void @test28c(i8* %p, i1 %x, i8* noalias %t) { +entry: + %f0 = call i8* @objc_retain(i8* %p) + br i1 %x, label %true, label %done + +true: + call void @callee() + store i8 0, i8* %p + br label %done + +done: + store i8 0, i8* %t + call void @objc_release(i8* %p), !clang.imprecise_release !0 + ret void +} + +; Like test28. but with two releases. + +; CHECK: define void @test29( +; CHECK-NOT: @objc_ +; CHECK: true: +; CHECK: call i8* @objc_retain( +; CHECK: call void @callee() +; CHECK: store +; CHECK: call void @objc_release +; CHECK-NOT: @objc_release +; CHECK: done: +; CHECK-NOT: @objc_ +; CHECK: ohno: +; CHECK-NOT: @objc_ +; CHECK: } +define void @test29(i8* %p, i1 %x, i1 %y) { +entry: + %f0 = call i8* @objc_retain(i8* %p) + br i1 %x, label %true, label %done + +true: + call void @callee() + store i8 0, i8* %p + br i1 %y, label %done, label %ohno + +done: + call void @objc_release(i8* %p) + ret void + +ohno: + call void @objc_release(i8* %p) + ret void +} + +; Basic case with the use and call in a diamond +; with an extra release. + +; CHECK: define void @test30( +; CHECK-NOT: @objc_ +; CHECK: true: +; CHECK: call i8* @objc_retain( +; CHECK: call void @callee() +; CHECK: store +; CHECK: call void @objc_release +; CHECK-NOT: @objc_release +; CHECK: false: +; CHECK-NOT: @objc_ +; CHECK: done: +; CHECK-NOT: @objc_ +; CHECK: ohno: +; CHECK-NOT: @objc_ +; CHECK: } +define void @test30(i8* %p, i1 %x, i1 %y, i1 %z) { +entry: + %f0 = call i8* @objc_retain(i8* %p) + br i1 %x, label %true, label %false + +true: + call void @callee() + store i8 0, i8* %p + br i1 %y, label %done, label %ohno + +false: + br i1 %z, label %done, label %ohno + +done: + call void @objc_release(i8* %p) + ret void + +ohno: + call void @objc_release(i8* %p) + ret void +} + +; Basic case with a mergeable release. + +; CHECK: define void @test31( +; CHECK: call i8* @objc_retain(i8* %p) +; CHECK: call void @callee() +; CHECK: store +; CHECK: call void @objc_release +; CHECK-NOT: @objc_release +; CHECK: true: +; CHECK-NOT: @objc_release +; CHECK: false: +; CHECK-NOT: @objc_release +; CHECK: ret void +; CHECK-NOT: @objc_release +; CHECK: } +define void @test31(i8* %p, i1 %x) { +entry: + %f0 = call i8* @objc_retain(i8* %p) + call void @callee() + store i8 0, i8* %p + br i1 %x, label %true, label %false +true: + call void @objc_release(i8* %p) + ret void +false: + call void @objc_release(i8* %p) + ret void +} + +; Don't consider bitcasts or getelementptrs direct uses. + +; CHECK: define void @test32( +; CHECK-NOT: @objc_ +; CHECK: true: +; CHECK: call i8* @objc_retain( +; CHECK: call void @callee() +; CHECK: store +; CHECK: call void @objc_release +; CHECK: done: +; CHECK-NOT: @objc_ +; CHECK: } +define void @test32(i8* %p, i1 %x) { +entry: + %f0 = call i8* @objc_retain(i8* %p) + br i1 %x, label %true, label %done + +true: + call void @callee() + store i8 0, i8* %p + br label %done + +done: + %g = bitcast i8* %p to i8* + %h = getelementptr i8* %g, i64 0 + call void @objc_release(i8* %g) + ret void +} + +; Do consider icmps to be direct uses. + +; CHECK: define void @test33( +; CHECK-NOT: @objc_ +; CHECK: true: +; CHECK: call i8* @objc_retain( +; CHECK: call void @callee() +; CHECK: icmp +; CHECK: call void @objc_release +; CHECK: done: +; CHECK-NOT: @objc_ +; CHECK: } +define void @test33(i8* %p, i1 %x, i8* %y) { +entry: + %f0 = call i8* @objc_retain(i8* %p) + br i1 %x, label %true, label %done + +true: + call void @callee() + %v = icmp eq i8* %p, %y + br label %done + +done: + %g = bitcast i8* %p to i8* + %h = getelementptr i8* %g, i64 0 + call void @objc_release(i8* %g) + ret void +} + +; Delete retain,release if there's just a possible dec. + +; CHECK: define void @test34( +; CHECK-NOT: @objc_ +; CHECK: } +define void @test34(i8* %p, i1 %x, i8* %y) { +entry: + %f0 = call i8* @objc_retain(i8* %p) + br i1 %x, label %true, label %done + +true: + call void @callee() + br label %done + +done: + %g = bitcast i8* %p to i8* + %h = getelementptr i8* %g, i64 0 + call void @objc_release(i8* %g) + ret void +} + +; Delete retain,release if there's just a use. + +; CHECK: define void @test35( +; CHECK-NOT: @objc_ +; CHECK: } +define void @test35(i8* %p, i1 %x, i8* %y) { +entry: + %f0 = call i8* @objc_retain(i8* %p) + br i1 %x, label %true, label %done + +true: + %v = icmp eq i8* %p, %y + br label %done + +done: + %g = bitcast i8* %p to i8* + %h = getelementptr i8* %g, i64 0 + call void @objc_release(i8* %g) + ret void +} + +; Delete a retain,release if there's no actual use. + +; CHECK: define void @test36( +; CHECK-NOT: @objc_ +; CHECK: call void @callee() +; CHECK-NOT: @objc_ +; CHECK: call void @callee() +; CHECK-NOT: @objc_ +; CHECK: } +define void @test36(i8* %p) { +entry: + call i8* @objc_retain(i8* %p) + call void @callee() + call void @callee() + call void @objc_release(i8* %p) + ret void +} + +; Like test36, but with metadata. + +; CHECK: define void @test37( +; CHECK-NOT: @objc_ +; CHECK: } +define void @test37(i8* %p) { +entry: + call i8* @objc_retain(i8* %p) + call void @callee() + call void @callee() + call void @objc_release(i8* %p), !clang.imprecise_release !0 + ret void +} + +; Be aggressive about analyzing phis to eliminate possible uses. + +; CHECK: define void @test38( +; CHECK-NOT: @objc_ +; CHECK: } +define void @test38(i8* %p, i1 %u, i1 %m, i8* %z, i8* %y, i8* %x, i8* %w) { +entry: + call i8* @objc_retain(i8* %p) + br i1 %u, label %true, label %false +true: + br i1 %m, label %a, label %b +false: + br i1 %m, label %c, label %d +a: + br label %e +b: + br label %e +c: + br label %f +d: + br label %f +e: + %j = phi i8* [ %z, %a ], [ %y, %b ] + br label %g +f: + %k = phi i8* [ %w, %c ], [ %x, %d ] + br label %g +g: + %h = phi i8* [ %j, %e ], [ %k, %f ] + call void @use_pointer(i8* %h) + call void @objc_release(i8* %p), !clang.imprecise_release !0 + ret void +} + +; Delete retain,release pairs around loops. + +; CHECK: define void @test39( +; CHECK_NOT: @objc_ +; CHECK: } +define void @test39(i8* %p) { +entry: + %0 = call i8* @objc_retain(i8* %p) + br label %loop + +loop: ; preds = %loop, %entry + br i1 undef, label %loop, label %exit + +exit: ; preds = %loop + call void @objc_release(i8* %0), !clang.imprecise_release !0 + ret void +} + +; Delete retain,release pairs around loops containing uses. + +; CHECK: define void @test39b( +; CHECK_NOT: @objc_ +; CHECK: } +define void @test39b(i8* %p) { +entry: + %0 = call i8* @objc_retain(i8* %p) + br label %loop + +loop: ; preds = %loop, %entry + store i8 0, i8* %0 + br i1 undef, label %loop, label %exit + +exit: ; preds = %loop + call void @objc_release(i8* %0), !clang.imprecise_release !0 + ret void +} + +; Delete retain,release pairs around loops containing potential decrements. + +; CHECK: define void @test39c( +; CHECK_NOT: @objc_ +; CHECK: } +define void @test39c(i8* %p) { +entry: + %0 = call i8* @objc_retain(i8* %p) + br label %loop + +loop: ; preds = %loop, %entry + call void @use_pointer(i8* %0) + br i1 undef, label %loop, label %exit + +exit: ; preds = %loop + call void @objc_release(i8* %0), !clang.imprecise_release !0 + ret void +} + +; Delete retain,release pairs around loops even if +; the successors are in a different order. + +; CHECK: define void @test40( +; CHECK_NOT: @objc_ +; CHECK: } +define void @test40(i8* %p) { +entry: + %0 = call i8* @objc_retain(i8* %p) + br label %loop + +loop: ; preds = %loop, %entry + call void @use_pointer(i8* %0) + br i1 undef, label %exit, label %loop + +exit: ; preds = %loop + call void @objc_release(i8* %0), !clang.imprecise_release !0 + ret void +} + +; Do the known-incremented retain+release elimination even if the pointer +; is also autoreleased. + +; CHECK: define void @test42( +; CHECK-NEXT: entry: +; CHECK-NEXT: call i8* @objc_retain(i8* %p) +; CHECK-NEXT: call i8* @objc_autorelease(i8* %p) +; CHECK-NEXT: call void @use_pointer(i8* %p) +; CHECK-NEXT: call void @use_pointer(i8* %p) +; CHECK-NEXT: ret void +; CHECK-NEXT: } +define void @test42(i8* %p) { +entry: + call i8* @objc_retain(i8* %p) + call i8* @objc_autorelease(i8* %p) + call i8* @objc_retain(i8* %p) + call void @use_pointer(i8* %p) + call void @use_pointer(i8* %p) + call void @objc_release(i8* %p) + ret void +} + +; Don't the known-incremented retain+release elimination if the pointer is +; autoreleased and there's an autoreleasePoolPop. + +; CHECK: define void @test43( +; CHECK-NEXT: entry: +; CHECK-NEXT: call i8* @objc_retain(i8* %p) +; CHECK-NEXT: call i8* @objc_autorelease(i8* %p) +; CHECK-NEXT: call i8* @objc_retain +; CHECK-NEXT: call void @use_pointer(i8* %p) +; CHECK-NEXT: call void @use_pointer(i8* %p) +; CHECK-NEXT: call void @objc_autoreleasePoolPop(i8* undef) +; CHECK-NEXT: call void @objc_release +; CHECK-NEXT: ret void +; CHECK-NEXT: } +define void @test43(i8* %p) { +entry: + call i8* @objc_retain(i8* %p) + call i8* @objc_autorelease(i8* %p) + call i8* @objc_retain(i8* %p) + call void @use_pointer(i8* %p) + call void @use_pointer(i8* %p) + call void @objc_autoreleasePoolPop(i8* undef) + call void @objc_release(i8* %p) + ret void +} + +; Do the known-incremented retain+release elimination if the pointer is +; autoreleased and there's an autoreleasePoolPush. + +; CHECK: define void @test43b +; CHECK-NEXT: entry: +; CHECK-NEXT: call i8* @objc_retain(i8* %p) +; CHECK-NEXT: call i8* @objc_autorelease(i8* %p) +; CHECK-NEXT: call void @use_pointer(i8* %p) +; CHECK-NEXT: call void @use_pointer(i8* %p) +; CHECK-NEXT: call void @objc_autoreleasePoolPush() +; CHECK-NEXT: ret void +; CHECK-NEXT: } +define void @test43b(i8* %p) { +entry: + call i8* @objc_retain(i8* %p) + call i8* @objc_autorelease(i8* %p) + call i8* @objc_retain(i8* %p) + call void @use_pointer(i8* %p) + call void @use_pointer(i8* %p) + call void @objc_autoreleasePoolPush() + call void @objc_release(i8* %p) + ret void +} + +; Do retain+release elimination for non-provenance pointers. + +; CHECK: define void @test44( +; CHECK-NOT: objc_ +; CHECK: } +define void @test44(i8** %pp) { + %p = load i8** %pp + %q = call i8* @objc_retain(i8* %p) + call void @objc_release(i8* %q) + ret void +} + +; Don't delete retain+release with an unknown-provenance +; may-alias objc_release between them. + +; CHECK: define void @test45( +; CHECK: call i8* @objc_retain(i8* %p) +; CHECK: call void @objc_release(i8* %q) +; CHECK: call void @use_pointer(i8* %p) +; CHECK: call void @objc_release(i8* %p) +define void @test45(i8** %pp, i8** %qq) { + %p = load i8** %pp + %q = load i8** %qq + call i8* @objc_retain(i8* %p) + call void @objc_release(i8* %q) + call void @use_pointer(i8* %p) + call void @objc_release(i8* %p) + ret void +} + +; Don't delete retain and autorelease here. + +; CHECK: define void @test46( +; CHECK: tail call i8* @objc_retain(i8* %p) nounwind +; CHECK: true: +; CHECK: tail call i8* @objc_autorelease(i8* %p) nounwind +define void @test46(i8* %p, i1 %a) { +entry: + call i8* @objc_retain(i8* %p) + br i1 %a, label %true, label %false + +true: + call i8* @objc_autorelease(i8* %p) + call void @use_pointer(i8* %p) + ret void + +false: + ret void +} + +; Delete no-op cast calls. + +; CHECK: define i8* @test47( +; CHECK-NOT: call +; CHECK: ret i8* %p +define i8* @test47(i8* %p) nounwind { + %x = call i8* @objc_retainedObject(i8* %p) + ret i8* %x +} + +; Delete no-op cast calls. + +; CHECK: define i8* @test48( +; CHECK-NOT: call +; CHECK: ret i8* %p +define i8* @test48(i8* %p) nounwind { + %x = call i8* @objc_unretainedObject(i8* %p) + ret i8* %x +} + +; Delete no-op cast calls. + +; CHECK: define i8* @test49( +; CHECK-NOT: call +; CHECK: ret i8* %p +define i8* @test49(i8* %p) nounwind { + %x = call i8* @objc_unretainedPointer(i8* %p) + ret i8* %x +} + +; Do delete retain+release with intervening stores of the +; address value; + +; CHECK: define void @test50( +; CHECK-NOT: @objc_ +; CHECK: } +define void @test50(i8* %p, i8** %pp) { + call i8* @objc_retain(i8* %p) + call void @callee() + store i8* %p, i8** %pp + call void @objc_release(i8* %p) + ret void +} + +; Don't delete retain+release with intervening stores through the +; address value. + +; CHECK: define void @test51( +; CHECK: call i8* @objc_retain(i8* %p) +; CHECK: call void @objc_release(i8* %p) +define void @test51(i8* %p) { + call i8* @objc_retain(i8* %p) + call void @callee() + store i8 0, i8* %p + call void @objc_release(i8* %p) + ret void +} + +; Don't delete retain+release with intervening use of a pointer of +; unknown provenance. + +; CHECK: define void @test52( +; CHECK: call i8* @objc_retain +; CHECK: call void @callee() +; CHECK: call void @use_pointer(i8* %z) +; CHECK: call void @objc_release +define void @test52(i8** %zz, i8** %pp) { + %p = load i8** %pp + %1 = call i8* @objc_retain(i8* %p) + call void @callee() + %z = load i8** %zz + call void @use_pointer(i8* %z) + call void @objc_release(i8* %p) + ret void +} + +; Like test52, but the pointer has function type, so it's assumed to +; be not reference counted. + +; CHECK: define void @test53( +; CHECK-NOT: @objc_ +; CHECK: } +define void @test53(void ()** %zz, i8** %pp) { + %p = load i8** %pp + %1 = call i8* @objc_retain(i8* %p) + call void @callee() + %z = load void ()** %zz + call void @callee_fnptr(void ()* %z) + call void @objc_release(i8* %p) + ret void +} + +; Convert autorelease to release if the value is unused. + +; CHECK: define void @test54( +; CHECK: call i8* @returner() +; CHECK-NEXT: call void @objc_release(i8* %t) nounwind, !clang.imprecise_release !0 +; CHECK-NEXT: ret void +define void @test54() { + %t = call i8* @returner() + call i8* @objc_autorelease(i8* %t) + ret void +} + +; Nested retain+release pairs. Delete them both. + +; CHECK: define void @test55( +; CHECK-NOT: @objc +; CHECK: } +define void @test55(i8* %x) { +entry: + %0 = call i8* @objc_retain(i8* %x) nounwind + %1 = call i8* @objc_retain(i8* %x) nounwind + call void @objc_release(i8* %x) nounwind + call void @objc_release(i8* %x) nounwind + ret void +} + +; Nested retain+release pairs where the inner pair depends +; on the outer pair to be removed, and then the outer pair +; can be partially eliminated. Plus an extra outer pair to +; eliminate, for fun. + +; CHECK: define void @test56( +; CHECK-NOT: @objc +; CHECK: if.then: +; CHECK-NEXT: %0 = tail call i8* @objc_retain(i8* %x) nounwind +; CHECK-NEXT: tail call void @use_pointer(i8* %x) +; CHECK-NEXT: tail call void @use_pointer(i8* %x) +; CHECK-NEXT: tail call void @objc_release(i8* %x) nounwind, !clang.imprecise_release !0 +; CHECK-NEXT: br label %if.end +; CHECK-NOT: @objc +; CHECK: } +define void @test56(i8* %x, i32 %n) { +entry: + %0 = tail call i8* @objc_retain(i8* %x) nounwind + %1 = tail call i8* @objc_retain(i8* %0) nounwind + %tobool = icmp eq i32 %n, 0 + br i1 %tobool, label %if.end, label %if.then + +if.then: ; preds = %entry + %2 = tail call i8* @objc_retain(i8* %1) nounwind + tail call void @use_pointer(i8* %2) + tail call void @use_pointer(i8* %2) + tail call void @objc_release(i8* %2) nounwind, !clang.imprecise_release !0 + br label %if.end + +if.end: ; preds = %entry, %if.then + tail call void @objc_release(i8* %1) nounwind, !clang.imprecise_release !0 + tail call void @objc_release(i8* %0) nounwind, !clang.imprecise_release !0 + ret void +} + +declare void @bar(i32 ()*) + +; A few real-world testcases. + +@.str4 = private unnamed_addr constant [33 x i8] c"-[A z] = { %f, %f, { %f, %f } }\0A\00" +@"OBJC_IVAR_$_A.myZ" = global i64 20, section "__DATA, __objc_const", align 8 +declare i32 @printf(i8* nocapture, ...) nounwind +declare i32 @puts(i8* nocapture) nounwind +@str = internal constant [16 x i8] c"-[ Top0 _getX ]\00" + +; CHECK: @"\01-[A z]" +; CHECK-NOT: @objc_ +; CHECK: } + +define {<2 x float>, <2 x float>} @"\01-[A z]"({}* %self, i8* nocapture %_cmd) nounwind { +invoke.cont: + %0 = bitcast {}* %self to i8* + %1 = tail call i8* @objc_retain(i8* %0) nounwind + tail call void @llvm.dbg.value(metadata !{{}* %self}, i64 0, metadata !0) + tail call void @llvm.dbg.value(metadata !{{}* %self}, i64 0, metadata !0) + %ivar = load i64* @"OBJC_IVAR_$_A.myZ", align 8 + %add.ptr = getelementptr i8* %0, i64 %ivar + %tmp1 = bitcast i8* %add.ptr to float* + %tmp2 = load float* %tmp1, align 4 + %conv = fpext float %tmp2 to double + %add.ptr.sum = add i64 %ivar, 4 + %tmp6 = getelementptr inbounds i8* %0, i64 %add.ptr.sum + %2 = bitcast i8* %tmp6 to float* + %tmp7 = load float* %2, align 4 + %conv8 = fpext float %tmp7 to double + %add.ptr.sum36 = add i64 %ivar, 8 + %tmp12 = getelementptr inbounds i8* %0, i64 %add.ptr.sum36 + %arrayidx = bitcast i8* %tmp12 to float* + %tmp13 = load float* %arrayidx, align 4 + %conv14 = fpext float %tmp13 to double + %tmp12.sum = add i64 %ivar, 12 + %arrayidx19 = getelementptr inbounds i8* %0, i64 %tmp12.sum + %3 = bitcast i8* %arrayidx19 to float* + %tmp20 = load float* %3, align 4 + %conv21 = fpext float %tmp20 to double + %call = tail call i32 (i8*, ...)* @printf(i8* getelementptr inbounds ([33 x i8]* @.str4, i64 0, i64 0), double %conv, double %conv8, double %conv14, double %conv21) + %ivar23 = load i64* @"OBJC_IVAR_$_A.myZ", align 8 + %add.ptr24 = getelementptr i8* %0, i64 %ivar23 + %4 = bitcast i8* %add.ptr24 to i128* + %srcval = load i128* %4, align 4 + tail call void @objc_release(i8* %0) nounwind + %tmp29 = trunc i128 %srcval to i64 + %tmp30 = bitcast i64 %tmp29 to <2 x float> + %tmp31 = insertvalue {<2 x float>, <2 x float>} undef, <2 x float> %tmp30, 0 + %tmp32 = lshr i128 %srcval, 64 + %tmp33 = trunc i128 %tmp32 to i64 + %tmp34 = bitcast i64 %tmp33 to <2 x float> + %tmp35 = insertvalue {<2 x float>, <2 x float>} %tmp31, <2 x float> %tmp34, 1 + ret {<2 x float>, <2 x float>} %tmp35 +} + +; CHECK: @"\01-[Top0 _getX]" +; CHECK-NOT: @objc_ +; CHECK: } + +define i32 @"\01-[Top0 _getX]"({}* %self, i8* nocapture %_cmd) nounwind { +invoke.cont: + %0 = bitcast {}* %self to i8* + %1 = tail call i8* @objc_retain(i8* %0) nounwind + %puts = tail call i32 @puts(i8* getelementptr inbounds ([16 x i8]* @str, i64 0, i64 0)) + tail call void @objc_release(i8* %0) nounwind + ret i32 0 +} + +@"\01L_OBJC_METH_VAR_NAME_" = internal global [5 x i8] c"frob\00", section "__TEXT,__cstring,cstring_literals", align 1@"\01L_OBJC_SELECTOR_REFERENCES_" = internal global i8* getelementptr inbounds ([5 x i8]* @"\01L_OBJC_METH_VAR_NAME_", i64 0, i64 0), section "__DATA, __objc_selrefs, literal_pointers, no_dead_strip" +@"\01L_OBJC_IMAGE_INFO" = internal constant [2 x i32] [i32 0, i32 16], section "__DATA, __objc_imageinfo, regular, no_dead_strip" +@llvm.used = appending global [3 x i8*] [i8* getelementptr inbounds ([5 x i8]* @"\01L_OBJC_METH_VAR_NAME_", i32 0, i32 0), i8* bitcast (i8** @"\01L_OBJC_SELECTOR_REFERENCES_" to i8*), i8* bitcast ([2 x i32]* @"\01L_OBJC_IMAGE_INFO" to i8*)], section "llvm.metadata" + +; A simple loop. Eliminate the retain and release inside of it! + +; CHECK: define void @loop +; CHECK: for.body: +; CHECK-NOT: @objc_ +; CHECK: @objc_msgSend +; CHECK-NOT: @objc_ +; CHECK: for.end: +define void @loop(i8* %x, i64 %n) { +entry: + %0 = tail call i8* @objc_retain(i8* %x) nounwind + %cmp9 = icmp sgt i64 %n, 0 + br i1 %cmp9, label %for.body, label %for.end + +for.body: ; preds = %entry, %for.body + %i.010 = phi i64 [ %inc, %for.body ], [ 0, %entry ] + %1 = tail call i8* @objc_retain(i8* %x) nounwind + %tmp5 = load i8** @"\01L_OBJC_SELECTOR_REFERENCES_", align 8 + %call = tail call i8* (i8*, i8*, ...)* @objc_msgSend(i8* %1, i8* %tmp5) + tail call void @objc_release(i8* %1) nounwind, !clang.imprecise_release !0 + %inc = add nsw i64 %i.010, 1 + %exitcond = icmp eq i64 %inc, %n + br i1 %exitcond, label %for.end, label %for.body + +for.end: ; preds = %for.body, %entry + tail call void @objc_release(i8* %x) nounwind, !clang.imprecise_release !0 + ret void +} + +; ObjCARCOpt can delete the retain,release on self. + +; CHECK: define void @TextEditTest +; CHECK-NOT: call i8* @objc_retain(i8* %tmp7) +; CHECK: } + +%0 = type { i8* (i8*, %struct._message_ref_t*, ...)*, i8* } +%1 = type opaque +%2 = type opaque +%3 = type opaque +%4 = type opaque +%5 = type opaque +%struct.NSConstantString = type { i32*, i32, i8*, i64 } +%struct._NSRange = type { i64, i64 } +%struct.__CFString = type opaque +%struct.__method_list_t = type { i32, i32, [0 x %struct._objc_method] } +%struct._class_ro_t = type { i32, i32, i32, i8*, i8*, %struct.__method_list_t*, %struct._objc_protocol_list*, %struct._ivar_list_t*, i8*, %struct._prop_list_t* } +%struct._class_t = type { %struct._class_t*, %struct._class_t*, %struct._objc_cache*, i8* (i8*, i8*)**, %struct._class_ro_t* } +%struct._ivar_list_t = type { i32, i32, [0 x %struct._ivar_t] } +%struct._ivar_t = type { i64*, i8*, i8*, i32, i32 } +%struct._message_ref_t = type { i8*, i8* } +%struct._objc_cache = type opaque +%struct._objc_method = type { i8*, i8*, i8* } +%struct._objc_protocol_list = type { i64, [0 x %struct._protocol_t*] } +%struct._prop_list_t = type { i32, i32, [0 x %struct._message_ref_t] } +%struct._protocol_t = type { i8*, i8*, %struct._objc_protocol_list*, %struct.__method_list_t*, %struct.__method_list_t*, %struct.__method_list_t*, %struct.__method_list_t*, %struct._prop_list_t*, i32, i32 } + +@"\01L_OBJC_CLASSLIST_REFERENCES_$_17" = external hidden global %struct._class_t*, section "__DATA, __objc_classrefs, regular, no_dead_strip", align 8 +@kUTTypePlainText = external constant %struct.__CFString* +@"\01L_OBJC_SELECTOR_REFERENCES_19" = external hidden global i8*, section "__DATA, __objc_selrefs, literal_pointers, no_dead_strip" +@"\01L_OBJC_SELECTOR_REFERENCES_21" = external hidden global i8*, section "__DATA, __objc_selrefs, literal_pointers, no_dead_strip" +@"\01L_OBJC_SELECTOR_REFERENCES_23" = external hidden global i8*, section "__DATA, __objc_selrefs, literal_pointers, no_dead_strip" +@"\01L_OBJC_SELECTOR_REFERENCES_25" = external hidden global i8*, section "__DATA, __objc_selrefs, literal_pointers, no_dead_strip" +@"\01L_OBJC_CLASSLIST_REFERENCES_$_26" = external hidden global %struct._class_t*, section "__DATA, __objc_classrefs, regular, no_dead_strip", align 8 +@"\01L_OBJC_SELECTOR_REFERENCES_28" = external hidden global i8*, section "__DATA, __objc_selrefs, literal_pointers, no_dead_strip" +@"\01L_OBJC_CLASSLIST_REFERENCES_$_29" = external hidden global %struct._class_t*, section "__DATA, __objc_classrefs, regular, no_dead_strip", align 8 +@"\01L_OBJC_SELECTOR_REFERENCES_31" = external hidden global i8*, section "__DATA, __objc_selrefs, literal_pointers, no_dead_strip" +@"\01L_OBJC_SELECTOR_REFERENCES_33" = external hidden global i8*, section "__DATA, __objc_selrefs, literal_pointers, no_dead_strip" +@"\01L_OBJC_SELECTOR_REFERENCES_35" = external hidden global i8*, section "__DATA, __objc_selrefs, literal_pointers, no_dead_strip" +@"\01L_OBJC_SELECTOR_REFERENCES_37" = external hidden global i8*, section "__DATA, __objc_selrefs, literal_pointers, no_dead_strip" +@"\01L_OBJC_CLASSLIST_REFERENCES_$_38" = external hidden global %struct._class_t*, section "__DATA, __objc_classrefs, regular, no_dead_strip", align 8 +@"\01L_OBJC_SELECTOR_REFERENCES_40" = external hidden global i8*, section "__DATA, __objc_selrefs, literal_pointers, no_dead_strip" +@"\01L_OBJC_SELECTOR_REFERENCES_42" = external hidden global i8*, section "__DATA, __objc_selrefs, literal_pointers, no_dead_strip" +@_unnamed_cfstring_44 = external hidden constant %struct.NSConstantString, section "__DATA,__cfstring" +@"\01L_OBJC_SELECTOR_REFERENCES_46" = external hidden global i8*, section "__DATA, __objc_selrefs, literal_pointers, no_dead_strip" +@"\01L_OBJC_SELECTOR_REFERENCES_48" = external hidden global i8*, section "__DATA, __objc_selrefs, literal_pointers, no_dead_strip" +@"\01l_objc_msgSend_fixup_isEqual_" = external hidden global %0, section "__DATA, __objc_msgrefs, coalesced", align 16 +@"\01L_OBJC_CLASSLIST_REFERENCES_$_50" = external hidden global %struct._class_t*, section "__DATA, __objc_classrefs, regular, no_dead_strip", align 8 +@NSCocoaErrorDomain = external constant %1* +@"\01L_OBJC_CLASSLIST_REFERENCES_$_51" = external hidden global %struct._class_t*, section "__DATA, __objc_classrefs, regular, no_dead_strip", align 8 +@NSFilePathErrorKey = external constant %1* +@"\01L_OBJC_SELECTOR_REFERENCES_53" = external hidden global i8*, section "__DATA, __objc_selrefs, literal_pointers, no_dead_strip" +@"\01L_OBJC_SELECTOR_REFERENCES_55" = external hidden global i8*, section "__DATA, __objc_selrefs, literal_pointers, no_dead_strip" +@"\01L_OBJC_CLASSLIST_REFERENCES_$_56" = external hidden global %struct._class_t*, section "__DATA, __objc_classrefs, regular, no_dead_strip", align 8 +@"\01L_OBJC_SELECTOR_REFERENCES_58" = external hidden global i8*, section "__DATA, __objc_selrefs, literal_pointers, no_dead_strip" +@"\01L_OBJC_SELECTOR_REFERENCES_60" = external hidden global i8*, section "__DATA, __objc_selrefs, literal_pointers, no_dead_strip" + +declare %1* @truncatedString(%1*, i64) +define void @TextEditTest(%2* %self, %3* %pboard) { +entry: + %err = alloca %4*, align 8 + %tmp7 = bitcast %2* %self to i8* + %tmp8 = call i8* @objc_retain(i8* %tmp7) nounwind + store %4* null, %4** %err, align 8 + %tmp1 = load %struct._class_t** @"\01L_OBJC_CLASSLIST_REFERENCES_$_17", align 8 + %tmp2 = load %struct.__CFString** @kUTTypePlainText, align 8 + %tmp3 = load i8** @"\01L_OBJC_SELECTOR_REFERENCES_19", align 8 + %tmp4 = bitcast %struct._class_t* %tmp1 to i8* + %call5 = call i8* (i8*, i8*, ...)* @objc_msgSend(i8* %tmp4, i8* %tmp3, %struct.__CFString* %tmp2) + %tmp5 = load i8** @"\01L_OBJC_SELECTOR_REFERENCES_21", align 8 + %tmp6 = bitcast %3* %pboard to i8* + %call76 = call i8* (i8*, i8*, ...)* @objc_msgSend(i8* %tmp6, i8* %tmp5, i8* %call5) + %tmp9 = call i8* @objc_retain(i8* %call76) nounwind + %tobool = icmp eq i8* %tmp9, null + br i1 %tobool, label %end, label %land.lhs.true + +land.lhs.true: ; preds = %entry + %tmp11 = load i8** @"\01L_OBJC_SELECTOR_REFERENCES_23", align 8 + %call137 = call i8* (i8*, i8*, ...)* @objc_msgSend(i8* %tmp6, i8* %tmp11, i8* %tmp9) + %tmp = bitcast i8* %call137 to %1* + %tmp10 = call i8* @objc_retain(i8* %call137) nounwind + call void @objc_release(i8* null) nounwind + %tmp12 = call i8* @objc_retain(i8* %call137) nounwind + call void @objc_release(i8* null) nounwind + %tobool16 = icmp eq i8* %call137, null + br i1 %tobool16, label %end, label %if.then + +if.then: ; preds = %land.lhs.true + %tmp19 = load i8** @"\01L_OBJC_SELECTOR_REFERENCES_25", align 8 + %call21 = call signext i8 bitcast (i8* (i8*, i8*, ...)* @objc_msgSend to i8 (i8*, i8*)*)(i8* %call137, i8* %tmp19) + %tobool22 = icmp eq i8 %call21, 0 + br i1 %tobool22, label %if.then44, label %land.lhs.true23 + +land.lhs.true23: ; preds = %if.then + %tmp24 = load %struct._class_t** @"\01L_OBJC_CLASSLIST_REFERENCES_$_26", align 8 + %tmp26 = load i8** @"\01L_OBJC_SELECTOR_REFERENCES_28", align 8 + %tmp27 = bitcast %struct._class_t* %tmp24 to i8* + %call2822 = call i8* (i8*, i8*, ...)* @objc_msgSend(i8* %tmp27, i8* %tmp26, i8* %call137) + %tmp13 = bitcast i8* %call2822 to %5* + %tmp14 = call i8* @objc_retain(i8* %call2822) nounwind + call void @objc_release(i8* null) nounwind + %tobool30 = icmp eq i8* %call2822, null + br i1 %tobool30, label %if.then44, label %if.end + +if.end: ; preds = %land.lhs.true23 + %tmp32 = load %struct._class_t** @"\01L_OBJC_CLASSLIST_REFERENCES_$_29", align 8 + %tmp33 = load i8** @"\01L_OBJC_SELECTOR_REFERENCES_31", align 8 + %tmp34 = bitcast %struct._class_t* %tmp32 to i8* + %call35 = call i8* (i8*, i8*, ...)* @objc_msgSend(i8* %tmp34, i8* %tmp33) + %tmp37 = load i8** @"\01L_OBJC_SELECTOR_REFERENCES_33", align 8 + %call3923 = call i8* (i8*, i8*, ...)* @objc_msgSend(i8* %call35, i8* %tmp37, i8* %call2822, i32 signext 1, %4** %err) + %cmp = icmp eq i8* %call3923, null + br i1 %cmp, label %if.then44, label %end + +if.then44: ; preds = %if.end, %land.lhs.true23, %if.then + %url.025 = phi %5* [ %tmp13, %if.end ], [ %tmp13, %land.lhs.true23 ], [ null, %if.then ] + %tmp49 = load i8** @"\01L_OBJC_SELECTOR_REFERENCES_35", align 8 + %call51 = call %struct._NSRange bitcast (i8* (i8*, i8*, ...)* @objc_msgSend to %struct._NSRange (i8*, i8*, i64, i64)*)(i8* %call137, i8* %tmp49, i64 0, i64 0) + %call513 = extractvalue %struct._NSRange %call51, 0 + %call514 = extractvalue %struct._NSRange %call51, 1 + %tmp52 = load i8** @"\01L_OBJC_SELECTOR_REFERENCES_37", align 8 + %call548 = call i8* (i8*, i8*, ...)* @objc_msgSend(i8* %call137, i8* %tmp52, i64 %call513, i64 %call514) + %tmp55 = load %struct._class_t** @"\01L_OBJC_CLASSLIST_REFERENCES_$_38", align 8 + %tmp56 = load i8** @"\01L_OBJC_SELECTOR_REFERENCES_40", align 8 + %tmp57 = bitcast %struct._class_t* %tmp55 to i8* + %call58 = call i8* (i8*, i8*, ...)* @objc_msgSend(i8* %tmp57, i8* %tmp56) + %tmp59 = load i8** @"\01L_OBJC_SELECTOR_REFERENCES_42", align 8 + %call6110 = call i8* (i8*, i8*, ...)* @objc_msgSend(i8* %call548, i8* %tmp59, i8* %call58) + %tmp15 = call i8* @objc_retain(i8* %call6110) nounwind + call void @objc_release(i8* %call137) nounwind + %tmp64 = load i8** @"\01L_OBJC_SELECTOR_REFERENCES_46", align 8 + %call66 = call signext i8 bitcast (i8* (i8*, i8*, ...)* @objc_msgSend to i8 (i8*, i8*, %1*)*)(i8* %call6110, i8* %tmp64, %1* bitcast (%struct.NSConstantString* @_unnamed_cfstring_44 to %1*)) + %tobool67 = icmp eq i8 %call66, 0 + br i1 %tobool67, label %if.end74, label %if.then68 + +if.then68: ; preds = %if.then44 + %tmp70 = load i8** @"\01L_OBJC_SELECTOR_REFERENCES_48", align 8 + %call7220 = call i8* (i8*, i8*, ...)* @objc_msgSend(i8* %call6110, i8* %tmp70) + %tmp16 = call i8* @objc_retain(i8* %call7220) nounwind + call void @objc_release(i8* %call6110) nounwind + br label %if.end74 + +if.end74: ; preds = %if.then68, %if.then44 + %filename.0.in = phi i8* [ %call7220, %if.then68 ], [ %call6110, %if.then44 ] + %filename.0 = bitcast i8* %filename.0.in to %1* + %tmp17 = load i8** bitcast (%0* @"\01l_objc_msgSend_fixup_isEqual_" to i8**), align 16 + %tmp18 = bitcast i8* %tmp17 to i8 (i8*, %struct._message_ref_t*, i8*, ...)* + %call78 = call signext i8 (i8*, %struct._message_ref_t*, i8*, ...)* %tmp18(i8* %call137, %struct._message_ref_t* bitcast (%0* @"\01l_objc_msgSend_fixup_isEqual_" to %struct._message_ref_t*), i8* %filename.0.in) + %tobool79 = icmp eq i8 %call78, 0 + br i1 %tobool79, label %land.lhs.true80, label %if.then109 + +land.lhs.true80: ; preds = %if.end74 + %tmp82 = load i8** @"\01L_OBJC_SELECTOR_REFERENCES_25", align 8 + %call84 = call signext i8 bitcast (i8* (i8*, i8*, ...)* @objc_msgSend to i8 (i8*, i8*)*)(i8* %filename.0.in, i8* %tmp82) + %tobool86 = icmp eq i8 %call84, 0 + br i1 %tobool86, label %if.then109, label %if.end106 + +if.end106: ; preds = %land.lhs.true80 + %tmp88 = load %struct._class_t** @"\01L_OBJC_CLASSLIST_REFERENCES_$_26", align 8 + %tmp90 = load i8** @"\01L_OBJC_SELECTOR_REFERENCES_28", align 8 + %tmp91 = bitcast %struct._class_t* %tmp88 to i8* + %call9218 = call i8* (i8*, i8*, ...)* @objc_msgSend(i8* %tmp91, i8* %tmp90, i8* %filename.0.in) + %tmp20 = bitcast i8* %call9218 to %5* + %tmp21 = call i8* @objc_retain(i8* %call9218) nounwind + %tmp22 = bitcast %5* %url.025 to i8* + call void @objc_release(i8* %tmp22) nounwind + %tmp94 = load %struct._class_t** @"\01L_OBJC_CLASSLIST_REFERENCES_$_29", align 8 + %tmp95 = load i8** @"\01L_OBJC_SELECTOR_REFERENCES_31", align 8 + %tmp96 = bitcast %struct._class_t* %tmp94 to i8* + %call97 = call i8* (i8*, i8*, ...)* @objc_msgSend(i8* %tmp96, i8* %tmp95) + %tmp99 = load i8** @"\01L_OBJC_SELECTOR_REFERENCES_33", align 8 + %call10119 = call i8* (i8*, i8*, ...)* @objc_msgSend(i8* %call97, i8* %tmp99, i8* %call9218, i32 signext 1, %4** %err) + %phitmp = icmp eq i8* %call10119, null + br i1 %phitmp, label %if.then109, label %end + +if.then109: ; preds = %if.end106, %land.lhs.true80, %if.end74 + %url.129 = phi %5* [ %tmp20, %if.end106 ], [ %url.025, %if.end74 ], [ %url.025, %land.lhs.true80 ] + %tmp110 = load %4** %err, align 8 + %tobool111 = icmp eq %4* %tmp110, null + br i1 %tobool111, label %if.then112, label %if.end125 + +if.then112: ; preds = %if.then109 + %tmp113 = load %struct._class_t** @"\01L_OBJC_CLASSLIST_REFERENCES_$_50", align 8 + %tmp114 = load %1** @NSCocoaErrorDomain, align 8 + %tmp115 = load %struct._class_t** @"\01L_OBJC_CLASSLIST_REFERENCES_$_51", align 8 + %call117 = call %1* @truncatedString(%1* %filename.0, i64 1034) + %tmp118 = load %1** @NSFilePathErrorKey, align 8 + %tmp119 = load i8** @"\01L_OBJC_SELECTOR_REFERENCES_53", align 8 + %tmp120 = bitcast %struct._class_t* %tmp115 to i8* + %call12113 = call i8* (i8*, i8*, ...)* @objc_msgSend(i8* %tmp120, i8* %tmp119, %1* %call117, %1* %tmp118, i8* null) + %tmp122 = load i8** @"\01L_OBJC_SELECTOR_REFERENCES_55", align 8 + %tmp123 = bitcast %struct._class_t* %tmp113 to i8* + %call12414 = call i8* (i8*, i8*, ...)* @objc_msgSend(i8* %tmp123, i8* %tmp122, %1* %tmp114, i64 258, i8* %call12113) + %tmp23 = call i8* @objc_retain(i8* %call12414) nounwind + %tmp25 = call i8* @objc_autorelease(i8* %tmp23) nounwind + %tmp28 = bitcast i8* %tmp25 to %4* + store %4* %tmp28, %4** %err, align 8 + br label %if.end125 + +if.end125: ; preds = %if.then112, %if.then109 + %tmp127 = phi %4* [ %tmp110, %if.then109 ], [ %tmp28, %if.then112 ] + %tmp126 = load %struct._class_t** @"\01L_OBJC_CLASSLIST_REFERENCES_$_56", align 8 + %tmp128 = load i8** @"\01L_OBJC_SELECTOR_REFERENCES_58", align 8 + %tmp129 = bitcast %struct._class_t* %tmp126 to i8* + %call13015 = call i8* (i8*, i8*, ...)* @objc_msgSend(i8* %tmp129, i8* %tmp128, %4* %tmp127) + %tmp131 = load i8** @"\01L_OBJC_SELECTOR_REFERENCES_60", align 8 + %call13317 = call i8* (i8*, i8*, ...)* @objc_msgSend(i8* %call13015, i8* %tmp131) + br label %end + +end: ; preds = %if.end125, %if.end106, %if.end, %land.lhs.true, %entry + %filename.2 = phi %1* [ %filename.0, %if.end106 ], [ %filename.0, %if.end125 ], [ %tmp, %land.lhs.true ], [ null, %entry ], [ %tmp, %if.end ] + %origFilename.0 = phi %1* [ %tmp, %if.end106 ], [ %tmp, %if.end125 ], [ %tmp, %land.lhs.true ], [ null, %entry ], [ %tmp, %if.end ] + %url.2 = phi %5* [ %tmp20, %if.end106 ], [ %url.129, %if.end125 ], [ null, %land.lhs.true ], [ null, %entry ], [ %tmp13, %if.end ] + call void @objc_release(i8* %tmp9) nounwind, !clang.imprecise_release !0 + %tmp29 = bitcast %5* %url.2 to i8* + call void @objc_release(i8* %tmp29) nounwind, !clang.imprecise_release !0 + %tmp30 = bitcast %1* %origFilename.0 to i8* + call void @objc_release(i8* %tmp30) nounwind, !clang.imprecise_release !0 + %tmp31 = bitcast %1* %filename.2 to i8* + call void @objc_release(i8* %tmp31) nounwind, !clang.imprecise_release !0 + call void @objc_release(i8* %tmp7) nounwind, !clang.imprecise_release !0 + ret void +} + +!0 = metadata !{} diff --git a/test/Transforms/ObjCARC/cfg-hazards.ll b/test/Transforms/ObjCARC/cfg-hazards.ll new file mode 100644 index 00000000000..e3624df57be --- /dev/null +++ b/test/Transforms/ObjCARC/cfg-hazards.ll @@ -0,0 +1,86 @@ +; RUN: opt -S -objc-arc < %s | FileCheck %s +; rdar://9503416 + +; Detect loop boundaries and don't move retains and releases +; across them. + +declare void @use_pointer(i8*) +declare i8* @objc_retain(i8*) +declare void @objc_release(i8*) + +; CHECK: define void @test0( +; CHECK: call i8* @objc_retain( +; CHECK: for.body: +; CHECK-NOT: @objc +; CHECK: for.end: +; CHECK: call void @objc_release( +; CHECK: } +define void @test0(i8* %digits) { +entry: + %tmp1 = call i8* @objc_retain(i8* %digits) nounwind + call void @use_pointer(i8* %tmp1) + br label %for.body + +for.body: ; preds = %for.body, %entry + %upcDigitIndex.01 = phi i64 [ 2, %entry ], [ %inc, %for.body ] + call void @use_pointer(i8* %tmp1) + %inc = add i64 %upcDigitIndex.01, 1 + %cmp = icmp ult i64 %inc, 12 + br i1 %cmp, label %for.body, label %for.end + +for.end: ; preds = %for.body + call void @objc_release(i8* %tmp1) nounwind, !clang.imprecise_release !0 + ret void +} + +; CHECK: define void @test1( +; CHECK: call i8* @objc_retain( +; CHECK: for.body: +; CHECK-NOT: @objc +; CHECK: for.end: +; CHECK: void @objc_release( +; CHECK: } +define void @test1(i8* %digits) { +entry: + %tmp1 = call i8* @objc_retain(i8* %digits) nounwind + br label %for.body + +for.body: ; preds = %for.body, %entry + %upcDigitIndex.01 = phi i64 [ 2, %entry ], [ %inc, %for.body ] + call void @use_pointer(i8* %tmp1) + call void @use_pointer(i8* %tmp1) + %inc = add i64 %upcDigitIndex.01, 1 + %cmp = icmp ult i64 %inc, 12 + br i1 %cmp, label %for.body, label %for.end + +for.end: ; preds = %for.body + call void @objc_release(i8* %tmp1) nounwind, !clang.imprecise_release !0 + ret void +} + +; CHECK: define void @test2( +; CHECK: call i8* @objc_retain( +; CHECK: for.body: +; CHECK-NOT: @objc +; CHECK: for.end: +; CHECK: void @objc_release( +; CHECK: } +define void @test2(i8* %digits) { +entry: + %tmp1 = call i8* @objc_retain(i8* %digits) nounwind + br label %for.body + +for.body: ; preds = %for.body, %entry + %upcDigitIndex.01 = phi i64 [ 2, %entry ], [ %inc, %for.body ] + call void @use_pointer(i8* %tmp1) + %inc = add i64 %upcDigitIndex.01, 1 + %cmp = icmp ult i64 %inc, 12 + br i1 %cmp, label %for.body, label %for.end + +for.end: ; preds = %for.body + call void @use_pointer(i8* %tmp1) + call void @objc_release(i8* %tmp1) nounwind, !clang.imprecise_release !0 + ret void +} + +!0 = metadata !{} diff --git a/test/Transforms/ObjCARC/contract-marker.ll b/test/Transforms/ObjCARC/contract-marker.ll new file mode 100644 index 00000000000..01d978a0e21 --- /dev/null +++ b/test/Transforms/ObjCARC/contract-marker.ll @@ -0,0 +1,23 @@ +; RUN: opt -S -objc-arc-contract < %s | FileCheck %s + +; CHECK: %call = tail call i32* @qux() +; CHECK-NEXT: %tcall = bitcast i32* %call to i8* +; CHECK-NEXT: call void asm sideeffect "mov\09r7, r7\09\09@ marker for objc_retainAutoreleaseReturnValue", ""() +; CHECK-NEXT: %0 = tail call i8* @objc_retainAutoreleasedReturnValue(i8* %tcall) nounwind + +define void @foo() { +entry: + %call = tail call i32* @qux() + %tcall = bitcast i32* %call to i8* + %0 = tail call i8* @objc_retainAutoreleasedReturnValue(i8* %tcall) nounwind + tail call void @bar(i8* %0) + ret void +} + +declare i32* @qux() +declare i8* @objc_retainAutoreleasedReturnValue(i8*) +declare void @bar(i8*) + +!clang.arc.retainAutoreleasedReturnValueMarker = !{!0} + +!0 = metadata !{metadata !"mov\09r7, r7\09\09@ marker for objc_retainAutoreleaseReturnValue"} diff --git a/test/Transforms/ObjCARC/contract-storestrong-ivar.ll b/test/Transforms/ObjCARC/contract-storestrong-ivar.ll new file mode 100644 index 00000000000..4ad78e75347 --- /dev/null +++ b/test/Transforms/ObjCARC/contract-storestrong-ivar.ll @@ -0,0 +1,31 @@ +; RUN: opt -objc-arc-contract -S < %s | FileCheck %s + +; CHECK: call void @objc_storeStrong(i8** + +target datalayout = "e-p:64:64:64-i1:8:8-i8:8:8-i16:16:16-i32:32:32-i64:64:64-f32:32:32-f64:64:64-v64:64:64-v128:128:128-a0:0:64-s0:64:64-f80:128:128-n8:16:32:64" +target triple = "x86_64-apple-darwin11.0.0" + +%0 = type opaque +%1 = type opaque + +@"OBJC_IVAR_$_Controller.preferencesController" = external global i64, section "__DATA, __objc_const", align 8 + +declare i8* @objc_retain(i8*) + +declare void @objc_release(i8*) + +define hidden void @y(%0* nocapture %self, %1* %preferencesController) nounwind { +entry: + %ivar = load i64* @"OBJC_IVAR_$_Controller.preferencesController", align 8 + %tmp = bitcast %0* %self to i8* + %add.ptr = getelementptr inbounds i8* %tmp, i64 %ivar + %tmp1 = bitcast i8* %add.ptr to %1** + %tmp2 = load %1** %tmp1, align 8 + %tmp3 = bitcast %1* %preferencesController to i8* + %tmp4 = tail call i8* @objc_retain(i8* %tmp3) nounwind + %tmp5 = bitcast %1* %tmp2 to i8* + tail call void @objc_release(i8* %tmp5) nounwind + %tmp6 = bitcast i8* %tmp4 to %1* + store %1* %tmp6, %1** %tmp1, align 8 + ret void +} diff --git a/test/Transforms/ObjCARC/contract-storestrong.ll b/test/Transforms/ObjCARC/contract-storestrong.ll new file mode 100644 index 00000000000..50ed260eb08 --- /dev/null +++ b/test/Transforms/ObjCARC/contract-storestrong.ll @@ -0,0 +1,59 @@ +; RUN: opt -objc-arc-contract -S < %s | FileCheck %s + +target datalayout = "e-p:64:64:64" + +declare i8* @objc_retain(i8*) +declare void @objc_release(i8*) + +@x = external global i8* + +; CHECK: define void @test0( +; CHECK: entry: +; CHECK-NEXT: call void @objc_storeStrong(i8** @x, i8* %p) nounwind +; CHECK-NEXT: ret void +define void @test0(i8* %p) { +entry: + %0 = tail call i8* @objc_retain(i8* %p) nounwind + %tmp = load i8** @x, align 8 + store i8* %0, i8** @x, align 8 + tail call void @objc_release(i8* %tmp) nounwind + ret void +} + +; Don't do this if the load is volatile. + +; CHECK: define void @test1(i8* %p) { +; CHECK-NEXT: entry: +; CHECK-NEXT: %0 = tail call i8* @objc_retain(i8* %p) nounwind +; CHECK-NEXT: %tmp = volatile load i8** @x, align 8 +; CHECK-NEXT: store i8* %0, i8** @x, align 8 +; CHECK-NEXT: tail call void @objc_release(i8* %tmp) nounwind +; CHECK-NEXT: ret void +; CHECK-NEXT: } +define void @test1(i8* %p) { +entry: + %0 = tail call i8* @objc_retain(i8* %p) nounwind + %tmp = volatile load i8** @x, align 8 + store i8* %0, i8** @x, align 8 + tail call void @objc_release(i8* %tmp) nounwind + ret void +} + +; Don't do this if the store is volatile. + +; CHECK: define void @test2(i8* %p) { +; CHECK-NEXT: entry: +; CHECK-NEXT: %0 = tail call i8* @objc_retain(i8* %p) nounwind +; CHECK-NEXT: %tmp = load i8** @x, align 8 +; CHECK-NEXT: volatile store i8* %0, i8** @x, align 8 +; CHECK-NEXT: tail call void @objc_release(i8* %tmp) nounwind +; CHECK-NEXT: ret void +; CHECK-NEXT: } +define void @test2(i8* %p) { +entry: + %0 = tail call i8* @objc_retain(i8* %p) nounwind + %tmp = load i8** @x, align 8 + volatile store i8* %0, i8** @x, align 8 + tail call void @objc_release(i8* %tmp) nounwind + ret void +} diff --git a/test/Transforms/ObjCARC/contract-testcases.ll b/test/Transforms/ObjCARC/contract-testcases.ll new file mode 100644 index 00000000000..69fa8376757 --- /dev/null +++ b/test/Transforms/ObjCARC/contract-testcases.ll @@ -0,0 +1,63 @@ +; RUN: opt -objc-arc-contract -S < %s | FileCheck %s +; rdar://9511608 + +%0 = type opaque +%1 = type opaque +%2 = type { i64, i64 } +%3 = type { i8*, i8* } +%4 = type opaque + +declare %0* @"\01-[NSAttributedString(Terminal) pathAtIndex:effectiveRange:]"(%1*, i8* nocapture, i64, %2*) optsize +declare i8* @objc_retainAutoreleasedReturnValue(i8*) +declare i8* @objc_msgSend_fixup(i8*, %3*, ...) +declare void @objc_release(i8*) +declare %2 @NSUnionRange(i64, i64, i64, i64) optsize +declare i8* @objc_autoreleaseReturnValue(i8*) +declare i8* @objc_autorelease(i8*) +declare i8* @objc_msgSend() nonlazybind + +; Don't get in trouble on bugpointed code. + +; CHECK: define void @test0( +define void @test0() { +bb: + %tmp = bitcast %4* undef to i8* + %tmp1 = tail call i8* @objc_retainAutoreleasedReturnValue(i8* %tmp) nounwind + br label %bb3 + +bb3: ; preds = %bb2 + br i1 undef, label %bb6, label %bb4 + +bb4: ; preds = %bb3 + switch i64 undef, label %bb5 [ + i64 9223372036854775807, label %bb6 + i64 0, label %bb6 + ] + +bb5: ; preds = %bb4 + br label %bb6 + +bb6: ; preds = %bb5, %bb4, %bb4, %bb3 + %tmp7 = phi %4* [ undef, %bb5 ], [ undef, %bb4 ], [ undef, %bb3 ], [ undef, %bb4 ] + unreachable +} + +; When rewriting operands for a phi which has multiple operands +; for the same block, use the exactly same value in each block. + +; CHECK: define void @test1( +; CHECK: %0 = bitcast i8* %tmp3 to %0* +; CHECK: br i1 undef, label %bb7, label %bb7 +; CHECK: bb7: +; CHECK: %tmp8 = phi %0* [ %0, %bb ], [ %0, %bb ] +define void @test1() { +bb: + %tmp = tail call %0* bitcast (i8* ()* @objc_msgSend to %0* ()*)() + %tmp2 = bitcast %0* %tmp to i8* + %tmp3 = tail call i8* @objc_retainAutoreleasedReturnValue(i8* %tmp2) nounwind + br i1 undef, label %bb7, label %bb7 + +bb7: ; preds = %bb6, %bb6, %bb5 + %tmp8 = phi %0* [ %tmp, %bb ], [ %tmp, %bb ] + unreachable +} diff --git a/test/Transforms/ObjCARC/contract.ll b/test/Transforms/ObjCARC/contract.ll new file mode 100644 index 00000000000..04ae3ca505f --- /dev/null +++ b/test/Transforms/ObjCARC/contract.ll @@ -0,0 +1,145 @@ +; RUN: opt -objc-arc-contract -S < %s | FileCheck %s + +target datalayout = "e-p:64:64:64" + +declare i8* @objc_retain(i8*) +declare void @objc_release(i8*) +declare i8* @objc_autorelease(i8*) +declare i8* @objc_autoreleaseReturnValue(i8*) +declare i8* @objc_retainAutoreleasedReturnValue(i8*) + +declare void @use_pointer(i8*) +declare i8* @returner() + +; CHECK: define void @test0 +; CHECK: call void @use_pointer(i8* %0) +; CHECK: } +define void @test0(i8* %x) nounwind { +entry: + %0 = call i8* @objc_retain(i8* %x) nounwind + call void @use_pointer(i8* %x) + ret void +} + +; CHECK: define void @test1 +; CHECK: call void @use_pointer(i8* %0) +; CHECK: } +define void @test1(i8* %x) nounwind { +entry: + %0 = call i8* @objc_autorelease(i8* %x) nounwind + call void @use_pointer(i8* %x) + ret void +} + +; Merge objc_retain and objc_autorelease into objc_retainAutorelease. + +; CHECK: define void @test2( +; CHECK: tail call i8* @objc_retainAutorelease(i8* %x) nounwind +; CHECK: } +define void @test2(i8* %x) nounwind { +entry: + %0 = tail call i8* @objc_retain(i8* %x) nounwind + tail call i8* @objc_autorelease(i8* %0) nounwind + call void @use_pointer(i8* %x) + ret void +} + +; Same as test2 but the value is returned. Do an RV optimization. + +; CHECK: define i8* @test2b( +; CHECK: tail call i8* @objc_retainAutoreleaseReturnValue(i8* %x) nounwind +; CHECK: } +define i8* @test2b(i8* %x) nounwind { +entry: + %0 = tail call i8* @objc_retain(i8* %x) nounwind + tail call i8* @objc_autoreleaseReturnValue(i8* %0) nounwind + ret i8* %x +} + +; Merge a retain,autorelease pair around a call. + +; CHECK: define void @test3( +; CHECK: tail call i8* @objc_retainAutorelease(i8* %x) nounwind +; CHECK: @use_pointer(i8* %0) +; CHECK: } +define void @test3(i8* %x, i64 %n) { +entry: + tail call i8* @objc_retain(i8* %x) nounwind + call void @use_pointer(i8* %x) + tail call i8* @objc_autorelease(i8* %x) nounwind + ret void +} + +; Trivial retain,autorelease pair with intervening call, but it's post-dominated +; by another release. The retain and autorelease can be merged. + +; CHECK: define void @test4( +; CHECK-NEXT: entry: +; CHECK-NEXT: @objc_retainAutorelease(i8* %x) nounwind +; CHECK-NEXT: @use_pointer +; CHECK-NEXT: @objc_release +; CHECK-NEXT: ret void +; CHECK-NEXT: } +define void @test4(i8* %x, i64 %n) { +entry: + tail call i8* @objc_retain(i8* %x) nounwind + call void @use_pointer(i8* %x) + tail call i8* @objc_autorelease(i8* %x) nounwind + tail call void @objc_release(i8* %x) nounwind + ret void +} + +; Don't merge retain and autorelease if they're not control-equivalent. + +; CHECK: define void @test5( +; CHECK: tail call i8* @objc_retain(i8* %p) nounwind +; CHECK: true: +; CHECK: tail call i8* @objc_autorelease(i8* %0) nounwind +; CHECK: } +define void @test5(i8* %p, i1 %a) { +entry: + tail call i8* @objc_retain(i8* %p) nounwind + br i1 %a, label %true, label %false + +true: + tail call i8* @objc_autorelease(i8* %p) nounwind + call void @use_pointer(i8* %p) + ret void + +false: + ret void +} + +; Don't eliminate objc_retainAutoreleasedReturnValue by merging it into +; an objc_autorelease. +; TODO? Merge objc_retainAutoreleasedReturnValue and objc_autorelease into +; objc_retainAutoreleasedReturnValueAutorelease and merge +; objc_retainAutoreleasedReturnValue and objc_autoreleaseReturnValue +; into objc_retainAutoreleasedReturnValueAutoreleaseReturnValue? +; Those entrypoints don't exist yet though. + +; CHECK: define i8* @test6( +; CHECK: call i8* @objc_retainAutoreleasedReturnValue(i8* %p) nounwind +; CHECK: %t = tail call i8* @objc_autoreleaseReturnValue(i8* %1) nounwind +; CHECK: } +define i8* @test6() { + %p = call i8* @returner() + tail call i8* @objc_retainAutoreleasedReturnValue(i8* %p) nounwind + %t = tail call i8* @objc_autoreleaseReturnValue(i8* %p) nounwind + call void @use_pointer(i8* %t) + ret i8* %t +} + +; Don't spoil the RV optimization. + +; CHECK: define i8* @test7(i8* %p) +; CHECK: tail call i8* @objc_retain(i8* %p) +; CHECK: call void @use_pointer(i8* %1) +; CHECK: tail call i8* @objc_autoreleaseReturnValue(i8* %1) +; CHECK: ret i8* %2 +define i8* @test7(i8* %p) { + %1 = tail call i8* @objc_retain(i8* %p) + call void @use_pointer(i8* %p) + %2 = tail call i8* @objc_autoreleaseReturnValue(i8* %p) + ret i8* %p +} diff --git a/test/Transforms/ObjCARC/dg.exp b/test/Transforms/ObjCARC/dg.exp new file mode 100644 index 00000000000..f2005891a59 --- /dev/null +++ b/test/Transforms/ObjCARC/dg.exp @@ -0,0 +1,3 @@ +load_lib llvm.exp + +RunLLVMTests [lsort [glob -nocomplain $srcdir/$subdir/*.{ll,c,cpp}]] diff --git a/test/Transforms/ObjCARC/expand.ll b/test/Transforms/ObjCARC/expand.ll new file mode 100644 index 00000000000..5388673f2b9 --- /dev/null +++ b/test/Transforms/ObjCARC/expand.ll @@ -0,0 +1,28 @@ +; RUN: opt -objc-arc-expand -S < %s | FileCheck %s + +target datalayout = "e-p:64:64:64" + +declare i8* @objc_retain(i8*) +declare i8* @objc_autorelease(i8*) + +declare void @use_pointer(i8*) + +; CHECK: define void @test0 +; CHECK: call void @use_pointer(i8* %x) +; CHECK: } +define void @test0(i8* %x) nounwind { +entry: + %0 = call i8* @objc_retain(i8* %x) nounwind + call void @use_pointer(i8* %0) + ret void +} + +; CHECK: define void @test1 +; CHECK: call void @use_pointer(i8* %x) +; CHECK: } +define void @test1(i8* %x) nounwind { +entry: + %0 = call i8* @objc_autorelease(i8* %x) nounwind + call void @use_pointer(i8* %x) + ret void +} diff --git a/test/Transforms/ObjCARC/gvn.ll b/test/Transforms/ObjCARC/gvn.ll new file mode 100644 index 00000000000..6917b02e032 --- /dev/null +++ b/test/Transforms/ObjCARC/gvn.ll @@ -0,0 +1,21 @@ +; RUN: opt -S -basicaa -objc-arc -gvn < %s | FileCheck %s + +@x = common global i8* null, align 8 + +declare i8* @objc_retain(i8*) + +; GVN should be able to eliminate this redundant load, with ARC-specific +; alias analysis. + +; CHECK: @foo +; CHECK-NEXT: entry: +; CHECK-NEXT: %s = load i8** @x +; CHECK-NOT: load +; CHECK: ret i8* %s +define i8* @foo(i32 %n) nounwind { +entry: + %s = load i8** @x + %0 = tail call i8* @objc_retain(i8* %s) nounwind + %t = load i8** @x + ret i8* %s +} diff --git a/test/Transforms/ObjCARC/move-and-form-retain-autorelease.ll b/test/Transforms/ObjCARC/move-and-form-retain-autorelease.ll new file mode 100644 index 00000000000..170d0a99c98 --- /dev/null +++ b/test/Transforms/ObjCARC/move-and-form-retain-autorelease.ll @@ -0,0 +1,221 @@ +; RUN: opt -S -objc-arc-contract < %s | FileCheck %s + +; The optimizer should be able to move the autorelease past a control triangle +; and various scary looking things and fold it into an objc_retainAutorelease. + +; CHECK: bb57: +; CHECK: tail call i8* @objc_retainAutorelease(i8* %tmp71x) nounwind +; CHECK: bb99: + +target datalayout = "e-p:64:64:64-i1:8:8-i8:8:8-i16:16:16-i32:32:32-i64:64:64-f32:32:32-f64:64:64-v64:64:64-v128:128:128-a0:0:64-s0:64:64-f80:128:128-n8:16:32:64" +target triple = "x86_64-apple-darwin11.0.0" + +%0 = type { i8* (i8*, %1*, ...)*, i8* } +%1 = type { i8*, i8* } +%2 = type { %2*, %2*, %3*, i8* (i8*, i8*)**, %4* } +%3 = type opaque +%4 = type { i32, i32, i32, i8*, i8*, %5*, %7*, %10*, i8*, %9* } +%5 = type { i32, i32, [0 x %6] } +%6 = type { i8*, i8*, i8* } +%7 = type { i64, [0 x %8*] } +%8 = type { i8*, i8*, %7*, %5*, %5*, %5*, %5*, %9*, i32, i32 } +%9 = type { i32, i32, [0 x %1] } +%10 = type { i32, i32, [0 x %11] } +%11 = type { i64*, i8*, i8*, i32, i32 } +%12 = type { i32*, i32, i8*, i64 } +%13 = type opaque +%14 = type opaque +%15 = type opaque +%16 = type opaque +%17 = type opaque +%18 = type opaque +%19 = type opaque +%20 = type opaque +%21 = type opaque +%22 = type opaque +%23 = type opaque +%24 = type opaque +%25 = type opaque + +@"\01l_objc_msgSend_fixup_alloc" = external hidden global %0, section "__DATA, __objc_msgrefs, coalesced", align 16 +@"\01L_OBJC_SELECTOR_REFERENCES_8" = external hidden global i8*, section "__DATA, __objc_selrefs, literal_pointers, no_dead_strip" +@"\01L_OBJC_SELECTOR_REFERENCES_3725" = external hidden global i8*, section "__DATA, __objc_selrefs, literal_pointers, no_dead_strip" +@"\01L_OBJC_CLASSLIST_REFERENCES_$_40" = external hidden global %2*, section "__DATA, __objc_classrefs, regular, no_dead_strip", align 8 +@"\01L_OBJC_SELECTOR_REFERENCES_4227" = external hidden global i8*, section "__DATA, __objc_selrefs, literal_pointers, no_dead_strip" +@"\01L_OBJC_SELECTOR_REFERENCES_4631" = external hidden global i8*, section "__DATA, __objc_selrefs, literal_pointers, no_dead_strip" +@"\01L_OBJC_CLASSLIST_REFERENCES_$_70" = external hidden global %2*, section "__DATA, __objc_classrefs, regular, no_dead_strip", align 8 +@"\01L_OBJC_SELECTOR_REFERENCES_148" = external hidden global i8*, section "__DATA, __objc_selrefs, literal_pointers, no_dead_strip" +@"\01L_OBJC_SELECTOR_REFERENCES_159" = external hidden global i8*, section "__DATA, __objc_selrefs, literal_pointers, no_dead_strip" +@"\01L_OBJC_SELECTOR_REFERENCES_188" = external hidden global i8*, section "__DATA, __objc_selrefs, literal_pointers, no_dead_strip" +@"\01L_OBJC_SELECTOR_REFERENCES_328" = external hidden global i8*, section "__DATA, __objc_selrefs, literal_pointers, no_dead_strip" +@"\01l_objc_msgSend_fixup_objectAtIndex_" = external hidden global %0, section "__DATA, __objc_msgrefs, coalesced", align 16 +@_unnamed_cfstring_386 = external hidden constant %12, section "__DATA,__cfstring" +@"\01l_objc_msgSend_fixup_count" = external hidden global %0, section "__DATA, __objc_msgrefs, coalesced", align 16 +@"\01L_OBJC_SELECTOR_REFERENCES_389" = external hidden global i8*, section "__DATA, __objc_selrefs, literal_pointers, no_dead_strip" +@"\01L_OBJC_SELECTOR_REFERENCES_391" = external hidden global i8*, section "__DATA, __objc_selrefs, literal_pointers, no_dead_strip" +@"\01L_OBJC_SELECTOR_REFERENCES_393" = external hidden global i8*, section "__DATA, __objc_selrefs, literal_pointers, no_dead_strip" +@NSPrintHeaderAndFooter = external constant %13* +@"\01L_OBJC_SELECTOR_REFERENCES_395" = external hidden global i8*, section "__DATA, __objc_selrefs, literal_pointers, no_dead_strip" +@"\01L_OBJC_CLASSLIST_REFERENCES_$_396" = external hidden global %2*, section "__DATA, __objc_classrefs, regular, no_dead_strip", align 8 +@"\01L_OBJC_SELECTOR_REFERENCES_398" = external hidden global i8*, section "__DATA, __objc_selrefs, literal_pointers, no_dead_strip" +@"\01L_OBJC_SELECTOR_REFERENCES_400" = external hidden global i8*, section "__DATA, __objc_selrefs, literal_pointers, no_dead_strip" +@"\01L_OBJC_SELECTOR_REFERENCES_402" = external hidden global i8*, section "__DATA, __objc_selrefs, literal_pointers, no_dead_strip" +@"\01L_OBJC_SELECTOR_REFERENCES_404" = external hidden global i8*, section "__DATA, __objc_selrefs, literal_pointers, no_dead_strip" +@"\01L_OBJC_SELECTOR_REFERENCES_406" = external hidden global i8*, section "__DATA, __objc_selrefs, literal_pointers, no_dead_strip" +@"\01L_OBJC_SELECTOR_REFERENCES_408" = external hidden global i8*, section "__DATA, __objc_selrefs, literal_pointers, no_dead_strip" +@"\01L_OBJC_CLASSLIST_REFERENCES_$_409" = external hidden global %2*, section "__DATA, __objc_classrefs, regular, no_dead_strip", align 8 +@"\01L_OBJC_SELECTOR_REFERENCES_411" = external hidden global i8*, section "__DATA, __objc_selrefs, literal_pointers, no_dead_strip" +@"\01L_OBJC_SELECTOR_REFERENCES_413" = external hidden global i8*, section "__DATA, __objc_selrefs, literal_pointers, no_dead_strip" +@"\01L_OBJC_SELECTOR_REFERENCES_415" = external hidden global i8*, section "__DATA, __objc_selrefs, literal_pointers, no_dead_strip" + +declare i8* @objc_msgSend(i8*, i8*, ...) + +declare i8* @objc_retain(i8*) + +declare void @objc_release(i8*) + +declare i8* @objc_autorelease(i8*) + +declare i8* @objc_explicit_autorelease(i8*) + +define hidden %14* @foo(%15* %arg, %16* %arg2) { +bb: + %tmp = load i8** @"\01L_OBJC_SELECTOR_REFERENCES_3725", align 8 + %tmp4 = bitcast %15* %arg to i8* + %tmp5 = tail call %18* bitcast (i8* (i8*, i8*, ...)* @objc_msgSend to %18* (i8*, i8*)*)(i8* %tmp4, i8* %tmp) + %tmp6 = bitcast %18* %tmp5 to i8* + %tmp7 = tail call i8* @objc_retain(i8* %tmp6) nounwind + %tmp8 = load %2** @"\01L_OBJC_CLASSLIST_REFERENCES_$_40", align 8 + %tmp9 = load i8** @"\01L_OBJC_SELECTOR_REFERENCES_4227", align 8 + %tmp10 = bitcast %2* %tmp8 to i8* + %tmp11 = tail call %19* bitcast (i8* (i8*, i8*, ...)* @objc_msgSend to %19* (i8*, i8*)*)(i8* %tmp10, i8* %tmp9) + %tmp12 = load i8** @"\01L_OBJC_SELECTOR_REFERENCES_4631", align 8 + %tmp13 = bitcast %19* %tmp11 to i8* + %tmp14 = tail call signext i8 bitcast (i8* (i8*, i8*, ...)* @objc_msgSend to i8 (i8*, i8*, %13*)*)(i8* %tmp13, i8* %tmp12, %13* bitcast (%12* @_unnamed_cfstring_386 to %13*)) + %tmp15 = bitcast %16* %arg2 to i8* + %tmp16 = load i8** bitcast (%0* @"\01l_objc_msgSend_fixup_count" to i8**), align 16 + %tmp17 = bitcast i8* %tmp16 to i64 (i8*, %1*)* + %tmp18 = tail call i64 %tmp17(i8* %tmp15, %1* bitcast (%0* @"\01l_objc_msgSend_fixup_count" to %1*)) + %tmp19 = icmp eq i64 %tmp18, 0 + br i1 %tmp19, label %bb22, label %bb20 + +bb20: ; preds = %bb + %tmp21 = icmp eq i8 %tmp14, 0 + br label %bb25 + +bb22: ; preds = %bb + %tmp23 = bitcast i8* %tmp7 to %18* + %tmp24 = icmp eq i8 %tmp14, 0 + br i1 %tmp24, label %bb46, label %bb25 + +bb25: ; preds = %bb22, %bb20 + %tmp26 = phi i1 [ %tmp21, %bb20 ], [ false, %bb22 ] + %tmp27 = load i8** @"\01L_OBJC_SELECTOR_REFERENCES_188", align 8 + %tmp28 = tail call i8* bitcast (i8* (i8*, i8*, ...)* @objc_msgSend to i8* (i8*, i8*)*)(i8* %tmp7, i8* %tmp27) + %tmp29 = tail call i8* @objc_explicit_autorelease(i8* %tmp28) nounwind + %tmp30 = bitcast i8* %tmp29 to %18* + tail call void @objc_release(i8* %tmp7) nounwind + %tmp31 = load i8** @"\01L_OBJC_SELECTOR_REFERENCES_389", align 8 + %tmp32 = tail call %20* bitcast (i8* (i8*, i8*, ...)* @objc_msgSend to %20* (i8*, i8*)*)(i8* %tmp29, i8* %tmp31) + %tmp33 = load i8** @"\01L_OBJC_SELECTOR_REFERENCES_391", align 8 + %tmp34 = bitcast %20* %tmp32 to i8* + tail call void bitcast (i8* (i8*, i8*, ...)* @objc_msgSend to void (i8*, i8*, %16*)*)(i8* %tmp34, i8* %tmp33, %16* %arg2) + br i1 %tmp26, label %bb46, label %bb35 + +bb35: ; preds = %bb25 + %tmp36 = load i8** @"\01L_OBJC_SELECTOR_REFERENCES_389", align 8 + %tmp37 = tail call %20* bitcast (i8* (i8*, i8*, ...)* @objc_msgSend to %20* (i8*, i8*)*)(i8* %tmp29, i8* %tmp36) + %tmp38 = load %2** @"\01L_OBJC_CLASSLIST_REFERENCES_$_70", align 8 + %tmp39 = load i8** @"\01L_OBJC_SELECTOR_REFERENCES_393", align 8 + %tmp40 = bitcast %2* %tmp38 to i8* + %tmp41 = tail call %21* bitcast (i8* (i8*, i8*, ...)* @objc_msgSend to %21* (i8*, i8*, i8)*)(i8* %tmp40, i8* %tmp39, i8 signext 1) + %tmp42 = bitcast %21* %tmp41 to i8* + %tmp43 = load %13** @NSPrintHeaderAndFooter, align 8 + %tmp44 = load i8** @"\01L_OBJC_SELECTOR_REFERENCES_159", align 8 + %tmp45 = bitcast %20* %tmp37 to i8* + tail call void bitcast (i8* (i8*, i8*, ...)* @objc_msgSend to void (i8*, i8*, i8*, %13*)*)(i8* %tmp45, i8* %tmp44, i8* %tmp42, %13* %tmp43) + br label %bb46 + +bb46: ; preds = %bb35, %bb25, %bb22 + %tmp47 = phi %18* [ %tmp30, %bb35 ], [ %tmp30, %bb25 ], [ %tmp23, %bb22 ] + %tmp48 = load i8** @"\01L_OBJC_SELECTOR_REFERENCES_328", align 8 + %tmp49 = tail call %22* bitcast (i8* (i8*, i8*, ...)* @objc_msgSend to %22* (i8*, i8*)*)(i8* %tmp4, i8* %tmp48) + %tmp50 = bitcast %22* %tmp49 to i8* + %tmp51 = load i8** bitcast (%0* @"\01l_objc_msgSend_fixup_count" to i8**), align 16 + %tmp52 = bitcast i8* %tmp51 to i64 (i8*, %1*)* + %tmp53 = tail call i64 %tmp52(i8* %tmp50, %1* bitcast (%0* @"\01l_objc_msgSend_fixup_count" to %1*)) + %tmp54 = icmp eq i64 %tmp53, 0 + br i1 %tmp54, label %bb55, label %bb57 + +bb55: ; preds = %bb46 + %tmp56 = load i8** @"\01L_OBJC_SELECTOR_REFERENCES_395", align 8 + tail call void bitcast (i8* (i8*, i8*, ...)* @objc_msgSend to void (i8*, i8*)*)(i8* %tmp4, i8* %tmp56) + br label %bb57 + +bb57: ; preds = %bb55, %bb46 + %tmp58 = load %2** @"\01L_OBJC_CLASSLIST_REFERENCES_$_396", align 8 + %tmp59 = load i8** @"\01L_OBJC_SELECTOR_REFERENCES_328", align 8 + %tmp60 = tail call %22* bitcast (i8* (i8*, i8*, ...)* @objc_msgSend to %22* (i8*, i8*)*)(i8* %tmp4, i8* %tmp59) + %tmp61 = bitcast %22* %tmp60 to i8* + %tmp62 = load i8** bitcast (%0* @"\01l_objc_msgSend_fixup_objectAtIndex_" to i8**), align 16 + %tmp63 = bitcast i8* %tmp62 to i8* (i8*, %1*, i64)* + %tmp64 = tail call i8* %tmp63(i8* %tmp61, %1* bitcast (%0* @"\01l_objc_msgSend_fixup_objectAtIndex_" to %1*), i64 0) + %tmp65 = load i8** @"\01L_OBJC_SELECTOR_REFERENCES_398", align 8 + %tmp66 = tail call i8* bitcast (i8* (i8*, i8*, ...)* @objc_msgSend to i8* (i8*, i8*)*)(i8* %tmp64, i8* %tmp65) + %tmp67 = bitcast i8* %tmp66 to %23* + %tmp68 = load i8** @"\01L_OBJC_SELECTOR_REFERENCES_400", align 8 + %tmp69 = bitcast %2* %tmp58 to i8* + %tmp70 = tail call %14* bitcast (i8* (i8*, i8*, ...)* @objc_msgSend to %14* (i8*, i8*, %23*, %18*)*)(i8* %tmp69, i8* %tmp68, %23* %tmp67, %18* %tmp47) + %tmp71 = bitcast %14* %tmp70 to i8* + ; hack to prevent the optimize from using objc_retainAutoreleasedReturnValue. + %tmp71x = getelementptr i8* %tmp71, i64 1 + %tmp72 = tail call i8* @objc_retain(i8* %tmp71x) nounwind + %tmp73 = load i8** @"\01L_OBJC_SELECTOR_REFERENCES_402", align 8 + tail call void bitcast (i8* (i8*, i8*, ...)* @objc_msgSend to void (i8*, i8*, i8)*)(i8* %tmp72, i8* %tmp73, i8 signext 1) + %tmp74 = load i8** @"\01L_OBJC_SELECTOR_REFERENCES_404", align 8 + tail call void bitcast (i8* (i8*, i8*, ...)* @objc_msgSend to void (i8*, i8*, i8)*)(i8* %tmp72, i8* %tmp74, i8 signext 1) + %tmp75 = load i8** @"\01L_OBJC_SELECTOR_REFERENCES_328", align 8 + %tmp76 = tail call %22* bitcast (i8* (i8*, i8*, ...)* @objc_msgSend to %22* (i8*, i8*)*)(i8* %tmp4, i8* %tmp75) + %tmp77 = bitcast %22* %tmp76 to i8* + %tmp78 = load i8** bitcast (%0* @"\01l_objc_msgSend_fixup_objectAtIndex_" to i8**), align 16 + %tmp79 = bitcast i8* %tmp78 to i8* (i8*, %1*, i64)* + %tmp80 = tail call i8* %tmp79(i8* %tmp77, %1* bitcast (%0* @"\01l_objc_msgSend_fixup_objectAtIndex_" to %1*), i64 0) + %tmp81 = load i8** @"\01L_OBJC_SELECTOR_REFERENCES_406", align 8 + tail call void bitcast (i8* (i8*, i8*, ...)* @objc_msgSend to void (i8*, i8*, i64)*)(i8* %tmp80, i8* %tmp81, i64 9223372036854775807) + %tmp82 = load i8** @"\01L_OBJC_SELECTOR_REFERENCES_408", align 8 + %tmp83 = tail call %24* bitcast (i8* (i8*, i8*, ...)* @objc_msgSend to %24* (i8*, i8*)*)(i8* %tmp72, i8* %tmp82) + %tmp84 = bitcast %24* %tmp83 to i8* + %tmp85 = tail call i8* @objc_retain(i8* %tmp84) nounwind + %tmp86 = load %2** @"\01L_OBJC_CLASSLIST_REFERENCES_$_409", align 8 + %tmp87 = bitcast %2* %tmp86 to i8* + %tmp88 = load i8** bitcast (%0* @"\01l_objc_msgSend_fixup_alloc" to i8**), align 16 + %tmp89 = bitcast i8* %tmp88 to i8* (i8*, %1*)* + %tmp90 = tail call i8* %tmp89(i8* %tmp87, %1* bitcast (%0* @"\01l_objc_msgSend_fixup_alloc" to %1*)) + %tmp91 = load i8** @"\01L_OBJC_SELECTOR_REFERENCES_8", align 8 + %tmp92 = tail call i8* bitcast (i8* (i8*, i8*, ...)* @objc_msgSend to i8* (i8*, i8*)*)(i8* %tmp90, i8* %tmp91) + %tmp93 = tail call i8* @objc_explicit_autorelease(i8* %tmp92) nounwind + %tmp94 = bitcast i8* %tmp93 to %25* + %tmp95 = load i8** @"\01L_OBJC_SELECTOR_REFERENCES_411", align 8 + tail call void bitcast (i8* (i8*, i8*, ...)* @objc_msgSend to void (i8*, i8*, %25*)*)(i8* %tmp85, i8* %tmp95, %25* %tmp94) + tail call void @objc_release(i8* %tmp93) nounwind + %tmp96 = load i8** @"\01L_OBJC_SELECTOR_REFERENCES_148", align 8 + %tmp97 = tail call signext i8 bitcast (i8* (i8*, i8*, ...)* @objc_msgSend to i8 (i8*, i8*)*)(i8* %tmp4, i8* %tmp96) + %tmp98 = icmp eq i8 %tmp97, 0 + br i1 %tmp98, label %bb99, label %bb104 + +bb99: ; preds = %bb57 + %tmp100 = load i8** @"\01L_OBJC_SELECTOR_REFERENCES_413", align 8 + %tmp101 = tail call i64 bitcast (i8* (i8*, i8*, ...)* @objc_msgSend to i64 (i8*, i8*)*)(i8* %tmp85, i8* %tmp100) + %tmp102 = or i64 %tmp101, 12 + %tmp103 = load i8** @"\01L_OBJC_SELECTOR_REFERENCES_415", align 8 + tail call void bitcast (i8* (i8*, i8*, ...)* @objc_msgSend to void (i8*, i8*, i64)*)(i8* %tmp85, i8* %tmp103, i64 %tmp102) + br label %bb104 + +bb104: ; preds = %bb99, %bb57 + %tmp105 = tail call i8* @objc_autorelease(i8* %tmp72) nounwind + %tmp106 = bitcast i8* %tmp105 to %14* + tail call void @objc_release(i8* %tmp85) nounwind + %tmp107 = bitcast %18* %tmp47 to i8* + tail call void @objc_release(i8* %tmp107) nounwind + ret %14* %tmp106 +} diff --git a/test/Transforms/ObjCARC/move-and-merge-autorelease.ll b/test/Transforms/ObjCARC/move-and-merge-autorelease.ll new file mode 100644 index 00000000000..8462c70a48e --- /dev/null +++ b/test/Transforms/ObjCARC/move-and-merge-autorelease.ll @@ -0,0 +1,108 @@ +; RUN: opt -S -objc-arc < %s | FileCheck %s + +; The optimizer should be able to move the autorelease past two phi nodes +; and fold it with the release in bb65. + +; CHECK: bb65: +; CHECK: call i8* @objc_retainAutorelease +; CHECK: br label %bb76 + +target datalayout = "e-p:64:64:64-i1:8:8-i8:8:8-i16:16:16-i32:32:32-i64:64:64-f32:32:32-f64:64:64-v64:64:64-v128:128:128-a0:0:64-s0:64:64-f80:128:128-n8:16:32:64" +target triple = "x86_64-apple-darwin11.0.0" + +%0 = type opaque +%1 = type opaque +%2 = type opaque +%3 = type opaque +%4 = type opaque +%5 = type opaque + +@"\01L_OBJC_SELECTOR_REFERENCES_11" = external hidden global i8*, section "__DATA, __objc_selrefs, literal_pointers, no_dead_strip" +@"\01L_OBJC_SELECTOR_REFERENCES_421455" = external hidden global i8*, section "__DATA, __objc_selrefs, literal_pointers, no_dead_strip" +@"\01L_OBJC_SELECTOR_REFERENCES_598" = external hidden global i8*, section "__DATA, __objc_selrefs, literal_pointers, no_dead_strip" +@"\01L_OBJC_SELECTOR_REFERENCES_620" = external hidden global i8*, section "__DATA, __objc_selrefs, literal_pointers, no_dead_strip" +@"\01L_OBJC_SELECTOR_REFERENCES_622" = external hidden global i8*, section "__DATA, __objc_selrefs, literal_pointers, no_dead_strip" +@"\01L_OBJC_SELECTOR_REFERENCES_624" = external hidden global i8*, section "__DATA, __objc_selrefs, literal_pointers, no_dead_strip" +@"\01L_OBJC_SELECTOR_REFERENCES_626" = external hidden global i8*, section "__DATA, __objc_selrefs, literal_pointers, no_dead_strip" + +declare i8* @objc_msgSend(i8*, i8*, ...) + +declare i8* @objc_retain(i8*) + +declare void @objc_release(i8*) + +declare i8* @objc_autorelease(i8*) + +define hidden %0* @foo(%1* %arg, %3* %arg3) { +bb: + %tmp16 = load i8** @"\01L_OBJC_SELECTOR_REFERENCES_620", align 8 + %tmp17 = bitcast %3* %arg3 to i8* + %tmp18 = call %4* bitcast (i8* (i8*, i8*, ...)* @objc_msgSend to %4* (i8*, i8*)*)(i8* %tmp17, i8* %tmp16) + %tmp19 = load i8** @"\01L_OBJC_SELECTOR_REFERENCES_622", align 8 + %tmp20 = bitcast %4* %tmp18 to i8* + %tmp21 = call %5* bitcast (i8* (i8*, i8*, ...)* @objc_msgSend to %5* (i8*, i8*)*)(i8* %tmp20, i8* %tmp19) + %tmp22 = bitcast %5* %tmp21 to i8* + %tmp23 = call i8* @objc_retain(i8* %tmp22) nounwind + %tmp24 = bitcast i8* %tmp23 to %5* + %tmp26 = icmp eq i8* %tmp23, null + br i1 %tmp26, label %bb81, label %bb27 + +bb27: ; preds = %bb + %tmp29 = load i8** @"\01L_OBJC_SELECTOR_REFERENCES_11", align 8 + %tmp30 = bitcast %1* %arg to i8* + %tmp31 = call i8* bitcast (i8* (i8*, i8*, ...)* @objc_msgSend to i8* (i8*, i8*)*)(i8* %tmp30, i8* %tmp29) + %tmp34 = call i8* @objc_retain(i8* %tmp31) nounwind + %tmp37 = load i8** @"\01L_OBJC_SELECTOR_REFERENCES_421455", align 8 + %tmp39 = call %0* bitcast (i8* (i8*, i8*, ...)* @objc_msgSend to %0* (i8*, i8*)*)(i8* %tmp34, i8* %tmp37) + %tmp40 = bitcast %0* %tmp39 to i8* + %tmp41 = call i8* @objc_retain(i8* %tmp40) nounwind + %tmp42 = bitcast i8* %tmp41 to %0* + %tmp44 = icmp eq i8* %tmp41, null + br i1 %tmp44, label %bb45, label %bb55 + +bb45: ; preds = %bb27 + %tmp47 = load i8** @"\01L_OBJC_SELECTOR_REFERENCES_624", align 8 + %tmp49 = call %0* bitcast (i8* (i8*, i8*, ...)* @objc_msgSend to %0* (i8*, i8*)*)(i8* %tmp34, i8* %tmp47) + %tmp51 = bitcast %0* %tmp49 to i8* + %tmp52 = call i8* @objc_retain(i8* %tmp51) nounwind + call void @objc_release(i8* %tmp41) nounwind + br label %bb55 + +bb55: ; preds = %bb27, %bb45 + %tmp13.0 = phi %0* [ %tmp42, %bb27 ], [ %tmp49, %bb45 ] + %tmp57 = icmp eq %0* %tmp13.0, null + br i1 %tmp57, label %bb76, label %bb58 + +bb58: ; preds = %bb55 + %tmp60 = load i8** @"\01L_OBJC_SELECTOR_REFERENCES_598", align 8 + %tmp61 = bitcast %0* %tmp13.0 to i8* + %tmp62 = call signext i8 bitcast (i8* (i8*, i8*, ...)* @objc_msgSend to i8 (i8*, i8*)*)(i8* %tmp61, i8* %tmp60) + %tmp64 = icmp eq i8 %tmp62, 0 + br i1 %tmp64, label %bb76, label %bb65 + +bb65: ; preds = %bb58 + %tmp68 = load i8** @"\01L_OBJC_SELECTOR_REFERENCES_626", align 8 + %tmp69 = bitcast %0* %tmp13.0 to i8* + %tmp70 = call %0* bitcast (i8* (i8*, i8*, ...)* @objc_msgSend to %0* (i8*, i8*, %5*)*)(i8* %tmp69, i8* %tmp68, %5* %tmp24) + %tmp72 = bitcast %0* %tmp70 to i8* + %tmp73 = call i8* @objc_retain(i8* %tmp72) nounwind + br label %bb76 + +bb76: ; preds = %bb58, %bb55, %bb65 + %tmp10.0 = phi %0* [ %tmp70, %bb65 ], [ null, %bb58 ], [ null, %bb55 ] + %tmp78 = bitcast %0* %tmp13.0 to i8* + call void @objc_release(i8* %tmp78) nounwind + call void @objc_release(i8* %tmp34) nounwind + br label %bb81 + +bb81: ; preds = %bb, %bb76 + %tmp10.1 = phi %0* [ %tmp10.0, %bb76 ], [ null, %bb ] + %tmp83 = bitcast %0* %tmp10.1 to i8* + %tmp84 = call i8* @objc_retain(i8* %tmp83) nounwind + %tmp88 = bitcast i8* %tmp87 to %0* + call void @objc_release(i8* %tmp23) nounwind + %tmp87 = call i8* @objc_autorelease(i8* %tmp84) nounwind + %tmp92 = bitcast %0* %tmp10.1 to i8* + call void @objc_release(i8* %tmp92) nounwind + ret %0* %tmp88 +} diff --git a/test/Transforms/ObjCARC/post-inlining.ll b/test/Transforms/ObjCARC/post-inlining.ll new file mode 100644 index 00000000000..ad69ccdd794 --- /dev/null +++ b/test/Transforms/ObjCARC/post-inlining.ll @@ -0,0 +1,48 @@ +; RUN: opt -S -objc-arc < %s | FileCheck %s + +declare void @use_pointer(i8*) +declare i8* @returner() +declare i8* @objc_retain(i8*) +declare i8* @objc_autoreleaseReturnValue(i8*) +declare i8* @objc_retainAutoreleasedReturnValue(i8*) + +; Clean up residue left behind after inlining. + +; CHECK: define void @test0( +; CHECK: entry: +; CHECK-NEXT: ret void +; CHECK-NEXT: } +define void @test0(i8* %call.i) { +entry: + %0 = tail call i8* @objc_retain(i8* %call.i) nounwind + %1 = tail call i8* @objc_autoreleaseReturnValue(i8* %0) nounwind + ret void +} + +; Same as test0, but with slightly different use arrangements. + +; CHECK: define void @test1( +; CHECK: entry: +; CHECK-NEXT: ret void +; CHECK-NEXT: } +define void @test1(i8* %call.i) { +entry: + %0 = tail call i8* @objc_retain(i8* %call.i) nounwind + %1 = tail call i8* @objc_autoreleaseReturnValue(i8* %call.i) nounwind + ret void +} + +; Delete a retainRV+autoreleaseRV even if the pointer is used. + +; CHECK: define void @test24( +; CHECK-NEXT: entry: +; CHECK-NEXT: call void @use_pointer(i8* %p) +; CHECK-NEXT: ret void +; CHECK-NEXT: } +define void @test24(i8* %p) { +entry: + call i8* @objc_autoreleaseReturnValue(i8* %p) nounwind + call i8* @objc_retainAutoreleasedReturnValue(i8* %p) nounwind + call void @use_pointer(i8* %p) + ret void +} diff --git a/test/Transforms/ObjCARC/retain-not-declared.ll b/test/Transforms/ObjCARC/retain-not-declared.ll new file mode 100644 index 00000000000..e1fe1170633 --- /dev/null +++ b/test/Transforms/ObjCARC/retain-not-declared.ll @@ -0,0 +1,25 @@ +; RUN: opt -S -objc-arc -objc-arc-contract < %s | FileCheck %s + +; Test that the optimizer can create an objc_retainAutoreleaseReturnValue +; declaration even if no objc_retain declaration exists. +; rdar://9401303 + +target datalayout = "e-p:64:64:64-i1:8:8-i8:8:8-i16:16:16-i32:32:32-i64:64:64-f32:32:32-f64:64:64-v64:64:64-v128:128:128-a0:0:64-s0:64:64-f80:128:128-n8:16:32:64" +declare i8* @objc_unretainedObject(i8*) +declare i8* @objc_retainAutoreleasedReturnValue(i8*) +declare i8* @objc_autoreleaseReturnValue(i8*) + +; CHECK: define i8* @foo(i8* %p) { +; CHECK-NEXT: entry: +; CHECK-NEXT: %0 = tail call i8* @objc_retainAutoreleaseReturnValue(i8* %p) nounwind +; CHECK-NEXT: ret i8* %0 +; CHECK-NEXT: } + +define i8* @foo(i8* %p) { +entry: + %call = tail call i8* @objc_unretainedObject(i8* %p) + %0 = tail call i8* @objc_retainAutoreleasedReturnValue(i8* %call) nounwind + %1 = tail call i8* @objc_autoreleaseReturnValue(i8* %0) nounwind + ret i8* %1 +} + diff --git a/test/Transforms/ObjCARC/rle-s2l.ll b/test/Transforms/ObjCARC/rle-s2l.ll new file mode 100644 index 00000000000..8f8d5c0d382 --- /dev/null +++ b/test/Transforms/ObjCARC/rle-s2l.ll @@ -0,0 +1,135 @@ +; RUN: opt -S -basicaa -objc-arc < %s | FileCheck %s + +declare i8* @objc_loadWeak(i8**) +declare i8* @objc_loadWeakRetained(i8**) +declare i8* @objc_storeWeak(i8**, i8*) +declare i8* @objc_initWeak(i8**, i8*) +declare void @use_pointer(i8*) +declare void @callee() + +; Basic redundant @objc_loadWeak elimination. + +; CHECK: define void @test0(i8** %p) { +; CHECK-NEXT: %y = call i8* @objc_loadWeak(i8** %p) +; CHECK-NEXT: call void @use_pointer(i8* %y) +; CHECK-NEXT: ret void +; CHECK-NEXT: } +define void @test0(i8** %p) { + %x = call i8* @objc_loadWeak(i8** %p) + %y = call i8* @objc_loadWeak(i8** %p) + call void @use_pointer(i8* %y) + ret void +} + +; DCE the @objc_loadWeak. + +; CHECK: define void @test1(i8** %p) { +; CHECK-NEXT: %y = call i8* @objc_loadWeakRetained(i8** %p) +; CHECK-NEXT: call void @use_pointer(i8* %y) +; CHECK-NEXT: ret void +; CHECK-NEXT: } +define void @test1(i8** %p) { + %x = call i8* @objc_loadWeak(i8** %p) + %y = call i8* @objc_loadWeakRetained(i8** %p) + call void @use_pointer(i8* %y) + ret void +} + +; Basic redundant @objc_loadWeakRetained elimination. + +; CHECK: define void @test2(i8** %p) { +; CHECK-NEXT: %x = call i8* @objc_loadWeak(i8** %p) +; CHECK-NEXT: store i8 3, i8* %x +; CHECK-NEXT: %1 = tail call i8* @objc_retain(i8* %x) +; CHECK-NEXT: call void @use_pointer(i8* %x) +; CHECK-NEXT: ret void +; CHECK-NEXT: } +define void @test2(i8** %p) { + %x = call i8* @objc_loadWeak(i8** %p) + store i8 3, i8* %x + %y = call i8* @objc_loadWeakRetained(i8** %p) + call void @use_pointer(i8* %y) + ret void +} + +; Basic redundant @objc_loadWeakRetained elimination, this time +; with a readonly call instead of a store. + +; CHECK: define void @test3(i8** %p) { +; CHECK-NEXT: %x = call i8* @objc_loadWeak(i8** %p) +; CHECK-NEXT: call void @use_pointer(i8* %x) readonly +; CHECK-NEXT: %1 = tail call i8* @objc_retain(i8* %x) +; CHECK-NEXT: call void @use_pointer(i8* %x) +; CHECK-NEXT: ret void +; CHECK-NEXT: } +define void @test3(i8** %p) { + %x = call i8* @objc_loadWeak(i8** %p) + call void @use_pointer(i8* %x) readonly + %y = call i8* @objc_loadWeakRetained(i8** %p) + call void @use_pointer(i8* %y) + ret void +} + +; A regular call blocks redundant weak load elimination. + +; CHECK: define void @test4(i8** %p) { +; CHECK-NEXT: %x = call i8* @objc_loadWeak(i8** %p) +; CHECK-NEXT: call void @use_pointer(i8* %x) readonly +; CHECK-NEXT: call void @callee() +; CHECK-NEXT: %y = call i8* @objc_loadWeak(i8** %p) +; CHECK-NEXT: call void @use_pointer(i8* %y) +; CHECK-NEXT: ret void +; CHECK-NEXT: } +define void @test4(i8** %p) { + %x = call i8* @objc_loadWeak(i8** %p) + call void @use_pointer(i8* %x) readonly + call void @callee() + %y = call i8* @objc_loadWeak(i8** %p) + call void @use_pointer(i8* %y) + ret void +} + +; Store to load forwarding. + +; CHECK: define void @test5(i8** %p, i8* %n) { +; CHECK-NEXT: %1 = call i8* @objc_storeWeak(i8** %p, i8* %n) +; CHECK-NEXT: call void @use_pointer(i8* %n) +; CHECK-NEXT: ret void +; CHECK-NEXT: } +define void @test5(i8** %p, i8* %n) { + call i8* @objc_storeWeak(i8** %p, i8* %n) + %y = call i8* @objc_loadWeak(i8** %p) + call void @use_pointer(i8* %y) + ret void +} + +; Store to load forwarding with objc_initWeak. + +; CHECK: define void @test6(i8** %p, i8* %n) { +; CHECK-NEXT: %1 = call i8* @objc_initWeak(i8** %p, i8* %n) +; CHECK-NEXT: call void @use_pointer(i8* %n) +; CHECK-NEXT: ret void +; CHECK-NEXT: } +define void @test6(i8** %p, i8* %n) { + call i8* @objc_initWeak(i8** %p, i8* %n) + %y = call i8* @objc_loadWeak(i8** %p) + call void @use_pointer(i8* %y) + ret void +} + +; Don't forward if there's a may-alias store in the way. + +; CHECK: define void @test7(i8** %p, i8* %n, i8** %q, i8* %m) { +; CHECK-NEXT: call i8* @objc_initWeak(i8** %p, i8* %n) +; CHECK-NEXT: call i8* @objc_storeWeak(i8** %q, i8* %m) +; CHECK-NEXT: %y = call i8* @objc_loadWeak(i8** %p) +; CHECK-NEXT: call void @use_pointer(i8* %y) +; CHECK-NEXT: ret void +; CHECK-NEXT: } +define void @test7(i8** %p, i8* %n, i8** %q, i8* %m) { + call i8* @objc_initWeak(i8** %p, i8* %n) + call i8* @objc_storeWeak(i8** %q, i8* %m) + %y = call i8* @objc_loadWeak(i8** %p) + call void @use_pointer(i8* %y) + ret void +} diff --git a/test/Transforms/ObjCARC/rv.ll b/test/Transforms/ObjCARC/rv.ll new file mode 100644 index 00000000000..da53a86b10f --- /dev/null +++ b/test/Transforms/ObjCARC/rv.ll @@ -0,0 +1,331 @@ +; RUN: opt -objc-arc -S < %s | FileCheck %s + +target datalayout = "e-p:64:64:64" + +declare i8* @objc_retain(i8*) +declare i8* @objc_retainAutoreleasedReturnValue(i8*) +declare void @objc_release(i8*) +declare i8* @objc_autorelease(i8*) +declare i8* @objc_autoreleaseReturnValue(i8*) +declare i8* @objc_retainAutoreleaseReturnValue(i8*) +declare void @objc_autoreleasePoolPop(i8*) +declare void @objc_autoreleasePoolPush() +declare i8* @objc_retainBlock(i8*) + +declare i8* @objc_retainedObject(i8*) +declare i8* @objc_unretainedObject(i8*) +declare i8* @objc_unretainedPointer(i8*) + +declare void @use_pointer(i8*) +declare void @callee() +declare void @callee_fnptr(void ()*) +declare void @invokee() +declare i8* @returner() + +; Test that retain+release elimination is suppressed when the +; retain is an objc_retainAutoreleasedReturnValue, since it's +; better to do the RV optimization. + +; CHECK: define void @test0( +; CHECK-NEXT: entry: +; CHECK-NEXT: %x = call i8* @returner +; CHECK-NEXT: %0 = tail call i8* @objc_retainAutoreleasedReturnValue(i8* %x) nounwind +; CHECK: t: +; CHECK-NOT: @objc_ +; CHECK: return: +; CHECK-NEXT: call void @objc_release(i8* %x) +; CHECK-NEXT: ret void +; CHECK-NEXT: } +define void @test0(i1 %p) nounwind { +entry: + %x = call i8* @returner() + %0 = call i8* @objc_retainAutoreleasedReturnValue(i8* %x) + br i1 %p, label %t, label %return + +t: + call void @use_pointer(i8* %x) + store i8 0, i8* %x + br label %return + +return: + call void @objc_release(i8* %x) nounwind + ret void +} + +; Delete no-ops. + +; CHECK: define void @test2 +; CHECK-NOT: @objc_ +; CHECK: } +define void @test2() { + call i8* @objc_retainAutoreleasedReturnValue(i8* null) + call i8* @objc_autoreleaseReturnValue(i8* null) + ; call i8* @objc_retainAutoreleaseReturnValue(i8* null) ; TODO + ret void +} + +; Delete a redundant retainRV,autoreleaseRV when forwaring a call result +; directly to a return value. + +; CHECK: define i8* @test3 +; CHECK: call i8* @returner() +; CHECK-NEXT: ret i8* %call +define i8* @test3() { +entry: + %call = call i8* @returner() + %0 = call i8* @objc_retainAutoreleasedReturnValue(i8* %call) nounwind + %1 = call i8* @objc_autoreleaseReturnValue(i8* %0) nounwind + ret i8* %1 +} + +; Delete a redundant retain,autoreleaseRV when forwaring a call result +; directly to a return value. + +; CHECK: define i8* @test4 +; CHECK: call i8* @returner() +; CHECK-NEXT: ret i8* %call +define i8* @test4() { +entry: + %call = call i8* @returner() + %0 = call i8* @objc_retain(i8* %call) nounwind + %1 = call i8* @objc_autoreleaseReturnValue(i8* %0) nounwind + ret i8* %1 +} + +; Delete a redundant fused retain+autoreleaseRV when forwaring a call result +; directly to a return value. + +; TODO +; HECK: define i8* @test5 +; HECK: call i8* @returner() +; HECK-NEXT: ret i8* %call +;define i8* @test5() { +;entry: +; %call = call i8* @returner() +; %0 = call i8* @objc_retainAutoreleaseReturnValue(i8* %call) nounwind +; ret i8* %0 +;} + +; Don't eliminate objc_retainAutoreleasedReturnValue by merging it into +; an objc_autorelease. +; TODO? Merge objc_retainAutoreleasedReturnValue and objc_autorelease into +; objc_retainAutoreleasedReturnValueAutorelease and merge +; objc_retainAutoreleasedReturnValue and objc_autoreleaseReturnValue +; into objc_retainAutoreleasedReturnValueAutoreleaseReturnValue? +; Those entrypoints don't exist yet though. + +; CHECK: define i8* @test7( +; CHECK: call i8* @objc_retainAutoreleasedReturnValue(i8* %p) +; CHECK: %t = tail call i8* @objc_autoreleaseReturnValue(i8* %p) +define i8* @test7() { + %p = call i8* @returner() + call i8* @objc_retainAutoreleasedReturnValue(i8* %p) + %t = call i8* @objc_autoreleaseReturnValue(i8* %p) + call void @use_pointer(i8* %t) + ret i8* %t +} + +; CHECK: define i8* @test7b( +; CHECK: call i8* @objc_retain(i8* %p) +; CHECK: %t = tail call i8* @objc_autoreleaseReturnValue(i8* %p) +define i8* @test7b() { + %p = call i8* @returner() + call void @use_pointer(i8* %p) + call i8* @objc_retainAutoreleasedReturnValue(i8* %p) + %t = call i8* @objc_autoreleaseReturnValue(i8* %p) + ret i8* %t +} + +; Turn objc_retain into objc_retainAutoreleasedReturnValue if its operand +; is a return value. + +; CHECK: define void @test8() +; CHECK: tail call i8* @objc_retainAutoreleasedReturnValue(i8* %p) +define void @test8() { + %p = call i8* @returner() + call i8* @objc_retain(i8* %p) + ret void +} + +; Don't apply the RV optimization to autorelease if there's no retain. + +; CHECK: define i8* @test9(i8* %p) +; CHECK: tail call i8* @objc_autorelease(i8* %p) +define i8* @test9(i8* %p) { + call i8* @objc_autorelease(i8* %p) + ret i8* %p +} + +; Apply the RV optimization. + +; CHECK: define i8* @test10(i8* %p) +; CHECK: tail call i8* @objc_retain(i8* %p) nounwind +; CHECK: tail call i8* @objc_autoreleaseReturnValue(i8* %p) nounwind +; CHECK-NEXT: ret i8* %p +define i8* @test10(i8* %p) { + %1 = call i8* @objc_retain(i8* %p) + %2 = call i8* @objc_autorelease(i8* %p) + ret i8* %p +} + +; Don't do the autoreleaseRV optimization because @use_pointer +; could undo the retain. + +; CHECK: define i8* @test11(i8* %p) +; CHECK: tail call i8* @objc_retain(i8* %p) +; CHECK-NEXT: call void @use_pointer(i8* %p) +; CHECK: tail call i8* @objc_autorelease(i8* %p) +; CHECK-NEXT: ret i8* %p +define i8* @test11(i8* %p) { + %1 = call i8* @objc_retain(i8* %p) + call void @use_pointer(i8* %p) + %2 = call i8* @objc_autorelease(i8* %p) + ret i8* %p +} + +; Don't spoil the RV optimization. + +; CHECK: define i8* @test12(i8* %p) +; CHECK: tail call i8* @objc_retain(i8* %p) +; CHECK: call void @use_pointer(i8* %p) +; CHECK: tail call i8* @objc_autoreleaseReturnValue(i8* %p) +; CHECK: ret i8* %p +define i8* @test12(i8* %p) { + %1 = call i8* @objc_retain(i8* %p) + call void @use_pointer(i8* %p) + %2 = call i8* @objc_autoreleaseReturnValue(i8* %p) + ret i8* %p +} + +; Don't zap the objc_retainAutoreleasedReturnValue. + +; CHECK: define i8* @test13( +; CHECK: tail call i8* @objc_retainAutoreleasedReturnValue(i8* %p) +; CHECK: tail call i8* @objc_autorelease(i8* %p) +; CHECK: ret i8* %p +define i8* @test13() { + %p = call i8* @returner() + %1 = call i8* @objc_retainAutoreleasedReturnValue(i8* %p) + call void @callee() + %2 = call i8* @objc_autorelease(i8* %p) + ret i8* %p +} + +; Convert objc_retainAutoreleasedReturnValue to objc_retain if its +; argument is not a return value. + +; CHECK: define void @test14( +; CHECK-NEXT: tail call i8* @objc_retain(i8* %p) nounwind +; CHECK-NEXT: ret void +define void @test14(i8* %p) { + call i8* @objc_retainAutoreleasedReturnValue(i8* %p) + ret void +} + +; Don't convert objc_retainAutoreleasedReturnValue to objc_retain if its +; argument is a return value. + +; CHECK: define void @test15( +; CHECK-NEXT: %y = call i8* @returner() +; CHECK-NEXT: tail call i8* @objc_retainAutoreleasedReturnValue(i8* %y) nounwind +; CHECK-NEXT: ret void +define void @test15() { + %y = call i8* @returner() + call i8* @objc_retainAutoreleasedReturnValue(i8* %y) + ret void +} + +; Convert objc_retain to objc_retainAutoreleasedReturnValue if its +; argument is a return value. + +; CHECK: define void @test16( +; CHECK-NEXT: %y = call i8* @returner() +; CHECK-NEXT: tail call i8* @objc_retainAutoreleasedReturnValue(i8* %y) nounwind +; CHECK-NEXT: ret void +define void @test16() { + %y = call i8* @returner() + call i8* @objc_retain(i8* %y) + ret void +} + +; Don't convert objc_retain to objc_retainAutoreleasedReturnValue if its +; argument is not a return value. + +; CHECK: define void @test17( +; CHECK-NEXT: tail call i8* @objc_retain(i8* %y) nounwind +; CHECK-NEXT: ret void +define void @test17(i8* %y) { + call i8* @objc_retain(i8* %y) + ret void +} + +; Don't Convert objc_retain to objc_retainAutoreleasedReturnValue if it +; isn't next to the call providing its return value. + +; CHECK: define void @test18( +; CHECK-NEXT: %y = call i8* @returner() +; CHECK-NEXT: call void @callee() +; CHECK-NEXT: tail call i8* @objc_retain(i8* %y) nounwind +; CHECK-NEXT: ret void +define void @test18() { + %y = call i8* @returner() + call void @callee() + call i8* @objc_retain(i8* %y) + ret void +} + +; Delete autoreleaseRV+retainRV pairs. + +; CHECK: define i8* @test19(i8* %p) { +; CHECK-NEXT: ret i8* %p +define i8* @test19(i8* %p) { + call i8* @objc_autoreleaseReturnValue(i8* %p) + call i8* @objc_retainAutoreleasedReturnValue(i8* %p) + ret i8* %p +} + +; Like test19 but with plain autorelease. + +; CHECK: define i8* @test20(i8* %p) { +; CHECK-NEXT: call i8* @objc_autorelease(i8* %p) +; CHECK-NEXT: call i8* @objc_retain(i8* %p) +; CHECK-NEXT: ret i8* %p +define i8* @test20(i8* %p) { + call i8* @objc_autorelease(i8* %p) + call i8* @objc_retainAutoreleasedReturnValue(i8* %p) + ret i8* %p +} + +; Like test19 but with plain retain. + +; CHECK: define i8* @test21(i8* %p) { +; CHECK-NEXT: call i8* @objc_autoreleaseReturnValue(i8* %p) +; CHECK-NEXT: call i8* @objc_retain(i8* %p) +; CHECK-NEXT: ret i8* %p +define i8* @test21(i8* %p) { + call i8* @objc_autoreleaseReturnValue(i8* %p) + call i8* @objc_retain(i8* %p) + ret i8* %p +} + +; Like test19 but with plain retain and autorelease. + +; CHECK: define i8* @test22(i8* %p) { +; CHECK-NEXT: call i8* @objc_autorelease(i8* %p) +; CHECK-NEXT: call i8* @objc_retain(i8* %p) +; CHECK-NEXT: ret i8* %p +define i8* @test22(i8* %p) { + call i8* @objc_autorelease(i8* %p) + call i8* @objc_retain(i8* %p) + ret i8* %p +} + +; Convert autoreleaseRV to autorelease. + +; CHECK: define void @test23( +; CHECK: tail call i8* @objc_autorelease(i8* %p) nounwind +define void @test23(i8* %p) { + store i8 0, i8* %p + call i8* @objc_autoreleaseReturnValue(i8* %p) + ret void +} diff --git a/test/Transforms/ObjCARC/weak-contract.ll b/test/Transforms/ObjCARC/weak-contract.ll new file mode 100644 index 00000000000..ca69c7087dd --- /dev/null +++ b/test/Transforms/ObjCARC/weak-contract.ll @@ -0,0 +1,14 @@ +; RUN: opt -objc-arc-contract -S < %s | FileCheck %s + +declare i8* @objc_initWeak(i8**, i8*) + +; Convert objc_initWeak(p, null) to *p = null. + +; CHECK: define i8* @test0(i8** %p) { +; CHECK-NEXT: store i8* null, i8** %p +; CHECK-NEXT: ret i8* null +; CHECK-NEXT: } +define i8* @test0(i8** %p) { + %t = call i8* @objc_initWeak(i8** %p, i8* null) + ret i8* %t +} diff --git a/test/Transforms/ObjCARC/weak-copies.ll b/test/Transforms/ObjCARC/weak-copies.ll new file mode 100644 index 00000000000..e1a94bb4749 --- /dev/null +++ b/test/Transforms/ObjCARC/weak-copies.ll @@ -0,0 +1,87 @@ +; RUN: opt -S -basicaa -objc-arc < %s | FileCheck %s + +target datalayout = "e-p:64:64:64-i1:8:8-i8:8:8-i16:16:16-i32:32:32-i64:64:64-f32:32:32-f64:64:64-v64:64:64-v128:128:128-a0:0:64-s0:64:64-f80:128:128-n8:16:32:64" +target triple = "x86_64-apple-darwin11.0.0" + +%0 = type { i64, i64, i8*, i8*, i8*, i8* } +%1 = type <{ i8*, i32, i32, i8*, %struct.__block_descriptor*, i8* }> +%struct.__block_descriptor = type { i64, i64 } + +@_NSConcreteStackBlock = external global i8* +@.str = private unnamed_addr constant [6 x i8] c"v8@?0\00" +@"\01L_OBJC_CLASS_NAME_" = internal global [3 x i8] c"\01@\00", section "__TEXT,__objc_classname,cstring_literals", align 1 +@__block_descriptor_tmp = internal constant %0 { i64 0, i64 40, i8* bitcast (void (i8*, i8*)* @__copy_helper_block_ to i8*), i8* bitcast (void (i8*)* @__destroy_helper_block_ to i8*), i8* getelementptr inbounds ([6 x i8]* @.str, i32 0, i32 0), i8* getelementptr inbounds ([3 x i8]* @"\01L_OBJC_CLASS_NAME_", i32 0, i32 0) } +@"\01L_OBJC_IMAGE_INFO" = internal constant [2 x i32] [i32 0, i32 16], section "__DATA, __objc_imageinfo, regular, no_dead_strip" +@llvm.used = appending global [2 x i8*] [i8* getelementptr inbounds ([3 x i8]* @"\01L_OBJC_CLASS_NAME_", i32 0, i32 0), i8* bitcast ([2 x i32]* @"\01L_OBJC_IMAGE_INFO" to i8*)], section "llvm.metadata" + +; Eliminate unnecessary weak pointer copies. + +; CHECK: define void @foo() { +; CHECK-NEXT: entry: +; CHECK-NEXT: %call = call i8* @bar() +; CHECK-NEXT: call void @use(i8* %call) nounwind +; CHECK-NEXT: ret void +; CHECK-NEXT: } +define void @foo() { +entry: + %w = alloca i8*, align 8 + %x = alloca i8*, align 8 + %call = call i8* @bar() + %0 = call i8* @objc_initWeak(i8** %w, i8* %call) nounwind + %1 = call i8* @objc_loadWeak(i8** %w) nounwind + %2 = call i8* @objc_initWeak(i8** %x, i8* %1) nounwind + %3 = call i8* @objc_loadWeak(i8** %x) nounwind + call void @use(i8* %3) nounwind + call void @objc_destroyWeak(i8** %x) nounwind + call void @objc_destroyWeak(i8** %w) nounwind + ret void +} + +; Eliminate unnecessary weak pointer copies in a block initialization. + +; CHECK: define void @qux(i8* %me) nounwind { +; CHECK-NEXT: entry: +; CHECK-NEXT: %block = alloca %1, align 8 +; CHECK-NOT: alloca +; CHECK: } +define void @qux(i8* %me) nounwind { +entry: + %w = alloca i8*, align 8 + %block = alloca %1, align 8 + %0 = call i8* @objc_retain(i8* %me) nounwind + %1 = call i8* @objc_initWeak(i8** %w, i8* %0) nounwind + %block.isa = getelementptr inbounds %1* %block, i64 0, i32 0 + store i8* bitcast (i8** @_NSConcreteStackBlock to i8*), i8** %block.isa, align 8 + %block.flags = getelementptr inbounds %1* %block, i64 0, i32 1 + store i32 1107296256, i32* %block.flags, align 8 + %block.reserved = getelementptr inbounds %1* %block, i64 0, i32 2 + store i32 0, i32* %block.reserved, align 4 + %block.invoke = getelementptr inbounds %1* %block, i64 0, i32 3 + store i8* bitcast (void (i8*)* @__qux_block_invoke_0 to i8*), i8** %block.invoke, align 8 + %block.descriptor = getelementptr inbounds %1* %block, i64 0, i32 4 + store %struct.__block_descriptor* bitcast (%0* @__block_descriptor_tmp to %struct.__block_descriptor*), %struct.__block_descriptor** %block.descriptor, align 8 + %block.captured = getelementptr inbounds %1* %block, i64 0, i32 5 + %2 = call i8* @objc_loadWeak(i8** %w) nounwind + %3 = call i8* @objc_initWeak(i8** %block.captured, i8* %2) nounwind + %4 = bitcast %1* %block to void ()* + call void @use_block(void ()* %4) nounwind + call void @objc_destroyWeak(i8** %block.captured) nounwind + call void @objc_destroyWeak(i8** %w) nounwind + call void @objc_release(i8* %0) nounwind, !clang.imprecise_release !0 + ret void +} + +declare i8* @objc_retain(i8*) +declare void @use_block(void ()*) nounwind +declare void @__qux_block_invoke_0(i8* %.block_descriptor) nounwind +declare void @__copy_helper_block_(i8*, i8*) nounwind +declare void @objc_copyWeak(i8**, i8**) +declare void @__destroy_helper_block_(i8*) nounwind +declare void @objc_release(i8*) +declare i8* @bar() +declare i8* @objc_initWeak(i8**, i8*) +declare i8* @objc_loadWeak(i8**) +declare void @use(i8*) nounwind +declare void @objc_destroyWeak(i8**) + +!0 = metadata !{} diff --git a/test/Transforms/ObjCARC/weak.ll b/test/Transforms/ObjCARC/weak.ll new file mode 100644 index 00000000000..85a290c0981 --- /dev/null +++ b/test/Transforms/ObjCARC/weak.ll @@ -0,0 +1,57 @@ +; RUN: opt -objc-arc -S < %s | FileCheck %s + +declare i8* @objc_initWeak(i8**, i8*) +declare i8* @objc_storeWeak(i8**, i8*) +declare i8* @objc_loadWeak(i8**) +declare void @objc_destroyWeak(i8**) +declare i8* @objc_loadWeakRetained(i8**) +declare void @objc_moveWeak(i8**, i8**) +declare void @objc_copyWeak(i8**, i8**) + +; If the pointer-to-weak-pointer is null, it's undefined behavior. + +; CHECK: define void @test0( +; CHECK: store i8* undef, i8** null +; CHECK: store i8* undef, i8** null +; CHECK: store i8* undef, i8** null +; CHECK: store i8* undef, i8** null +; CHECK: store i8* undef, i8** null +; CHECK: store i8* undef, i8** null +; CHECK: store i8* undef, i8** null +; CHECK: store i8* undef, i8** null +; CHECK: store i8* undef, i8** null +; CHECK: store i8* undef, i8** null +; CHECK: store i8* undef, i8** null +; CHECK: store i8* undef, i8** null +; CHECK: store i8* undef, i8** null +; CHECK: store i8* undef, i8** null +; CHECK: store i8* undef, i8** null +; CHECK: store i8* undef, i8** null +; CHECK: store i8* undef, i8** null +; CHECK: store i8* undef, i8** null +; CHECK: ret void +define void @test0(i8* %p, i8** %q) { +entry: + call i8* @objc_storeWeak(i8** null, i8* %p) + call i8* @objc_storeWeak(i8** undef, i8* %p) + call i8* @objc_loadWeakRetained(i8** null) + call i8* @objc_loadWeakRetained(i8** undef) + call i8* @objc_loadWeak(i8** null) + call i8* @objc_loadWeak(i8** undef) + call i8* @objc_initWeak(i8** null, i8* %p) + call i8* @objc_initWeak(i8** undef, i8* %p) + call void @objc_destroyWeak(i8** null) + call void @objc_destroyWeak(i8** undef) + + call void @objc_copyWeak(i8** null, i8** %q) + call void @objc_copyWeak(i8** undef, i8** %q) + call void @objc_copyWeak(i8** %q, i8** null) + call void @objc_copyWeak(i8** %q, i8** undef) + + call void @objc_moveWeak(i8** null, i8** %q) + call void @objc_moveWeak(i8** undef, i8** %q) + call void @objc_moveWeak(i8** %q, i8** null) + call void @objc_moveWeak(i8** %q, i8** undef) + + ret void +}