diff --git a/clang/include/clang/AST/Mangle.h b/clang/include/clang/AST/Mangle.h index 13fa0d1c880b..3d3a70cb95b3 100644 --- a/clang/include/clang/AST/Mangle.h +++ b/clang/include/clang/AST/Mangle.h @@ -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 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; diff --git a/clang/include/clang/CodeGen/CodeGenABITypes.h b/clang/include/clang/CodeGen/CodeGenABITypes.h index 16f39b991a27..627dd0f0453a 100644 --- a/clang/include/clang/CodeGen/CodeGenABITypes.h +++ b/clang/include/clang/CodeGen/CodeGenABITypes.h @@ -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 diff --git a/clang/lib/AST/Mangle.cpp b/clang/lib/AST/Mangle.cpp index 58216667116a..4dca4b5b7e69 100644 --- a/clang/lib/AST/Mangle.cpp +++ b/clang/lib/AST/Mangle.cpp @@ -32,8 +32,12 @@ using namespace clang; void clang::mangleObjCMethodName(raw_ostream &OS, bool includePrefixByte, bool isInstanceMethod, StringRef ClassName, std::optional 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, diff --git a/clang/lib/CodeGen/CGDecl.cpp b/clang/lib/CodeGen/CGDecl.cpp index 8b5ffde1b73f..3f32fecc5099 100644 --- a/clang/lib/CodeGen/CGDecl.cpp +++ b/clang/lib/CodeGen/CGDecl.cpp @@ -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. diff --git a/clang/lib/CodeGen/CGObjC.cpp b/clang/lib/CodeGen/CGObjC.cpp index f89be6a7a0d5..3a7a330d9ca0 100644 --- a/clang/lib/CodeGen/CGObjC.cpp +++ b/clang/lib/CodeGen/CGObjC.cpp @@ -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); } diff --git a/clang/lib/CodeGen/CGObjCMac.cpp b/clang/lib/CodeGen/CGObjCMac.cpp index c5d86c786ffc..eeb61a6b12eb 100644 --- a/clang/lib/CodeGen/CGObjCMac.cpp +++ b/clang/lib/CodeGen/CGObjCMac.cpp @@ -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 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(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(&CGM.getObjCRuntime()); + return MacRuntime->GetDirectMethodCallee(OMD, CD, ReceiverCanBeNull, + ClassObjectCanBeUnrealized); +} diff --git a/clang/lib/CodeGen/CGObjCRuntime.cpp b/clang/lib/CodeGen/CGObjCRuntime.cpp index 3d47dc9560c6..2b8bd170b719 100644 --- a/clang/lib/CodeGen/CGObjCRuntime.cpp +++ b/clang/lib/CodeGen/CGObjCRuntime.cpp @@ -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; } diff --git a/clang/lib/CodeGen/CGObjCRuntime.h b/clang/lib/CodeGen/CGObjCRuntime.h index 7aa66adaa516..9ce1f385409b 100644 --- a/clang/lib/CodeGen/CGObjCRuntime.h +++ b/clang/lib/CodeGen/CGObjCRuntime.h @@ -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, diff --git a/clang/lib/CodeGen/CodeGenFunction.h b/clang/lib/CodeGen/CodeGenFunction.h index 50ba46c04c81..fd474c09044e 100644 --- a/clang/lib/CodeGen/CodeGenFunction.h +++ b/clang/lib/CodeGen/CodeGenFunction.h @@ -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); diff --git a/clang/lib/CodeGen/CodeGenModule.h b/clang/lib/CodeGen/CodeGenModule.h index 0a697c84b66a..30e401a3e5d6 100644 --- a/clang/lib/CodeGen/CodeGenModule.h +++ b/clang/lib/CodeGen/CodeGenModule.h @@ -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; } diff --git a/clang/test/CodeGenObjC/direct-method-ret-mismatch.m b/clang/test/CodeGenObjC/direct-method-ret-mismatch.m index 889a6d68da0d..12985f06e256 100644 --- a/clang/test/CodeGenObjC/direct-method-ret-mismatch.m +++ b/clang/test/CodeGenObjC/direct-method-ret-mismatch.m @@ -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 diff --git a/clang/test/CodeGenObjC/expose-direct-method-linkedlist.m b/clang/test/CodeGenObjC/expose-direct-method-linkedlist.m new file mode 100644 index 000000000000..76146af4aafd --- /dev/null +++ b/clang/test/CodeGenObjC/expose-direct-method-linkedlist.m @@ -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; +} diff --git a/clang/test/CodeGenObjC/expose-direct-method-varargs.m b/clang/test/CodeGenObjC/expose-direct-method-varargs.m new file mode 100644 index 000000000000..eb5a0d859d30 --- /dev/null +++ b/clang/test/CodeGenObjC/expose-direct-method-varargs.m @@ -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]; +} diff --git a/clang/test/CodeGenObjC/expose-direct-method-visibility-linkage.m b/clang/test/CodeGenObjC/expose-direct-method-visibility-linkage.m new file mode 100644 index 000000000000..dd15f960d114 --- /dev/null +++ b/clang/test/CodeGenObjC/expose-direct-method-visibility-linkage.m @@ -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" diff --git a/clang/test/CodeGenObjC/expose-direct-method.m b/clang/test/CodeGenObjC/expose-direct-method.m new file mode 100644 index 000000000000..bc7a94db112c --- /dev/null +++ b/clang/test/CodeGenObjC/expose-direct-method.m @@ -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: