diff --git a/clang/include/clang/AST/DeclObjC.h b/clang/include/clang/AST/DeclObjC.h --- a/clang/include/clang/AST/DeclObjC.h +++ b/clang/include/clang/AST/DeclObjC.h @@ -2194,6 +2194,10 @@ data().ReferencedProtocols.set(List, Num, Locs, C); } + /// This is true iff the protocol is tagged with the `objc_static_protocol` + /// attribute. + bool isNonRuntimeProtocol() const; + ObjCProtocolDecl *lookupProtocolNamed(IdentifierInfo *PName); // Lookup a method. First, we search locally. If a method isn't diff --git a/clang/include/clang/Basic/Attr.td b/clang/include/clang/Basic/Attr.td --- a/clang/include/clang/Basic/Attr.td +++ b/clang/include/clang/Basic/Attr.td @@ -1934,6 +1934,13 @@ let Documentation = [ObjCDirectMembersDocs]; } +def ObjCNonRuntimeProtocol : Attr { + let Spellings = [Clang<"objc_non_runtime_protocol">]; + let Subjects = SubjectList<[ObjCProtocol], ErrorDiag>; + let LangOpts = [ObjC]; + let Documentation = [ObjCNonRuntimeProtocolDocs]; +} + def ObjCRuntimeName : Attr { let Spellings = [Clang<"objc_runtime_name">]; let Subjects = SubjectList<[ObjCInterface, ObjCProtocol], ErrorDiag>; diff --git a/clang/include/clang/Basic/AttrDocs.td b/clang/include/clang/Basic/AttrDocs.td --- a/clang/include/clang/Basic/AttrDocs.td +++ b/clang/include/clang/Basic/AttrDocs.td @@ -4137,6 +4137,21 @@ }]; } +def ObjCNonRuntimeProtocolDocs : Documentation { + let Category = DocCatDecl; + let Content = [{ +The ``objc_non_runtime_protocol`` attribute can be used to mark an Objective-C +to not generate runtime metadata. A non-runtime protocol is used only to +perform compile time checks on it's conformances. A standard protocol will emit +various chunks of metadata to enable dynamic runtime behaviors via, e.g., +``@protocol`` and ``objc_getProtocol``. These two tools require metadata +emitted into the binary that is loaded at runtime. With +``objc_non_runtime_protocol`` these chunks of metadata are removed. If you only +intend to use protocols to implement compile time behaviors then the metadata is +uneeded overhead. + }]; +} + def SelectAnyDocs : Documentation { let Category = DocCatDecl; let Content = [{ diff --git a/clang/include/clang/Basic/DiagnosticSemaKinds.td b/clang/include/clang/Basic/DiagnosticSemaKinds.td --- a/clang/include/clang/Basic/DiagnosticSemaKinds.td +++ b/clang/include/clang/Basic/DiagnosticSemaKinds.td @@ -1017,6 +1017,8 @@ "string is ill-formed as UTF-8 and will become a null %0 when boxed">, InGroup; +def err_objc_non_runtime_protocol_in_protocol_expr : Error< + "can't use a protocol declared 'objc_non_runtime_protocol' in a @protocol expression">; def err_objc_direct_on_protocol : Error< "'objc_direct' attribute cannot be applied to %select{methods|properties}0 " "declared in an Objective-C protocol">; @@ -1038,6 +1040,9 @@ def warn_objc_direct_property_ignored : Warning< "direct attribute on property %0 ignored (not implemented by this Objective-C runtime)">, InGroup; +def warn_objc_non_runtime_protocol_ignored : Warning< + "non_runtime_protocol attribute on protocol %0 ignored (not implemented by this Objective-C runtime)">, + InGroup; def err_objc_direct_dynamic_property : Error< "direct property cannot be @dynamic">; diff --git a/clang/include/clang/Basic/ObjCRuntime.h b/clang/include/clang/Basic/ObjCRuntime.h --- a/clang/include/clang/Basic/ObjCRuntime.h +++ b/clang/include/clang/Basic/ObjCRuntime.h @@ -460,6 +460,27 @@ llvm_unreachable("bad kind"); } + /// Returns true if this Objective-C runtime supports non-runtime Protocols. + bool allowsNonRuntimeProtocols() 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. /// diff --git a/clang/lib/AST/DeclObjC.cpp b/clang/lib/AST/DeclObjC.cpp --- a/clang/lib/AST/DeclObjC.cpp +++ b/clang/lib/AST/DeclObjC.cpp @@ -1896,6 +1896,10 @@ return Result; } +bool ObjCProtocolDecl::isNonRuntimeProtocol() const { + return hasAttr(); +} + ObjCProtocolDecl *ObjCProtocolDecl::lookupProtocolNamed(IdentifierInfo *Name) { ObjCProtocolDecl *PDecl = this; diff --git a/clang/lib/CodeGen/CGObjCMac.cpp b/clang/lib/CodeGen/CGObjCMac.cpp --- a/clang/lib/CodeGen/CGObjCMac.cpp +++ b/clang/lib/CodeGen/CGObjCMac.cpp @@ -3030,7 +3030,8 @@ // it now. Otherwise do nothing, the protocol objects are lazily // emitted. if (Protocols.count(PD->getIdentifier())) - GetOrEmitProtocol(PD); + if (!PD->isNonRuntimeProtocol()) + GetOrEmitProtocol(PD); } llvm::Constant *CGObjCCommonMac::GetProtocolRef(const ObjCProtocolDecl *PD) { @@ -6675,7 +6676,8 @@ // This routine is called for @protocol only. So, we must build definition // of protocol's meta-data (not a reference to it!) - // + assert(!PD->isNonRuntimeProtocol() && + "attempting to get a protocol ref to a static protocol."); llvm::Constant *Init = llvm::ConstantExpr::getBitCast(GetOrEmitProtocol(PD), ObjCTypes.getExternalProtocolPtrTy()); @@ -7032,6 +7034,8 @@ const ObjCProtocolDecl *PD) { llvm::GlobalVariable *&Entry = Protocols[PD->getIdentifier()]; + assert(!PD->isNonRuntimeProtocol() && + "attempting to GetOrEmit a non-runtime protocol"); if (!Entry) { // We use the initializer as a marker of whether this is a forward // reference or not. At module finalization we add the empty @@ -7072,6 +7076,9 @@ llvm::Constant *CGObjCNonFragileABIMac::GetOrEmitProtocol( const ObjCProtocolDecl *PD) { + + assert(!PD->isNonRuntimeProtocol() && + "attempting to GetOrEmit non-runtime protocol"); llvm::GlobalVariable *Entry = Protocols[PD->getIdentifier()]; // Early exit if a defining object has already been generated. @@ -7181,6 +7188,18 @@ if (begin == end) return llvm::Constant::getNullValue(ObjCTypes.ProtocolListnfABIPtrTy); + std::vector protocols; + for (; begin != end; ++begin) { + auto it = *begin; + if (it->isNonRuntimeProtocol()) + continue; + protocols.push_back(GetProtocolRef(it)); + } + // If all of the protocols in the protocol list are objc_non_runtime_protocol + // just return null + if (protocols.size() == 0) + return llvm::Constant::getNullValue(ObjCTypes.ProtocolListnfABIPtrTy); + // FIXME: We shouldn't need to do this lookup here, should we? SmallString<256> TmpName; Name.toVector(TmpName); @@ -7195,8 +7214,8 @@ // A null-terminated array of protocols. auto array = values.beginArray(ObjCTypes.ProtocolnfABIPtrTy); - for (; begin != end; ++begin) - array.add(GetProtocolRef(*begin)); // Implemented??? + for (auto const &proto : protocols) + array.add(proto); auto count = array.size(); array.addNullPointer(ObjCTypes.ProtocolnfABIPtrTy); diff --git a/clang/lib/Sema/SemaDeclAttr.cpp b/clang/lib/Sema/SemaDeclAttr.cpp --- a/clang/lib/Sema/SemaDeclAttr.cpp +++ b/clang/lib/Sema/SemaDeclAttr.cpp @@ -2603,6 +2603,15 @@ D->addAttr(newAttr); } +static void handleObjCNonRuntimeProtocolAttr(Sema &S, Decl *D, + const ParsedAttr &AL) { + if (S.getLangOpts().ObjCRuntime.allowsNonRuntimeProtocols()) { + handleSimpleAttribute(S, D, AL); + } else { + S.Diag(AL.getLoc(), diag::warn_objc_non_runtime_protocol_ignored) << AL; + } +} + static void handleObjCDirectAttr(Sema &S, Decl *D, const ParsedAttr &AL) { // objc_direct cannot be set on methods declared in the context of a protocol if (isa(D->getDeclContext())) { @@ -7146,6 +7155,9 @@ case ParsedAttr::AT_ObjCDirect: handleObjCDirectAttr(S, D, AL); break; + case ParsedAttr::AT_ObjCNonRuntimeProtocol: + handleObjCNonRuntimeProtocolAttr(S, D, AL); + break; case ParsedAttr::AT_ObjCDirectMembers: handleObjCDirectMembersAttr(S, D, AL); handleSimpleAttribute(S, D, AL); diff --git a/clang/lib/Sema/SemaExprObjC.cpp b/clang/lib/Sema/SemaExprObjC.cpp --- a/clang/lib/Sema/SemaExprObjC.cpp +++ b/clang/lib/Sema/SemaExprObjC.cpp @@ -1280,6 +1280,9 @@ Diag(ProtoLoc, diag::err_undeclared_protocol) << ProtocolId; return true; } + if (PDecl->isNonRuntimeProtocol()) + Diag(ProtoLoc, diag::err_objc_non_runtime_protocol_in_protocol_expr) + << PDecl; if (!PDecl->hasDefinition()) { Diag(ProtoLoc, diag::err_atprotocol_protocol) << PDecl; Diag(PDecl->getLocation(), diag::note_entity_declared_at) << PDecl; diff --git a/clang/test/CodeGenObjC/non-runtime-protocol.m b/clang/test/CodeGenObjC/non-runtime-protocol.m new file mode 100644 --- /dev/null +++ b/clang/test/CodeGenObjC/non-runtime-protocol.m @@ -0,0 +1,45 @@ +// RUN: %clang_cc1 -emit-llvm -fobjc-arc -triple x86_64-apple-darwin10 %s -o - \ +// RUN: | FileCheck %s +// RUN: not %clang_cc1 -emit-llvm -fobjc-arc -triple x86_64-apple-darwin10 %s -DPROTOEXPR -o - 2>&1 \ +// RUN: | FileCheck -check-prefix=PROTOEXPR %s + +__attribute__((objc_root_class)) +@interface Root +@end +@implementation Root +@end + +// Confirm that we're not emitting protocol information for the +// CHECK-NOT: OBJC_CLASS_NAME{{.*}}NonRuntimeProtocol +// CHECK-NOT: _OBJC_$_PROTOCOL_INSTANCE_METHODS_NonRuntimeProtocol +// CHECK-NOT: _OBJC_$_PROTOCOL_CLASS_METHODS_NonRuntimeProtocol +// CHECK-NOT: _OBJC_PROTOCOL_$_NonRuntimeProtocol +// CHECK-NOT: _OBJC_LABEL_PROTOCOL_$_NonRuntimeProtocol +// CHECK-NOT: _OBJC_CLASS_PROTOCOLS_$_NonRuntimeImplementer +// CHECK-NOT: @llvm.compiler.used {{.*}}NonRuntimeProtocol +__attribute__((objc_non_runtime_protocol)) +@protocol NonRuntimeProtocol +- (void)doThing; ++ (void)doClassThing; +@end +// CHECK: @"_OBJC_METACLASS_RO_$_NonRuntimeImplementer" {{.*}} %struct._objc_protocol_list* null +// CHECK: @"_OBJC_CLASS_RO_$_NonRuntimeImplementer" {{.*}} %struct._objc_protocol_list* null +@interface NonRuntimeImplementer : Root +- (void)doThing; ++ (void)doClassThing; +@end + +@implementation NonRuntimeImplementer +- (void)doThing {} ++ (void)doClassThing {} +@end + +void useNonRuntime(NonRuntimeImplementer *si) { + [si doThing]; + [NonRuntimeImplementer doClassThing]; + +#ifdef PROTOEXPR +// PROTOEXPR: can't use a protocol declared 'objc_non_runtime_protocol' in a @protocol expression + Protocol* p = @protocol(NonRuntimeProtocol); +#endif +} diff --git a/clang/test/Misc/pragma-attribute-supported-attributes-list.test b/clang/test/Misc/pragma-attribute-supported-attributes-list.test --- a/clang/test/Misc/pragma-attribute-supported-attributes-list.test +++ b/clang/test/Misc/pragma-attribute-supported-attributes-list.test @@ -114,6 +114,7 @@ // CHECK-NEXT: ObjCExternallyRetained (SubjectMatchRule_variable_not_is_parameter, SubjectMatchRule_function, SubjectMatchRule_block, SubjectMatchRule_objc_method) // CHECK-NEXT: ObjCMethodFamily (SubjectMatchRule_objc_method) // CHECK-NEXT: ObjCNonLazyClass (SubjectMatchRule_objc_interface, SubjectMatchRule_objc_implementation) +// CHECK-NEXT: ObjCNonRuntimeProtocol (SubjectMatchRule_objc_protocol) // CHECK-NEXT: ObjCPreciseLifetime (SubjectMatchRule_variable) // CHECK-NEXT: ObjCRequiresPropertyDefs (SubjectMatchRule_objc_interface) // CHECK-NEXT: ObjCRequiresSuper (SubjectMatchRule_objc_method)