Fish tank with Puck the Fish Fish tank with Puck the Fish Water on the floor spelling Fish
 

Nil

May 29th, 2005

What does sending a message to nil do in Objective-C? What value does it return? You probably know that if the method is declared to return an object, you get back nil, but what if the method is declared to return a char? An int? A long long? A float? A struct? And how does it all work?

Let’s find out!

objc_msgSend

Here’s what I do know. When you send a message to an object, such as [someObject doSomething], the compiler compiles that into a function call to the objc_msgSend() function (or objc_msgSend_stret() if the method is declared to return a struct; more on that below). So what does objc_msgSend() do with nil?

Fortunately, that code is available publicly, from Apple’s public source page. We want the objc-msg-ppc.s file (Warning: Apple ID required). .s means assembly, but don’t worry, we can handle it.

(I am displaying a few snippets of that code in this post. All such code is Copyright Apple Computer.)

Where’s the objc_msgSend() function? Oh, there it is; search for “ENTRY _objc_msgSend”.

        ENTRY _objc_msgSend
; check whether receiver is nil
        cmplwi  r3,0                            ; receiver nil?
        beq-    LMsgSendNilSelf         ; if so, call handler or return nil

So the first instruction cmplwi r3,0 compares r3 (that’s the object) against 0, and the second instruction beq- LMsgSendNilSelf jumps to LMsgSendNilSelf if they were equal.

LMsgSendNilSelf

Ok, so what’s this LMsgSendNilSelf thingy? Search for it within the file…ah-ha, there it is. This is where we go if the message receiver is nil, but what does it do?

LMsgSendNilSelf:
        mflr    r0                      ; load new receiver
        bcl     20,31,1f                ; 31 is cr7[so]
1:      mflr    r11
        addis   r11,r11,ha16(__objc_nilReceiver-1b)
        lwz     r11,lo16(__objc_nilReceiver-1b)(r11)
        mtlr    r0

        cmplwi  r11,0                   ; return nil if no new receiver
        beqlr

        mr      r3,r11                  ; send to new receiver
        b       LMsgSendReceiverOk

The first six instructions within LMsgSendNilSelf are just there to load the global variable __objc_nilReceiver into register r11 (yeah, six instructions just to load a variable! Globals with position independent code sure isn’t pretty). Then it compares that variable against nil, via cmplwi r11, 0, and if it was nil the whole objc_msgSend() function returns. No return value; it just returns.

(If __objc_nilReceiver were not nil, it moves __objc_nilReceiver into r3, where the message receiver goes, and acts like the receiver was __objc_nilReceiver all along, meaning that __objc_nilReceiver is there to let you replace the default message-to-nil behavior with your own! How cool is that!?)

What gets returned

So to sum up, if you send a message to nil, it’s as if you executed a C function that just called return without specifying any return value. So what return value does that give us? I guess we need to know how Mac OS X on the PowerPC returns values.

Well, that depends on the type the function is declared to return! That stuff is all documented in the lovely PowerPC Runtime Architecture Guide, which is also hiding out somewhere in /Developer on your system. Ugh, what a mess. Ok, here’s what we’re after: Function Return. This is what it says, paraphrased:

  • If a function returns a pointer or an integral type (other than long long), the return value goes in r3, the fourth general purpose register.
  • If a function returns a floating point value, the return value goes in FPR1, a floating point register.
  • If a function returns long long, the upper half goes in r3, the lower half in r4.
  • Struct returning works like this: the caller makes space for the return value and sticks a pointer to the space in r3 (and therefore starts putting parameters in r4 rather than r3), and the callee is responsible for copying its return value into that space. Now we see why struct returns need that special objc_msgSend_stret() function: all the parameters are one register off from where they usually are.

But wait, isn’t r3 the same register where we put the object we sent the message to in the first place? Sure is! So when you send a message to nil where the method is declared to return an object, you get back the very same nil for the return value. The function doesn’t touch the object parameter, and since the return value is in the same place as the receiver parameter to objc_msgSend(), if the receiver is nil then so is the return value. Lucky!

Types, types, types

And since ints, shorts, chars, and longs are also returned in r3, the same thing happens. Sending a message to nil where you expect to get any of those types back will always give you 0.

What about long long, in which case the return value is half in r3 and the other half in r4? Well, since objc_msgSend() doesn’t touch r4 either, what we send is what we get back, and in this case we send the selector in r4. So for the case of a long long, we expect to get the top 32 bits all 0 and the bottom 32 bits equal to the selector we sent. We will never get 0 back if we send a message to nil and expect a long long. Instead, we get the selector converted to a long long!

And floating point? objc_msgSend() doesn’t touch the floating point registers, so we’ll get back whatever happened to be in FPR1 when we sent the message.

Ok, now for the trickiest case: structs. objc_msgSend_stret() behaves identically to objc_msgSend() with regard to a nil-receiver. And remember that it’s the callee’s responsibility to copy the return value into the caller provided space. Since objc_msgSend_stret() neglects to do that, the data in the space provided by the caller is unchanged. This often means that code like myPoint = [nil someMethodReturningPoint]; will leave myPoint unchanged (but this behavior is dependent on the compiler’s optimizations, and so you shouldn’t rely on it).

Phew, my hat’s off to you if you trudged through all that. Hopefully you found something new to you.

 

The Internet!

π = 3.2860203432

A pretty interesting article about Application Binary Interface of powerpc.

Very nice insight! Keep the good stuff coming! I never thought of [nil something] to not return a kind of zero. Thanks for pointing this out. This will help in some weird debugging moment – (i already had too much of them anyway ;-) )

