[clang] [ObjCDirect] Move nil check to a thunk function (PR #126639)

Peter Rong via cfe-commits cfe-commits at lists.llvm.org
Tue Apr 29 16:19:34 PDT 2025


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

>From f6166a6c2ab45edaa0ff273e2ba917fff778a243 Mon Sep 17 00:00:00 2001
From: Peter Rong <PeterRong at meta.com>
Date: Tue, 29 Apr 2025 14:24:49 -0700
Subject: [PATCH 01/12] [clang] change mangling API to allow calls from
 swift-frontend

Signed-off-by: Peter Rong <PeterRong at meta.com>
---
 clang/include/clang/AST/Mangle.h |  7 ++++++
 clang/lib/AST/Mangle.cpp         | 37 +++++++++++++++++++++++---------
 2 files changed, 34 insertions(+), 10 deletions(-)

diff --git a/clang/include/clang/AST/Mangle.h b/clang/include/clang/AST/Mangle.h
index a0162fb7125fe..1afbf80df40cf 100644
--- a/clang/include/clang/AST/Mangle.h
+++ b/clang/include/clang/AST/Mangle.h
@@ -40,6 +40,13 @@ struct ThisAdjustment;
 struct ThunkInfo;
 class VarDecl;
 
+/// Extract mangling function name from MangleContext such that swift can call
+/// it to prepare for ObjCDirect in swift.
+void mangleObjCMethodName(raw_ostream &OS, bool includePrefixByte,
+  bool isInstanceMethod, StringRef ClassName,
+  std::optional<StringRef> CategoryName,
+  StringRef MethodName);
+
 /// MangleContext - Context for tracking state which persists across multiple
 /// calls to the C++ name mangler.
 class MangleContext {
diff --git a/clang/lib/AST/Mangle.cpp b/clang/lib/AST/Mangle.cpp
index 741c031a40385..9652fdbc4e125 100644
--- a/clang/lib/AST/Mangle.cpp
+++ b/clang/lib/AST/Mangle.cpp
@@ -29,6 +29,23 @@
 
 using namespace clang;
 
+void clang::mangleObjCMethodName(raw_ostream &OS, bool includePrefixByte,
+                                 bool isInstanceMethod, StringRef ClassName,
+                                 std::optional<StringRef> CategoryName,
+                                 StringRef MethodName) {
+  // \01+[ContainerName(CategoryName) SelectorName]
+  if (includePrefixByte)
+    OS << "\01";
+  OS << (isInstanceMethod ? '-' : '+');
+  OS << '[';
+  OS << ClassName;
+  if (CategoryName)
+    OS << "(" << *CategoryName << ")";
+  OS << " ";
+  OS << MethodName;
+  OS << ']';
+}
+
 // FIXME: For blocks we currently mimic GCC's mangling scheme, which leaves
 // much to be desired. Come up with a better mangling scheme.
 
@@ -362,26 +379,26 @@ void MangleContext::mangleObjCMethodName(const ObjCMethodDecl *MD,
   }
 
   // \01+[ContainerName(CategoryName) SelectorName]
-  if (includePrefixByte) {
-    OS << '\01';
-  }
-  OS << (MD->isInstanceMethod() ? '-' : '+') << '[';
+  auto CategoryName = std::optional<StringRef>();
+  StringRef ClassName = "";
   if (const auto *CID = MD->getCategory()) {
     if (const auto *CI = CID->getClassInterface()) {
-      OS << CI->getName();
+      ClassName = CI->getName();
       if (includeCategoryNamespace) {
-        OS << '(' << *CID << ')';
+        CategoryName = CID->getName();
       }
     }
   } else if (const auto *CD =
                  dyn_cast<ObjCContainerDecl>(MD->getDeclContext())) {
-    OS << CD->getName();
+    ClassName = CD->getName();
   } else {
     llvm_unreachable("Unexpected ObjC method decl context");
   }
-  OS << ' ';
-  MD->getSelector().print(OS);
-  OS << ']';
+  std::string MethodName;
+  llvm::raw_string_ostream MethodNameOS(MethodName);
+  MD->getSelector().print(MethodNameOS);
+  clang::mangleObjCMethodName(OS, includePrefixByte, MD->isInstanceMethod(),
+                              ClassName, CategoryName, MethodName);
 }
 
 void MangleContext::mangleObjCMethodNameAsSourceName(const ObjCMethodDecl *MD,

>From 165f5119fd701ee42ca205ee259d8f65a10a0925 Mon Sep 17 00:00:00 2001
From: Peter Rong <PeterRong at meta.com>
Date: Tue, 29 Apr 2025 14:52:14 -0700
Subject: [PATCH 02/12] Format

---
 clang/include/clang/AST/Mangle.h | 6 +++---
 1 file changed, 3 insertions(+), 3 deletions(-)

diff --git a/clang/include/clang/AST/Mangle.h b/clang/include/clang/AST/Mangle.h
index 1afbf80df40cf..ca72dcfd4483d 100644
--- a/clang/include/clang/AST/Mangle.h
+++ b/clang/include/clang/AST/Mangle.h
@@ -43,9 +43,9 @@ class VarDecl;
 /// Extract mangling function name from MangleContext such that swift can call
 /// it to prepare for ObjCDirect in swift.
 void mangleObjCMethodName(raw_ostream &OS, bool includePrefixByte,
-  bool isInstanceMethod, StringRef ClassName,
-  std::optional<StringRef> CategoryName,
-  StringRef MethodName);
+                          bool isInstanceMethod, StringRef ClassName,
+                          std::optional<StringRef> CategoryName,
+                          StringRef MethodName);
 
 /// MangleContext - Context for tracking state which persists across multiple
 /// calls to the C++ name mangler.

>From 96506c82e3b651de7b56874fee9d50cb0ac5204c Mon Sep 17 00:00:00 2001
From: Peter Rong <PeterRong at meta.com>
Date: Mon, 10 Feb 2025 16:47:13 -0800
Subject: [PATCH 03/12] [ObjCDirect] Move nil check to a thunk function

Signed-off-by: Peter Rong <PeterRong at meta.com>
---
 clang/include/clang/AST/DeclObjC.h            |   6 +
 clang/include/clang/AST/Mangle.h              |   5 +-
 clang/include/clang/Basic/CodeGenOptions.def  |   1 +
 clang/include/clang/Basic/LangOptions.def     |   1 +
 clang/include/clang/Driver/Options.td         |   2 +
 clang/lib/AST/Mangle.cpp                      |  12 +-
 clang/lib/CodeGen/CGCall.cpp                  |  20 +-
 clang/lib/CodeGen/CGObjC.cpp                  |  63 +++-
 clang/lib/CodeGen/CGObjCGNU.cpp               |  17 +-
 clang/lib/CodeGen/CGObjCMac.cpp               | 147 ++++++--
 clang/lib/CodeGen/CGObjCRuntime.cpp           |   9 +-
 clang/lib/CodeGen/CGObjCRuntime.h             |  11 +-
 clang/lib/CodeGen/CodeGenFunction.cpp         |   6 +
 clang/lib/CodeGen/CodeGenFunction.h           |   8 +
 clang/lib/CodeGen/CodeGenModule.h             |   8 +
 clang/lib/Driver/ToolChains/Clang.cpp         |   3 +
 clang/lib/Frontend/CompilerInvocation.cpp     |   7 +
 .../direct-method-nil-check-linkedlist.m      | 138 ++++++++
 .../direct-method-nil-check-thunk-arc.m       | 153 +++++++++
 .../direct-method-nil-check-thunk-consumed.m  |  56 +++
 .../direct-method-nil-check-thunk-opt.m       |  68 ++++
 .../direct-method-nil-check-thunk-vararg.m    |  59 ++++
 .../direct-method-nil-check-thunk.m           | 324 ++++++++++++++++++
 .../CodeGenObjC/direct-method-ret-mismatch.m  |   8 +
 24 files changed, 1090 insertions(+), 42 deletions(-)
 create mode 100644 clang/test/CodeGenObjC/direct-method-nil-check-linkedlist.m
 create mode 100644 clang/test/CodeGenObjC/direct-method-nil-check-thunk-arc.m
 create mode 100644 clang/test/CodeGenObjC/direct-method-nil-check-thunk-consumed.m
 create mode 100644 clang/test/CodeGenObjC/direct-method-nil-check-thunk-opt.m
 create mode 100644 clang/test/CodeGenObjC/direct-method-nil-check-thunk-vararg.m
 create mode 100644 clang/test/CodeGenObjC/direct-method-nil-check-thunk.m

diff --git a/clang/include/clang/AST/DeclObjC.h b/clang/include/clang/AST/DeclObjC.h
index 4663603f79754..69c36b77da6c2 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;
 
+  // Only direct instance method that have a fixed number of arguments can have
+  // nil check thunk functions.
+  bool canHaveNilCheckThunk() const {
+    return isDirectMethod() && isInstanceMethod() && !isVariadic();
+  }
+
   /// True if the method has a parameter that's destroyed in the callee.
   bool hasParamDestroyedInCallee() const;
 
diff --git a/clang/include/clang/AST/Mangle.h b/clang/include/clang/AST/Mangle.h
index ca72dcfd4483d..47e482586496e 100644
--- a/clang/include/clang/AST/Mangle.h
+++ b/clang/include/clang/AST/Mangle.h
@@ -45,7 +45,7 @@ class VarDecl;
 void mangleObjCMethodName(raw_ostream &OS, bool includePrefixByte,
                           bool isInstanceMethod, StringRef ClassName,
                           std::optional<StringRef> CategoryName,
-                          StringRef MethodName);
+                          StringRef MethodName, bool isThunk);
 
 /// MangleContext - Context for tracking state which persists across multiple
 /// calls to the C++ name mangler.
@@ -160,7 +160,8 @@ class MangleContext {
 
   void mangleObjCMethodName(const ObjCMethodDecl *MD, raw_ostream &OS,
                             bool includePrefixByte = true,
-                            bool includeCategoryNamespace = true) const;
+                            bool includeCategoryNamespace = true,
+                            bool isThunk = true) const;
   void mangleObjCMethodNameAsSourceName(const ObjCMethodDecl *MD,
                                         raw_ostream &) const;
 
diff --git a/clang/include/clang/Basic/CodeGenOptions.def b/clang/include/clang/Basic/CodeGenOptions.def
index c5990fb248689..9ec1d90e1d960 100644
--- a/clang/include/clang/Basic/CodeGenOptions.def
+++ b/clang/include/clang/Basic/CodeGenOptions.def
@@ -361,6 +361,7 @@ CODEGENOPT(EmitLLVMUseLists, 1, 0) ///< Control whether to serialize use-lists.
 
 CODEGENOPT(WholeProgramVTables, 1, 0) ///< Whether to apply whole-program
                                       ///  vtable optimization.
+CODEGENOPT(ObjCEmitNilCheckThunk , 1, 0) ///< Whether objc_direct methods should emit a nil check thunk.
 
 CODEGENOPT(VirtualFunctionElimination, 1, 0) ///< Whether to apply the dead
                                              /// virtual function elimination
diff --git a/clang/include/clang/Basic/LangOptions.def b/clang/include/clang/Basic/LangOptions.def
index 85ca523c44157..a529ef47ac9fc 100644
--- a/clang/include/clang/Basic/LangOptions.def
+++ b/clang/include/clang/Basic/LangOptions.def
@@ -371,6 +371,7 @@ LANGOPT(IncludeDefaultHeader, 1, 0, "Include default header file for OpenCL")
 LANGOPT(DeclareOpenCLBuiltins, 1, 0, "Declare OpenCL builtin functions")
 BENIGN_LANGOPT(DelayedTemplateParsing , 1, 0, "delayed template parsing")
 LANGOPT(BlocksRuntimeOptional , 1, 0, "optional blocks runtime")
