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

Reply via email to