Cool stuff. I had never thought much about objc_msgSend_stret, except to know that it was “different.” Now you’ve motivated me to figure out what the new “objc_msgSend_rtp” calls are that I’m seeing all over the place in Tiger.

Hmm, nothing in the obj_msg_ppc.s file you linked to. Nothing by searching ADC. Lots of stack crawls from crash descriptions by searching Google.

Aha! Obviously, I have to look at the obj_msg_ppc.s source from Tiger. There it is, in all it’s confusing (to me) glory. It has something to do with “runtime pages.” OK, on Tiger there is a copy of objc_msgSend in high-memory at address 0xfffeff00. I guess it’s some kind of performance optimization to allow apps to do an absolute branch to the subroutine. I don’t know much beyond that, but it’s nice to know that it’s “basically just objc_msgSend” :)

I look forward to more articles like this. Good stuff!

It’s great to get an official answer on this. After tracking down a heisenbug I figured that [nil returnFloatingPointValue] probably just returned whatever was in FP1 when called, but I wasn’t sure. Thanks for clearing this up (and pointing out the problem with long long as well – I didn’t know about that one).

Running

Because it went rather well in November and many people asked to have another one, we are having another Running Dinner this weekend. We wanted this to be really…

Very interesting read – please keep it coming – and make sure it gets included in the docs on the runtime :-) . Thank you very much.

Cool – an interesting read there. Please keep more of them coming!

Cliff L. Biffle

This actually explains some oddities I was tracking down.

It pretty much supports my current policy, derived from one too many heisenbugs: “Don’t send to nil if you can help it.” :-)

Looking forward to more!

Awesome blog — I’ll definitely be following it.

I’ve recently switched my primary development machine to be my mac, and It’s so much nicer.

Now — how bad would it be (from a performance standpoint) to move 0 into fp1 when you go down that path for sending a message to nil, thus eliminating a class of bugs. And in the case of struct returns, how bad would it be to fill the return struct with all 0’s in the case of sending to nil?

In my opinion, it would not result in any noticable performance penalty (I’m assuming that messages passed to nil are the exception, not the rule), and could result in increased stability of apps running on this platform.

What do you think?

Regards,
Ian Ameline,
Software Architect,
Alias Sketchbook Pro.

ridiculous_fish

> Now — how bad would it be (from a performance standpoint) to move 0 into fp1 when you go
> down that path for sending a message to nil, thus eliminating a class of bugs. And in the case of
> struct returns, how bad would it be to fill the return struct with all 0’s in the case of sending to nil?

Moving 0 into fp1, to make float and double returns 0, would be very cheap. And while you’re at it, moving 0 into r4, to make long long returns 0, would be even cheaper. But doing this with structs would probably require substantial runtime changes, since the objc_msgSend_stret() function doesn’t know how big the struct return must be.

> In my opinion, it would not result in any noticable performance penalty (I’m assuming that
> messages passed to nil are the exception, not the rule), and could result in increased stability
> of apps running on this platform.

I agree with you, and I’ve asked this before. The answer I was given was that Apple doesn’t want to “special case” floats, doubles, and long longs since there’s not a ready solution for the general case.

You can see the conversation I had on Usenet.

Ages ago, IIRC, it was *documented* behavior that [nil blah...] was OK and would return nil if the method returned an id (or other object pointer). And of course, on machines where pointers and ints are register-compatible it works for ints as well. As you point out it’s easy to get sloppy and use it for a float or a struct and end up with hard-to-reproduce behavior…

Daniel Ericsson

“On a Macintosh using an Intel microprocessor, Objective-C messages sent to nil return garbage for return values that are typed as float or double. On a Macintosh using a PowerPC microprocessor these messages return 0.0.”

Page 46.

http://developer.apple.com/documentation/MacOSX/Conceptual/universal_binary/universal_binary.pdf

Thanks, Daniel. I had noticed that Monday – the PDF document is incorrect and should be fixed.

Rob Winchester

At WWDC one of the small (critical) tidbits they mentioned is that, on INTEL, you can no longer send messages to NIL… it will crash. Just a minor change there… ;-)

Matt Gallagher

Trying to give other developers heart-attacks, Rob?

Fortunately: http://developer.apple.com/documentation/MacOSX/Conceptual/universal_binary/universal_binary_tips/chapter_5_section_20.html

which says:

Do not rely on the nil-messaging feature for methods whose return types are float, double, long long, or struct. On an Intel-based Macintosh computer, the return value is undefined for Objective-C messages sent to nil for these return types.

Phew. No hideous debug/audit required.

P.S. Great blog, thanks ridiculous_fish!

rpd

Well, based on the revised universal binary guidelines, it looks like everything including floats and structs returns zero:

http://developer.apple.com/documentation/MacOSX/Conceptual/universal_binary/universal_binary_tips/chapter_5_section_20.html

On Intel-based Macintosh computers, messages to a nil object always return 0.0 for methods whose return type is float, double, long double, or long long. Methods whose return value is a struct, as defined by the Mac OS X ABI Function Call Guide to be returned in registers, will return 0.0 for every field in the data structure. Other struct data types will not be filled with zeros. This is also true under Rosetta. On PowerPC Macintosh computers, the behavior is undefined.

Key phrase there is “to be returned in registers.” Small structs that are returned in registers are zeroed; other structs are not and cannot be.

You guys (here and at http://unixjunkie.blogspot.com/2006/02/messaging-nil-in-objective-c.html ) talk as though there were no language standard for Objective-C. I don’t care because I don’t swim in your world, but it’s a sad state when you decide how to program by looking at an implementation of the language.