+LANGOPT(ObjCEmitNilCheckThunk, 1, 0, "Emit a thunk to do nil check for objc direct methods")
 LANGOPT(
     CompleteMemberPointers, 1, 0,
     "Require member pointer base types to be complete at the point where the "
diff --git a/clang/include/clang/Driver/Options.td b/clang/include/clang/Driver/Options.td
index c0f469e04375c..fd77449f7263d 100644
--- a/clang/include/clang/Driver/Options.td
+++ b/clang/include/clang/Driver/Options.td
@@ -3049,6 +3049,8 @@ def fthin_link_bitcode_EQ : Joined<["-"], "fthin-link-bitcode=">,
   Visibility<[ClangOption, CLOption, CC1Option]>, Group<f_Group>,
   HelpText<"Write minimized bitcode to <file> for the ThinLTO thin link only">,
   MarshallingInfoString<CodeGenOpts<"ThinLinkBitcodeFile">>;
+def fobjc_emit_nil_check_thunk:
+  Flag<["-"], "fobjc-emit-nil-check-thunk">, Visibility<[ClangOption, CC1Option]>, Group<f_Group>;
 defm fat_lto_objects : BoolFOption<"fat-lto-objects",
   CodeGenOpts<"FatLTO">, DefaultFalse,
   PosFlag<SetTrue, [], [ClangOption, CC1Option], "Enable">,
diff --git a/clang/lib/AST/Mangle.cpp b/clang/lib/AST/Mangle.cpp
index 9652fdbc4e125..ed4c1b2dd2096 100644
--- a/clang/lib/AST/Mangle.cpp
+++ b/clang/lib/AST/Mangle.cpp
@@ -32,7 +32,7 @@ using namespace clang;
 void clang::mangleObjCMethodName(raw_ostream &OS, bool includePrefixByte,
                                  bool isInstanceMethod, StringRef ClassName,
                                  std::optional<StringRef> CategoryName,
-                                 StringRef MethodName) {
+                                 StringRef MethodName, bool isThunk) {
   // \01+[ContainerName(CategoryName) SelectorName]
   if (includePrefixByte)
     OS << "\01";
@@ -40,12 +40,13 @@ void clang::mangleObjCMethodName(raw_ostream &OS, bool includePrefixByte,
   OS << '[';
   OS << ClassName;
   if (CategoryName)
-    OS << "(" << *CategoryName << ")";
+    OS << "(" << CategoryName.value() << ")";
   OS << " ";
   OS << MethodName;
   OS << ']';
+  if (!isThunk)
+    OS << "_inner";
 }
-
 // FIXME: For blocks we currently mimic GCC's mangling scheme, which leaves
 // much to be desired. Come up with a better mangling scheme.
 
@@ -345,7 +346,8 @@ void MangleContext::mangleBlock(const DeclContext *DC, const BlockDecl *BD,
 void MangleContext::mangleObjCMethodName(const ObjCMethodDecl *MD,
                                          raw_ostream &OS,
                                          bool includePrefixByte,
-                                         bool includeCategoryNamespace) const {
+                                         bool includeCategoryNamespace,
+                                         bool isThunk) const {
   if (getASTContext().getLangOpts().ObjCRuntime.isGNUFamily()) {
     // This is the mangling we've always used on the GNU runtimes, but it
     // has obvious collisions in the face of underscores within class
@@ -398,7 +400,7 @@ void MangleContext::mangleObjCMethodName(const ObjCMethodDecl *MD,
   llvm::raw_string_ostream MethodNameOS(MethodName);
   MD->getSelector().print(MethodNameOS);
   clang::mangleObjCMethodName(OS, includePrefixByte, MD->isInstanceMethod(),
-                              ClassName, CategoryName, MethodName);
+                              ClassName, CategoryName, MethodName, isThunk);
 }
 
 void MangleContext::mangleObjCMethodNameAsSourceName(const ObjCMethodDecl *MD,
diff --git a/clang/lib/CodeGen/CGCall.cpp b/clang/lib/CodeGen/CGCall.cpp
index c7fbbbc6fd40d..0438e5da6fad2 100644
--- a/clang/lib/CodeGen/CGCall.cpp
+++ b/clang/lib/CodeGen/CGCall.cpp
@@ -2771,6 +2771,18 @@ void CodeGenModule::ConstructAttributeList(StringRef Name,
 
     ArgAttrs[IRArgs.first] = llvm::AttributeSet::get(getLLVMContext(), Attrs);
   }
+  // Direct method prologue should not contain nil check anymore.
+  // As a result, we can set `self` to be NonNull to prepare for further
+  // optimizations.
+  if (getLangOpts().ObjCEmitNilCheckThunk && TargetDecl) {
+    auto OMD = dyn_cast<ObjCMethodDecl>(TargetDecl);
+    bool isDirect = OMD && OMD->isDirectMethod();
+    if (isDirect && !IsThunk) {
+      auto IRArgs = IRFunctionArgs.getIRArgs(0);
+      ArgAttrs[IRArgs.first] = ArgAttrs[IRArgs.first].addAttribute(
+          getLLVMContext(), llvm::Attribute::NonNull);
+    }
+  }
 
   unsigned ArgNo = 0;
   for (CGFunctionInfo::const_arg_iterator I = FI.arg_begin(),
@@ -3466,7 +3478,13 @@ void CodeGenFunction::EmitFunctionProlog(const CGFunctionInfo &FI,
     }
   }
 
-  if (getTarget().getCXXABI().areArgsDestroyedLeftToRightInCallee()) {
+  if (InnerFn) {
+    // Don't emit any arg except for `self` if we are in a thunk function.
+    // We still need self for nil check, other arguments aren't used in this
+    // function and thus is not needed. Avoid emitting them also prevents
+    // accidental release/retain.
+    EmitParmDecl(*Args[0], ArgVals[0], 1);
+  } else if (getTarget().getCXXABI().areArgsDestroyedLeftToRightInCallee()) {
     for (int I = Args.size() - 1; I >= 0; --I)
       EmitParmDecl(*Args[I], ArgVals[I], I + 1);
   } else {
diff --git a/clang/lib/CodeGen/CGObjC.cpp b/clang/lib/CodeGen/CGObjC.cpp
index 27c7c2fa9cba1..a6ddf37872da8 100644
--- a/clang/lib/CodeGen/CGObjC.cpp
+++ b/clang/lib/CodeGen/CGObjC.cpp
@@ -756,12 +756,13 @@ void CodeGenFunction::StartObjCMethod(const ObjCMethodDecl *OMD,
   if (OMD->hasAttr<NoDebugAttr>())
     DebugInfo = nullptr; // disable debug info indefinitely for this function
 
-  llvm::Function *Fn = CGM.getObjCRuntime().GenerateMethod(OMD, CD);
+  bool isInner = CGM.shouldHaveNilCheckThunk(OMD) && !InnerFn;
+  llvm::Function *Fn = CGM.getObjCRuntime().GenerateMethod(OMD, CD, !isInner);
 
   const CGFunctionInfo &FI = CGM.getTypes().arrangeObjCMethodDeclaration(OMD);
   if (OMD->isDirectMethod()) {
     Fn->setVisibility(llvm::Function::HiddenVisibility);
-    CGM.SetLLVMFunctionAttributes(OMD, FI, Fn, /*IsThunk=*/false);
+    CGM.SetLLVMFunctionAttributes(OMD, FI, Fn, /*IsThunk=*/InnerFn);
     CGM.SetLLVMFunctionAttributesForDefinition(OMD, Fn);
   } else {
     CGM.SetInternalFunctionAttributes(OMD, Fn, FI);
@@ -780,11 +781,21 @@ 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);
+    if (CGM.getLangOpts().ObjCRuntime.isNeXTFamily()) {
+      // Having `InnerFn` indicates that we are generating a nil check thunk.
+      // In that case our job is done here.
+      if (InnerFn)
+        return;
+      if (CGM.shouldHaveNilCheckThunk(OMD))
+        // Go generate  a nil check thunk around `Fn`
+        CodeGenFunction(CGM, /*InnerFn=*/Fn).GenerateObjCDirectThunk(OMD, CD);
+      else
+        CGM.getObjCRuntime().GenerateObjCDirectNilCheck(*this, OMD, CD);
+      CGM.getObjCRuntime().GenerateCmdIfNecessary(*this, OMD);
+    } else {
+      // For GNU family, since GNU Step2 also supports direct methods now.
+      CGM.getObjCRuntime().GenerateDirectMethodPrologue(*this, Fn, OMD, CD);
+    }
   }
 
   // In ARC, certain methods get an extra cleanup.
@@ -1637,6 +1648,44 @@ void CodeGenFunction::GenerateObjCSetter(ObjCImplementationDecl *IMP,
   FinishFunction(OMD->getEndLoc());
 }
 
+void CodeGenFunction::GenerateObjCDirectThunk(const ObjCMethodDecl *OMD,
+                                              const ObjCContainerDecl *CD) {
+  assert(InnerFn && CGM.shouldHaveNilCheckThunk(OMD) &&
+         "Should only generate wrapper when the flag is set.");
+  StartObjCMethod(OMD, CD);
+
+  // Manually pop all the clean up that doesn't need to happen in the outer
+  // function. InnerFn will do this for us.
+  while (EHStack.stable_begin() != PrologueCleanupDepth)
+    EHStack.popCleanup();
+
+  // Generate a nil check.
+  CGM.getObjCRuntime().GenerateObjCDirectNilCheck(*this, OMD, CD);
+  // Call the InnerFn and pass the return value
+  SmallVector<llvm::Value *> Args(CurFn->arg_size());
+  std::transform(CurFn->arg_begin(), CurFn->arg_end(), Args.begin(),
+                 [](llvm::Argument &arg) { return &arg; });
+
+  // This will be optimized into a tail call.
+  auto *CallInst = EmitCallOrInvoke(InnerFn, Args);
+  // Preserve the inner function's attributes to the call instruction.
+  CallInst->setAttributes(InnerFn->getAttributes());
+  llvm::Value *RetVal = CallInst;
+
+  // If `AutoreleaseResult` is set, the return value is not void.
+  if (AutoreleaseResult)
+    RetVal = EmitARCRetainAutoreleasedReturnValue(RetVal);
+
+  // This excessive store is totally unnecessary.
+  // But `FinishFunction` really wants us to store the result so it can
+  // clean up the function properly.
+  // The unnecessary store-load of the ret value will be optimized out anyway.
+  if (!CurFn->getReturnType()->isVoidTy())
+    Builder.CreateStore(RetVal, ReturnValue);
+
+  // Nil check's end location is the function's start location.
+  FinishFunction(OMD->getBeginLoc());
+}
 namespace {
   struct DestroyIvar final : EHScopeStack::Cleanup {
   private:
diff --git a/clang/lib/CodeGen/CGObjCGNU.cpp b/clang/lib/CodeGen/CGObjCGNU.cpp
index 33f6b0470061f..840ff627941b6 100644
--- a/clang/lib/CodeGen/CGObjCGNU.cpp
+++ b/clang/lib/CodeGen/CGObjCGNU.cpp
@@ -594,8 +594,23 @@ class CGObjCGNU : public CGObjCRuntime {
   }
   llvm::Constant *GetEHType(QualType T) override;
 
+  void GenerateObjCDirectNilCheck(CodeGenFunction &CGF,
+                                  const ObjCMethodDecl *OMD,
+                                  const ObjCContainerDecl *CD) override {
+    // GNU runtime doesn't support nil check thunks at this time
+  };
+  void GenerateCmdIfNecessary(CodeGenFunction &CGF,
+                              const ObjCMethodDecl *OMD) override {
+    // GNU runtime doesn't support nil check thunks at this time
+  }
+  llvm::Function *GenerateMethod(const ObjCMethodDecl *OMD,
+                                 const ObjCContainerDecl *CD,
+                                 bool isThunk) override {
+    // isThunk is irrelevent for GNU.
+    return GenerateMethod(OMD, CD);
+  };
   llvm::Function *GenerateMethod(const ObjCMethodDecl *OMD,
-                                 const ObjCContainerDecl *CD) override;
+                                 const ObjCContainerDecl *CD);
 
   // Map to unify direct method definitions.
   llvm::DenseMap<const ObjCMethodDecl *, llvm::Function *>
diff --git a/clang/lib/CodeGen/CGObjCMac.cpp b/clang/lib/CodeGen/CGObjCMac.cpp
index 1c23a8b4db918..62739beeaa109 100644
--- a/clang/lib/CodeGen/CGObjCMac.cpp
+++ b/clang/lib/CodeGen/CGObjCMac.cpp
@@ -1049,16 +1049,26 @@ class CGObjCCommonMac : public CodeGen::CGObjCRuntime {
   ConstantAddress GenerateConstantString(const StringLiteral *SL) override;
   ConstantAddress GenerateConstantNSString(const StringLiteral *SL);
 
-  llvm::Function *
-  GenerateMethod(const ObjCMethodDecl *OMD,
-                 const ObjCContainerDecl *CD = nullptr) override;
+  llvm::Function *GenerateMethod(const ObjCMethodDecl *OMD,
+                                 const ObjCContainerDecl *CD = nullptr,
+                                 bool isThunk = true) override;
 
   llvm::Function *GenerateDirectMethod(const ObjCMethodDecl *OMD,
-                                       const ObjCContainerDecl *CD);
+                                       const ObjCContainerDecl *CD,
+                                       bool isThunk);
 
   void GenerateDirectMethodPrologue(CodeGenFunction &CGF, llvm::Function *Fn,
                                     const ObjCMethodDecl *OMD,
                                     const ObjCContainerDecl *CD) override;
+  /// We split `GenerateDirectMethodPrologue` into the following two functions.
+  /// The motivation is that nil check thunk function does the nil check, it
+  /// definitely doesn't need _cmd.
+  void GenerateObjCDirectNilCheck(CodeGenFunction &CGF,
+                                  const ObjCMethodDecl *OMD,
+                                  const ObjCContainerDecl *CD) override;
+  /// The inner function can decide if it needs `_cmd` for itself.
+  void GenerateCmdIfNecessary(CodeGenFunction &CGF,
+                              const ObjCMethodDecl *OMD) override;
 
   void GenerateProtocol(const ObjCProtocolDecl *PD) override;
 
@@ -2078,7 +2088,7 @@ CodeGen::RValue CGObjCCommonMac::EmitMessageSend(
   llvm::FunctionCallee Fn = nullptr;
   if (Method && Method->isDirectMethod()) {
     assert(!IsSuper);
-    Fn = GenerateDirectMethod(Method, Method->getClassInterface());
+    Fn = GenerateDirectMethod(Method, Method->getClassInterface(), /*isThunk=*/true);
     // Direct methods will synthesize the proper `_cmd` internally,
     // so just don't bother with setting the `_cmd` argument.
     RequiresSelValue = false;
@@ -2102,10 +2112,6 @@ CodeGen::RValue CGObjCCommonMac::EmitMessageSend(
                         : ObjCTypes.getSendFn(IsSuper);
   }
 
-  // Cast function to proper signature
-  llvm::Constant *BitcastFn = cast<llvm::Constant>(
-      CGF.Builder.CreateBitCast(Fn.getCallee(), MSI.MessengerType));
-
   // We don't need to emit a null check to zero out an indirect result if the
   // result is ignored.
   if (Return.isUnused())
@@ -2115,6 +2121,16 @@ CodeGen::RValue CGObjCCommonMac::EmitMessageSend(
   if (!RequiresNullCheck && Method && Method->hasParamDestroyedInCallee())
     RequiresNullCheck = true;
 
+  // `RequiresNullCheck` will do the null check at the caller site.
+  // In that case, a direct instance method can skip the null check and call the
+  // inner function instead.
+  if (CGM.shouldHaveNilCheckThunk(Method))
+    if (RequiresNullCheck || !ReceiverCanBeNull)
+      Fn = GenerateDirectMethod(Method, Method->getClassInterface(),
+                                /*isThunk=*/false);
+  // Cast function to proper signature
+  llvm::Constant *BitcastFn = cast<llvm::Constant>(
+      CGF.Builder.CreateBitCast(Fn.getCallee(), MSI.MessengerType));
   NullReturnState nullReturn;
   if (RequiresNullCheck) {
     nullReturn.init(CGF, Arg0);
@@ -3841,11 +3857,12 @@ CGObjCMac::emitMethodList(Twine name, MethodListType MLT,
 }
 
 llvm::Function *CGObjCCommonMac::GenerateMethod(const ObjCMethodDecl *OMD,
-                                                const ObjCContainerDecl *CD) {
+                                                const ObjCContainerDecl *CD,
+                                                bool isThunk) {
   llvm::Function *Method;
 
   if (OMD->isDirectMethod()) {
-    Method = GenerateDirectMethod(OMD, CD);
+    Method = GenerateDirectMethod(OMD, CD, isThunk);
   } else {
     auto Name = getSymbolNameForMethod(OMD);
 
@@ -3861,14 +3878,13 @@ llvm::Function *CGObjCCommonMac::GenerateMethod(const ObjCMethodDecl *OMD,
   return Method;
 }
 
-llvm::Function *
-CGObjCCommonMac::GenerateDirectMethod(const ObjCMethodDecl *OMD,
-                                      const ObjCContainerDecl *CD) {
+llvm::Function *CGObjCCommonMac::GenerateDirectMethod(
+    const ObjCMethodDecl *OMD, const ObjCContainerDecl *CD, bool isThunk) {
   auto *COMD = OMD->getCanonicalDecl();
   auto I = DirectMethodDefinitions.find(COMD);
   llvm::Function *OldFn = nullptr, *Fn = nullptr;
 
-  if (I != DirectMethodDefinitions.end()) {
+  if (isThunk && I != DirectMethodDefinitions.end()) {
     // Objective-C allows for the declaration and implementation types
     // to differ slightly.
     //
@@ -3897,16 +3913,107 @@ CGObjCCommonMac::GenerateDirectMethod(const ObjCMethodDecl *OMD,
     // Replace the cached function in the map.
     I->second = 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 Name =
+        getSymbolNameForMethod(OMD, /*include category*/ false, isThunk);
+    // Non-thunk functions are not cached and may be repeatedly created.
+    // Therefore, we try to find it before we create one.
+    Fn = CGM.getModule().getFunction(Name);
+    if (!Fn)
+      Fn = llvm::Function::Create(MethodTy, llvm::GlobalValue::ExternalLinkage,
+                                  Name, &CGM.getModule());
+    if (isThunk)
+      DirectMethodDefinitions.insert(std::make_pair(COMD, Fn));
   }
 
   return Fn;
 }
 
+void CGObjCCommonMac::GenerateObjCDirectNilCheck(CodeGenFunction &CGF,
+                                                 const ObjCMethodDecl *OMD,
+                                                 const ObjCContainerDecl *CD) {
+  auto &Builder = CGF.Builder;
+  bool ReceiverCanBeNull = true;
+  auto selfAddr = CGF.GetAddrOfLocalVar(OMD->getSelfDecl());
+  auto selfValue = Builder.CreateLoad(selfAddr);
+
+  // Generate:
+  //
+  // /* for class methods only to force class lazy initialization */
+  // self = [self self];
+  //
+  // /* unless the receiver is never NULL */
+  // if (self == nil) {
+  //     return (ReturnType){ };
+  // }
+
+  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
+    // method which always receives an initialized `self`, or it might have just
+    // forced initialization on its own.
+    //
+    // 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);
+
+    // Nullable `Class` expressions cannot be messaged with a direct method
+    // so the only reason why the receive can be null would be because
+    // of weak linking.
+    ReceiverCanBeNull = isWeakLinkedClass(OID);
+  }
+
+  if (ReceiverCanBeNull) {
+    llvm::BasicBlock *SelfIsNilBlock =
+        CGF.createBasicBlock("objc_direct_method.self_is_nil");
+    llvm::BasicBlock *ContBlock =
+        CGF.createBasicBlock("objc_direct_method.cont");
+
+    // if (self == nil) {
+    auto selfTy = cast<llvm::PointerType>(selfValue->getType());
+    auto Zero = llvm::ConstantPointerNull::get(selfTy);
+
+    llvm::MDBuilder MDHelper(CGM.getLLVMContext());
+    Builder.CreateCondBr(Builder.CreateICmpEQ(selfValue, Zero), SelfIsNilBlock,
+                         ContBlock, MDHelper.createBranchWeights(1, 1 << 20));
+
+    CGF.EmitBlock(SelfIsNilBlock);
+
+    //   return (ReturnType){ };
+    auto retTy = OMD->getReturnType();
+    Builder.SetInsertPoint(SelfIsNilBlock);
+    if (!retTy->isVoidType()) {
+      CGF.EmitNullInitialization(CGF.ReturnValue, retTy);
+    }
+    CGF.EmitBranchThroughCleanup(CGF.ReturnBlock);
+    // }
+
+    CGF.EmitBlock(ContBlock);
+    Builder.SetInsertPoint(ContBlock);
+  }
+}
+void CGObjCCommonMac::GenerateCmdIfNecessary(CodeGenFunction &CGF,
+                                             const ObjCMethodDecl *OMD) {
+  // only synthesize _cmd if it's referenced
+  if (OMD->getCmdDecl()->isUsed()) {
+    // `_cmd` is not a parameter to direct methods, so storage must be
+    // explicitly declared for it.
+    CGF.EmitVarDecl(*OMD->getCmdDecl());
+    CGF.Builder.CreateStore(GetSelector(CGF, OMD),
+                            CGF.GetAddrOfLocalVar(OMD->getCmdDecl()));
+  }
+}
+
 void CGObjCCommonMac::GenerateDirectMethodPrologue(
     CodeGenFunction &CGF, llvm::Function *Fn, const ObjCMethodDecl *OMD,
     const ObjCContainerDecl *CD) {
diff --git a/clang/lib/CodeGen/CGObjCRuntime.cpp b/clang/lib/CodeGen/CGObjCRuntime.cpp
index dfb0fd14d93ac..a0cc65e7eb211 100644
--- a/clang/lib/CodeGen/CGObjCRuntime.cpp
+++ b/clang/lib/CodeGen/CGObjCRuntime.cpp
@@ -468,11 +468,12 @@ clang::CodeGen::emitObjCProtocolObject(CodeGenModule &CGM,
 }
 
 std::string CGObjCRuntime::getSymbolNameForMethod(const ObjCMethodDecl *OMD,
-                                                  bool includeCategoryName) {
+                                                  bool includeCategoryName,
+                                                  bool isThunk) {
   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=*/true, includeCategoryName, isThunk);
   return buffer;
 }
diff --git a/clang/lib/CodeGen/CGObjCRuntime.h b/clang/lib/CodeGen/CGObjCRuntime.h
index 72997bf6348ae..5bdea39f618bd 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 isThunk = true);
 
   /// Generate the function required to register all Objective-C components in
   /// this compilation unit with the runtime library.
@@ -223,7 +224,8 @@ class CGObjCRuntime {
   // should also be generating the loads of the parameters, as the runtime
   // should have full control over how parameters are passed.
   virtual llvm::Function *GenerateMethod(const ObjCMethodDecl *OMD,
-                                         const ObjCContainerDecl *CD) = 0;
+                                         const ObjCContainerDecl *CD,
+                                         bool isThunk = true) = 0;
 
   /// Generates prologue for direct Objective-C Methods.
   virtual void GenerateDirectMethodPrologue(CodeGenFunction &CGF,
@@ -231,6 +233,11 @@ class CGObjCRuntime {
                                             const ObjCMethodDecl *OMD,
                                             const ObjCContainerDecl *CD) = 0;
 
+  virtual void GenerateObjCDirectNilCheck(CodeGenFunction &CGF,
+                                          const ObjCMethodDecl *OMD,
+                                          const ObjCContainerDecl *CD) = 0;
+  virtual void GenerateCmdIfNecessary(CodeGenFunction &CGF,
+                                      const ObjCMethodDecl *OMD) = 0;
   /// Return the runtime function for getting properties.
   virtual llvm::FunctionCallee GetPropertyGetFunction() = 0;
 
diff --git a/clang/lib/CodeGen/CodeGenFunction.cpp b/clang/lib/CodeGen/CodeGenFunction.cpp
index 4d29ceace646f..aa26c228cadf6 100644
--- a/clang/lib/CodeGen/CodeGenFunction.cpp
+++ b/clang/lib/CodeGen/CodeGenFunction.cpp
@@ -74,6 +74,12 @@ static bool shouldEmitLifetimeMarkers(const CodeGenOptions &CGOpts,
   return CGOpts.OptimizationLevel != 0;
 }
 
+CodeGenFunction::CodeGenFunction(CodeGenModule &cgm, llvm::Function *Inner,
+                                 bool suppressNewContext)
+    : CodeGenFunction(cgm, suppressNewContext) {
+  InnerFn = Inner;
+  assert(InnerFn && "The inner function provided should not be null");
+}
 CodeGenFunction::CodeGenFunction(CodeGenModule &cgm, bool suppressNewContext)
     : CodeGenTypeCache(cgm), CGM(cgm), Target(cgm.getTarget()),
       Builder(cgm, cgm.getModule().getContext(), llvm::ConstantFolder(),
diff --git a/clang/lib/CodeGen/CodeGenFunction.h b/clang/lib/CodeGen/CodeGenFunction.h
index 4c5e8a8a44926..8821ca174a542 100644
--- a/clang/lib/CodeGen/CodeGenFunction.h
+++ b/clang/lib/CodeGen/CodeGenFunction.h
@@ -1673,6 +1673,10 @@ class CodeGenFunction : public CodeGenTypeCache {
   }
   void markStmtMaybeUsed(const Stmt *S) { PGO.markStmtMaybeUsed(S); }
 
+  /// If `InnerFn` is set, this CGF generates a thunk function that does the nil
+  /// check before calling `InnerFn`. `InnerFn` has to be an objc_direct method.
+  llvm::Function* InnerFn = nullptr;
+
   /// Increment the profiler's counter for the given statement by \p StepV.
   /// If \p StepV is null, the default increment is 1.
   void incrementProfileCounter(const Stmt *S, llvm::Value *StepV = nullptr);
@@ -2166,6 +2170,8 @@ class CodeGenFunction : public CodeGenTypeCache {
 
 public:
   CodeGenFunction(CodeGenModule &cgm, bool suppressNewContext = false);
+  CodeGenFunction(CodeGenModule &cgm, llvm::Function *inner,
+                  bool suppressNewContext = false);
   ~CodeGenFunction();
 
   CodeGenTypes &getTypes() const { return CGM.getTypes(); }
@@ -2317,6 +2323,8 @@ class CodeGenFunction : public CodeGenTypeCache {
   void generateObjCSetterBody(const ObjCImplementationDecl *classImpl,
                               const ObjCPropertyImplDecl *propImpl,
                               llvm::Constant *AtomicHelperFn);
+  void GenerateObjCDirectThunk(const ObjCMethodDecl *OMD,
+                               const ObjCContainerDecl *CD);
 
   //===--------------------------------------------------------------------===//
   //                                  Block Bits
diff --git a/clang/lib/CodeGen/CodeGenModule.h b/clang/lib/CodeGen/CodeGenModule.h
index 59f400570fb7a..84439d08b3366 100644
--- a/clang/lib/CodeGen/CodeGenModule.h
+++ b/clang/lib/CodeGen/CodeGenModule.h
@@ -327,6 +327,14 @@ class CodeGenModule : public CodeGenTypeCache {
   void operator=(const CodeGenModule &) = delete;
 
 public:
+  // Returns true if the nil check thunk flag is turned on and the method is
+  // thunkable. A method is thunkable if it is a direct instance method that
+  // have a fixed number of arguments.
+  bool shouldHaveNilCheckThunk(const ObjCMethodDecl *OMD) const {
+    return getCodeGenOpts().ObjCEmitNilCheckThunk &&
+           getLangOpts().ObjCRuntime.isNeXTFamily() && OMD &&
+           OMD->canHaveNilCheckThunk();
+  }
   struct Structor {
     Structor()
         : Priority(0), LexOrder(~0u), Initializer(nullptr),
diff --git a/clang/lib/Driver/ToolChains/Clang.cpp b/clang/lib/Driver/ToolChains/Clang.cpp
index 762f8af886920..f1c2235cbcbfb 100644
--- a/clang/lib/Driver/ToolChains/Clang.cpp
+++ b/clang/lib/Driver/ToolChains/Clang.cpp
@@ -8131,6 +8131,9 @@ void Clang::ConstructJob(Compilation &C, const JobAction &JA,
       Input.getInputArg().renderAsInput(Args, CmdArgs);
   }
 
+  if (Args.hasArg(options::OPT_fobjc_emit_nil_check_thunk))
+    CmdArgs.push_back("-fobjc-emit-nil-check-thunk");
+
   if (D.CC1Main && !D.CCGenDiagnostics) {
     // Invoke the CC1 directly in this process
     C.addCommand(std::make_unique<CC1Command>(
diff --git a/clang/lib/Frontend/CompilerInvocation.cpp b/clang/lib/Frontend/CompilerInvocation.cpp
index 8ff62ae2552c3..ef0298471924b 100644
--- a/clang/lib/Frontend/CompilerInvocation.cpp
+++ b/clang/lib/Frontend/CompilerInvocation.cpp
@@ -1609,6 +1609,9 @@ void CompilerInvocationBase::GenerateCodeGenArgs(const CodeGenOptions &Opts,
   else if (!Opts.DirectAccessExternalData && LangOpts->PICLevel == 0)
     GenerateArg(Consumer, OPT_fno_direct_access_external_data);
 
+  if (Opts.ObjCEmitNilCheckThunk)
+    GenerateArg(Consumer, OPT_fobjc_emit_nil_check_thunk);
+
   std::optional<StringRef> DebugInfoVal;
   switch (Opts.DebugInfo) {
   case llvm::codegenoptions::DebugLineTablesOnly:
@@ -1942,6 +1945,8 @@ bool CompilerInvocation::ParseCodeGenArgs(CodeGenOptions &Opts, ArgList &Args,
       Opts.setDebugInfo(llvm::codegenoptions::LimitedDebugInfo);
   }
 
+  Opts.ObjCEmitNilCheckThunk = Args.hasArg(OPT_fobjc_emit_nil_check_thunk);
+
   for (const auto &Arg : Args.getAllArgValues(OPT_fdebug_prefix_map_EQ)) {
     auto Split = StringRef(Arg).split('=');
     Opts.DebugPrefixMap.emplace_back(Split.first, Split.second);
@@ -4200,6 +4205,8 @@ bool CompilerInvocation::ParseLangArgs(LangOptions &Opts, ArgList &Args,
   Opts.ZOSExt =
       Args.hasFlag(OPT_fzos_extensions, OPT_fno_zos_extensions, T.isOSzOS());
 
+  Opts.ObjCEmitNilCheckThunk = Args.hasArg(OPT_fobjc_emit_nil_check_thunk);
+
   Opts.Blocks = Args.hasArg(OPT_fblocks) || (Opts.OpenCL
     && Opts.OpenCLVersion == 200);
 
diff --git a/clang/test/CodeGenObjC/direct-method-nil-check-linkedlist.m b/clang/test/CodeGenObjC/direct-method-nil-check-linkedlist.m
new file mode 100644
index 0000000000000..6dcd9242e8a23
--- /dev/null
+++ b/clang/test/CodeGenObjC/direct-method-nil-check-linkedlist.m
@@ -0,0 +1,138 @@
+// REQUIRES: system-darwin
+
+// RUN: mkdir -p %t
+
+// RUN: %clang -fobjc-emit-nil-check-thunk      \
+// RUN:   -target arm64-apple-darwin -fobjc-arc \
+// RUN:   -O2 -framework Foundation %s -o %t/thunk-linkedlist
+
+// RUN: %t/thunk-linkedlist 8 7 6 | FileCheck %s --check-prefix=CHECK-EXE
+#import <Foundation/Foundation.h>
+
+ at interface LinkedList: NSObject
+ at property(direct, readonly, nonatomic) int v;
+ at property(direct, strong, nonatomic) LinkedList* next;
+ at property(direct, readonly, nonatomic) int instanceId;
+ at property(strong, nonatomic, direct) void ( ^ printBlock )( void );
+ at property(class) int numInstances;
+
+// Prints instantceId before dealloc
+- (void) dealloc;
+- (instancetype)initWithV:(int)v Next:(id)next __attribute__((objc_direct));
+- (instancetype)clone __attribute__((objc_direct));
+- (void)print __attribute__((objc_direct));
+- (instancetype) reverseWithPrev:(id) prev __attribute__((objc_direct));
+- (void) printWithFormat:(NSString*)format, ... NS_FORMAT_FUNCTION(1, 2) __attribute__((objc_direct));
+- (int) size __attribute__((objc_direct));
+- (int) sum __attribute__((objc_direct));
+- (double) avg __attribute__((objc_direct));
+ at end
+
+ at implementation LinkedList
+ at dynamic numInstances;
+static int numInstances=0;
+
+- (void) dealloc {
+  printf("Dealloc id: %d\n", self.instanceId);
+}
+
+- (instancetype)initWithV:(int)v Next:(id)next{
+  if (self = [super init]) {
+    _v = v;
+    _next = next;
+    _instanceId = numInstances;
+    LinkedList* __weak weakSelf = self;
+    _printBlock = ^void(void) { [weakSelf print]; };
+    numInstances++;
+    printf("Alloc id: %d, v: %d\n", self.instanceId, self.v);
+  }
+  return self;
+}
+- (instancetype) clone {
+  return [[LinkedList alloc] initWithV:self.v Next:[self.next clone]];
+}
+
+- (void) print {
+  printf("id: %d, v: %d\n", self.instanceId, self.v);
+  [self.next print];
+}
+
+- (void) printWithFormat:(NSString*)format, ...{
+  [self print];
+  NSString *description;
+  if ([format length] > 0) {
+      va_list args;
+      va_start(args, format);
+      description = [[NSString alloc] initWithFormat:(id)format arguments:args];
+      va_end(args);
+  }
+  printf("%s", description.UTF8String);
+}
+
+- (LinkedList*) reverseWithPrev:(LinkedList*) prev{
+  LinkedList* newHead = (self.next == nil) ? self : [self.next reverseWithPrev:self];
+  self.next = prev;
+  return newHead;
+}
+
+- (int) size {
+  return 1 + [self.next size];
+}
+- (int) sum {
+  return self.v + [self.next sum];
+}
+- (double) avg {
+  return (double)[self sum] / (double)[self size];
+}
+ at end
+
+int main(int argc, char** argv) { // argv = ["8", "7", "6"]
+ at autoreleasepool {
+  // CHECK-EXE: Alloc id: 0, v: 7
+  // CHECK-EXE: Alloc id: 1, v: 8
+  LinkedList* ll = [[LinkedList alloc] initWithV:atoi(argv[1]) Next:[[LinkedList alloc] initWithV:atoi(argv[2]) Next:nil]];
+  // CHECK-EXE: Alloc id: 2, v: 6
+  ll.next.next = [[LinkedList alloc] initWithV:atoi(argv[3]) Next:nil];
+  // CHECK-EXE: id: 1, v: 8
+  // CHECK-EXE: id: 0, v: 7
+  // CHECK-EXE: id: 2, v: 6
+  [ll print];
+
+  // Because of the recursive clone, the tail is allocated first.
+  // CHECK-EXE: Alloc id: 3, v: 6
+  // CHECK-EXE: Alloc id: 4, v: 7
+  // CHECK-EXE: Alloc id: 5, v: 8
+  LinkedList* cloned = [ll clone];
+
+  // CHECK-EXE: id: 5, v: 8
+  // CHECK-EXE: id: 4, v: 7
+  // CHECK-EXE: id: 3, v: 6
+  [cloned print];
+
+  // CHECK-EXE: id: 5, v: 8
+  // CHECK-EXE: id: 4, v: 7
+  // CHECK-EXE: id: 3, v: 6
+  cloned.printBlock();
+
+  // CHECK-EXE: id: 5, v: 8
+  // CHECK-EXE: id: 4, v: 7
+  // CHECK-EXE: id: 3, v: 6
+  // CHECK-EXE: Hello world, I'm cloned, I have 3 elements
+  [cloned printWithFormat:@"Hello world, I'm cloned, I have %d elements\n", [cloned size]];
+
+  ll = [ll reverseWithPrev:nil];
+  // CHECK-EXE: id: 2, v: 6
+  // CHECK-EXE: id: 0, v: 7
+  // CHECK-EXE: id: 1, v: 8
+  [ll print];
+
+  // All objects should be deallocated.
+  // CHECK-EXE: Dealloc
+  // CHECK-EXE: Dealloc
+  // CHECK-EXE: Dealloc
+  // CHECK-EXE: Dealloc
+  // CHECK-EXE: Dealloc
+  // CHECK-EXE: Dealloc
+}
+  return 0;
+}
diff --git a/clang/test/CodeGenObjC/direct-method-nil-check-thunk-arc.m b/clang/test/CodeGenObjC/direct-method-nil-check-thunk-arc.m
new file mode 100644
index 0000000000000..190099b0117c6
--- /dev/null
+++ b/clang/test/CodeGenObjC/direct-method-nil-check-thunk-arc.m
@@ -0,0 +1,153 @@
+// REQUIRES: system-darwin
+
+// RUN: mkdir -p %t
+
+// RUN: %clang -fobjc-emit-nil-check-thunk -S -emit-llvm    \
+// RUN:   -target arm64-apple-darwin -O0 %s -o - -fobjc-arc \
+// RUN:   | FileCheck %s
+
+// RUN: %clang -fobjc-emit-nil-check-thunk -S -emit-llvm   \
+// RUN:   -target arm64-apple-darwin -O0 %s -o -           \
+// RUN:   | FileCheck --check-prefix=NO-ARC %s
+
+// RUN: %clang -fobjc-emit-nil-check-thunk      \
+// RUN:   -target arm64-apple-darwin -fobjc-arc \
+// RUN:   -O2 -framework Foundation %s -o %t/shape
+
+// RUN: %t/shape 1 2 3 4 | FileCheck %s --check-prefix=CHECK-EXE
+
+// NO-ARC-NOT: autoreleaseReturnValue
+// NO-ARC-NOT: retainAutoreleasedReturnValue
+// NO-ARC-NOT: asm sideeffect "mov\09fp, fp\09\09// marker for objc_retainAutoreleaseReturnValue"
+
+#import <Foundation/Foundation.h>
+#include "math.h"
+
+ at interface Shape: NSObject
+ at property(direct, readonly) int x;
+ at property(direct, readonly) int y;
+ at property(direct) Shape* innerShape;
+ at property(class) int numInstances;
+ at property(direct) int instanceId;
+- (void) dealloc;
+- (instancetype)initWithX:(int)x Y:(int)y __attribute__((objc_direct, , visibility("default")));
+- (instancetype)initDefault __attribute__((objc_direct, , visibility("default")));
+- (double) distanceFrom: (Shape *) __attribute__((ns_consumed)) s __attribute__((objc_direct, , visibility("default")));
++ (Shape *) default __attribute__((objc_direct, , visibility("default")));
+- (instancetype) clone __attribute__((objc_direct, , visibility("default")));
+ at end
+
+ at implementation Shape
+ at dynamic numInstances;
+static int numInstances=0;
+
+- (void) dealloc {
+  printf("Dealloc %d\n", self.instanceId);
+}
+- (instancetype)initWithX:(int)x Y:(int)y  {
+  if (self = [super init]) {
+    _x = x;
+    _y = y;
+    _innerShape = nil;
+    _instanceId = numInstances;
+    printf("Alloc %d\n", _instanceId);
+    numInstances++;
+  }
+  return self;
+}
+
+// Thunk function should not release anything.
+// CHECK-LABEL: define hidden ptr @"\01-[Shape initDefault]"
+// CHECK-NOT: call void @llvm.objc.storeStrong
+// CHECK-LABEL: }
+- (instancetype)initDefault {
+  return [self initWithX:0 Y:0];
+}
+
+// CHECK-LABEL: define hidden ptr @"\01+[Shape default]"
+// CHECK: [[SHAPE:%.*]] = call ptr @"\01-[Shape initDefault]"
+// CHECK-NEXT: [[AUTORELEASE_SHAPE:%.*]] = tail call ptr @llvm.objc.autoreleaseReturnValue(ptr [[SHAPE]])
+// CHECK-NEXT: ret ptr [[AUTORELEASE_SHAPE]]
+// CHECK-LABEL: }
++ (Shape*) default {
+  return [[Shape alloc] initDefault];
+}
+
+// CHECK-LABEL: define {{.*}} @"\01-[Shape clone]_inner"
+// CHECK: [[CALL_INIT:%.*]] = call ptr @"\01-[Shape initWithX:Y:]"
+// CHECK-NEXT: [[AUTORELEASE_CLONE:%.*]] = tail call ptr @llvm.objc.autoreleaseReturnValue(ptr [[CALL_INIT]])
+// CHECK-NEXT: ret ptr [[AUTORELEASE_CLONE]]
+// CHECK-LABEL: }
+
+// CHECK-LABEL: define {{.*}} @"\01-[Shape clone]"
+// CHECK: [[CALL_INNER:%.*]] = call ptr @"\01-[Shape clone]_inner"
+// CHECK-NEXT: call void asm sideeffect "mov\09fp, fp\09\09// marker for objc_retainAutoreleaseReturnValue", ""()
+// CHECK-NEXT: [[RETAINED:%.*]] = call ptr @llvm.objc.retainAutoreleasedReturnValue(ptr [[CALL_INNER]])
+// CHECK-NEXT: store ptr [[RETAINED]], ptr [[RETADDR:%.*]]
+// CHECK: [[RET:%.*]] = load ptr, ptr [[RETADDR]]
+// CHECK-NEXT: [[AUTORELEASE_RET:%.*]] = tail call ptr @llvm.objc.autoreleaseReturnValue(ptr [[RET]])
+// CHECK-NEXT: ret ptr [[AUTORELEASE_RET]]
+// CHECK-LABEL: }
+- (instancetype) clone {
+  return [[Shape alloc] initWithX:self.x Y:self.y];
+}
+
+// InnerFn will release the value since it is "consumed".
+// CHECK: define hidden double @"\01-[Shape distanceFrom:]_inner"(ptr noundef nonnull %{{.*}}, ptr noundef [[S:%.*]]) #0 {
+// CHECK: {{%.*}} = alloca ptr
+// CHECK: [[S_ADDR:%.*]] = alloca ptr
+// CHECK: store ptr [[S]], ptr [[S_ADDR]]
+// CHECK: call void @llvm.objc.storeStrong(ptr [[S_ADDR]], ptr null)
+
+// Thunk function should not release anything even with ns_consumed
+// CHECK-LABEL: define hidden double @"\01-[Shape distanceFrom:]"
+// CHECK-NOT: call void @llvm.objc.storeStrong
+// CHECK-LABEL: }
+- (double) distanceFrom:(Shape *) __attribute__((ns_consumed)) s __attribute__((objc_direct)) {
+  double dist = sqrt((s.x - self.x) * (s.x - self.x) + (s.y - self.y) * (s.y - self.y));
+  return dist;
+}
+ at end
+
+// CHECK-LABEL: define i32 @main
+int main(int argc, char** argv) { // argv = ["1", "2", "3", "4"]
+ at autoreleasepool {
+  // CHECK-EXE: Alloc
+  Shape* classDefault = [Shape default];
+  // CHECK-EXE-NEXT: Alloc
+  Shape* s = [[Shape alloc] initWithX:atoi(argv[0]) Y:atoi(argv[1])];
+  // CHECK-EXE-NEXT: Alloc
+  Shape* t = [[Shape alloc] initWithX:atoi(argv[2]) Y:atoi(argv[3])];
+  // CHECK-EXE-NEXT: Alloc
+  Shape* zero = [[Shape alloc] initDefault];
+  // CHECK-EXE-NEXT: Alloc
+  Shape* anotherDefault = [Shape default];
+
+  // CHECK: [[CALL_CLONE:%.*]] = call ptr @"\01-[Shape clone]"
+  // CHECK-NEXT: call void asm sideeffect "mov\09fp, fp\09\09// marker for objc_retainAutoreleaseReturnValue", ""()
+  // CHECK-NEXT: {{%.*}} = call ptr @llvm.objc.retainAutoreleasedReturnValue(ptr [[CALL_CLONE]])
+
+  // CHECK-EXE-NEXT: Alloc [[CLOND_ID:.*]]
+  Shape* cloned = [s clone];
+
+  Shape* null = nil;
+  // CHECK-EXE: Dist: 2.82
+  printf("Dist: %lf\n", [s distanceFrom:t]);
+  // CHECK-EXE-NEXT: Dist: 3.60
+  printf("Dist: %lf\n", [zero distanceFrom:t]);
+  // CHECK-EXE-NEXT: Dist: 3.60
+  printf("Dist: %lf\n", [classDefault distanceFrom:t]);
+  // CHECK-EXE-NEXT: Dist: 0.00
+  printf("Dist: %lf\n", [s distanceFrom:s]);
+  // CHECK-EXE-NEXT: Dist: 0.00
+  printf("Dist: %lf\n", [classDefault distanceFrom:anotherDefault]);
+  // CHECK-EXE-NEXT: Dist: 0.00
+  printf("Dist: %lf\n", [null distanceFrom:zero]);
+  // CHECK-EXE-NEXT: Dist: 0.00
+  printf("Dist: %lf\n", [s distanceFrom:cloned]);
+
+  // Cloned object should be released as well
+  // CHECK-EXE: Dealloc [[CLOND_ID]]
+}
+  return 0;
+}
diff --git a/clang/test/CodeGenObjC/direct-method-nil-check-thunk-consumed.m b/clang/test/CodeGenObjC/direct-method-nil-check-thunk-consumed.m
new file mode 100644
index 0000000000000..482cc2ebedc03
--- /dev/null
+++ b/clang/test/CodeGenObjC/direct-method-nil-check-thunk-consumed.m
@@ -0,0 +1,56 @@
+// REQUIRES: system-darwin
+
+// RUN: %clang -fobjc-emit-nil-check-thunk    \
+// RUN: -target arm64-apple-darwin -fobjc-arc \
+// RUN: -O0 -S -emit-llvm %s -o - | FileCheck %s
+
+#import <Foundation/Foundation.h>
+
+ at interface Shape: NSObject
+ at property(direct) int x;
+ at property(direct) int y;
+- (instancetype)initWithX:(int)x Y:(int)y __attribute__((objc_direct));
+- (void) move: (Shape *) __attribute__((ns_consumed)) s __attribute__((objc_direct));
++ (Shape*) default __attribute__((objc_direct));
+ at end
+
+ at implementation Shape
+- (instancetype)initWithX:(int)x Y:(int)y  {
+  if (self = [super init]) {
+    _x = x;
+    _y = y;
+  }
+  return self;
+}
+
+// Inner function should
+//  1. Call inner set (because we alreaday know self is not null)
+//  2. Call thunk get (because we don't know if s is null)
+//  3. Release s.
+// CHECK-LABEL: define hidden void @"\01-[Shape move:]_inner"
+// CHECK: {{.*}} = call i32 @"\01-[Shape x]"
+// CHECK: call void @"\01-[Shape setX:]_inner"
+// CHECK: {{.*}} = call i32 @"\01-[Shape y]"
+// CHECK: call void @"\01-[Shape setY:]_inner"
+// CHECK: call void @llvm.objc.storeStrong
+// CHECK-LABEL: }
+
+// Outer function should not release anything.
+// CHECK-LABEL: define hidden void @"\01-[Shape move:]"
+// CHECK-NOT: call void @llvm.objc.storeStrong
+// CHECK-LABEL: }
+- (void) move: (Shape *) s {
+  self.x = s.x;
+  self.y = s.y;
+}
+
++ (Shape*) default {
+  return [[Shape alloc] initWithX:1 Y:1];
+}
+ at end
+
+int main() {
+  Shape *s = [Shape default];
+  Shape *t = nil;
+  [t move:s];
+}
diff --git a/clang/test/CodeGenObjC/direct-method-nil-check-thunk-opt.m b/clang/test/CodeGenObjC/direct-method-nil-check-thunk-opt.m
new file mode 100644
index 0000000000000..fe4008bfa13e4
--- /dev/null
+++ b/clang/test/CodeGenObjC/direct-method-nil-check-thunk-opt.m
@@ -0,0 +1,68 @@
+// This file checks that certain nil checks can be removed from direct method calls.
+
+// RUN: %clang_cc1 -fobjc-emit-nil-check-thunk -O0 -emit-llvm -fobjc-arc -triple arm64-apple-darwin %s -o - | FileCheck %s
+// RUN: %clang_cc1 -fobjc-emit-nil-check-thunk -O2 -emit-llvm -fobjc-arc -triple arm64-apple-darwin %s -o - | FileCheck %s --check-prefixes=OPT
+
+__attribute__((objc_root_class))
+ at interface Root
+ at property(direct) int idx;
+- (int)privateLoadWithOffset:(int *)ptr __attribute__((objc_direct));
+ at end
+
+// Optimization is enabled because the source code is available, and the compiler can reason that some methods don't require nil checks.
+ at interface Fib : Root
+- (int)fibWithN:(int)n __attribute__((objc_direct));
+ at end
+
+ at implementation Fib
+// With optimization, the inner function call should be a tail call.
+// OPT-LABEL: define hidden i32 @"\01-[Fib fibWithN:]_inner"
+// OPT: {{.*}} tail call i32 @"\01-[Fib fibWithN:]_inner"
+
+// The inner function knows that self is non null so it can call the method without the nil check.
+// CHECK-LABEL: define hidden i32 @"\01-[Fib fibWithN:]_inner"
+// CHECK: {{.*}} call i32 @"\01-[Fib fibWithN:]_inner"
+// CHECK: {{.*}} call i32 @"\01-[Fib fibWithN:]_inner"
+
+// Thunk function calls the inner function as usual.
+// CHECK-LABEL: define hidden i32 @"\01-[Fib fibWithN:]"
+// CHECK: {{.*}} call i32 @"\01-[Fib fibWithN:]_inner"
+- (int)fibWithN:(int)n {
+  if (n <= 0) return 0;
+  if (n == 1) return 1;
+  return [self fibWithN:n-1] + [self fibWithN:n-2];
+}
+ at end
+
+ at interface SubRoot : Root
+ at property(direct) int val;
+
+- (int)calculateWithPtr:(int*)ptr __attribute__((objc_direct));
+- (int)privateMethod:(int)n __attribute__((objc_direct));
+ at end
+ at implementation SubRoot
+- (int)calculateWithPtr:(int*)ptr {
+  // For inner functions, it is trivial to reason that the receiver `self` can't be null
+  // CHECK-LABEL: define hidden i32 @"\01-[SubRoot calculateWithPtr:]_inner"
+  // CHECK: {{.*}} = call i32 @"\01-[SubRoot val]_inner"
+  // CHECK: call void @"\01-[SubRoot setVal:]_inner"
+  // CHECK: {{.*}} = call i32 @"\01-[Root privateLoadWithOffset:]_inner"
+  // CHECK: {{.*}} = call i32 @"\01-[SubRoot privateMethod:]_inner"
+  // CHECK: {{.*}} = call i32 @"\01-[Root idx]_inner"
+  // CHECK: call void @"\01-[Root setIdx:]_inner"
+  int ret = [self val];
+  [self setVal:*ptr];
+  ret += [self privateLoadWithOffset:ptr];
+  ret += [self privateMethod:ret];
+  ret += [self idx];
+  [self setIdx:ret];
+  return ret;
+}
+ at end
+
+// The thunk declarations don't exist since all calls to them are non null.
+// We trust that these symbols will be generated when the definition is available.
+// CHECK-LABEL: declare i32 @"\01-[Root privateLoadWithOffset:]_inner"(ptr, ptr)
+// CHECK-LABEL: declare i32 @"\01-[SubRoot privateMethod:]_inner"(ptr, i32)
+// CHECK-LABEL: declare i32 @"\01-[Root idx]_inner"(ptr)
+// CHECK-LABEL: declare void @"\01-[Root setIdx:]_inner"(ptr, i32)
diff --git a/clang/test/CodeGenObjC/direct-method-nil-check-thunk-vararg.m b/clang/test/CodeGenObjC/direct-method-nil-check-thunk-vararg.m
new file mode 100644
index 0000000000000..05ea75178f360
--- /dev/null
+++ b/clang/test/CodeGenObjC/direct-method-nil-check-thunk-vararg.m
@@ -0,0 +1,59 @@
+// RUN: %clang -fobjc-emit-nil-check-thunk      \
+// RUN:   -target arm64-apple-darwin -fobjc-arc \
+// RUN:   -O0 -S -emit-llvm %s -o - | FileCheck %s
+
+
+#include <stdio.h>
+#include <stdarg.h>
+__attribute__((objc_root_class))
+ at interface Root
+- (void)printWithFormat:(const char *)format, ... __attribute__((objc_direct, visibility("default")));
+- (void)vprintWithFormat:(const char *)format Args:(va_list) args __attribute__((objc_direct, visibility("default")));
+ at end
+
+ at implementation Root
+// CHECK-LABEL: define {{.*}} void @"\01-[Root printWithFormat:]"
+- (void)printWithFormat:(const char *)format, ... {
+  // Inner functions won't be called since var arg functions don't have a thunk.
+  // CHECK: call void (ptr, ptr, ...) @"\01-[Root printWithFormat:]"
+  // CHECK: call void (ptr, ptr, ...) @"\01-[Root printWithFormat:]"
+  [self printWithFormat:format, "Hello World"];
+  [self printWithFormat:format, "!", 1, 2.0];
+  va_list args;
+  // CHECK: call void @llvm.va_start
+  va_start(args, format);
+  // CHECK: call void @"\01-[Root vprintWithFormat:Args:]_inner"
+  [self vprintWithFormat:format Args:args];
+  // CHECK: call void @llvm.va_end
+  va_end(args);
+}
+// CHECK-NOT: <Root printWithFormat]_inner
+
+// CHECK-LABEL: define {{.*}} void @"\01-[Root vprintWithFormat:Args:]"
+// CHECK: call void @"\01-[Root vprintWithFormat:Args:]_inner"
+
+// CHECK-LABEL: define {{.*}} void @"\01-[Root vprintWithFormat:Args:]_inner"
+-(void)vprintWithFormat:(const char *)format Args:(va_list) args{
+  // CHECK: call void @"\01-[Root vprintWithFormat:Args:]_inner"
+  [self vprintWithFormat:format Args:args];
+  // CHECK: call i32 @vprintf
+  vprintf(format, args);
+}
+ at end
+
+void printRoot(Root* root, const char* format, ...) {
+  // CHECK: call void (ptr, ptr, ...) @"\01-[Root printWithFormat:]"(ptr {{.*}}, ptr {{.*}})
+  [root printWithFormat:"Hello World"];
+  // CHECK: call void (ptr, ptr, ...) @"\01-[Root printWithFormat:]"(ptr {{.*}}, ptr {{.*}}, ptr {{.*}}, i32 noundef 1, double noundef 2.000000e+00)
+  [root printWithFormat:"Hello World%s %d %lf", "!", 1, 2.0];
+
+  // CHECK: call void @"\01-[Root vprintWithFormat:Args:]"(ptr {{.*}}, ptr {{.*}}, ptr {{.*}} null)
+  [root vprintWithFormat:"Hello World" Args:NULL];
+  va_list args;
+  // CHECK: call void @llvm.va_start
+  va_start(args, format);
+  // CHECK: call void @"\01-[Root vprintWithFormat:Args:]"(ptr {{.*}}, ptr {{.*}}, ptr {{.*}})
+  [root vprintWithFormat:format Args:args];
+  // CHECK: call void @llvm.va_end
+  va_end(args);
+}
diff --git a/clang/test/CodeGenObjC/direct-method-nil-check-thunk.m b/clang/test/CodeGenObjC/direct-method-nil-check-thunk.m
new file mode 100644
index 0000000000000..51f730bfc1e11
--- /dev/null
+++ b/clang/test/CodeGenObjC/direct-method-nil-check-thunk.m
@@ -0,0 +1,324 @@
+// RUN: %clang_cc1 -fobjc-emit-nil-check-thunk -emit-llvm -fobjc-arc -triple x86_64-apple-darwin10 %s -O0 -o - | FileCheck %s
+// RUN: %clang_cc1 -fobjc-emit-nil-check-thunk -emit-llvm -triple x86_64-apple-darwin10 %s -O0 -o - | FileCheck --check-prefix=NO-ARC %s
+
+// If objc-arc is not set, we should not emit any arc related intrinsics.
+// NO-ARC-NOT: retainAutoreleasedReturnValue
+// NO-ARC-NOT: objc_retainAutoreleasedReturnValue
+// NO-ARC-NOT: call void asm sideeffect "mov\09fp, fp\09\09// marker for objc_retainAutoreleaseReturnValue", ""()
+struct my_complex_struct {
+  int a, b;
+};
+
+struct my_aggregate_struct {
+  int a, b;
+  char buf[128];
+};
+
+__attribute__((objc_root_class))
+ at interface Root
+- (int)getInt __attribute__((objc_direct));
+ at property(direct, readonly) int intProperty;
+ at property(direct, readonly) int intProperty2;
+ at property(direct, readonly) Root* objectProperty;
+ at end
+
+ at implementation Root
+// CHECK-LABEL: define hidden i32 @"\01-[Root intProperty2]_inner"(ptr noundef nonnull %{{.*}}
+// CHECK-LABEL: define hidden i32 @"\01-[Root intProperty2]"(ptr noundef %{{.*}}
+- (int)intProperty2 {
+  return 42;
+}
+
+// CHECK-LABEL: define hidden i32 @"\01-[Root getInt]_inner"(ptr noundef nonnull %{{.*}}
+// CHECK-NEXT: entry:
+// CHECK-NEXT: [[SELFADDR:%.*]] = alloca ptr,
+// CHECK-NEXT: store ptr %{{.*}}, ptr [[SELFADDR]],
+// CHECK-NEXT: ret i32 42
+- (int)getInt __attribute__((objc_direct)) {
+  // loading parameters
+  // CHECK: define hidden i32 @"\01-[Root getInt]"(ptr noundef [[ARG0:%.*]])
+  // CHECK-NEXT: entry:
+  // CHECK-NEXT: [[RETVAL:%.*]] = alloca
+  // CHECK-NEXT: [[SELFADDR:%.*]] = alloca ptr,
+  // CHECK-NEXT: store ptr %{{.*}}, ptr [[SELFADDR]],
+
+  // self nil-check
+  // CHECK-NEXT: [[SELF:%.*]] = load ptr, ptr [[SELFADDR]],
+  // CHECK-NEXT: [[NILCHECK:%.*]] = icmp eq ptr [[SELF]], null
+  // CHECK-NEXT: br i1 [[NILCHECK]],
+
+  // setting return value to nil
+  // CHECK-LABEL: objc_direct_method.self_is_nil:
+  // CHECK-NEXT: call void @llvm.memset{{[^(]*}}({{[^,]*}}[[RETVAL]], i8 0,
+  // CHECK-NEXT: br label
+
+  // set value
+  // CHECK-LABEL: objc_direct_method.cont:
+  // CHECK-NEXT: [[RET:%.*]] = call i32 @"\01-[Root getInt]_inner"(ptr noundef nonnull [[ARG0]]
+  // CHECK-NEXT: store i32 [[RET]], ptr [[RETVAL]]
+  // CHECK-NEXT: br label %return
+
+  // return
+  // CHECK-LABEL: return:
+  // CHECK-NEXT: {{%.*}} = load{{.*}}[[RETVAL]],
+  // CHECK-NEXT: ret
+  return 42;
+}
+
+// CHECK-NOT: @"\01+[Root classGetInt]_inner"
++ (int)classGetInt __attribute__((objc_direct)) {
+  // CHECK: define hidden i32 @"\01+[Root classGetInt]"(ptr noundef nonnull [[ARGSELF:%.*]])
+  // [self self]
+  // CHECK-LABEL: entry:
+  // CHECK-NEXT: [[SELFADDR:%.*]] = alloca ptr,
+  // CHECK-NEXT: store ptr [[ARGSELF]], ptr [[SELFADDR]],
+  // CHECK-NEXT: [[SELF:%.*]] = load ptr, ptr [[SELFADDR]],
+  // CHECK-NEXT: [[SELFSEL:%.*]] = load ptr, ptr @OBJC_SELECTOR_REFERENCES_
+  // CHECK-NEXT: [[CALL:%.*]] = call {{.*}} @objc_msgSend(ptr noundef [[SELF]], ptr noundef [[SELFSEL]])
+  // CHECK-NEXT: store ptr [[CALL]], ptr [[SELFADDR]],
+  // CHECK-NEXT: ret i32 42
+  return 42;
+}
+
+// CHECK-LABEL: define hidden i64 @"\01-[Root getComplex]_inner"(ptr noundef nonnull %{{.*}}
+// CHECK-LABEL: entry:
+// CHECK-NEXT: [[RETVAL:%.*]] = alloca
+// CHECK-NEXT: [[SELFADDR:%.*]] = alloca ptr,
+// CHECK-NEXT: store ptr %{{.*}}, ptr [[SELFADDR]],
+// CHECK-NEXT: call void @llvm.memcpy{{[^(]*}}({{[^,]*}}[[RETVAL]],
+// CHECK-NEXT: [[RET:%.*]] = load{{.*}}[[RETVAL]],
+// CHECK-NEXT: ret i64 [[RET]]
+- (struct my_complex_struct)getComplex __attribute__((objc_direct)) {
+
+  // CHECK: define hidden i64 @"\01-[Root getComplex]"(ptr noundef [[ARGSELF:%.*]])
+  // self nil-check
+  // CHECK-LABEL: entry:
+  // CHECK-NEXT: [[RETVAL:%.*]] = alloca
+  // CHECK-NEXT: [[SELFADDR:%.*]] = alloca ptr,
+  // CHECK-NEXT: store ptr [[ARGSELF]], ptr [[SELFADDR]],
+  // CHECK-NEXT: [[SELF:%.*]] = load ptr, ptr [[SELFADDR]],
+  // CHECK-NEXT: [[NILCHECK:%.*]] = icmp eq ptr [[SELF]], null
+  // CHECK-NEXT: br i1 [[NILCHECK]],
+
+  // setting return value to nil
+  // CHECK-LABEL: objc_direct_method.self_is_nil:
+  // CHECK-NEXT: call void @llvm.memset{{[^(]*}}({{[^,]*}}[[RETVAL]], i8 0,
+  // CHECK-NEXT: br label
+
+  // call the inner function set value
+  // CHECK-LABEL: objc_direct_method.cont:
+  // CHECK-NEXT: [[CALL:%.*]] = call i64 @"\01-[Root getComplex]_inner"(ptr noundef nonnull [[ARGSELF]])
+  // CHECK-NEXT: store i64 [[CALL]], ptr [[RETVAL]]
+  // CHECK-NEXT: br label
+
+  // return
+  // CHECK-LABEL: return:
+  // CHECK-NEXT: [[RET:%.*]] = load{{.*}}[[RETVAL]],
+  // CHECK-NEXT: ret i64 [[RET]]
+  struct my_complex_struct st = {.a = 42};
+  return st;
+}
+
+// CHECK-NOT: @"\01+[Root classGetComplex]_inner"
++ (struct my_complex_struct)classGetComplex __attribute__((objc_direct)) {
+  // CHECK-LABEL: define hidden i64 @"\01+[Root classGetComplex]"(ptr noundef
+  struct my_complex_struct st = {.a = 42};
+  return st;
+}
+
+// CHECK-LABEL: define hidden void @"\01-[Root getAggregate]_inner"(
+// CHECK: ptr {{.*}} sret(%struct.my_aggregate_struct) align 4 [[RETVAL:%[^,]*]], ptr noundef nonnull %self
+// CHECK-LABEL: entry:
+// CHECK-NEXT: [[SELFADDR:%.*]] = alloca ptr,
+// CHECK-NEXT: store ptr %{{.*}}, ptr [[SELFADDR]],
+// CHECK-NEXT: call void @llvm.memset{{[^(]*}}({{[^,]*}}[[RETVAL]], i8 0,
+// CHECK-NEXT: [[A:%.*]] = getelementptr {{.*}} %struct.my_aggregate_struct, ptr [[RETVAL]], i32 0, i32 0
+// CHECK-NEXT: store i32 42, ptr [[A]]
+// CHECK-NEXT: ret void
+- (struct my_aggregate_struct)getAggregate __attribute__((objc_direct)) {
+
+  // loading parameters
+  // CHECK-LABEL: define hidden void @"\01-[Root getAggregate]"(
+  // CHECK: ptr {{.*}} sret(%struct.my_aggregate_struct) align 4 [[RETVAL:%[^,]*]], ptr noundef [[ARGSELF:%.*]])
+  // CHECK-LABEL: entry:
+  // CHECK-NEXT: [[SELFADDR:%.*]] = alloca ptr,
+  // CHECK-NEXT: store ptr %{{.*}}, ptr [[SELFADDR]],
+  // self nil-check
+  // CHECK-NEXT: [[SELF:%.*]] = load ptr, ptr [[SELFADDR]],
+  // CHECK-NEXT: [[NILCHECK:%.*]] = icmp eq ptr [[SELF]], null
+  // CHECK-NEXT: br i1 [[NILCHECK]],
+
+  // setting return value to nil
+  // CHECK-LABEL: objc_direct_method.self_is_nil:
+  // CHECK-NEXT: call void @llvm.memset{{[^(]*}}({{[^,]*}}[[RETVAL]], i8 0,
+  // CHECK-NEXT: br label
+
+  // set value
+  // CHECK-LABEL: objc_direct_method.cont:
+  // CHECK-NEXT: call void @"\01-[Root getAggregate]_inner"(ptr dead_on_unwind noalias writable sret(%struct.my_aggregate_struct) align 4 [[RETVAL]], ptr noundef nonnull [[ARGSELF]])
+  // CHECK-NEXT: br label
+
+  // return
+  // CHECK-LABEL: return:
+  // CHECK-NEXT: ret void
+  struct my_aggregate_struct st = {.a = 42};
+  return st;
+}
+
+// CHECK-LABEL: define hidden void @"\01+[Root classGetAggregate]"({{.*}}, ptr noundef nonnull {{.*}})
+// CHECK-NOT: @"\01+[Root classGetAggregate]_inner"
++ (struct my_aggregate_struct)classGetAggregate __attribute__((objc_direct)) {
+  struct my_aggregate_struct st = {.a = 42};
+  return st;
+}
+
+// CHECK-LABEL: define hidden void @"\01-[Root accessCmd]_inner"(ptr noundef nonnull
+// CHECK-LABEL: entry:
+// CHECK-NEXT: [[SELFADDR:%.*]] = alloca ptr,
+// CHECK-NEXT: [[CMDVAL:%_cmd]] = alloca ptr,
+// CHECK-NEXT: [[SELVAL:%sel]] = alloca ptr,
+// CHECK-NEXT: store ptr %{{.*}}, ptr [[SELFADDR]],
+// loading the _cmd selector
+// CHECK-NEXT: [[CMD1:%.*]] = load ptr, ptr @OBJC_SELECTOR_REFERENCES_
+// CHECK-NEXT: store ptr [[CMD1]], ptr [[CMDVAL]],
+// CHECK-NEXT: [[SEL:%.*]] = load ptr, ptr [[CMDVAL]],
+// CHECK-NEXT: store ptr [[SEL]], ptr [[SELVAL]],
+// CHECK-NEXT: ret void
+- (void)accessCmd __attribute__((objc_direct)) {
+  // CHECK-LABEL: define hidden void @"\01-[Root accessCmd]"(ptr noundef %{{.*}})
+
+  // CHECK-LABEL: objc_direct_method.self_is_nil:
+  // There is nothing for us to initialize, so this is an empty block
+  // CHECK-NEXT: br label %return
+
+  // CHECK-LABEL: objc_direct_method.cont:
+  // CHECK-NEXT: call void @"\01-[Root accessCmd]_inner"(ptr noundef nonnull %{{.*}})
+  // CHECK-NEXT: br label %return
+  SEL sel = _cmd;
+}
+
+ at end
+// CHECK-LABEL: define hidden i32 @"\01-[Root intProperty]_inner"(ptr noundef nonnull %{{.*}})
+// CHECK-LABEL: define hidden i32 @"\01-[Root intProperty]"(ptr noundef %{{.*}})
+
+// Check the synthesized objectProperty calls objc_getProperty(); this also
+// checks that the synthesized method passes undef for the `cmd` argument.
+// CHECK-LABEL: define hidden ptr @"\01-[Root objectProperty]_inner"(ptr noundef nonnull {{%.*}})
+// CHECK-NEXT: entry:
+// CHECK-NEXT: [[SELFADDR:%.*]] = alloca ptr,
+// CHECK-NEXT: store ptr %{{.*}}, ptr [[SELFADDR]],
+// CHECK-NEXT: [[SELFVAL:%.*]] = load {{.*}} [[SELFADDR]],
+// CHECK-NEXT: [[CALL:%.*]] = tail call ptr @objc_getProperty(ptr noundef [[SELF]], ptr noundef poison, i64 noundef 8, {{.*}})
+
+// CHECK-LABEL: define hidden ptr @"\01-[Root objectProperty]"(ptr noundef %{{.*}})
+// CHECK-LABEL: entry:
+// CHECK: [[RETADDR:%.*]] = alloca ptr,
+
+// CHECK-LABEL: objc_direct_method.cont:
+// CHECK-NEXT: [[RETVAL:%.*]] = call ptr @"\01-[Root objectProperty]_inner"
+// CHECK-NEXT: [[RETAINED_RET:%.*]] = notail call ptr @llvm.objc.retainAutoreleasedReturnValue(ptr [[RETVAL]])
+// CHECK-NEXT: store ptr [[RETAINED_RET]], ptr [[RETADDR]],
+// CHECK-NEXT: br label %return
+
+// CHECK-LABEL: return:
+// CHECK-NEXT: [[RET:%.*]] = load ptr, ptr [[RETADDR]],
+// CHECK-NEXT: [[AUTORELEASED:%.*]] = tail call ptr @llvm.objc.autoreleaseReturnValue(ptr [[RET]])
+// CHECK-NEXT: ret ptr [[AUTORELEASED]]
+ at interface Foo : Root {
+  id __strong _cause_cxx_destruct;
+}
+ at property(nonatomic, readonly, direct) int getDirect_setDynamic;
+ at property(nonatomic, readonly) int getDynamic_setDirect;
+ at end
+
+ at interface Foo ()
+ at property(nonatomic, readwrite) int getDirect_setDynamic;
+ at property(nonatomic, readwrite, direct) int getDynamic_setDirect;
+- (int)directMethodInExtension __attribute__((objc_direct));
+ at end
+
+ at interface Foo (Cat)
+- (int)directMethodInCategory __attribute__((objc_direct));
+ at end
+
+__attribute__((objc_direct_members))
+ at implementation Foo
+// CHECK-LABEL: define hidden i32 @"\01-[Foo directMethodInExtension]_inner"(
+// CHECK-LABEL: define hidden i32 @"\01-[Foo directMethodInExtension]"(
+- (int)directMethodInExtension {
+  return 42;
+}
+// CHECK-LABEL: define hidden i32 @"\01-[Foo getDirect_setDynamic]_inner"(
+// CHECK-LABEL: define hidden i32 @"\01-[Foo getDirect_setDynamic]"(
+// CHECK-LABEL: define internal void @"\01-[Foo setGetDirect_setDynamic:]"(
+// CHECK-LABEL: define internal i32 @"\01-[Foo getDynamic_setDirect]"(
+// CHECK-LABEL: define hidden void @"\01-[Foo setGetDynamic_setDirect:]_inner"(
+// CHECK-LABEL: define hidden void @"\01-[Foo setGetDynamic_setDirect:]"(
+// CHECK-LABEL: define internal void @"\01-[Foo .cxx_destruct]"(
+ at end
+
+ at implementation Foo (Cat)
+// CHECK-LABEL: define hidden i32 @"\01-[Foo directMethodInCategory]_inner"(
+// CHECK-LABEL: define hidden i32 @"\01-[Foo directMethodInCategory]"(
+- (int)directMethodInCategory {
+  return 42;
+}
+// CHECK-LABEL: define hidden i32 @"\01-[Foo directMethodInCategoryNoDecl]_inner"(
+// CHECK-LABEL: define hidden i32 @"\01-[Foo directMethodInCategoryNoDecl]"(
+- (int)directMethodInCategoryNoDecl __attribute__((objc_direct)) {
+  return 42;
+}
+ at end
+
+// CHECK: define i32 @useClassMethod()
+// CHECK:   call {{.*}} @"\01+[Root classGetInt]"
+// CHECK:   call {{.*}} @"\01+[Root classGetComplex]"
+// CHECK:   call {{.*}} @"\01+[Root classGetAggregate]"
+int useClassMethod() {
+  return [Root classGetInt] + [Root classGetComplex].a + [Root classGetAggregate].a;
+}
+
+int useRoot(Root *r) {
+  // CHECK-LABEL: define{{.*}} i32 @useRoot
+  // CHECK: %{{[^ ]*}} = call i32  @"\01-[Root getInt]"
+  // CHECK: %{{[^ ]*}} = call i32  @"\01-[Root intProperty]"
+  // CHECK: %{{[^ ]*}} = call i32  @"\01-[Root intProperty2]"
+  return [r getInt] + [r intProperty] + [r intProperty2];
+}
+
+// Currently, we don't have analysis on nonnull attributes yet.
+__attribute__((nonnull))int useFoo(const Foo *f) {
+  // CHECK-LABEL: define{{.*}} i32 @useFoo
+  // CHECK: call void @"\01-[Foo setGetDynamic_setDirect:]"
+  // CHECK: %{{[^ ]*}} = call i32 @"\01-[Foo getDirect_setDynamic]"
+  // CHECK: %{{[^ ]*}} = call i32 @"\01-[Foo directMethodInExtension]"
+  // CHECK: %{{[^ ]*}} = call i32 @"\01-[Foo directMethodInCategory]"
+  // CHECK: %{{[^ ]*}} = call i32 @"\01-[Foo directMethodInCategoryNoDecl]"
+  [f setGetDynamic_setDirect:1];
+  return [f getDirect_setDynamic] +
+         [f directMethodInExtension] +
+         [f directMethodInCategory] +
+         [f directMethodInCategoryNoDecl];
+}
+
+__attribute__((objc_root_class))
+ at interface RootDeclOnly
+ at property(direct, readonly) int intProperty;
+ at end
+
+// We can't be sure `r` is nonnull, so we need to call the thunk without underscore.
+int useRootDeclOnly(RootDeclOnly *r) {
+  // CHECK-LABEL: define{{.*}} i32 @useRootDeclOnly
+  // CHECK: %{{[^ ]*}} = call i32 @"\01-[RootDeclOnly intProperty]"
+  return [r intProperty];
+}
+
+// CHECK-LABEL: define i32 @getObjectIntProperty
+int getObjectIntProperty(Root *r) {
+  // CHECK: [[OBJ:%.*]] = call ptr @"\01-[Root objectProperty]"
+  // CHECK-NEXT: [[RETAINED:%.*]] = notail call ptr @llvm.objc.retainAutoreleasedReturnValue(ptr [[OBJ]])
+  // CHECK-NEXT: {{.*}} = call i32 @"\01-[Root intProperty]"(ptr noundef [[RETAINED]])
+  // CHECK-NEXT: call void @llvm.objc.release(ptr [[RETAINED]])
+    return r.objectProperty.intProperty;
+}
+// CHECK-LABEL: }
diff --git a/clang/test/CodeGenObjC/direct-method-ret-mismatch.m b/clang/test/CodeGenObjC/direct-method-ret-mismatch.m
index 889a6d68da0d7..57df5589da6a3 100644
--- a/clang/test/CodeGenObjC/direct-method-ret-mismatch.m
+++ b/clang/test/CodeGenObjC/direct-method-ret-mismatch.m
@@ -1,4 +1,5 @@
 // RUN: %clang_cc1 -emit-llvm -fobjc-arc -triple x86_64-apple-darwin10 %s -o - | FileCheck %s
+// RUN: %clang_cc1 -fobjc-emit-nil-check-thunk -emit-llvm -fobjc-arc -triple arm64-apple-darwin %s -o - | FileCheck --check-prefixes=CHECK-THUNK %s
 
 __attribute__((objc_root_class))
 @interface Root
@@ -17,3 +18,10 @@ - (id)method {
   return self;
 }
 @end
+// The inner function should not contain any nil check anymore.
+// CHECK-THUNK-LABEL: define hidden ptr @"\01-[Root method]_inner"(ptr noundef nonnull
+// CHECK-THUNK-NOT: br i1 %1, label %objc_direct_method.self_is_nil, label %objc_direct_method.cont
+
+// The direct function contains the nil check.
+// CHECK-THUNK-LABEL: define hidden ptr @"\01-[Root method]"(ptr noundef
+// CHECK-THUNK-LABEL: br i1 %1, label %objc_direct_method.self_is_nil, label %objc_direct_method.cont

>From 2e4e400425d74ac7f6d2175f006aabb9a5e06622 Mon Sep 17 00:00:00 2001
From: Peter Rong <PeterRong at meta.com>
Date: Tue, 18 Feb 2025 17:38:46 -0800
Subject: [PATCH 04/12] format

---
 clang/lib/CodeGen/CodeGenFunction.cpp | 1 +
 clang/lib/CodeGen/CodeGenFunction.h   | 2 +-
 2 files changed, 2 insertions(+), 1 deletion(-)

diff --git a/clang/lib/CodeGen/CodeGenFunction.cpp b/clang/lib/CodeGen/CodeGenFunction.cpp
index aa26c228cadf6..bf3bb527cdfad 100644
--- a/clang/lib/CodeGen/CodeGenFunction.cpp
+++ b/clang/lib/CodeGen/CodeGenFunction.cpp
@@ -80,6 +80,7 @@ CodeGenFunction::CodeGenFunction(CodeGenModule &cgm, llvm::Function *Inner,
   InnerFn = Inner;
   assert(InnerFn && "The inner function provided should not be null");
 }
+
 CodeGenFunction::CodeGenFunction(CodeGenModule &cgm, bool suppressNewContext)
     : CodeGenTypeCache(cgm), CGM(cgm), Target(cgm.getTarget()),
       Builder(cgm, cgm.getModule().getContext(), llvm::ConstantFolder(),
diff --git a/clang/lib/CodeGen/CodeGenFunction.h b/clang/lib/CodeGen/CodeGenFunction.h
index 8821ca174a542..6d410609fbb72 100644
--- a/clang/lib/CodeGen/CodeGenFunction.h
+++ b/clang/lib/CodeGen/CodeGenFunction.h
@@ -1675,7 +1675,7 @@ class CodeGenFunction : public CodeGenTypeCache {
 
   /// If `InnerFn` is set, this CGF generates a thunk function that does the nil
   /// check before calling `InnerFn`. `InnerFn` has to be an objc_direct method.
-  llvm::Function* InnerFn = nullptr;
+  llvm::Function *InnerFn = nullptr;
 
   /// Increment the profiler's counter for the given statement by \p StepV.
   /// If \p StepV is null, the default increment is 1.

>From 8afbebeda2e266c2d3540c50394cf0f2dd72d0a3 Mon Sep 17 00:00:00 2001
From: Peter Rong <PeterRong at meta.com>
Date: Tue, 18 Feb 2025 17:44:04 -0800
Subject: [PATCH 05/12] format

---
 clang/lib/CodeGen/CGObjCMac.cpp | 3 ++-
 1 file changed, 2 insertions(+), 1 deletion(-)

diff --git a/clang/lib/CodeGen/CGObjCMac.cpp b/clang/lib/CodeGen/CGObjCMac.cpp
index 62739beeaa109..82285be46ffe2 100644
--- a/clang/lib/CodeGen/CGObjCMac.cpp
+++ b/clang/lib/CodeGen/CGObjCMac.cpp
@@ -2088,7 +2088,8 @@ CodeGen::RValue CGObjCCommonMac::EmitMessageSend(
   llvm::FunctionCallee Fn = nullptr;
   if (Method && Method->isDirectMethod()) {
     assert(!IsSuper);
-    Fn = GenerateDirectMethod(Method, Method->getClassInterface(), /*isThunk=*/true);
+    Fn = GenerateDirectMethod(Method, Method->getClassInterface(),
+                              /*isThunk=*/true);
     // Direct methods will synthesize the proper `_cmd` internally,
     // so just don't bother with setting the `_cmd` argument.
     RequiresSelValue = false;

>From 038bb2fda4ab47bf973dc3eb736c88a1d51c26b8 Mon Sep 17 00:00:00 2001
From: Peter Rong <PeterRong at meta.com>
Date: Wed, 19 Feb 2025 12:37:25 -0800
Subject: [PATCH 06/12] Address Daniel's comment

---
 clang/lib/AST/Mangle.cpp          |   2 +-
 clang/lib/CodeGen/CGCall.cpp      |   2 +-
 clang/lib/CodeGen/CGObjC.cpp      |   6 +-
 clang/lib/CodeGen/CGObjCGNU.cpp   |   9 ---
 clang/lib/CodeGen/CGObjCMac.cpp   | 112 ++++--------------------------
 clang/lib/CodeGen/CGObjCRuntime.h |   5 --
 6 files changed, 18 insertions(+), 118 deletions(-)

diff --git a/clang/lib/AST/Mangle.cpp b/clang/lib/AST/Mangle.cpp
index ed4c1b2dd2096..2be800410c92a 100644
--- a/clang/lib/AST/Mangle.cpp
+++ b/clang/lib/AST/Mangle.cpp
@@ -40,7 +40,7 @@ void clang::mangleObjCMethodName(raw_ostream &OS, bool includePrefixByte,
   OS << '[';
   OS << ClassName;
   if (CategoryName)
-    OS << "(" << CategoryName.value() << ")";
+    OS << "(" << *CategoryName << ")";
   OS << " ";
   OS << MethodName;
   OS << ']';
diff --git a/clang/lib/CodeGen/CGCall.cpp b/clang/lib/CodeGen/CGCall.cpp
index 0438e5da6fad2..5033c2c4982dd 100644
--- a/clang/lib/CodeGen/CGCall.cpp
+++ b/clang/lib/CodeGen/CGCall.cpp
@@ -3481,7 +3481,7 @@ void CodeGenFunction::EmitFunctionProlog(const CGFunctionInfo &FI,
   if (InnerFn) {
     // Don't emit any arg except for `self` if we are in a thunk function.
     // We still need self for nil check, other arguments aren't used in this
-    // function and thus is not needed. Avoid emitting them also prevents
+    // function and thus are not needed. Avoid emitting them also prevents
     // accidental release/retain.
     EmitParmDecl(*Args[0], ArgVals[0], 1);
   } else if (getTarget().getCXXABI().areArgsDestroyedLeftToRightInCallee()) {
diff --git a/clang/lib/CodeGen/CGObjC.cpp b/clang/lib/CodeGen/CGObjC.cpp
index a6ddf37872da8..9e12f9ac50ffb 100644
--- a/clang/lib/CodeGen/CGObjC.cpp
+++ b/clang/lib/CodeGen/CGObjC.cpp
@@ -789,9 +789,7 @@ void CodeGenFunction::StartObjCMethod(const ObjCMethodDecl *OMD,
       if (CGM.shouldHaveNilCheckThunk(OMD))
         // Go generate  a nil check thunk around `Fn`
         CodeGenFunction(CGM, /*InnerFn=*/Fn).GenerateObjCDirectThunk(OMD, CD);
-      else
-        CGM.getObjCRuntime().GenerateObjCDirectNilCheck(*this, OMD, CD);
-      CGM.getObjCRuntime().GenerateCmdIfNecessary(*this, OMD);
+      CGM.getObjCRuntime().GenerateDirectMethodPrologue(*this, Fn, OMD, CD);
     } else {
       // For GNU family, since GNU Step2 also supports direct methods now.
       CGM.getObjCRuntime().GenerateDirectMethodPrologue(*this, Fn, OMD, CD);
@@ -1660,7 +1658,7 @@ void CodeGenFunction::GenerateObjCDirectThunk(const ObjCMethodDecl *OMD,
     EHStack.popCleanup();
 
   // Generate a nil check.
-  CGM.getObjCRuntime().GenerateObjCDirectNilCheck(*this, OMD, CD);
+  CGM.getObjCRuntime().GenerateDirectMethodPrologue(*this, CurFn, OMD, CD);
   // Call the InnerFn and pass the return value
   SmallVector<llvm::Value *> Args(CurFn->arg_size());
   std::transform(CurFn->arg_begin(), CurFn->arg_end(), Args.begin(),
diff --git a/clang/lib/CodeGen/CGObjCGNU.cpp b/clang/lib/CodeGen/CGObjCGNU.cpp
index 840ff627941b6..3b81e0c2d58d6 100644
--- a/clang/lib/CodeGen/CGObjCGNU.cpp
+++ b/clang/lib/CodeGen/CGObjCGNU.cpp
@@ -594,15 +594,6 @@ class CGObjCGNU : public CGObjCRuntime {
   }
   llvm::Constant *GetEHType(QualType T) override;
 
-  void GenerateObjCDirectNilCheck(CodeGenFunction &CGF,
-                                  const ObjCMethodDecl *OMD,
-                                  const ObjCContainerDecl *CD) override {
-    // GNU runtime doesn't support nil check thunks at this time
-  };
-  void GenerateCmdIfNecessary(CodeGenFunction &CGF,
-                              const ObjCMethodDecl *OMD) override {
-    // GNU runtime doesn't support nil check thunks at this time
-  }
   llvm::Function *GenerateMethod(const ObjCMethodDecl *OMD,
                                  const ObjCContainerDecl *CD,
                                  bool isThunk) override {
diff --git a/clang/lib/CodeGen/CGObjCMac.cpp b/clang/lib/CodeGen/CGObjCMac.cpp
index 82285be46ffe2..5c9b4126913af 100644
--- a/clang/lib/CodeGen/CGObjCMac.cpp
+++ b/clang/lib/CodeGen/CGObjCMac.cpp
@@ -1060,15 +1060,6 @@ class CGObjCCommonMac : public CodeGen::CGObjCRuntime {
   void GenerateDirectMethodPrologue(CodeGenFunction &CGF, llvm::Function *Fn,
                                     const ObjCMethodDecl *OMD,
                                     const ObjCContainerDecl *CD) override;
-  /// We split `GenerateDirectMethodPrologue` into the following two functions.
-  /// The motivation is that nil check thunk function does the nil check, it
-  /// definitely doesn't need _cmd.
-  void GenerateObjCDirectNilCheck(CodeGenFunction &CGF,
-                                  const ObjCMethodDecl *OMD,
-                                  const ObjCContainerDecl *CD) override;
-  /// The inner function can decide if it needs `_cmd` for itself.
-  void GenerateCmdIfNecessary(CodeGenFunction &CGF,
-                              const ObjCMethodDecl *OMD) override;
 
   void GenerateProtocol(const ObjCProtocolDecl *PD) override;
 
@@ -3929,92 +3920,6 @@ llvm::Function *CGObjCCommonMac::GenerateDirectMethod(
   return Fn;
 }
 
-void CGObjCCommonMac::GenerateObjCDirectNilCheck(CodeGenFunction &CGF,
-                                                 const ObjCMethodDecl *OMD,
-                                                 const ObjCContainerDecl *CD) {
-  auto &Builder = CGF.Builder;
-  bool ReceiverCanBeNull = true;
-  auto selfAddr = CGF.GetAddrOfLocalVar(OMD->getSelfDecl());
-  auto selfValue = Builder.CreateLoad(selfAddr);
-
-  // Generate:
-  //
-  // /* for class methods only to force class lazy initialization */
-  // self = [self self];
-  //
-  // /* unless the receiver is never NULL */
-  // if (self == nil) {
-  //     return (ReturnType){ };
-  // }
-
-  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
-    // method which always receives an initialized `self`, or it might have just
-    // forced initialization on its own.
-    //
-    // 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);
-
-    // Nullable `Class` expressions cannot be messaged with a direct method
-    // so the only reason why the receive can be null would be because
-    // of weak linking.
-    ReceiverCanBeNull = isWeakLinkedClass(OID);
-  }
-
-  if (ReceiverCanBeNull) {
-    llvm::BasicBlock *SelfIsNilBlock =
-        CGF.createBasicBlock("objc_direct_method.self_is_nil");
-    llvm::BasicBlock *ContBlock =
-        CGF.createBasicBlock("objc_direct_method.cont");
-
-    // if (self == nil) {
-    auto selfTy = cast<llvm::PointerType>(selfValue->getType());
-    auto Zero = llvm::ConstantPointerNull::get(selfTy);
-
-    llvm::MDBuilder MDHelper(CGM.getLLVMContext());
-    Builder.CreateCondBr(Builder.CreateICmpEQ(selfValue, Zero), SelfIsNilBlock,
-                         ContBlock, MDHelper.createBranchWeights(1, 1 << 20));
-
-    CGF.EmitBlock(SelfIsNilBlock);
-
-    //   return (ReturnType){ };
-    auto retTy = OMD->getReturnType();
-    Builder.SetInsertPoint(SelfIsNilBlock);
-    if (!retTy->isVoidType()) {
-      CGF.EmitNullInitialization(CGF.ReturnValue, retTy);
-    }
-    CGF.EmitBranchThroughCleanup(CGF.ReturnBlock);
-    // }
-
-    CGF.EmitBlock(ContBlock);
-    Builder.SetInsertPoint(ContBlock);
-  }
-}
-void CGObjCCommonMac::GenerateCmdIfNecessary(CodeGenFunction &CGF,
-                                             const ObjCMethodDecl *OMD) {
-  // only synthesize _cmd if it's referenced
-  if (OMD->getCmdDecl()->isUsed()) {
-    // `_cmd` is not a parameter to direct methods, so storage must be
-    // explicitly declared for it.
-    CGF.EmitVarDecl(*OMD->getCmdDecl());
-    CGF.Builder.CreateStore(GetSelector(CGF, OMD),
-                            CGF.GetAddrOfLocalVar(OMD->getCmdDecl()));
-  }
-}
-
 void CGObjCCommonMac::GenerateDirectMethodPrologue(
     CodeGenFunction &CGF, llvm::Function *Fn, const ObjCMethodDecl *OMD,
     const ObjCContainerDecl *CD) {
@@ -4022,6 +3927,9 @@ void CGObjCCommonMac::GenerateDirectMethodPrologue(
   bool ReceiverCanBeNull = true;
   auto selfAddr = CGF.GetAddrOfLocalVar(OMD->getSelfDecl());
   auto selfValue = Builder.CreateLoad(selfAddr);
+  bool shouldHaveNilCheckThunk =
+      CGF.CGM.shouldHaveNilCheckThunk(OMD) bool isNilCheckThunk =
+          shouldHaveNilCheckThunk && CGF.InnerFn;
 
   // Generate:
   //
@@ -4063,7 +3971,10 @@ void CGObjCCommonMac::GenerateDirectMethodPrologue(
     ReceiverCanBeNull = isWeakLinkedClass(OID);
   }
 
-  if (ReceiverCanBeNull) {
+  // Only emit nil check if this is a nil check thunk or the method
+  // decides that its receiver can be null
+  if (isNilCheckThunk ||
+      (!CGF.CGM.shouldHaveNilCheckThunk(OMD) && ReceiverCanBeNull)) {
     llvm::BasicBlock *SelfIsNilBlock =
         CGF.createBasicBlock("objc_direct_method.self_is_nil");
     llvm::BasicBlock *ContBlock =
@@ -4093,14 +4004,19 @@ void CGObjCCommonMac::GenerateDirectMethodPrologue(
     Builder.SetInsertPoint(ContBlock);
   }
 
-  // only synthesize _cmd if it's referenced
-  if (OMD->getCmdDecl()->isUsed()) {
+  // Only synthesize _cmd if it's referenced
+  // However, a nil check thunk doesn't need _cmd even if it's referenced
+  if (!isNilCheckThunk && OMD->getCmdDecl()->isUsed()) {
     // `_cmd` is not a parameter to direct methods, so storage must be
     // explicitly declared for it.
     CGF.EmitVarDecl(*OMD->getCmdDecl());
     Builder.CreateStore(GetSelector(CGF, OMD),
                         CGF.GetAddrOfLocalVar(OMD->getCmdDecl()));
   }
+
+  // It's possible that selfValue is never used.
+  if (selfValue->use_empty())
+    selfValue->eraseFromParent();
 }
 
 llvm::GlobalVariable *
diff --git a/clang/lib/CodeGen/CGObjCRuntime.h b/clang/lib/CodeGen/CGObjCRuntime.h
index 5bdea39f618bd..26217483126b4 100644
--- a/clang/lib/CodeGen/CGObjCRuntime.h
+++ b/clang/lib/CodeGen/CGObjCRuntime.h
@@ -233,11 +233,6 @@ class CGObjCRuntime {
                                             const ObjCMethodDecl *OMD,
                                             const ObjCContainerDecl *CD) = 0;
 
-  virtual void GenerateObjCDirectNilCheck(CodeGenFunction &CGF,
-                                          const ObjCMethodDecl *OMD,
-                                          const ObjCContainerDecl *CD) = 0;
-  virtual void GenerateCmdIfNecessary(CodeGenFunction &CGF,
-                                      const ObjCMethodDecl *OMD) = 0;
   /// Return the runtime function for getting properties.
   virtual llvm::FunctionCallee GetPropertyGetFunction() = 0;
 

>From 259550154655e755013e65305f3cde60c8fc0e81 Mon Sep 17 00:00:00 2001
From: Peter Rong <PeterRong at meta.com>
Date: Wed, 19 Feb 2025 12:40:51 -0800
Subject: [PATCH 07/12] update

---
 clang/lib/CodeGen/CGObjC.cpp    | 22 +++++++++-------------
 clang/lib/CodeGen/CGObjCMac.cpp |  8 +++-----
 2 files changed, 12 insertions(+), 18 deletions(-)

diff --git a/clang/lib/CodeGen/CGObjC.cpp b/clang/lib/CodeGen/CGObjC.cpp
index 9e12f9ac50ffb..1fd4c1d2c8a0f 100644
--- a/clang/lib/CodeGen/CGObjC.cpp
+++ b/clang/lib/CodeGen/CGObjC.cpp
@@ -781,19 +781,15 @@ void CodeGenFunction::StartObjCMethod(const ObjCMethodDecl *OMD,
                 OMD->getLocation(), StartLoc);
 
   if (OMD->isDirectMethod()) {
-    if (CGM.getLangOpts().ObjCRuntime.isNeXTFamily()) {
-      // Having `InnerFn` indicates that we are generating a nil check thunk.
-      // In that case our job is done here.
-      if (InnerFn)
-        return;
-      if (CGM.shouldHaveNilCheckThunk(OMD))
-        // Go generate  a nil check thunk around `Fn`
-        CodeGenFunction(CGM, /*InnerFn=*/Fn).GenerateObjCDirectThunk(OMD, CD);
-      CGM.getObjCRuntime().GenerateDirectMethodPrologue(*this, Fn, OMD, CD);
-    } else {
-      // For GNU family, since GNU Step2 also supports direct methods now.
-      CGM.getObjCRuntime().GenerateDirectMethodPrologue(*this, Fn, OMD, CD);
-    }
+    // Having `InnerFn` indicates that we are generating a nil check thunk.
+    // In that case our job is done here.
+    if (InnerFn)
+      return;
+    // Only NeXTFamily can have nil check thunk.
+    if (CGM.shouldHaveNilCheckThunk(OMD))
+      // Go generate  a nil check thunk around `Fn`
+      CodeGenFunction(CGM, /*InnerFn=*/Fn).GenerateObjCDirectThunk(OMD, CD);
+    CGM.getObjCRuntime().GenerateDirectMethodPrologue(*this, Fn, OMD, CD);
   }
 
   // In ARC, certain methods get an extra cleanup.
diff --git a/clang/lib/CodeGen/CGObjCMac.cpp b/clang/lib/CodeGen/CGObjCMac.cpp
index 5c9b4126913af..e2571cbf03e04 100644
--- a/clang/lib/CodeGen/CGObjCMac.cpp
+++ b/clang/lib/CodeGen/CGObjCMac.cpp
@@ -3927,9 +3927,8 @@ void CGObjCCommonMac::GenerateDirectMethodPrologue(
   bool ReceiverCanBeNull = true;
   auto selfAddr = CGF.GetAddrOfLocalVar(OMD->getSelfDecl());
   auto selfValue = Builder.CreateLoad(selfAddr);
-  bool shouldHaveNilCheckThunk =
-      CGF.CGM.shouldHaveNilCheckThunk(OMD) bool isNilCheckThunk =
-          shouldHaveNilCheckThunk && CGF.InnerFn;
+  bool shouldHaveNilCheckThunk = CGF.CGM.shouldHaveNilCheckThunk(OMD);
+  bool isNilCheckThunk = shouldHaveNilCheckThunk && CGF.InnerFn;
 
   // Generate:
   //
@@ -3973,8 +3972,7 @@ void CGObjCCommonMac::GenerateDirectMethodPrologue(
 
   // Only emit nil check if this is a nil check thunk or the method
   // decides that its receiver can be null
-  if (isNilCheckThunk ||
-      (!CGF.CGM.shouldHaveNilCheckThunk(OMD) && ReceiverCanBeNull)) {
+  if (isNilCheckThunk || (!shouldHaveNilCheckThunk && ReceiverCanBeNull)) {
     llvm::BasicBlock *SelfIsNilBlock =
         CGF.createBasicBlock("objc_direct_method.self_is_nil");
     llvm::BasicBlock *ContBlock =

>From d189cceb0fc0a37adec8a88144154346fdaefa1c Mon Sep 17 00:00:00 2001
From: Peter Rong <PeterRong at meta.com>
Date: Wed, 19 Feb 2025 12:56:24 -0800
Subject: [PATCH 08/12] Small bug fix, shouldn't mark class method as nonnull

---
 clang/lib/CodeGen/CGCall.cpp                           | 5 ++---
 clang/test/CodeGenObjC/direct-method-nil-check-thunk.m | 4 ++--
 2 files changed, 4 insertions(+), 5 deletions(-)

diff --git a/clang/lib/CodeGen/CGCall.cpp b/clang/lib/CodeGen/CGCall.cpp
index 5033c2c4982dd..dddbdfcaa322c 100644
--- a/clang/lib/CodeGen/CGCall.cpp
+++ b/clang/lib/CodeGen/CGCall.cpp
@@ -2774,10 +2774,9 @@ void CodeGenModule::ConstructAttributeList(StringRef Name,
   // Direct method prologue should not contain nil check anymore.
   // As a result, we can set `self` to be NonNull to prepare for further
   // optimizations.
-  if (getLangOpts().ObjCEmitNilCheckThunk && TargetDecl) {
+  if (TargetDecl) {
     auto OMD = dyn_cast<ObjCMethodDecl>(TargetDecl);
-    bool isDirect = OMD && OMD->isDirectMethod();
-    if (isDirect && !IsThunk) {
+    if (shouldHaveNilCheckThunk(OMD) && !IsThunk) {
       auto IRArgs = IRFunctionArgs.getIRArgs(0);
       ArgAttrs[IRArgs.first] = ArgAttrs[IRArgs.first].addAttribute(
           getLLVMContext(), llvm::Attribute::NonNull);
diff --git a/clang/test/CodeGenObjC/direct-method-nil-check-thunk.m b/clang/test/CodeGenObjC/direct-method-nil-check-thunk.m
index 51f730bfc1e11..1d336708feae3 100644
--- a/clang/test/CodeGenObjC/direct-method-nil-check-thunk.m
+++ b/clang/test/CodeGenObjC/direct-method-nil-check-thunk.m
@@ -67,7 +67,7 @@ - (int)getInt __attribute__((objc_direct)) {
 
 // CHECK-NOT: @"\01+[Root classGetInt]_inner"
 + (int)classGetInt __attribute__((objc_direct)) {
-  // CHECK: define hidden i32 @"\01+[Root classGetInt]"(ptr noundef nonnull [[ARGSELF:%.*]])
+  // CHECK: define hidden i32 @"\01+[Root classGetInt]"(ptr noundef [[ARGSELF:%.*]])
   // [self self]
   // CHECK-LABEL: entry:
   // CHECK-NEXT: [[SELFADDR:%.*]] = alloca ptr,
@@ -165,7 +165,7 @@ - (struct my_aggregate_struct)getAggregate __attribute__((objc_direct)) {
   return st;
 }
 
-// CHECK-LABEL: define hidden void @"\01+[Root classGetAggregate]"({{.*}}, ptr noundef nonnull {{.*}})
+// CHECK-LABEL: define hidden void @"\01+[Root classGetAggregate]"({{.*}}, ptr noundef {{.*}})
 // CHECK-NOT: @"\01+[Root classGetAggregate]_inner"
 + (struct my_aggregate_struct)classGetAggregate __attribute__((objc_direct)) {
   struct my_aggregate_struct st = {.a = 42};

>From d1c15a96cba52e99a86ada15c37701e17059634e Mon Sep 17 00:00:00 2001
From: Peter Rong <PeterRong at meta.com>
Date: Wed, 19 Feb 2025 13:56:11 -0800
Subject: [PATCH 09/12] remove stdio.h to make windows happy

---
 .../test/CodeGenObjC/direct-method-nil-check-thunk-vararg.m  | 5 ++++-
 1 file changed, 4 insertions(+), 1 deletion(-)

diff --git a/clang/test/CodeGenObjC/direct-method-nil-check-thunk-vararg.m b/clang/test/CodeGenObjC/direct-method-nil-check-thunk-vararg.m
index 05ea75178f360..6f5934e79ee0c 100644
--- a/clang/test/CodeGenObjC/direct-method-nil-check-thunk-vararg.m
+++ b/clang/test/CodeGenObjC/direct-method-nil-check-thunk-vararg.m
@@ -3,8 +3,11 @@
 // RUN:   -O0 -S -emit-llvm %s -o - | FileCheck %s
 
 
-#include <stdio.h>
 #include <stdarg.h>
+
+int vprintf(const char * restrict format, va_list ap);
+#define NULL ((void *)0)
+
 __attribute__((objc_root_class))
 @interface Root
 - (void)printWithFormat:(const char *)format, ... __attribute__((objc_direct, visibility("default")));

>From 8fd859eb96c3bf12cc7f993d68456cad6a7bc7be Mon Sep 17 00:00:00 2001
From: Peter Rong <PeterRong at meta.com>
Date: Mon, 10 Mar 2025 17:06:21 -0700
Subject: [PATCH 10/12] rebase, modify names and update test

Signed-off-by: Peter Rong <PeterRong at meta.com>
---
 clang/lib/AST/Mangle.cpp                      |  2 +-
 .../direct-method-nil-check-thunk-arc.m       |  6 +--
 .../direct-method-nil-check-thunk-consumed.m  |  6 +--
 .../direct-method-nil-check-thunk-opt.m       | 34 ++++++++--------
 .../direct-method-nil-check-thunk-vararg.m    |  8 ++--
 .../direct-method-nil-check-thunk.m           | 40 +++++++++----------
 .../CodeGenObjC/direct-method-ret-mismatch.m  |  2 +-
 7 files changed, 49 insertions(+), 49 deletions(-)

diff --git a/clang/lib/AST/Mangle.cpp b/clang/lib/AST/Mangle.cpp
index 2be800410c92a..c01a29ad0891d 100644
--- a/clang/lib/AST/Mangle.cpp
+++ b/clang/lib/AST/Mangle.cpp
@@ -45,7 +45,7 @@ void clang::mangleObjCMethodName(raw_ostream &OS, bool includePrefixByte,
   OS << MethodName;
   OS << ']';
   if (!isThunk)
-    OS << "_inner";
+    OS << "_nonnull";
 }
 // FIXME: For blocks we currently mimic GCC's mangling scheme, which leaves
 // much to be desired. Come up with a better mangling scheme.
diff --git a/clang/test/CodeGenObjC/direct-method-nil-check-thunk-arc.m b/clang/test/CodeGenObjC/direct-method-nil-check-thunk-arc.m
index 190099b0117c6..57eada2250bdf 100644
--- a/clang/test/CodeGenObjC/direct-method-nil-check-thunk-arc.m
+++ b/clang/test/CodeGenObjC/direct-method-nil-check-thunk-arc.m
@@ -73,14 +73,14 @@ + (Shape*) default {
   return [[Shape alloc] initDefault];
 }
 
-// CHECK-LABEL: define {{.*}} @"\01-[Shape clone]_inner"
+// CHECK-LABEL: define {{.*}} @"\01-[Shape clone]_nonnull"
 // CHECK: [[CALL_INIT:%.*]] = call ptr @"\01-[Shape initWithX:Y:]"
 // CHECK-NEXT: [[AUTORELEASE_CLONE:%.*]] = tail call ptr @llvm.objc.autoreleaseReturnValue(ptr [[CALL_INIT]])
 // CHECK-NEXT: ret ptr [[AUTORELEASE_CLONE]]
 // CHECK-LABEL: }
 
 // CHECK-LABEL: define {{.*}} @"\01-[Shape clone]"
-// CHECK: [[CALL_INNER:%.*]] = call ptr @"\01-[Shape clone]_inner"
+// CHECK: [[CALL_INNER:%.*]] = call ptr @"\01-[Shape clone]_nonnull"
 // CHECK-NEXT: call void asm sideeffect "mov\09fp, fp\09\09// marker for objc_retainAutoreleaseReturnValue", ""()
 // CHECK-NEXT: [[RETAINED:%.*]] = call ptr @llvm.objc.retainAutoreleasedReturnValue(ptr [[CALL_INNER]])
 // CHECK-NEXT: store ptr [[RETAINED]], ptr [[RETADDR:%.*]]
@@ -93,7 +93,7 @@ - (instancetype) clone {
 }
 
 // InnerFn will release the value since it is "consumed".
-// CHECK: define hidden double @"\01-[Shape distanceFrom:]_inner"(ptr noundef nonnull %{{.*}}, ptr noundef [[S:%.*]]) #0 {
+// CHECK: define hidden double @"\01-[Shape distanceFrom:]_nonnull"(ptr noundef nonnull %{{.*}}, ptr noundef [[S:%.*]]) #0 {
 // CHECK: {{%.*}} = alloca ptr
 // CHECK: [[S_ADDR:%.*]] = alloca ptr
 // CHECK: store ptr [[S]], ptr [[S_ADDR]]
diff --git a/clang/test/CodeGenObjC/direct-method-nil-check-thunk-consumed.m b/clang/test/CodeGenObjC/direct-method-nil-check-thunk-consumed.m
index 482cc2ebedc03..11b37ea3537fa 100644
--- a/clang/test/CodeGenObjC/direct-method-nil-check-thunk-consumed.m
+++ b/clang/test/CodeGenObjC/direct-method-nil-check-thunk-consumed.m
@@ -27,11 +27,11 @@ - (instancetype)initWithX:(int)x Y:(int)y  {
 //  1. Call inner set (because we alreaday know self is not null)
 //  2. Call thunk get (because we don't know if s is null)
 //  3. Release s.
-// CHECK-LABEL: define hidden void @"\01-[Shape move:]_inner"
+// CHECK-LABEL: define hidden void @"\01-[Shape move:]_nonnull"
 // CHECK: {{.*}} = call i32 @"\01-[Shape x]"
-// CHECK: call void @"\01-[Shape setX:]_inner"
+// CHECK: call void @"\01-[Shape setX:]_nonnull"
 // CHECK: {{.*}} = call i32 @"\01-[Shape y]"
-// CHECK: call void @"\01-[Shape setY:]_inner"
+// CHECK: call void @"\01-[Shape setY:]_nonnull"
 // CHECK: call void @llvm.objc.storeStrong
 // CHECK-LABEL: }
 
diff --git a/clang/test/CodeGenObjC/direct-method-nil-check-thunk-opt.m b/clang/test/CodeGenObjC/direct-method-nil-check-thunk-opt.m
index fe4008bfa13e4..6896673bf3179 100644
--- a/clang/test/CodeGenObjC/direct-method-nil-check-thunk-opt.m
+++ b/clang/test/CodeGenObjC/direct-method-nil-check-thunk-opt.m
@@ -16,17 +16,17 @@ - (int)fibWithN:(int)n __attribute__((objc_direct));
 
 @implementation Fib
 // With optimization, the inner function call should be a tail call.
-// OPT-LABEL: define hidden i32 @"\01-[Fib fibWithN:]_inner"
-// OPT: {{.*}} tail call i32 @"\01-[Fib fibWithN:]_inner"
+// OPT-LABEL: define hidden i32 @"\01-[Fib fibWithN:]_nonnull"
+// OPT: {{.*}} tail call i32 @"\01-[Fib fibWithN:]_nonnull"
 
 // The inner function knows that self is non null so it can call the method without the nil check.
-// CHECK-LABEL: define hidden i32 @"\01-[Fib fibWithN:]_inner"
-// CHECK: {{.*}} call i32 @"\01-[Fib fibWithN:]_inner"
-// CHECK: {{.*}} call i32 @"\01-[Fib fibWithN:]_inner"
+// CHECK-LABEL: define hidden i32 @"\01-[Fib fibWithN:]_nonnull"
+// CHECK: {{.*}} call i32 @"\01-[Fib fibWithN:]_nonnull"
+// CHECK: {{.*}} call i32 @"\01-[Fib fibWithN:]_nonnull"
 
 // Thunk function calls the inner function as usual.
 // CHECK-LABEL: define hidden i32 @"\01-[Fib fibWithN:]"
-// CHECK: {{.*}} call i32 @"\01-[Fib fibWithN:]_inner"
+// CHECK: {{.*}} call i32 @"\01-[Fib fibWithN:]_nonnull"
 - (int)fibWithN:(int)n {
   if (n <= 0) return 0;
   if (n == 1) return 1;
@@ -43,13 +43,13 @@ - (int)privateMethod:(int)n __attribute__((objc_direct));
 @implementation SubRoot
 - (int)calculateWithPtr:(int*)ptr {
   // For inner functions, it is trivial to reason that the receiver `self` can't be null
-  // CHECK-LABEL: define hidden i32 @"\01-[SubRoot calculateWithPtr:]_inner"
-  // CHECK: {{.*}} = call i32 @"\01-[SubRoot val]_inner"
-  // CHECK: call void @"\01-[SubRoot setVal:]_inner"
-  // CHECK: {{.*}} = call i32 @"\01-[Root privateLoadWithOffset:]_inner"
-  // CHECK: {{.*}} = call i32 @"\01-[SubRoot privateMethod:]_inner"
-  // CHECK: {{.*}} = call i32 @"\01-[Root idx]_inner"
-  // CHECK: call void @"\01-[Root setIdx:]_inner"
+  // CHECK-LABEL: define hidden i32 @"\01-[SubRoot calculateWithPtr:]_nonnull"
+  // CHECK: {{.*}} = call i32 @"\01-[SubRoot val]_nonnull"
+  // CHECK: call void @"\01-[SubRoot setVal:]_nonnull"
+  // CHECK: {{.*}} = call i32 @"\01-[Root privateLoadWithOffset:]_nonnull"
+  // CHECK: {{.*}} = call i32 @"\01-[SubRoot privateMethod:]_nonnull"
+  // CHECK: {{.*}} = call i32 @"\01-[Root idx]_nonnull"
+  // CHECK: call void @"\01-[Root setIdx:]_nonnull"
   int ret = [self val];
   [self setVal:*ptr];
   ret += [self privateLoadWithOffset:ptr];
@@ -62,7 +62,7 @@ - (int)calculateWithPtr:(int*)ptr {
 
 // The thunk declarations don't exist since all calls to them are non null.
 // We trust that these symbols will be generated when the definition is available.
-// CHECK-LABEL: declare i32 @"\01-[Root privateLoadWithOffset:]_inner"(ptr, ptr)
-// CHECK-LABEL: declare i32 @"\01-[SubRoot privateMethod:]_inner"(ptr, i32)
-// CHECK-LABEL: declare i32 @"\01-[Root idx]_inner"(ptr)
-// CHECK-LABEL: declare void @"\01-[Root setIdx:]_inner"(ptr, i32)
+// CHECK-LABEL: declare i32 @"\01-[Root privateLoadWithOffset:]_nonnull"(ptr, ptr)
+// CHECK-LABEL: declare i32 @"\01-[SubRoot privateMethod:]_nonnull"(ptr, i32)
+// CHECK-LABEL: declare i32 @"\01-[Root idx]_nonnull"(ptr)
+// CHECK-LABEL: declare void @"\01-[Root setIdx:]_nonnull"(ptr, i32)
diff --git a/clang/test/CodeGenObjC/direct-method-nil-check-thunk-vararg.m b/clang/test/CodeGenObjC/direct-method-nil-check-thunk-vararg.m
index 6f5934e79ee0c..85d5b838cbb4b 100644
--- a/clang/test/CodeGenObjC/direct-method-nil-check-thunk-vararg.m
+++ b/clang/test/CodeGenObjC/direct-method-nil-check-thunk-vararg.m
@@ -25,7 +25,7 @@ - (void)printWithFormat:(const char *)format, ... {
   va_list args;
   // CHECK: call void @llvm.va_start
   va_start(args, format);
-  // CHECK: call void @"\01-[Root vprintWithFormat:Args:]_inner"
+  // CHECK: call void @"\01-[Root vprintWithFormat:Args:]_nonnull"
   [self vprintWithFormat:format Args:args];
   // CHECK: call void @llvm.va_end
   va_end(args);
@@ -33,11 +33,11 @@ - (void)printWithFormat:(const char *)format, ... {
 // CHECK-NOT: <Root printWithFormat]_inner
 
 // CHECK-LABEL: define {{.*}} void @"\01-[Root vprintWithFormat:Args:]"
-// CHECK: call void @"\01-[Root vprintWithFormat:Args:]_inner"
+// CHECK: call void @"\01-[Root vprintWithFormat:Args:]_nonnull"
 
-// CHECK-LABEL: define {{.*}} void @"\01-[Root vprintWithFormat:Args:]_inner"
+// CHECK-LABEL: define {{.*}} void @"\01-[Root vprintWithFormat:Args:]_nonnull"
 -(void)vprintWithFormat:(const char *)format Args:(va_list) args{
-  // CHECK: call void @"\01-[Root vprintWithFormat:Args:]_inner"
+  // CHECK: call void @"\01-[Root vprintWithFormat:Args:]_nonnull"
   [self vprintWithFormat:format Args:args];
   // CHECK: call i32 @vprintf
   vprintf(format, args);
diff --git a/clang/test/CodeGenObjC/direct-method-nil-check-thunk.m b/clang/test/CodeGenObjC/direct-method-nil-check-thunk.m
index 1d336708feae3..398103afa5ada 100644
--- a/clang/test/CodeGenObjC/direct-method-nil-check-thunk.m
+++ b/clang/test/CodeGenObjC/direct-method-nil-check-thunk.m
@@ -23,13 +23,13 @@ - (int)getInt __attribute__((objc_direct));
 @end
 
 @implementation Root
-// CHECK-LABEL: define hidden i32 @"\01-[Root intProperty2]_inner"(ptr noundef nonnull %{{.*}}
+// CHECK-LABEL: define hidden i32 @"\01-[Root intProperty2]_nonnull"(ptr noundef nonnull %{{.*}}
 // CHECK-LABEL: define hidden i32 @"\01-[Root intProperty2]"(ptr noundef %{{.*}}
 - (int)intProperty2 {
   return 42;
 }
 
-// CHECK-LABEL: define hidden i32 @"\01-[Root getInt]_inner"(ptr noundef nonnull %{{.*}}
+// CHECK-LABEL: define hidden i32 @"\01-[Root getInt]_nonnull"(ptr noundef nonnull %{{.*}}
 // CHECK-NEXT: entry:
 // CHECK-NEXT: [[SELFADDR:%.*]] = alloca ptr,
 // CHECK-NEXT: store ptr %{{.*}}, ptr [[SELFADDR]],
@@ -54,7 +54,7 @@ - (int)getInt __attribute__((objc_direct)) {
 
   // set value
   // CHECK-LABEL: objc_direct_method.cont:
-  // CHECK-NEXT: [[RET:%.*]] = call i32 @"\01-[Root getInt]_inner"(ptr noundef nonnull [[ARG0]]
+  // CHECK-NEXT: [[RET:%.*]] = call i32 @"\01-[Root getInt]_nonnull"(ptr noundef nonnull [[ARG0]]
   // CHECK-NEXT: store i32 [[RET]], ptr [[RETVAL]]
   // CHECK-NEXT: br label %return
 
@@ -65,7 +65,7 @@ - (int)getInt __attribute__((objc_direct)) {
   return 42;
 }
 
-// CHECK-NOT: @"\01+[Root classGetInt]_inner"
+// CHECK-NOT: @"\01+[Root classGetInt]_nonnull"
 + (int)classGetInt __attribute__((objc_direct)) {
   // CHECK: define hidden i32 @"\01+[Root classGetInt]"(ptr noundef [[ARGSELF:%.*]])
   // [self self]
@@ -80,7 +80,7 @@ + (int)classGetInt __attribute__((objc_direct)) {
   return 42;
 }
 
-// CHECK-LABEL: define hidden i64 @"\01-[Root getComplex]_inner"(ptr noundef nonnull %{{.*}}
+// CHECK-LABEL: define hidden i64 @"\01-[Root getComplex]_nonnull"(ptr noundef nonnull %{{.*}}
 // CHECK-LABEL: entry:
 // CHECK-NEXT: [[RETVAL:%.*]] = alloca
 // CHECK-NEXT: [[SELFADDR:%.*]] = alloca ptr,
@@ -107,7 +107,7 @@ - (struct my_complex_struct)getComplex __attribute__((objc_direct)) {
 
   // call the inner function set value
   // CHECK-LABEL: objc_direct_method.cont:
-  // CHECK-NEXT: [[CALL:%.*]] = call i64 @"\01-[Root getComplex]_inner"(ptr noundef nonnull [[ARGSELF]])
+  // CHECK-NEXT: [[CALL:%.*]] = call i64 @"\01-[Root getComplex]_nonnull"(ptr noundef nonnull [[ARGSELF]])
   // CHECK-NEXT: store i64 [[CALL]], ptr [[RETVAL]]
   // CHECK-NEXT: br label
 
@@ -119,14 +119,14 @@ - (struct my_complex_struct)getComplex __attribute__((objc_direct)) {
   return st;
 }
 
-// CHECK-NOT: @"\01+[Root classGetComplex]_inner"
+// CHECK-NOT: @"\01+[Root classGetComplex]_nonnull"
 + (struct my_complex_struct)classGetComplex __attribute__((objc_direct)) {
   // CHECK-LABEL: define hidden i64 @"\01+[Root classGetComplex]"(ptr noundef
   struct my_complex_struct st = {.a = 42};
   return st;
 }
 
-// CHECK-LABEL: define hidden void @"\01-[Root getAggregate]_inner"(
+// CHECK-LABEL: define hidden void @"\01-[Root getAggregate]_nonnull"(
 // CHECK: ptr {{.*}} sret(%struct.my_aggregate_struct) align 4 [[RETVAL:%[^,]*]], ptr noundef nonnull %self
 // CHECK-LABEL: entry:
 // CHECK-NEXT: [[SELFADDR:%.*]] = alloca ptr,
@@ -155,7 +155,7 @@ - (struct my_aggregate_struct)getAggregate __attribute__((objc_direct)) {
 
   // set value
   // CHECK-LABEL: objc_direct_method.cont:
-  // CHECK-NEXT: call void @"\01-[Root getAggregate]_inner"(ptr dead_on_unwind noalias writable sret(%struct.my_aggregate_struct) align 4 [[RETVAL]], ptr noundef nonnull [[ARGSELF]])
+  // CHECK-NEXT: call void @"\01-[Root getAggregate]_nonnull"(ptr dead_on_unwind noalias writable sret(%struct.my_aggregate_struct) align 4 [[RETVAL]], ptr noundef nonnull [[ARGSELF]])
   // CHECK-NEXT: br label
 
   // return
@@ -166,13 +166,13 @@ - (struct my_aggregate_struct)getAggregate __attribute__((objc_direct)) {
 }
 
 // CHECK-LABEL: define hidden void @"\01+[Root classGetAggregate]"({{.*}}, ptr noundef {{.*}})
-// CHECK-NOT: @"\01+[Root classGetAggregate]_inner"
+// CHECK-NOT: @"\01+[Root classGetAggregate]_nonnull"
 + (struct my_aggregate_struct)classGetAggregate __attribute__((objc_direct)) {
   struct my_aggregate_struct st = {.a = 42};
   return st;
 }
 
-// CHECK-LABEL: define hidden void @"\01-[Root accessCmd]_inner"(ptr noundef nonnull
+// CHECK-LABEL: define hidden void @"\01-[Root accessCmd]_nonnull"(ptr noundef nonnull
 // CHECK-LABEL: entry:
 // CHECK-NEXT: [[SELFADDR:%.*]] = alloca ptr,
 // CHECK-NEXT: [[CMDVAL:%_cmd]] = alloca ptr,
@@ -192,18 +192,18 @@ - (void)accessCmd __attribute__((objc_direct)) {
   // CHECK-NEXT: br label %return
 
   // CHECK-LABEL: objc_direct_method.cont:
-  // CHECK-NEXT: call void @"\01-[Root accessCmd]_inner"(ptr noundef nonnull %{{.*}})
+  // CHECK-NEXT: call void @"\01-[Root accessCmd]_nonnull"(ptr noundef nonnull %{{.*}})
   // CHECK-NEXT: br label %return
   SEL sel = _cmd;
 }
 
 @end
-// CHECK-LABEL: define hidden i32 @"\01-[Root intProperty]_inner"(ptr noundef nonnull %{{.*}})
+// CHECK-LABEL: define hidden i32 @"\01-[Root intProperty]_nonnull"(ptr noundef nonnull %{{.*}})
 // CHECK-LABEL: define hidden i32 @"\01-[Root intProperty]"(ptr noundef %{{.*}})
 
 // Check the synthesized objectProperty calls objc_getProperty(); this also
 // checks that the synthesized method passes undef for the `cmd` argument.
-// CHECK-LABEL: define hidden ptr @"\01-[Root objectProperty]_inner"(ptr noundef nonnull {{%.*}})
+// CHECK-LABEL: define hidden ptr @"\01-[Root objectProperty]_nonnull"(ptr noundef nonnull {{%.*}})
 // CHECK-NEXT: entry:
 // CHECK-NEXT: [[SELFADDR:%.*]] = alloca ptr,
 // CHECK-NEXT: store ptr %{{.*}}, ptr [[SELFADDR]],
@@ -215,7 +215,7 @@ - (void)accessCmd __attribute__((objc_direct)) {
 // CHECK: [[RETADDR:%.*]] = alloca ptr,
 
 // CHECK-LABEL: objc_direct_method.cont:
-// CHECK-NEXT: [[RETVAL:%.*]] = call ptr @"\01-[Root objectProperty]_inner"
+// CHECK-NEXT: [[RETVAL:%.*]] = call ptr @"\01-[Root objectProperty]_nonnull"
 // CHECK-NEXT: [[RETAINED_RET:%.*]] = notail call ptr @llvm.objc.retainAutoreleasedReturnValue(ptr [[RETVAL]])
 // CHECK-NEXT: store ptr [[RETAINED_RET]], ptr [[RETADDR]],
 // CHECK-NEXT: br label %return
@@ -243,27 +243,27 @@ - (int)directMethodInCategory __attribute__((objc_direct));
 
 __attribute__((objc_direct_members))
 @implementation Foo
-// CHECK-LABEL: define hidden i32 @"\01-[Foo directMethodInExtension]_inner"(
+// CHECK-LABEL: define hidden i32 @"\01-[Foo directMethodInExtension]_nonnull"(
 // CHECK-LABEL: define hidden i32 @"\01-[Foo directMethodInExtension]"(
 - (int)directMethodInExtension {
   return 42;
 }
-// CHECK-LABEL: define hidden i32 @"\01-[Foo getDirect_setDynamic]_inner"(
+// CHECK-LABEL: define hidden i32 @"\01-[Foo getDirect_setDynamic]_nonnull"(
 // CHECK-LABEL: define hidden i32 @"\01-[Foo getDirect_setDynamic]"(
 // CHECK-LABEL: define internal void @"\01-[Foo setGetDirect_setDynamic:]"(
 // CHECK-LABEL: define internal i32 @"\01-[Foo getDynamic_setDirect]"(
-// CHECK-LABEL: define hidden void @"\01-[Foo setGetDynamic_setDirect:]_inner"(
+// CHECK-LABEL: define hidden void @"\01-[Foo setGetDynamic_setDirect:]_nonnull"(
 // CHECK-LABEL: define hidden void @"\01-[Foo setGetDynamic_setDirect:]"(
 // CHECK-LABEL: define internal void @"\01-[Foo .cxx_destruct]"(
 @end
 
 @implementation Foo (Cat)
-// CHECK-LABEL: define hidden i32 @"\01-[Foo directMethodInCategory]_inner"(
+// CHECK-LABEL: define hidden i32 @"\01-[Foo directMethodInCategory]_nonnull"(
 // CHECK-LABEL: define hidden i32 @"\01-[Foo directMethodInCategory]"(
 - (int)directMethodInCategory {
   return 42;
 }
-// CHECK-LABEL: define hidden i32 @"\01-[Foo directMethodInCategoryNoDecl]_inner"(
+// CHECK-LABEL: define hidden i32 @"\01-[Foo directMethodInCategoryNoDecl]_nonnull"(
 // CHECK-LABEL: define hidden i32 @"\01-[Foo directMethodInCategoryNoDecl]"(
 - (int)directMethodInCategoryNoDecl __attribute__((objc_direct)) {
   return 42;
diff --git a/clang/test/CodeGenObjC/direct-method-ret-mismatch.m b/clang/test/CodeGenObjC/direct-method-ret-mismatch.m
index 57df5589da6a3..edbb8ed11c193 100644
--- a/clang/test/CodeGenObjC/direct-method-ret-mismatch.m
+++ b/clang/test/CodeGenObjC/direct-method-ret-mismatch.m
@@ -19,7 +19,7 @@ - (id)method {
 }
 @end
 // The inner function should not contain any nil check anymore.
-// CHECK-THUNK-LABEL: define hidden ptr @"\01-[Root method]_inner"(ptr noundef nonnull
+// CHECK-THUNK-LABEL: define hidden ptr @"\01-[Root method]_nonnull"(ptr noundef nonnull
 // CHECK-THUNK-NOT: br i1 %1, label %objc_direct_method.self_is_nil, label %objc_direct_method.cont
 
 // The direct function contains the nil check.

>From a98b4864691c7555a91e05ee5f661ca070b82073 Mon Sep 17 00:00:00 2001
From: Peter Rong <PeterRong at meta.com>
Date: Mon, 10 Mar 2025 17:09:34 -0700
Subject: [PATCH 11/12] isThunk -> isNilCheckThunk

Signed-off-by: Peter Rong <PeterRong at meta.com>
---
 clang/include/clang/AST/Mangle.h | 2 +-
 clang/lib/AST/Mangle.cpp         | 4 ++--
 2 files changed, 3 insertions(+), 3 deletions(-)

diff --git a/clang/include/clang/AST/Mangle.h b/clang/include/clang/AST/Mangle.h
index 47e482586496e..58d2fc69de50a 100644
--- a/clang/include/clang/AST/Mangle.h
+++ b/clang/include/clang/AST/Mangle.h
@@ -45,7 +45,7 @@ class VarDecl;
 void mangleObjCMethodName(raw_ostream &OS, bool includePrefixByte,
                           bool isInstanceMethod, StringRef ClassName,
                           std::optional<StringRef> CategoryName,
-                          StringRef MethodName, bool isThunk);
+                          StringRef MethodName, bool isNilCheckThunk);
 
 /// MangleContext - Context for tracking state which persists across multiple
 /// calls to the C++ name mangler.
diff --git a/clang/lib/AST/Mangle.cpp b/clang/lib/AST/Mangle.cpp
index c01a29ad0891d..2e202441f7437 100644
--- a/clang/lib/AST/Mangle.cpp
+++ b/clang/lib/AST/Mangle.cpp
@@ -32,7 +32,7 @@ using namespace clang;
 void clang::mangleObjCMethodName(raw_ostream &OS, bool includePrefixByte,
                                  bool isInstanceMethod, StringRef ClassName,
                                  std::optional<StringRef> CategoryName,
-                                 StringRef MethodName, bool isThunk) {
+                                 StringRef MethodName, bool isNilCheckThunk) {
   // \01+[ContainerName(CategoryName) SelectorName]
   if (includePrefixByte)
     OS << "\01";
@@ -44,7 +44,7 @@ void clang::mangleObjCMethodName(raw_ostream &OS, bool includePrefixByte,
   OS << " ";
   OS << MethodName;
   OS << ']';
-  if (!isThunk)
+  if (!isNilCheckThunk)
     OS << "_nonnull";
 }
 // FIXME: For blocks we currently mimic GCC's mangling scheme, which leaves

>From cb27bd7c79052e6e4aa0abcb7420590372f4192e Mon Sep 17 00:00:00 2001
From: Peter Rong <PeterRong at meta.com>
Date: Tue, 11 Mar 2025 10:34:38 -0700
Subject: [PATCH 12/12] Use hasNilCheck to be more clear

---
 clang/include/clang/AST/Mangle.h | 2 +-
 clang/lib/AST/Mangle.cpp         | 8 ++++----
 2 files changed, 5 insertions(+), 5 deletions(-)

diff --git a/clang/include/clang/AST/Mangle.h b/clang/include/clang/AST/Mangle.h
index 58d2fc69de50a..6b5c15d2ccf18 100644
--- a/clang/include/clang/AST/Mangle.h
+++ b/clang/include/clang/AST/Mangle.h
@@ -161,7 +161,7 @@ class MangleContext {
   void mangleObjCMethodName(const ObjCMethodDecl *MD, raw_ostream &OS,
                             bool includePrefixByte = true,
                             bool includeCategoryNamespace = true,
-                            bool isThunk = true) const;
+                            bool hasNilCheck = true) const;
   void mangleObjCMethodNameAsSourceName(const ObjCMethodDecl *MD,
                                         raw_ostream &) const;
 
diff --git a/clang/lib/AST/Mangle.cpp b/clang/lib/AST/Mangle.cpp
index 2e202441f7437..e89faa85166b6 100644
--- a/clang/lib/AST/Mangle.cpp
+++ b/clang/lib/AST/Mangle.cpp
@@ -32,7 +32,7 @@ using namespace clang;
 void clang::mangleObjCMethodName(raw_ostream &OS, bool includePrefixByte,
                                  bool isInstanceMethod, StringRef ClassName,
                                  std::optional<StringRef> CategoryName,
-                                 StringRef MethodName, bool isNilCheckThunk) {
+                                 StringRef MethodName, bool hasNilCheck) {
   // \01+[ContainerName(CategoryName) SelectorName]
   if (includePrefixByte)
     OS << "\01";
@@ -44,7 +44,7 @@ void clang::mangleObjCMethodName(raw_ostream &OS, bool includePrefixByte,
   OS << " ";
   OS << MethodName;
   OS << ']';
-  if (!isNilCheckThunk)
+  if (!hasNilCheck)
     OS << "_nonnull";
 }
 // FIXME: For blocks we currently mimic GCC's mangling scheme, which leaves
@@ -347,7 +347,7 @@ void MangleContext::mangleObjCMethodName(const ObjCMethodDecl *MD,
                                          raw_ostream &OS,
                                          bool includePrefixByte,
                                          bool includeCategoryNamespace,
-                                         bool isThunk) const {
+                                         bool hasNilCheck) const {
   if (getASTContext().getLangOpts().ObjCRuntime.isGNUFamily()) {
     // This is the mangling we've always used on the GNU runtimes, but it
     // has obvious collisions in the face of underscores within class
@@ -400,7 +400,7 @@ void MangleContext::mangleObjCMethodName(const ObjCMethodDecl *MD,
   llvm::raw_string_ostream MethodNameOS(MethodName);
   MD->getSelector().print(MethodNameOS);
   clang::mangleObjCMethodName(OS, includePrefixByte, MD->isInstanceMethod(),
-                              ClassName, CategoryName, MethodName, isThunk);
+                              ClassName, CategoryName, MethodName, hasNilCheck);
 }
 
 void MangleContext::mangleObjCMethodNameAsSourceName(const ObjCMethodDecl *MD,



More information about the cfe-commits mailing list