llvmbot wrote:
<!--LLVM PR SUMMARY COMMENT--> @llvm/pr-subscribers-clang-driver Author: Peter Rong (DataCorrupted) <details> <summary>Changes</summary> ## TL;DR This is a stack of PRs implementing features to expose direct methods ABI. You can see the RFC, design, and discussion [here](https://discourse.llvm.org/t/rfc-optimizing-code-size-of-objc-direct-by-exposing-function-symbols-and-moving-nil-checks-to-thunks/88866). https://github.com/llvm/llvm-project/pull/170616 **Flag -fexpose-objc-direct set up** https://github.com/llvm/llvm-project/pull/170617 Code refactoring to ease later reviews https://github.com/llvm/llvm-project/pull/170618 Thunk generation https://github.com/llvm/llvm-project/pull/170619 Optimizations, some class objects can be known to be realized ## Implementation details 1. Add a flag. I used `expose-direct-method` instead of `-fobjc-direct-caller-thunks` in the proposal for two reasons: a. It conveys our intention more clearly, that we are trying to remove the `\01` byte before the name of direct method so the developers can expose a symbol if they want b. Much of the code is actually handling corner cases of var_arg, which don't have a thunk. Thus the name `thunk` can be confusing. 2. Clean up and set up helper functions to implement later a. `canMessageReceiverBeNull` / `canClassObjectBeUnrealized` these two functions will be helpful later to determine which function (true implementation or nil check thunk) we should dispatch a call to. Formatting. b. `getSymbolNameForMethod` has a new argument `includePrefixByte`, which allows us to erase the prefixing `\01` when the flag is enabled c. `shouldExposeSymbol` is the single source of truth of what we should do. It not only checks for the flag, but also whether the method is qualified and we are in the right runtime. A method that `shouldExposeSymbol` is either `shouldHaveNilCheckThunk` or `shouldHaveNilCheckInline`. ## Tests None, existing ones shouldn't break --- Full diff: https://github.com/llvm/llvm-project/pull/170616.diff 7 Files Affected: - (modified) clang/include/clang/AST/DeclObjC.h (+6) - (modified) clang/include/clang/Basic/CodeGenOptions.def (+2) - (modified) clang/include/clang/Options/Options.td (+5) - (modified) clang/lib/CodeGen/CGObjCRuntime.cpp (+15-11) - (modified) clang/lib/CodeGen/CGObjCRuntime.h (+14-1) - (modified) clang/lib/CodeGen/CodeGenModule.h (+26) - (modified) clang/lib/Driver/ToolChains/Clang.cpp (+4) ``````````diff diff --git a/clang/include/clang/AST/DeclObjC.h b/clang/include/clang/AST/DeclObjC.h index 2541edba83855..e2292cbdea042 100644 --- a/clang/include/clang/AST/DeclObjC.h +++ b/clang/include/clang/AST/DeclObjC.h @@ -482,6 +482,12 @@ class ObjCMethodDecl : public NamedDecl, public DeclContext { /// True if the method is tagged as objc_direct bool isDirectMethod() const; + /// Check if this direct method can move nil-check to thunk. + /// Variadic functions cannot use thunks (musttail incompatible with va_arg) + bool canHaveNilCheckThunk() const { + return isDirectMethod() && !isVariadic(); + } + /// True if the method has a parameter that's destroyed in the callee. bool hasParamDestroyedInCallee() const; diff --git a/clang/include/clang/Basic/CodeGenOptions.def b/clang/include/clang/Basic/CodeGenOptions.def index 76a6463881c6f..a7e8564c9e83c 100644 --- a/clang/include/clang/Basic/CodeGenOptions.def +++ b/clang/include/clang/Basic/CodeGenOptions.def @@ -210,6 +210,8 @@ ENUM_CODEGENOPT(ObjCDispatchMethod, ObjCDispatchMethodKind, 2, Legacy, Benign) /// Replace certain message sends with calls to ObjC runtime entrypoints CODEGENOPT(ObjCConvertMessagesToRuntimeCalls , 1, 1, Benign) CODEGENOPT(ObjCAvoidHeapifyLocalBlocks, 1, 0, Benign) +/// Expose objc_direct method symbols publicly and optimize nil checks. +CODEGENOPT(ObjCExposeDirectMethods, 1, 0, Benign) // The optimization options affect frontend options, which in turn do affect the AST. diff --git a/clang/include/clang/Options/Options.td b/clang/include/clang/Options/Options.td index 756d6deed7130..ddc04b30cbaa2 100644 --- a/clang/include/clang/Options/Options.td +++ b/clang/include/clang/Options/Options.td @@ -3775,6 +3775,11 @@ defm objc_avoid_heapify_local_blocks : BoolFOption<"objc-avoid-heapify-local-blo PosFlag<SetTrue, [], [ClangOption], "Try">, NegFlag<SetFalse, [], [ClangOption], "Don't try">, BothFlags<[], [CC1Option], " to avoid heapifying local blocks">>; +defm objc_expose_direct_methods : BoolFOption<"objc-expose-direct-methods", + CodeGenOpts<"ObjCExposeDirectMethods">, DefaultFalse, + PosFlag<SetTrue, [], [ClangOption, CC1Option], + "Expose direct method symbols and move nil checks to caller-side thunks">, + NegFlag<SetFalse>>; defm disable_block_signature_string : BoolFOption<"disable-block-signature-string", CodeGenOpts<"DisableBlockSignatureString">, DefaultFalse, PosFlag<SetTrue, [], [ClangOption], "Disable">, diff --git a/clang/lib/CodeGen/CGObjCRuntime.cpp b/clang/lib/CodeGen/CGObjCRuntime.cpp index 76e0054f4c9da..a4b4460fdc49c 100644 --- a/clang/lib/CodeGen/CGObjCRuntime.cpp +++ b/clang/lib/CodeGen/CGObjCRuntime.cpp @@ -382,11 +382,9 @@ CGObjCRuntime::getMessageSendInfo(const ObjCMethodDecl *method, return MessageSendInfo(argsInfo, signatureType); } -bool CGObjCRuntime::canMessageReceiverBeNull(CodeGenFunction &CGF, - const ObjCMethodDecl *method, - bool isSuper, - const ObjCInterfaceDecl *classReceiver, - llvm::Value *receiver) { +bool CGObjCRuntime::canMessageReceiverBeNull( + CodeGenFunction &CGF, const ObjCMethodDecl *method, bool isSuper, + const ObjCInterfaceDecl *classReceiver, llvm::Value *receiver) { // Super dispatch assumes that self is non-null; even the messenger // doesn't have a null check internally. if (isSuper) @@ -399,8 +397,7 @@ bool CGObjCRuntime::canMessageReceiverBeNull(CodeGenFunction &CGF, // If we're emitting a method, and self is const (meaning just ARC, for now), // and the receiver is a load of self, then self is a valid object. - if (auto curMethod = - dyn_cast_or_null<ObjCMethodDecl>(CGF.CurCodeDecl)) { + if (auto curMethod = dyn_cast_or_null<ObjCMethodDecl>(CGF.CurCodeDecl)) { auto self = curMethod->getSelfDecl(); if (self->getType().isConstQualified()) { if (auto LI = dyn_cast<llvm::LoadInst>(receiver->stripPointerCasts())) { @@ -416,6 +413,13 @@ bool CGObjCRuntime::canMessageReceiverBeNull(CodeGenFunction &CGF, return true; } +bool CGObjCRuntime::canClassObjectBeUnrealized( + const ObjCInterfaceDecl *CalleeClassDecl, CodeGenFunction &CGF) const { + // TODO + // Otherwise, assume it can be unrealized. + return true; +} + bool CGObjCRuntime::isWeakLinkedClass(const ObjCInterfaceDecl *ID) { do { if (ID->isWeakImported()) @@ -466,11 +470,11 @@ clang::CodeGen::emitObjCProtocolObject(CodeGenModule &CGM, } std::string CGObjCRuntime::getSymbolNameForMethod(const ObjCMethodDecl *OMD, - bool includeCategoryName) { + bool includeCategoryName, + bool includePrefixByte) { std::string buffer; llvm::raw_string_ostream out(buffer); - CGM.getCXXABI().getMangleContext().mangleObjCMethodName(OMD, out, - /*includePrefixByte=*/true, - includeCategoryName); + CGM.getCXXABI().getMangleContext().mangleObjCMethodName( + OMD, out, includePrefixByte, includeCategoryName); return buffer; } diff --git a/clang/lib/CodeGen/CGObjCRuntime.h b/clang/lib/CodeGen/CGObjCRuntime.h index 72997bf6348ae..0f0cc6ba09200 100644 --- a/clang/lib/CodeGen/CGObjCRuntime.h +++ b/clang/lib/CodeGen/CGObjCRuntime.h @@ -117,7 +117,8 @@ class CGObjCRuntime { virtual ~CGObjCRuntime(); std::string getSymbolNameForMethod(const ObjCMethodDecl *method, - bool includeCategoryName = true); + bool includeCategoryName = true, + bool includePrefixByte = true); /// Generate the function required to register all Objective-C components in /// this compilation unit with the runtime library. @@ -322,10 +323,22 @@ class CGObjCRuntime { MessageSendInfo getMessageSendInfo(const ObjCMethodDecl *method, QualType resultType, CallArgList &callArgs); + bool canMessageReceiverBeNull(CodeGenFunction &CGF, const ObjCMethodDecl *method, bool isSuper, const ObjCInterfaceDecl *classReceiver, llvm::Value *receiver); + + /// Check if a class object can be unrealized (not yet initialized). + /// Returns true if the class may be unrealized, false if provably realized. + /// + /// TODO: Returns false if: + /// - An instance method on the same class was called in a dominating path + /// - The class was explicitly realized earlier in control flow + /// - Note: [Parent foo] does NOT realize Child (inheritance care needed) + virtual bool canClassObjectBeUnrealized(const ObjCInterfaceDecl *ClassDecl, + CodeGenFunction &CGF) const; + static bool isWeakLinkedClass(const ObjCInterfaceDecl *cls); /// Destroy the callee-destroyed arguments of the given method, diff --git a/clang/lib/CodeGen/CodeGenModule.h b/clang/lib/CodeGen/CodeGenModule.h index a253bcda2d06c..f65739de10957 100644 --- a/clang/lib/CodeGen/CodeGenModule.h +++ b/clang/lib/CodeGen/CodeGenModule.h @@ -717,6 +717,32 @@ class CodeGenModule : public CodeGenTypeCache { /// Return true iff an Objective-C runtime has been configured. bool hasObjCRuntime() { return !!ObjCRuntime; } + /// Check if a direct method should have its symbol exposed (no \01 prefix). + /// This applies to ALL direct methods (including variadic). + /// Returns false if OMD is null or not a direct method. + bool shouldExposeSymbol(const ObjCMethodDecl *OMD) const { + return OMD && OMD->isDirectMethod() && + getLangOpts().ObjCRuntime.isNeXTFamily() && + getCodeGenOpts().ObjCExposeDirectMethods; + } + + /// Check if a direct method should use nil-check thunks at call sites. + /// This applies only to non-variadic direct methods. + /// Variadic methods cannot use thunks (musttail incompatible with va_arg). + /// Returns false if OMD is null or not eligible for thunks. + bool shouldHaveNilCheckThunk(const ObjCMethodDecl *OMD) const { + return OMD && shouldExposeSymbol(OMD) && OMD->canHaveNilCheckThunk(); + } + + /// Check if a direct method should have inline nil checks at call sites. + /// This applies to direct methods that cannot use thunks (e.g., variadic + /// methods). These methods get exposed symbols but need inline nil checks + /// instead of thunks. Returns false if OMD is null or not eligible for inline + /// nil checks. + bool shouldHaveNilCheckInline(const ObjCMethodDecl *OMD) const { + return OMD && shouldExposeSymbol(OMD) && !OMD->canHaveNilCheckThunk(); + } + const std::string &getModuleNameHash() const { return ModuleNameHash; } /// Return a reference to the configured OpenCL runtime. diff --git a/clang/lib/Driver/ToolChains/Clang.cpp b/clang/lib/Driver/ToolChains/Clang.cpp index 0380568412e62..2c3beb94f2ef8 100644 --- a/clang/lib/Driver/ToolChains/Clang.cpp +++ b/clang/lib/Driver/ToolChains/Clang.cpp @@ -4096,6 +4096,10 @@ static void RenderObjCOptions(const ToolChain &TC, const Driver &D, } } + // Forward -fobjc-expose-direct-methods to cc1 + if (Args.hasArg(options::OPT_fobjc_expose_direct_methods)) + CmdArgs.push_back("-fobjc-expose-direct-methods"); + // When ObjectiveC legacy runtime is in effect on MacOSX, turn on the option // to do Array/Dictionary subscripting by default. if (Arch == llvm::Triple::x86 && T.isMacOSX() && `````````` </details> https://github.com/llvm/llvm-project/pull/170616 _______________________________________________ cfe-commits mailing list [email protected] https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits
