Index: llvm/lib/Transforms/ObjCARC/ObjCARCOpts.cpp =================================================================== --- llvm/lib/Transforms/ObjCARC/ObjCARCOpts.cpp +++ llvm/lib/Transforms/ObjCARC/ObjCARCOpts.cpp @@ -507,10 +507,20 @@ 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, @@ -622,8 +632,7 @@ } /// 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. @@ -649,39 +658,6 @@ } } - // 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; @@ -699,6 +675,60 @@ 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"); + + // We don't need the RetainRV or ClaimRV anymore. + Inst->replaceAllUsesWith(cast(Inst)->getArgOperand(0)); + EraseInstruction(Inst); + if (Class == ARCInstKind::RetainRV) { + // Also delete the AutoreleaseRV. + AutoreleaseRV->replaceAllUsesWith( + cast(AutoreleaseRV)->getArgOperand(0)); + EraseInstruction(AutoreleaseRV); + return true; + } + + // Convert the AutoreleaseRV into a normal Release and run the normal + // optimizations. + assert(Class == ARCInstKind::ClaimRV); + Value *CallArg = cast(AutoreleaseRV)->getArgOperand(0); + CallInst *Release = CallInst::Create( + EP.get(ARCRuntimeEntryPointKind::Release), CallArg, "", AutoreleaseRV); + assert(IsAlwaysTail(ARCInstKind::AutoreleaseRV) && + "Expected AutoreleaseRV to be safe to tail call"); + Release->setTailCall(); + AutoreleaseRV->replaceAllUsesWith(CallArg); + EraseInstruction(AutoreleaseRV); + 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, @@ -785,31 +815,74 @@ 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); + }; + // 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: + // If we're delaying an AutoreleaseRV, check if it's safe to skip over + // this call. If not then optimize now. + if (DelayedAutoreleaseRV) + if (!isSafeBetweenRVCalls(Inst)) + optimizeDelayedAutoreleaseRV(); continue; + case ARCInstKind::AutoreleaseRV: + optimizeDelayedAutoreleaseRV(); + setDelayedAutoreleaseRV(Inst); + continue; + case ARCInstKind::RetainRV: + case ARCInstKind::ClaimRV: + if (DelayedAutoreleaseRV) + 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)) { @@ -958,7 +1031,9 @@ 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)) { Index: llvm/test/Transforms/ObjCARC/inlined-autorelease-return-value.ll =================================================================== --- /dev/null +++ llvm/test/Transforms/ObjCARC/inlined-autorelease-return-value.ll @@ -0,0 +1,294 @@ +; 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* + %c = bitcast i32* %x to i8* + %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_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: %a = bitcast i32* %x to i8* +; CHECK-NEXT: %c = bitcast i32* %x to i8* +; CHECK-NEXT: tail call void @llvm.objc.release(i8* %a) +; CHECK-NEXT: ret i8* %c +define i8* @elide_with_claimRV_bitcast(i32* %x) nounwind { +entry: + %a = bitcast i32* %x to i8* + %c = bitcast i32* %x to i8* + %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_phi( +; CHECK-NOT: define +; CHECK: phis: +; CHECK-NEXT: %a = phi i8* +; CHECK-NEXT: %c = phi i8* +; CHECK-NEXT: tail call void @llvm.objc.release(i8* %a) +; 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: tail call void @llvm.objc.release(i8* %x) +; CHECK-NEXT: call void @llvm.lifetime.end.p0i8(i64 8, 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 +} Index: llvm/test/Transforms/ObjCARC/unsafe-claim-rv.ll =================================================================== --- llvm/test/Transforms/ObjCARC/unsafe-claim-rv.ll +++ llvm/test/Transforms/ObjCARC/unsafe-claim-rv.ll @@ -40,8 +40,7 @@ ; 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