[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 <PeterRong@meta.com>
Co-authored-by: Kyungwoo Lee <kyulee@meta.com>
This commit is contained in:
Peter Rong 2026-01-05 14:42:03 -08:00 committed by GitHub
parent 71925cbdf8
commit 3023c1069e
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
8 changed files with 81 additions and 12 deletions

View File

@ -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.

View File

@ -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<OptionIgnored>;
def warn_drv_unsupported_option_for_runtime : Warning<
"ignoring '%0' option as it is not currently supported for runtime '%1'">,
InGroup<OptionIgnored>;
def warn_drv_unsupported_openmp_library : Warning<
"the library '%0=%1' is not supported, OpenMP will not be enabled">,
InGroup<OptionIgnored>;

View File

@ -3799,6 +3799,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_direct_precondition_thunk : BoolFOption<"objc-direct-precondition-thunk",
CodeGenOpts<"ObjCDirectPreconditionThunk">, DefaultFalse,
PosFlag<SetTrue, [], [ClangOption, CC1Option],
"Move precondition checks for direct methods into thunks">,
NegFlag<SetFalse>>;
defm disable_block_signature_string : BoolFOption<"disable-block-signature-string",
CodeGenOpts<"DisableBlockSignatureString">, DefaultFalse,
PosFlag<SetTrue, [], [ClangOption], "Disable">,

View File

@ -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())) {
@ -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;
}

View File

@ -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,

View File

@ -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.

View File

@ -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() &&

View File

@ -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