Skip to content

Commit d4e1ba3

Browse files
MadCoderdexonsmith
authored andcommitted
Implement __attribute__((objc_direct)), __attribute__((objc_direct_members))
__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
1 parent 6e20d70 commit d4e1ba3

24 files changed

+1024
-50
lines changed

clang/include/clang/AST/DeclObjC.h

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -410,7 +410,7 @@ class ObjCMethodDecl : public NamedDecl, public DeclContext {
410410
/// \return the type for \c self and set \arg selfIsPseudoStrong and
411411
/// \arg selfIsConsumed accordingly.
412412
QualType getSelfType(ASTContext &Context, const ObjCInterfaceDecl *OID,
413-
bool &selfIsPseudoStrong, bool &selfIsConsumed);
413+
bool &selfIsPseudoStrong, bool &selfIsConsumed) const;
414414

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

479+
/// True if the method is tagged as objc_direct
480+
bool isDirectMethod() const;
481+
479482
/// Returns the property associated with this method's selector.
480483
///
481484
/// Note that even if this particular method is not marked as a property
@@ -757,13 +760,14 @@ class ObjCPropertyDecl : public NamedDecl {
757760
/// property attribute rather than a type qualifier.
758761
OBJC_PR_nullability = 0x1000,
759762
OBJC_PR_null_resettable = 0x2000,
760-
OBJC_PR_class = 0x4000
763+
OBJC_PR_class = 0x4000,
764+
OBJC_PR_direct = 0x8000
761765
// Adding a property should change NumPropertyAttrsBits
762766
};
763767

764768
enum {
765769
/// Number of bits fitting all the property attributes.
766-
NumPropertyAttrsBits = 15
770+
NumPropertyAttrsBits = 16
767771
};
768772

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

887891
bool isInstanceProperty() const { return !isClassProperty(); }
888892
bool isClassProperty() const { return PropertyAttributes & OBJC_PR_class; }
893+
bool isDirectProperty() const { return PropertyAttributes & OBJC_PR_direct; }
889894

890895
ObjCPropertyQueryKind getQueryKind() const {
891896
return isClassProperty() ? ObjCPropertyQueryKind::OBJC_PR_query_class :

clang/include/clang/Basic/Attr.td

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1861,6 +1861,20 @@ def ObjCDesignatedInitializer : Attr {
18611861
let Documentation = [Undocumented];
18621862
}
18631863

1864+
def ObjCDirect : Attr {
1865+
let Spellings = [Clang<"objc_direct">];
1866+
let Subjects = SubjectList<[ObjCMethod], ErrorDiag>;
1867+
let LangOpts = [ObjC];
1868+
let Documentation = [ObjCDirectDocs];
1869+
}
1870+
1871+
def ObjCDirectMembers : Attr {
1872+
let Spellings = [Clang<"objc_direct_members">];
1873+
let Subjects = SubjectList<[ObjCImpl, ObjCCategory], ErrorDiag>;
1874+
let LangOpts = [ObjC];
1875+
let Documentation = [ObjCDirectMembersDocs];
1876+
}
1877+
18641878
def ObjCRuntimeName : Attr {
18651879
let Spellings = [Clang<"objc_runtime_name">];
18661880
let Subjects = SubjectList<[ObjCInterface, ObjCProtocol], ErrorDiag>;

clang/include/clang/Basic/AttrDocs.td

Lines changed: 98 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3912,6 +3912,104 @@ overheads associated with defining and calling such a method.
39123912
}];
39133913
}
39143914

