From 669812befd5fdb449b4efd9e6da51bf748b741bc Mon Sep 17 00:00:00 2001 From: "Duncan P. N. Exon Smith" Date: Sun, 17 Nov 2019 16:29:44 -0800 Subject: [PATCH 1/3] llvm/ObjCARC: Use continue to reduce some nesting, NFC (cherry picked from commit a937a588dd29fa77018561e2eab5dc760c766229) --- llvm/lib/Transforms/ObjCARC/ObjCARCOpts.cpp | 132 ++++++++++---------- 1 file changed, 66 insertions(+), 66 deletions(-) diff --git a/llvm/lib/Transforms/ObjCARC/ObjCARCOpts.cpp b/llvm/lib/Transforms/ObjCARC/ObjCARCOpts.cpp index 87d6bed6b1bdb..7ebde8611f3f6 100644 --- a/llvm/lib/Transforms/ObjCARC/ObjCARCOpts.cpp +++ b/llvm/lib/Transforms/ObjCARC/ObjCARCOpts.cpp @@ -988,75 +988,75 @@ void ObjCARCOpt::OptimizeIndividualCalls(Function &F) { } } // 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 (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); + 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()); } } From 9eda4238fa289678177318106a4a14fe1932cac0 Mon Sep 17 00:00:00 2001 From: "Duncan P. N. Exon Smith" Date: Sun, 17 Nov 2019 16:51:34 -0800 Subject: [PATCH 2/3] llvm/ObjCARC: Split OptimizeIndividualCallImpl out of OptimizeIndividualCalls, NFC Split out a helper function for the individual call optimizations and skip useless calls to it (where the instruction is not an ARC intrinsic). Besides reducing indentation (and possibly speeding up compile time in some small way), an upcoming patch will add additional calls and expand out the `switch`. (cherry picked from commit 783cb86b616d9de59213ea17649d6e2df8c1ebbb) --- llvm/lib/Transforms/ObjCARC/ObjCARCOpts.cpp | 509 ++++++++++---------- 1 file changed, 264 insertions(+), 245 deletions(-) diff --git a/llvm/lib/Transforms/ObjCARC/ObjCARCOpts.cpp b/llvm/lib/Transforms/ObjCARC/ObjCARCOpts.cpp index 7ebde8611f3f6..085f30d53bf1d 100644 --- a/llvm/lib/Transforms/ObjCARC/ObjCARCOpts.cpp +++ b/llvm/lib/Transforms/ObjCARC/ObjCARCOpts.cpp @@ -505,6 +505,10 @@ namespace { void OptimizeAutoreleaseRVCall(Function &F, Instruction *AutoreleaseRV, ARCInstKind &Class); void OptimizeIndividualCalls(Function &F); + void + OptimizeIndividualCallImpl(Function &F, + DenseMap &BlockColors, + Instruction *Inst, ARCInstKind Class); void CheckForCFGHazards(const BasicBlock *BB, DenseMap &BBStates, @@ -787,278 +791,293 @@ void ObjCARCOpt::OptimizeIndividualCalls(Function &F) { 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. 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; - break; - case ARCInstKind::AutoreleaseRV: - OptimizeAutoreleaseRVCall(F, Inst, Class); + default: break; + case ARCInstKind::CallOrUser: + case ARCInstKind::User: + case ARCInstKind::None: + continue; } - // 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); + } +} + +void ObjCARCOpt::OptimizeIndividualCallImpl( + Function &F, DenseMap &BlockColors, + Instruction *Inst, ARCInstKind Class) { + // 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; } + } + + // 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(); + } - // Keep track of which of retain, release, autorelease, and retain_block - // are actually present in this function. + // 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 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))) - continue; + const Value *Arg = GetArgRCIdentityRoot(Inst); - 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; - } - } - // If we have null operands and no critical edges, optimize. - if (HasCriticalEdges) - continue; - if (!HasNull) - continue; + // 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; - SmallPtrSet DependingInstructions; - SmallPtrSet Visited; + 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; - // 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); + 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; - 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) - continue; - if (*DependingInstructions.begin() != PN) - continue; + SmallPtrSet DependingInstructions; + SmallPtrSet Visited; - 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()); - } + // 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 From 5da777193445ebb80415464eccd4c2715214f998 Mon Sep 17 00:00:00 2001 From: "Duncan P. N. Exon Smith" Date: Sun, 17 Nov 2019 17:58:01 -0800 Subject: [PATCH 3/3] llvm/ObjCARC: Eliminate inlined AutoreleaseRV calls Pair up inlined AutoreleaseRV calls with their matching RetainRV or ClaimRV. - RetainRV cancels out AutoreleaseRV. Delete both instructions. - ClaimRV is a peephole for RetainRV+Release. Delete AutoreleaseRV and replace ClaimRV with Release. This avoids problems where more aggressive inlining triggers memory regressions. This patch is happy to skip over non-callable instructions and non-ARC intrinsics looking for the pair. It is likely sound to also skip over opaque function calls, but that's harder to reason about, and it's not relevant to the goal here: if there's an opaque function call splitting up a pair, it's very unlikely that a handshake would have happened dynamically without inlining. Note that this patch also subsumes the previous logic that looked backwards from ReleaseRV. https://reviews.llvm.org/D70370 rdar://problem/46509586 (cherry picked from commit 3279724905c14a8db383ade53af40a0dd49504d8) --- llvm/lib/Transforms/ObjCARC/ObjCARCOpts.cpp | 216 ++++++++----- .../inlined-autorelease-return-value.ll | 292 ++++++++++++++++++ .../Transforms/ObjCARC/unsafe-claim-rv.ll | 3 +- 3 files changed, 438 insertions(+), 73 deletions(-) create mode 100644 llvm/test/Transforms/ObjCARC/inlined-autorelease-return-value.ll diff --git a/llvm/lib/Transforms/ObjCARC/ObjCARCOpts.cpp b/llvm/lib/Transforms/ObjCARC/ObjCARCOpts.cpp index 085f30d53bf1d..5134ffb5476af 100644 --- a/llvm/lib/Transforms/ObjCARC/ObjCARCOpts.cpp +++ b/llvm/lib/Transforms/ObjCARC/ObjCARCOpts.cpp @@ -505,10 +505,20 @@ namespace { void OptimizeAutoreleaseRVCall(Function &F, Instruction *AutoreleaseRV, ARCInstKind &Class); void OptimizeIndividualCalls(Function &F); - void - OptimizeIndividualCallImpl(Function &F, - DenseMap &BlockColors, - Instruction *Inst, ARCInstKind Class); + + /// 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, @@ -592,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. @@ -647,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; @@ -697,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, @@ -783,31 +788,98 @@ 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"); - // Skip this loop if this instruction isn't itself an ARC intrinsic. + const Value *Arg = nullptr; switch (Class) { 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: + 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; } - OptimizeIndividualCallImpl(F, BlockColors, Inst, Class); + OptimizeIndividualCallImpl(F, BlockColors, Inst, Class, Arg); } + + // Catch the final delayed AutoreleaseRV. + optimizeDelayedAutoreleaseRV(); } void ObjCARCOpt::OptimizeIndividualCallImpl( Function &F, DenseMap &BlockColors, - Instruction *Inst, ARCInstKind Class) { + 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)) { @@ -956,7 +1028,9 @@ void ObjCARCOpt::OptimizeIndividualCallImpl( return; } - const Value *Arg = GetArgRCIdentityRoot(Inst); + // 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)) { 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