Skip to content

Commit f36845d

Browse files
Enable direct methods and fast alloc calls for libobjc2. (#78030)
These will be supported in the upcoming 2.2 release and so are gated on that version. Direct methods call `objc_send_initialize` if they are class methods that may not have called initialize. This is guarded by checking for the class flag bit that is set on initialisation in the class. This bit now forms part of the ABI, but it's been stable for 30+ years so that's fine as a contract going forwards.
1 parent 376f019 commit f36845d

File tree

3 files changed

+239
-27
lines changed

3 files changed

+239
-27
lines changed

clang/include/clang/Basic/ObjCRuntime.h

Lines changed: 12 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -211,7 +211,13 @@ class ObjCRuntime {
211211
case GCC:
212212
return false;
213213
case GNUstep:
214-
return false;
214+
// This could be enabled for all versions, except for the fact that the
215+
// implementation of `objc_retain` and friends prior to 2.2 call [object
216+
// retain] in their fall-back paths, which leads to infinite recursion if
217+
// the runtime is built with this enabled. Since distributions typically
218+
// build all Objective-C things with the same compiler version and flags,
219+
// it's better to be conservative here.
220+
return (getVersion() >= VersionTuple(2, 2));
215221
case ObjFW:
216222
return false;
217223
}
@@ -248,7 +254,7 @@ class ObjCRuntime {
248254
case GCC:
249255
return false;
250256
case GNUstep:
251-
return false;
257+
return getVersion() >= VersionTuple(2, 2);
252258
case ObjFW:
253259
return false;
254260
}
@@ -266,6 +272,8 @@ class ObjCRuntime {
266272
return getVersion() >= VersionTuple(12, 2);
267273
case WatchOS:
268274
return getVersion() >= VersionTuple(5, 2);
275+
case GNUstep:
276+
return getVersion() >= VersionTuple(2, 2);
269277
default:
270278
return false;
271279
}
@@ -463,7 +471,8 @@ class ObjCRuntime {
463471
case iOS: return true;
464472
case WatchOS: return true;
465473
case GCC: return false;
466-
case GNUstep: return false;
474+
case GNUstep:
475+
return (getVersion() >= VersionTuple(2, 2));
467476
case ObjFW: return false;
468477
}
469478
llvm_unreachable("bad kind");

clang/lib/CodeGen/CGObjCGNU.cpp

Lines changed: 190 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,8 @@
1818
#include "CGObjCRuntime.h"
1919
#include "CodeGenFunction.h"
2020
#include "CodeGenModule.h"
21+
#include "CodeGenTypes.h"
22+
#include "SanitizerMetadata.h"
2123
#include "clang/AST/ASTContext.h"
2224
#include "clang/AST/Attr.h"
2325
#include "clang/AST/Decl.h"
@@ -597,6 +599,10 @@ class CGObjCGNU : public CGObjCRuntime {
597599

598600
llvm::Function *GenerateMethod(const ObjCMethodDecl *OMD,
599601
const ObjCContainerDecl *CD) override;
602+
603+
// Map to unify direct method definitions.
604+
llvm::DenseMap<const ObjCMethodDecl *, llvm::Function *>
605+
DirectMethodDefinitions;
600606
void GenerateDirectMethodPrologue(CodeGenFunction &CGF, llvm::Function *Fn,
601607
const ObjCMethodDecl *OMD,
602608
const ObjCContainerDecl *CD) override;
@@ -917,6 +923,14 @@ class CGObjCGNUstep2 : public CGObjCGNUstep {
917923
ClassAliasSection,
918924
ConstantStringSection
919925
};
926+
/// The subset of `objc_class_flags` used at compile time.
927+
enum ClassFlags {
928+
/// This is a metaclass
929+
ClassFlagMeta = (1 << 0),
930+
/// This class has been initialised by the runtime (+initialize has been
931+
/// sent if necessary).
932+
ClassFlagInitialized = (1 << 8),
933+
};
920934
static const char *const SectionsBaseNames[8];
921935
static const char *const PECOFFSectionsBaseNames[8];
922936
template<SectionKind K>
@@ -932,6 +946,8 @@ class CGObjCGNUstep2 : public CGObjCGNUstep {
932946
/// structure describing the receiver and the class, and a selector as
933947
/// arguments. Returns the IMP for the corresponding method.
934948
LazyRuntimeFunction MsgLookupSuperFn;
949+
/// Function to ensure that +initialize is sent to a class.
950+
LazyRuntimeFunction SentInitializeFn;
935951
/// A flag indicating if we've emitted at least one protocol.
936952
/// If we haven't, then we need to emit an empty protocol, to ensure that the
937953
/// __start__objc_protocols and __stop__objc_protocols sections exist.
@@ -1719,7 +1735,7 @@ class CGObjCGNUstep2 : public CGObjCGNUstep {
17191735
metaclassFields.addInt(LongTy, 0);
17201736
// unsigned long info;
17211737
// objc_class_flag_meta
1722-
metaclassFields.addInt(LongTy, 1);
1738+
metaclassFields.addInt(LongTy, ClassFlags::ClassFlagMeta);
17231739
// long instance_size;
17241740
// Setting this to zero is consistent with the older ABI, but it might be
17251741
// more sensible to set this to sizeof(struct objc_class)
@@ -1993,6 +2009,8 @@ class CGObjCGNUstep2 : public CGObjCGNUstep {
19932009
CGObjCGNUstep2(CodeGenModule &Mod) : CGObjCGNUstep(Mod, 10, 4, 2) {
19942010
MsgLookupSuperFn.init(&CGM, "objc_msg_lookup_super", IMPTy,
19952011
PtrToObjCSuperTy, SelectorTy);
2012+
SentInitializeFn.init(&CGM, "objc_send_initialize",
2013+
llvm::Type::getVoidTy(VMContext), IdTy);
19962014
// struct objc_property
19972015
// {
19982016
// const char *name;
@@ -2006,6 +2024,106 @@ class CGObjCGNUstep2 : public CGObjCGNUstep {
20062024
{ PtrToInt8Ty, PtrToInt8Ty, PtrToInt8Ty, PtrToInt8Ty, PtrToInt8Ty });
20072025
}
20082026

2027+
void GenerateDirectMethodPrologue(CodeGenFunction &CGF, llvm::Function *Fn,
2028+
const ObjCMethodDecl *OMD,
2029+
const ObjCContainerDecl *CD) override {
2030+
auto &Builder = CGF.Builder;
2031+
bool ReceiverCanBeNull = true;
2032+
auto selfAddr = CGF.GetAddrOfLocalVar(OMD->getSelfDecl());
2033+
auto selfValue = Builder.CreateLoad(selfAddr);
2034+
2035+
// Generate:
2036+
//
2037+
// /* unless the receiver is never NULL */
2038+
// if (self == nil) {
2039+
// return (ReturnType){ };
2040+
// }
2041+
//
2042+
// /* for class methods only to force class lazy initialization */
2043+
// if (!__objc_{class}_initialized)
2044+
// {
2045+
// objc_send_initialize(class);
2046+
// __objc_{class}_initialized = 1;
2047+
// }
2048+
//
2049+
// _cmd = @selector(...)
2050+
// ...
2051+
2052+
if (OMD->isClassMethod()) {
2053+
const ObjCInterfaceDecl *OID = cast<ObjCInterfaceDecl>(CD);
2054+
2055+
// Nullable `Class` expressions cannot be messaged with a direct method
2056+
// so the only reason why the receive can be null would be because
2057+
// of weak linking.
2058+
ReceiverCanBeNull = isWeakLinkedClass(OID);
2059+
}
2060+
2061+
llvm::MDBuilder MDHelper(CGM.getLLVMContext());
2062+
if (ReceiverCanBeNull) {
2063+
llvm::BasicBlock *SelfIsNilBlock =
2064+
CGF.createBasicBlock("objc_direct_method.self_is_nil");
2065+
llvm::BasicBlock *ContBlock =
2066+
CGF.createBasicBlock("objc_direct_method.cont");
2067+
2068+
// if (self == nil) {
2069+
auto selfTy = cast<llvm::PointerType>(selfValue->getType());
2070+
auto Zero = llvm::ConstantPointerNull::get(selfTy);
2071+
2072+
Builder.CreateCondBr(Builder.CreateICmpEQ(selfValue, Zero),
2073+
SelfIsNilBlock, ContBlock,
2074+
MDHelper.createBranchWeights(1, 1 << 20));
2075+
2076+
CGF.EmitBlock(SelfIsNilBlock);
2077+
2078+
// return (ReturnType){ };
2079+
auto retTy = OMD->getReturnType();
2080+
Builder.SetInsertPoint(SelfIsNilBlock);
2081+
if (!retTy->isVoidType()) {
2082+
CGF.EmitNullInitialization(CGF.ReturnValue, retTy);
2083+
}
2084+
CGF.EmitBranchThroughCleanup(CGF.ReturnBlock);
2085+
// }
2086+
2087+
// rest of the body
2088+
CGF.EmitBlock(ContBlock);
2089+
Builder.SetInsertPoint(ContBlock);
2090+
}
2091+
2092+
if (OMD->isClassMethod()) {
2093+
// Prefix of the class type.
2094+
auto *classStart =
2095+
llvm::StructType::get(PtrTy, PtrTy, PtrTy, LongTy, LongTy);
2096+
auto &astContext = CGM.getContext();
2097+
auto flags = Builder.CreateLoad(
2098+
Address{Builder.CreateStructGEP(classStart, selfValue, 4), LongTy,
2099+
CharUnits::fromQuantity(
2100+
astContext.getTypeAlign(astContext.UnsignedLongTy))});
2101+
auto isInitialized =
2102+
Builder.CreateAnd(flags, ClassFlags::ClassFlagInitialized);
2103+
llvm::BasicBlock *notInitializedBlock =
2104+
CGF.createBasicBlock("objc_direct_method.class_uninitialized");
2105+
llvm::BasicBlock *initializedBlock =
2106+
CGF.createBasicBlock("objc_direct_method.class_initialized");
2107+
Builder.CreateCondBr(Builder.CreateICmpEQ(isInitialized, Zeros[0]),
2108+
notInitializedBlock, initializedBlock,
2109+
MDHelper.createBranchWeights(1, 1 << 20));
2110+
CGF.EmitBlock(notInitializedBlock);
2111+
Builder.SetInsertPoint(notInitializedBlock);
2112+
CGF.EmitRuntimeCall(SentInitializeFn, selfValue);
2113+
Builder.CreateBr(initializedBlock);
2114+
CGF.EmitBlock(initializedBlock);
2115+
Builder.SetInsertPoint(initializedBlock);
2116+
}
2117+
2118+
// only synthesize _cmd if it's referenced
2119+
if (OMD->getCmdDecl()->isUsed()) {
2120+
// `_cmd` is not a parameter to direct methods, so storage must be
2121+
// explicitly declared for it.
2122+
CGF.EmitVarDecl(*OMD->getCmdDecl());
2123+
Builder.CreateStore(GetSelector(CGF, OMD),
2124+
CGF.GetAddrOfLocalVar(OMD->getCmdDecl()));
2125+
}
2126+
}
20092127
};
20102128

20112129
const char *const CGObjCGNUstep2::SectionsBaseNames[8] =
@@ -2649,13 +2767,18 @@ CGObjCGNU::GenerateMessageSend(CodeGenFunction &CGF,
26492767
}
26502768
}
26512769

2770+
bool isDirect = Method && Method->isDirectMethod();
2771+
26522772
IdTy = cast<llvm::PointerType>(CGM.getTypes().ConvertType(ASTIdTy));
26532773
llvm::Value *cmd;
2654-
if (Method)
2655-
cmd = GetSelector(CGF, Method);
2656-
else
2657-
cmd = GetSelector(CGF, Sel);
2658-
cmd = EnforceType(Builder, cmd, SelectorTy);
2774+
if (!isDirect) {
2775+
if (Method)
2776+
cmd = GetSelector(CGF, Method);
2777+
else
2778+
cmd = GetSelector(CGF, Sel);
2779+
cmd = EnforceType(Builder, cmd, SelectorTy);
2780+
}
2781+
26592782
Receiver = EnforceType(Builder, Receiver, IdTy);
26602783

26612784
llvm::Metadata *impMD[] = {
@@ -2667,7 +2790,8 @@ CGObjCGNU::GenerateMessageSend(CodeGenFunction &CGF,
26672790

26682791
CallArgList ActualArgs;
26692792
ActualArgs.add(RValue::get(Receiver), ASTIdTy);
2670-
ActualArgs.add(RValue::get(cmd), CGF.getContext().getObjCSelType());
2793+
if (!isDirect)
2794+
ActualArgs.add(RValue::get(cmd), CGF.getContext().getObjCSelType());
26712795
ActualArgs.addFrom(CallArgs);
26722796

26732797
MessageSendInfo MSI = getMessageSendInfo(Method, ResultType, ActualArgs);
@@ -2686,7 +2810,7 @@ CGObjCGNU::GenerateMessageSend(CodeGenFunction &CGF,
26862810
// Rather than doing a whole target-specific analysis, we assume it
26872811
// only works for void, integer, and pointer types, and in all
26882812
// other cases we do an explicit nil check is emitted code. In
2689-
// addition to ensuring we produe a zero value for other types, this
2813+
// addition to ensuring we produce a zero value for other types, this
26902814
// sidesteps the few outright CC incompatibilities we know about that
26912815
// could otherwise lead to crashes, like when a method is expected to
26922816
// return on the x87 floating point stack or adjust the stack pointer
@@ -2720,8 +2844,9 @@ CGObjCGNU::GenerateMessageSend(CodeGenFunction &CGF,
27202844
// FIXME: we probably need a size limit here, but we've
27212845
// never imposed one before
27222846
} else {
2723-
// Otherwise, use an explicit check just to be sure.
2724-
requiresExplicitZeroResult = true;
2847+
// Otherwise, use an explicit check just to be sure, unless we're
2848+
// calling a direct method, where the implementation does this for us.
2849+
requiresExplicitZeroResult = !isDirect;
27252850
}
27262851
}
27272852

@@ -2765,10 +2890,14 @@ CGObjCGNU::GenerateMessageSend(CodeGenFunction &CGF,
27652890
// Get the IMP to call
27662891
llvm::Value *imp;
27672892

2768-
// If we have non-legacy dispatch specified, we try using the objc_msgSend()
2769-
// functions. These are not supported on all platforms (or all runtimes on a
2770-
// given platform), so we
2771-
switch (CGM.getCodeGenOpts().getObjCDispatchMethod()) {
2893+
// If this is a direct method, just emit it here.
2894+
if (isDirect)
2895+
imp = GenerateMethod(Method, Method->getClassInterface());
2896+
else
2897+
// If we have non-legacy dispatch specified, we try using the
2898+
// objc_msgSend() functions. These are not supported on all platforms
2899+
// (or all runtimes on a given platform), so we
2900+
switch (CGM.getCodeGenOpts().getObjCDispatchMethod()) {
27722901
case CodeGenOptions::Legacy:
27732902
imp = LookupIMP(CGF, Receiver, cmd, node, MSI);
27742903
break;
@@ -2791,7 +2920,7 @@ CGObjCGNU::GenerateMessageSend(CodeGenFunction &CGF,
27912920
llvm::FunctionType::get(IdTy, IdTy, true), "objc_msgSend")
27922921
.getCallee();
27932922
}
2794-
}
2923+
}
27952924

27962925
// Reset the receiver in case the lookup modified it
27972926
ActualArgs[0] = CallArg(RValue::get(Receiver), ASTIdTy);
@@ -2801,7 +2930,8 @@ CGObjCGNU::GenerateMessageSend(CodeGenFunction &CGF,
28012930
llvm::CallBase *call;
28022931
CGCallee callee(CGCalleeInfo(), imp);
28032932
RValue msgRet = CGF.EmitCall(MSI.CallInfo, callee, Return, ActualArgs, &call);
2804-
call->setMetadata(msgSendMDKind, node);
2933+
if (!isDirect)
2934+
call->setMetadata(msgSendMDKind, node);
28052935

28062936
if (requiresNilReceiverCheck) {
28072937
llvm::BasicBlock *nonNilPathBB = CGF.Builder.GetInsertBlock();
@@ -3924,14 +4054,50 @@ llvm::Function *CGObjCGNU::GenerateMethod(const ObjCMethodDecl *OMD,
39244054
CodeGenTypes &Types = CGM.getTypes();
39254055
llvm::FunctionType *MethodTy =
39264056
Types.GetFunctionType(Types.arrangeObjCMethodDeclaration(OMD));
3927-
std::string FunctionName = getSymbolNameForMethod(OMD);
3928-
3929-
llvm::Function *Method
3930-
= llvm::Function::Create(MethodTy,
3931-
llvm::GlobalValue::InternalLinkage,
3932-
FunctionName,
3933-
&TheModule);
3934-
return Method;
4057+
4058+
bool isDirect = OMD->isDirectMethod();
4059+
std::string FunctionName =
4060+
getSymbolNameForMethod(OMD, /*include category*/ !isDirect);
4061+
4062+
if (!isDirect)
4063+
return llvm::Function::Create(MethodTy,
4064+
llvm::GlobalVariable::InternalLinkage,
4065+
FunctionName, &TheModule);
4066+
4067+
auto *COMD = OMD->getCanonicalDecl();
4068+
auto I = DirectMethodDefinitions.find(COMD);
4069+
llvm::Function *OldFn = nullptr, *Fn = nullptr;
4070+
4071+
if (I == DirectMethodDefinitions.end()) {
4072+
auto *F =
4073+
llvm::Function::Create(MethodTy, llvm::GlobalVariable::ExternalLinkage,
4074+
FunctionName, &TheModule);
4075+
DirectMethodDefinitions.insert(std::make_pair(COMD, F));
4076+
return F;
4077+
}
4078+
4079+
// Objective-C allows for the declaration and implementation types
4080+
// to differ slightly.
4081+
//
4082+
// If we're being asked for the Function associated for a method
4083+
// implementation, a previous value might have been cached
4084+
// based on the type of the canonical declaration.
4085+
//
4086+
// If these do not match, then we'll replace this function with
4087+
// a new one that has the proper type below.
4088+
if (!OMD->getBody() || COMD->getReturnType() == OMD->getReturnType())
4089+
return I->second;
4090+
4091+
OldFn = I->second;
4092+
Fn = llvm::Function::Create(MethodTy, llvm::GlobalValue::ExternalLinkage, "",
4093+
&CGM.getModule());
4094+
Fn->takeName(OldFn);
4095+
OldFn->replaceAllUsesWith(Fn);
4096+
OldFn->eraseFromParent();
4097+
4098+
// Replace the cached function in the map.
4099+
I->second = Fn;
4100+
return Fn;
39354101
}
39364102

39374103
void CGObjCGNU::GenerateDirectMethodPrologue(CodeGenFunction &CGF,

0 commit comments

Comments
 (0)