From 3023c1069e7029cbf13d23746273b7a04d4e0d1a Mon Sep 17 00:00:00 2001 From: Peter Rong Date: Mon, 5 Jan 2026 14:42:03 -0800 Subject: [PATCH] [ObjCDirectPreconditionThunk] Adding a flag to with objc_direct symbols' prefix (#170616) ## 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). The stack of the following four PRs completes the whole feature. https://github.com/llvm/llvm-project/pull/170616 **Flag `-fobjc-direct-precondition-thunk` 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 `-fobjc-direct-precondition-thunk` instead of `-fobjc-direct-caller-thunks` as discussed in this PR. 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. `usePreconditionThunk` 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 `usePreconditionThunk` is either `shouldHavePreconditionThunk` or `shouldHavePreconditionInline`. ## Tests Driver tests --------- Signed-off-by: Peter Rong Co-authored-by: Kyungwoo Lee --- clang/include/clang/Basic/CodeGenOptions.def | 2 ++ .../clang/Basic/DiagnosticDriverKinds.td | 3 ++ clang/include/clang/Options/Options.td | 5 ++++ clang/lib/CodeGen/CGObjCRuntime.cpp | 19 +++++------- clang/lib/CodeGen/CGObjCRuntime.h | 5 +++- clang/lib/CodeGen/CodeGenModule.h | 29 +++++++++++++++++++ clang/lib/Driver/ToolChains/Clang.cpp | 13 +++++++++ clang/test/Driver/clang_f_opts.c | 17 +++++++++++ 8 files changed, 81 insertions(+), 12 deletions(-) diff --git a/clang/include/clang/Basic/CodeGenOptions.def b/clang/include/clang/Basic/CodeGenOptions.def index a1572578fb14..3784844ef102 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) +/// Generate direct method precondition thunks to expose symbols and optimize nil checks. +CODEGENOPT(ObjCDirectPreconditionThunk, 1, 0, Benign) // The optimization options affect frontend options, which in turn do affect the AST. diff --git a/clang/include/clang/Basic/DiagnosticDriverKinds.td b/clang/include/clang/Basic/DiagnosticDriverKinds.td index 5cae8fb86347..3fc3c19f2658 100644 --- a/clang/include/clang/Basic/DiagnosticDriverKinds.td +++ b/clang/include/clang/Basic/DiagnosticDriverKinds.td @@ -161,6 +161,9 @@ def warn_drv_unsupported_diag_option_for_flang : Warning< def warn_drv_unsupported_option_for_processor : Warning< "ignoring '%0' option as it is not currently supported for processor '%1'">, InGroup; +def warn_drv_unsupported_option_for_runtime : Warning< + "ignoring '%0' option as it is not currently supported for runtime '%1'">, + InGroup; def warn_drv_unsupported_openmp_library : Warning< "the library '%0=%1' is not supported, OpenMP will not be enabled">, InGroup; diff --git a/clang/include/clang/Options/Options.td b/clang/include/clang/Options/Options.td index 04756ce486ea..dd039fd47426 100644 --- a/clang/include/clang/Options/Options.td +++ b/clang/include/clang/Options/Options.td @@ -3799,6 +3799,11 @@ defm objc_avoid_heapify_local_blocks : BoolFOption<"objc-avoid-heapify-local-blo PosFlag, NegFlag, BothFlags<[], [CC1Option], " to avoid heapifying local blocks">>; +defm objc_direct_precondition_thunk : BoolFOption<"objc-direct-precondition-thunk", + CodeGenOpts<"ObjCDirectPreconditionThunk">, DefaultFalse, + PosFlag, + NegFlag>; defm disable_block_signature_string : BoolFOption<"disable-block-signature-string", CodeGenOpts<"DisableBlockSignatureString">, DefaultFalse, PosFlag, diff --git a/clang/lib/CodeGen/CGObjCRuntime.cpp b/clang/lib/CodeGen/CGObjCRuntime.cpp index 76e0054f4c9d..3d47dc9560c6 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(CGF.CurCodeDecl)) { + if (auto curMethod = dyn_cast_or_null(CGF.CurCodeDecl)) { auto self = curMethod->getSelfDecl(); if (self->getType().isConstQualified()) { if (auto LI = dyn_cast(receiver->stripPointerCasts())) { @@ -466,11 +463,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 72997bf6348a..8cd75b19c4d8 100644 --- a/clang/lib/CodeGen/CGObjCRuntime.h +++ b/clang/lib/CodeGen/CGObjCRuntime.h @@ -117,7 +117,8 @@ public: 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,12 @@ public: MessageSendInfo getMessageSendInfo(const ObjCMethodDecl *method, QualType resultType, CallArgList &callArgs); + bool canMessageReceiverBeNull(CodeGenFunction &CGF, const ObjCMethodDecl *method, bool isSuper, const ObjCInterfaceDecl *classReceiver, llvm::Value *receiver); + 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 e36ad87e67e3..38b052e5cd1d 100644 --- a/clang/lib/CodeGen/CodeGenModule.h +++ b/clang/lib/CodeGen/CodeGenModule.h @@ -717,6 +717,35 @@ public: /// Return true iff an Objective-C runtime has been configured. bool hasObjCRuntime() { return !!ObjCRuntime; } + /// Check if a direct method should use precondition thunks (exposed symbols). + /// This applies to ALL direct methods (including variadic). + /// Returns false if OMD is null or not a direct method. + /// + /// Also checks the runtime family, currently we only support NeXT. + /// TODO: Add support for GNUStep as well. + bool usePreconditionThunk(const ObjCMethodDecl *OMD) const { + return OMD && OMD->isDirectMethod() && + getLangOpts().ObjCRuntime.allowsDirectDispatch() && + getLangOpts().ObjCRuntime.isNeXTFamily() && + getCodeGenOpts().ObjCDirectPreconditionThunk; + } + + /// Check if a direct method should use precondition thunks at call sites. + /// This applies only to non-variadic direct methods. + /// Returns false if OMD is null or not eligible for thunks (variadic + /// methods). + bool shouldHavePreconditionThunk(const ObjCMethodDecl *OMD) const { + return OMD && usePreconditionThunk(OMD) && !OMD->isVariadic(); + } + + /// Check if a direct method should have inline precondition checks at call + /// sites. This applies to direct methods that cannot use thunks (variadic + /// methods). These methods get exposed symbols but need inline precondition + /// checks instead of thunks. Returns false if OMD is null or not eligible. + bool shouldHavePreconditionInline(const ObjCMethodDecl *OMD) const { + return OMD && usePreconditionThunk(OMD) && OMD->isVariadic(); + } + 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 8692200b40a6..a495a433cd15 100644 --- a/clang/lib/Driver/ToolChains/Clang.cpp +++ b/clang/lib/Driver/ToolChains/Clang.cpp @@ -4098,6 +4098,19 @@ static void RenderObjCOptions(const ToolChain &TC, const Driver &D, } } + // Forward -fobjc-direct-precondition-thunk to cc1 + // Defaults to false and needs explict turn on for now + // TODO: switch to default true and needs explict turn off in the future. + // TODO: add support for other runtimes + if (Args.hasFlag(options::OPT_fobjc_direct_precondition_thunk, + options::OPT_fno_objc_direct_precondition_thunk, false)) { + if (Runtime.isNeXTFamily()) { + CmdArgs.push_back("-fobjc-direct-precondition-thunk"); + } else { + D.Diag(diag::warn_drv_unsupported_option_for_runtime) + << "-fobjc-direct-precondition-thunk" << Runtime.getAsString(); + } + } // 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() && diff --git a/clang/test/Driver/clang_f_opts.c b/clang/test/Driver/clang_f_opts.c index e5a23270ea73..5871f1580d6b 100644 --- a/clang/test/Driver/clang_f_opts.c +++ b/clang/test/Driver/clang_f_opts.c @@ -597,6 +597,23 @@ // CHECK_DISABLE_DIRECT: -fobjc-disable-direct-methods-for-testing // CHECK_NO_DISABLE_DIRECT-NOT: -fobjc-disable-direct-methods-for-testing +// RUN: %clang -### --target=arm64-apple-macos10 -xobjective-c -fobjc-direct-precondition-thunk %s 2>&1 | FileCheck -check-prefix=CHECK_DIRECT_PRECONDITION_THUNK %s +// RUN: %clang -### --target=arm64-apple-macos10 -xobjective-c -fno-objc-direct-precondition-thunk %s 2>&1 | FileCheck -check-prefix=CHECK_NO_DIRECT_PRECONDITION_THUNK %s +// RUN: %clang -### --target=arm64-apple-macos10 -xobjective-c -fobjc-direct-precondition-thunk -fno-objc-direct-precondition-thunk %s 2>&1 | FileCheck -check-prefix=CHECK_NO_DIRECT_PRECONDITION_THUNK %s +// RUN: %clang -### --target=arm64-apple-macos10 -xobjective-c -fno-objc-direct-precondition-thunk -fobjc-direct-precondition-thunk %s 2>&1 | FileCheck -check-prefix=CHECK_DIRECT_PRECONDITION_THUNK %s +// RUN: %clang -### --target=arm64-apple-macos10 -xobjective-c %s 2>&1 | FileCheck -check-prefix=CHECK_NO_DIRECT_PRECONDITION_THUNK %s +// CHECK_DIRECT_PRECONDITION_THUNK: "-fobjc-direct-precondition-thunk" +// CHECK_NO_DIRECT_PRECONDITION_THUNK-NOT: -fobjc-direct-precondition-thunk + +// Test that -fobjc-direct-precondition-thunk emits a warning when used with GNU runtime +// and that the flag is not passed to cc1. +// RUN: %clang --target=x86_64-linux-gnu -fobjc-runtime=gnustep-2.0 -fobjc-direct-precondition-thunk -### -c -xobjective-c %s 2>&1 | FileCheck -check-prefix=CHECK_GNUSTEP_WARN %s +// CHECK_GNUSTEP_WARN: warning: ignoring '-fobjc-direct-precondition-thunk' option as it is not currently supported for runtime 'gnustep-2.0' [-Woption-ignored] +// CHECK_GNUSTEP_WARN-NOT: "-fobjc-direct-precondition-thunk" +// RUN: %clang --target=x86_64-linux-gnu -fobjc-runtime=gcc -fobjc-direct-precondition-thunk -### -c -xobjective-c %s 2>&1 | FileCheck -check-prefix=CHECK_GCC_WARN %s +// CHECK_GCC_WARN: warning: ignoring '-fobjc-direct-precondition-thunk' option as it is not currently supported for runtime 'gcc' [-Woption-ignored] +// CHECK_GCC_WARN-NOT: "-fobjc-direct-precondition-thunk" + // RUN: %clang -### -S -fjmc --target=x86_64-unknown-linux %s 2>&1 | FileCheck -check-prefixes=CHECK_JMC_WARN,CHECK_NOJMC %s // RUN: %clang -### -S -fjmc --target=x86_64-pc-windows-msvc %s 2>&1 | FileCheck -check-prefixes=CHECK_JMC_WARN,CHECK_NOJMC %s // RUN: %clang -### -S -fjmc -g --target=x86_64-pc-windows-msvc %s 2>&1 | FileCheck -check-prefix=CHECK_JMC %s