Skip to content

Commit 14f6bfc

Browse files
committed
[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
1 parent ace6440 commit 14f6bfc

File tree

13 files changed

+336
-16
lines changed

13 files changed

+336
-16
lines changed

clang/include/clang/AST/DeclObjC.h

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2178,6 +2178,14 @@ class ObjCProtocolDecl : public ObjCContainerDecl,
21782178
data().ReferencedProtocols.set(List, Num, Locs, C);
21792179
}
21802180

2181+
/// This is true iff the protocol is tagged with the `objc_static_protocol`
2182+
/// attribute.
2183+
bool isNonRuntimeProtocol() const;
2184+
2185+
/// Get the set of all protocols implied by this protocols inheritance
2186+
/// hierarchy.
2187+
void getImpliedProtocols(llvm::DenseSet<const ObjCProtocolDecl *> &IPs) const;
2188+
21812189
ObjCProtocolDecl *lookupProtocolNamed(IdentifierInfo *PName);
21822190

21832191
// Lookup a method. First, we search locally. If a method isn't

clang/include/clang/Basic/Attr.td

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2024,6 +2024,13 @@ def ObjCDirectMembers : Attr {
20242024
let Documentation = [ObjCDirectMembersDocs];
20252025
}
20262026

2027+
def ObjCNonRuntimeProtocol : Attr {
2028+
let Spellings = [Clang<"objc_non_runtime_protocol">];
2029+
let Subjects = SubjectList<[ObjCProtocol], ErrorDiag>;
2030+
let LangOpts = [ObjC];
2031+
let Documentation = [ObjCNonRuntimeProtocolDocs];
2032+
}
2033+
20272034
def ObjCRuntimeName : Attr {
20282035
let Spellings = [Clang<"objc_runtime_name">];
20292036
let Subjects = SubjectList<[ObjCInterface, ObjCProtocol], ErrorDiag>;

clang/include/clang/Basic/AttrDocs.td

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4620,6 +4620,22 @@ properties, including auto-synthesized properties.
46204620
}];
46214621
}
46224622

