Skip to content

[ObjC] Support emission of selector and class stubs calls instead of objc_msgSend.#12570

Open
ahatanaka wants to merge 2 commits intostable/21.xfrom
objc-stubs
Open

[ObjC] Support emission of selector and class stubs calls instead of objc_msgSend.#12570
ahatanaka wants to merge 2 commits intostable/21.xfrom
objc-stubs

Conversation

@ahatanaka
Copy link
Copy Markdown

No description provided.

ahatanak and others added 2 commits March 11, 2026 16:48
…nd. (llvm#183922)

This optimizes objc_msgSend calls by emitting "selector stubs" instead.

Usually, the linker redirects calls to external symbols to a symbol stub
it generates, which loads the target function's address from the GOT and
branches to it:

  <symbol stub for _func:>
    adrp x16, _func@GOTPAGE
    ldr x16, [x16, _func@GOTPAGEOFF]
    br x16

with msgSend selector stubs, we extend that to compute the selector as
well:

  <selector stub for "foo":>
    adrp x1, <selector ref for "foo">@page
    ldr x1, [x1, <selector ref for "foo">@PAGEOFF]
    adrp x16, _objc_msgSend@GOTPAGE
    ldr x16, [x16, _objc_msgSend@GOTPAGEOFF]
    br x16

This lets us avoid loading the selector in the compiler, hopefully
leading to codesize reductions.

We're considering two kinds of stubs, the combined one above with the
objc_msgSend dispatch included, or a shorter one that forwards to the
existing stub for objc_msgSend. That's a decision to be made in the
linker.

Concretely, instead of something like:

  %sel = load i8*, i8** @<selector ref for "foo">
  call ... @objc_msgSend(i8* self, i8* sel)

we emit a call to a newly-declared external "function":

  call ... @"objc_msgSend$foo"(i8* self, i8* undef)

where "objc_msgSend$foo" is treated as any other external symbol
reference throughout the compiler, and, at link-time, is recognized by
the linker as a selector stub.

There are other ways to implement this. An obvious one is to combine
away the objc_msgSend(self, load(sel)) pattern at the IR level.

Both seem feasible, but the IRGen approach might save us a tiny bit of
compile-time, with the assumption that loads need more mid-level
analysis than boring external functions.

This optimization requires linker version 811.2 or newer for arm64,
arm64e, and arm64_32.

rdar://84437635
Instead of translating class messages to `objc_msgSend` calls, clang now
emits calls to stub functions that are synthesized by the linker. Each
stub loads the class reference and the selector name and forwards them
to `objc_msgSend`.

The stub function is named using the following format:
`objc_msgSendClass$selName$_OBJC_CLASS_$_className`

Note that the optimization is disabled in the following cases:
- When the class name is unknown at compile time (e.g, `[id
classMethod]`).
- The selector name contains a `$`, which serves as the delimiter in
stub
   function names.
- The class is annotated with either `objc_class_stub` or
  `objc_runtime_visible`.

In such cases, clang reverts to the existing message send optimization,
where each stub loads only the selector name.

Also add class objects to llvm.used when class msgSend optimization is
enabled. This is needed as the linker currently cannot generate the
stub,
which references the class object, if LTO internalizes the global
variable
for the class object.

rdar://147604613

---------

Co-authored-by: Ahmed Bougacha <ahmed@bougacha.org>
@ahatanaka ahatanaka requested a review from a team as a code owner March 11, 2026 23:53
@ahatanaka
Copy link
Copy Markdown
Author

@swift-ci test

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants