https://github.com/DataCorrupted created https://github.com/llvm/llvm-project/pull/170615
None >From 76489044b1150f846af5fe86f07d8d8f32e56179 Mon Sep 17 00:00:00 2001 From: Peter Rong <[email protected]> Date: Wed, 3 Dec 2025 22:18:02 -0800 Subject: [PATCH 1/3] [ExposeObjCDirect] Adding a flag to allow new objc direct ABI 1. Add a flag 2. Clean up and set up helper functions to implement later Signed-off-by: Peter Rong <[email protected]> --- clang/include/clang/AST/DeclObjC.h | 6 ++++ clang/include/clang/Basic/CodeGenOptions.def | 2 ++ clang/include/clang/Options/Options.td | 5 +++ clang/lib/CodeGen/CGObjCRuntime.cpp | 26 +++++++++------ clang/lib/CodeGen/CGObjCRuntime.h | 35 ++++++++++++++++++-- clang/lib/CodeGen/CodeGenModule.h | 26 +++++++++++++++ clang/lib/Driver/ToolChains/Clang.cpp | 4 +++ 7 files changed, 90 insertions(+), 14 deletions(-) diff --git a/clang/include/clang/AST/DeclObjC.h b/clang/include/clang/AST/DeclObjC.h index 2541edba83855..e2292cbdea042 100644 --- a/clang/include/clang/AST/DeclObjC.h +++ b/clang/include/clang/AST/DeclObjC.h @@ -482,6 +482,12 @@ class ObjCMethodDecl : public NamedDecl, public DeclContext { /// True if the method is tagged as objc_direct bool isDirectMethod() const; + /// Check if this direct method can move nil-check to thunk. + /// Variadic functions cannot use thunks (musttail incompatible with va_arg) + bool canHaveNilCheckThunk() const { + return isDirectMethod() && !isVariadic(); + } + /// True if the method has a parameter that's destroyed in the callee. bool hasParamDestroyedInCallee() const; diff --git a/clang/include/clang/Basic/CodeGenOptions.def b/clang/include/clang/Basic/CodeGenOptions.def index 76a6463881c6f..a7e8564c9e83c 100644 --- a/clang/include/clang/Basic/CodeGenOptions.def +++ b/clang/include/clang/Basic/CodeGenOptions.def @@ -210,6 +210,8 @@ ENUM_CODEGENOPT(ObjCDispatchMethod, ObjCDispatchMethodKind, 2, Legacy, Benign) /// Replace certain message sends with calls to ObjC runtime entrypoints CODEGENOPT(ObjCConvertMessagesToRuntimeCalls , 1, 1, Benign) CODEGENOPT(ObjCAvoidHeapifyLocalBlocks, 1, 0, Benign) +/// Expose objc_direct method symbols publicly and optimize nil checks. +CODEGENOPT(ObjCExposeDirectMethods, 1, 0, Benign) // The optimization options affect frontend options, which in turn do affect the AST. diff --git a/clang/include/clang/Options/Options.td b/clang/include/clang/Options/Options.td index 756d6deed7130..ddc04b30cbaa2 100644 --- a/clang/include/clang/Options/Options.td +++ b/clang/include/clang/Options/Options.td @@ -3775,6 +3775,11 @@ defm objc_avoid_heapify_local_blocks : BoolFOption<"objc-avoid-heapify-local-blo PosFlag<SetTrue, [], [ClangOption], "Try">, NegFlag<SetFalse, [], [ClangOption], "Don't try">, BothFlags<[], [CC1Option], " to avoid heapifying local blocks">>; +defm objc_expose_direct_methods : BoolFOption<"objc-expose-direct-methods", + CodeGenOpts<"ObjCExposeDirectMethods">, DefaultFalse, + PosFlag<SetTrue, [], [ClangOption, CC1Option], + "Expose direct method symbols and move nil checks to caller-side thunks">, + NegFlag<SetFalse>>; defm disable_block_signature_string : BoolFOption<"disable-block-signature-string", CodeGenOpts<"DisableBlockSignatureString">, DefaultFalse, PosFlag<SetTrue, [], [ClangOption], "Disable">, diff --git a/clang/lib/CodeGen/CGObjCRuntime.cpp b/clang/lib/CodeGen/CGObjCRuntime.cpp index 76e0054f4c9da..38efd4d865284 100644 --- a/clang/lib/CodeGen/CGObjCRuntime.cpp +++ b/clang/lib/CodeGen/CGObjCRuntime.cpp @@ -382,11 +382,9 @@ CGObjCRuntime::getMessageSendInfo(const ObjCMethodDecl *method, return MessageSendInfo(argsInfo, signatureType); } -bool CGObjCRuntime::canMessageReceiverBeNull(CodeGenFunction &CGF, - const ObjCMethodDecl *method, - bool isSuper, - const ObjCInterfaceDecl *classReceiver, - llvm::Value *receiver) { +bool CGObjCRuntime::canMessageReceiverBeNull( + CodeGenFunction &CGF, const ObjCMethodDecl *method, bool isSuper, + const ObjCInterfaceDecl *classReceiver, llvm::Value *receiver) { // Super dispatch assumes that self is non-null; even the messenger // doesn't have a null check internally. if (isSuper) @@ -399,8 +397,7 @@ bool CGObjCRuntime::canMessageReceiverBeNull(CodeGenFunction &CGF, // If we're emitting a method, and self is const (meaning just ARC, for now), // and the receiver is a load of self, then self is a valid object. - if (auto curMethod = - dyn_cast_or_null<ObjCMethodDecl>(CGF.CurCodeDecl)) { + if (auto curMethod = dyn_cast_or_null<ObjCMethodDecl>(CGF.CurCodeDecl)) { auto self = curMethod->getSelfDecl(); if (self->getType().isConstQualified()) { if (auto LI = dyn_cast<llvm::LoadInst>(receiver->stripPointerCasts())) { @@ -416,6 +413,13 @@ bool CGObjCRuntime::canMessageReceiverBeNull(CodeGenFunction &CGF, return true; } +bool CGObjCRuntime::canClassObjectBeUnrealized( + const ObjCInterfaceDecl *CalleeClassDecl, CodeGenFunction &CGF) const { + + // Otherwise, assume it can be unrealized. + return true; +} + bool CGObjCRuntime::isWeakLinkedClass(const ObjCInterfaceDecl *ID) { do { if (ID->isWeakImported()) @@ -466,11 +470,11 @@ clang::CodeGen::emitObjCProtocolObject(CodeGenModule &CGM, } std::string CGObjCRuntime::getSymbolNameForMethod(const ObjCMethodDecl *OMD, - bool includeCategoryName) { + bool includeCategoryName, + bool includePrefixByte) { std::string buffer; llvm::raw_string_ostream out(buffer); - CGM.getCXXABI().getMangleContext().mangleObjCMethodName(OMD, out, - /*includePrefixByte=*/true, - includeCategoryName); + CGM.getCXXABI().getMangleContext().mangleObjCMethodName( + OMD, out, includePrefixByte, includeCategoryName); return buffer; } diff --git a/clang/lib/CodeGen/CGObjCRuntime.h b/clang/lib/CodeGen/CGObjCRuntime.h index 72997bf6348ae..1ee3b85e8a779 100644 --- a/clang/lib/CodeGen/CGObjCRuntime.h +++ b/clang/lib/CodeGen/CGObjCRuntime.h @@ -117,7 +117,8 @@ class CGObjCRuntime { virtual ~CGObjCRuntime(); std::string getSymbolNameForMethod(const ObjCMethodDecl *method, - bool includeCategoryName = true); + bool includeCategoryName = true, + bool includePrefixByte = true); /// Generate the function required to register all Objective-C components in /// this compilation unit with the runtime library. @@ -322,10 +323,38 @@ class CGObjCRuntime { MessageSendInfo getMessageSendInfo(const ObjCMethodDecl *method, QualType resultType, CallArgList &callArgs); - bool canMessageReceiverBeNull(CodeGenFunction &CGF, - const ObjCMethodDecl *method, bool isSuper, + + /// Check if the receiver of an ObjC message send can be null. + /// Returns true if the receiver may be null, false if provably non-null. + /// + /// This can be overridden by subclasses to add runtime-specific heuristics. + /// Base implementation checks: + /// - Super dispatch (always non-null) + /// - Self in const-qualified methods (ARC) + /// - Weak-linked classes + /// + /// Future enhancements in CGObjCCommonMac override: + /// - _Nonnull attributes + /// - Results of alloc, new, ObjC literals + virtual bool canMessageReceiverBeNull(CodeGenFunction &CGF, + const ObjCMethodDecl *method, + bool isSuper, 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. + /// + /// STUB IMPLEMENTATION: Base class always returns true (conservative). + /// Subclasses can override to add runtime-specific dominating-call analysis. + /// + /// Future: 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) + virtual 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/CodeGenModule.h b/clang/lib/CodeGen/CodeGenModule.h index a253bcda2d06c..f65739de10957 100644 --- a/clang/lib/CodeGen/CodeGenModule.h +++ b/clang/lib/CodeGen/CodeGenModule.h @@ -717,6 +717,32 @@ class CodeGenModule : public CodeGenTypeCache { /// Return true iff an Objective-C runtime has been configured. bool hasObjCRuntime() { return !!ObjCRuntime; } + /// Check if a direct method should have its symbol exposed (no \01 prefix). + /// This applies to ALL direct methods (including variadic). + /// Returns false if OMD is null or not a direct method. + bool shouldExposeSymbol(const ObjCMethodDecl *OMD) const { + return OMD && OMD->isDirectMethod() && + getLangOpts().ObjCRuntime.isNeXTFamily() && + getCodeGenOpts().ObjCExposeDirectMethods; + } + + /// Check if a direct method should use nil-check thunks at call sites. + /// This applies only to non-variadic direct methods. + /// Variadic methods cannot use thunks (musttail incompatible with va_arg). + /// Returns false if OMD is null or not eligible for thunks. + bool shouldHaveNilCheckThunk(const ObjCMethodDecl *OMD) const { + return OMD && shouldExposeSymbol(OMD) && OMD->canHaveNilCheckThunk(); + } + + /// Check if a direct method should have inline nil checks at call sites. + /// This applies to direct methods that cannot use thunks (e.g., variadic + /// methods). These methods get exposed symbols but need inline nil checks + /// instead of thunks. Returns false if OMD is null or not eligible for inline + /// nil checks. + bool shouldHaveNilCheckInline(const ObjCMethodDecl *OMD) const { + return OMD && shouldExposeSymbol(OMD) && !OMD->canHaveNilCheckThunk(); + } + const std::string &getModuleNameHash() const { return ModuleNameHash; } /// Return a reference to the configured OpenCL runtime. diff --git a/clang/lib/Driver/ToolChains/Clang.cpp b/clang/lib/Driver/ToolChains/Clang.cpp index 0380568412e62..2c3beb94f2ef8 100644 --- a/clang/lib/Driver/ToolChains/Clang.cpp +++ b/clang/lib/Driver/ToolChains/Clang.cpp @@ -4096,6 +4096,10 @@ static void RenderObjCOptions(const ToolChain &TC, const Driver &D, } } + // Forward -fobjc-expose-direct-methods to cc1 + if (Args.hasArg(options::OPT_fobjc_expose_direct_methods)) + CmdArgs.push_back("-fobjc-expose-direct-methods"); + // When ObjectiveC legacy runtime is in effect on MacOSX, turn on the option // to do Array/Dictionary subscripting by default. if (Arch == llvm::Triple::x86 && T.isMacOSX() && >From ff65d5543f5187998bf7a35017e70c5605008739 Mon Sep 17 00:00:00 2001 From: Peter Rong <[email protected]> Date: Wed, 3 Dec 2025 22:35:15 -0800 Subject: [PATCH 2/3] [ExposeObjCDirect] Setup helper functions 1. GenerateDirectMethodsPreconditionCheck: Move some functionalities to a separate functions. Those functions will be reused if we move precondition checks into a thunk 2. Create `DirectMethodInfo`, which will be used to manage true implementation and its thunk --- clang/lib/CodeGen/CGObjCGNU.cpp | 9 +++ clang/lib/CodeGen/CGObjCMac.cpp | 95 ++++++++++++++++++++++++------- clang/lib/CodeGen/CGObjCRuntime.h | 6 ++ 3 files changed, 88 insertions(+), 22 deletions(-) diff --git a/clang/lib/CodeGen/CGObjCGNU.cpp b/clang/lib/CodeGen/CGObjCGNU.cpp index 06643d4bdc211..9c814487860ac 100644 --- a/clang/lib/CodeGen/CGObjCGNU.cpp +++ b/clang/lib/CodeGen/CGObjCGNU.cpp @@ -600,6 +600,9 @@ class CGObjCGNU : public CGObjCRuntime { // Map to unify direct method definitions. llvm::DenseMap<const ObjCMethodDecl *, llvm::Function *> DirectMethodDefinitions; + void GenerateDirectMethodsPreconditionCheck( + CodeGenFunction &CGF, llvm::Function *Fn, const ObjCMethodDecl *OMD, + const ObjCContainerDecl *CD) override; void GenerateDirectMethodPrologue(CodeGenFunction &CGF, llvm::Function *Fn, const ObjCMethodDecl *OMD, const ObjCContainerDecl *CD) override; @@ -4196,6 +4199,12 @@ llvm::Function *CGObjCGNU::GenerateMethod(const ObjCMethodDecl *OMD, return Fn; } +void CGObjCGNU::GenerateDirectMethodsPreconditionCheck( + CodeGenFunction &CGF, llvm::Function *Fn, const ObjCMethodDecl *OMD, + const ObjCContainerDecl *CD) { + // GNU runtime doesn't support direct calls at this time +} + void CGObjCGNU::GenerateDirectMethodPrologue(CodeGenFunction &CGF, llvm::Function *Fn, const ObjCMethodDecl *OMD, diff --git a/clang/lib/CodeGen/CGObjCMac.cpp b/clang/lib/CodeGen/CGObjCMac.cpp index cb5bb403bb53b..3f4b11c634ce4 100644 --- a/clang/lib/CodeGen/CGObjCMac.cpp +++ b/clang/lib/CodeGen/CGObjCMac.cpp @@ -847,9 +847,19 @@ class CGObjCCommonMac : public CodeGen::CGObjCRuntime { /// this translation unit. llvm::DenseMap<const ObjCMethodDecl *, llvm::Function *> MethodDefinitions; + /// Information about a direct method definition + struct DirectMethodInfo { + llvm::Function + *Implementation; // The true implementation (where body is emitted) + llvm::Function *Thunk; // The nil-check thunk (nullptr if not generated) + + DirectMethodInfo(llvm::Function *Impl, llvm::Function *Thunk = nullptr) + : Implementation(Impl), Thunk(Thunk) {} + }; + /// DirectMethodDefinitions - map of direct methods which have been defined in /// this translation unit. - llvm::DenseMap<const ObjCMethodDecl *, llvm::Function *> + llvm::DenseMap<const ObjCMethodDecl *, DirectMethodInfo> DirectMethodDefinitions; /// PropertyNames - uniqued method variable names. @@ -1053,9 +1063,20 @@ class CGObjCCommonMac : public CodeGen::CGObjCRuntime { GenerateMethod(const ObjCMethodDecl *OMD, const ObjCContainerDecl *CD = nullptr) override; - llvm::Function *GenerateDirectMethod(const ObjCMethodDecl *OMD, + DirectMethodInfo &GenerateDirectMethod(const ObjCMethodDecl *OMD, const ObjCContainerDecl *CD); + /// Generate class realization code: [self self] + /// This is used for class methods to ensure the class is initialized. + /// Returns the realized class object. + llvm::Value *GenerateClassRealization(CodeGenFunction &CGF, + llvm::Value *classObject, + const ObjCInterfaceDecl *OID); + + void GenerateDirectMethodsPreconditionCheck( + CodeGenFunction &CGF, llvm::Function *Fn, const ObjCMethodDecl *OMD, + const ObjCContainerDecl *CD) override; + void GenerateDirectMethodPrologue(CodeGenFunction &CGF, llvm::Function *Fn, const ObjCMethodDecl *OMD, const ObjCContainerDecl *CD) override; @@ -3847,7 +3868,9 @@ llvm::Function *CGObjCCommonMac::GenerateMethod(const ObjCMethodDecl *OMD, llvm::Function *Method; if (OMD->isDirectMethod()) { - Method = GenerateDirectMethod(OMD, CD); + // Returns DirectMethodInfo& containing both Implementation and Thunk + DirectMethodInfo &Info = GenerateDirectMethod(OMD, CD); + Method = Info.Implementation; // Extract implementation for body generation } else { auto Name = getSymbolNameForMethod(OMD); @@ -3863,7 +3886,7 @@ llvm::Function *CGObjCCommonMac::GenerateMethod(const ObjCMethodDecl *OMD, return Method; } -llvm::Function * +CGObjCCommonMac::DirectMethodInfo & CGObjCCommonMac::GenerateDirectMethod(const ObjCMethodDecl *OMD, const ObjCContainerDecl *CD) { auto *COMD = OMD->getCanonicalDecl(); @@ -3882,7 +3905,7 @@ CGObjCCommonMac::GenerateDirectMethod(const ObjCMethodDecl *OMD, // a new one that has the proper type below. if (!OMD->getBody() || COMD->getReturnType() == OMD->getReturnType()) return I->second; - OldFn = I->second; + OldFn = I->second.Implementation; } CodeGenTypes &Types = CGM.getTypes(); @@ -3896,20 +3919,41 @@ CGObjCCommonMac::GenerateDirectMethod(const ObjCMethodDecl *OMD, OldFn->replaceAllUsesWith(Fn); OldFn->eraseFromParent(); - // Replace the cached function in the map. - I->second = Fn; + // Replace the cached implementation in the map. + I->second.Implementation = Fn; + } else { auto Name = getSymbolNameForMethod(OMD, /*include category*/ false); Fn = llvm::Function::Create(MethodTy, llvm::GlobalValue::ExternalLinkage, Name, &CGM.getModule()); - DirectMethodDefinitions.insert(std::make_pair(COMD, Fn)); + auto [It, inserted] = DirectMethodDefinitions.insert(std::make_pair(COMD, DirectMethodInfo(Fn))); + I = It; } - return Fn; + // Return reference to DirectMethodInfo (contains both Implementation and + // Thunk) + return I->second; } -void CGObjCCommonMac::GenerateDirectMethodPrologue( +llvm::Value * +CGObjCCommonMac::GenerateClassRealization(CodeGenFunction &CGF, + llvm::Value *classObject, + const ObjCInterfaceDecl *OID) { + // Generate: self = [self self] + // This forces class lazy initialization + Selector SelfSel = GetNullarySelector("self", CGM.getContext()); + auto ResultType = CGF.getContext().getObjCIdType(); + CallArgList Args; + + RValue result = GeneratePossiblySpecializedMessageSend( + CGF, ReturnValueSlot(), ResultType, SelfSel, classObject, Args, OID, + nullptr, true); + + return result.getScalarVal(); +} + +void CGObjCCommonMac::GenerateDirectMethodsPreconditionCheck( CodeGenFunction &CGF, llvm::Function *Fn, const ObjCMethodDecl *OMD, const ObjCContainerDecl *CD) { auto &Builder = CGF.Builder; @@ -3926,18 +3970,11 @@ void CGObjCCommonMac::GenerateDirectMethodPrologue( // if (self == nil) { // return (ReturnType){ }; // } - // - // _cmd = @selector(...) - // ... if (OMD->isClassMethod()) { const ObjCInterfaceDecl *OID = cast<ObjCInterfaceDecl>(CD); assert(OID && "GenerateDirectMethod() should be called with the Class Interface"); - Selector SelfSel = GetNullarySelector("self", CGM.getContext()); - auto ResultType = CGF.getContext().getObjCIdType(); - RValue result; - CallArgList Args; // TODO: If this method is inlined, the caller might know that `self` is // already initialized; for example, it might be an ordinary Objective-C @@ -3946,10 +3983,10 @@ void CGObjCCommonMac::GenerateDirectMethodPrologue( // // We should find a way to eliminate this unnecessary initialization in such // cases in LLVM. - result = GeneratePossiblySpecializedMessageSend( - CGF, ReturnValueSlot(), ResultType, SelfSel, selfValue, Args, OID, - nullptr, true); - Builder.CreateStore(result.getScalarVal(), selfAddr); + + // Perform class realization using the helper function + llvm::Value *realizedClass = GenerateClassRealization(CGF, selfValue, OID); + Builder.CreateStore(realizedClass, selfAddr); // Nullable `Class` expressions cannot be messaged with a direct method // so the only reason why the receive can be null would be because @@ -3957,6 +3994,7 @@ void CGObjCCommonMac::GenerateDirectMethodPrologue( ReceiverCanBeNull = isWeakLinkedClass(OID); } + // Generate nil check if (ReceiverCanBeNull) { llvm::BasicBlock *SelfIsNilBlock = CGF.createBasicBlock("objc_direct_method.self_is_nil"); @@ -3986,8 +4024,21 @@ void CGObjCCommonMac::GenerateDirectMethodPrologue( CGF.EmitBlock(ContBlock); Builder.SetInsertPoint(ContBlock); } +} - // only synthesize _cmd if it's referenced +void CGObjCCommonMac::GenerateDirectMethodPrologue( + CodeGenFunction &CGF, llvm::Function *Fn, const ObjCMethodDecl *OMD, + const ObjCContainerDecl *CD) { + // Generate precondition checks (class realization + nil check) if needed + // Without flag: precondition checks are in the implementation + // With flag: precondition checks will be in the thunk (not here) + if (!CGM.shouldExposeSymbol(OMD)) { + GenerateDirectMethodsPreconditionCheck(CGF, Fn, OMD, CD); + } + + auto &Builder = CGF.Builder; + // Only synthesize _cmd if it's referenced + // This is the actual "prologue" work that always happens if (OMD->getCmdDecl()->isUsed()) { // `_cmd` is not a parameter to direct methods, so storage must be // explicitly declared for it. diff --git a/clang/lib/CodeGen/CGObjCRuntime.h b/clang/lib/CodeGen/CGObjCRuntime.h index 1ee3b85e8a779..8d5ee1310e51f 100644 --- a/clang/lib/CodeGen/CGObjCRuntime.h +++ b/clang/lib/CodeGen/CGObjCRuntime.h @@ -226,6 +226,12 @@ class CGObjCRuntime { virtual llvm::Function *GenerateMethod(const ObjCMethodDecl *OMD, const ObjCContainerDecl *CD) = 0; +/// Generates precondition checks for direct Objective-C Methods. + /// This includes [self self] for class methods and nil checks. + virtual void GenerateDirectMethodsPreconditionCheck( + CodeGenFunction &CGF, llvm::Function *Fn, const ObjCMethodDecl *OMD, + const ObjCContainerDecl *CD) = 0; + /// Generates prologue for direct Objective-C Methods. virtual void GenerateDirectMethodPrologue(CodeGenFunction &CGF, llvm::Function *Fn, >From 81b0d800623f03b0c3fa9b4412e36dc585236ed8 Mon Sep 17 00:00:00 2001 From: Peter Rong <[email protected]> Date: Wed, 3 Dec 2025 22:42:51 -0800 Subject: [PATCH 3/3] [ExposeDirectMethod] Nil chech thunk generation - Generation - Dispatch --- clang/lib/CodeGen/CGDecl.cpp | 4 +- clang/lib/CodeGen/CGObjC.cpp | 17 +- clang/lib/CodeGen/CGObjCMac.cpp | 243 +++++++++++++++++++++++++++- clang/lib/CodeGen/CodeGenFunction.h | 7 + 4 files changed, 263 insertions(+), 8 deletions(-) diff --git a/clang/lib/CodeGen/CGDecl.cpp b/clang/lib/CodeGen/CGDecl.cpp index 8b1cd83af2396..9f0e09eac8866 100644 --- a/clang/lib/CodeGen/CGDecl.cpp +++ b/clang/lib/CodeGen/CGDecl.cpp @@ -2757,7 +2757,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 10aad2e26938d..f1b8627fc119a 100644 --- a/clang/lib/CodeGen/CGObjC.cpp +++ b/clang/lib/CodeGen/CGObjC.cpp @@ -761,7 +761,18 @@ void CodeGenFunction::StartObjCMethod(const ObjCMethodDecl *OMD, const CGFunctionInfo &FI = CGM.getTypes().arrangeObjCMethodDeclaration(OMD); if (OMD->isDirectMethod()) { - Fn->setVisibility(llvm::Function::HiddenVisibility); + Fn->setVisibility(llvm::GlobalValue::HiddenVisibility); + if (CGM.shouldExposeSymbol(OMD)) { + // Find the decl that may have visibility set (property or method) + const NamedDecl *Decl = OMD; + if (const auto *PD = OMD->findPropertyDecl()) { + Decl = PD; + } + // and 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 { @@ -781,10 +792,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 3f4b11c634ce4..741e5d85b5935 100644 --- a/clang/lib/CodeGen/CGObjCMac.cpp +++ b/clang/lib/CodeGen/CGObjCMac.cpp @@ -1066,6 +1066,15 @@ class CGObjCCommonMac : public CodeGen::CGObjCRuntime { 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. @@ -2094,6 +2103,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 RequiresSelValue = true; @@ -2101,7 +2113,11 @@ CodeGen::RValue CGObjCCommonMac::EmitMessageSend( llvm::FunctionCallee Fn = nullptr; if (Method && Method->isDirectMethod()) { assert(!IsSuper); - Fn = GenerateDirectMethod(Method, Method->getClassInterface()); + // Use GetDirectMethodCallee to decide whether to use implementation or + // thunk. + 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; @@ -2138,6 +2154,23 @@ CodeGen::RValue CGObjCCommonMac::EmitMessageSend( if (!RequiresNullCheck && Method && Method->hasParamDestroyedInCallee()) RequiresNullCheck = true; + if (CGM.shouldHaveNilCheckInline(Method)) { + // For variadic class methods, we need to inline pre condition checks. That + // include two things: + // 1. if this is a class method, we have to realize the class if we are not + // sure. + 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. inline the nil check if we are not sure if the receiver can be null. + // Luckly, `NullReturnState` already does that for corner cases like + // ns_consume, we only need to override the flag, even if return value is + // unused. + RequiresNullCheck |= ReceiverCanBeNull; + } + NullReturnState nullReturn; if (RequiresNullCheck) { nullReturn.init(CGF, Arg0); @@ -3912,6 +3945,8 @@ CGObjCCommonMac::GenerateDirectMethod(const ObjCMethodDecl *OMD, llvm::FunctionType *MethodTy = Types.GetFunctionType(Types.arrangeObjCMethodDeclaration(OMD)); + bool ExposeSymbol = CGM.shouldExposeSymbol(OMD); + if (OldFn) { Fn = llvm::Function::Create(MethodTy, llvm::GlobalValue::ExternalLinkage, "", &CGM.getModule()); @@ -3921,10 +3956,30 @@ CGObjCCommonMac::GenerateDirectMethod(const ObjCMethodDecl *OMD, // Replace the cached implementation in the map. I->second.Implementation = Fn; + llvm::Function *OldThunk = I->second.Thunk; + // If implementation was replaced, and old thunk exists, invalidate the old + // thunk + // + // TODO: ideally, new thunk shouldn't be necessary, if the different return + // type are just subclasses, at IR level they are just pointers, i.e. the + // NewThunk and the OldThunk are identical. + if (OldThunk) { + llvm::Function *NewThunk = GenerateObjCDirectThunk(OMD, CD, Fn); + + // Replace all uses before erasing + NewThunk->takeName(OldThunk); + OldThunk->replaceAllUsesWith(NewThunk); + OldThunk->eraseFromParent(); + + I->second.Thunk = NewThunk; + } } else { - auto Name = getSymbolNameForMethod(OMD, /*include category*/ false); + // Generate symbol without \01 prefix when optimization enabled + auto Name = getSymbolNameForMethod(OMD, /*include category*/ false, + /*includePrefixByte*/ !ExposeSymbol); + // ALWAYS use ExternalLinkage for true implementation Fn = llvm::Function::Create(MethodTy, llvm::GlobalValue::ExternalLinkage, Name, &CGM.getModule()); auto [It, inserted] = DirectMethodDefinitions.insert(std::make_pair(COMD, DirectMethodInfo(Fn))); @@ -3936,6 +3991,190 @@ CGObjCCommonMac::GenerateDirectMethod(const ObjCMethodDecl *OMD, return I->second; } +/// Start an Objective-C direct method thunk. +/// +/// The thunk must use musttail to remain transparent to ARC - any +/// ARC operations must happen in the caller, not in the thunk. +void CodeGenFunction::StartObjCDirectThunk(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. + CurFuncIsThunk = true; + + // Build argument list for StartFunction. + // We must include all parameters to match the thunk's LLVM function type. + // The thunk uses musttail to forward all arguments directly, so ARC + // processing in the prolog is harmless - the parameters are forwarded + // as-is without local copies. + 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 thunk. +void CodeGenFunction::FinishObjCDirectThunk() { + // 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.shouldHaveNilCheckThunk(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.StartObjCDirectThunk(OMD, Thunk, FI); + + // Copy function-level attributes from implementation to make musttail happy + llvm::AttributeList ImplAttrs = Implementation->getAttributes(); + Thunk->setAttributes(ImplAttrs); + + // - [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) + if (FI.getReturnInfo().isIndirect()) { + // SRet case: return void + CGF.Builder.CreateRetVoid(); + } else { + if (ThunkTy->getReturnType()->isVoidTy()) + CGF.Builder.CreateRetVoid(); + else + CGF.Builder.CreateRet(Call); + } + + // Finish the ObjC direct thunk (creates dummy block and calls FinishFunction) + CGF.FinishObjCDirectThunk(); + return Thunk; +} + +llvm::Function *CGObjCCommonMac::GetDirectMethodCallee( + const ObjCMethodDecl *OMD, const ObjCContainerDecl *CD, + bool ReceiverCanBeNull, bool ClassObjectCanBeUnrealized) { + + // Get from cache or populate the function declaration lazily + DirectMethodInfo &Info = GenerateDirectMethod(OMD, CD); + + // If optimization not enabled, always use implementation (which includes the + // nil check) + if (!CGM.shouldExposeSymbol(OMD)) { + return Info.Implementation; + } + + // Varidic methods doesn't have thunk, the caller need to inline the nil check + if (CGM.shouldHaveNilCheckInline(OMD)) { + return Info.Implementation; + } + + // Thunk is lazily generated. + auto getOrCreateThunk = [&]() { + if (!Info.Thunk) + Info.Thunk = GenerateObjCDirectThunk(OMD, CD, Info.Implementation); + return Info.Thunk; + }; + + assert(CGM.shouldHaveNilCheckThunk(OMD) && + "a method either has nil check thunk or have thunk inlined when " + "exposing its symbol"); + + if (OMD->isInstanceMethod()) { + // If we can prove instance methods receiver is not null, return the true + // implementation + return ReceiverCanBeNull ? getOrCreateThunk() : Info.Implementation; + } + if (OMD->isClassMethod()) { + // For class methods, it need to be non-null and realized before we dispatch + // to true implementation + return (ReceiverCanBeNull || ClassObjectCanBeUnrealized) + ? getOrCreateThunk() + : Info.Implementation; + } + llvm_unreachable("OMD should either be a class method or instance method"); +} + llvm::Value * CGObjCCommonMac::GenerateClassRealization(CodeGenFunction &CGF, llvm::Value *classObject, diff --git a/clang/lib/CodeGen/CodeGenFunction.h b/clang/lib/CodeGen/CodeGenFunction.h index 8c4c1c8c2dc95..f507146b37cc5 100644 --- a/clang/lib/CodeGen/CodeGenFunction.h +++ b/clang/lib/CodeGen/CodeGenFunction.h @@ -2422,6 +2422,13 @@ class CodeGenFunction : public CodeGenTypeCache { void FinishThunk(); + /// Start an Objective-C direct method thunk. + void StartObjCDirectThunk(const ObjCMethodDecl *OMD, llvm::Function *Fn, + const CGFunctionInfo &FI); + + /// Finish an Objective-C direct method thunk. + void FinishObjCDirectThunk(); + /// Emit a musttail call for a thunk with a potentially adjusted this pointer. void EmitMustTailThunk(GlobalDecl GD, llvm::Value *AdjustedThisPtr, llvm::FunctionCallee Callee); _______________________________________________ cfe-commits mailing list [email protected] https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits
