https://github.com/DataCorrupted updated 
https://github.com/llvm/llvm-project/pull/170619

>From bbf2e85a9bc07a52c83d13af5db0d35878484b9a Mon Sep 17 00:00:00 2001
From: Peter Rong <[email protected]>
Date: Wed, 3 Dec 2025 22:45:04 -0800
Subject: [PATCH 1/9] [ExposeObjCDirect] Optimizations

In many cases we can infer that class object has been realized
---
 clang/lib/CodeGen/CGObjCRuntime.cpp | 65 ++++++++++++++++++++++++++++-
 clang/lib/CodeGen/CGObjCRuntime.h   | 23 +++++++---
 2 files changed, 82 insertions(+), 6 deletions(-)

diff --git a/clang/lib/CodeGen/CGObjCRuntime.cpp 
b/clang/lib/CodeGen/CGObjCRuntime.cpp
index a4b4460fdc49c..fd227d9645ac1 100644
--- a/clang/lib/CodeGen/CGObjCRuntime.cpp
+++ b/clang/lib/CodeGen/CGObjCRuntime.cpp
@@ -415,7 +415,70 @@ bool CGObjCRuntime::canMessageReceiverBeNull(
 
 bool CGObjCRuntime::canClassObjectBeUnrealized(
     const ObjCInterfaceDecl *CalleeClassDecl, CodeGenFunction &CGF) const {
-  // TODO
+  if (!CalleeClassDecl)
+    return true;
+
+  // Heuristic 1: +load method on this class
+  // If the class has a +load method, it's realized when the binary is loaded.
+  ASTContext &Ctx = CGM.getContext();
+  const IdentifierInfo *LoadII = &Ctx.Idents.get("load");
+  Selector LoadSel = Ctx.Selectors.getSelector(0, &LoadII);
+
+  // TODO: if one if the child had +load, this class is guaranteed to be
+  // realized as well. We should have a translation unit specific map that
+  // precomputes all classes that are realized, and just do a lookup here.
+  // But we need to measure how expensive it is to create a map like that.
+  if (CalleeClassDecl->lookupClassMethod(LoadSel))
+    return false; // This class has +load, so it's already realized
+
+  // Heuristic 2: using Self / Super
+  // If we're currently executing a method of ClassDecl (or a subclass),
+  // then ClassDecl must already be realized.
+  if (const auto *CurMethod =
+          dyn_cast_or_null<ObjCMethodDecl>(CGF.CurCodeDecl)) {
+    const ObjCInterfaceDecl *CallerCalssDecl = CurMethod->getClassInterface();
+    if (CallerCalssDecl && CalleeClassDecl->isSuperClassOf(CallerCalssDecl))
+      return false;
+  }
+
+  // Heuristic 3: previously realized
+  // Heuristic 3.1: Walk through the current BasicBlock looking for calls that
+  // realize the class. All heuristics in this cluster share the same
+  // implementation pattern.
+  auto *BB = CGF.Builder.GetInsertBlock();
+  if (!BB)
+    return true; // No current block, assume unrealized
+
+  llvm::StringRef CalleeClassName = CalleeClassDecl->getName();
+
+  // Heuristic 3.2 / TODO: If realization happened in a dominating block, the
+  // class is realized Requires Dominator tree analysis. There should be an
+  // outer loop `for (BB: DominatingBasicBlocks)`
+  for (const auto &Inst : *BB) {
+    // Check if this is a call instruction
+    const auto *Call = llvm::dyn_cast<llvm::CallInst>(&Inst);
+    if (!Call)
+      continue;
+    llvm::Function *CalledFunc = Call->getCalledFunction();
+    if (!CalledFunc)
+      continue;
+
+    llvm::StringRef FuncNamePtr = CalledFunc->getName();
+    // Skip the \01 prefix if present
+    if (FuncNamePtr.starts_with("\01"))
+      FuncNamePtr = FuncNamePtr.drop_front(1);
+    // Check for instance method calls: "-[ClassName methodName]"
+    // or class method calls: "+[ClassName methodName]"
+    // Also check for thunks: "-[ClassName methodName]_thunk"
+    if ((FuncNamePtr.starts_with("-[") || FuncNamePtr.starts_with("+["))) {
+      FuncNamePtr = FuncNamePtr.drop_front(2);
+      // TODO: if the current class is the super class of the function that's
+      // used, it should've been realized as well
+      if (FuncNamePtr.starts_with(CalleeClassName))
+        return false;
+    }
+  }
+
   // Otherwise, assume it can be unrealized.
   return true;
 }
diff --git a/clang/lib/CodeGen/CGObjCRuntime.h 
b/clang/lib/CodeGen/CGObjCRuntime.h
index d3d4745cb77a7..b0cf04fc8553b 100644
--- a/clang/lib/CodeGen/CGObjCRuntime.h
+++ b/clang/lib/CodeGen/CGObjCRuntime.h
@@ -226,7 +226,7 @@ class CGObjCRuntime {
   virtual llvm::Function *GenerateMethod(const ObjCMethodDecl *OMD,
                                          const ObjCContainerDecl *CD) = 0;
 
-/// Generates precondition checks for direct Objective-C Methods.
+  /// 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,
@@ -330,10 +330,23 @@ class CGObjCRuntime {
                                      QualType resultType,
                                      CallArgList &callArgs);
 
-  bool canMessageReceiverBeNull(CodeGenFunction &CGF,
-                                const ObjCMethodDecl *method, bool isSuper,
-                                const ObjCInterfaceDecl *classReceiver,
-                                llvm::Value *receiver);
+  /// 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.

>From 49e0b9b6ac187a086244d8e48d51c1d57445043c Mon Sep 17 00:00:00 2001
From: Peter Rong <[email protected]>
Date: Thu, 4 Dec 2025 11:34:27 -0800
Subject: [PATCH 2/9] update test and fix incorrect heuristic

---
 clang/lib/CodeGen/CGObjCRuntime.cpp           |  47 ++----
 ...pose-direct-method-opt-class-realization.m | 148 ++++++++++++++++++
 clang/test/CodeGenObjC/expose-direct-method.m |   2 +
 3 files changed, 160 insertions(+), 37 deletions(-)
 create mode 100644 
clang/test/CodeGenObjC/expose-direct-method-opt-class-realization.m

diff --git a/clang/lib/CodeGen/CGObjCRuntime.cpp 
b/clang/lib/CodeGen/CGObjCRuntime.cpp
index fd227d9645ac1..f20ac144bc7ef 100644
--- a/clang/lib/CodeGen/CGObjCRuntime.cpp
+++ b/clang/lib/CodeGen/CGObjCRuntime.cpp
@@ -441,43 +441,16 @@ bool CGObjCRuntime::canClassObjectBeUnrealized(
       return false;
   }
 
-  // Heuristic 3: previously realized
-  // Heuristic 3.1: Walk through the current BasicBlock looking for calls that
-  // realize the class. All heuristics in this cluster share the same
-  // implementation pattern.
-  auto *BB = CGF.Builder.GetInsertBlock();
-  if (!BB)
-    return true; // No current block, assume unrealized
-
-  llvm::StringRef CalleeClassName = CalleeClassDecl->getName();
-
-  // Heuristic 3.2 / TODO: If realization happened in a dominating block, the
-  // class is realized Requires Dominator tree analysis. There should be an
-  // outer loop `for (BB: DominatingBasicBlocks)`
-  for (const auto &Inst : *BB) {
-    // Check if this is a call instruction
-    const auto *Call = llvm::dyn_cast<llvm::CallInst>(&Inst);
-    if (!Call)
-      continue;
-    llvm::Function *CalledFunc = Call->getCalledFunction();
-    if (!CalledFunc)
-      continue;
-
-    llvm::StringRef FuncNamePtr = CalledFunc->getName();
-    // Skip the \01 prefix if present
-    if (FuncNamePtr.starts_with("\01"))
-      FuncNamePtr = FuncNamePtr.drop_front(1);
-    // Check for instance method calls: "-[ClassName methodName]"
-    // or class method calls: "+[ClassName methodName]"
-    // Also check for thunks: "-[ClassName methodName]_thunk"
-    if ((FuncNamePtr.starts_with("-[") || FuncNamePtr.starts_with("+["))) {
-      FuncNamePtr = FuncNamePtr.drop_front(2);
-      // TODO: if the current class is the super class of the function that's
-      // used, it should've been realized as well
-      if (FuncNamePtr.starts_with(CalleeClassName))
-        return false;
-    }
-  }
+  // TODO: Heuristic 3: previously realized
+  // Walk through previous instructions can be inefficient, since
+  // `canClassObjectBeUnrealized` is called everytime we emit a class method.
+  // Besides, a realized subclass means parent class is realized. Therefore,
+  // a code like below also requires some special handling.
+  //
+  // ```
+  // +[Child foo];
+  // +[Parent foo];
+  // ```
 
   // Otherwise, assume it can be unrealized.
   return true;
diff --git 
a/clang/test/CodeGenObjC/expose-direct-method-opt-class-realization.m 
b/clang/test/CodeGenObjC/expose-direct-method-opt-class-realization.m
new file mode 100644
index 0000000000000..34f8537d75564
--- /dev/null
+++ b/clang/test/CodeGenObjC/expose-direct-method-opt-class-realization.m
@@ -0,0 +1,148 @@
+// RUN: %clang_cc1 -emit-llvm -fobjc-arc -triple arm64-apple-darwin10 \
+// RUN:   -fobjc-expose-direct-methods %s -o - | FileCheck %s
+
+// ============================================================================
+// HEURISTIC 1: Classes with +load method skip thunk for class methods
+// because they are guaranteed to be realized when the binary is loaded.
+// ============================================================================
+
+__attribute__((objc_root_class))
+@interface ClassWithLoad
++ (void)load;
++ (int)classDirectMethod __attribute__((objc_direct));
+@end
+
+@implementation ClassWithLoad
+
++ (void)load {
+  // This method causes the class to be realized at load time
+}
+
+// CHECK-LABEL: define hidden i32 @"+[ClassWithLoad classDirectMethod]"(ptr 
noundef %self)
++ (int)classDirectMethod {
+  return 42;
+}
+
+@end
+
+// A class without +load method for comparison
+__attribute__((objc_root_class))
+@interface ClassWithoutLoad
++ (int)classDirectMethod __attribute__((objc_direct));
+@end
+
+@implementation ClassWithoutLoad
+
+// CHECK-LABEL: define hidden i32 @"+[ClassWithoutLoad classDirectMethod]"(ptr 
noundef %self)
++ (int)classDirectMethod {
+  return 42;
+}
+
+@end
+
+// CHECK-LABEL: define{{.*}} i32 @testClassWithLoad()
+int testClassWithLoad(void) {
+  // Because ClassWithLoad has +load, it's guaranteed to be realized.
+  // So we should call the implementation directly, NOT through a thunk.
+  //
+  // CHECK: call i32 @"+[ClassWithLoad classDirectMethod]"(ptr noundef
+  // CHECK-NOT: call i32 @"+[ClassWithLoad classDirectMethod]_thunk"
+  return [ClassWithLoad classDirectMethod];
+}
+
+// CHECK-LABEL: define{{.*}} i32 @testClassWithoutLoad()
+int testClassWithoutLoad(void) {
+  // ClassWithoutLoad has no +load, so the class might not be realized.
+  // We need to call through the thunk which will realize the class.
+  //
+  // CHECK: call i32 @"+[ClassWithoutLoad classDirectMethod]_thunk"(ptr noundef
+  return [ClassWithoutLoad classDirectMethod];
+}
+
+// ============================================================================
+// HEURISTIC 2: Calls from within the same class skip thunk
+// because if we're executing a method of the class, it must be realized.
+// ============================================================================
+
+__attribute__((objc_root_class))
+@interface SameClassTest
++ (int)classDirectMethod __attribute__((objc_direct));
++ (int)callerClassMethod __attribute__((objc_direct));
+- (int)callerInstanceMethod __attribute__((objc_direct));
+@end
+
+@implementation SameClassTest
+
+// CHECK-LABEL: define hidden i32 @"+[SameClassTest classDirectMethod]"(ptr 
noundef %self)
++ (int)classDirectMethod {
+  return 42;
+}
+
+// CHECK-LABEL: define hidden i32 @"+[SameClassTest callerClassMethod]"(ptr 
noundef %self)
++ (int)callerClassMethod {
+  // Calling a class method from another class method of the SAME class.
+  // The class must be realized (we're already executing a method of it).
+  // Should call implementation directly, NOT through thunk.
+  //
+  // CHECK: call i32 @"+[SameClassTest classDirectMethod]"(ptr noundef
+  // CHECK-NOT: call i32 @"+[SameClassTest classDirectMethod]_thunk"
+  return [SameClassTest classDirectMethod];
+}
+
+// CHECK-LABEL: define hidden i32 @"-[SameClassTest callerInstanceMethod]"(ptr 
noundef %self)
+- (int)callerInstanceMethod {
+  // Calling a class method from an instance method of the SAME class.
+  // The class must be realized (we're already executing a method of it).
+  // Should call implementation directly, NOT through thunk.
+  //
+  // CHECK: call i32 @"+[SameClassTest classDirectMethod]"(ptr noundef
+  // CHECK-NOT: call i32 @"+[SameClassTest classDirectMethod]_thunk"
+  return [SameClassTest classDirectMethod];
+}
+
+@end
+
+__attribute__((objc_root_class))
+@interface SuperClass
++ (int)superClassMethod __attribute__((objc_direct));
+@end
+
+@implementation SuperClass
+
+// CHECK-LABEL: define hidden i32 @"+[SuperClass superClassMethod]"(ptr 
noundef %self)
++ (int)superClassMethod {
+  return 100;
+}
+
+@end
+
+@interface SubClass : SuperClass
++ (int)subCallerMethod __attribute__((objc_direct));
+- (int)subInstanceCaller __attribute__((objc_direct));
+@end
+
+@implementation SubClass
+
+// CHECK-LABEL: define hidden i32 @"+[SubClass subCallerMethod]"(ptr noundef 
%self)
++ (int)subCallerMethod {
+  // Calling a superclass's class method from a subclass method.
+  // SuperClass must be realized because SubClass inherits from it.
+  // Should call implementation directly, NOT through thunk.
+  //
+  // CHECK: call i32 @"+[SuperClass superClassMethod]"(ptr noundef
+  // CHECK-NOT: call i32 @"+[SuperClass superClassMethod]_thunk"
+  return [SuperClass superClassMethod];
+}
+
+// CHECK-LABEL: define hidden i32 @"-[SubClass subInstanceCaller]"(ptr noundef 
%self)
+- (int)subInstanceCaller {
+  // Calling a superclass's class method from a subclass instance method.
+  // SuperClass must be realized because SubClass inherits from it.
+  // Should call implementation directly, NOT through thunk.
+  //
+  // CHECK: call i32 @"+[SuperClass superClassMethod]"(ptr noundef
+  // CHECK-NOT: call i32 @"+[SuperClass superClassMethod]_thunk"
+  return [SuperClass superClassMethod];
+}
+
+@end
diff --git a/clang/test/CodeGenObjC/expose-direct-method.m 
b/clang/test/CodeGenObjC/expose-direct-method.m
index 3d1420619774b..b57670763eae0 100644
--- a/clang/test/CodeGenObjC/expose-direct-method.m
+++ b/clang/test/CodeGenObjC/expose-direct-method.m
@@ -267,8 +267,10 @@ int useSRet(Root *r) {
     // CHECK: call void @"-[Root getAggregate]_thunk"
     [r getAggregate].a +
     // TODO: The compiler is not smart enough to know the class object must be 
realized yet.
+    // CHECK-NOT: call i64 @"+[Root classGetComplex]"(ptr noundef
     // CHECK: call i64 @"+[Root classGetComplex]_thunk"(ptr noundef
     [Root classGetComplex].a +
+    // CHECK-NOT: call void @"+[Root classGetAggregate]"(ptr {{.*}}sret
     // CHECK: call void @"+[Root classGetAggregate]_thunk"(ptr {{.*}}sret
     [Root classGetAggregate].a
   );

