Tighten up the rules for precise lifetime and document

the requirements on the ARC optimizer.

rdar://13407451

llvm-svn: 176924
This commit is contained in:
John McCall 2013-03-13 03:10:54 +00:00
parent 0c61dee1b9
commit cdda29c968
11 changed files with 351 additions and 118 deletions

View File

@ -1330,27 +1330,179 @@ This is a new rule of the Objective-C language and applies outside of ARC.
Optimization
============
ARC applies aggressive rules for the optimization of local behavior. These
rules are based around a core assumption of :arc-term:`local balancing`: that
other code will perform retains and releases as necessary (and only as
necessary) for its own safety, and so the optimizer does not need to consider
global properties of the retain and release sequence. For example, if a retain
and release immediately bracket a call, the optimizer can delete the retain and
release on the assumption that the called function will not do a constant
number of unmotivated releases followed by a constant number of "balancing"
retains, such that the local retain/release pair is the only thing preventing
the called function from ending up with a dangling reference.
Within this section, the word :arc-term:`function` will be used to
refer to any structured unit of code, be it a C function, an
Objective-C method, or a block.
The optimizer assumes that when a new value enters local control, e.g. from a
load of a non-local object or as the result of a function call, it is
instaneously valid. Subsequently, a retain and release of a value are
necessary on a computation path only if there is a use of that value before the
release and after any operation which might cause a release of the value
(including indirectly or non-locally), and only if the value is not
demonstrably already retained.
This specification describes ARC as performing specific ``retain`` and
``release`` operations on retainable object pointers at specific
points during the execution of a program. These operations make up a
non-contiguous subsequence of the computation history of the program.
The portion of this sequence for a particular retainable object
pointer for which a specific function execution is directly
responsible is the :arc-term:`formal local retain history` of the
object pointer. The corresponding actual sequence executed is the
`dynamic local retain history`.
The complete optimization rules are quite complicated, but it would still be
useful to document them here.
However, under certain circumstances, ARC is permitted to re-order and
eliminate operations in a manner which may alter the overall
computation history beyond what is permitted by the general "as if"
rule of C/C++ and the :ref:`restrictions <_arc.objects.retains>` on
the implementation of ``retain`` and ``release``.
.. admonition:: Rationale
Specifically, ARC is sometimes permitted to optimize ``release``
operations in ways which might cause an object to be deallocated
before it would otherwise be. Without this, it would be almost
impossible to eliminate any ``retain``/``release`` pairs. For
example, consider the following code:
.. code-block:: objc
id x = _ivar;
[x foo];
If we were not permitted in any event to shorten the lifetime of the
object in ``x``, then we would not be able to eliminate this retain
and release unless we could prove that the message send could not
modify ``_ivar`` (or deallocate ``self``). Since message sends are
opaque to the optimizer, this is not possible, and so ARC's hands
would be almost completely tied.
ARC makes no guarantees about the execution of a computation history
which contains undefined behavior. In particular, ARC makes no
guarantees in the presence of race conditions.
ARC may assume that any retainable object pointers it receives or
generates are instantaneously valid from that point until a point
which, by the concurrency model of the host language, happens-after
the generation of the pointer and happens-before a release of that
object (possibly via an aliasing pointer or indirectly due to
destruction of a different object).
.. admonition:: Rationale
There is very little point in trying to guarantee correctness in the
presence of race conditions. ARC does not have a stack-scanning
garbage collector, and guaranteeing the atomicity of every load and
store operation would be prohibitive and preclude a vast amount of
optimization.
ARC may assume that non-ARC code engages in sensible balancing
behavior and does not rely on exact or minimum retain count values
except as guaranteed by ``__strong`` object invariants or +1 transfer
conventions. For example, if an object is provably double-retained
and double-released, ARC may eliminate the inner retain and release;
it does not need to guard against code which performs an unbalanced
release followed by a "balancing" retain.
.. _arc.optimization.liveness:
Object liveness
---------------
ARC may not allow a retainable object ``X`` to be deallocated at a
time ``T`` in a computation history if:
* ``X`` is the value stored in a ``__strong`` object ``S`` with
:ref:`precise lifetime semantics <arc.optimization.precise>`, or
* ``X`` is the value stored in a ``__strong`` object ``S`` with
imprecise lifetime semantics and, at some point after ``T`` but
before the next store to ``S``, the computation history features a
load from ``S`` and in some way depends on the value loaded, or
* ``X`` is a value described as being released at the end of the
current full-expression and, at some point after ``T`` but before
the end of the full-expression, the computation history depends
on that value.
.. admonition:: Rationale
The intent of the second rule is to say that objects held in normal
``__strong`` local variables may be released as soon as the value in
the variable is no longer being used: either the variable stops
being used completely or a new value is stored in the variable.
The intent of the third rule is to say that return values may be
released after they've been used.
A computation history depends on a pointer value ``P`` if it:
* performs a pointer comparison with ``P``,
* loads from ``P``,
* stores to ``P``,
* depends on a pointer value ``Q`` derived via pointer arithmetic
from ``P`` (including an instance-variable or field access), or
* depends on a pointer value ``Q`` loaded from ``P``.
Dependency applies only to values derived directly or indirectly from
a particular expression result and does not occur merely because a
separate pointer value dynamically aliases ``P``. Furthermore, this
dependency is not carried by values that are stored to objects.
.. admonition:: Rationale
The restrictions on dependency are intended to make this analysis
feasible by an optimizer with only incomplete information about a
program. Essentially, dependence is carried to "obvious" uses of a
pointer. Merely passing a pointer argument to a function does not
itself cause dependence, but since generally the optimizer will not
be able to prove that the function doesn't depend on that parameter,
it will be forced to conservatively assume it does.
Dependency propagates to values loaded from a pointer because those
values might be invalidated by deallocating the object. For
example, given the code ``__strong id x = p->ivar;``, ARC must not
move the release of ``p`` to between the load of ``p->ivar`` and the
retain of that value for storing into ``x``.
Dependency does not propagate through stores of dependent pointer
values because doing so would allow dependency to outlive the
full-expression which produced the original value. For example, the
address of an instance variable could be written to some global
location and then freely accessed during the lifetime of the local,
or a function could return an inner pointer of an object and store
it to a local. These cases would be potentially impossible to
reason about and so would basically prevent any optimizations based
on imprecise lifetime. There are also uncommon enough to make it
reasonable to require the precise-lifetime annotation if someone
really wants to rely on them.
Dependency does propagate through return values of pointer type.
The compelling source of need for this rule is a property accessor
which returns an un-autoreleased result; the calling function must
have the chance to operate on the value, e.g. to retain it, before
ARC releases the original pointer. Note again, however, that
dependence does not survive a store, so ARC does not guarantee the
continued validity of the return value past the end of the
full-expression.
.. _arc.optimization.object_lifetime:
No object lifetime extension
----------------------------
If, in the formal computation history of the program, an object ``X``
has been deallocated by the time of an observable side-effect, then
ARC must cause ``X`` to be deallocated by no later than the occurrence
of that side-effect, except as influenced by the re-ordering of the
destruction of objects.
.. admonition:: Rationale
This rule is intended to prohibit ARC from observably extending the
lifetime of a retainable object, other than as specified in this
document. Together with the rule limiting the transformation of
releases, this rule requires ARC to eliminate retains and release
only in pairs.
ARC's power to reorder the destruction of objects is critical to its
ability to do any optimization, for essentially the same reason that
it must retain the power to decrease the lifetime of an object.
Unfortunately, while it's generally poor style for the destruction
of objects to have arbitrary side-effects, it's certainly possible.
Hence the caveat.
.. _arc.optimization.precise:

