Skip to content

Commit

Permalink
Implement __attribute__((objc_direct)), __attribute__((objc_direct_me…
Browse files Browse the repository at this point in the history
…mbers))

__attribute__((objc_direct)) is an attribute on methods declaration, and
__attribute__((objc_direct_members)) on implementation, categories or
extensions.

A `direct` property specifier is added (@Property(direct) type name)

These attributes / specifiers cause the method to have no associated
Objective-C metadata (for the property or the method itself), and the
calling convention to be a direct C function call.

The symbol for the method has enforced hidden visibility and such direct
calls are hence unreachable cross image. An explicit C function must be
made if so desired to wrap them.

The implicit `self` and `_cmd` arguments are preserved, however to
maintain compatibility with the usual `objc_msgSend` semantics,
3 fundamental precautions are taken:

1) for instance methods, `self` is nil-checked. On arm64 backends this
   typically adds a single instruction (cbz x0, <closest-ret>) to the
   codegen, for the vast majority of the cases when the return type is a
   scalar.

2) for class methods, because the class may not be realized/initialized
   yet, a call to `[self self]` is emitted. When the proper deployment
   target is used, this is optimized to `objc_opt_self(self)`.

   However, long term we might want to emit something better that the
   optimizer can reason about. When inlining kicks in, these calls
   aren't optimized away as the optimizer has no idea that a single call
   is really necessary.

3) the calling convention for the `_cmd` argument is changed: the caller
   leaves the second argument to the call undefined, and the selector is
   loaded inside the body when it's referenced only.

As far as error reporting goes, the compiler refuses:
- making any overloads direct,
- making an overload of a direct method,
- implementations marked as direct when the declaration in the
  interface isn't (the other way around is allowed, as the direct
  attribute is inherited from the declaration),
- marking methods required for protocol conformance as direct,
- messaging an unqualified `id` with a direct method,
- forming any @selector() expression with only direct selectors.

As warnings:
- any inconsistency of direct-related calling convention when
  @selector() or messaging is used,
- forming any @selector() expression with a possibly direct selector.

Lastly an `objc_direct_members` attribute is added that can decorate
`@implementation` blocks and causes methods only declared there (and in
no `@interface`) to be automatically direct. When decorating an
`@interface` then all methods and properties declared in this block are
marked direct.

Radar-ID: rdar://problem/2684889
Differential Revision: https://reviews.llvm.org/D69991
Reviewed-By: John McCall
  • Loading branch information
MadCoder authored and dexonsmith committed Nov 18, 2019
1 parent 6e20d70 commit d4e1ba3
Show file tree
Hide file tree
Showing 24 changed files with 1,024 additions and 50 deletions.
11 changes: 8 additions & 3 deletions clang/include/clang/AST/DeclObjC.h
Original file line number Diff line number Diff line change
Expand Up @@ -410,7 +410,7 @@ class ObjCMethodDecl : public NamedDecl, public DeclContext {
/// \return the type for \c self and set \arg selfIsPseudoStrong and
/// \arg selfIsConsumed accordingly.
QualType getSelfType(ASTContext &Context, const ObjCInterfaceDecl *OID,
bool &selfIsPseudoStrong, bool &selfIsConsumed);
bool &selfIsPseudoStrong, bool &selfIsConsumed) const;

ImplicitParamDecl * getSelfDecl() const { return SelfDecl; }
void setSelfDecl(ImplicitParamDecl *SD) { SelfDecl = SD; }
Expand Down Expand Up @@ -476,6 +476,9 @@ class ObjCMethodDecl : public NamedDecl, public DeclContext {
ObjCMethodDeclBits.HasSkippedBody = Skipped;
}

/// True if the method is tagged as objc_direct
bool isDirectMethod() const;

/// Returns the property associated with this method's selector.
///
/// Note that even if this particular method is not marked as a property
Expand Down Expand Up @@ -757,13 +760,14 @@ class ObjCPropertyDecl : public NamedDecl {
/// property attribute rather than a type qualifier.
OBJC_PR_nullability = 0x1000,
OBJC_PR_null_resettable = 0x2000,
OBJC_PR_class = 0x4000
OBJC_PR_class = 0x4000,
OBJC_PR_direct = 0x8000
// Adding a property should change NumPropertyAttrsBits
};

enum {
/// Number of bits fitting all the property attributes.
NumPropertyAttrsBits = 15
NumPropertyAttrsBits = 16
};

enum SetterKind { Assign, Retain, Copy, Weak };
Expand Down Expand Up @@ -886,6 +890,7 @@ class ObjCPropertyDecl : public NamedDecl {

bool isInstanceProperty() const { return !isClassProperty(); }
bool isClassProperty() const { return PropertyAttributes & OBJC_PR_class; }
bool isDirectProperty() const { return PropertyAttributes & OBJC_PR_direct; }

ObjCPropertyQueryKind getQueryKind() const {
return isClassProperty() ? ObjCPropertyQueryKind::OBJC_PR_query_class :
Expand Down
14 changes: 14 additions & 0 deletions clang/include/clang/Basic/Attr.td
Original file line number Diff line number Diff line change
Expand Up @@ -1861,6 +1861,20 @@ def ObjCDesignatedInitializer : Attr {
let Documentation = [Undocumented];
}

def ObjCDirect : Attr {
let Spellings = [Clang<"objc_direct">];
let Subjects = SubjectList<[ObjCMethod], ErrorDiag>;
let LangOpts = [ObjC];
let Documentation = [ObjCDirectDocs];
}

def ObjCDirectMembers : Attr {
let Spellings = [Clang<"objc_direct_members">];
let Subjects = SubjectList<[ObjCImpl, ObjCCategory], ErrorDiag>;
let LangOpts = [ObjC];
let Documentation = [ObjCDirectMembersDocs];
}

def ObjCRuntimeName : Attr {
let Spellings = [Clang<"objc_runtime_name">];
let Subjects = SubjectList<[ObjCInterface, ObjCProtocol], ErrorDiag>;
Expand Down
98 changes: 98 additions & 0 deletions clang/include/clang/Basic/AttrDocs.td
Original file line number Diff line number Diff line change
Expand Up @@ -3912,6 +3912,104 @@ overheads associated with defining and calling such a method.
}];
}

def ObjCDirectDocs : Documentation {
let Category = DocCatDecl;
let Content = [{
The ``objc_direct`` attribute can be used to mark an Objective-C method as
being *direct*. A direct method is treated statically like an ordinary method,
but dynamically it behaves more like a C function. This lowers some of the costs
associated with the method but also sacrifices some of the ordinary capabilities
of Objective-C methods.

A message send of a direct method calls the implementation directly, as if it
were a C function, rather than using ordinary Objective-C method dispatch. This
is substantially faster and potentially allows the implementation to be inlined,
but it also means the method cannot be overridden in subclasses or replaced
dynamically, as ordinary Objective-C methods can.

Furthermore, a direct method is not listed in the class's method lists. This
substantially reduces the code-size overhead of the method but also means it
cannot be called dynamically using ordinary Objective-C method dispatch at all;
in particular, this means that it cannot override a superclass method or satisfy
a protocol requirement.

Because a direct method cannot be overridden, it is an error to perform
a ``super`` message send of one.

Although a message send of a direct method causes the method to be called
directly as if it were a C function, it still obeys Objective-C semantics in other
ways:

- If the receiver is ``nil``, the message send does nothing and returns the zero value
for the return type.

- A message send of a direct class method will cause the class to be initialized,
including calling the ``+initialize`` method if present.

- The implicit ``_cmd`` parameter containing the method's selector is still defined.
In order to minimize code-size costs, the implementation will not emit a reference
to the selector if the parameter is unused within the method.

Symbols for direct method implementations are implicitly given hidden
visibility, meaning that they can only be called within the same linkage unit.

It is an error to do any of the following:

- declare a direct method in a protocol,
- declare an override of a direct method with a method in a subclass,
- declare an override of a non-direct method with a direct method in a subclass,
- declare a method with different directness in different class interfaces, or
- implement a non-direct method (as declared in any class interface) with a direct method.

If any of these rules would be violated if every method defined in an
``@implementation`` within a single linkage unit were declared in an
appropriate class interface, the program is ill-formed with no diagnostic
required. If a violation of this rule is not diagnosed, behavior remains
well-defined; this paragraph is simply reserving the right to diagnose such
conflicts in the future, not to treat them as undefined behavior.

Additionally, Clang will warn about any ``@selector`` expression that
names a selector that is only known to be used for direct methods.

For the purpose of these rules, a "class interface" includes a class's primary
``@interface`` block, its class extensions, its categories, its declared protocols,
and all the class interfaces of its superclasses.

An Objective-C property can be declared with the ``direct`` property
attribute. If a direct property declaration causes an implicit declaration of
a getter or setter method (that is, if the given method is not explicitly
declared elsewhere), the method is declared to be direct.

Some programmers may wish to make many methods direct at once. In order
to simplify this, the ``objc_direct_members`` attribute is provided; see its
documentation for more information.
}];
}

