[clang] Implement objc_non_runtime_protocol to remove protocol metadata
Summary:
Motivated by the new objc_direct attribute, this change adds a new
attribute that remotes metadata from Protocols that the programmer knows
isn't going to be used at runtime. We simply have the frontend skip
generating any protocol metadata entries (e.g. OBJC_CLASS_NAME,
_OBJC_$_PROTOCOL_INSTANCE_METHDOS, _OBJC_PROTOCOL, etc) for a protocol
marked with `__attribute__((objc_non_runtime_protocol))`.
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 return `NULL` which
is consistent with the behavior of a non-existing protocol.
Subscribers: cfe-commits
Tags: #clang
Differential Revision: https://reviews.llvm.org/D75574
diff --git a/clang/lib/CodeGen/CGObjC.cpp b/clang/lib/CodeGen/CGObjC.cpp
index 99b896a..f905e17e 100644
--- a/clang/lib/CodeGen/CGObjC.cpp
+++ b/clang/lib/CodeGen/CGObjC.cpp
@@ -445,6 +445,75 @@
Method);
}
+static void AppendFirstImpliedRuntimeProtocols(
+ const ObjCProtocolDecl *PD,
+ llvm::UniqueVector<const ObjCProtocolDecl *> &PDs) {
+ if (!PD->isNonRuntimeProtocol()) {
+ const auto *Can = PD->getCanonicalDecl();
+ PDs.insert(Can);
+ return;
+ }
+
+ for (const auto *ParentPD : PD->protocols())
+ AppendFirstImpliedRuntimeProtocols(ParentPD, PDs);
+}
+
+std::vector<const ObjCProtocolDecl *>
+CGObjCRuntime::GetRuntimeProtocolList(ObjCProtocolDecl::protocol_iterator begin,
+ ObjCProtocolDecl::protocol_iterator end) {
+ std::vector<const ObjCProtocolDecl *> RuntimePds;
+ llvm::DenseSet<const ObjCProtocolDecl *> NonRuntimePDs;
+
+ for (; begin != end; ++begin) {
+ const auto *It = *begin;
+ const auto *Can = It->getCanonicalDecl();
+ if (Can->isNonRuntimeProtocol())
+ NonRuntimePDs.insert(Can);
+ else
+ RuntimePds.push_back(Can);
+ }
+
+ // If there are no non-runtime protocols then we can just stop now.
+ if (NonRuntimePDs.empty())
+ return RuntimePds;
+
+ // Else we have to search through the non-runtime protocol's inheritancy
+ // hierarchy DAG stopping whenever a branch either finds a runtime protocol or
+ // a non-runtime protocol without any parents. These are the "first-implied"
+ // protocols from a non-runtime protocol.
+ llvm::UniqueVector<const ObjCProtocolDecl *> FirstImpliedProtos;
+ for (const auto *PD : NonRuntimePDs)
+ AppendFirstImpliedRuntimeProtocols(PD, FirstImpliedProtos);
+
+ // Walk the Runtime list to get all protocols implied via the inclusion of
+ // this protocol, e.g. all protocols it inherits from including itself.
+ llvm::DenseSet<const ObjCProtocolDecl *> AllImpliedProtocols;
+ for (const auto *PD : RuntimePds) {
+ const auto *Can = PD->getCanonicalDecl();
+ AllImpliedProtocols.insert(Can);
+ Can->getImpliedProtocols(AllImpliedProtocols);
+ }
+
+ // Similar to above, walk the list of first-implied protocols to find the set
+ // all the protocols implied excluding the listed protocols themselves since
+ // they are not yet a part of the `RuntimePds` list.
+ for (const auto *PD : FirstImpliedProtos) {
+ PD->getImpliedProtocols(AllImpliedProtocols);
+ }
+
+ // From the first-implied list we have to finish building the final protocol
+ // list. If a protocol in the first-implied list was already implied via some
+ // inheritance path through some other protocols then it would be redundant to
+ // add it here and so we skip over it.
+ for (const auto *PD : FirstImpliedProtos) {
+ if (!AllImpliedProtocols.contains(PD)) {
+ RuntimePds.push_back(PD);
+ }
+ }
+
+ return RuntimePds;
+}
+
/// 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.
diff --git a/clang/lib/CodeGen/CGObjCGNU.cpp b/clang/lib/CodeGen/CGObjCGNU.cpp
index ed36e4a..c6500c0 100644
--- a/clang/lib/CodeGen/CGObjCGNU.cpp
+++ b/clang/lib/CodeGen/CGObjCGNU.cpp
@@ -1187,8 +1187,11 @@
}
llvm::Constant *GenerateCategoryProtocolList(const ObjCCategoryDecl *OCD)
override {
- SmallVector<llvm::Constant*, 16> Protocols;
- for (const auto *PI : OCD->getReferencedProtocols())
+ const auto &ReferencedProtocols = OCD->getReferencedProtocols();
+ auto RuntimeProtocols = GetRuntimeProtocolList(ReferencedProtocols.begin(),
+ ReferencedProtocols.end());
+ SmallVector<llvm::Constant *, 16> Protocols;
+ for (const auto *PI : RuntimeProtocols)
Protocols.push_back(
llvm::ConstantExpr::getBitCast(GenerateProtocolRef(PI),
ProtocolPtrTy));
@@ -1371,7 +1374,9 @@
}
SmallVector<llvm::Constant*, 16> Protocols;
- for (const auto *PI : PD->protocols())
+ auto RuntimeProtocols =
+ GetRuntimeProtocolList(PD->protocol_begin(), PD->protocol_end());
+ for (const auto *PI : RuntimeProtocols)
Protocols.push_back(
llvm::ConstantExpr::getBitCast(GenerateProtocolRef(PI),
ProtocolPtrTy));
@@ -1910,8 +1915,10 @@
// struct objc_class *sibling_class
classFields.addNullPointer(PtrTy);
// struct objc_protocol_list *protocols;
- SmallVector<llvm::Constant*, 16> Protocols;
- for (const auto *I : classDecl->protocols())
+ auto RuntimeProtocols = GetRuntimeProtocolList(classDecl->protocol_begin(),
+ classDecl->protocol_end());
+ SmallVector<llvm::Constant *, 16> Protocols;
+ for (const auto *I : RuntimeProtocols)
Protocols.push_back(
llvm::ConstantExpr::getBitCast(GenerateProtocolRef(I),
ProtocolPtrTy));
@@ -3076,6 +3083,9 @@
}
void CGObjCGNU::GenerateProtocol(const ObjCProtocolDecl *PD) {
+ if (PD->isNonRuntimeProtocol())
+ return;
+
std::string ProtocolName = PD->getNameAsString();
// Use the protocol definition, if there is one.
@@ -3228,8 +3238,11 @@
llvm::Constant *CGObjCGNU::GenerateCategoryProtocolList(const
ObjCCategoryDecl *OCD) {
+ const auto &RefPro = OCD->getReferencedProtocols();
+ const auto RuntimeProtos =
+ GetRuntimeProtocolList(RefPro.begin(), RefPro.end());
SmallVector<std::string, 16> Protocols;
- for (const auto *PD : OCD->getReferencedProtocols())
+ for (const auto *PD : RuntimeProtos)
Protocols.push_back(PD->getNameAsString());
return GenerateProtocolList(Protocols);
}
@@ -3515,8 +3528,11 @@
llvm::Constant *Properties = GeneratePropertyList(OID, ClassDecl);
// Collect the names of referenced protocols
+ auto RefProtocols = ClassDecl->protocols();
+ auto RuntimeProtocols =
+ GetRuntimeProtocolList(RefProtocols.begin(), RefProtocols.end());
SmallVector<std::string, 16> Protocols;
- for (const auto *I : ClassDecl->protocols())
+ for (const auto *I : RuntimeProtocols)
Protocols.push_back(I->getNameAsString());
// Get the superclass pointer.
diff --git a/clang/lib/CodeGen/CGObjCMac.cpp b/clang/lib/CodeGen/CGObjCMac.cpp
index aa50d21..dff8674 100644
--- a/clang/lib/CodeGen/CGObjCMac.cpp
+++ b/clang/lib/CodeGen/CGObjCMac.cpp
@@ -32,6 +32,7 @@
#include "llvm/ADT/SetVector.h"
#include "llvm/ADT/SmallPtrSet.h"
#include "llvm/ADT/SmallString.h"
+#include "llvm/ADT/UniqueVector.h"
#include "llvm/IR/DataLayout.h"
#include "llvm/IR/InlineAsm.h"
#include "llvm/IR/IntrinsicInst.h"
@@ -3196,7 +3197,8 @@
ObjCProtocolDecl::protocol_iterator begin,
ObjCProtocolDecl::protocol_iterator end) {
// Just return null for empty protocol lists
- if (begin == end)
+ auto PDs = GetRuntimeProtocolList(begin, end);
+ if (PDs.empty())
return llvm::Constant::getNullValue(ObjCTypes.ProtocolListPtrTy);
ConstantInitBuilder builder(CGM);
@@ -3209,9 +3211,9 @@
auto countSlot = values.addPlaceholder();
auto refsArray = values.beginArray(ObjCTypes.ProtocolPtrTy);
- for (; begin != end; ++begin) {
- refsArray.add(GetProtocolRef(*begin));
- }
+ for (const auto *Proto : PDs)
+ refsArray.add(GetProtocolRef(Proto));
+
auto count = refsArray.size();
// This list is null terminated.
@@ -6648,7 +6650,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());
@@ -7005,6 +7008,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
@@ -7148,10 +7153,20 @@
CGObjCNonFragileABIMac::EmitProtocolList(Twine Name,
ObjCProtocolDecl::protocol_iterator begin,
ObjCProtocolDecl::protocol_iterator end) {
- SmallVector<llvm::Constant *, 16> ProtocolRefs;
-
// Just return null for empty protocol lists
- if (begin == end)
+ auto Protocols = GetRuntimeProtocolList(begin, end);
+ if (Protocols.empty())
+ return llvm::Constant::getNullValue(ObjCTypes.ProtocolListnfABIPtrTy);
+
+ SmallVector<llvm::Constant *, 16> ProtocolRefs;
+ ProtocolRefs.reserve(Protocols.size());
+
+ for (const auto *PD : Protocols)
+ ProtocolRefs.push_back(GetProtocolRef(PD));
+
+ // If all of the protocols in the protocol list are objc_non_runtime_protocol
+ // just return null
+ if (ProtocolRefs.size() == 0)
return llvm::Constant::getNullValue(ObjCTypes.ProtocolListnfABIPtrTy);
// FIXME: We shouldn't need to do this lookup here, should we?
@@ -7168,8 +7183,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 : ProtocolRefs)
+ array.add(proto);
auto count = array.size();
array.addNullPointer(ObjCTypes.ProtocolnfABIPtrTy);
diff --git a/clang/lib/CodeGen/CGObjCRuntime.h b/clang/lib/CodeGen/CGObjCRuntime.h
index 60f9838..f56101d 100644
--- a/clang/lib/CodeGen/CGObjCRuntime.h
+++ b/clang/lib/CodeGen/CGObjCRuntime.h
@@ -20,6 +20,7 @@
#include "CGValue.h"
#include "clang/AST/DeclObjC.h"
#include "clang/Basic/IdentifierTable.h" // Selector
+#include "llvm/ADT/UniqueVector.h"
namespace llvm {
class Constant;
@@ -205,6 +206,16 @@
const CallArgList &CallArgs,
const ObjCMethodDecl *Method = nullptr) = 0;
+ /// Walk the list of protocol references from a class, category or
+ /// protocol to traverse the DAG formed from it's inheritance hierarchy. Find
+ /// the list of protocols that ends each walk at either a runtime
+ /// protocol or a non-runtime protocol with no parents. For the common case of
+ /// just a list of standard runtime protocols this just returns the same list
+ /// that was passed in.
+ std::vector<const ObjCProtocolDecl *>
+ GetRuntimeProtocolList(ObjCProtocolDecl::protocol_iterator begin,
+ ObjCProtocolDecl::protocol_iterator end);
+
/// Emit the code to return the named protocol as an object, as in a
/// \@protocol expression.
virtual llvm::Value *GenerateProtocolRef(CodeGenFunction &CGF,