[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:
parent
cabebddac9
commit
3e2f0bce95
@ -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;
|
||||
|
||||
|
||||
@ -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
|
||||
|
||||
|
||||
@ -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,
|
||||
|
||||
@ -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.
|
||||
|
||||
@ -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);
|
||||
}
|
||||
|
||||
|
||||
@ -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);
|
||||
}
|
||||
|
||||
@ -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;
|
||||
}
|
||||
|
||||
@ -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,
|
||||
|
||||
@ -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);
|
||||
|
||||
@ -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; }
|
||||
|
||||
@ -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
|
||||
|
||||
98
clang/test/CodeGenObjC/expose-direct-method-linkedlist.m
Normal file
98
clang/test/CodeGenObjC/expose-direct-method-linkedlist.m
Normal 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;
|
||||
}
|
||||
104
clang/test/CodeGenObjC/expose-direct-method-varargs.m
Normal file
104
clang/test/CodeGenObjC/expose-direct-method-varargs.m
Normal 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];
|
||||
}
|
||||
119
clang/test/CodeGenObjC/expose-direct-method-visibility-linkage.m
Normal file
119
clang/test/CodeGenObjC/expose-direct-method-visibility-linkage.m
Normal 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"
|
||||
330
clang/test/CodeGenObjC/expose-direct-method.m
Normal file
330
clang/test/CodeGenObjC/expose-direct-method.m
Normal 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:
|
||||
Loading…
x
Reference in New Issue
Block a user