def ObjCDirectMembersDocs : Documentation {
let Category = DocCatDecl;
let Content = [{
The ``objc_direct_members`` attribute can be placed on an Objective-C
``@interface`` or ``@implementation`` to mark that methods declared
therein should be considered direct by default. See the documentation
for ``objc_direct`` for more information about direct methods.

When ``objc_direct_members`` is placed on an ``@interface`` block, every
method in the block is considered to be declared as direct. This includes any
implicit method declarations introduced by property declarations. If the method
redeclares a non-direct method, the declaration is ill-formed, exactly as if the
method was annotated with the ``objc_direct`` attribute. ``objc_direct_members``
cannot be placed on the primary interface of a class, only on category or class
extension interfaces.

When ``objc_direct_members`` is placed on an ``@implementation`` block,
methods defined in the block are considered to be declared as direct unless
they have been previously declared as non-direct in any interface of the class.
This includes the implicit method definitions introduced by synthesized
properties, including auto-synthesized properties.
}];
}

def SelectAnyDocs : Documentation {
let Category = DocCatDecl;
let Content = [{
Expand Down
25 changes: 25 additions & 0 deletions clang/include/clang/Basic/DiagnosticSemaKinds.td
Original file line number Diff line number Diff line change
Expand Up @@ -988,6 +988,22 @@ def warn_objc_boxing_invalid_utf8_string : Warning<
"string is ill-formed as UTF-8 and will become a null %0 when boxed">,
InGroup<ObjCBoxing>;

def err_objc_direct_on_protocol : Error<
"'objc_direct' attribute cannot be applied to %select{methods|properties}0 "
"declared in an Objective-C protocol">;
def err_objc_direct_missing_on_decl : Error<
"direct method implementation was previously declared not direct">;
def err_objc_direct_on_override : Error<
"methods that %select{override superclass methods|implement protocol requirements}0 cannot be direct">;
def err_objc_override_direct_method : Error<
"cannot override a method that is declared direct by a superclass">;
def warn_objc_direct_ignored : Warning<
"%0 attribute isn't implemented by this Objective-C runtime">,
InGroup<IgnoredAttributes>;
def warn_objc_direct_property_ignored : Warning<
"direct attribute on property %0 ignored (not implemented by this Objective-C runtime)">,
InGroup<IgnoredAttributes>;

def warn_conflicting_overriding_ret_types : Warning<
"conflicting return type in "
"declaration of %0%diff{: $ vs $|}1,2">,
Expand Down Expand Up @@ -1073,6 +1089,7 @@ def warn_accessor_property_type_mismatch : Warning<
"type of property %0 does not match type of accessor %1">;
def note_conv_function_declared_at : Note<"type conversion function declared here">;
def note_method_declared_at : Note<"method %0 declared here">;
def note_direct_method_declared_at : Note<"direct method %0 declared here">;
def note_property_attribute : Note<"property %0 is declared "
"%select{deprecated|unavailable|partial}1 here">;
def err_setter_type_void : Error<"type of setter must be void">;
Expand Down Expand Up @@ -1308,6 +1325,8 @@ def warn_multiple_selectors: Warning<
"several methods with selector %0 of mismatched types are found "
"for the @selector expression">,
InGroup<SelectorTypeMismatch>, DefaultIgnore;
def err_direct_selector_expression: Error<
"@selector expression formed with direct selector %0">;

def err_objc_kindof_nonobject : Error<
"'__kindof' specifier cannot be applied to non-object type %0">;
Expand All @@ -1321,6 +1340,12 @@ def err_objc_method_unsupported_param_ret_type : Error<
def warn_messaging_unqualified_id : Warning<
"messaging unqualified id">, DefaultIgnore,
InGroup<DiagGroup<"objc-messaging-id">>;
def err_messaging_unqualified_id_with_direct_method : Error<
"messaging unqualified id with a method that is possibly direct">;
def err_messaging_super_with_direct_method : Error<
"messaging super with a direct method">;
def err_messaging_class_with_direct_method : Error<
"messaging a Class with a method that is possibly direct">;

// C++ declarations
def err_static_assert_expression_is_not_constant : Error<
Expand Down
14 changes: 14 additions & 0 deletions clang/include/clang/Basic/ObjCRuntime.h
Original file line number Diff line number Diff line change
Expand Up @@ -446,6 +446,20 @@ class ObjCRuntime {
llvm_unreachable("bad kind");
}

/// Does this runtime supports direct dispatch
bool allowsDirectDispatch() const {
switch (getKind()) {
case FragileMacOSX: return false;
case MacOSX: return true;
case iOS: return true;
case WatchOS: return true;
case GCC: return false;
case GNUstep: return false;
case ObjFW: return false;
}
llvm_unreachable("bad kind");
}

/// Try to parse an Objective-C runtime specification from the given
/// string.
///
Expand Down
5 changes: 3 additions & 2 deletions clang/include/clang/Sema/DeclSpec.h
Original file line number Diff line number Diff line change
Expand Up @@ -833,7 +833,8 @@ class ObjCDeclSpec {
DQ_PR_unsafe_unretained = 0x800,
DQ_PR_nullability = 0x1000,
DQ_PR_null_resettable = 0x2000,
DQ_PR_class = 0x4000
DQ_PR_class = 0x4000,
DQ_PR_direct = 0x8000,
};

ObjCDeclSpec()
Expand Down Expand Up @@ -903,7 +904,7 @@ class ObjCDeclSpec {
unsigned objcDeclQualifier : 7;

// NOTE: VC++ treats enums as signed, avoid using ObjCPropertyAttributeKind
unsigned PropertyAttributes : 15;
unsigned PropertyAttributes : 16;

unsigned Nullability : 2;

Expand Down
3 changes: 3 additions & 0 deletions clang/include/clang/Sema/Sema.h
Original file line number Diff line number Diff line change
Expand Up @@ -8901,6 +8901,9 @@ class Sema {
RTC_Unknown
};

void CheckObjCMethodDirectOverrides(ObjCMethodDecl *method,
ObjCMethodDecl *overridden);

void CheckObjCMethodOverrides(ObjCMethodDecl *ObjCMethod,
ObjCInterfaceDecl *CurrentClass,
ResultTypeCompatibilityKind RTC);
Expand Down
6 changes: 5 additions & 1 deletion clang/lib/AST/DeclObjC.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -823,6 +823,10 @@ ObjCMethodDecl *ObjCMethodDecl::CreateDeserialized(ASTContext &C, unsigned ID) {
Selector(), QualType(), nullptr, nullptr);
}

bool ObjCMethodDecl::isDirectMethod() const {
return hasAttr<ObjCDirectAttr>();
}

bool ObjCMethodDecl::isThisDeclarationADesignatedInitializer() const {
return getMethodFamily() == OMF_init &&
hasAttr<ObjCDesignatedInitializerAttr>();
Expand Down Expand Up @@ -1077,7 +1081,7 @@ ObjCMethodFamily ObjCMethodDecl::getMethodFamily() const {
QualType ObjCMethodDecl::getSelfType(ASTContext &Context,
const ObjCInterfaceDecl *OID,
bool &selfIsPseudoStrong,
bool &selfIsConsumed) {
bool &selfIsConsumed) const {
QualType selfTy;
selfIsPseudoStrong = false;
selfIsConsumed = false;
Expand Down
5 changes: 5 additions & 0 deletions clang/lib/AST/DeclPrinter.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1461,6 +1461,11 @@ void DeclPrinter::VisitObjCPropertyDecl(ObjCPropertyDecl *PDecl) {
first = false;
}

if (PDecl->getPropertyAttributes() & ObjCPropertyDecl::OBJC_PR_direct) {
Out << (first ? "" : ", ") << "direct";
first = false;
}

if (PDecl->getPropertyAttributes() &
ObjCPropertyDecl::OBJC_PR_nonatomic) {
Out << (first ? "" : ", ") << "nonatomic";
Expand Down
1 change: 1 addition & 0 deletions clang/lib/AST/JSONNodeDumper.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1017,6 +1017,7 @@ void JSONNodeDumper::VisitObjCPropertyDecl(const ObjCPropertyDecl *D) {
attributeOnlyIfTrue("unsafe_unretained",
Attrs & ObjCPropertyDecl::OBJC_PR_unsafe_unretained);
attributeOnlyIfTrue("class", Attrs & ObjCPropertyDecl::OBJC_PR_class);
attributeOnlyIfTrue("direct", Attrs & ObjCPropertyDecl::OBJC_PR_direct);
attributeOnlyIfTrue("nullability",
Attrs & ObjCPropertyDecl::OBJC_PR_nullability);
attributeOnlyIfTrue("null_resettable",
Expand Down
2 changes: 2 additions & 0 deletions clang/lib/AST/TextNodeDumper.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1921,6 +1921,8 @@ void TextNodeDumper::VisitObjCPropertyDecl(const ObjCPropertyDecl *D) {
OS << " unsafe_unretained";
if (Attrs & ObjCPropertyDecl::OBJC_PR_class)
OS << " class";
if (Attrs & ObjCPropertyDecl::OBJC_PR_direct)
OS << " direct";
if (Attrs & ObjCPropertyDecl::OBJC_PR_getter)
dumpDeclRef(D->getGetterMethodDecl(), "getter");
if (Attrs & ObjCPropertyDecl::OBJC_PR_setter)
Expand Down
43 changes: 32 additions & 11 deletions clang/lib/CodeGen/CGObjC.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -430,6 +430,20 @@ tryGenerateSpecializedMessageSend(CodeGenFunction &CGF, QualType ResultType,
return None;
}

CodeGen::RValue CGObjCRuntime::GeneratePossiblySpecializedMessageSend(
CodeGenFunction &CGF, ReturnValueSlot Return, QualType ResultType,
Selector Sel, llvm::Value *Receiver, const CallArgList &Args,
const ObjCInterfaceDecl *OID, const ObjCMethodDecl *Method,
bool isClassMessage) {
if (Optional<llvm::Value *> SpecializedResult =
tryGenerateSpecializedMessageSend(CGF, ResultType, Receiver, Args,
Sel, Method, isClassMessage)) {
return RValue::get(SpecializedResult.getValue());
}
return GenerateMessageSend(CGF, Return, ResultType, Sel, Receiver, Args, OID,
Method);
}

/// Instead of '[[MyClass alloc] init]', try to generate
/// 'objc_alloc_init(MyClass)'. This provides a code size improvement on the
/// caller side, as well as the optimized objc_alloc.
Expand Down Expand Up @@ -611,16 +625,9 @@ RValue CodeGenFunction::EmitObjCMessageExpr(const ObjCMessageExpr *E,
method);
} else {
// Call runtime methods directly if we can.
if (Optional<llvm::Value *> SpecializedResult =
tryGenerateSpecializedMessageSend(*this, ResultType, Receiver, Args,
E->getSelector(), method,
isClassMessage)) {
result = RValue::get(SpecializedResult.getValue());
} else {
result = Runtime.GenerateMessageSend(*this, Return, ResultType,
E->getSelector(), Receiver, Args,
OID, method);
}
result = Runtime.GeneratePossiblySpecializedMessageSend(
*this, Return, ResultType, E->getSelector(), Receiver, Args, OID,
method, isClassMessage);
}

// For delegate init calls in ARC, implicitly store the result of
Expand Down Expand Up @@ -683,7 +690,13 @@ void CodeGenFunction::StartObjCMethod(const ObjCMethodDecl *OMD,
llvm::Function *Fn = CGM.getObjCRuntime().GenerateMethod(OMD, CD);

const CGFunctionInfo &FI = CGM.getTypes().arrangeObjCMethodDeclaration(OMD);
CGM.SetInternalFunctionAttributes(OMD, Fn, FI);
if (OMD->isDirectMethod()) {
Fn->setVisibility(llvm::Function::HiddenVisibility);
CGM.SetLLVMFunctionAttributes(OMD, FI, Fn);
CGM.SetLLVMFunctionAttributesForDefinition(OMD, Fn);
} else {
CGM.SetInternalFunctionAttributes(OMD, Fn, FI);
}

args.push_back(OMD->getSelfDecl());
args.push_back(OMD->getCmdDecl());
Expand All @@ -696,6 +709,14 @@ void CodeGenFunction::StartObjCMethod(const ObjCMethodDecl *OMD,
StartFunction(OMD, OMD->getReturnType(), Fn, FI, args,
OMD->getLocation(), StartLoc);

if (OMD->isDirectMethod()) {
// This function is a direct call, it has to implement a nil check
// on entry.
//
// TODO: possibly have several entry points to elide the check
CGM.getObjCRuntime().GenerateDirectMethodPrologue(*this, Fn, OMD, CD);
}

// In ARC, certain methods get an extra cleanup.
if (CGM.getLangOpts().ObjCAutoRefCount &&
OMD->isInstanceMethod() &&
Expand Down
Loading

1 comment on commit d4e1ba3

@JuunChen
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hi, I may have found a bug.

It says in the official document

When objc_direct_members is placed on an @implementation block, methods defined in the block are considered to be declared as direct unless they have been previously declared as non-direct in any interface of the class. This includes the implicit method definitions introduced by synthesized properties, including auto-synthesized properties.

But I found that it didn't make property's setter or getter became direct method.
I wrote 3 demos, the code is as follows:

//normal_excutable
@interface ViewController ()
@property (nonatomic, copy) NSString *name;
@end

@implementation ViewController
@end
//use_objc_direct_members
@interface ViewController ()
@property (nonatomic, copy) NSString *name;
@end

__attribute__((objc_direct_members))
@implementation ViewController
@end
//use_direct_property
@interface ViewController ()
@property (nonatomic, copy, direct) NSString *name;
@end

@implementation ViewController
@end

Compile them separately and compare their Mach-O products:
image

View the instanceMethod of the ViewController in MachOView:
image

So, I don't know if I misunderstood the sentence:

This includes the implicit method definitions introduced by synthesized properties, including auto-synthesized properties.

Or the document writes error ? Or the objc_direct_members has a bug?

Please sign in to comment.