objc_msgSend problems on x86

  • I've noticed when running code like:

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

    Sincerely,
    Rosyna Keller
    Technical Support/Holy Knight/Always needs a hug

    Unsanity: Unsane Tools for Insanely Great People

    It's either this, or imagining Phil Schiller in a thong.
  • On 9 Feb 2006, at 10:25, Rosyna wrote:

    > I've noticed when running code like:
    >
    > 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

    The error might be conveying useful information.  Would you like to
    tell us what it says?

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

    In your old code you were taking the result of objc_msgSend as a
    BOOL; it fails and gives you an error.  In the later case you
    explicitly tell the compiler that it is something else and it works.
    Two things to note: firstly, the function objc_msgSend returns an id,
    not a BOOL, which is probably what your error is about.  Secondly, in
    the Universal Binary Programming Guidelines [1] you'll find that the
    BOOL type is represented differently on the Intel processors compared
    to how it was on the PPC.  The first observation means that your
    original code was making an assumption about how something works and
    the second observations means that how things work with respect to
    BOOL types has changed, so I'm guessing your code fails because your
    assumption is no longer correct.

    Nicko

    [1] http://developer.apple.com/documentation/MacOSX/Conceptual/
    universal_binary/index.html#//apple_ref/doc/uid/TP40002217
  • Ack, at 2/9/06, Nicko van Someren said:

    > On 9 Feb 2006, at 10:25, Rosyna wrote:
    >
    >> I've noticed when running code like:
    >>
    >> 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
    >
    > The error might be conveying useful information.  Would you like to
    > tell us what it says?

    If I had typed BOOL instead of bool it would have said something
    about casting to a smaller size. But it's bool so it won't (on PPC).

    >> 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?
    >
    > In your old code you were taking the result of objc_msgSend as a
    > BOOL; it fails and gives you an error.  In the later case you
    > explicitly tell the compiler that it is something else and it works.
    > Two things to note: firstly, the function objc_msgSend returns an
    > id, not a BOOL, which is probably what your error is about.
    > Secondly, in the Universal Binary Programming Guidelines [1] you'll
    > find that the BOOL type is represented differently on the Intel
    > processors compared to how it was on the PPC.  The first observation
    > means that your original code was making an assumption about how
    > something works and the second observations means that how things
    > work with respect to BOOL types has changed, so I'm guessing your
    > code fails because your assumption is no longer correct.

    Where are you seeing this in the universal programming guidelines?
    BOOL and bool are two distinct types. bool has changed on the ICBMs
    but I don't see any indication that the behaviour of BOOL has.

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

    The above code produces no error. But it does not work 100% of the
    time on the ICBMs. It works 100% of the time on the PPCs. Whether it
    works or not on the ICBMs seems to be a total crapshoot.
    --

    Sincerely,
    Rosyna Keller
    Technical Support/Holy Knight/Always needs a hug

    Unsanity: Unsane Tools for Insanely Great People

    It's either this, or imagining Phil Schiller in a thong.
  • Hi Rosyna,

    > if (objc_msgSend(string, NSSelectorFromString(CFSTR
    > ("isEqualToString:")), otherString))
    >
    > The above code produces no error. But it does not work 100% of the
    > time on the ICBMs. It works 100% of the time on the PPCs. Whether
    > it works or not on the ICBMs seems to be a total crapshoot.

    When you say it doesn't work does it fail given the same input
    strings all the time?
    What's in the AX register after the call fails? Are AH and AL
    inverted in some wacky way, as in does AH hold the correct result all
    the time while AL varies? What happens if you make sure AX is zeroed
    before the call? Oh, and if you use @"isEqualToString:" does that fix
    it?

    Anyway - it's nutty. :)

    Take care,
    Guy
  • 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  %ebp
            movl    %esp, %ebp
            movl    12(%ebp), %eax
            cmpl    8(%ebp), %eax
            sete    %al
            popl    %ebp
            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    <gparker...>    Runtime Wrangler
  • Ack, at 2/11/06, Greg Parker said:

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

    I was guessing as much for the PPC but am very glad to get a concrete answer.

    > * For floating-point return values on i386, use objc_msgSend_fpret()
    > instead of objc_msgSend() itself.

    That's very good to know. I was wondering about this case as well.
    I'm assuming I'd have to do the same magic function typecast on
    objc_msgSend_fpret() that I have to do on objc_msgSend() if I want a
    float and not a double?

    > 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  %ebp
    > movl    %esp, %ebp
    > movl    12(%ebp), %eax
    > cmpl    8(%ebp), %eax
    > sete    %al
    > popl    %ebp
    > 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.

    That explains it then, many thanks.

    --

    Sincerely,
    Rosyna Keller
    Technical Support/Holy Knight/Always needs a hug

    Unsanity: Unsane Tools for Insanely Great People

    It's either this, or imagining Phil Schiller in a thong.
  • Greg Parker wrote :

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

    According to the objc-runtime.h header, one must use
    objc_msgSend_fpret on i386 for methods returning a double value :

    /* Floating-point-returning Messaging Primitives (prototypes)
    *
    * On some platforms, the ABI for functions returning a floating-point
    * value is incompatible with that for functions returning an integral
    type.
    * objc_msgSend_fpret must be used for these.
    *
    * ppc: objc_msgSend_fpret not used
    * ppc64: objc_msgSend_fpret not used
    * i386: objc_msgSend_fpret REQUIRED
    *
    * For `float` or `long double` return types, cast the function
    * to an appropriate function pointer type first.
    */

    #ifdef __i386__
    OBJC_EXPORT double objc_msgSend_fpret(id self, SEL op, ...);
    #endif

    --
    Damien Bobillot
  • BOOL is not a floating point value... so using objc_msgSend_fpret()
    to call a method that returns BOOL would be especially heinous.

    Ack, at 2/12/06, Damien Bobillot said:

    > Greg Parker wrote :
    >
    >> 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.
    >
    > According to the objc-runtime.h header, one must use
    > objc_msgSend_fpret on i386 for methods returning a double value :
    >
    > /* Floating-point-returning Messaging Primitives (prototypes)
    > *
    > * On some platforms, the ABI for functions returning a floating-point
    > * value is incompatible with that for functions returning an integral type.
    > * objc_msgSend_fpret must be used for these.
    > *
    > * ppc: objc_msgSend_fpret not used
    > * ppc64: objc_msgSend_fpret not used
    > * i386: objc_msgSend_fpret REQUIRED
    > *
    > * For `float` or `long double` return types, cast the function
    > * to an appropriate function pointer type first.
    > */
    >
    > #ifdef __i386__
    > OBJC_EXPORT double objc_msgSend_fpret(id self, SEL op, ...);
    > #endif

    --

    Sincerely,
    Rosyna Keller
    Technical Support/Holy Knight/Always needs a hug

    Unsanity: Unsane Tools for Insanely Great People

    It's either this, or imagining Phil Schiller in a thong.
previous month february 2006 next month
MTWTFSS
    1 2 3 4 5
6 7 8 9 10 11 12
13 14 15 16 17 18 19
20 21 22 23 24 25 26
27 28          
Go to today
MindNode
MindNode offered a free license !