[ObjCDirectPreconditionThunk] precondition check thunk generation (#170618)

## TL;DR

This is a stack of PRs implementing features to expose direct methods
ABI.
You can see the RFC, design, and discussion
[here](https://discourse.llvm.org/t/rfc-optimizing-code-size-of-objc-direct-by-exposing-function-symbols-and-moving-nil-checks-to-thunks/88866).

https://github.com/llvm/llvm-project/pull/170616 Flag
`-fobjc-direct-precondition-thunk` set up
https://github.com/llvm/llvm-project/pull/170617 Code refactoring to
ease later reviews
https://github.com/llvm/llvm-project/pull/170618 **Thunk generation**
https://github.com/llvm/llvm-project/pull/170619 Optimizations, some
class objects can be known to be realized

## Implementation details

### Dispatching
- `GetDirectMethodCallee` handles the dispatching logic. Previously we
only need to call `GenerateDirectMethod` to get the declaration of a
direct method.
- `GenerateDirectMethod` first attempts to acquire the declaration of
the implementation, and return it if the flag is not set.
- Generate and return thunk if we can't dispatch to true implementation
(i.e. we can't reason receiver is def not null or class object is not
realized)

### Precondition check thunk generation

- `GenerateObjCDirectThunk` generates the thunk, it is called on demand
by `GetDirectMethodCallee`
- Thunk inherits all attributes from the true implementation, see
`StartObjCDirectThunk` for more detail.
- `StartObjCDirectThunk` and `FinishObjCDirectThunk` follows the design
pattern of `StartThunk` and `FinishThunk` in CGVTable.

### Precondition check inline generation

- If the function need to have precondition check inlined
(`shouldHaveNilCheckInline`), caller will emit the nil check during
`EmitMessageSend`
- Class realization is generated inline
- No extra nil check is generated - we reuse `NullReturnState` to emit
the nil check for us, it already emits nil check at caller side to
handle `ns_consumed`, we just need to tell `NullReturnState` to do the
work by setting the flag `RequiresNullCheck |= ReceiverCanBeNull;`

### Visibility and linkage

- Visibility is still by default `Hidden`. But `StartObjCMethod` will
now respect source level visibility attributes so methods with
`__attribute((visibility("default"))` can be used in other linking units
- Linkage is by default `External`

## Tests

- `expose-direct-method.m` follow the example of `direct-method.m`
- `direct-method-ret-mismatch.m` make sure we can handle the corner case
- `expose-direct-method-consumed.m ` and
`expose-direct-method-linkedlist.m` executable test on Mac only to
validate ARC correctness
- `expose-direct-method-varargs.m`
- `expose-direct-method-visibility-linkage.m`
This commit is contained in:
Peter Rong 2026-03-30 10:32:09 -07:00 committed by GitHub
parent cabebddac9
commit 3e2f0bce95
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
15 changed files with 1072 additions and 67 deletions

View File

@ -42,10 +42,14 @@ class VarDecl;
/// Extract mangling function name from MangleContext such that swift can call
/// it to prepare for ObjCDirect in swift.
/// Produces the mangling:
/// \01-[ClassName(Category) method:arg1:arg2:]
/// Or, if useDirectABI is true (for Direct ABI):
/// -[ClassName(Category) method:arg1:arg2:]D
void mangleObjCMethodName(raw_ostream &OS, bool includePrefixByte,
bool isInstanceMethod, StringRef ClassName,
std::optional<StringRef> CategoryName,
StringRef MethodName);
StringRef MethodName, bool useDirectABI);
/// MangleContext - Context for tracking state which persists across multiple
/// calls to the C++ name mangler.
@ -160,7 +164,8 @@ public:
void mangleObjCMethodName(const ObjCMethodDecl *MD, raw_ostream &OS,
bool includePrefixByte = true,
bool includeCategoryNamespace = true) const;
bool includeCategoryNamespace = true,
bool useDirectABI = false) const;
void mangleObjCMethodNameAsSourceName(const ObjCMethodDecl *MD,
raw_ostream &) const;

View File

@ -44,6 +44,7 @@ class CXXDestructorDecl;
class CXXRecordDecl;
class CXXMethodDecl;
class GlobalDecl;
class ObjCContainerDecl;
class ObjCMethodDecl;
class ObjCProtocolDecl;
@ -212,6 +213,20 @@ llvm::Function *getNonTrivialCStructDestructor(CodeGenModule &CGM,
/// object.
llvm::Constant *emitObjCProtocolObject(CodeGenModule &CGM,
const ObjCProtocolDecl *p);
/// Get the appropriate callee for an ObjC direct method. Returns the thunk
/// if the receiver may be null (or class may be unrealized) and precondition
/// thunks are enabled, otherwise returns the true implementation.
///
/// This allows external compilers (e.g., Swift) to reuse Clang's thunk
/// generation logic when calling ObjC direct methods, ensuring consistent
/// nil-check behavior.
llvm::Function *getObjCDirectMethodCallee(CodeGenModule &CGM,
const ObjCMethodDecl *OMD,
const ObjCContainerDecl *CD,
bool ReceiverCanBeNull,
bool ClassObjectCanBeUnrealized);
} // end namespace CodeGen
} // end namespace clang

View File

