From: Akira Hatanaka Date: Sat, 29 Apr 2017 00:23:11 +0000 (+0000) Subject: [ObjCARC] Do not move a release between a call and a X-Git-Url: https://granicus.if.org/sourcecode?a=commitdiff_plain;h=e85d77f956b243ef05803e30d9694cbee1f2dde8;p=llvm [ObjCARC] Do not move a release between a call and a retainAutoreleasedReturnValue that retains the returned value. This commit fixes a bug in ARC optimizer where it moves a release between a call and a retainAutoreleasedReturnValue, causing the returned object to be released before the retainAutoreleasedReturnValue can retain it. This commit accomplishes that by doing a lookahead and checking whether the call prevents the release from moving upwards. In the long term, we should treat the region between the retainAutoreleasedReturnValue and the call as a critical section and disallow moving anything there (possibly using operand bundles). rdar://problem/20449878 git-svn-id: https://llvm.org/svn/llvm-project/llvm/trunk@301724 91177308-0d34-0410-b5e6-96231b3b80d8 --- diff --git a/lib/Transforms/ObjCARC/ObjCARC.h b/lib/Transforms/ObjCARC/ObjCARC.h index f02b75f0b45..cd9b3d96a14 100644 --- a/lib/Transforms/ObjCARC/ObjCARC.h +++ b/lib/Transforms/ObjCARC/ObjCARC.h @@ -69,6 +69,19 @@ static inline void EraseInstruction(Instruction *CI) { RecursivelyDeleteTriviallyDeadInstructions(OldArg); } +/// If Inst is a ReturnRV and its operand is a call or invoke, return the +/// operand. Otherwise return null. +static inline const Instruction *getreturnRVOperand(const Instruction &Inst, + ARCInstKind Class) { + if (Class != ARCInstKind::RetainRV) + return nullptr; + + const auto *Opnd = Inst.getOperand(0)->stripPointerCasts(); + if (const auto *C = dyn_cast(Opnd)) + return C; + return dyn_cast(Opnd); +} + } // end namespace objcarc } // end namespace llvm diff --git a/lib/Transforms/ObjCARC/PtrState.cpp b/lib/Transforms/ObjCARC/PtrState.cpp index c1bbc4e96b1..d13e941044f 100644 --- a/lib/Transforms/ObjCARC/PtrState.cpp +++ b/lib/Transforms/ObjCARC/PtrState.cpp @@ -244,6 +244,18 @@ void BottomUpPtrState::HandlePotentialUse(BasicBlock *BB, Instruction *Inst, const Value *Ptr, ProvenanceAnalysis &PA, ARCInstKind Class) { + auto SetSeqAndInsertReverseInsertPt = [&](Sequence NewSeq){ + assert(!HasReverseInsertPts()); + SetSeq(NewSeq); + // If this is an invoke instruction, we're scanning it as part of + // one of its successor blocks, since we can't insert code after it + // in its own block, and we don't want to split critical edges. + if (isa(Inst)) + InsertReverseInsertPt(&*BB->getFirstInsertionPt()); + else + InsertReverseInsertPt(&*++Inst->getIterator()); + }; + // Check for possible direct uses. switch (GetSeq()) { case S_Release: @@ -251,26 +263,18 @@ void BottomUpPtrState::HandlePotentialUse(BasicBlock *BB, Instruction *Inst, if (CanUse(Inst, Ptr, PA, Class)) { DEBUG(dbgs() << " CanUse: Seq: " << GetSeq() << "; " << *Ptr << "\n"); - assert(!HasReverseInsertPts()); - // If this is an invoke instruction, we're scanning it as part of - // one of its successor blocks, since we can't insert code after it - // in its own block, and we don't want to split critical edges. - if (isa(Inst)) - InsertReverseInsertPt(&*BB->getFirstInsertionPt()); - else - InsertReverseInsertPt(&*++Inst->getIterator()); - SetSeq(S_Use); + SetSeqAndInsertReverseInsertPt(S_Use); } else if (Seq == S_Release && IsUser(Class)) { DEBUG(dbgs() << " PreciseReleaseUse: Seq: " << GetSeq() << "; " << *Ptr << "\n"); // Non-movable releases depend on any possible objc pointer use. - SetSeq(S_Stop); - assert(!HasReverseInsertPts()); - // As above; handle invoke specially. - if (isa(Inst)) - InsertReverseInsertPt(&*BB->getFirstInsertionPt()); - else - InsertReverseInsertPt(&*++Inst->getIterator()); + SetSeqAndInsertReverseInsertPt(S_Stop); + } else if (const auto *Call = getreturnRVOperand(*Inst, Class)) { + if (CanUse(Call, Ptr, PA, GetBasicARCInstKind(Call))) { + DEBUG(dbgs() << " ReleaseUse: Seq: " << GetSeq() << "; " + << *Ptr << "\n"); + SetSeqAndInsertReverseInsertPt(S_Stop); + } } break; case S_Stop: diff --git a/test/Transforms/ObjCARC/rv.ll b/test/Transforms/ObjCARC/rv.ll index 85a16127c6d..e99ba92dc45 100644 --- a/test/Transforms/ObjCARC/rv.ll +++ b/test/Transforms/ObjCARC/rv.ll @@ -291,4 +291,29 @@ define {}* @test24(i8* %p) { ret {}* %s } +declare i8* @first_test25(); +declare i8* @second_test25(i8*); +declare void @somecall_test25(); + +; ARC optimizer used to move the last release between the call to second_test25 +; and the call to objc_retainAutoreleasedReturnValue, causing %second to be +; released prematurely when %first and %second were pointing to the same object. + +; CHECK-LABEL: define void @test25( +; CHECK: %[[CALL1:.*]] = call i8* @second_test25( +; CHECK-NEXT: tail call i8* @objc_retainAutoreleasedReturnValue(i8* %[[CALL1]]) + +define void @test25() { + %first = call i8* @first_test25() + %v0 = call i8* @objc_retain(i8* %first) + call void @somecall_test25() + %second = call i8* @second_test25(i8* %first) + %call2 = call i8* @objc_retainAutoreleasedReturnValue(i8* %second) + call void @objc_release(i8* %second), !clang.imprecise_release !0 + call void @objc_release(i8* %first), !clang.imprecise_release !0 + ret void +} + +!0 = !{} + ; CHECK: attributes [[NUW]] = { nounwind }