diff --git a/llvm/lib/Transforms/ObjCARC/ObjCARCOpts.cpp b/llvm/lib/Transforms/ObjCARC/ObjCARCOpts.cpp index 87d6bed6b1bdb..5134ffb5476af 100644 --- a/llvm/lib/Transforms/ObjCARC/ObjCARCOpts.cpp +++ b/llvm/lib/Transforms/ObjCARC/ObjCARCOpts.cpp @@ -506,6 +506,20 @@ namespace { ARCInstKind &Class); void OptimizeIndividualCalls(Function &F); + /// Optimize an individual call, optionally passing the + /// GetArgRCIdentityRoot if it has already been computed. + void OptimizeIndividualCallImpl( + Function &F, DenseMap &BlockColors, + Instruction *Inst, ARCInstKind Class, const Value *Arg); + + /// Try to optimize an AutoreleaseRV with a RetainRV or ClaimRV. If the + /// optimization occurs, returns true to indicate that the caller should + /// assume the instructions are dead. + bool OptimizeInlinedAutoreleaseRVCall( + Function &F, DenseMap &BlockColors, + Instruction *Inst, const Value *&Arg, ARCInstKind Class, + Instruction *AutoreleaseRV, const Value *&AutoreleaseRVArg); + void CheckForCFGHazards(const BasicBlock *BB, DenseMap &BBStates, BBState &MyStates) const; @@ -588,36 +602,8 @@ void ObjCARCOpt::getAnalysisUsage(AnalysisUsage &AU) const { AU.setPreservesCFG(); } -static bool isSafeBetweenRVCalls(const Instruction *I) { - if (IsNoopInstruction(I)) - return true; - - auto *CB = dyn_cast(I); - if (!CB) - return false; - - Intrinsic::ID IID = CB->getIntrinsicID(); - if (IID == Intrinsic::not_intrinsic) - return false; - - switch (IID) { - case Intrinsic::lifetime_start: - case Intrinsic::lifetime_end: - // The inliner adds new lifetime markers as part of the return sequence, - // which should be skipped when looking for paired return RV call. - LLVM_FALLTHROUGH; - case Intrinsic::stacksave: - case Intrinsic::stackrestore: - // If the inlined code contains dynamic allocas, the above applies as well. - return true; - default: - return false; - } -} - /// 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. +/// not a return value. bool ObjCARCOpt::OptimizeRetainRVCall(Function &F, Instruction *RetainRV) { // Check for the argument being from an immediately preceding call or invoke. @@ -643,39 +629,6 @@ ObjCARCOpt::OptimizeRetainRVCall(Function &F, Instruction *RetainRV) { } } - // Track PHIs which are equivalent to our Arg. - SmallDenseSet EquivalentArgs; - EquivalentArgs.insert(Arg); - - // Add PHIs that are equivalent to Arg to ArgUsers. - if (const PHINode *PN = dyn_cast(Arg)) { - SmallVector ArgUsers; - getEquivalentPHIs(*PN, ArgUsers); - EquivalentArgs.insert(ArgUsers.begin(), ArgUsers.end()); - } - - // Check for being preceded by an objc_autoreleaseReturnValue on the same - // pointer. In this case, we can delete the pair. - BasicBlock::iterator I = RetainRV->getIterator(), - Begin = RetainRV->getParent()->begin(); - if (I != Begin) { - do - --I; - while (I != Begin && isSafeBetweenRVCalls(&*I)); - if (GetBasicARCInstKind(&*I) == ARCInstKind::AutoreleaseRV && - EquivalentArgs.count(GetArgRCIdentityRoot(&*I))) { - Changed = true; - ++NumPeeps; - - LLVM_DEBUG(dbgs() << "Erasing autoreleaseRV,retainRV pair: " << *I << "\n" - << "Erasing " << *RetainRV << "\n"); - - EraseInstruction(&*I); - EraseInstruction(RetainRV); - return true; - } - } - // Turn it to a plain objc_retain. Changed = true; ++NumPeeps; @@ -693,6 +646,62 @@ ObjCARCOpt::OptimizeRetainRVCall(Function &F, Instruction *RetainRV) { return false; } +bool ObjCARCOpt::OptimizeInlinedAutoreleaseRVCall( + Function &F, DenseMap &BlockColors, + Instruction *Inst, const Value *&Arg, ARCInstKind Class, + Instruction *AutoreleaseRV, const Value *&AutoreleaseRVArg) { + // Must be in the same basic block. + assert(Inst->getParent() == AutoreleaseRV->getParent()); + + // Must operate on the same root. + Arg = GetArgRCIdentityRoot(Inst); + AutoreleaseRVArg = GetArgRCIdentityRoot(AutoreleaseRV); + if (Arg != AutoreleaseRVArg) { + // If there isn't an exact match, check if we have equivalent PHIs. + const PHINode *PN = dyn_cast(Arg); + if (!PN) + return false; + + SmallVector ArgUsers; + getEquivalentPHIs(*PN, ArgUsers); + if (llvm::find(ArgUsers, AutoreleaseRVArg) == ArgUsers.end()) + return false; + } + + // Okay, this is a match. Merge them. + ++NumPeeps; + LLVM_DEBUG(dbgs() << "Found inlined objc_autoreleaseReturnValue '" + << *AutoreleaseRV << "' paired with '" << *Inst << "'\n"); + + // Delete the RV pair, starting with the AutoreleaseRV. + AutoreleaseRV->replaceAllUsesWith( + cast(AutoreleaseRV)->getArgOperand(0)); + EraseInstruction(AutoreleaseRV); + if (Class == ARCInstKind::RetainRV) { + // AutoreleaseRV and RetainRV cancel out. Delete the RetainRV. + Inst->replaceAllUsesWith(cast(Inst)->getArgOperand(0)); + EraseInstruction(Inst); + return true; + } + + // ClaimRV is a frontend peephole for RetainRV + Release. Since the + // AutoreleaseRV and RetainRV cancel out, replace the ClaimRV with a Release. + assert(Class == ARCInstKind::ClaimRV); + Value *CallArg = cast(Inst)->getArgOperand(0); + CallInst *Release = CallInst::Create( + EP.get(ARCRuntimeEntryPointKind::Release), CallArg, "", Inst); + assert(IsAlwaysTail(ARCInstKind::ClaimRV) && + "Expected ClaimRV to be safe to tail call"); + Release->setTailCall(); + Inst->replaceAllUsesWith(CallArg); + EraseInstruction(Inst); + + // Run the normal optimizations on Release. + OptimizeIndividualCallImpl(F, BlockColors, Release, ARCInstKind::Release, + Arg); + return true; +} + /// Turn objc_autoreleaseReturnValue into objc_autorelease if the result is not /// used as a return value. void ObjCARCOpt::OptimizeAutoreleaseRVCall(Function &F, @@ -779,286 +788,370 @@ void ObjCARCOpt::OptimizeIndividualCalls(Function &F) { isScopedEHPersonality(classifyEHPersonality(F.getPersonalityFn()))) BlockColors = colorEHFunclets(F); + // Store any delayed AutoreleaseRV intrinsics, so they can be easily paired + // with RetainRV and ClaimRV. + Instruction *DelayedAutoreleaseRV = nullptr; + const Value *DelayedAutoreleaseRVArg = nullptr; + auto setDelayedAutoreleaseRV = [&](Instruction *AutoreleaseRV) { + assert(!DelayedAutoreleaseRV || !AutoreleaseRV); + DelayedAutoreleaseRV = AutoreleaseRV; + DelayedAutoreleaseRVArg = nullptr; + }; + auto optimizeDelayedAutoreleaseRV = [&]() { + if (!DelayedAutoreleaseRV) + return; + OptimizeIndividualCallImpl(F, BlockColors, DelayedAutoreleaseRV, + ARCInstKind::AutoreleaseRV, + DelayedAutoreleaseRVArg); + setDelayedAutoreleaseRV(nullptr); + }; + auto shouldDelayAutoreleaseRV = [&](Instruction *NonARCInst) { + // Nothing to delay, but we may as well skip the logic below. + if (!DelayedAutoreleaseRV) + return true; + + // If we hit the end of the basic block we're not going to find an RV-pair. + // Stop delaying. + if (NonARCInst->isTerminator()) + return false; + + // Given the frontend rules for emitting AutoreleaseRV, RetainRV, and + // ClaimRV, it's probably safe to skip over even opaque function calls + // here since OptimizeInlinedAutoreleaseRVCall will confirm that they + // have the same RCIdentityRoot. However, what really matters is + // skipping instructions or intrinsics that the inliner could leave behind; + // be conservative for now and don't skip over opaque calls, which could + // potentially include other ARC calls. + auto *CB = dyn_cast(NonARCInst); + if (!CB) + return true; + return CB->getIntrinsicID() != Intrinsic::not_intrinsic; + }; + // Visit all objc_* calls in F. for (inst_iterator I = inst_begin(&F), E = inst_end(&F); I != E; ) { Instruction *Inst = &*I++; ARCInstKind Class = GetBasicARCInstKind(Inst); - LLVM_DEBUG(dbgs() << "Visiting: Class: " << Class << "; " << *Inst << "\n"); - - // Some of the ARC calls can be deleted if their arguments are global - // variables that are inert in ARC. - if (IsNoopOnGlobal(Class)) { - Value *Opnd = Inst->getOperand(0); - if (auto *GV = dyn_cast(Opnd->stripPointerCasts())) - if (GV->hasAttribute("objc_arc_inert")) { - if (!Inst->getType()->isVoidTy()) - Inst->replaceAllUsesWith(Opnd); - Inst->eraseFromParent(); - continue; - } - } - + // Skip this loop if this instruction isn't itself an ARC intrinsic. + const Value *Arg = nullptr; 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 ARCInstKind::NoopCast: - Changed = true; - ++NumNoops; - LLVM_DEBUG(dbgs() << "Erasing no-op cast: " << *Inst << "\n"); - EraseInstruction(Inst); - continue; - - // If the pointer-to-weak-pointer is null, it's undefined behavior. - case ARCInstKind::StoreWeak: - case ARCInstKind::LoadWeak: - case ARCInstKind::LoadWeakRetained: - case ARCInstKind::InitWeak: - case ARCInstKind::DestroyWeak: { - CallInst *CI = cast(Inst); - if (IsNullOrUndef(CI->getArgOperand(0))) { - Changed = true; - Type *Ty = CI->getArgOperand(0)->getType(); - new StoreInst(UndefValue::get(cast(Ty)->getElementType()), - Constant::getNullValue(Ty), - CI); - Value *NewValue = UndefValue::get(CI->getType()); - LLVM_DEBUG( - dbgs() << "A null pointer-to-weak-pointer is undefined behavior." - "\nOld = " - << *CI << "\nNew = " << *NewValue << "\n"); - CI->replaceAllUsesWith(NewValue); - CI->eraseFromParent(); - continue; - } - break; - } - case ARCInstKind::CopyWeak: - case ARCInstKind::MoveWeak: { - CallInst *CI = cast(Inst); - if (IsNullOrUndef(CI->getArgOperand(0)) || - IsNullOrUndef(CI->getArgOperand(1))) { - Changed = true; - Type *Ty = CI->getArgOperand(0)->getType(); - new StoreInst(UndefValue::get(cast(Ty)->getElementType()), - Constant::getNullValue(Ty), - CI); - - Value *NewValue = UndefValue::get(CI->getType()); - LLVM_DEBUG( - dbgs() << "A null pointer-to-weak-pointer is undefined behavior." - "\nOld = " - << *CI << "\nNew = " << *NewValue << "\n"); - - CI->replaceAllUsesWith(NewValue); - CI->eraseFromParent(); - continue; - } - break; - } - case ARCInstKind::RetainRV: - if (OptimizeRetainRVCall(F, Inst)) - continue; + default: + optimizeDelayedAutoreleaseRV(); break; + case ARCInstKind::CallOrUser: + case ARCInstKind::User: + case ARCInstKind::None: + // This is a non-ARC instruction. If we're delaying an AutoreleaseRV, + // check if it's safe to skip over it; if not, optimize the AutoreleaseRV + // now. + if (!shouldDelayAutoreleaseRV(Inst)) + optimizeDelayedAutoreleaseRV(); + continue; case ARCInstKind::AutoreleaseRV: - OptimizeAutoreleaseRVCall(F, Inst, Class); + optimizeDelayedAutoreleaseRV(); + setDelayedAutoreleaseRV(Inst); + continue; + case ARCInstKind::RetainRV: + case ARCInstKind::ClaimRV: + if (DelayedAutoreleaseRV) { + // We have a potential RV pair. Check if they cancel out. + if (OptimizeInlinedAutoreleaseRVCall(F, BlockColors, Inst, Arg, Class, + DelayedAutoreleaseRV, + DelayedAutoreleaseRVArg)) { + setDelayedAutoreleaseRV(nullptr); + continue; + } + optimizeDelayedAutoreleaseRV(); + } 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(); - - Function *Decl = EP.get(ARCRuntimeEntryPointKind::Release); - CallInst *NewCall = CallInst::Create(Decl, Call->getArgOperand(0), "", - Call); - NewCall->setMetadata(MDKindCache.get(ARCMDKindID::ImpreciseRelease), - MDNode::get(C, None)); - - LLVM_DEBUG( - dbgs() << "Replacing autorelease{,RV}(x) with objc_release(x) " - "since x is otherwise unused.\nOld: " - << *Call << "\nNew: " << *NewCall << "\n"); - - EraseInstruction(Call); - Inst = NewCall; - Class = ARCInstKind::Release; + OptimizeIndividualCallImpl(F, BlockColors, Inst, Class, Arg); + } + + // Catch the final delayed AutoreleaseRV. + optimizeDelayedAutoreleaseRV(); +} + +void ObjCARCOpt::OptimizeIndividualCallImpl( + Function &F, DenseMap &BlockColors, + Instruction *Inst, ARCInstKind Class, const Value *Arg) { + LLVM_DEBUG(dbgs() << "Visiting: Class: " << Class << "; " << *Inst << "\n"); + + // Some of the ARC calls can be deleted if their arguments are global + // variables that are inert in ARC. + if (IsNoopOnGlobal(Class)) { + Value *Opnd = Inst->getOperand(0); + if (auto *GV = dyn_cast(Opnd->stripPointerCasts())) + if (GV->hasAttribute("objc_arc_inert")) { + if (!Inst->getType()->isVoidTy()) + Inst->replaceAllUsesWith(Opnd); + Inst->eraseFromParent(); + return; } - } + } + + switch (Class) { + default: + break; - // For functions which can never be passed stack arguments, add - // a tail keyword. - if (IsAlwaysTail(Class) && !cast(Inst)->isNoTailCall()) { + // 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 ARCInstKind::NoopCast: + Changed = true; + ++NumNoops; + LLVM_DEBUG(dbgs() << "Erasing no-op cast: " << *Inst << "\n"); + EraseInstruction(Inst); + return; + + // If the pointer-to-weak-pointer is null, it's undefined behavior. + case ARCInstKind::StoreWeak: + case ARCInstKind::LoadWeak: + case ARCInstKind::LoadWeakRetained: + case ARCInstKind::InitWeak: + case ARCInstKind::DestroyWeak: { + CallInst *CI = cast(Inst); + if (IsNullOrUndef(CI->getArgOperand(0))) { Changed = true; + Type *Ty = CI->getArgOperand(0)->getType(); + new StoreInst(UndefValue::get(cast(Ty)->getElementType()), + Constant::getNullValue(Ty), CI); + Value *NewValue = UndefValue::get(CI->getType()); LLVM_DEBUG( - dbgs() << "Adding tail keyword to function since it can never be " - "passed stack args: " - << *Inst << "\n"); - cast(Inst)->setTailCall(); + dbgs() << "A null pointer-to-weak-pointer is undefined behavior." + "\nOld = " + << *CI << "\nNew = " << *NewValue << "\n"); + CI->replaceAllUsesWith(NewValue); + CI->eraseFromParent(); + return; } - - // Ensure that functions that can never have a "tail" keyword due to the - // semantics of ARC truly do not do so. - if (IsNeverTail(Class)) { + break; + } + case ARCInstKind::CopyWeak: + case ARCInstKind::MoveWeak: { + CallInst *CI = cast(Inst); + if (IsNullOrUndef(CI->getArgOperand(0)) || + IsNullOrUndef(CI->getArgOperand(1))) { Changed = true; - LLVM_DEBUG(dbgs() << "Removing tail keyword from function: " << *Inst - << "\n"); - cast(Inst)->setTailCall(false); + Type *Ty = CI->getArgOperand(0)->getType(); + new StoreInst(UndefValue::get(cast(Ty)->getElementType()), + Constant::getNullValue(Ty), CI); + + Value *NewValue = UndefValue::get(CI->getType()); + LLVM_DEBUG( + dbgs() << "A null pointer-to-weak-pointer is undefined behavior." + "\nOld = " + << *CI << "\nNew = " << *NewValue << "\n"); + + CI->replaceAllUsesWith(NewValue); + CI->eraseFromParent(); + return; } + break; + } + case ARCInstKind::RetainRV: + if (OptimizeRetainRVCall(F, Inst)) + return; + break; + case ARCInstKind::AutoreleaseRV: + OptimizeAutoreleaseRVCall(F, Inst, Class); + break; + } - // Set nounwind as needed. - if (IsNoThrow(Class)) { + // 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; - LLVM_DEBUG(dbgs() << "Found no throw class. Setting nounwind on: " - << *Inst << "\n"); - cast(Inst)->setDoesNotThrow(); - } + ++NumAutoreleases; - if (!IsNoopOnNull(Class)) { - UsedInThisFunction |= 1 << unsigned(Class); - continue; - } + // Create the declaration lazily. + LLVMContext &C = Inst->getContext(); - const Value *Arg = GetArgRCIdentityRoot(Inst); + Function *Decl = EP.get(ARCRuntimeEntryPointKind::Release); + CallInst *NewCall = + CallInst::Create(Decl, Call->getArgOperand(0), "", Call); + NewCall->setMetadata(MDKindCache.get(ARCMDKindID::ImpreciseRelease), + MDNode::get(C, None)); - // ARC calls with null are no-ops. Delete them. - if (IsNullOrUndef(Arg)) { - Changed = true; - ++NumNoops; - LLVM_DEBUG(dbgs() << "ARC calls with null are no-ops. Erasing: " << *Inst - << "\n"); - EraseInstruction(Inst); - continue; + LLVM_DEBUG(dbgs() << "Replacing autorelease{,RV}(x) with objc_release(x) " + "since x is otherwise unused.\nOld: " + << *Call << "\nNew: " << *NewCall << "\n"); + + EraseInstruction(Call); + Inst = NewCall; + Class = ARCInstKind::Release; } + } - // Keep track of which of retain, release, autorelease, and retain_block - // are actually present in this function. + // For functions which can never be passed stack arguments, add + // a tail keyword. + if (IsAlwaysTail(Class) && !cast(Inst)->isNoTailCall()) { + Changed = true; + LLVM_DEBUG( + dbgs() << "Adding tail keyword to function since it can never be " + "passed stack args: " + << *Inst << "\n"); + cast(Inst)->setTailCall(); + } + + // Ensure that functions that can never have a "tail" keyword due to the + // semantics of ARC truly do not do so. + if (IsNeverTail(Class)) { + Changed = true; + LLVM_DEBUG(dbgs() << "Removing tail keyword from function: " << *Inst + << "\n"); + cast(Inst)->setTailCall(false); + } + + // Set nounwind as needed. + if (IsNoThrow(Class)) { + Changed = true; + LLVM_DEBUG(dbgs() << "Found no throw class. Setting nounwind on: " << *Inst + << "\n"); + cast(Inst)->setDoesNotThrow(); + } + + // Note: This catches instructions unrelated to ARC. + if (!IsNoopOnNull(Class)) { UsedInThisFunction |= 1 << unsigned(Class); + return; + } + + // If we haven't already looked up the root, look it up now. + if (!Arg) + Arg = GetArgRCIdentityRoot(Inst); + + // ARC calls with null are no-ops. Delete them. + if (IsNullOrUndef(Arg)) { + Changed = true; + ++NumNoops; + LLVM_DEBUG(dbgs() << "ARC calls with null are no-ops. Erasing: " << *Inst + << "\n"); + EraseInstruction(Inst); + return; + } + + // Keep track of which of retain, release, autorelease, and retain_block + // are actually present in this function. + UsedInThisFunction |= 1 << unsigned(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, and the call + // is not a release that doesn't have the clang.imprecise_release tag, 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. + if (Class == ARCInstKind::Release && + !Inst->getMetadata(MDKindCache.get(ARCMDKindID::ImpreciseRelease))) + return; + + 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; - // 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, and the call - // is not a release that doesn't have the clang.imprecise_release tag, 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. - if (Class == ARCInstKind::Release && - !Inst->getMetadata(MDKindCache.get(ARCMDKindID::ImpreciseRelease))) + const PHINode *PN = dyn_cast(Arg); + if (!PN) continue; - 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 = - GetRCIdentityRoot(PN->getIncomingValue(i)); - if (IsNullOrUndef(Incoming)) - HasNull = true; - else if (PN->getIncomingBlock(i)->getTerminator()->getNumSuccessors() != - 1) { - HasCriticalEdges = true; - break; - } + // 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 = GetRCIdentityRoot(PN->getIncomingValue(i)); + if (IsNullOrUndef(Incoming)) + HasNull = true; + else if (PN->getIncomingBlock(i)->getTerminator()->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. - switch (Class) { - case ARCInstKind::Retain: - case ARCInstKind::RetainBlock: - // These can always be moved up. - break; - case ARCInstKind::Release: - // These can't be moved across things that care about the retain - // count. - FindDependencies(NeedsPositiveRetainCount, Arg, - Inst->getParent(), Inst, - DependingInstructions, Visited, PA); - break; - case ARCInstKind::Autorelease: - // These can't be moved across autorelease pool scope boundaries. - FindDependencies(AutoreleasePoolBoundary, Arg, - Inst->getParent(), Inst, - DependingInstructions, Visited, PA); - break; - case ARCInstKind::ClaimRV: - case ARCInstKind::RetainRV: - case ARCInstKind::AutoreleaseRV: - // Don't move these; the RV optimization depends on the autoreleaseRV - // being tail called, and the retainRV being immediately after a call - // (which might still happen if we get lucky with codegen layout, but - // it's not worth taking the chance). - continue; - default: - llvm_unreachable("Invalid dependence flavor"); - } + } + // If we have null operands and no critical edges, optimize. + if (HasCriticalEdges) + continue; + if (!HasNull) + continue; - 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); - Type *ParamTy = CInst->getArgOperand(0)->getType(); - for (unsigned i = 0, e = PN->getNumIncomingValues(); i != e; ++i) { - Value *Incoming = - GetRCIdentityRoot(PN->getIncomingValue(i)); - if (!IsNullOrUndef(Incoming)) { - Value *Op = PN->getIncomingValue(i); - Instruction *InsertPos = &PN->getIncomingBlock(i)->back(); - CallInst *Clone = cast(CloneCallInstForBB( - *CInst, *InsertPos->getParent(), BlockColors)); - if (Op->getType() != ParamTy) - Op = new BitCastInst(Op, ParamTy, "", InsertPos); - Clone->setArgOperand(0, Op); - Clone->insertBefore(InsertPos); - - LLVM_DEBUG(dbgs() << "Cloning " << *CInst - << "\n" - "And inserting clone at " - << *InsertPos << "\n"); - Worklist.push_back(std::make_pair(Clone, Incoming)); - } - } - // Erase the original call. - LLVM_DEBUG(dbgs() << "Erasing: " << *CInst << "\n"); - EraseInstruction(CInst); - continue; - } - } - } while (!Worklist.empty()); - } + SmallPtrSet DependingInstructions; + SmallPtrSet Visited; + + // Check that there is nothing that cares about the reference + // count between the call and the phi. + switch (Class) { + case ARCInstKind::Retain: + case ARCInstKind::RetainBlock: + // These can always be moved up. + break; + case ARCInstKind::Release: + // These can't be moved across things that care about the retain + // count. + FindDependencies(NeedsPositiveRetainCount, Arg, Inst->getParent(), Inst, + DependingInstructions, Visited, PA); + break; + case ARCInstKind::Autorelease: + // These can't be moved across autorelease pool scope boundaries. + FindDependencies(AutoreleasePoolBoundary, Arg, Inst->getParent(), Inst, + DependingInstructions, Visited, PA); + break; + case ARCInstKind::ClaimRV: + case ARCInstKind::RetainRV: + case ARCInstKind::AutoreleaseRV: + // Don't move these; the RV optimization depends on the autoreleaseRV + // being tail called, and the retainRV being immediately after a call + // (which might still happen if we get lucky with codegen layout, but + // it's not worth taking the chance). + continue; + default: + llvm_unreachable("Invalid dependence flavor"); + } + + if (DependingInstructions.size() != 1) + continue; + if (*DependingInstructions.begin() != PN) + continue; + + Changed = true; + ++NumPartialNoops; + // Clone the call into each predecessor that has a non-null value. + CallInst *CInst = cast(Inst); + Type *ParamTy = CInst->getArgOperand(0)->getType(); + for (unsigned i = 0, e = PN->getNumIncomingValues(); i != e; ++i) { + Value *Incoming = GetRCIdentityRoot(PN->getIncomingValue(i)); + if (IsNullOrUndef(Incoming)) + continue; + Value *Op = PN->getIncomingValue(i); + Instruction *InsertPos = &PN->getIncomingBlock(i)->back(); + CallInst *Clone = cast( + CloneCallInstForBB(*CInst, *InsertPos->getParent(), BlockColors)); + if (Op->getType() != ParamTy) + Op = new BitCastInst(Op, ParamTy, "", InsertPos); + Clone->setArgOperand(0, Op); + Clone->insertBefore(InsertPos); + + LLVM_DEBUG(dbgs() << "Cloning " << *CInst << "\n" + "And inserting clone at " + << *InsertPos << "\n"); + Worklist.push_back(std::make_pair(Clone, Incoming)); + } + // Erase the original call. + LLVM_DEBUG(dbgs() << "Erasing: " << *CInst << "\n"); + EraseInstruction(CInst); + } while (!Worklist.empty()); } /// If we have a top down pointer in the S_Use state, make sure that there are diff --git a/llvm/test/Transforms/ObjCARC/inlined-autorelease-return-value.ll b/llvm/test/Transforms/ObjCARC/inlined-autorelease-return-value.ll new file mode 100644 index 0000000000000..84d33193ece68 --- /dev/null +++ b/llvm/test/Transforms/ObjCARC/inlined-autorelease-return-value.ll @@ -0,0 +1,292 @@ +; RUN: opt -basicaa -objc-arc -S < %s | FileCheck %s + +target datalayout = "e-p:64:64:64" + +declare i8* @llvm.objc.retain(i8*) +declare i8* @llvm.objc.autoreleaseReturnValue(i8*) +declare i8* @llvm.objc.retainAutoreleasedReturnValue(i8*) +declare i8* @llvm.objc.unsafeClaimAutoreleasedReturnValue(i8*) +declare void @opaque() +declare void @llvm.lifetime.start(i64, i8* nocapture) +declare void @llvm.lifetime.end(i64, i8* nocapture) + +; CHECK-LABEL: define i8* @elide_with_retainRV( +; CHECK-NEXT: entry: +; CHECK-NEXT: ret i8* %x +define i8* @elide_with_retainRV(i8* %x) nounwind { +entry: + %b = call i8* @llvm.objc.autoreleaseReturnValue(i8* %x) nounwind + %c = call i8* @llvm.objc.retainAutoreleasedReturnValue(i8* %b) nounwind + ret i8* %c +} + +; CHECK-LABEL: define i8* @elide_with_retainRV_bitcast( +; CHECK-NEXT: entry: +; CHECK-NEXT: %c = bitcast i32* %x to i8* +; CHECK-NEXT: ret i8* %c +define i8* @elide_with_retainRV_bitcast(i32* %x) nounwind { +entry: + %a = bitcast i32* %x to i8* + %b = call i8* @llvm.objc.autoreleaseReturnValue(i8* %a) nounwind + %c = bitcast i32* %x to i8* + %d = call i8* @llvm.objc.retainAutoreleasedReturnValue(i8* %c) nounwind + ret i8* %d +} + +; CHECK-LABEL: define i8* @elide_with_retainRV_phi( +; CHECK-NOT: define +; CHECK: phis: +; CHECK-NEXT: phi i8* +; CHECK-NEXT: ret i8* +define i8* @elide_with_retainRV_phi(i8* %x) nounwind { +entry: + br label %phis + +phis: + %a = phi i8* [ %x, %entry ] + %c = phi i8* [ %x, %entry ] + %b = call i8* @llvm.objc.autoreleaseReturnValue(i8* %a) nounwind + %d = call i8* @llvm.objc.retainAutoreleasedReturnValue(i8* %c) nounwind + ret i8* %d +} + +; CHECK-LABEL: define i8* @elide_with_retainRV_splitByRetain( +; CHECK-NEXT: entry: +; CHECK-NEXT: %b = call i8* @llvm.objc.autorelease(i8* %x) +; CHECK-NEXT: tail call i8* @llvm.objc.retain(i8* %x) +; CHECK-NEXT: tail call i8* @llvm.objc.retain(i8* %b) +define i8* @elide_with_retainRV_splitByRetain(i8* %x) nounwind { +entry: + ; Cleanup is blocked by other ARC intrinsics for ease of implementation; we + ; only delay processing AutoreleaseRV until the very next ARC intrinsic. In + ; practice, it would be very strange for this to matter. + %b = call i8* @llvm.objc.autoreleaseReturnValue(i8* %x) nounwind + %c = call i8* @llvm.objc.retain(i8* %x) nounwind + %d = call i8* @llvm.objc.retainAutoreleasedReturnValue(i8* %b) nounwind + ret i8* %d +} + +; CHECK-LABEL: define i8* @elide_with_retainRV_splitByOpaque( +; CHECK-NEXT: entry: +; CHECK-NEXT: %b = call i8* @llvm.objc.autorelease(i8* %x) +; CHECK-NEXT: call void @opaque() +; CHECK-NEXT: %d = tail call i8* @llvm.objc.retain(i8* %b) +; CHECK-NEXT: ret i8* %d +define i8* @elide_with_retainRV_splitByOpaque(i8* %x) nounwind { +entry: + ; Cleanup should get blocked by opaque calls. + %b = call i8* @llvm.objc.autoreleaseReturnValue(i8* %x) nounwind + call void @opaque() nounwind + %d = call i8* @llvm.objc.retainAutoreleasedReturnValue(i8* %b) nounwind + ret i8* %d +} + +; CHECK-LABEL: define i8* @elide_with_retainRV_splitByLifetime( +; CHECK-NEXT: entry: +; CHECK-NEXT: call void @llvm.lifetime.start.p0i8(i64 8, i8* %x) +; CHECK-NEXT: call void @llvm.lifetime.end.p0i8(i64 8, i8* %x) +; CHECK-NEXT: ret i8* %x +define i8* @elide_with_retainRV_splitByLifetime(i8* %x) nounwind { +entry: + ; Cleanup should skip over lifetime intrinsics. + call void @llvm.lifetime.start(i64 8, i8* %x) + %b = call i8* @llvm.objc.autoreleaseReturnValue(i8* %x) nounwind + call void @llvm.lifetime.end(i64 8, i8* %x) + %d = call i8* @llvm.objc.retainAutoreleasedReturnValue(i8* %b) nounwind + ret i8* %d +} + +; CHECK-LABEL: define i8* @elide_with_retainRV_wrongArg( +; CHECK-NEXT: entry: +; CHECK-NEXT: call void @llvm.objc.release(i8* %x) +; CHECK-NEXT: tail call i8* @llvm.objc.retain(i8* %y) +define i8* @elide_with_retainRV_wrongArg(i8* %x, i8* %y) nounwind { +entry: + %b = call i8* @llvm.objc.autoreleaseReturnValue(i8* %x) nounwind + %c = call i8* @llvm.objc.retainAutoreleasedReturnValue(i8* %y) nounwind + ret i8* %c +} + +; CHECK-LABEL: define i8* @elide_with_retainRV_wrongBB( +; CHECK-NEXT: entry: +; CHECK-NEXT: call i8* @llvm.objc.autorelease(i8* %x) +; CHECK-NEXT: br label %next +; CHECK: next: +; CHECK-NEXT: tail call i8* @llvm.objc.retain( +; CHECK-NEXT: ret i8* +define i8* @elide_with_retainRV_wrongBB(i8* %x) nounwind { +entry: + %b = call i8* @llvm.objc.autoreleaseReturnValue(i8* %x) nounwind + br label %next + +next: + %c = call i8* @llvm.objc.retainAutoreleasedReturnValue(i8* %b) nounwind + ret i8* %c +} + +; CHECK-LABEL: define i8* @elide_with_retainRV_beforeAutoreleaseRV( +; CHECK-NEXT: entry: +; CHECK-NEXT: tail call i8* @llvm.objc.autoreleaseReturnValue(i8* %x) +; CHECK-NEXT: ret i8* %x +define i8* @elide_with_retainRV_beforeAutoreleaseRV(i8* %x) nounwind { +entry: + %b = call i8* @llvm.objc.autoreleaseReturnValue(i8* %x) nounwind + %c = call i8* @llvm.objc.retainAutoreleasedReturnValue(i8* %b) nounwind + %d = call i8* @llvm.objc.autoreleaseReturnValue(i8* %c) nounwind + ret i8* %c +} + +; CHECK-LABEL: define i8* @elide_with_retainRV_afterRetain( +; CHECK-NEXT: entry: +; CHECK-NEXT: tail call i8* @llvm.objc.retain(i8* %x) +; CHECK-NEXT: ret i8* %a +define i8* @elide_with_retainRV_afterRetain(i8* %x) nounwind { +entry: + %a = call i8* @llvm.objc.retain(i8* %x) nounwind + %b = call i8* @llvm.objc.autoreleaseReturnValue(i8* %a) nounwind + %c = call i8* @llvm.objc.retainAutoreleasedReturnValue(i8* %b) nounwind + ret i8* %c +} + +; CHECK-LABEL: define i8* @elide_with_claimRV( +; CHECK-NEXT: entry: +; CHECK-NEXT: tail call void @llvm.objc.release(i8* %x) +; CHECK-NEXT: ret i8* %x +define i8* @elide_with_claimRV(i8* %x) nounwind { +entry: + %b = call i8* @llvm.objc.autoreleaseReturnValue(i8* %x) nounwind + %c = call i8* @llvm.objc.unsafeClaimAutoreleasedReturnValue(i8* %b) nounwind + ret i8* %c +} + +; CHECK-LABEL: define i8* @elide_with_claimRV_bitcast( +; CHECK-NEXT: entry: +; CHECK-NEXT: %c = bitcast i32* %x to i8* +; CHECK-NEXT: tail call void @llvm.objc.release(i8* %c) +; CHECK-NEXT: ret i8* %c +define i8* @elide_with_claimRV_bitcast(i32* %x) nounwind { +entry: + %a = bitcast i32* %x to i8* + %b = call i8* @llvm.objc.autoreleaseReturnValue(i8* %a) nounwind + %c = bitcast i32* %x to i8* + %d = call i8* @llvm.objc.unsafeClaimAutoreleasedReturnValue(i8* %c) nounwind + ret i8* %d +} + +; CHECK-LABEL: define i8* @elide_with_claimRV_phi( +; CHECK-NOT: define +; CHECK: phis: +; CHECK-NEXT: %c = phi i8* +; CHECK-NEXT: tail call void @llvm.objc.release(i8* %c) +; CHECK-NEXT: ret i8* %c +define i8* @elide_with_claimRV_phi(i8* %x) nounwind { +entry: + br label %phis + +phis: + %a = phi i8* [ %x, %entry ] + %c = phi i8* [ %x, %entry ] + %b = call i8* @llvm.objc.autoreleaseReturnValue(i8* %a) nounwind + %d = call i8* @llvm.objc.unsafeClaimAutoreleasedReturnValue(i8* %c) nounwind + ret i8* %d +} + +; CHECK-LABEL: define i8* @elide_with_claimRV_splitByRetain( +; CHECK-NEXT: entry: +; CHECK-NEXT: %b = call i8* @llvm.objc.autorelease(i8* %x) +; CHECK-NEXT: tail call i8* @llvm.objc.retain(i8* %x) +; CHECK-NEXT: tail call i8* @llvm.objc.unsafeClaimAutoreleasedReturnValue(i8* %b) +define i8* @elide_with_claimRV_splitByRetain(i8* %x) nounwind { +entry: + ; Cleanup is blocked by other ARC intrinsics for ease of implementation; we + ; only delay processing AutoreleaseRV until the very next ARC intrinsic. In + ; practice, it would be very strange for this to matter. + %b = call i8* @llvm.objc.autoreleaseReturnValue(i8* %x) nounwind + %c = call i8* @llvm.objc.retain(i8* %x) nounwind + %d = call i8* @llvm.objc.unsafeClaimAutoreleasedReturnValue(i8* %b) nounwind + ret i8* %d +} + +; CHECK-LABEL: define i8* @elide_with_claimRV_splitByOpaque( +; CHECK-NEXT: entry: +; CHECK-NEXT: %b = call i8* @llvm.objc.autorelease(i8* %x) +; CHECK-NEXT: call void @opaque() +; CHECK-NEXT: %d = tail call i8* @llvm.objc.unsafeClaimAutoreleasedReturnValue(i8* %b) +; CHECK-NEXT: ret i8* %d +define i8* @elide_with_claimRV_splitByOpaque(i8* %x) nounwind { +entry: + ; Cleanup should get blocked by opaque calls. + %b = call i8* @llvm.objc.autoreleaseReturnValue(i8* %x) nounwind + call void @opaque() nounwind + %d = call i8* @llvm.objc.unsafeClaimAutoreleasedReturnValue(i8* %b) nounwind + ret i8* %d +} + +; CHECK-LABEL: define i8* @elide_with_claimRV_splitByLifetime( +; CHECK-NEXT: entry: +; CHECK-NEXT: call void @llvm.lifetime.start.p0i8(i64 8, i8* %x) +; CHECK-NEXT: call void @llvm.lifetime.end.p0i8(i64 8, i8* %x) +; CHECK-NEXT: tail call void @llvm.objc.release(i8* %x) +; CHECK-NEXT: ret i8* %x +define i8* @elide_with_claimRV_splitByLifetime(i8* %x) nounwind { +entry: + ; Cleanup should skip over lifetime intrinsics. + call void @llvm.lifetime.start(i64 8, i8* %x) + %b = call i8* @llvm.objc.autoreleaseReturnValue(i8* %x) nounwind + call void @llvm.lifetime.end(i64 8, i8* %x) + %d = call i8* @llvm.objc.unsafeClaimAutoreleasedReturnValue(i8* %b) nounwind + ret i8* %d +} + +; CHECK-LABEL: define i8* @elide_with_claimRV_wrongArg( +; CHECK-NEXT: entry: +; CHECK-NEXT: call void @llvm.objc.release(i8* %x) +; CHECK-NEXT: tail call i8* @llvm.objc.unsafeClaimAutoreleasedReturnValue(i8* %y) +define i8* @elide_with_claimRV_wrongArg(i8* %x, i8* %y) nounwind { +entry: + %b = call i8* @llvm.objc.autoreleaseReturnValue(i8* %x) nounwind + %c = call i8* @llvm.objc.unsafeClaimAutoreleasedReturnValue(i8* %y) nounwind + ret i8* %c +} + +; CHECK-LABEL: define i8* @elide_with_claimRV_wrongBB( +; CHECK-NEXT: entry: +; CHECK-NEXT: call i8* @llvm.objc.autorelease(i8* %x) +; CHECK-NEXT: br label %next +; CHECK: next: +; CHECK-NEXT: tail call i8* @llvm.objc.unsafeClaimAutoreleasedReturnValue( +; CHECK-NEXT: ret i8* +define i8* @elide_with_claimRV_wrongBB(i8* %x) nounwind { +entry: + %b = call i8* @llvm.objc.autoreleaseReturnValue(i8* %x) nounwind + br label %next + +next: + %c = call i8* @llvm.objc.unsafeClaimAutoreleasedReturnValue(i8* %b) nounwind + ret i8* %c +} + + +; CHECK-LABEL: define i8* @elide_with_claimRV_beforeAutoreleaseRV( +; CHECK-NEXT: entry: +; CHECK-NEXT: tail call void @llvm.objc.release(i8* %x) +; CHECK-NEXT: tail call i8* @llvm.objc.autoreleaseReturnValue(i8* %x) +; CHECK-NEXT: ret i8* %x +define i8* @elide_with_claimRV_beforeAutoreleaseRV(i8* %x) nounwind { +entry: + %b = call i8* @llvm.objc.autoreleaseReturnValue(i8* %x) nounwind + %c = call i8* @llvm.objc.unsafeClaimAutoreleasedReturnValue(i8* %b) nounwind + %d = call i8* @llvm.objc.autoreleaseReturnValue(i8* %c) nounwind + ret i8* %c +} + +; CHECK-LABEL: define i8* @elide_with_claimRV_afterRetain( +; CHECK-NEXT: entry: +; CHECK-NEXT: ret i8* %x +define i8* @elide_with_claimRV_afterRetain(i8* %x) nounwind { +entry: + %a = call i8* @llvm.objc.retain(i8* %x) nounwind + %b = call i8* @llvm.objc.autoreleaseReturnValue(i8* %a) nounwind + %c = call i8* @llvm.objc.unsafeClaimAutoreleasedReturnValue(i8* %b) nounwind + ret i8* %c +} diff --git a/llvm/test/Transforms/ObjCARC/unsafe-claim-rv.ll b/llvm/test/Transforms/ObjCARC/unsafe-claim-rv.ll index 8b64802565521..a76a792952899 100644 --- a/llvm/test/Transforms/ObjCARC/unsafe-claim-rv.ll +++ b/llvm/test/Transforms/ObjCARC/unsafe-claim-rv.ll @@ -40,8 +40,7 @@ if.end: ; preds = %if.then, %entry ; CHECK: if.then ; CHECK: tail call i8* @llvm.objc.retain -; CHECK-NEXT: call i8* @llvm.objc.autorelease ; CHECK: %Y.0 = phi -; CHECK-NEXT: tail call i8* @llvm.objc.unsafeClaimAutoreleasedReturnValue(i8* %Y.0) +; CHECK-NEXT: tail call void @llvm.objc.release ; CHECK-NEXT: tail call void @llvm.objc.release