lanza created this revision.
Herald added a project: clang.
Herald added a subscriber: cfe-commits.

Motivated by the new objc_direct attribute, this change aims to limit
metadata generation from Protocols that the programmer knows isn't
going to be used at runtime. This attribute simply causes the frontend
to not generate any protocol metadata entries (e.g. OBJC_CLASS_NAME,
_OBJC_$_PROTOCOL_INSTANCE_METHDOS, _OBJC_PROTOCOL, etc) for a protocol
marked with `__attribute__((objc_direct))`.

There are a few APIs used to retrieve a protocol at runtime.
`@protocol(SomeProtocol)` will now error out of the requested protocol
is marked with attribute. `objc_getProtocol` will returl `NULL` which
is consistent with the behavior of a non-existing protocol.


Repository:
  rG LLVM Github Monorepo

https://reviews.llvm.org/D75574

Files:
  clang/include/clang/AST/DeclObjC.h
  clang/include/clang/Basic/Attr.td
  clang/include/clang/Basic/AttrDocs.td
  clang/include/clang/Basic/DiagnosticSemaKinds.td
  clang/include/clang/Basic/ObjCRuntime.h
  clang/lib/AST/DeclObjC.cpp
  clang/lib/CodeGen/CGObjCMac.cpp
  clang/lib/Sema/SemaDeclAttr.cpp
  clang/lib/Sema/SemaExprObjC.cpp
  clang/test/CodeGenObjC/static-protocol.m

Index: clang/test/CodeGenObjC/static-protocol.m
===================================================================
--- /dev/null
+++ clang/test/CodeGenObjC/static-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{{.*}}StaticProtocol
+// CHECK-NOT: _OBJC_$_PROTOCOL_INSTANCE_METHODS_StaticProtocol
+// CHECK-NOT: _OBJC_$_PROTOCOL_CLASS_METHODS_StaticProtocol
+// CHECK-NOT: _OBJC_PROTOCOL_$_StaticProtocol
+// CHECK-NOT: _OBJC_LABEL_PROTOCOL_$_StaticProtocol
+// CHECK-NOT: _OBJC_CLASS_PROTOCOLS_$_StaticImplementer
+// CHECK-NOT: @llvm.compiler.used {{.*}}StaticProtocol
+__attribute__((objc_static_protocol))
+@protocol StaticProtocol
+- (void)doThing;
++ (void)doClassThing;
+@end
+// CHECK: @"_OBJC_METACLASS_RO_$_StaticImplementer" {{.*}} %struct._objc_protocol_list* null
+// CHECK: @"_OBJC_CLASS_RO_$_StaticImplementer" {{.*}} %struct._objc_protocol_list* null
+@interface StaticImplementer : Root <StaticProtocol>
+- (void)doThing;
++ (void)doClassThing;
+@end
+
+@implementation StaticImplementer
+- (void)doThing {}
++ (void)doClassThing {}
+@end
+
+void useStatic(StaticImplementer *si) {
+  [si doThing];
+  [StaticImplementer doClassThing];
+
+#ifdef PROTOEXPR
+// PROTOEXPR: can't use a protocol declared 'objc_static_protocol' in a @protocol expression
+  Protocol* p = @protocol(StaticProtocol);
+#endif
+}
Index: clang/lib/Sema/SemaExprObjC.cpp
===================================================================
--- clang/lib/Sema/SemaExprObjC.cpp
+++ clang/lib/Sema/SemaExprObjC.cpp
@@ -1280,6 +1280,8 @@
     Diag(ProtoLoc, diag::err_undeclared_protocol) << ProtocolId;
     return true;
   }
+  if (PDecl->isStaticProtocol())
+    Diag(ProtoLoc, diag::err_objc_static_protocol_in_protocol_expr) << PDecl;
   if (!PDecl->hasDefinition()) {
     Diag(ProtoLoc, diag::err_atprotocol_protocol) << PDecl;
     Diag(PDecl->getLocation(), diag::note_entity_declared_at) << PDecl;
Index: clang/lib/Sema/SemaDeclAttr.cpp
===================================================================
--- clang/lib/Sema/SemaDeclAttr.cpp
+++ clang/lib/Sema/SemaDeclAttr.cpp
@@ -2604,6 +2604,15 @@
     D->addAttr(newAttr);
 }
 