View File

@ -1547,7 +1547,7 @@ CodeGenFunction::GenerateDestroyHelperFunction(const CGBlockInfo &blockInfo) {
// Destroy strong objects with a call if requested.
} else if (useARCStrongDestroy) {
EmitARCDestroyStrong(srcField, /*precise*/ false);
EmitARCDestroyStrong(srcField, ARCImpreciseLifetime);
// Otherwise we call _Block_object_dispose. It wouldn't be too
// hard to just emit this as a cleanup if we wanted to make sure
@ -1656,7 +1656,7 @@ public:
}
void emitDispose(CodeGenFunction &CGF, llvm::Value *field) {
CGF.EmitARCDestroyStrong(field, /*precise*/ false);
CGF.EmitARCDestroyStrong(field, ARCImpreciseLifetime);
}
void profileImpl(llvm::FoldingSetNodeID &id) const {
@ -1686,7 +1686,7 @@ public:
}
void emitDispose(CodeGenFunction &CGF, llvm::Value *field) {
CGF.EmitARCDestroyStrong(field, /*precise*/ false);
CGF.EmitARCDestroyStrong(field, ARCImpreciseLifetime);
}
void profileImpl(llvm::FoldingSetNodeID &id) const {

View File

@ -627,7 +627,7 @@ void CodeGenFunction::EmitScalarInit(const Expr *init,
if (accessedByInit && lifetime == Qualifiers::OCL_Strong) {
llvm::Value *oldValue = EmitLoadOfScalar(lvalue);
EmitStoreOfScalar(value, lvalue, /* isInitialization */ true);
EmitARCRelease(oldValue, /*precise*/ false);
EmitARCRelease(oldValue, ARCImpreciseLifetime);
return;
}
@ -1490,12 +1490,15 @@ namespace {
/// ns_consumed argument when we can't reasonably do that just by
/// not doing the initial retain for a __block argument.
struct ConsumeARCParameter : EHScopeStack::Cleanup {
ConsumeARCParameter(llvm::Value *param) : Param(param) {}
ConsumeARCParameter(llvm::Value *param,
ARCPreciseLifetime_t precise)
: Param(param), Precise(precise) {}
llvm::Value *Param;
ARCPreciseLifetime_t Precise;
void Emit(CodeGenFunction &CGF, Flags flags) {
CGF.EmitARCRelease(Param, /*precise*/ false);
CGF.EmitARCRelease(Param, Precise);
}
};
}
@ -1585,8 +1588,12 @@ void CodeGenFunction::EmitParmDecl(const VarDecl &D, llvm::Value *Arg,
}
} else {
// Push the cleanup for a consumed parameter.
if (isConsumed)
EHStack.pushCleanup<ConsumeARCParameter>(getARCCleanupKind(), Arg);
if (isConsumed) {
ARCPreciseLifetime_t precise = (D.hasAttr<ObjCPreciseLifetimeAttr>()
? ARCPreciseLifetime : ARCImpreciseLifetime);
EHStack.pushCleanup<ConsumeARCParameter>(getARCCleanupKind(), Arg,
precise);
}
if (lt == Qualifiers::OCL_Weak) {
EmitARCInitWeak(DeclPtr, Arg);

View File

@ -1800,10 +1800,6 @@ LValue CodeGenFunction::EmitDeclRefLValue(const DeclRefExpr *E) {
bool isBlockVariable = VD->hasAttr<BlocksAttr>();
bool NonGCable = VD->hasLocalStorage() &&
!VD->getType()->isReferenceType() &&
!isBlockVariable;
llvm::Value *V = LocalDeclMap.lookup(VD);
if (!V && VD->isStaticLocal())
V = CGM.getStaticLocalDeclAddress(VD);
@ -1837,10 +1833,20 @@ LValue CodeGenFunction::EmitDeclRefLValue(const DeclRefExpr *E) {
LV = MakeAddrLValue(V, T, Alignment);
}
bool isLocalStorage = VD->hasLocalStorage();
bool NonGCable = isLocalStorage &&
!VD->getType()->isReferenceType() &&
!isBlockVariable;
if (NonGCable) {
LV.getQuals().removeObjCGCAttr();
LV.setNonGC(true);
}
bool isImpreciseLifetime =
(isLocalStorage && !VD->hasAttr<ObjCPreciseLifetimeAttr>());
if (isImpreciseLifetime)
LV.setARCPreciseLifetime(ARCImpreciseLifetime);
setObjCGCLValueClass(getContext(), E, LV);
return LV;
}
@ -2911,7 +2917,7 @@ RValue CodeGenFunction::EmitCallExpr(const CallExpr *E,
case Qualifiers::OCL_Strong:
EmitARCRelease(Builder.CreateLoad(BaseValue,
PseudoDtor->getDestroyedType().isVolatileQualified()),
/*precise*/ true);
ARCPreciseLifetime);
break;
case Qualifiers::OCL_Weak:

View File

@ -1451,7 +1451,7 @@ static void EmitObjectDelete(CodeGenFunction &CGF,
llvm::Value *PtrValue = CGF.Builder.CreateLoad(Ptr,
ElementType.isVolatileQualified());
CGF.EmitARCRelease(PtrValue, /*precise*/ true);
CGF.EmitARCRelease(PtrValue, ARCPreciseLifetime);
break;
}

View File

@ -1686,7 +1686,8 @@ namespace {
llvm::Value *object;
void Emit(CodeGenFunction &CGF, Flags flags) {
CGF.EmitARCRelease(object, /*precise*/ true);
// Releases at the end of the full-expression are imprecise.
CGF.EmitARCRelease(object, ARCImpreciseLifetime);
}
};
}
@ -1940,7 +1941,8 @@ CodeGenFunction::EmitARCRetainAutoreleasedReturnValue(llvm::Value *value) {
/// Release the given object.
/// call void \@objc_release(i8* %value)
void CodeGenFunction::EmitARCRelease(llvm::Value *value, bool precise) {
void CodeGenFunction::EmitARCRelease(llvm::Value *value,
ARCPreciseLifetime_t precise) {
if (isa<llvm::ConstantPointerNull>(value)) return;
llvm::Constant *&fn = CGM.getARCEntrypoints().objc_release;
@ -1956,7 +1958,7 @@ void CodeGenFunction::EmitARCRelease(llvm::Value *value, bool precise) {
// Call objc_release.
llvm::CallInst *call = EmitNounwindRuntimeCall(fn, value);
if (!precise) {
if (precise == ARCImpreciseLifetime) {
SmallVector<llvm::Value*,1> args;
call->setMetadata("clang.imprecise_release",
llvm::MDNode::get(Builder.getContext(), args));
@ -1972,7 +1974,8 @@ void CodeGenFunction::EmitARCRelease(llvm::Value *value, bool precise) {
/// At -O1 and above, just load and call objc_release.
///
/// call void \@objc_storeStrong(i8** %addr, i8* null)
void CodeGenFunction::EmitARCDestroyStrong(llvm::Value *addr, bool precise) {
void CodeGenFunction::EmitARCDestroyStrong(llvm::Value *addr,
ARCPreciseLifetime_t precise) {
if (CGM.getCodeGenOpts().OptimizationLevel == 0) {
llvm::PointerType *addrTy = cast<llvm::PointerType>(addr->getType());
llvm::Value *null = llvm::ConstantPointerNull::get(
@ -2042,7 +2045,7 @@ llvm::Value *CodeGenFunction::EmitARCStoreStrong(LValue dst,
EmitStoreOfScalar(newValue, dst);
// Finally, release the old value.
EmitARCRelease(oldValue, /*precise*/ false);
EmitARCRelease(oldValue, dst.isARCPreciseLifetime());
return newValue;
}
@ -2254,13 +2257,13 @@ void CodeGenFunction::EmitObjCMRRAutoreleasePoolPop(llvm::Value *Arg) {
void CodeGenFunction::destroyARCStrongPrecise(CodeGenFunction &CGF,
llvm::Value *addr,
QualType type) {
CGF.EmitARCDestroyStrong(addr, /*precise*/ true);
CGF.EmitARCDestroyStrong(addr, ARCPreciseLifetime);
}
void CodeGenFunction::destroyARCStrongImprecise(CodeGenFunction &CGF,
llvm::Value *addr,
QualType type) {
CGF.EmitARCDestroyStrong(addr, /*precise*/ false);
CGF.EmitARCDestroyStrong(addr, ARCImpreciseLifetime);
}
void CodeGenFunction::destroyARCWeak(CodeGenFunction &CGF,
@ -2737,7 +2740,7 @@ CodeGenFunction::EmitARCStoreStrong(const BinaryOperator *e,
llvm::Value *oldValue =
EmitLoadOfScalar(lvalue);
EmitStoreOfScalar(value, lvalue);
EmitARCRelease(oldValue, /*precise*/ false);
EmitARCRelease(oldValue, lvalue.isARCPreciseLifetime());
} else {
value = EmitARCStoreStrong(lvalue, value, ignored);
}

View File

@ -1617,7 +1617,7 @@ struct NullReturnState {
RValue RV = I->RV;
assert(RV.isScalar() &&
"NullReturnState::complete - arg not on object");
CGF.EmitARCRelease(RV.getScalarVal(), true);
CGF.EmitARCRelease(RV.getScalarVal(), ARCImpreciseLifetime);
}
}
}

View File

@ -97,6 +97,10 @@ public:
}
};
/// Does an ARC strong l-value have precise lifetime?
enum ARCPreciseLifetime_t {
ARCImpreciseLifetime, ARCPreciseLifetime,
};
/// LValue - This represents an lvalue references. Because C/C++ allow
/// bitfields, this is not a simple LLVM pointer, it may be a pointer plus a
@ -147,6 +151,10 @@ class LValue {
// Lvalue is a thread local reference
bool ThreadLocalRef : 1;
// Lvalue has ARC imprecise lifetime. We store this inverted to try
// to make the default bitfield pattern all-zeroes.
bool ImpreciseLifetime : 1;
Expr *BaseIvarExp;
/// TBAAInfo - TBAA information to attach to dereferences of this LValue.
@ -164,6 +172,7 @@ private:
// Initialize Objective-C flags.
this->Ivar = this->ObjIsArray = this->NonGC = this->GlobalObjCRef = false;
this->ImpreciseLifetime = false;
this->ThreadLocalRef = false;
this->BaseIvarExp = 0;
this->TBAAInfo = TBAAInfo;
@ -202,6 +211,13 @@ public:
bool isThreadLocalRef() const { return ThreadLocalRef; }
void setThreadLocalRef(bool Value) { ThreadLocalRef = Value;}
ARCPreciseLifetime_t isARCPreciseLifetime() const {
return ARCPreciseLifetime_t(!ImpreciseLifetime);
}
void setARCPreciseLifetime(ARCPreciseLifetime_t value) {
ImpreciseLifetime = (value == ARCImpreciseLifetime);
}
bool isObjCWeak() const {
return Quals.getObjCGCAttr() == Qualifiers::Weak;
}

View File

@ -2446,14 +2446,14 @@ public:
llvm::Value *EmitARCRetainAutorelease(QualType type, llvm::Value *value);
llvm::Value *EmitARCRetainAutoreleaseNonBlock(llvm::Value *value);
llvm::Value *EmitARCStoreStrong(LValue lvalue, llvm::Value *value,
bool ignored);
bool resultIgnored);
llvm::Value *EmitARCStoreStrongCall(llvm::Value *addr, llvm::Value *value,
bool ignored);
bool resultIgnored);
llvm::Value *EmitARCRetain(QualType type, llvm::Value *value);
llvm::Value *EmitARCRetainNonBlock(llvm::Value *value);
llvm::Value *EmitARCRetainBlock(llvm::Value *value, bool mandatory);
void EmitARCDestroyStrong(llvm::Value *addr, bool precise);
void EmitARCRelease(llvm::Value *value, bool precise);
void EmitARCDestroyStrong(llvm::Value *addr, ARCPreciseLifetime_t precise);
void EmitARCRelease(llvm::Value *value, ARCPreciseLifetime_t precise);
llvm::Value *EmitARCAutorelease(llvm::Value *value);
llvm::Value *EmitARCAutoreleaseReturnValue(llvm::Value *value);
llvm::Value *EmitARCRetainAutoreleaseReturnValue(llvm::Value *value);

View File

@ -0,0 +1,120 @@
// RUN: %clang_cc1 -triple x86_64-apple-darwin10 -emit-llvm -fblocks -fobjc-arc -fobjc-runtime-has-weak -O2 -disable-llvm-optzns -o - %s | FileCheck %s
#define PRECISE_LIFETIME __attribute__((objc_precise_lifetime))
id test0_helper(void) __attribute__((ns_returns_retained));
void test0() {
PRECISE_LIFETIME id x = test0_helper();
x = 0;
// CHECK: [[X:%.*]] = alloca i8*
// CHECK-NEXT: [[CALL:%.*]] = call i8* @test0_helper()
// CHECK-NEXT: store i8* [[CALL]], i8** [[X]]
// CHECK-NEXT: [[T1:%.*]] = load i8** [[X]]
// CHECK-NEXT: store i8* null, i8** [[X]]
// CHECK-NEXT: call void @objc_release(i8* [[T1]]) [[NUW:#[0-9]+]]
// CHECK-NOT: clang.imprecise_release
// CHECK-NEXT: [[T1:%.*]] = load i8** [[X]]
// CHECK-NEXT: call void @objc_release(i8* [[T1]]) [[NUW:#[0-9]+]]
// CHECK-NOT: clang.imprecise_release
// CHECK-NEXT: ret void
}
// rdar://problem/9821110
@interface Test1
- (char*) interior __attribute__((objc_returns_inner_pointer));
// Should we allow this on properties?
@end
extern Test1 *test1_helper(void);
// CHECK: define void @test1a()
void test1a(void) {
// CHECK: [[T0:%.*]] = call [[TEST1:%.*]]* @test1_helper()
// CHECK-NEXT: [[T1:%.*]] = bitcast [[TEST1]]* [[T0]] to i8*
// CHECK-NEXT: [[T2:%.*]] = call i8* @objc_retainAutoreleasedReturnValue(i8* [[T1]])
// CHECK-NEXT: [[T3:%.*]] = bitcast i8* [[T2]] to [[TEST1]]*
// CHECK-NEXT: store [[TEST1]]* [[T3]]
// CHECK-NEXT: [[T0:%.*]] = load [[TEST1]]**
// CHECK-NEXT: [[T1:%.*]] = bitcast [[TEST1]]* [[T0]] to i8*
// CHECK-NEXT: [[T2:%.*]] = call i8* @objc_retainAutorelease(i8* [[T1]])
// CHECK-NEXT: [[T3:%.*]] = bitcast i8* [[T2]] to [[TEST1]]*
// CHECK-NEXT: [[T4:%.*]] = load i8** @"\01L_OBJC_SELECTOR_REFERENCES_
// CHECK-NEXT: [[T5:%.*]] = bitcast [[TEST1]]* [[T3]] to i8*
// CHECK-NEXT: [[T6:%.*]] = call i8* bitcast
// CHECK-NEXT: store i8* [[T6]], i8**
// CHECK-NEXT: [[T0:%.*]] = load [[TEST1]]**
// CHECK-NEXT: [[T1:%.*]] = bitcast [[TEST1]]* [[T0]] to i8*
// CHECK-NEXT: call void @objc_release(i8* [[T1]]) [[NUW]], !clang.imprecise_release
// CHECK-NEXT: ret void
Test1 *ptr = test1_helper();
char *c = [(ptr) interior];
}
// CHECK: define void @test1b()
void test1b(void) {
// CHECK: [[T0:%.*]] = call [[TEST1:%.*]]* @test1_helper()
// CHECK-NEXT: [[T1:%.*]] = bitcast [[TEST1]]* [[T0]] to i8*
// CHECK-NEXT: [[T2:%.*]] = call i8* @objc_retainAutoreleasedReturnValue(i8* [[T1]])
// CHECK-NEXT: [[T3:%.*]] = bitcast i8* [[T2]] to [[TEST1]]*
// CHECK-NEXT: store [[TEST1]]* [[T3]]
// CHECK-NEXT: [[T0:%.*]] = load [[TEST1]]**
// CHECK-NEXT: [[T1:%.*]] = load i8** @"\01L_OBJC_SELECTOR_REFERENCES_
// CHECK-NEXT: [[T2:%.*]] = bitcast [[TEST1]]* [[T0]] to i8*
// CHECK-NEXT: [[T3:%.*]] = call i8* bitcast
// CHECK-NEXT: store i8* [[T3]], i8**
// CHECK-NEXT: [[T0:%.*]] = load [[TEST1]]**
// CHECK-NEXT: [[T1:%.*]] = bitcast [[TEST1]]* [[T0]] to i8*
// CHECK-NEXT: call void @objc_release(i8* [[T1]]) [[NUW]]
// CHECK-NOT: clang.imprecise_release
// CHECK-NEXT: ret void
__attribute__((objc_precise_lifetime)) Test1 *ptr = test1_helper();
char *c = [ptr interior];
}
@interface Test2 {
@public
id ivar;
}
@end
// CHECK: define void @test2(
void test2(Test2 *x) {
x->ivar = 0;
// CHECK: [[X:%.*]] = alloca [[TEST2:%.*]]*
// CHECK-NEXT: [[T0:%.*]] = bitcast [[TEST2]]* {{%.*}} to i8*
// CHECK-NEXT: [[T1:%.*]] = call i8* @objc_retain(i8* [[T0]]) [[NUW]]
// CHECK-NEXT: [[T2:%.*]] = bitcast i8* [[T1]] to [[TEST2]]*
// CHECK-NEXT: store [[TEST2]]* [[T2]], [[TEST2]]** [[X]],
// CHECK-NEXT: [[T0:%.*]] = load [[TEST2]]** [[X]],
// CHECK-NEXT: [[OFFSET:%.*]] = load i64* @"OBJC_IVAR_$_Test2.ivar"
// CHECK-NEXT: [[T1:%.*]] = bitcast [[TEST2]]* [[T0]] to i8*
// CHECK-NEXT: [[T2:%.*]] = getelementptr inbounds i8* [[T1]], i64 [[OFFSET]]
// CHECK-NEXT: [[T3:%.*]] = bitcast i8* [[T2]] to i8**
// CHECK-NEXT: [[T4:%.*]] = load i8** [[T3]],
// CHECK-NEXT: store i8* null, i8** [[T3]],
// CHECK-NEXT: call void @objc_release(i8* [[T4]]) [[NUW]]
// CHECK-NOT: imprecise
// CHECK-NEXT: [[T0:%.*]] = load [[TEST2]]** [[X]]
// CHECK-NEXT: [[T1:%.*]] = bitcast [[TEST2]]* [[T0]] to i8*
// CHECK-NEXT: call void @objc_release(i8* [[T1]]) [[NUW]], !clang.imprecise_release
// CHECK-NEXT: ret void
}
// CHECK: define void @test3(i8*
void test3(PRECISE_LIFETIME id x) {
// CHECK: [[X:%.*]] = alloca i8*,
// CHECK-NEXT: [[T0:%.*]] = call i8* @objc_retain(i8* {{%.*}}) [[NUW]]
// CHECK-NEXT: store i8* [[T0]], i8** [[X]],
// CHECK-NEXT: [[T0:%.*]] = load i8** [[X]]
// CHECK-NEXT: call void @objc_release(i8* [[T0]]) [[NUW]]
// CHECK-NOT: imprecise_release
// CHECK-NEXT: ret void
}
// CHECK: attributes [[NUW]] = { nounwind }

View File

@ -273,27 +273,7 @@ void test8() {
// CHECK: [[X:%.*]] = alloca i8*
// CHECK-NEXT: [[T0:%.*]] = call i8* @test8_helper()
// CHECK-NEXT: store i8* [[T0]], i8** [[X]]
// CHECK-NEXT: call void @objc_release(i8* [[T0]]) [[NUW]]
// CHECK-NOT: imprecise_release
// CHECK-NEXT: ret void
}
id test9_helper(void) __attribute__((ns_returns_retained));
void test9() {
id x __attribute__((objc_precise_lifetime)) = test9_helper();
x = 0;
// CHECK: [[X:%.*]] = alloca i8*
// CHECK-NEXT: [[CALL:%.*]] = call i8* @test9_helper()
// CHECK-NEXT: store i8* [[CALL]], i8** [[X]]
// CHECK-NEXT: [[T1:%.*]] = load i8** [[X]]
// CHECK-NEXT: store i8* null, i8** [[X]]
// CHECK-NEXT: call void @objc_release(i8* [[T1]]) [[NUW]], !clang.imprecise_release
// CHECK-NEXT: [[T1:%.*]] = load i8** [[X]]
// CHECK-NEXT: call void @objc_release(i8* [[T1]]) [[NUW]]
// CHECK-NOT: clang.imprecise_release
// CHECK-NEXT: call void @objc_release(i8* [[T0]]) [[NUW]], !clang.imprecise_release
// CHECK-NEXT: ret void
}
@ -1239,57 +1219,6 @@ void test56_test(void) {
// CHECK-NEXT: [[T5:%.*]] = load i8** [[T4]]
// CHECK-NEXT: ret i8* [[T5]]
// rdar://problem/9821110
@interface Test58
- (char*) interior __attribute__((objc_returns_inner_pointer));
// Should we allow this on properties?
@end
extern Test58 *test58_helper(void);
// CHECK: define void @test58a()
void test58a(void) {
// CHECK: [[T0:%.*]] = call [[TEST58:%.*]]* @test58_helper()
// CHECK-NEXT: [[T1:%.*]] = bitcast [[TEST58]]* [[T0]] to i8*
// CHECK-NEXT: [[T2:%.*]] = call i8* @objc_retainAutoreleasedReturnValue(i8* [[T1]])
// CHECK-NEXT: [[T3:%.*]] = bitcast i8* [[T2]] to [[TEST58]]*
// CHECK-NEXT: store [[TEST58]]* [[T3]]
// CHECK-NEXT: [[T0:%.*]] = load [[TEST58]]**
// CHECK-NEXT: [[T1:%.*]] = bitcast [[TEST58]]* [[T0]] to i8*
// CHECK-NEXT: [[T2:%.*]] = call i8* @objc_retainAutorelease(i8* [[T1]])
// CHECK-NEXT: [[T3:%.*]] = bitcast i8* [[T2]] to [[TEST58]]*
// CHECK-NEXT: [[T4:%.*]] = load i8** @"\01L_OBJC_SELECTOR_REFERENCES_
// CHECK-NEXT: [[T5:%.*]] = bitcast [[TEST58]]* [[T3]] to i8*
// CHECK-NEXT: [[T6:%.*]] = call i8* bitcast
// CHECK-NEXT: store i8* [[T6]], i8**
// CHECK-NEXT: [[T0:%.*]] = load [[TEST58]]**
// CHECK-NEXT: [[T1:%.*]] = bitcast [[TEST58]]* [[T0]] to i8*
// CHECK-NEXT: call void @objc_release(i8* [[T1]]) [[NUW]], !clang.imprecise_release
// CHECK-NEXT: ret void
Test58 *ptr = test58_helper();
char *c = [(ptr) interior];
}
// CHECK: define void @test58b()
void test58b(void) {
// CHECK: [[T0:%.*]] = call [[TEST58:%.*]]* @test58_helper()
// CHECK-NEXT: [[T1:%.*]] = bitcast [[TEST58]]* [[T0]] to i8*
// CHECK-NEXT: [[T2:%.*]] = call i8* @objc_retainAutoreleasedReturnValue(i8* [[T1]])
// CHECK-NEXT: [[T3:%.*]] = bitcast i8* [[T2]] to [[TEST58]]*
// CHECK-NEXT: store [[TEST58]]* [[T3]]
// CHECK-NEXT: [[T0:%.*]] = load [[TEST58]]**
// CHECK-NEXT: [[T1:%.*]] = load i8** @"\01L_OBJC_SELECTOR_REFERENCES_
// CHECK-NEXT: [[T2:%.*]] = bitcast [[TEST58]]* [[T0]] to i8*
// CHECK-NEXT: [[T3:%.*]] = call i8* bitcast
// CHECK-NEXT: store i8* [[T3]], i8**
// CHECK-NEXT: [[T0:%.*]] = load [[TEST58]]**
// CHECK-NEXT: [[T1:%.*]] = bitcast [[TEST58]]* [[T0]] to i8*
// CHECK-NEXT: call void @objc_release(i8* [[T1]]) [[NUW]]
// CHECK-NOT: clang.imprecise_release
// CHECK-NEXT: ret void
__attribute__((objc_precise_lifetime)) Test58 *ptr = test58_helper();
char *c = [ptr interior];
}
// rdar://problem/9842343
void test59(void) {
extern id test59_getlock(void);