Skip to content

Commit

Permalink
[ObjCARC] Add an new attribute, objc_externally_retained
Browse files Browse the repository at this point in the history
This attribute, called "objc_externally_retained", exposes clang's
notion of pseudo-__strong variables in ARC. Pseudo-strong variables
"borrow" their initializer, meaning that they don't retain/release
it, instead assuming that someone else is keeping their value alive.

If a function is annotated with this attribute, implicitly strong
parameters of that function aren't implicitly retained/released in
the function body, and are implicitly const. This is useful to expose
for performance reasons, most functions don't need the extra safety
of the retain/release, so programmers can opt out as needed.

This attribute can also apply to declarations of local variables,
with similar effect.

Differential revision: https://reviews.llvm.org/D55865

llvm-svn: 350422
  • Loading branch information
epilk committed Jan 4, 2019
1 parent 6153565 commit 1e36882
Show file tree
Hide file tree
Showing 16 changed files with 500 additions and 57 deletions.
87 changes: 73 additions & 14 deletions clang/docs/AutomaticReferenceCounting.rst
Expand Up @@ -1734,20 +1734,78 @@ A program is ill-formed if it refers to the ``NSAutoreleasePool`` class.
rest of the language. Not draining the pool during an unwind is apparently
required by the Objective-C exceptions implementation.

.. _arc.misc.externally_retained:

Externally-Retained Variables
-----------------------------

In some situations, variables with strong ownership are considered
externally-retained by the implementation. This means that the variable is
retained elsewhere, and therefore the implementation can elide retaining and
releasing its value. Such a variable is implicitly ``const`` for safety. In
contrast with ``__unsafe_unretained``, an externally-retained variable still
behaves as a strong variable outside of initialization and destruction. For
instance, when an externally-retained variable is captured in a block the value
of the variable is retained and released on block capture and destruction. It
also affects C++ features such as lambda capture, ``decltype``, and template
argument deduction.

Implicitly, the implementation assumes that the :ref:`self parameter in a
non-init method <arc.misc.self>` and the :ref:`variable in a for-in loop
<arc.misc.enumeration>` are externally-retained.

Externally-retained semantics can also be opted into with the
``objc_externally_retained`` attribute. This attribute can apply to strong local
variables, functions, methods, or blocks:

.. code-block:: objc
@class WobbleAmount;
@interface Widget : NSObject
-(void)wobble:(WobbleAmount *)amount;
@end
@implementation Widget
-(void)wobble:(WobbleAmount *)amount
__attribute__((objc_externally_retained)) {
// 'amount' and 'alias' aren't retained on entry, nor released on exit.
__attribute__((objc_externally_retained)) WobbleAmount *alias = amount;
}
@end
Annotating a function with this attribute makes every parameter with strong
retainable object pointer type externally-retained, unless the variable was
explicitly qualified with ``__strong``. For instance, ``first_param`` is
externally-retained (and therefore ``const``) below, but not ``second_param``:
.. code-block:: objc
__attribute__((objc_externally_retained))
void f(NSArray *first_param, __strong NSArray *second_param) {
// ...
}
You can test if your compiler has support for ``objc_externally_retained`` with
``__has_attribute``:
.. code-block:: objc
#if __has_attribute(objc_externally_retained)
// Use externally retained...
#endif
.. _arc.misc.self:
``self``
--------
The ``self`` parameter variable of an Objective-C method is never actually
retained by the implementation. It is undefined behavior, or at least
dangerous, to cause an object to be deallocated during a message send to that
object.

To make this safe, for Objective-C instance methods ``self`` is implicitly
``const`` unless the method is in the :ref:`init family
<arc.family.semantics.init>`. Further, ``self`` is **always** implicitly
``const`` within a class method.
The ``self`` parameter variable of an non-init Objective-C method is considered
:ref:`externally-retained <arc.misc.externally_retained>` by the implementation.
It is undefined behavior, or at least dangerous, to cause an object to be
deallocated during a message send to that object. In an init method, ``self``
follows the :ref:``init family rules <arc.family.semantics.init>``.
.. admonition:: Rationale
Expand All @@ -1758,9 +1816,9 @@ To make this safe, for Objective-C instance methods ``self`` is implicitly
without this retain and release. Since it's extremely uncommon to actually
do so, even unintentionally, and since there's no natural way for the
programmer to remove this retain/release pair otherwise (as there is for
other parameters by, say, making the variable ``__unsafe_unretained``), we
chose to make this optimizing assumption and shift some amount of risk to the
user.
other parameters by, say, making the variable ``objc_externally_retained`` or
qualifying it with ``__unsafe_unretained``), we chose to make this optimizing
assumption and shift some amount of risk to the user.
.. _arc.misc.enumeration:
Expand All @@ -1769,8 +1827,9 @@ Fast enumeration iteration variables
If a variable is declared in the condition of an Objective-C fast enumeration
loop, and the variable has no explicit ownership qualifier, then it is
qualified with ``const __strong`` and objects encountered during the
enumeration are not actually retained.
implicitly :ref:`externally-retained <arc.misc.externally_retained>` so that
objects encountered during the enumeration are not actually retained and
released.
.. admonition:: Rationale
Expand Down
30 changes: 14 additions & 16 deletions clang/include/clang/AST/Decl.h
Expand Up @@ -866,8 +866,12 @@ class VarDecl : public DeclaratorDecl, public Redeclarable<VarDecl> {
unsigned SClass : 3;
unsigned TSCSpec : 2;
unsigned InitStyle : 2;

/// Whether this variable is an ARC pseudo-__strong variable; see
/// isARCPseudoStrong() for details.
unsigned ARCPseudoStrong : 1;
};
enum { NumVarDeclBits = 7 };
enum { NumVarDeclBits = 8 };