+static void handleObjCStaticProtocolAttr(Sema &S, Decl *D,
+                                         const ParsedAttr &AL) {
+  if (S.getLangOpts().ObjCRuntime.allowsStaticProtocols()) {
+    handleSimpleAttribute<ObjCStaticProtocolAttr>(S, D, AL);
+  } else {
+    S.Diag(AL.getLoc(), diag::warn_objc_static_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<ObjCProtocolDecl>(D->getDeclContext())) {
@@ -7095,6 +7104,9 @@
   case ParsedAttr::AT_ObjCDirect:
     handleObjCDirectAttr(S, D, AL);
     break;
+  case ParsedAttr::AT_ObjCStaticProtocol:
+    handleObjCStaticProtocolAttr(S, D, AL);
+    break;
   case ParsedAttr::AT_ObjCDirectMembers:
     handleObjCDirectMembersAttr(S, D, AL);
     handleSimpleAttribute<ObjCDirectMembersAttr>(S, D, AL);
Index: clang/lib/CodeGen/CGObjCMac.cpp
===================================================================
--- clang/lib/CodeGen/CGObjCMac.cpp
+++ clang/lib/CodeGen/CGObjCMac.cpp
@@ -3031,7 +3031,8 @@
   // it now. Otherwise do nothing, the protocol objects are lazily
   // emitted.
   if (Protocols.count(PD->getIdentifier()))
-    GetOrEmitProtocol(PD);
+    if (!PD->isStaticProtocol())
+      GetOrEmitProtocol(PD);
 }
 
 llvm::Constant *CGObjCCommonMac::GetProtocolRef(const ObjCProtocolDecl *PD) {
@@ -6674,10 +6675,11 @@
 
   // This routine is called for @protocol only. So, we must build definition
   // of protocol's meta-data (not a reference to it!)
-  //
+  assert(!PD->isStaticProtocol() &&
+         "attempting to get a protocol ref to a static protocol.");
   llvm::Constant *Init =
-    llvm::ConstantExpr::getBitCast(GetOrEmitProtocol(PD),
-                                   ObjCTypes.getExternalProtocolPtrTy());
+    llvm::ConstantExpr::getBitCast(
+      GetOrEmitProtocol(PD), ObjCTypes.getExternalProtocolPtrTy());
 
   std::string ProtocolName("_OBJC_PROTOCOL_REFERENCE_$_");
   ProtocolName += PD->getObjCRuntimeNameAsString();
@@ -7031,6 +7033,7 @@
   const ObjCProtocolDecl *PD) {
   llvm::GlobalVariable *&Entry = Protocols[PD->getIdentifier()];
 
+  assert(PD->isStaticProtocol() && "attempting to GetOrEmit static 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
@@ -7071,6 +7074,8 @@
 
 llvm::Constant *CGObjCNonFragileABIMac::GetOrEmitProtocol(
   const ObjCProtocolDecl *PD) {
+
+  assert(!PD->isStaticProtocol() && "attempting to GetOrEmit static protocol");
   llvm::GlobalVariable *Entry = Protocols[PD->getIdentifier()];
 
   // Early exit if a defining object has already been generated.
@@ -7180,6 +7185,19 @@
   if (begin == end)
     return llvm::Constant::getNullValue(ObjCTypes.ProtocolListnfABIPtrTy);
 
+
+  std::vector<llvm::Constant*> protocols;
+  for (; begin != end; ++begin) {
+    auto it = *begin;
+    if (it->isStaticProtocol())
+      continue;
+    protocols.push_back(GetProtocolRef(it));
+  }
+  // If all of the protocols in the protocol list are objc_static_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);
@@ -7194,8 +7212,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);
 
Index: clang/lib/AST/DeclObjC.cpp
===================================================================
--- clang/lib/AST/DeclObjC.cpp
+++ clang/lib/AST/DeclObjC.cpp
@@ -1898,6 +1898,10 @@
   return Result;
 }
 
+bool ObjCProtocolDecl::isStaticProtocol() const {
+  return hasAttr<ObjCStaticProtocolAttr>();
+}
+
 ObjCProtocolDecl *ObjCProtocolDecl::lookupProtocolNamed(IdentifierInfo *Name) {
   ObjCProtocolDecl *PDecl = this;
 
Index: clang/include/clang/Basic/ObjCRuntime.h
===================================================================
--- clang/include/clang/Basic/ObjCRuntime.h
+++ clang/include/clang/Basic/ObjCRuntime.h
@@ -460,6 +460,20 @@
     llvm_unreachable("bad kind");
   }
 
+  /// Does this runtime supprt static protocols
+  bool allowsStaticProtocols() 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.
   ///
Index: clang/include/clang/Basic/DiagnosticSemaKinds.td
===================================================================
--- clang/include/clang/Basic/DiagnosticSemaKinds.td
+++ clang/include/clang/Basic/DiagnosticSemaKinds.td
@@ -1015,6 +1015,8 @@
   "string is ill-formed as UTF-8 and will become a null %0 when boxed">,
   InGroup<ObjCBoxing>;
 
+def err_objc_static_protocol_in_protocol_expr : Error<
+  "can't use a protocol declared 'objc_static_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">;
@@ -1036,6 +1038,9 @@
 def warn_objc_direct_property_ignored : Warning<
   "direct attribute on property %0 ignored (not implemented by this Objective-C runtime)">,
   InGroup<IgnoredAttributes>;
+def warn_objc_static_protocol_ignored : Warning<
+  "static_protocol attribute on protocol %0 ignored (not implemented by this Objective-C runtime)">,
+  InGroup<IgnoredAttributes>;
 def err_objc_direct_dynamic_property : Error<
   "direct property cannot be @dynamic">;
 
Index: clang/include/clang/Basic/AttrDocs.td
===================================================================
--- clang/include/clang/Basic/AttrDocs.td
+++ clang/include/clang/Basic/AttrDocs.td
@@ -4137,6 +4137,23 @@
   }];
 }
 
+def ObjCStaticProtocolDocs : Documentation {
+  let Category = DocCatDecl;
+  let Content = [{
+The ``objc_static_protocol`` attribute can be used to mark an Objective-C
+protocol as being *static*.  A static protocol is used only to perform compile
+time checks on it's conformances. A standard non-staic 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_static_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.
+
+
+IDK finish this...
+  }];
+}
+
 def SelectAnyDocs : Documentation {
   let Category = DocCatDecl;
   let Content = [{
Index: clang/include/clang/Basic/Attr.td
===================================================================
--- clang/include/clang/Basic/Attr.td
+++ clang/include/clang/Basic/Attr.td
@@ -1931,6 +1931,13 @@
   let Documentation = [ObjCDirectMembersDocs];
 }
 
+def ObjCStaticProtocol : Attr {
+  let Spellings = [Clang<"objc_static_protocol">];
+  let Subjects = SubjectList<[ObjCProtocol], ErrorDiag>;
+  let LangOpts = [ObjC];
+  let Documentation = [ObjCStaticProtocolDocs];
+}
+
 def ObjCRuntimeName : Attr {
   let Spellings = [Clang<"objc_runtime_name">];
   let Subjects = SubjectList<[ObjCInterface, ObjCProtocol], ErrorDiag>;
Index: clang/include/clang/AST/DeclObjC.h
===================================================================
--- clang/include/clang/AST/DeclObjC.h
+++ clang/include/clang/AST/DeclObjC.h
@@ -2194,6 +2194,9 @@
     data().ReferencedProtocols.set(List, Num, Locs, C);
   }
 
+  /// True if the protocol is tagged as objc_static_protocol
+  bool isStaticProtocol() const;
+
   ObjCProtocolDecl *lookupProtocolNamed(IdentifierInfo *PName);
 
   // Lookup a method. First, we search locally. If a method isn't
_______________________________________________
cfe-commits mailing list
cfe-commits@lists.llvm.org
https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits

Reply via email to