@ -32,8 +32,12 @@ using namespace clang;
void clang::mangleObjCMethodName(raw_ostream &OS, bool includePrefixByte,
bool isInstanceMethod, StringRef ClassName,
std::optional<StringRef> CategoryName,
StringRef MethodName) {
StringRef MethodName, bool useDirectABI) {
assert(
!(includePrefixByte && useDirectABI) &&
"includePrefixByte and useDirectABI shouldn't be set at the same time");
// \01+[ContainerName(CategoryName) SelectorName]
// Or for direct ABI: +[ContainerName(CategoryName) SelectorName]D
if (includePrefixByte)
OS << "\01";
OS << (isInstanceMethod ? '-' : '+');
@ -44,6 +48,8 @@ void clang::mangleObjCMethodName(raw_ostream &OS, bool includePrefixByte,
OS << " ";
OS << MethodName;
OS << ']';
if (useDirectABI)
OS << 'D';
}
// FIXME: For blocks we currently mimic GCC's mangling scheme, which leaves
@ -378,7 +384,8 @@ void MangleContext::mangleBlock(const DeclContext *DC, const BlockDecl *BD,
void MangleContext::mangleObjCMethodName(const ObjCMethodDecl *MD,
raw_ostream &OS,
bool includePrefixByte,
bool includeCategoryNamespace) const {
bool includeCategoryNamespace,
bool useDirectABI) const {
if (getASTContext().getLangOpts().ObjCRuntime.isGNUFamily()) {
// This is the mangling we've always used on the GNU runtimes, but it
// has obvious collisions in the face of underscores within class
@ -430,8 +437,16 @@ void MangleContext::mangleObjCMethodName(const ObjCMethodDecl *MD,
std::string MethodName;
llvm::raw_string_ostream MethodNameOS(MethodName);
MD->getSelector().print(MethodNameOS);
clang::mangleObjCMethodName(OS, includePrefixByte, MD->isInstanceMethod(),
ClassName, CategoryName, MethodName);
// Normal methods always have internal linkage, and we prefix them with '\01'
// for reasons that are somewhat lost to time. We suppress this for direct
// methods because they have non-internal linkage and we don't want to make it
// unnecessarily difficult to refer to them, e.g. in things like export lists.
// Direct methods also have a distinct ABI, so we add a suffix to make them
// obvious to tools like debuggers and to elevate incompatible uses into
// linker errors.
clang::mangleObjCMethodName(OS, includePrefixByte && !useDirectABI,
MD->isInstanceMethod(), ClassName, CategoryName,
MethodName, useDirectABI);
}
void MangleContext::mangleObjCMethodNameAsSourceName(const ObjCMethodDecl *MD,

View File

@ -2780,7 +2780,9 @@ void CodeGenFunction::EmitParmDecl(const VarDecl &D, ParamValue Arg,
llvm::Value *ArgVal = (DoStore ? Arg.getDirectValue() : nullptr);
LValue lv = MakeAddrLValue(DeclPtr, Ty);
if (IsScalar) {
// If this is a thunk, don't bother with ARC lifetime management.
// The true implementation will take care of that.
if (IsScalar && !CurFuncIsThunk) {
Qualifiers qs = Ty.getQualifiers();
if (Qualifiers::ObjCLifetime lt = qs.getObjCLifetime()) {
// We honor __attribute__((ns_consumed)) for types with lifetime.

View File

@ -779,7 +779,20 @@ void CodeGenFunction::StartObjCMethod(const ObjCMethodDecl *OMD,
const CGFunctionInfo &FI = CGM.getTypes().arrangeObjCMethodDeclaration(OMD);
if (OMD->isDirectMethod()) {
// Default hidden visibility
Fn->setVisibility(llvm::Function::HiddenVisibility);
if (CGM.isObjCDirectPreconditionThunkEnabled()) {
// However, if we expose the symbol, and the decl (property or method)
// have visibility attribute set ...
const NamedDecl *Decl = OMD;
if (const auto *PD = OMD->findPropertyDecl()) {
Decl = PD;
}
// ... then respect source level visibility setting
if (auto V = Decl->getExplicitVisibility(NamedDecl::VisibilityForValue)) {
Fn->setVisibility(CGM.GetLLVMVisibility(*V));
}
}
CGM.SetLLVMFunctionAttributes(OMD, FI, Fn, /*IsThunk=*/false);
CGM.SetLLVMFunctionAttributesForDefinition(OMD, Fn);
} else {
@ -799,10 +812,6 @@ void CodeGenFunction::StartObjCMethod(const ObjCMethodDecl *OMD,
OMD->getLocation(), StartLoc);
if (OMD->isDirectMethod()) {
// This function is a direct call, it has to implement a nil check
// on entry.
//
// TODO: possibly have several entry points to elide the check
CGM.getObjCRuntime().GenerateDirectMethodPrologue(*this, Fn, OMD, CD);
}

View File

@ -26,6 +26,7 @@
#include "clang/AST/StmtObjC.h"
#include "clang/Basic/CodeGenOptions.h"
#include "clang/Basic/LangOptions.h"
#include "clang/CodeGen/CodeGenABITypes.h"
#include "clang/CodeGen/ConstantInitBuilder.h"
#include "llvm/ADT/CachedHashString.h"
#include "llvm/ADT/DenseSet.h"
@ -1217,6 +1218,15 @@ public:
DirectMethodInfo &GenerateDirectMethod(const ObjCMethodDecl *OMD,
const ObjCContainerDecl *CD);
llvm::Function *GenerateObjCDirectThunk(const ObjCMethodDecl *OMD,
const ObjCContainerDecl *CD,
llvm::Function *Implementation);
llvm::Function *GetDirectMethodCallee(const ObjCMethodDecl *OMD,
const ObjCContainerDecl *CD,
bool ReceiverCanBeNull,
bool ClassObjectCanBeUnrealized);
/// Generate class realization code: [self self]
/// This is used for class methods to ensure the class is initialized.
/// Returns the realized class object.
@ -2741,6 +2751,9 @@ CodeGen::RValue CGObjCCommonMac::EmitMessageSend(
bool ReceiverCanBeNull =
canMessageReceiverBeNull(CGF, Method, IsSuper, ClassReceiver, Arg0);
bool ClassObjectCanBeUnrealized =
Method && Method->isClassMethod() &&
canClassObjectBeUnrealized(ClassReceiver, CGF);
bool RequiresNullCheck = false;
bool RequiresReceiverValue = true;
@ -2749,8 +2762,8 @@ CodeGen::RValue CGObjCCommonMac::EmitMessageSend(
llvm::FunctionCallee Fn = nullptr;
if (Method && Method->isDirectMethod()) {
assert(!IsSuper);
auto Info = GenerateDirectMethod(Method, Method->getClassInterface());
Fn = Info.Implementation;
Fn = GetDirectMethodCallee(Method, Method->getClassInterface(),
ReceiverCanBeNull, ClassObjectCanBeUnrealized);
// Direct methods will synthesize the proper `_cmd` internally,
// so just don't bother with setting the `_cmd` argument.
RequiresSelValue = false;
@ -2811,6 +2824,23 @@ CodeGen::RValue CGObjCCommonMac::EmitMessageSend(
if (!RequiresNullCheck && Method && Method->hasParamDestroyedInCallee())
RequiresNullCheck = true;
if (CGM.shouldHavePreconditionInline(Method)) {
// For variadic class methods, we need to inline precondition checks. That
// include two things:
// 1. We have to inline the class realization if we are not sure if it must
// have been realized.
if (ClassReceiver && ClassObjectCanBeUnrealized) {
// Perform class realization using the helper function
Arg0 = GenerateClassRealization(CGF, Arg0, ClassReceiver);
ActualArgs[0] = CallArg(RValue::get(Arg0), ActualArgs[0].Ty);
}
// 2. We have to inline the precondition thunk if we are not sure if the
// receiver can be null. Luckly, `NullReturnState` already does that for
// corner cases like ns_consume, so we only need to override the flag,
// regardless if the return value is unused.
RequiresNullCheck |= ReceiverCanBeNull;
}
NullReturnState nullReturn;
if (RequiresNullCheck) {
nullReturn.init(CGF, Arg0);
@ -4563,55 +4593,262 @@ llvm::Function *CGObjCCommonMac::GenerateMethod(const ObjCMethodDecl *OMD,
return Method;
}
/// Generate or retrieve a direct method info.
CGObjCCommonMac::DirectMethodInfo &
CGObjCCommonMac::GenerateDirectMethod(const ObjCMethodDecl *OMD,
const ObjCContainerDecl *CD) {
auto *COMD = OMD->getCanonicalDecl();
auto I = DirectMethodDefinitions.find(COMD);
llvm::Function *OldFn = nullptr, *Fn = nullptr;
if (I != DirectMethodDefinitions.end()) {
// Objective-C allows for the declaration and implementation types
// to differ slightly.
//
// If we're being asked for the Function associated for a method
// implementation, a previous value might have been cached
// based on the type of the canonical declaration.
//
// If these do not match, then we'll replace this function with
// a new one that has the proper type below.
// Fast path: return cached entry if this is not an implementation (no body)
// or if the return types match between declaration and implementation.
auto Cached = DirectMethodDefinitions.find(COMD);
if (Cached != DirectMethodDefinitions.end()) {
if (!OMD->getBody() || COMD->getReturnType() == OMD->getReturnType())
return I->second;
OldFn = I->second.Implementation;
return Cached->second;
}
CodeGenTypes &Types = CGM.getTypes();
llvm::FunctionType *MethodTy =
Types.GetFunctionType(Types.arrangeObjCMethodDeclaration(OMD));
std::string Name =
getSymbolNameForMethod(OMD, /*includeCategoryName*/ false,
CGM.isObjCDirectPreconditionThunkEnabled());
std::string ThunkName = Name + "_thunk";
if (OldFn) {
Fn = llvm::Function::Create(MethodTy, llvm::GlobalValue::ExternalLinkage,
"", &CGM.getModule());
Fn->takeName(OldFn);
OldFn->replaceAllUsesWith(Fn);
// Replace OldFn with NewFn: transfer name, replace all uses, and erase.
auto ReplaceFunction = [](llvm::Function *OldFn, llvm::Function *NewFn) {
NewFn->takeName(OldFn);
OldFn->replaceAllUsesWith(NewFn);
OldFn->eraseFromParent();
};
// Replace the cached implementation in the map.
I->second.Implementation = Fn;
} else {
auto Name = getSymbolNameForMethod(OMD, /*include category*/ false);
// Check if the function already exists in the module (created by Clang or
// Swift).
llvm::Function *Fn = CGM.getModule().getFunction(Name);
// Function doesn't exist yet.
if (!Fn) {
Fn = llvm::Function::Create(MethodTy, llvm::GlobalValue::ExternalLinkage,
Name, &CGM.getModule());
auto [It, inserted] = DirectMethodDefinitions.insert(
std::make_pair(COMD, DirectMethodInfo(Fn)));
I = It;
return DirectMethodDefinitions.insert({COMD, DirectMethodInfo(Fn)})
.first->second;
}
// Return reference to DirectMethodInfo (contains both Implementation and
// Thunk)
return I->second;
// Function exists with matching type.
// Other frontends operating on the same module may have created the function.
if (Fn->getFunctionType() == MethodTy) {
// Reinforce linkage in case Swift created it with different linkage.
Fn->setLinkage(llvm::GlobalValue::ExternalLinkage);
// Check if Swift also created a thunk for this method.
DirectMethodInfo Info(Fn);
if (llvm::Function *Thunk = CGM.getModule().getFunction(ThunkName))
Info.Thunk = Thunk;
return DirectMethodDefinitions.insert({COMD, Info}).first->second;
}
// Function exists but with mismatched type - replace it.
// This happens when Swift's optional handling differs from ObjC, or when
// ObjC declaration and implementation have slightly different return types.
llvm::Function *NewFn = llvm::Function::Create(
MethodTy, llvm::GlobalValue::ExternalLinkage, "", &CGM.getModule());
ReplaceFunction(Fn, NewFn);
// Check if the thunk also needs replacement.
DirectMethodInfo Info(NewFn);
if (llvm::Function *OldThunk = CGM.getModule().getFunction(ThunkName)) {
llvm::Function *NewThunk = GenerateObjCDirectThunk(OMD, CD, NewFn);
ReplaceFunction(OldThunk, NewThunk);
Info.Thunk = NewThunk;
}
if (Cached != DirectMethodDefinitions.end()) {
Cached->second = Info;
return Cached->second;
}
return DirectMethodDefinitions.insert({COMD, Info}).first->second;
}
/// Start an Objective-C direct method precondition thunk.
void CodeGenFunction::StartObjCDirectPreconditionThunk(
const ObjCMethodDecl *OMD, llvm::Function *Fn, const CGFunctionInfo &FI) {
// Mark this as a thunk function to disable ARC parameter processing
// and other thunk-inappropriate behavior. We don't need to retain
// parameters because we're going to immediately forward them.
//
// Skipping ARC parameter processing is correct as long as (1) we don't
// run any code that could invalidate the parameters between the start of
// the thunk and the call and (2) we don't use the parameters after the
// call. Both hold whether we tail-call or not. Class realization could in
// theory invalidate parameters, but we assume that doesn't happen in
// practice.
CurFuncIsThunk = true;
// Build argument list for StartFunction.
// We must include all parameters to match the thunk's LLVM function type.
FunctionArgList FunctionArgs;
FunctionArgs.push_back(OMD->getSelfDecl());
FunctionArgs.append(OMD->param_begin(), OMD->param_end());
// The Start/Finish thunk pattern is borrowed from CGVTables.cpp
// for C++ virtual method thunks, but adapted for ObjC direct methods.
//
// Like C++ thunks, we don't have an actual AST body for the thunk - we only
// have the method's parameter declarations. Therefore, we pass empty
// `GlobalDecl` to `StartFunction` ...
StartFunction(GlobalDecl(), OMD->getReturnType(), Fn, FI, FunctionArgs,
OMD->getLocation(), OMD->getLocation());
// and manually set the decl afterwards so other utilities / helpers in CGF
// can still access the AST (e.g. arrange function arguments)
CurCodeDecl = OMD;
CurFuncDecl = OMD;
}
/// Finish an Objective-C direct method precondition thunk.
void CodeGenFunction::FinishObjCDirectPreconditionThunk() {
// Create a dummy block to return the value of the thunk.
//
// The non-nil branch alredy returned because of musttail.
// Only nil branch will jump to this return block.
// If the nil check is not emitted (for class methods), this will be a dead
// block.
//
// Either way, the LLVM optimizer will simplify it later. This is just to make
// CFG happy.
EmitBlock(createBasicBlock("dummy_ret_block"));
// Disable the final ARC autorelease.
// Thunk functions are tailcall to actual implementation, so it doesn't need
// to worry about ARC.
AutoreleaseResult = false;
// Clear these to restore the invariants expected by
// StartFunction/FinishFunction.
CurCodeDecl = nullptr;
CurFuncDecl = nullptr;
FinishFunction();
}
llvm::Function *
CGObjCCommonMac::GenerateObjCDirectThunk(const ObjCMethodDecl *OMD,
const ObjCContainerDecl *CD,
llvm::Function *Implementation) {
assert(CGM.shouldHavePreconditionThunk(OMD) &&
"Should only generate thunk when optimization enabled");
assert(Implementation && "Implementation must exist");
llvm::FunctionType *ThunkTy = Implementation->getFunctionType();
std::string ThunkName = Implementation->getName().str() + "_thunk";
// Create thunk with linkonce_odr linkage (allows deduplication)
llvm::Function *Thunk =
llvm::Function::Create(ThunkTy, llvm::GlobalValue::LinkOnceODRLinkage,
ThunkName, &CGM.getModule());
// Thunks should always have hidden visibility, other link units will have
// their own version of the (identical) thunk. If they make cross link-unit
// call, they are either calling through their thunk or directly dispatching
// to the true implementation, so making thunk visibile is meaningless.
Thunk->setVisibility(llvm::GlobalValue::HiddenVisibility);
Thunk->setUnnamedAddr(llvm::GlobalValue::UnnamedAddr::Global);
// Start the ObjC direct thunk (sets up state and calls StartFunction)
const CGFunctionInfo &FI = CGM.getTypes().arrangeObjCMethodDeclaration(OMD);
// Create a CodeGenFunction to generate the thunk body
CodeGenFunction CGF(CGM);
CGF.StartObjCDirectPreconditionThunk(OMD, Thunk, FI);
// Set function attributes from CGFunctionInfo to ensure the thunk has
// matching parameter attributes (especially sret) for musttail correctness.
// We use SetLLVMFunctionAttributes rather than copying from Implementation
// because Implementation may not have its attributes set yet at this point.
CGM.SetLLVMFunctionAttributes(GlobalDecl(OMD), FI, Thunk, /*IsThunk=*/false);
CGM.SetLLVMFunctionAttributesForDefinition(OMD, Thunk);
// - [self self] for class methods (class realization)
// - if (self == nil) branch to nil block with zero return
// - continuation block for non-nil case
GenerateDirectMethodsPreconditionCheck(CGF, Thunk, OMD, CD);
// Now emit the musttail call to the true implementation
// Collect all arguments for forwarding
SmallVector<llvm::Value *, 8> Args;
for (auto &Arg : Thunk->args())
Args.push_back(&Arg);
// Create musttail call to the implementation
llvm::CallInst *Call = CGF.Builder.CreateCall(Implementation, Args);
Call->setTailCallKind(llvm::CallInst::TCK_MustTail);
// Apply call-site attributes using ConstructAttributeList
// When sret is used, the call must have matching sret attributes on the first
// parameter for musttail to work correctly. This mirrors what C++ thunks do
// in EmitMustTailThunk.
unsigned CallingConv;
llvm::AttributeList Attrs;
CGM.ConstructAttributeList(Implementation->getName(), FI, GlobalDecl(OMD),
Attrs, CallingConv, /*AttrOnCallSite=*/true,
/*IsThunk=*/false);
Call->setAttributes(Attrs);
Call->setCallingConv(static_cast<llvm::CallingConv::ID>(CallingConv));
// Immediately return the call result (musttail requirement).
// For sret returns, the Apple ABI produces void-returning LLVM functions,
// so checking the LLVM return type is suffice.
if (ThunkTy->getReturnType()->isVoidTy())
CGF.Builder.CreateRetVoid();
else
CGF.Builder.CreateRet(Call);
// Finish the ObjC direct thunk (creates dummy block and calls FinishFunction)
CGF.FinishObjCDirectPreconditionThunk();
return Thunk;
}
llvm::Function *CGObjCCommonMac::GetDirectMethodCallee(
const ObjCMethodDecl *OMD, const ObjCContainerDecl *CD,
bool ReceiverCanBeNull, bool ClassObjectCanBeUnrealized) {
// Get from cache or populate the function declaration.
// Copy by value to avoid holding a reference into DirectMethodDefinitions
// DenseMap, which could be invalidated by future insertions.
DirectMethodInfo Info = GenerateDirectMethod(OMD, CD);
// If thunk optimization not enabled (or variadic method which can't use
// thunks), use implementation directly. Variadic methods and methods without
// the optimization enabled include precondition checks in the implementation.
if (!CGM.shouldHavePreconditionThunk(OMD)) {
return Info.Implementation;
}
// Thunk is lazily generated.
auto getOrCreateThunk = [&]() {
if (!Info.Thunk) {
Info.Thunk = GenerateObjCDirectThunk(OMD, CD, Info.Implementation);
// Write back the lazily created thunk to the map.
DirectMethodDefinitions.insert_or_assign(OMD->getCanonicalDecl(), Info);
}
return Info.Thunk;
};
if (OMD->isInstanceMethod()) {
// If we can prove instance methods receiver is not null, return the true
// implementation
return ReceiverCanBeNull ? getOrCreateThunk() : Info.Implementation;
}
assert(OMD->isClassMethod() &&
"OMD should either be a class method or instance method");
// For class methods, it need to be non-null and realized before we dispatch
// to true implementation
return (ReceiverCanBeNull || ClassObjectCanBeUnrealized)
? getOrCreateThunk()
: Info.Implementation;
}
llvm::Value *
@ -4708,7 +4945,8 @@ void CGObjCCommonMac::GenerateDirectMethodPrologue(
CodeGenFunction &CGF, llvm::Function *Fn, const ObjCMethodDecl *OMD,
const ObjCContainerDecl *CD) {
// Generate precondition checks (class realization + nil check) if needed
GenerateDirectMethodsPreconditionCheck(CGF, Fn, OMD, CD);
if (!CGM.isObjCDirectPreconditionThunkEnabled())
GenerateDirectMethodsPreconditionCheck(CGF, Fn, OMD, CD);
auto &Builder = CGF.Builder;
// Only synthesize _cmd if it's referenced
@ -8533,3 +8771,18 @@ CodeGen::CreateMacObjCRuntime(CodeGen::CodeGenModule &CGM) {
}
llvm_unreachable("bad runtime");
}
// Public wrapper function for external compilers (e.g., Swift) to access
// the Mac runtime's GetDirectMethodCallee functionality.
llvm::Function *clang::CodeGen::getObjCDirectMethodCallee(
CodeGenModule &CGM, const ObjCMethodDecl *OMD, const ObjCContainerDecl *CD,
bool ReceiverCanBeNull, bool ClassObjectCanBeUnrealized) {
// This function should only be called when targeting Darwin platforms,
// which always use the Mac runtime.
assert(CGM.getLangOpts().ObjCRuntime.isNeXTFamily() &&
"getObjCDirectMethodCallee requires Mac ObjC runtime");
CGObjCCommonMac *MacRuntime =
static_cast<CGObjCCommonMac *>(&CGM.getObjCRuntime());
return MacRuntime->GetDirectMethodCallee(OMD, CD, ReceiverCanBeNull,
ClassObjectCanBeUnrealized);
}

View File

@ -413,6 +413,13 @@ bool CGObjCRuntime::canMessageReceiverBeNull(
return true;
}
bool CGObjCRuntime::canClassObjectBeUnrealized(
const ObjCInterfaceDecl *CalleeClassDecl, CodeGenFunction &CGF) const {
// TODO
// Otherwise, assume it can be unrealized.
return true;
}
bool CGObjCRuntime::isWeakLinkedClass(const ObjCInterfaceDecl *ID) {
do {
if (ID->isWeakImported())
@ -464,10 +471,10 @@ clang::CodeGen::emitObjCProtocolObject(CodeGenModule &CGM,
std::string CGObjCRuntime::getSymbolNameForMethod(const ObjCMethodDecl *OMD,
bool includeCategoryName,
bool includePrefixByte) {
bool useDirectABI) {
std::string buffer;
llvm::raw_string_ostream out(buffer);
CGM.getCXXABI().getMangleContext().mangleObjCMethodName(
OMD, out, includePrefixByte, includeCategoryName);
OMD, out, /*includePrefixByte=*/true, includeCategoryName, useDirectABI);
return buffer;
}

View File

@ -118,7 +118,7 @@ public:
std::string getSymbolNameForMethod(const ObjCMethodDecl *method,
bool includeCategoryName = true,
bool includePrefixByte = true);
bool useDirectABI = false);
/// Generate the function required to register all Objective-C components in
/// this compilation unit with the runtime library.
@ -347,6 +347,16 @@ public:
const ObjCInterfaceDecl *classReceiver,
llvm::Value *receiver);
/// Check if a class object can be unrealized (not yet initialized).
/// Returns true if the class may be unrealized, false if provably realized.
///
/// TODO: Returns false if:
/// - An instance method on the same class was called in a dominating path
/// - The class was explicitly realized earlier in control flow
/// - Note: [Parent foo] does NOT realize Child (inheritance care needed)
bool canClassObjectBeUnrealized(const ObjCInterfaceDecl *ClassDecl,
CodeGenFunction &CGF) const;
static bool isWeakLinkedClass(const ObjCInterfaceDecl *cls);
/// Destroy the callee-destroyed arguments of the given method,

View File

@ -2462,6 +2462,14 @@ public:
void FinishThunk();
/// Start an Objective-C direct method thunk.
void StartObjCDirectPreconditionThunk(const ObjCMethodDecl *OMD,
llvm::Function *Fn,
const CGFunctionInfo &FI);
/// Finish an Objective-C direct method thunk.
void FinishObjCDirectPreconditionThunk();
/// Emit a musttail call for a thunk with a potentially adjusted this pointer.
void EmitMustTailThunk(GlobalDecl GD, llvm::Value *AdjustedThisPtr,
llvm::FunctionCallee Callee);

View File

@ -722,33 +722,37 @@ public:
/// Return true iff an Objective-C runtime has been configured.
bool hasObjCRuntime() { return !!ObjCRuntime; }
/// Check if a direct method should use precondition thunks (exposed symbols).
/// This applies to ALL direct methods (including variadic).
/// Returns false if OMD is null or not a direct method.
/// Check if the precondition thunk optimization is enabled.
/// This checks runtime support and codegen options, but does NOT check
/// whether a specific method is eligible for thunks or inline preconditions.
///
/// Also checks the runtime family, currently we only support NeXT.
/// TODO: Add support for GNUStep as well.
bool usePreconditionThunk(const ObjCMethodDecl *OMD) const {
return OMD && OMD->isDirectMethod() &&
getLangOpts().ObjCRuntime.allowsDirectDispatch() &&
/// TODO: Add support for GNUStep as well, currently only supports NeXT
/// family.
bool isObjCDirectPreconditionThunkEnabled() const {
return getLangOpts().ObjCRuntime.allowsDirectDispatch() &&
getLangOpts().ObjCRuntime.isNeXTFamily() &&
getCodeGenOpts().ObjCDirectPreconditionThunk;
}
/// Check if a direct method should use precondition thunks at call sites.
/// This applies only to non-variadic direct methods.
/// Returns false if OMD is null or not eligible for thunks (variadic
/// methods).
/// Returns false if OMD is null, not a direct method, or variadic.
///
/// Variadic methods use inline preconditions instead of thunks to avoid
/// musttail complexity across different architectures.
bool shouldHavePreconditionThunk(const ObjCMethodDecl *OMD) const {
return OMD && usePreconditionThunk(OMD) && !OMD->isVariadic();
return OMD && OMD->isDirectMethod() && !OMD->isVariadic() &&
isObjCDirectPreconditionThunkEnabled();
}
/// Check if a direct method should have inline precondition checks at call
/// sites. This applies to direct methods that cannot use thunks (variadic
/// methods). These methods get exposed symbols but need inline precondition
/// checks instead of thunks. Returns false if OMD is null or not eligible.
/// sites.
/// Returns false if OMD is null, not a direct method, or not variadic.
///
/// Variadic direct methods use inline preconditions rather than thunks
/// to avoid musttail complexity across different architectures.
bool shouldHavePreconditionInline(const ObjCMethodDecl *OMD) const {
return OMD && usePreconditionThunk(OMD) && OMD->isVariadic();
return OMD && OMD->isDirectMethod() && OMD->isVariadic() &&
isObjCDirectPreconditionThunkEnabled();
}
const std::string &getModuleNameHash() const { return ModuleNameHash; }

View File

@ -1,19 +1,45 @@
// RUN: %clang_cc1 -emit-llvm -fobjc-arc -triple x86_64-apple-darwin10 %s -o - | FileCheck %s
// RUN: %clang_cc1 -emit-llvm -fobjc-arc -triple arm64-apple-darwin10 -fobjc-direct-precondition-thunk %s -o - | FileCheck %s --check-prefix=EXPOSE-DIRECT
__attribute__((objc_root_class))
@interface Root
- (Root *)method __attribute__((objc_direct));
@end
// EXPOSE-DIRECT-LABEL: define ptr @useMethod
Root* useMethod(Root *root) {
// EXPOSE-DIRECT: call ptr @"-[Root method]D_thunk"
return [root method];
}
@implementation Root
// The IR emission order depends on the ret-type mismatch: [Root method] is
// emitted before [Root something] because GenerateDirectMethod replaces the
// function when declaration (Root *) and implementation (id) types differ.
//
// CHECK-LABEL: define hidden ptr @"\01-[Root method]"(
// EXPOSE-DIRECT-LABEL: define hidden ptr @"-[Root method]D"(ptr noundef
- (id)method {
return self;
}
// EXPOSE-DIRECT-LABEL: define linkonce_odr hidden ptr @"-[Root method]D_thunk"
// EXPOSE-DIRECT-LABEL: entry:
// EXPOSE-DIRECT: %[[IS_NIL:.*]] = icmp eq ptr {{.*}}, null
// EXPOSE-DIRECT: br i1 %[[IS_NIL]], label %objc_direct_method.self_is_nil, label %objc_direct_method.cont
// EXPOSE-DIRECT-LABEL: objc_direct_method.self_is_nil:
// EXPOSE-DIRECT: call void @llvm.memset.p0.i64
// EXPOSE-DIRECT: br label %dummy_ret_block
// EXPOSE-DIRECT-LABEL: objc_direct_method.cont:
// EXPOSE-DIRECT: %[[RET:.*]] = musttail call ptr @"-[Root method]D"
// EXPOSE-DIRECT: ret ptr %[[RET]]
// EXPOSE-DIRECT-LABEL: dummy_ret_block:
// CHECK-LABEL: define internal ptr @"\01-[Root something]"(
// EXPOSE-DIRECT-LABEL: define internal ptr @"\01-[Root something]"(ptr noundef
- (id)something {
// CHECK: %{{[^ ]*}} = call {{.*}} @"\01-[Root method]"
return [self method];
}
// CHECK-LABEL: define hidden ptr @"\01-[Root method]"(
- (id)method {
return self;
}
@end

View File

@ -0,0 +1,98 @@
// RUN: %clang_cc1 -emit-llvm -fobjc-arc -fblocks -fobjc-runtime-has-weak \
// RUN: -triple arm64-apple-macos11.0 \
// RUN: -fobjc-direct-precondition-thunk %s -o - | FileCheck %s
#define nil ((id)0)
__attribute__((objc_root_class))
@interface LinkedList
@property(direct, readonly, nonatomic) int v;
@property(direct, strong, nonatomic) LinkedList* next;
@property(direct, readonly, nonatomic) int instanceId;
@property(strong, nonatomic, direct) void ( ^ printBlock )( void );
+ (instancetype)alloc;
- (instancetype)initWithV:(int)v Next:(id)next __attribute__((objc_direct));
- (instancetype)clone __attribute__((objc_direct));
- (void)print __attribute__((objc_direct));
- (instancetype) reverseWithPrev:(id) prev __attribute__((objc_direct));
- (int) size __attribute__((objc_direct));
- (int) sum __attribute__((objc_direct));
- (double) avg __attribute__((objc_direct));
- (int) sumWith:(LinkedList *) __attribute__((ns_consumed)) other __attribute__((objc_direct));
@end
@implementation LinkedList
static int numInstances=0;
- (instancetype)initWithV:(int)v Next:(id)next{
_v = v;
_next = next;
_instanceId = numInstances;
LinkedList* __weak weakSelf = self;
_printBlock = ^void(void) { [weakSelf print]; };
numInstances++;
return self;
}
- (instancetype) clone {
return [[LinkedList alloc] initWithV:self.v Next:[self.next clone]];
}
- (void) print {
[self.next print];
}
- (LinkedList*) reverseWithPrev:(LinkedList*) prev{
LinkedList* newHead = (self.next == nil) ? self : [self.next reverseWithPrev:self];
self.next = prev;
return newHead;
}
- (int) size {
return 1 + [self.next size];
}
- (int) sum {
return self.v + [self.next sum];
}
- (double) avg {
return (double)[self sum] / (double)[self size];
}
- (int) sumWith:(LinkedList *) __attribute__((ns_consumed)) other {
return [self sum] + [other sum];
}
@end
int main() {
// CHECK: call ptr @"-[LinkedList initWithV:Next:]D_thunk"
// CHECK: call ptr @"-[LinkedList initWithV:Next:]D_thunk"
LinkedList* ll = [[LinkedList alloc] initWithV:8 Next:[[LinkedList alloc] initWithV:7 Next:nil]];
// CHECK: call ptr @"-[LinkedList initWithV:Next:]D_thunk"
// CHECK: call ptr @"-[LinkedList next]D_thunk"
// CHECK: call void @"-[LinkedList setNext:]D_thunk"
ll.next.next = [[LinkedList alloc] initWithV:6 Next:nil];
// CHECK: call void @"-[LinkedList print]D_thunk"
[ll print];
// CHECK: call ptr @"-[LinkedList clone]D_thunk"
LinkedList* cloned = [ll clone];
// CHECK: call void @"-[LinkedList print]D_thunk"
[cloned print];
// CHECK: call ptr @"-[LinkedList printBlock]D_thunk"
cloned.printBlock();
// Test ns_consumed parameter with direct method thunk.
// CHECK: call i32 @"-[LinkedList sumWith:]D_thunk"
int combined = [ll sumWith:[cloned clone]];
// CHECK: call ptr @"-[LinkedList reverseWithPrev:]D_thunk"
ll = [ll reverseWithPrev:nil];
// CHECK: call void @"-[LinkedList print]D_thunk"
[ll print];
return 0;
}

View File

@ -0,0 +1,104 @@
// Test variadic direct methods - use inline preconditions instead of thunks
// to avoid musttail complexity across different architectures.
// RUN: %clang_cc1 -emit-llvm -fobjc-arc -triple arm64-apple-darwin10 \
// RUN: -fobjc-direct-precondition-thunk %s -o - | FileCheck %s
__attribute__((objc_root_class))
@interface Root
- (int)varMethod:(int)first, ... __attribute__((objc_direct));
+ (void)printf:(Root *)format, ... __attribute__((objc_direct));
@end
// Add a weakly linked class and a weakly linked class method
__attribute__((objc_root_class, weak_import))
@interface WeakRoot
+ (int)weakPrintf:(int)first, ... __attribute__((objc_direct, weak_import));
@end
@implementation Root
// Variadic methods use inline preconditions instead of thunks
// to avoid musttail complexity across different architectures.
// CHECK-LABEL: define hidden i32 @"-[Root varMethod:]D"(
// CHECK-NOT: @"\01-[Root varMethod:]"
// CHECK-NOT: @"-[Root varMethod:]D_thunk"
- (int)varMethod:(int)first, ... {
// Should NOT have nil check (moved to caller)
// CHECK-NOT: icmp eq ptr {{.*}}, null
// CHECK-NOT: objc_direct_method.self_is_nil
return first;
}
// CHECK-LABEL: define hidden void @"+[Root printf:]D"(
// CHECK-NOT: @"\01+[Root printf:]"
// CHECK-NOT: @"+[Root printf:]D_thunk"
// Class realization should be at the call site, not in the implementation.
// CHECK-NOT: objc_msgSend
+ (void)printf:(Root *)format, ... {}
@end
// Test: Nullable receiver should have inline nil check
// CHECK-LABEL: define{{.*}} void @useRoot(
void useRoot(Root *_Nullable root) {
// For nullable receivers, we should emit nil check inline
// CHECK: icmp eq ptr %{{[0-9]+}}, null
// CHECK: br i1 %{{[0-9]+}}, label %msgSend.null-receiver, label %msgSend.call
// CHECK: msgSend.call:
// CHECK: call i32 (ptr, i32, ...) @"-[Root varMethod:]D"(ptr noundef %{{[0-9]+}}, i32 noundef 1, i32 noundef 2, double noundef 3.0{{.*}})
// CHECK: br label %msgSend.cont
// CHECK: msgSend.null-receiver:
// CHECK: br label %msgSend.cont
// CHECK: msgSend.cont:
[root varMethod:1, 2, 3.0];
// Class realization before call
// CHECK: %{{.*}} = load ptr, ptr @"OBJC_CLASSLIST_REFERENCES_$
// CHECK: %{{.*}} = load ptr, ptr @OBJC_SELECTOR_REFERENCES_,
// CHECK: %{{.*}} = call ptr @objc_msgSend
// CHECK: call void (ptr, ptr, ...) @"+[Root printf:]D"(
[Root printf:root, "hello", root];
// For weakly linked class, inline realization first
// CHECK: %{{.*}} = load ptr, ptr @"OBJC_CLASSLIST_REFERENCES_$
// CHECK: %{{.*}} = load ptr, ptr @OBJC_SELECTOR_REFERENCES_,
// CHECK: %{{.*}} = call ptr @objc_msgSend
// Then perform nil check
// CHECK: %{{.*}} = icmp eq ptr %{{.*}}, null
// CHECK: br i1 %{{.*}}, label %msgSend.null-receiver{{.*}}, label %msgSend.call{{.*}}
// Finally call the class method
// CHECK: %{{.*}} = call i32 (ptr, i32, ...) @"+[WeakRoot weakPrintf:]D"
[WeakRoot weakPrintf: 1, 2, 3.0];
}
// Test: Non-null receiver
// CHECK-LABEL: define{{.*}} void @useRootNonNull(
void useRootNonNull(Root *_Nonnull root) {
// TODO: For nonnull receivers, we should not emit nil check inline
// CHECK: icmp eq ptr %{{[0-9]+}}, null
// CHECK: br i1 %{{[0-9]+}}, label %msgSend.null-receiver, label %msgSend.call
// CHECK: msgSend.call:
// CHECK: call i32 (ptr, i32, ...) @"-[Root varMethod:]D"(ptr noundef %{{[0-9]+}}, i32 noundef 1, i32 noundef 2, double noundef 3.0{{.*}})
// CHECK: br label %msgSend.cont
// CHECK: msgSend.null-receiver:
// CHECK: br label %msgSend.cont
// CHECK: msgSend.cont:
[root varMethod:1, 2, 3.0];
// Class realization before call
// CHECK: %{{.*}} = load ptr, ptr @"OBJC_CLASSLIST_REFERENCES_$
// CHECK: %{{.*}} = load ptr, ptr @OBJC_SELECTOR_REFERENCES_,
// CHECK: %{{.*}} = call ptr @objc_msgSend
// CHECK: call void (ptr, ptr, ...) @"+[Root printf:]D"(
[Root printf:root, "hello", root];
}

View File

@ -0,0 +1,119 @@
// RUN: rm -rf %t
// RUN: split-file %s %t
// Test 1: Check IR from library implementation (visibility attributes)
// RUN: %clang_cc1 -triple arm64-apple-darwin -fobjc-arc \
// RUN: -fobjc-direct-precondition-thunk -emit-llvm -o - %t/foo.m \
// RUN: -I %t | FileCheck %s --check-prefix=FOO_M
// Test 2: Check IR from main (consumer)
// RUN: %clang_cc1 -triple arm64-apple-darwin -fobjc-arc \
// RUN: -fobjc-direct-precondition-thunk -emit-llvm -o - %t/main.m \
// RUN: -I %t | FileCheck %s --check-prefix=MAIN_M
//--- foo.h
// Header for libFoo
__attribute__((objc_root_class))
@interface Foo
// Direct properties with default hidden visibility
@property (nonatomic, direct) int privateValue;
// Direct properties with explicit default visibility
@property (nonatomic, direct) int exportedValue __attribute__((visibility("default")));
+ (instancetype)alloc;
- (instancetype)initWithprivateValue:(int)x exportedValue:(int)y;
// Default hidden visibility
- (int)instanceMethod:(int)x __attribute__((objc_direct));
+ (int)classMethod:(int)x __attribute__((objc_direct));
// Explicit default visibility (should be exported)
- (int)exportedInstanceMethod:(int)x __attribute__((objc_direct, visibility("default")));
+ (int)exportedClassMethod:(int)x __attribute__((objc_direct, visibility("default")));
@end
//--- foo.m
// libFoo does not have thunks because the true implementation is not used internally.
// FOO_M-NOT: @{{.*}}_thunk
#import "foo.h"
@implementation Foo
// FOO_M-LABEL: define internal ptr @"\01-[Foo initWithprivateValue:exportedValue:]"
- (instancetype)initWithprivateValue:(int)x exportedValue:(int)y {
_privateValue = x;
_exportedValue = y;
return self;
}
// FOO_M-LABEL: define hidden i32 @"-[Foo instanceMethod:]D"
- (int)instanceMethod:(int)x {
// Compiler is smart enough to know that self is non-nil, so we dispatch to
// true implementation.
// FOO_M: call i32 @"-[Foo privateValue]D"
// FOO_M: call i32 @"-[Foo exportedValue]D"
return x + [self privateValue] + [self exportedValue];
}
// Hidden property getter and setter (default visibility)
// FOO_M-LABEL: define hidden i32 @"-[Foo privateValue]D"
// Exported property getter and setter (explicit default visibility)
// FOO_M-LABEL: define dso_local i32 @"-[Foo exportedValue]D"
// FOO_M-LABEL: define hidden i32 @"+[Foo classMethod:]D"
+ (int)classMethod:(int)x {
return x * 3;
}
// FOO_M-LABEL: define dso_local i32 @"-[Foo exportedInstanceMethod:]D"
- (int)exportedInstanceMethod:(int)x {
// FOO_M: call i32 @"-[Foo privateValue]D"
// FOO_M: call i32 @"-[Foo exportedValue]D"
return x + [self privateValue] + [self exportedValue];
}
// FOO_M-LABEL: define dso_local i32 @"+[Foo exportedClassMethod:]D"
+ (int)exportedClassMethod:(int)x {
return x * 5;
}
// Hidden property getter and setter (default visibility)
// FOO_M-LABEL: define hidden void @"-[Foo setPrivateValue:]D"
// Exported property getter and setter (explicit default visibility)
// FOO_M-LABEL: define dso_local void @"-[Foo setExportedValue:]D"
@end
//--- main.m
// Consumer of libFoo (separate linkage unit)
#import "foo.h"
int printf(const char *, ...);
int main() {
Foo *obj = [[Foo alloc] initWithprivateValue:10 exportedValue:20];
printf("Allocated Foo\n");
// MAIN_M: call void @"-[Foo setExportedValue:]D_thunk"
[obj setExportedValue:30];
// MAIN_M: call i32 @"-[Foo exportedValue]D_thunk"
printf("Reset exportedValue: %d\n", [obj exportedValue]);
// MAIN_M: call i32 @"-[Foo exportedInstanceMethod:]D_thunk"
printf("Exported instance: %d\n", [obj exportedInstanceMethod:10]);
// MAIN_M: call i32 @"+[Foo exportedClassMethod:]D_thunk"
printf("Exported class: %d\n", [Foo exportedClassMethod:10]);
return 0;
}
// Thunks are generated during compilation
// MAIN_M-LABEL: define linkonce_odr hidden void @"-[Foo setExportedValue:]D_thunk"
// MAIN_M-LABEL: define linkonce_odr hidden i32 @"-[Foo exportedValue]D_thunk"
// MAIN_M-LABEL: define linkonce_odr hidden i32 @"-[Foo exportedInstanceMethod:]D_thunk"
// MAIN_M-LABEL: define linkonce_odr hidden i32 @"+[Foo exportedClassMethod:]D_thunk"

View File

@ -0,0 +1,330 @@
// RUN: %clang_cc1 -emit-llvm -fobjc-arc -triple arm64-apple-darwin10 \
// RUN: -fobjc-direct-precondition-thunk %s -o - | FileCheck %s
struct my_complex_struct {
int a, b;
};
struct my_aggregate_struct {
int a, b;
char buf[128];
};
__attribute__((objc_root_class))
@interface Root
- (int)getInt __attribute__((objc_direct));
@property(direct, readonly) int intProperty;
@property(direct, readonly) int intProperty2;
@property(direct, readonly) id objectProperty;
@end
@implementation Root
// CHECK-LABEL: define hidden i32 @"-[Root intProperty2]D"(ptr noundef %self)
- (int)intProperty2 {
return 42;
}
// CHECK-LABEL: define hidden i32 @"-[Root getInt]D"(ptr noundef %self)
- (int)getInt __attribute__((objc_direct)) {
return 42;
}
// CHECK-LABEL: define hidden i32 @"+[Root classGetInt]D"(ptr noundef %self)
+ (int)classGetInt __attribute__((objc_direct)) {
return 42;
}
// CHECK-LABEL: define hidden i64 @"-[Root getComplex]D"(ptr noundef %self)
- (struct my_complex_struct)getComplex __attribute__((objc_direct)) {
struct my_complex_struct st = {.a = 42};
return st;
}
// CHECK-LABEL: define hidden i64 @"+[Root classGetComplex]D"(ptr noundef %self)
+ (struct my_complex_struct)classGetComplex __attribute__((objc_direct)) {
struct my_complex_struct st = {.a = 42};
return st;
}
// CHECK-LABEL: define hidden void @"-[Root getAggregate]D"(ptr {{.*}} sret(%struct.my_aggregate_struct) {{.*}} %agg.result, ptr noundef %self)
- (struct my_aggregate_struct)getAggregate __attribute__((objc_direct)) {
struct my_aggregate_struct st = {.a = 42};
return st;
}
// CHECK-LABEL: define hidden void @"+[Root classGetAggregate]D"(ptr {{.*}} sret(%struct.my_aggregate_struct) {{.*}} %agg.result, ptr noundef %self)
+ (struct my_aggregate_struct)classGetAggregate __attribute__((objc_direct)) {
struct my_aggregate_struct st = {.a = 42};
return st;
}
// CHECK-LABEL: define hidden void @"-[Root accessCmd]D"(ptr noundef %self)
- (void)accessCmd __attribute__((objc_direct)) {
// loading the _cmd selector
SEL sel = _cmd;
}
@end
// CHECK-LABEL: define hidden i32 @"-[Root intProperty]D"(ptr noundef %self)
// CHECK-LABEL: define hidden ptr @"-[Root objectProperty]D"(ptr noundef %self)
@interface Foo : Root {
id __strong _cause_cxx_destruct;
}
@property(nonatomic, readonly, direct) int getDirect_setDynamic;
@property(nonatomic, readonly) int getDynamic_setDirect;
@end
@interface Foo ()
@property(nonatomic, readwrite) int getDirect_setDynamic;
@property(nonatomic, readwrite, direct) int getDynamic_setDirect;
- (int)directMethodInExtension __attribute__((objc_direct));
@end
@interface Foo (Cat)
- (int)directMethodInCategory __attribute__((objc_direct));
@end
__attribute__((objc_direct_members))
@implementation Foo
// CHECK-LABEL: define hidden i32 @"-[Foo directMethodInExtension]D"(ptr noundef %self)
- (int)directMethodInExtension {
return 42;
}
// CHECK-LABEL: define hidden i32 @"-[Foo getDirect_setDynamic]D"(ptr noundef %self)
// CHECK-LABEL: define internal void @"\01-[Foo setGetDirect_setDynamic:]"(ptr noundef %self, ptr noundef %_cmd, i32 noundef %getDirect_setDynamic)
// CHECK-LABEL: define internal i32 @"\01-[Foo getDynamic_setDirect]"(ptr noundef %self, ptr noundef %_cmd)
// CHECK-LABEL: define hidden void @"-[Foo setGetDynamic_setDirect:]D"(ptr noundef %self, i32 noundef %getDynamic_setDirect)
@end
@implementation Foo (Cat)
// CHECK-LABEL: define hidden i32 @"-[Foo directMethodInCategory]D"(ptr noundef %self)
- (int)directMethodInCategory {
return 42;
}
// CHECK-LABEL: define hidden i32 @"-[Foo directMethodInCategoryNoDecl]D"(ptr noundef %self)
- (int)directMethodInCategoryNoDecl __attribute__((objc_direct)) {
return 42;
}
@end
// CHECK-LABEL: define{{.*}} i32 @useRoot(ptr noundef %r)
int useRoot(Root *r) {
// CHECK: call i32 @"-[Root getInt]D_thunk"(ptr noundef %{{[0-9]+}})
// CHECK: call i32 @"-[Root intProperty]D_thunk"(ptr noundef %{{[0-9]+}})
// CHECK: call i32 @"-[Root intProperty2]D_thunk"(ptr noundef %{{[0-9]+}})
return [r getInt] + [r intProperty] + [r intProperty2];
}
// Thunks are emitted after the first function that uses them
// CHECK-LABEL: define linkonce_odr hidden i32 @"-[Root getInt]D_thunk"(ptr noundef %self)
// CHECK: entry:
// CHECK: %[[IS_NIL:.*]] = icmp eq ptr {{.*}}, null
// CHECK: br i1 %[[IS_NIL]], label %objc_direct_method.self_is_nil, label %objc_direct_method.cont
// CHECK: objc_direct_method.self_is_nil:
// CHECK: call void @llvm.memset
// CHECK: br label %dummy_ret_block
// CHECK: objc_direct_method.cont:
// CHECK: %[[RET:.*]] = musttail call i32 @"-[Root getInt]D"(ptr noundef %self)
// CHECK: ret i32 %[[RET]]
// CHECK: dummy_ret_block:
// CHECK-LABEL: define linkonce_odr hidden i32 @"-[Root intProperty]D_thunk"(ptr noundef %self)
// CHECK: entry:
// CHECK: %[[IS_NIL:.*]] = icmp eq ptr {{.*}}, null
// CHECK: br i1 %[[IS_NIL]], label %objc_direct_method.self_is_nil, label %objc_direct_method.cont
// CHECK: objc_direct_method.self_is_nil:
// CHECK: call void @llvm.memset
// CHECK: br label %dummy_ret_block
// CHECK: objc_direct_method.cont:
// CHECK: %[[RET:.*]] = musttail call i32 @"-[Root intProperty]D"(ptr noundef %self)
// CHECK: ret i32 %[[RET]]
// CHECK: dummy_ret_block:
// CHECK-LABEL: define linkonce_odr hidden i32 @"-[Root intProperty2]D_thunk"(ptr noundef %self)
// CHECK: entry:
// CHECK: %[[IS_NIL:.*]] = icmp eq ptr {{.*}}, null
// CHECK: br i1 %[[IS_NIL]], label %objc_direct_method.self_is_nil, label %objc_direct_method.cont
// CHECK: objc_direct_method.self_is_nil:
// CHECK: call void @llvm.memset
// CHECK: br label %dummy_ret_block
// CHECK: objc_direct_method.cont:
// CHECK: %[[RET:.*]] = musttail call i32 @"-[Root intProperty2]D"(ptr noundef %self)
// CHECK: ret i32 %[[RET]]
// CHECK: dummy_ret_block:
// CHECK-LABEL: define{{.*}} i32 @useFoo(ptr noundef %f)
int useFoo(Foo *f) {
// CHECK: call void @"-[Foo setGetDynamic_setDirect:]D_thunk"(ptr noundef %{{[0-9]+}}, i32 noundef 1)
// CHECK: call i32 @"-[Foo getDirect_setDynamic]D_thunk"(ptr noundef %{{[0-9]+}})
// CHECK: call i32 @"-[Foo directMethodInExtension]D_thunk"(ptr noundef %{{[0-9]+}})
// CHECK: call i32 @"-[Foo directMethodInCategory]D_thunk"(ptr noundef %{{[0-9]+}})
// CHECK: call i32 @"-[Foo directMethodInCategoryNoDecl]D_thunk"(ptr noundef %{{[0-9]+}})
[f setGetDynamic_setDirect:1];
return [f getDirect_setDynamic] +
[f directMethodInExtension] +
[f directMethodInCategory] +
[f directMethodInCategoryNoDecl];
}
// CHECK-LABEL: define linkonce_odr hidden void @"-[Foo setGetDynamic_setDirect:]D_thunk"(ptr noundef %self, i32 noundef
// CHECK: entry:
// CHECK: %[[IS_NIL:.*]] = icmp eq ptr {{.*}}, null
// CHECK: br i1 %[[IS_NIL]], label %objc_direct_method.self_is_nil, label %objc_direct_method.cont
// CHECK: objc_direct_method.self_is_nil:
// CHECK: br label %dummy_ret_block
// CHECK: objc_direct_method.cont:
// CHECK: musttail call void @"-[Foo setGetDynamic_setDirect:]D"(ptr noundef %self, i32 noundef
// CHECK: ret void
// CHECK: dummy_ret_block:
// CHECK: ret void
// CHECK-LABEL: define linkonce_odr hidden i32 @"-[Foo getDirect_setDynamic]D_thunk"(ptr noundef %self)
// CHECK: entry:
// CHECK: %[[IS_NIL:.*]] = icmp eq ptr {{.*}}, null
// CHECK: br i1 %[[IS_NIL]], label %objc_direct_method.self_is_nil, label %objc_direct_method.cont
// CHECK: objc_direct_method.self_is_nil:
// CHECK: call void @llvm.memset
// CHECK: br label %dummy_ret_block
// CHECK: objc_direct_method.cont:
// CHECK: %[[RET:.*]] = musttail call i32 @"-[Foo getDirect_setDynamic]D"(ptr noundef %self)
// CHECK: ret i32 %[[RET]]
// CHECK: dummy_ret_block:
// CHECK-LABEL: define linkonce_odr hidden i32 @"-[Foo directMethodInExtension]D_thunk"(ptr noundef %self)
// CHECK: entry:
// CHECK: %[[IS_NIL:.*]] = icmp eq ptr {{.*}}, null
// CHECK: br i1 %[[IS_NIL]], label %objc_direct_method.self_is_nil, label %objc_direct_method.cont
// CHECK: objc_direct_method.self_is_nil:
// CHECK: call void @llvm.memset
// CHECK: br label %dummy_ret_block
// CHECK: objc_direct_method.cont:
// CHECK: %[[RET:.*]] = musttail call i32 @"-[Foo directMethodInExtension]D"(ptr noundef %self)
// CHECK: ret i32 %[[RET]]
// CHECK: dummy_ret_block:
// CHECK-LABEL: define linkonce_odr hidden i32 @"-[Foo directMethodInCategory]D_thunk"(ptr noundef %self)
// CHECK: entry:
// CHECK: %[[IS_NIL:.*]] = icmp eq ptr {{.*}}, null
// CHECK: br i1 %[[IS_NIL]], label %objc_direct_method.self_is_nil, label %objc_direct_method.cont
// CHECK: objc_direct_method.self_is_nil:
// CHECK: call void @llvm.memset
// CHECK: br label %dummy_ret_block
// CHECK: objc_direct_method.cont:
// CHECK: %[[RET:.*]] = musttail call i32 @"-[Foo directMethodInCategory]D"(ptr noundef %self)
// CHECK: ret i32 %[[RET]]
// CHECK: dummy_ret_block:
// CHECK-LABEL: define linkonce_odr hidden i32 @"-[Foo directMethodInCategoryNoDecl]D_thunk"(ptr noundef %self)
// CHECK: entry:
// CHECK: %[[IS_NIL:.*]] = icmp eq ptr {{.*}}, null
// CHECK: br i1 %[[IS_NIL]], label %objc_direct_method.self_is_nil, label %objc_direct_method.cont
// CHECK: objc_direct_method.self_is_nil:
// CHECK: call void @llvm.memset
// CHECK: br label %dummy_ret_block
// CHECK: objc_direct_method.cont:
// CHECK: %[[RET:.*]] = musttail call i32 @"-[Foo directMethodInCategoryNoDecl]D"(ptr noundef %self)
// CHECK: ret i32 %[[RET]]
// CHECK: dummy_ret_block:
__attribute__((objc_root_class))
@interface RootDeclOnly
@property(direct, readonly) int intProperty;
@end
__attribute__((objc_root_class, weak_import))
@interface WeakRoot
+ (int)classGetInt __attribute__((objc_direct, weak_import));
@end
// CHECK-LABEL: define{{.*}} i32 @useRootDeclOnly(ptr noundef %r)
int useRootDeclOnly(RootDeclOnly *r) {
// CHECK: call i32 @"-[RootDeclOnly intProperty]D_thunk"(ptr noundef %{{[0-9]+}})
return [r intProperty];
}
// Verify thunk is generated for external direct method
// CHECK: declare{{.*}} i32 @"-[RootDeclOnly intProperty]D"(ptr)
// CHECK-LABEL: define linkonce_odr hidden i32 @"-[RootDeclOnly intProperty]D_thunk"(ptr noundef %self)
// CHECK: entry:
// CHECK: %[[IS_NIL:.*]] = icmp eq ptr {{.*}}, null
// CHECK: br i1 %[[IS_NIL]], label %objc_direct_method.self_is_nil, label %objc_direct_method.cont
// CHECK: objc_direct_method.self_is_nil:
// CHECK: call void @llvm.memset
// CHECK: br label %dummy_ret_block
// CHECK: objc_direct_method.cont:
// CHECK: %[[RET:.*]] = musttail call i32 @"-[RootDeclOnly intProperty]D"(ptr noundef %self)
// CHECK: ret i32 %[[RET]]
// CHECK: dummy_ret_block:
int useSRet(Root *r) {
return (
// First call is to instance method - uses thunk
// CHECK: call i64 @"-[Root getComplex]D_thunk"
[r getComplex].a +
// TODO: we should know that this instance is non nil.
// CHECK: call void @"-[Root getAggregate]D_thunk"
[r getAggregate].a +
// TODO: The compiler is not smart enough to know the class object must be realized yet.
// CHECK: call i64 @"+[Root classGetComplex]D_thunk"(ptr noundef
[Root classGetComplex].a +
// CHECK: call void @"+[Root classGetAggregate]D_thunk"(ptr {{.*}}sret
[Root classGetAggregate].a
);
}
// CHECK-LABEL: define linkonce_odr hidden i64 @"-[Root getComplex]D_thunk"
// CHECK-LABEL: define linkonce_odr hidden void @"-[Root getAggregate]D_thunk"(ptr dead_on_unwind writable sret(%struct.my_aggregate_struct) {{.*}} %agg.result, ptr noundef %self)
// CHECK: entry:
// CHECK: %[[IS_NIL:.*]] = icmp eq ptr {{.*}}, null
// CHECK: br i1 %[[IS_NIL]], label %objc_direct_method.self_is_nil, label %objc_direct_method.cont
// CHECK: objc_direct_method.self_is_nil:
// CHECK: call void @llvm.memset
// CHECK: br label %dummy_ret_block
// CHECK: objc_direct_method.cont:
// CHECK: musttail call void @"-[Root getAggregate]D"(ptr dead_on_unwind writable sret(%struct.my_aggregate_struct) {{.*}} %agg.result, ptr noundef %self)
// CHECK: ret void
// CHECK: dummy_ret_block:
// CHECK: ret void
// Class method thunks should have class realization (objc_msgSend)
// instead of a nil check.
// CHECK-LABEL: define linkonce_odr hidden i64 @"+[Root classGetComplex]D_thunk"(ptr noundef %self)
// CHECK: entry:
// CHECK-NOT: icmp eq ptr
// CHECK: call ptr @objc_msgSend
// CHECK: musttail call i64 @"+[Root classGetComplex]D"(ptr noundef %self)
// CHECK: ret i64
// CHECK-LABEL: define linkonce_odr hidden void @"+[Root classGetAggregate]D_thunk"(ptr {{.*}} sret(%struct.my_aggregate_struct) {{.*}} %agg.result, ptr noundef %self)
// CHECK: entry:
// CHECK-NOT: icmp eq ptr
// CHECK: call ptr @objc_msgSend
// CHECK: musttail call void @"+[Root classGetAggregate]D"(ptr {{.*}} sret(%struct.my_aggregate_struct) {{.*}} %agg.result, ptr noundef %self)
// CHECK: ret void
// CHECK-LABEL: define{{.*}} i32 @useWeakRoot()
int useWeakRoot() {
// CHECK: call i32 @"+[WeakRoot classGetInt]D_thunk"
return [WeakRoot classGetInt];
}
// Weakly linked class method thunks should have class realization
// AND a nil check (the weak class may not exist at runtime).
// CHECK-LABEL: define linkonce_odr hidden i32 @"+[WeakRoot classGetInt]D_thunk"(ptr noundef %self)
// CHECK: entry:
// CHECK: call ptr @objc_msgSend
// CHECK: %[[IS_NIL:.*]] = icmp eq ptr {{.*}}, null
// CHECK: br i1 %[[IS_NIL]], label %objc_direct_method.self_is_nil, label %objc_direct_method.cont
// CHECK: objc_direct_method.self_is_nil:
// CHECK: call void @llvm.memset
// CHECK: br label %dummy_ret_block
// CHECK: objc_direct_method.cont:
// CHECK: %[[RET:.*]] = musttail call i32 @"+[WeakRoot classGetInt]D"(ptr noundef %self)
// CHECK: ret i32 %[[RET]]
// CHECK: dummy_ret_block: