What follows (across this and 3 more posts, maybe more) is a rather detailed tour of
objc_msgSend() as implemented in Mac OS X 10.6.2. Rather detailed in that every instruction will be explained. Even though it is relatively few instructions, there is a considerable amount of background information that is helpful to understanding the
objc_msgSend() instruction stream.
The motivation behind these posts is entirely selfish. I find the best way for me to learn something is to know it well enough to be able to explain any detail to a room full of folks in full-blown student mode.
Table of Contents
- objc_msgSend() Tour Part 1: The Road Map
- objc_msgSend() Tour Part 2: Setting the Stage
- objc_msgSend() Tour Part 3: The Fast Path
- objc_msgSend() Tour Part 4: Method Lookup & Some Odds and Ends
For a variety of reasons, I oft find myself staring at streams of x86_64 instructions. This is often the stream of instructions that sit at the very core of Objective-C and dispatch each and every method call. That is,
Well, technically, a handful of different versions of
objc_msgSend(), each written to handle the calling conventions required to deal with various return types under the x86_64 ABI (Application Binary Interface). Oh, and the vtable dispatch stuff (different post).
objc_msgSend() is called 10s of millions of times simply booting the system and launching some apps. Thus, every cycle counts and, no surprise,
objc_msgSend() is written in hand tuned assembly. Of all of the other performance optimizations and implementation details in this code, there are three that I find particularly notable.
Don’t Mess With Registers (unless absolutely necessary)
Tail Call Optimized
Don’t Mess with the Argument List
These are really all three part of the same thing (i.e. some arguments are in registers, for example).
objc_msgSend() is designed to dynamically determine the implementation of a method — the function pointer that is the IMP tied to the selector on the targeted instance — without changing any of the caller/callee state. This enables a tail-call optimization that allows
objc_msgSend() to jump [JMP] directly to the implementation of a method.
This is also the reason why you don’t see
objc_msgSend() in backtraces save for when a crash occurs in
objc_msgSend(). Given that there were several 10s of millions of invocations of
objc_msgSend() prior to the one that crashed, you can pretty much always assume that the crash is because of a bad pointer being passed to
objc_msgSend() and not due to a bug in
objc_msgSend() itself. Of course, if you find yourself crashing in
objc_msgSend(), go read and then re-read this.
This relative handful of instructions — 76 in the disassembly below (taken on Snow Leopard 10.6.2 — objc-437.1), most of which are not executed in a typical method dispatch — does an awful lot of stuff upon each invocation.
- Check for ignored selectors (GC) and short-circuit.
- Check for nil target.
- If nil & nil receiver handler configured, jump to handler
- If nil & no handler (default), cleanup and return.
- Find the IMP on the class of the target and jump to it
- Search the class’s method cache for the method IMP
- If found, jump to it.
- Not found: lookup the method IMP in the class itself
If found, jump to it.
- If not found, jump to forwarding mechanism.
The next part will set the stage for diving into the actual