4623+
def ObjCNonRuntimeProtocolDocs : Documentation {
4624+
let Category = DocCatDecl;
4625+
let Content = [{
4626+
The ``objc_non_runtime_protocol`` attribute can be used to mark that an
4627+
Objective-C protocol is only used during static type-checking and doesn't need
4628+
to be represented dynamically. This avoids several small code-size and run-time
4629+
overheads associated with handling the protocol's metadata. A non-runtime
4630+
protocol cannot be used as the operand of a ``@protocol`` expression, and
4631+
dynamic attempts to find it with ``objc_getProtocol`` will fail.
4632+
4633+
If a non-runtime protocol inherits from any ordinary protocols, classes and
4634+
derived protocols that declare conformance to the non-runtime protocol will
4635+
dynamically list their conformance to those bare protocols.
4636+
}];
4637+
}
4638+
46234639
def SelectAnyDocs : Documentation {
46244640
let Category = DocCatDecl;
46254641
let Content = [{

clang/include/clang/Basic/DiagnosticSemaKinds.td

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1034,6 +1034,8 @@ def warn_objc_boxing_invalid_utf8_string : Warning<
10341034
"string is ill-formed as UTF-8 and will become a null %0 when boxed">,
10351035
InGroup<ObjCBoxing>;
10361036

1037+
def err_objc_non_runtime_protocol_in_protocol_expr : Error<
1038+
"cannot use a protocol declared 'objc_non_runtime_protocol' in a @protocol expression">;
10371039
def err_objc_direct_on_protocol : Error<
10381040
"'objc_direct' attribute cannot be applied to %select{methods|properties}0 "
10391041
"declared in an Objective-C protocol">;

clang/lib/AST/DeclObjC.cpp

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@
3333
#include <cassert>
3434
#include <cstdint>
3535
#include <cstring>
36+
#include <queue>
3637
#include <utility>
3738

3839
using namespace clang;
@@ -1905,6 +1906,27 @@ ObjCProtocolDecl *ObjCProtocolDecl::CreateDeserialized(ASTContext &C,
19051906
return Result;
19061907
}
19071908

1909+
bool ObjCProtocolDecl::isNonRuntimeProtocol() const {
1910+
return hasAttr<ObjCNonRuntimeProtocolAttr>();
1911+
}
1912+
1913+
void ObjCProtocolDecl::getImpliedProtocols(
1914+
llvm::DenseSet<const ObjCProtocolDecl *> &IPs) const {
1915+
std::queue<const ObjCProtocolDecl *> WorkQueue;
1916+
WorkQueue.push(this);
1917+
1918+
while (!WorkQueue.empty()) {
1919+
const auto *PD = WorkQueue.front();
1920+
WorkQueue.pop();
1921+
for (const auto *Parent : PD->protocols()) {
1922+
const auto *Can = Parent->getCanonicalDecl();
1923+
auto Result = IPs.insert(Can);
1924+
if (Result.second)
1925+
WorkQueue.push(Parent);
1926+
}
1927+
}
1928+
}
1929+
19081930
ObjCProtocolDecl *ObjCProtocolDecl::lookupProtocolNamed(IdentifierInfo *Name) {
19091931
ObjCProtocolDecl *PDecl = this;
19101932

clang/lib/CodeGen/CGObjC.cpp

Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -445,6 +445,75 @@ CodeGen::RValue CGObjCRuntime::GeneratePossiblySpecializedMessageSend(
445445
Method);
446446
}
447447

448+
static void AppendFirstImpliedRuntimeProtocols(
449+
const ObjCProtocolDecl *PD,
450+
llvm::UniqueVector<const ObjCProtocolDecl *> &PDs) {
451+
if (!PD->isNonRuntimeProtocol()) {
452+
const auto *Can = PD->getCanonicalDecl();
453+
PDs.insert(Can);
454+
return;
455+
}
456+
457+
for (const auto *ParentPD : PD->protocols())
458+
AppendFirstImpliedRuntimeProtocols(ParentPD, PDs);
459+
}
460+
461+
std::vector<const ObjCProtocolDecl *>
462+
CGObjCRuntime::GetRuntimeProtocolList(ObjCProtocolDecl::protocol_iterator begin,
463+
ObjCProtocolDecl::protocol_iterator end) {
464+
std::vector<const ObjCProtocolDecl *> RuntimePds;
465+
llvm::DenseSet<const ObjCProtocolDecl *> NonRuntimePDs;
466+
467+
for (; begin != end; ++begin) {
468+
const auto *It = *begin;
469+
const auto *Can = It->getCanonicalDecl();
470+
if (Can->isNonRuntimeProtocol())
471+
NonRuntimePDs.insert(Can);
472+
else
473+
RuntimePds.push_back(Can);
474+
}
475+
476+
// If there are no non-runtime protocols then we can just stop now.
477+
if (NonRuntimePDs.empty())
478+
return RuntimePds;
479+
480+
// Else we have to search through the non-runtime protocol's inheritancy
481+
// hierarchy DAG stopping whenever a branch either finds a runtime protocol or
482+
// a non-runtime protocol without any parents. These are the "first-implied"
483+
// protocols from a non-runtime protocol.
484+
llvm::UniqueVector<const ObjCProtocolDecl *> FirstImpliedProtos;
485+
for (const auto *PD : NonRuntimePDs)
486+
AppendFirstImpliedRuntimeProtocols(PD, FirstImpliedProtos);
487+
488+
// Walk the Runtime list to get all protocols implied via the inclusion of
489+
// this protocol, e.g. all protocols it inherits from including itself.
490+
llvm::DenseSet<const ObjCProtocolDecl *> AllImpliedProtocols;
491+
for (const auto *PD : RuntimePds) {
492+
const auto *Can = PD->getCanonicalDecl();
493+
AllImpliedProtocols.insert(Can);
494+
Can->getImpliedProtocols(AllImpliedProtocols);
495+
}
496+
497+
// Similar to above, walk the list of first-implied protocols to find the set
498+
// all the protocols implied excluding the listed protocols themselves since
499+
// they are not yet a part of the `RuntimePds` list.
500+
for (const auto *PD : FirstImpliedProtos) {
501+
PD->getImpliedProtocols(AllImpliedProtocols);
502+
}
503+
504+
// From the first-implied list we have to finish building the final protocol
505+
// list. If a protocol in the first-implied list was already implied via some
506+
// inheritance path through some other protocols then it would be redundant to
507+
// add it here and so we skip over it.
508+
for (const auto *PD : FirstImpliedProtos) {
509+
if (!AllImpliedProtocols.contains(PD)) {
510+
RuntimePds.push_back(PD);
511+
}
512+
}
513+
514+
return RuntimePds;
515+
}
516+
448517
/// Instead of '[[MyClass alloc] init]', try to generate
449518
/// 'objc_alloc_init(MyClass)'. This provides a code size improvement on the
450519
/// caller side, as well as the optimized objc_alloc.

clang/lib/CodeGen/CGObjCGNU.cpp

Lines changed: 23 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1187,8 +1187,11 @@ class CGObjCGNUstep2 : public CGObjCGNUstep {
11871187
}
11881188
llvm::Constant *GenerateCategoryProtocolList(const ObjCCategoryDecl *OCD)
11891189
override {
1190-
SmallVector<llvm::Constant*, 16> Protocols;
1191-
for (const auto *PI : OCD->getReferencedProtocols())
1190+
const auto &ReferencedProtocols = OCD->getReferencedProtocols();
1191+
auto RuntimeProtocols = GetRuntimeProtocolList(ReferencedProtocols.begin(),
1192+
ReferencedProtocols.end());
1193+
SmallVector<llvm::Constant *, 16> Protocols;
1194+
for (const auto *PI : RuntimeProtocols)
11921195
Protocols.push_back(
11931196
llvm::ConstantExpr::getBitCast(GenerateProtocolRef(PI),
11941197
ProtocolPtrTy));
@@ -1371,7 +1374,9 @@ class CGObjCGNUstep2 : public CGObjCGNUstep {
13711374
}
13721375

13731376
SmallVector<llvm::Constant*, 16> Protocols;
1374-
for (const auto *PI : PD->protocols())
1377+
auto RuntimeProtocols =
1378+
GetRuntimeProtocolList(PD->protocol_begin(), PD->protocol_end());
1379+
for (const auto *PI : RuntimeProtocols)
13751380
Protocols.push_back(
13761381
llvm::ConstantExpr::getBitCast(GenerateProtocolRef(PI),
13771382
ProtocolPtrTy));
@@ -1910,8 +1915,10 @@ class CGObjCGNUstep2 : public CGObjCGNUstep {
19101915
// struct objc_class *sibling_class
19111916
classFields.addNullPointer(PtrTy);
19121917
// struct objc_protocol_list *protocols;
1913-
SmallVector<llvm::Constant*, 16> Protocols;
1914-
for (const auto *I : classDecl->protocols())
1918+
auto RuntimeProtocols = GetRuntimeProtocolList(classDecl->protocol_begin(),
1919+
classDecl->protocol_end());
1920+
SmallVector<llvm::Constant *, 16> Protocols;
1921+
for (const auto *I : RuntimeProtocols)
19151922
Protocols.push_back(
19161923
llvm::ConstantExpr::getBitCast(GenerateProtocolRef(I),
19171924
ProtocolPtrTy));
@@ -3076,6 +3083,9 @@ CGObjCGNU::GenerateEmptyProtocol(StringRef ProtocolName) {
30763083
}
30773084

30783085
void CGObjCGNU::GenerateProtocol(const ObjCProtocolDecl *PD) {
3086+
if (PD->isNonRuntimeProtocol())
3087+
return;
3088+
30793089
std::string ProtocolName = PD->getNameAsString();
30803090

30813091
// Use the protocol definition, if there is one.
@@ -3228,8 +3238,11 @@ llvm::Constant *CGObjCGNU::MakeBitField(ArrayRef<bool> bits) {
32283238

32293239
llvm::Constant *CGObjCGNU::GenerateCategoryProtocolList(const
32303240
ObjCCategoryDecl *OCD) {
3241+
const auto &RefPro = OCD->getReferencedProtocols();
3242+
const auto RuntimeProtos =
3243+
GetRuntimeProtocolList(RefPro.begin(), RefPro.end());
32313244
SmallVector<std::string, 16> Protocols;
3232-
for (const auto *PD : OCD->getReferencedProtocols())
3245+
for (const auto *PD : RuntimeProtos)
32333246
Protocols.push_back(PD->getNameAsString());
32343247
return GenerateProtocolList(Protocols);
32353248
}
@@ -3515,8 +3528,11 @@ void CGObjCGNU::GenerateClass(const ObjCImplementationDecl *OID) {
35153528
llvm::Constant *Properties = GeneratePropertyList(OID, ClassDecl);
35163529

35173530
// Collect the names of referenced protocols
3531+
auto RefProtocols = ClassDecl->protocols();
3532+
auto RuntimeProtocols =
3533+
GetRuntimeProtocolList(RefProtocols.begin(), RefProtocols.end());
35183534
SmallVector<std::string, 16> Protocols;
3519-
for (const auto *I : ClassDecl->protocols())
3535+
for (const auto *I : RuntimeProtocols)
35203536
Protocols.push_back(I->getNameAsString());
35213537

35223538
// Get the superclass pointer.

clang/lib/CodeGen/CGObjCMac.cpp

Lines changed: 24 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@
3232
#include "llvm/ADT/SetVector.h"
3333
#include "llvm/ADT/SmallPtrSet.h"
3434
#include "llvm/ADT/SmallString.h"
35+
#include "llvm/ADT/UniqueVector.h"
3536
#include "llvm/IR/DataLayout.h"
3637
#include "llvm/IR/InlineAsm.h"
3738
#include "llvm/IR/IntrinsicInst.h"
@@ -3196,7 +3197,8 @@ CGObjCMac::EmitProtocolList(Twine name,
31963197
ObjCProtocolDecl::protocol_iterator begin,
31973198
ObjCProtocolDecl::protocol_iterator end) {
31983199
// Just return null for empty protocol lists
3199-
if (begin == end)
3200+
auto PDs = GetRuntimeProtocolList(begin, end);
3201+
if (PDs.empty())
32003202
return llvm::Constant::getNullValue(ObjCTypes.ProtocolListPtrTy);
32013203

32023204
ConstantInitBuilder builder(CGM);
@@ -3209,9 +3211,9 @@ CGObjCMac::EmitProtocolList(Twine name,
32093211
auto countSlot = values.addPlaceholder();
32103212

32113213
auto refsArray = values.beginArray(ObjCTypes.ProtocolPtrTy);
3212-
for (; begin != end; ++begin) {
3213-
refsArray.add(GetProtocolRef(*begin));
3214-
}
3214+
for (const auto *Proto : PDs)
3215+
refsArray.add(GetProtocolRef(Proto));
3216+
32153217
auto count = refsArray.size();
32163218

32173219
// This list is null terminated.
@@ -6648,7 +6650,8 @@ llvm::Value *CGObjCNonFragileABIMac::GenerateProtocolRef(CodeGenFunction &CGF,
66486650

66496651
// This routine is called for @protocol only. So, we must build definition
66506652
// of protocol's meta-data (not a reference to it!)
6651-
//
6653+
assert(!PD->isNonRuntimeProtocol() &&
6654+
"attempting to get a protocol ref to a static protocol.");
66526655
llvm::Constant *Init =
66536656
llvm::ConstantExpr::getBitCast(GetOrEmitProtocol(PD),
66546657
ObjCTypes.getExternalProtocolPtrTy());
@@ -7005,6 +7008,8 @@ llvm::Constant *CGObjCNonFragileABIMac::GetOrEmitProtocolRef(
70057008
const ObjCProtocolDecl *PD) {
70067009
llvm::GlobalVariable *&Entry = Protocols[PD->getIdentifier()];
70077010

7011+
assert(!PD->isNonRuntimeProtocol() &&
7012+
"attempting to GetOrEmit a non-runtime protocol");
70087013
if (!Entry) {
70097014
// We use the initializer as a marker of whether this is a forward
70107015
// reference or not. At module finalization we add the empty
@@ -7148,10 +7153,20 @@ llvm::Constant *
71487153
CGObjCNonFragileABIMac::EmitProtocolList(Twine Name,
71497154
ObjCProtocolDecl::protocol_iterator begin,
71507155
ObjCProtocolDecl::protocol_iterator end) {
7156+
// Just return null for empty protocol lists
7157+
auto Protocols = GetRuntimeProtocolList(begin, end);
7158+
if (Protocols.empty())
7159+
return llvm::Constant::getNullValue(ObjCTypes.ProtocolListnfABIPtrTy);
7160+
71517161
SmallVector<llvm::Constant *, 16> ProtocolRefs;
7162+
ProtocolRefs.reserve(Protocols.size());
71527163

7153-
// Just return null for empty protocol lists
7154-
if (begin == end)
7164+
for (const auto *PD : Protocols)
7165+
ProtocolRefs.push_back(GetProtocolRef(PD));
7166+
7167+
// If all of the protocols in the protocol list are objc_non_runtime_protocol
7168+
// just return null
7169+
if (ProtocolRefs.size() == 0)
71557170
return llvm::Constant::getNullValue(ObjCTypes.ProtocolListnfABIPtrTy);
71567171

71577172
// FIXME: We shouldn't need to do this lookup here, should we?
@@ -7168,8 +7183,8 @@ CGObjCNonFragileABIMac::EmitProtocolList(Twine Name,
71687183

71697184
// A null-terminated array of protocols.
71707185
auto array = values.beginArray(ObjCTypes.ProtocolnfABIPtrTy);
7171-
for (; begin != end; ++begin)
7172-
array.add(GetProtocolRef(*begin)); // Implemented???
7186+
for (auto const &proto : ProtocolRefs)
7187+
array.add(proto);
71737188
auto count = array.size();
71747189
array.addNullPointer(ObjCTypes.ProtocolnfABIPtrTy);
71757190

clang/lib/CodeGen/CGObjCRuntime.h

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020
#include "CGValue.h"
2121
#include "clang/AST/DeclObjC.h"
2222
#include "clang/Basic/IdentifierTable.h" // Selector
23+
#include "llvm/ADT/UniqueVector.h"
2324

2425
namespace llvm {
2526
class Constant;
@@ -205,6 +206,16 @@ class CGObjCRuntime {
205206
const CallArgList &CallArgs,
206207
const ObjCMethodDecl *Method = nullptr) = 0;
207208

209+
/// Walk the list of protocol references from a class, category or
210+
/// protocol to traverse the DAG formed from it's inheritance hierarchy. Find
211+
/// the list of protocols that ends each walk at either a runtime
212+
/// protocol or a non-runtime protocol with no parents. For the common case of
213+
/// just a list of standard runtime protocols this just returns the same list
214+
/// that was passed in.
215+
std::vector<const ObjCProtocolDecl *>
216+
GetRuntimeProtocolList(ObjCProtocolDecl::protocol_iterator begin,
217+
ObjCProtocolDecl::protocol_iterator end);
218+
208219
/// Emit the code to return the named protocol as an object, as in a
209220
/// \@protocol expression.
210221
virtual llvm::Value *GenerateProtocolRef(CodeGenFunction &CGF,

clang/lib/Sema/SemaDeclAttr.cpp

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2617,6 +2617,11 @@ static void handleVisibilityAttr(Sema &S, Decl *D, const ParsedAttr &AL,
26172617
D->addAttr(newAttr);
26182618
}
26192619

2620+
static void handleObjCNonRuntimeProtocolAttr(Sema &S, Decl *D,
2621+
const ParsedAttr &AL) {
2622+
handleSimpleAttribute<ObjCNonRuntimeProtocolAttr>(S, D, AL);
2623+
}
2624+
26202625
static void handleObjCDirectAttr(Sema &S, Decl *D, const ParsedAttr &AL) {
26212626
// objc_direct cannot be set on methods declared in the context of a protocol
26222627
if (isa<ObjCProtocolDecl>(D->getDeclContext())) {
@@ -7665,6 +7670,9 @@ static void ProcessDeclAttribute(Sema &S, Scope *scope, Decl *D,
76657670
case ParsedAttr::AT_ObjCDirect:
76667671
handleObjCDirectAttr(S, D, AL);
76677672
break;
7673+
case ParsedAttr::AT_ObjCNonRuntimeProtocol:
7674+
handleObjCNonRuntimeProtocolAttr(S, D, AL);
7675+
break;
76687676
case ParsedAttr::AT_ObjCDirectMembers:
76697677
handleObjCDirectMembersAttr(S, D, AL);
76707678
handleSimpleAttribute<ObjCDirectMembersAttr>(S, D, AL);

0 commit comments

Comments
 (0)