• Open Menu Close Menu
  • Apple
  • Shopping Bag
  • Apple
  • Mac
  • iPad
  • iPhone
  • Watch
  • TV
  • Music
  • Support
  • Search apple.com
  • Shopping Bag

Lists

Open Menu Close Menu
  • Terms and Conditions
  • Lists hosted on this site
  • Email the Postmaster
  • Tips for posting to public mailing lists
Re: objc_msgSend problems on x86
[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

Re: objc_msgSend problems on x86


  • Subject: Re: objc_msgSend problems on x86
  • From: Greg Parker <email@hidden>
  • Date: Sat, 11 Feb 2006 15:15:00 -0800

Rosyna wrote:
bool isEqual=objc_msgSend(string, NSSelectorFromString(CFSTR ("isEqualToString:")), otherString);

On the PPC will work just fine 100% of the time. If I run the same code on the ICBMs it'll return true even when the strings weren't equal at all in some rare cases. It also shows an error in GCC

Even worse was trying something like:

if (objc_msgSend(string, NSSelectorFromString(CFSTR ("isEqualToString:")), otherString))

which returns true the far majority of the time even when it the strings are not equal. However, if I change the code to read:

if ((signed char(*)(id, SEL, id)objc_msgSend)(string, NSSelectorFromString(CFSTR("isEqualToString:")), otherString))

it always returns the correct value. Is there a reason for this change between the PPC and the x86?

The problem is that this code is sloppy with its types, and in this case the PPC instruction set is more forgiving than i386.


Executive summary:
If you want to call objc_msgSend() directly for a method that doesn't return `id`, you should always use an appropriately-typed function pointer instead of objc_msgSend() itself. If you don't, your code might work on PPC, but is likely to fail on i386 or PPC64.


Bonus tips:
* Using an appropriately-typed function pointer is equally important for calling IMPs directly as it is for objc_msgSend().
* Using an appropriately-typed function pointer is especially important for objc_msgSend_stret(). The function pointer must return the struct type, rather than pass the address of the struct as the first parameter.
* For floating-point return values on i386, use objc_msgSend_fpret() instead of objc_msgSend() itself.



Consider this simple method:

    @implementation Test
    +(BOOL) returnFalse { return (self == (id)_cmd); }
    @end

This method always returns NO, because `self` and `_cmd` are never equal.


The PPC assembly (at -O3) looks like this:

    "+[Test returnFalse]":
        xor r3,r4,r3
        subfic r0,r3,0
        adde r3,r0,r3
        blr

The compiler optimizer is being clever here, using some arithmetic to do the comparison without any branches. Suffice to say that the return value is in register r3, and that a NO return sets all of register r3 to zero. In general, the instructions used to compute a one-byte BOOL value also set the rest of the register to zero when returning NO.


On i386, the assembly looks like this:

    "+[Test returnFalse]":
        pushl   ëp
        movl    %esp, ëp
        movl    12(ëp), êx
        cmpl    8(ëp), êx
        sete    %al
        popl    ëp
        ret

Here, the return value is placed in register al, which is another name for the first byte of the four-byte register eax. Importantly, the other three bytes of eax are left as random garbage, which is different from the PPC case. This is usually ok because BOOL is only one byte, but in this case it causes trouble, as we'll see below.


Now let's call the +returnFalse method using objc_msgSend() directly.

    // BAD version
    if (objc_msgSend([Test class], @selector(returnFalse))) {
        printf("YES\n");
    } else {
        printf("NO\n");
    }

As far as the compiler can see, objc_msgSend() returns a four-byte `id` value in register r3 or eax. The `if` check therefore uses the entire four bytes of that register. On PPC that's ok, because all of r3 is zero. But on i386, the other three bytes of eax are random garbage, so the `if` check comes out true even though the method returned NO.


If you call +returnFalse using a function pointer cast to the right type, it works better.


    // GOOD version
    BOOL (*fn)(id, SEL) = (BOOL (*)(id, SEL)) objc_msgSend;
    if ((*fn)([Test class], @selector(returnFalse))) {
        printf("YES\n");
    } else {
        printf("NO\n");
    }

Now the compiler knows that the return value is only one byte of register r3 or eax. The `if` check ignores the other three bytes, so you get the value you expected on both PPC and i386.


You might think that you could get away with this:

    // RISKY version
    if ((BOOL) objc_msgSend([Test class, @selector(returnFalse))) {
        printf("YES\n");
    } else {
        printf("NO\n");
    }

Don't do this. In the BOOL case it works, I think. In other cases it fails (for example, if you cast to double instead of BOOL). Using an appropriately-typed function pointer always works.


-- Greg Parker email@hidden Runtime Wrangler


_______________________________________________ Do not post admin requests to the list. They will be ignored. Cocoa-dev mailing list (email@hidden) Help/Unsubscribe/Update your Subscription: This email sent to email@hidden
  • Follow-Ups:
    • Re: objc_msgSend problems on x86
      • From: Damien Bobillot <email@hidden>
    • Re: objc_msgSend problems on x86
      • From: Rosyna <email@hidden>
    • NSString and char *
      • From: "Lars Elden" <email@hidden>
  • Prev by Date: Re: Problem moving subview within superview
  • Next by Date: NSString and char *
  • Previous by thread: Re: objc_msgSend problems on x86
  • Next by thread: NSString and char *
  • Index(es):
    • Date
    • Thread