protected:
enum { NumParameterIndexBits = 8 };
Expand Down Expand Up @@ -940,10 +944,6 @@ class VarDecl : public DeclaratorDecl, public Redeclarable<VarDecl> {
/// Whether this variable is the for-in loop declaration in Objective-C.
unsigned ObjCForDecl : 1;

/// Whether this variable is an ARC pseudo-__strong
/// variable; see isARCPseudoStrong() for details.
unsigned ARCPseudoStrong : 1;

/// Whether this variable is (C++1z) inline.
unsigned IsInline : 1;

Expand Down Expand Up @@ -1349,17 +1349,15 @@ class VarDecl : public DeclaratorDecl, public Redeclarable<VarDecl> {
NonParmVarDeclBits.ObjCForDecl = FRD;
}

/// Determine whether this variable is an ARC pseudo-__strong
/// variable. A pseudo-__strong variable has a __strong-qualified
/// type but does not actually retain the object written into it.
/// Generally such variables are also 'const' for safety.
bool isARCPseudoStrong() const {
return isa<ParmVarDecl>(this) ? false : NonParmVarDeclBits.ARCPseudoStrong;
}
void setARCPseudoStrong(bool ps) {
assert(!isa<ParmVarDecl>(this));
NonParmVarDeclBits.ARCPseudoStrong = ps;
}
/// Determine whether this variable is an ARC pseudo-__strong variable. A
/// pseudo-__strong variable has a __strong-qualified type but does not
/// actually retain the object written into it. Generally such variables are
/// also 'const' for safety. There are 3 cases where this will be set, 1) if
/// the variable is annotated with the objc_externally_retained attribute, 2)
/// if its 'self' in a non-init method, or 3) if its the variable in an for-in
/// loop.
bool isARCPseudoStrong() const { return VarDeclBits.ARCPseudoStrong; }
void setARCPseudoStrong(bool PS) { VarDeclBits.ARCPseudoStrong = PS; }

/// Whether this variable is (C++1z) inline.
bool isInline() const {
Expand Down
8 changes: 8 additions & 0 deletions clang/include/clang/AST/DeclObjC.h
Expand Up @@ -369,6 +369,14 @@ class ObjCMethodDecl : public NamedDecl, public DeclContext {
NumParams);
}

ParmVarDecl *getParamDecl(unsigned Idx) {
assert(Idx < NumParams && "Index out of bounds!");
return getParams()[Idx];
}
const ParmVarDecl *getParamDecl(unsigned Idx) const {
return const_cast<ObjCMethodDecl *>(this)->getParamDecl(Idx);
}

/// Sets the method's parameters and selector source locations.
/// If the method is implicit (not coming from source) \p SelLocs is
/// ignored.
Expand Down
8 changes: 8 additions & 0 deletions clang/include/clang/Basic/Attr.td
Expand Up @@ -300,6 +300,7 @@ def OpenCL : LangOpt<"OpenCL">;
def RenderScript : LangOpt<"RenderScript">;
def ObjC : LangOpt<"ObjC">;
def BlocksSupported : LangOpt<"Blocks">;
def ObjCAutoRefCount : LangOpt<"ObjCAutoRefCount">;

// Defines targets for target-specific attributes. Empty lists are unchecked.
class TargetSpec {
Expand Down Expand Up @@ -3141,3 +3142,10 @@ def Uninitialized : InheritableAttr {
let Subjects = SubjectList<[LocalVar]>;
let Documentation = [UninitializedDocs];
}

def ObjCExternallyRetained : InheritableAttr {
let LangOpts = [ObjCAutoRefCount];
let Spellings = [Clang<"objc_externally_retained">];
let Subjects = SubjectList<[NonParmVar, Function, Block, ObjCMethod]>;
let Documentation = [ObjCExternallyRetainedDocs];
}
28 changes: 28 additions & 0 deletions clang/include/clang/Basic/AttrDocs.td
Expand Up @@ -3824,3 +3824,31 @@ def SpeculativeLoadHardeningDocs : Documentation {
(even after inlining) end up hardened.
}];
}