>From 0c89c244d66c1a0c36323bfb217162c0f0034c0c Mon Sep 17 00:00:00 2001
From: Peter Rong <[email protected]>
Date: Thu, 4 Dec 2025 11:53:38 -0800
Subject: [PATCH 3/9] fix mac tests

---
 clang/lib/CodeGen/CGObjCRuntime.cpp | 13 ++++++++-----
 1 file changed, 8 insertions(+), 5 deletions(-)

diff --git a/clang/lib/CodeGen/CGObjCRuntime.cpp 
b/clang/lib/CodeGen/CGObjCRuntime.cpp
index f20ac144bc7ef..f336d0d3b9454 100644
--- a/clang/lib/CodeGen/CGObjCRuntime.cpp
+++ b/clang/lib/CodeGen/CGObjCRuntime.cpp
@@ -425,11 +425,14 @@ bool CGObjCRuntime::canClassObjectBeUnrealized(
   Selector LoadSel = Ctx.Selectors.getSelector(0, &LoadII);
 
   // TODO: if one if the child had +load, this class is guaranteed to be
-  // realized as well. We should have a translation unit specific map that
-  // precomputes all classes that are realized, and just do a lookup here.
-  // But we need to measure how expensive it is to create a map like that.
-  if (CalleeClassDecl->lookupClassMethod(LoadSel))
-    return false; // This class has +load, so it's already realized
+  // realized as well. We can't search for all child classes here. Ideally, we
+  // should have a translation unit level `SmallSet` to include all classes 
with
+  // +load. Every time a class has +load, put itself and all parents in it, and
+  // we can just query that `SmallSet` here.
+  if (CalleeClassDecl->lookupMethod(LoadSel, /*isInstance=*/false,
+                                    /*shallowCategoryLookup=*/false,
+                                    /*followSuper=*/false))
+    return false;
 
   // Heuristic 2: using Self / Super
   // If we're currently executing a method of ClassDecl (or a subclass),

>From 7aa65a2f74b47af04223e20602b716ddd5609767 Mon Sep 17 00:00:00 2001
From: Peter Rong <[email protected]>
Date: Thu, 4 Dec 2025 13:12:20 -0800
Subject: [PATCH 4/9] evict weak class

---
 clang/lib/CodeGen/CGObjCRuntime.cpp | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/clang/lib/CodeGen/CGObjCRuntime.cpp 
b/clang/lib/CodeGen/CGObjCRuntime.cpp
index f336d0d3b9454..0e41c9c6b9105 100644
--- a/clang/lib/CodeGen/CGObjCRuntime.cpp
+++ b/clang/lib/CodeGen/CGObjCRuntime.cpp
@@ -415,7 +415,7 @@ bool CGObjCRuntime::canMessageReceiverBeNull(
 
 bool CGObjCRuntime::canClassObjectBeUnrealized(
     const ObjCInterfaceDecl *CalleeClassDecl, CodeGenFunction &CGF) const {
-  if (!CalleeClassDecl)
+  if (!CalleeClassDecl || isWeakLinkedClass(CalleeClassDecl))
     return true;
 
   // Heuristic 1: +load method on this class

>From 97bef2a53d288399177f2effcd0a152f516ba341 Mon Sep 17 00:00:00 2001
From: Peter Rong <[email protected]>
Date: Thu, 4 Dec 2025 15:36:54 -0800
Subject: [PATCH 5/9] fix some lint warnings

---
 clang/lib/CodeGen/CGObjCRuntime.cpp | 6 +++---
 1 file changed, 3 insertions(+), 3 deletions(-)

diff --git a/clang/lib/CodeGen/CGObjCRuntime.cpp 
b/clang/lib/CodeGen/CGObjCRuntime.cpp
index 0e41c9c6b9105..88373fd0426b2 100644
--- a/clang/lib/CodeGen/CGObjCRuntime.cpp
+++ b/clang/lib/CodeGen/CGObjCRuntime.cpp
@@ -397,10 +397,10 @@ bool CGObjCRuntime::canMessageReceiverBeNull(
 
   // 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)) {
-    auto self = curMethod->getSelfDecl();
+  if (const auto *curMethod = 
dyn_cast_or_null<ObjCMethodDecl>(CGF.CurCodeDecl)) {
+    const auto *self = curMethod->getSelfDecl();
     if (self->getType().isConstQualified()) {
-      if (auto LI = dyn_cast<llvm::LoadInst>(receiver->stripPointerCasts())) {
+      if (const auto *LI = 
dyn_cast<llvm::LoadInst>(receiver->stripPointerCasts())) {
         llvm::Value *selfAddr = 
CGF.GetAddrOfLocalVar(self).emitRawPointer(CGF);
         if (selfAddr == LI->getPointerOperand()) {
           return false;

>From 5cb282dd467fedfcc02ea26238c797fba658e213 Mon Sep 17 00:00:00 2001
From: Peter Rong <[email protected]>
Date: Thu, 4 Dec 2025 15:42:20 -0800
Subject: [PATCH 6/9] format

---
 clang/lib/CodeGen/CGObjCRuntime.cpp | 6 ++++--
 1 file changed, 4 insertions(+), 2 deletions(-)

diff --git a/clang/lib/CodeGen/CGObjCRuntime.cpp 
b/clang/lib/CodeGen/CGObjCRuntime.cpp
index 88373fd0426b2..be027777747d5 100644
--- a/clang/lib/CodeGen/CGObjCRuntime.cpp
+++ b/clang/lib/CodeGen/CGObjCRuntime.cpp
@@ -397,10 +397,12 @@ bool CGObjCRuntime::canMessageReceiverBeNull(
 
   // 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 (const auto *curMethod = 
dyn_cast_or_null<ObjCMethodDecl>(CGF.CurCodeDecl)) {
+  if (const auto *curMethod =
+          dyn_cast_or_null<ObjCMethodDecl>(CGF.CurCodeDecl)) {
     const auto *self = curMethod->getSelfDecl();
     if (self->getType().isConstQualified()) {
-      if (const auto *LI = 
dyn_cast<llvm::LoadInst>(receiver->stripPointerCasts())) {
+      if (const auto *LI =
+              dyn_cast<llvm::LoadInst>(receiver->stripPointerCasts())) {
         llvm::Value *selfAddr = 
CGF.GetAddrOfLocalVar(self).emitRawPointer(CGF);
         if (selfAddr == LI->getPointerOperand()) {
           return false;

>From 0ecb1b2af4554f3832859a4e48d2d559c9ad7145 Mon Sep 17 00:00:00 2001
From: Peter Rong <[email protected]>
Date: Thu, 4 Dec 2025 15:53:24 -0800
Subject: [PATCH 7/9] simplify tests

---
 ...pose-direct-method-opt-class-realization.m | 84 ++++++++-----------
 1 file changed, 33 insertions(+), 51 deletions(-)

diff --git 
a/clang/test/CodeGenObjC/expose-direct-method-opt-class-realization.m 
b/clang/test/CodeGenObjC/expose-direct-method-opt-class-realization.m
index 34f8537d75564..8814179ccb8e3 100644
--- a/clang/test/CodeGenObjC/expose-direct-method-opt-class-realization.m
+++ b/clang/test/CodeGenObjC/expose-direct-method-opt-class-realization.m
@@ -7,7 +7,18 @@
 // ============================================================================
 
 __attribute__((objc_root_class))
-@interface ClassWithLoad
+@interface Root
++ (int)rootDirectMethod __attribute__((objc_direct));
+@end
+
+@implementation Root
+
+// CHECK-LABEL: define hidden i32 @"+[Root rootDirectMethod]"(ptr noundef 
%self)
++ (int)rootDirectMethod { return 100; }
+
+@end
+
+@interface ClassWithLoad : Root
 + (void)load;
 + (int)classDirectMethod __attribute__((objc_direct));
 @end
@@ -19,15 +30,12 @@ + (void)load {
 }
 
 // CHECK-LABEL: define hidden i32 @"+[ClassWithLoad classDirectMethod]"(ptr 
noundef %self)
-+ (int)classDirectMethod {
-  return 42;
-}
++ (int)classDirectMethod { return 42; }
 
 @end
 
 // A class without +load method for comparison
-__attribute__((objc_root_class))
-@interface ClassWithoutLoad
+@interface ClassWithoutLoad : Root
 + (int)classDirectMethod __attribute__((objc_direct));
 @end
 
@@ -64,8 +72,7 @@ int testClassWithoutLoad(void) {
 // because if we're executing a method of the class, it must be realized.
 // ============================================================================
 
-__attribute__((objc_root_class))
-@interface SameClassTest
+@interface SameClassTest : Root
 + (int)classDirectMethod __attribute__((objc_direct));
 + (int)callerClassMethod __attribute__((objc_direct));
 - (int)callerInstanceMethod __attribute__((objc_direct));
@@ -86,7 +93,17 @@ + (int)callerClassMethod {
   //
   // CHECK: call i32 @"+[SameClassTest classDirectMethod]"(ptr noundef
   // CHECK-NOT: call i32 @"+[SameClassTest classDirectMethod]_thunk"
-  return [SameClassTest classDirectMethod];
+  int a = [SameClassTest classDirectMethod];
+
+  // Calling the root class's class method from a subclass method.
+  // Root must be realized because SubClass inherits from it.
+  // Should call implementation directly, NOT through thunk.
+  //
+  // CHECK: call i32 @"+[Root rootDirectMethod]"(ptr noundef
+  // CHECK-NOT: call i32 @"+[Root rootDirectMethod]_thunk"
+  int b = [Root rootDirectMethod];
+
+  return a + b;
 }
 
 // CHECK-LABEL: define hidden i32 @"-[SameClassTest callerInstanceMethod]"(ptr 
noundef %self)
@@ -97,52 +114,17 @@ - (int)callerInstanceMethod {
   //
   // CHECK: call i32 @"+[SameClassTest classDirectMethod]"(ptr noundef
   // CHECK-NOT: call i32 @"+[SameClassTest classDirectMethod]_thunk"
-  return [SameClassTest classDirectMethod];
-}
+  int a = [SameClassTest classDirectMethod];
 
-@end
-
-__attribute__((objc_root_class))
-@interface SuperClass
-+ (int)superClassMethod __attribute__((objc_direct));
-@end
-
-@implementation SuperClass
-
-// CHECK-LABEL: define hidden i32 @"+[SuperClass superClassMethod]"(ptr 
noundef %self)
-+ (int)superClassMethod {
-  return 100;
-}
-
-@end
-
-@interface SubClass : SuperClass
-+ (int)subCallerMethod __attribute__((objc_direct));
-- (int)subInstanceCaller __attribute__((objc_direct));
-@end
-
-@implementation SubClass
-
-// CHECK-LABEL: define hidden i32 @"+[SubClass subCallerMethod]"(ptr noundef 
%self)
-+ (int)subCallerMethod {
-  // Calling a superclass's class method from a subclass method.
-  // SuperClass must be realized because SubClass inherits from it.
+  // Calling the root class's class method from a subclass instance method.
+  // Root must be realized because SubClass inherits from it.
   // Should call implementation directly, NOT through thunk.
   //
-  // CHECK: call i32 @"+[SuperClass superClassMethod]"(ptr noundef
-  // CHECK-NOT: call i32 @"+[SuperClass superClassMethod]_thunk"
-  return [SuperClass superClassMethod];
-}
+  // CHECK: call i32 @"+[Root rootDirectMethod]"(ptr noundef
+  // CHECK-NOT: call i32 @"+[Root rootDirectMethod]_thunk"
+  int b = [Root rootDirectMethod];
 
-// CHECK-LABEL: define hidden i32 @"-[SubClass subInstanceCaller]"(ptr noundef 
%self)
-- (int)subInstanceCaller {
-  // Calling a superclass's class method from a subclass instance method.
-  // SuperClass must be realized because SubClass inherits from it.
-  // Should call implementation directly, NOT through thunk.
-  //
-  // CHECK: call i32 @"+[SuperClass superClassMethod]"(ptr noundef
-  // CHECK-NOT: call i32 @"+[SuperClass superClassMethod]_thunk"
-  return [SuperClass superClassMethod];
+  return a + b;
 }
 
 @end

>From bceeae80772504cece97691e5e1a1e97dbf07808 Mon Sep 17 00:00:00 2001
From: Peter Rong <[email protected]>
Date: Thu, 4 Dec 2025 16:09:51 -0800
Subject: [PATCH 8/9] Add a cache to remember all classes that should've been
 realized by load

---
 clang/lib/CodeGen/CGObjCRuntime.cpp | 51 +++++++++++++++++++++--------
 clang/lib/CodeGen/CGObjCRuntime.h   | 10 ++++++
 2 files changed, 47 insertions(+), 14 deletions(-)

diff --git a/clang/lib/CodeGen/CGObjCRuntime.cpp 
b/clang/lib/CodeGen/CGObjCRuntime.cpp
index be027777747d5..0ed4feac31ea8 100644
--- a/clang/lib/CodeGen/CGObjCRuntime.cpp
+++ b/clang/lib/CodeGen/CGObjCRuntime.cpp
@@ -420,20 +420,11 @@ bool CGObjCRuntime::canClassObjectBeUnrealized(
   if (!CalleeClassDecl || isWeakLinkedClass(CalleeClassDecl))
     return true;
 
-  // Heuristic 1: +load method on this class
-  // If the class has a +load method, it's realized when the binary is loaded.
-  ASTContext &Ctx = CGM.getContext();
-  const IdentifierInfo *LoadII = &Ctx.Idents.get("load");
-  Selector LoadSel = Ctx.Selectors.getSelector(0, &LoadII);
-
-  // TODO: if one if the child had +load, this class is guaranteed to be
-  // realized as well. We can't search for all child classes here. Ideally, we
-  // should have a translation unit level `SmallSet` to include all classes 
with
-  // +load. Every time a class has +load, put itself and all parents in it, and
-  // we can just query that `SmallSet` here.
-  if (CalleeClassDecl->lookupMethod(LoadSel, /*isInstance=*/false,
-                                    /*shallowCategoryLookup=*/false,
-                                    /*followSuper=*/false))
+  // Heuristic 1: +load method on this class or any subclass
+  // If the class or any of its subclasses has a +load method, it's realized
+  // when the binary is loaded. We cache this information to avoid repeatedly
+  // scanning the translation unit.
+  if (getOrPopulateRealizedClasses().contains(CalleeClassDecl))
     return false;
 
   // Heuristic 2: using Self / Super
@@ -461,6 +452,38 @@ bool CGObjCRuntime::canClassObjectBeUnrealized(
   return true;
 }
 
+const RealizedClassSet &CGObjCRuntime::getOrPopulateRealizedClasses() const {
+  if (RealizedClasses)
+    return *RealizedClasses;
+  RealizedClasses = llvm::DenseSet<const ObjCInterfaceDecl *>();
+
+  ASTContext &Ctx = CGM.getContext();
+  const IdentifierInfo *LoadII = &Ctx.Idents.get("load");
+  Selector LoadSel = Ctx.Selectors.getSelector(0, &LoadII);
+
+  TranslationUnitDecl *TUDecl = Ctx.getTranslationUnitDecl();
+  llvm::DenseSet<const ObjCInterfaceDecl *> VisitedClasses;
+  for (const auto *D : TUDecl->decls()) {
+    if (const auto *OID = dyn_cast<ObjCInterfaceDecl>(D)) {
+      if (VisitedClasses.contains(OID))
+        continue;
+      // Check if this class has a +load method
+      if (OID->lookupMethod(LoadSel, /*isInstance=*/false,
+                            /*shallowCategoryLookup=*/false,
+                            /*followSuper=*/false)) {
+        // Add this class and all its superclasses to the realized set
+        const ObjCInterfaceDecl *Cls = OID;
+        while (Cls) {
+          RealizedClasses->insert(Cls);
+          VisitedClasses.insert(Cls);
+          Cls = Cls->getSuperClass();
+        }
+      }
+    }
+  }
+  return *RealizedClasses;
+}
+
 bool CGObjCRuntime::isWeakLinkedClass(const ObjCInterfaceDecl *ID) {
   do {
     if (ID->isWeakImported())
diff --git a/clang/lib/CodeGen/CGObjCRuntime.h 
b/clang/lib/CodeGen/CGObjCRuntime.h
index b0cf04fc8553b..06faea476cc34 100644
--- a/clang/lib/CodeGen/CGObjCRuntime.h
+++ b/clang/lib/CodeGen/CGObjCRuntime.h
@@ -20,6 +20,7 @@
 #include "CGValue.h"
 #include "clang/AST/DeclObjC.h"
 #include "clang/Basic/IdentifierTable.h" // Selector
+#include "llvm/ADT/DenseSet.h"
 #include "llvm/ADT/UniqueVector.h"
 
 namespace llvm {
@@ -60,6 +61,7 @@ class CGBlockInfo;
 
 // FIXME: Several methods should be pure virtual but aren't to avoid the
 // partially-implemented subclass breaking.
+typedef llvm::DenseSet<const ObjCInterfaceDecl *> RealizedClassSet;
 
 /// Implements runtime-specific code generation functions.
 class CGObjCRuntime {
@@ -67,6 +69,14 @@ class CGObjCRuntime {
   CodeGen::CodeGenModule &CGM;
   CGObjCRuntime(CodeGen::CodeGenModule &CGM) : CGM(CGM) {}
 
+  /// Cache of classes that are guaranteed to be realized because they or one
+  /// of their subclasses has a +load method. Lazily populated on first query.
+  mutable std::optional<RealizedClassSet> RealizedClasses;
+
+  /// Populate the RealizedClasses cache by scanning all ObjCInterfaceDecls
+  /// in the translation unit for +load methods.
+  const RealizedClassSet &getOrPopulateRealizedClasses() const;
+
   // Utility functions for unified ivar access. These need to
   // eventually be folded into other places (the structure layout
   // code).

>From a8aa6a9374540d80dab497ef7a21f040d1a22130 Mon Sep 17 00:00:00 2001
From: Peter Rong <[email protected]>
Date: Fri, 5 Dec 2025 11:02:06 -0800
Subject: [PATCH 9/9] Add a cache to remember previously realized classes

---
 clang/lib/CodeGen/CGObjCMac.cpp               | 14 +++++++
 clang/lib/CodeGen/CGObjCRuntime.cpp           | 32 +++++++++++-----
 clang/lib/CodeGen/CodeGenFunction.h           |  9 +++++
 ...pose-direct-method-opt-class-realization.m | 37 +++++++++++++++++++
 clang/test/CodeGenObjC/expose-direct-method.m |  7 ++--
 5 files changed, 86 insertions(+), 13 deletions(-)

diff --git a/clang/lib/CodeGen/CGObjCMac.cpp b/clang/lib/CodeGen/CGObjCMac.cpp
index 641302d7d32bc..5b60342d13758 100644
--- a/clang/lib/CodeGen/CGObjCMac.cpp
+++ b/clang/lib/CodeGen/CGObjCMac.cpp
@@ -2190,6 +2190,20 @@ CodeGen::RValue CGObjCCommonMac::EmitMessageSend(
     CallSite->setDoesNotReturn();
   }
 
+  // If this was a class method call on a non-weakly-linked class, record it
+  // as realized for the "previously realized" heuristic.
+  if (ClassReceiver && Method && !isWeakLinkedClass(ClassReceiver)) {
+    if (llvm::BasicBlock *CurrentBB = CGF.Builder.GetInsertBlock())
+      // 1. Class methods have forced class realization (regardless direct or
+      // not)
+      // 2. Direct methods whose receiver is not null means the class is
+      // previously realized.
+      if (Method->isClassMethod() ||
+          (Method->isInstanceMethod() && !ReceiverCanBeNull)) {
+        CGF.ObjCRealizedClasses[CurrentBB].insert(ClassReceiver);
+      }
+  }
+
   return nullReturn.complete(CGF, Return, rvalue, ResultType, CallArgs,
                              RequiresNullCheck ? Method : nullptr);
 }
diff --git a/clang/lib/CodeGen/CGObjCRuntime.cpp 
b/clang/lib/CodeGen/CGObjCRuntime.cpp
index 0ed4feac31ea8..82780e3268f9e 100644
--- a/clang/lib/CodeGen/CGObjCRuntime.cpp
+++ b/clang/lib/CodeGen/CGObjCRuntime.cpp
@@ -437,16 +437,30 @@ bool CGObjCRuntime::canClassObjectBeUnrealized(
       return false;
   }
 
-  // TODO: Heuristic 3: previously realized
-  // Walk through previous instructions can be inefficient, since
-  // `canClassObjectBeUnrealized` is called everytime we emit a class method.
-  // Besides, a realized subclass means parent class is realized. Therefore,
-  // a code like below also requires some special handling.
+  // Heuristic 3: previously realized classes
+  // If we've already emitted a class method call for this class (or a 
subclass)
+  // earlier, then the class must be realized.
   //
-  // ```
-  // +[Child foo];
-  // +[Parent foo];
-  // ```
+  // TODO: Iter over all dominating blocks instead of just looking at the
+  // current block. While we can construct a DT using CFG.CurFn, it is 
expensive
+  // to do so repeatly when CGF is still emitting blocks.
+  if (auto *CurBB = CGF.Builder.GetInsertBlock()) {
+    auto It = CGF.ObjCRealizedClasses.find(CurBB);
+    if (It != CGF.ObjCRealizedClasses.end()) {
+      // Check if CalleeClassDecl is the same as or a superclass of any
+      // realized class in the cache. A realized subclass implies the parent
+      // is realized.
+      for (const auto *RealizedClass : It->second) {
+        if (CalleeClassDecl == RealizedClass)
+          return false;
+        if (CalleeClassDecl->isSuperClassOf(RealizedClass)) {
+          // Also cache this class to reduce future `isSuperClassOf` calls
+          It->second.insert(CalleeClassDecl);
+          return false;
+        }
+      }
+    }
+  }
 
   // Otherwise, assume it can be unrealized.
   return true;
diff --git a/clang/lib/CodeGen/CodeGenFunction.h 
b/clang/lib/CodeGen/CodeGenFunction.h
index f507146b37cc5..159d06022eda9 100644
--- a/clang/lib/CodeGen/CodeGenFunction.h
+++ b/clang/lib/CodeGen/CodeGenFunction.h
@@ -870,6 +870,15 @@ class CodeGenFunction : public CodeGenTypeCache {
   /// rethrows.
   SmallVector<llvm::Value *, 8> ObjCEHValueStack;
 
+  /// Per-basic-block cache of ObjC classes that have been realized during
+  /// codegen. When a class method is emitted on a non-weakly-linked class,
+  /// we record it here. This supports the "previously realized" heuristic
+  /// in canClassObjectBeUnrealized. The structure supports future
+  /// dominator-based analysis where we can check dominating blocks.
+  llvm::DenseMap<llvm::BasicBlock *,
+                 llvm::SmallPtrSet<const ObjCInterfaceDecl *, 4>>
+      ObjCRealizedClasses;
+
   /// A class controlling the emission of a finally block.
   class FinallyInfo {
     /// Where the catchall's edge through the cleanup should go.
diff --git 
a/clang/test/CodeGenObjC/expose-direct-method-opt-class-realization.m 
b/clang/test/CodeGenObjC/expose-direct-method-opt-class-realization.m
index 8814179ccb8e3..cf43123b3ab40 100644
--- a/clang/test/CodeGenObjC/expose-direct-method-opt-class-realization.m
+++ b/clang/test/CodeGenObjC/expose-direct-method-opt-class-realization.m
@@ -128,3 +128,40 @@ - (int)callerInstanceMethod {
 }
 
 @end
+
+// ============================================================================
+// HEURISTIC 3: Previously realized classes in the same basic block skip thunk.
+// If we've already called a class method (which realizes the class),
+// subsequent calls to the same class or its superclasses can skip the thunk.
+// ============================================================================
+
+// CHECK-LABEL: define{{.*}} i32 @testPreviouslyRealizedParentClass
+int testPreviouslyRealizedParentClass(int flag) {
+  if (flag) {
+    // First call to ClassWithoutLoad - needs thunk (class might not be 
realized)
+    // CHECK: call i32 @"+[ClassWithoutLoad classDirectMethod]_thunk"(ptr 
noundef
+    int a = [ClassWithoutLoad classDirectMethod];
+
+    // Second call to same class - should skip thunk (class was just realized)
+    // CHECK: call i32 @"+[ClassWithoutLoad classDirectMethod]"(ptr noundef
+    // CHECK-NOT: call i32 @"+[ClassWithoutLoad classDirectMethod]_thunk"
+    int b = [ClassWithoutLoad classDirectMethod];
+
+    // Call to Root (parent of ClassWithoutLoad) - should skip thunk
+    // because realizing ClassWithoutLoad also realizes its superclass Root.
+    // CHECK: call i32 @"+[Root rootDirectMethod]"(ptr noundef
+    // CHECK-NOT: call i32 @"+[Root rootDirectMethod]_thunk"
+    int c = [Root rootDirectMethod];
+    return a + b + c;
+
+  }
+  // New block, we are not sure if prev block is executed, so we have to 
conservatively realize again.
+  // CHECK: call i32 @"+[ClassWithoutLoad classDirectMethod]_thunk"
+  // CHECK-NOT: call i32 @"+[ClassWithoutLoad classDirectMethod]"(ptr noundef
+  int b = [ClassWithoutLoad classDirectMethod];
+  // CHECK: call i32 @"+[Root rootDirectMethod]"(ptr noundef
+  // CHECK-NOT: call i32 @"+[Root rootDirectMethod]_thunk"
+  int c = [Root rootDirectMethod];
+
+  return b + c;
+}
diff --git a/clang/test/CodeGenObjC/expose-direct-method.m 
b/clang/test/CodeGenObjC/expose-direct-method.m
index b57670763eae0..fceedf4e944c0 100644
--- a/clang/test/CodeGenObjC/expose-direct-method.m
+++ b/clang/test/CodeGenObjC/expose-direct-method.m
@@ -266,12 +266,11 @@ int useSRet(Root *r) {
     // TODO: we should know that this instance is non nil.
     // CHECK: call void @"-[Root getAggregate]_thunk"
     [r getAggregate].a +
-    // TODO: The compiler is not smart enough to know the class object must be 
realized yet.
     // CHECK-NOT: call i64 @"+[Root classGetComplex]"(ptr noundef
     // CHECK: call i64 @"+[Root classGetComplex]_thunk"(ptr noundef
     [Root classGetComplex].a +
-    // CHECK-NOT: call void @"+[Root classGetAggregate]"(ptr {{.*}}sret
-    // CHECK: call void @"+[Root classGetAggregate]_thunk"(ptr {{.*}}sret
+    // CHECK-NOT: call void @"+[Root classGetAggregate]_thunk"(ptr {{.*}}sret
+    // CHECK: call void @"+[Root classGetAggregate]"(ptr {{.*}}sret
     [Root classGetAggregate].a
   );
 }
@@ -291,4 +290,4 @@ int useSRet(Root *r) {
 // CHECK:   ret void
 
 // CHECK: define {{.*}} @"+[Root classGetComplex]_thunk"
-// CHECK: define {{.*}} @"+[Root classGetAggregate]_thunk"
+// CHECK-NOT: define {{.*}} @"+[Root classGetAggregate]_thunk"

_______________________________________________
llvm-branch-commits mailing list
[email protected]
https://lists.llvm.org/cgi-bin/mailman/listinfo/llvm-branch-commits

Reply via email to