3915+
def ObjCDirectDocs : Documentation {
3916+
let Category = DocCatDecl;
3917+
let Content = [{
3918+
The ``objc_direct`` attribute can be used to mark an Objective-C method as
3919+
being *direct*. A direct method is treated statically like an ordinary method,
3920+
but dynamically it behaves more like a C function. This lowers some of the costs
3921+
associated with the method but also sacrifices some of the ordinary capabilities
3922+
of Objective-C methods.
3923+
3924+
A message send of a direct method calls the implementation directly, as if it
3925+
were a C function, rather than using ordinary Objective-C method dispatch. This
3926+
is substantially faster and potentially allows the implementation to be inlined,
3927+
but it also means the method cannot be overridden in subclasses or replaced
3928+
dynamically, as ordinary Objective-C methods can.
3929+
3930+
Furthermore, a direct method is not listed in the class's method lists. This
3931+
substantially reduces the code-size overhead of the method but also means it
3932+
cannot be called dynamically using ordinary Objective-C method dispatch at all;
3933+
in particular, this means that it cannot override a superclass method or satisfy
3934+
a protocol requirement.
3935+
3936+
Because a direct method cannot be overridden, it is an error to perform
3937+
a ``super`` message send of one.
3938+
3939+
Although a message send of a direct method causes the method to be called
3940+
directly as if it were a C function, it still obeys Objective-C semantics in other
3941+
ways:
3942+
3943+
- If the receiver is ``nil``, the message send does nothing and returns the zero value
3944+
for the return type.
3945+
3946+
- A message send of a direct class method will cause the class to be initialized,
3947+
including calling the ``+initialize`` method if present.
3948+
3949+
- The implicit ``_cmd`` parameter containing the method's selector is still defined.
3950+
In order to minimize code-size costs, the implementation will not emit a reference
3951+
to the selector if the parameter is unused within the method.
3952+
3953+
Symbols for direct method implementations are implicitly given hidden
3954+
visibility, meaning that they can only be called within the same linkage unit.
3955+
3956+
It is an error to do any of the following:
3957+
3958+
- declare a direct method in a protocol,
3959+
- declare an override of a direct method with a method in a subclass,
3960+
- declare an override of a non-direct method with a direct method in a subclass,
3961+
- declare a method with different directness in different class interfaces, or
3962+
- implement a non-direct method (as declared in any class interface) with a direct method.
3963+
3964+
If any of these rules would be violated if every method defined in an
3965+
``@implementation`` within a single linkage unit were declared in an
3966+
appropriate class interface, the program is ill-formed with no diagnostic
3967+
required. If a violation of this rule is not diagnosed, behavior remains
3968+
well-defined; this paragraph is simply reserving the right to diagnose such
3969+
conflicts in the future, not to treat them as undefined behavior.
3970+
3971+
Additionally, Clang will warn about any ``@selector`` expression that
3972+
names a selector that is only known to be used for direct methods.
3973+
3974+
For the purpose of these rules, a "class interface" includes a class's primary
3975+
``@interface`` block, its class extensions, its categories, its declared protocols,
3976+
and all the class interfaces of its superclasses.
3977+
3978+
An Objective-C property can be declared with the ``direct`` property
3979+
attribute. If a direct property declaration causes an implicit declaration of
3980+
a getter or setter method (that is, if the given method is not explicitly
3981+
declared elsewhere), the method is declared to be direct.
3982+
3983+
Some programmers may wish to make many methods direct at once. In order
3984+
to simplify this, the ``objc_direct_members`` attribute is provided; see its
3985+
documentation for more information.
3986+
}];
3987+
}
3988+
3989+
def ObjCDirectMembersDocs : Documentation {
3990+
let Category = DocCatDecl;
3991+
let Content = [{
3992+
The ``objc_direct_members`` attribute can be placed on an Objective-C
3993+
``@interface`` or ``@implementation`` to mark that methods declared
3994+
therein should be considered direct by default. See the documentation
3995+
for ``objc_direct`` for more information about direct methods.
3996+
3997+
When ``objc_direct_members`` is placed on an ``@interface`` block, every
3998+
method in the block is considered to be declared as direct. This includes any
3999+
implicit method declarations introduced by property declarations. If the method
4000+
redeclares a non-direct method, the declaration is ill-formed, exactly as if the
4001+
method was annotated with the ``objc_direct`` attribute. ``objc_direct_members``
4002+
cannot be placed on the primary interface of a class, only on category or class
4003+
extension interfaces.
4004+
4005+
When ``objc_direct_members`` is placed on an ``@implementation`` block,
4006+
methods defined in the block are considered to be declared as direct unless
4007+
they have been previously declared as non-direct in any interface of the class.
4008+
This includes the implicit method definitions introduced by synthesized
4009+
properties, including auto-synthesized properties.
4010+
}];
4011+
}
4012+
39154013
def SelectAnyDocs : Documentation {
39164014
let Category = DocCatDecl;
39174015
let Content = [{

clang/include/clang/Basic/DiagnosticSemaKinds.td

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -988,6 +988,22 @@ def warn_objc_boxing_invalid_utf8_string : Warning<
988988
"string is ill-formed as UTF-8 and will become a null %0 when boxed">,
989989
InGroup<ObjCBoxing>;
990990

991+
def err_objc_direct_on_protocol : Error<
992+
"'objc_direct' attribute cannot be applied to %select{methods|properties}0 "
993+
"declared in an Objective-C protocol">;
994+
def err_objc_direct_missing_on_decl : Error<
995+
"direct method implementation was previously declared not direct">;
996+
def err_objc_direct_on_override : Error<
997+
"methods that %select{override superclass methods|implement protocol requirements}0 cannot be direct">;
998+
def err_objc_override_direct_method : Error<
999+
"cannot override a method that is declared direct by a superclass">;
1000+
def warn_objc_direct_ignored : Warning<
1001+
"%0 attribute isn't implemented by this Objective-C runtime">,
1002+
InGroup<IgnoredAttributes>;
1003+
def warn_objc_direct_property_ignored : Warning<
1004+
"direct attribute on property %0 ignored (not implemented by this Objective-C runtime)">,
1005+
InGroup<IgnoredAttributes>;
1006+
9911007
def warn_conflicting_overriding_ret_types : Warning<
9921008
"conflicting return type in "
9931009
"declaration of %0%diff{: $ vs $|}1,2">,
@@ -1073,6 +1089,7 @@ def warn_accessor_property_type_mismatch : Warning<
10731089
"type of property %0 does not match type of accessor %1">;
10741090
def note_conv_function_declared_at : Note<"type conversion function declared here">;
10751091
def note_method_declared_at : Note<"method %0 declared here">;
1092+
def note_direct_method_declared_at : Note<"direct method %0 declared here">;
10761093
def note_property_attribute : Note<"property %0 is declared "
10771094
"%select{deprecated|unavailable|partial}1 here">;
10781095
def err_setter_type_void : Error<"type of setter must be void">;
@@ -1308,6 +1325,8 @@ def warn_multiple_selectors: Warning<
13081325
"several methods with selector %0 of mismatched types are found "
13091326
"for the @selector expression">,
13101327
InGroup<SelectorTypeMismatch>, DefaultIgnore;
1328+
def err_direct_selector_expression: Error<
1329+
"@selector expression formed with direct selector %0">;
13111330

13121331
def err_objc_kindof_nonobject : Error<
13131332
"'__kindof' specifier cannot be applied to non-object type %0">;
@@ -1321,6 +1340,12 @@ def err_objc_method_unsupported_param_ret_type : Error<
13211340
def warn_messaging_unqualified_id : Warning<
13221341
"messaging unqualified id">, DefaultIgnore,
13231342
InGroup<DiagGroup<"objc-messaging-id">>;
1343+
def err_messaging_unqualified_id_with_direct_method : Error<
1344+
"messaging unqualified id with a method that is possibly direct">;
1345+
def err_messaging_super_with_direct_method : Error<
1346+
"messaging super with a direct method">;
1347+
def err_messaging_class_with_direct_method : Error<
1348+
"messaging a Class with a method that is possibly direct">;
13241349

13251350
// C++ declarations
13261351
def err_static_assert_expression_is_not_constant : Error<

clang/include/clang/Basic/ObjCRuntime.h

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -446,6 +446,20 @@ class ObjCRuntime {
446446
llvm_unreachable("bad kind");
447447
}
448448

449+
/// Does this runtime supports direct dispatch
450+
bool allowsDirectDispatch() const {
451+
switch (getKind()) {
452+
case FragileMacOSX: return false;
453+
case MacOSX: return true;
454+
case iOS: return true;
455+
case WatchOS: return true;
456+
case GCC: return false;
457+
case GNUstep: return false;
458+
case ObjFW: return false;
459+
}
460+
llvm_unreachable("bad kind");
461+
}
462+
449463
/// Try to parse an Objective-C runtime specification from the given
450464
/// string.
451465
///

clang/include/clang/Sema/DeclSpec.h

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -833,7 +833,8 @@ class ObjCDeclSpec {
833833
DQ_PR_unsafe_unretained = 0x800,
834834
DQ_PR_nullability = 0x1000,
835835
DQ_PR_null_resettable = 0x2000,
836-
DQ_PR_class = 0x4000
836+
DQ_PR_class = 0x4000,
837+
DQ_PR_direct = 0x8000,
837838
};
838839

839840
ObjCDeclSpec()
@@ -903,7 +904,7 @@ class ObjCDeclSpec {
903904
unsigned objcDeclQualifier : 7;
904905

905906
// NOTE: VC++ treats enums as signed, avoid using ObjCPropertyAttributeKind
906-
unsigned PropertyAttributes : 15;
907+
unsigned PropertyAttributes : 16;
907908

908909
unsigned Nullability : 2;
909910

clang/include/clang/Sema/Sema.h

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8901,6 +8901,9 @@ class Sema {
89018901
RTC_Unknown
89028902
};
89038903

8904+
void CheckObjCMethodDirectOverrides(ObjCMethodDecl *method,
8905+
ObjCMethodDecl *overridden);
8906+
89048907
void CheckObjCMethodOverrides(ObjCMethodDecl *ObjCMethod,
89058908
ObjCInterfaceDecl *CurrentClass,
89068909
ResultTypeCompatibilityKind RTC);

clang/lib/AST/DeclObjC.cpp

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -823,6 +823,10 @@ ObjCMethodDecl *ObjCMethodDecl::CreateDeserialized(ASTContext &C, unsigned ID) {
823823
Selector(), QualType(), nullptr, nullptr);
824824
}
825825

826+
bool ObjCMethodDecl::isDirectMethod() const {
827+
return hasAttr<ObjCDirectAttr>();
828+
}
829+
826830
bool ObjCMethodDecl::isThisDeclarationADesignatedInitializer() const {
827831
return getMethodFamily() == OMF_init &&
828832
hasAttr<ObjCDesignatedInitializerAttr>();
@@ -1077,7 +1081,7 @@ ObjCMethodFamily ObjCMethodDecl::getMethodFamily() const {
10771081
QualType ObjCMethodDecl::getSelfType(ASTContext &Context,
10781082
const ObjCInterfaceDecl *OID,
10791083
bool &selfIsPseudoStrong,
1080-
bool &selfIsConsumed) {
1084+
bool &selfIsConsumed) const {
10811085
QualType selfTy;
10821086
selfIsPseudoStrong = false;
10831087
selfIsConsumed = false;

clang/lib/AST/DeclPrinter.cpp

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1461,6 +1461,11 @@ void DeclPrinter::VisitObjCPropertyDecl(ObjCPropertyDecl *PDecl) {
14611461
first = false;
14621462
}
14631463

1464+
if (PDecl->getPropertyAttributes() & ObjCPropertyDecl::OBJC_PR_direct) {
1465+
Out << (first ? "" : ", ") << "direct";
1466+
first = false;
1467+
}
1468+
14641469
if (PDecl->getPropertyAttributes() &
14651470
ObjCPropertyDecl::OBJC_PR_nonatomic) {
14661471
Out << (first ? "" : ", ") << "nonatomic";

clang/lib/AST/JSONNodeDumper.cpp

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1017,6 +1017,7 @@ void JSONNodeDumper::VisitObjCPropertyDecl(const ObjCPropertyDecl *D) {
10171017
attributeOnlyIfTrue("unsafe_unretained",
10181018
Attrs & ObjCPropertyDecl::OBJC_PR_unsafe_unretained);
10191019
attributeOnlyIfTrue("class", Attrs & ObjCPropertyDecl::OBJC_PR_class);
1020+
attributeOnlyIfTrue("direct", Attrs & ObjCPropertyDecl::OBJC_PR_direct);
10201021
attributeOnlyIfTrue("nullability",
10211022
Attrs & ObjCPropertyDecl::OBJC_PR_nullability);
10221023
attributeOnlyIfTrue("null_resettable",

clang/lib/AST/TextNodeDumper.cpp

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1921,6 +1921,8 @@ void TextNodeDumper::VisitObjCPropertyDecl(const ObjCPropertyDecl *D) {
19211921
OS << " unsafe_unretained";
19221922
if (Attrs & ObjCPropertyDecl::OBJC_PR_class)
19231923
OS << " class";
1924+
if (Attrs & ObjCPropertyDecl::OBJC_PR_direct)
1925+
OS << " direct";
19241926
if (Attrs & ObjCPropertyDecl::OBJC_PR_getter)
19251927
dumpDeclRef(D->getGetterMethodDecl(), "getter");
19261928
if (Attrs & ObjCPropertyDecl::OBJC_PR_setter)

clang/lib/CodeGen/CGObjC.cpp

Lines changed: 32 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -430,6 +430,20 @@ tryGenerateSpecializedMessageSend(CodeGenFunction &CGF, QualType ResultType,
430430
return None;
431431
}
432432

433+
CodeGen::RValue CGObjCRuntime::GeneratePossiblySpecializedMessageSend(
434+
CodeGenFunction &CGF, ReturnValueSlot Return, QualType ResultType,
435+
Selector Sel, llvm::Value *Receiver, const CallArgList &Args,
436+
const ObjCInterfaceDecl *OID, const ObjCMethodDecl *Method,
437+
bool isClassMessage) {
438+
if (Optional<llvm::Value *> SpecializedResult =
439+
tryGenerateSpecializedMessageSend(CGF, ResultType, Receiver, Args,
440+
Sel, Method, isClassMessage)) {
441+
return RValue::get(SpecializedResult.getValue());
442+
}
443+
return GenerateMessageSend(CGF, Return, ResultType, Sel, Receiver, Args, OID,
444+
Method);
445+
}
446+
433447
/// Instead of '[[MyClass alloc] init]', try to generate
434448
/// 'objc_alloc_init(MyClass)'. This provides a code size improvement on the
435449
/// caller side, as well as the optimized objc_alloc.
@@ -611,16 +625,9 @@ RValue CodeGenFunction::EmitObjCMessageExpr(const ObjCMessageExpr *E,
611625
method);
612626
} else {
613627
// Call runtime methods directly if we can.
614-
if (Optional<llvm::Value *> SpecializedResult =
615-
tryGenerateSpecializedMessageSend(*this, ResultType, Receiver, Args,
616-
E->getSelector(), method,
617-
isClassMessage)) {
618-
result = RValue::get(SpecializedResult.getValue());
619-
} else {
620-
result = Runtime.GenerateMessageSend(*this, Return, ResultType,
621-
E->getSelector(), Receiver, Args,
622-
OID, method);
623-
}
628+
result = Runtime.GeneratePossiblySpecializedMessageSend(
629+
*this, Return, ResultType, E->getSelector(), Receiver, Args, OID,
630+
method, isClassMessage);
624631
}
625632

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

685692
const CGFunctionInfo &FI = CGM.getTypes().arrangeObjCMethodDeclaration(OMD);
686-
CGM.SetInternalFunctionAttributes(OMD, Fn, FI);
693+
if (OMD->isDirectMethod()) {
694+
Fn->setVisibility(llvm::Function::HiddenVisibility);
695+
CGM.SetLLVMFunctionAttributes(OMD, FI, Fn);
696+
CGM.SetLLVMFunctionAttributesForDefinition(OMD, Fn);
697+
} else {
698+
CGM.SetInternalFunctionAttributes(OMD, Fn, FI);
699+
}
687700

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

712+
if (OMD->isDirectMethod()) {
713+
// This function is a direct call, it has to implement a nil check
714+
// on entry.
715+
//
716+
// TODO: possibly have several entry points to elide the check
717+
CGM.getObjCRuntime().GenerateDirectMethodPrologue(*this, Fn, OMD, CD);
718+
}
719+
699720
// In ARC, certain methods get an extra cleanup.
700721
if (CGM.getLangOpts().ObjCAutoRefCount &&
701722
OMD->isInstanceMethod() &&

0 commit comments

Comments
 (0)