[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