def ObjCExternallyRetainedDocs : Documentation {
let Category = DocCatVariable;
let Content = [{
The ``objc_externally_retained`` attribute can be applied to strong local
variables, functions, methods, or blocks to opt into
`externally-retained semantics
<https://clang.llvm.org/docs/AutomaticReferenceCounting.html#externally-retained-variables>`_.

When applied to the definition of a function, method, or block, every parameter
of the function with implicit strong retainable object pointer type is
considered externally-retained, and becomes ``const``. By explicitly annotating
a parameter with ``__strong``, you can opt back into the default
non-externally-retained behaviour for that parameter. For instance,
``first_param`` is externally-retained below, but not ``second_param``:

.. code-block:: objc

__attribute__((objc_externally_retained))
void f(NSArray *first_param, __strong NSArray *second_param) {
// ...
}

Likewise, when applied to a strong local variable, that variable becomes
``const`` and is considered externally-retained.

When compiled without ``-fobjc-arc``, this attribute is ignored.
}]; }
8 changes: 8 additions & 0 deletions clang/include/clang/Basic/DiagnosticSemaKinds.td
Expand Up @@ -3485,6 +3485,11 @@ def err_objc_bridged_related_known_method : Error<
def err_objc_attr_protocol_requires_definition : Error<
"attribute %0 can only be applied to @protocol definitions, not forward declarations">;

def warn_ignored_objc_externally_retained : Warning<
"'objc_externally_retained' can only be applied to local variables "
"%select{of retainable type|with strong ownership}0">,
InGroup<IgnoredAttributes>;

// Function Parameter Semantic Analysis.
def err_param_with_void_type : Error<"argument may not have 'void' type">;
def err_void_only_param : Error<
Expand Down Expand Up @@ -5254,6 +5259,9 @@ def err_typecheck_arc_assign_self_class_method : Error<
def err_typecheck_arr_assign_enumeration : Error<
"fast enumeration variables cannot be modified in ARC by default; "
"declare the variable __strong to allow this">;
def err_typecheck_arc_assign_externally_retained : Error<
"variable declared with 'objc_externally_retained' "
"cannot be modified in ARC">;
def warn_arc_retained_assign : Warning<
"assigning retained object to %select{weak|unsafe_unretained}0 "
"%select{property|variable}1"
Expand Down
28 changes: 15 additions & 13 deletions clang/lib/CodeGen/CGDecl.cpp
Expand Up @@ -797,15 +797,21 @@ void CodeGenFunction::EmitScalarInit(const Expr *init, const ValueDecl *D,
case Qualifiers::OCL_None:
llvm_unreachable("present but none");

case Qualifiers::OCL_Strong: {
if (!D || !isa<VarDecl>(D) || !cast<VarDecl>(D)->isARCPseudoStrong()) {
value = EmitARCRetainScalarExpr(init);
break;
}
// If D is pseudo-strong, treat it like __unsafe_unretained here. This means
// that we omit the retain, and causes non-autoreleased return values to be
// immediately released.
LLVM_FALLTHROUGH;
}

case Qualifiers::OCL_ExplicitNone:
value = EmitARCUnsafeUnretainedScalarExpr(init);
break;

case Qualifiers::OCL_Strong: {
value = EmitARCRetainScalarExpr(init);
break;
}

case Qualifiers::OCL_Weak: {
// If it's not accessed by the initializer, try to emit the
// initialization with a copy or move.
Expand Down Expand Up @@ -2324,15 +2330,11 @@ void CodeGenFunction::EmitParmDecl(const VarDecl &D, ParamValue Arg,
// cleanup to do the release at the end of the function.
bool isConsumed = D.hasAttr<NSConsumedAttr>();

// 'self' is always formally __strong, but if this is not an
// init method then we don't want to retain it.
// If a parameter is pseudo-strong then we can omit the implicit retain.
if (D.isARCPseudoStrong()) {
const ObjCMethodDecl *method = cast<ObjCMethodDecl>(CurCodeDecl);
assert(&D == method->getSelfDecl());
assert(lt == Qualifiers::OCL_Strong);
assert(qs.hasConst());
assert(method->getMethodFamily() != OMF_init);
(void) method;
assert(lt == Qualifiers::OCL_Strong &&
"pseudo-strong variable isn't strong?");
assert(qs.hasConst() && "pseudo-strong variable should be const!");
lt = Qualifiers::OCL_ExplicitNone;
}

Expand Down
8 changes: 6 additions & 2 deletions clang/lib/CodeGen/CGExpr.cpp
Expand Up @@ -419,8 +419,12 @@ LValue CodeGenFunction::
EmitMaterializeTemporaryExpr(const MaterializeTemporaryExpr *M) {
const Expr *E = M->GetTemporaryExpr();

// FIXME: ideally this would use EmitAnyExprToMem, however, we cannot do so
// as that will cause the lifetime adjustment to be lost for ARC
assert((!M->getExtendingDecl() || !isa<VarDecl>(M->getExtendingDecl()) ||
!cast<VarDecl>(M->getExtendingDecl())->isARCPseudoStrong()) &&
"Reference should never be pseudo-strong!");

// FIXME: ideally this would use EmitAnyExprToMem, however, we cannot do so
// as that will cause the lifetime adjustment to be lost for ARC
auto ownership = M->getType().getObjCLifetime();
if (ownership != Qualifiers::OCL_None &&
ownership != Qualifiers::OCL_ExplicitNone) {
Expand Down

0 comments on commit 1e36882

Please sign in to comment.