Returning values from objc_msgSend etc

  • Are there any Objective-C wizards out there who could review a few
    conceptual and practical issues I have returning values from
    objc_msgSend and its friends?

    I've just released Rococoa, a generic replacement for the Cocoa-Java
    bridge, under LGPL. Some things do not work as expected on PPC. I know
    I really need to understand how objc_msgSend works, but cannot find a
    complete discussion anywhere.

    In short, my questions are

    * If both objc_msgSend_stret and objc_msgSend hack the stack to return
    different sized return values, why are there 2 functions?
    * Should I call objc_msgSend_stret when returning longs on PPC?
    * Do I really need to call objc_msgSend_fpret for floating point on Intel?

    In long, my understanding of the issue is set out here:

    https://rococoa.dev.java.net/objc_msgSend.html

    If anyone could spare some time reading this document and putting me
    straight, you'd be helping an up-and-coming open-source project. It
    might even be tax-deductible.

    Thanks in anticipation

    Duncan McGregor
  • On Feb 29, 2008, at 11:05 AM, Duncan McGregor wrote:
    > Are there any Objective-C wizards out there who could review a few
    > conceptual and practical issues I have returning values from
    > objc_msgSend and its friends?
    >
    > I've just released Rococoa, a generic replacement for the Cocoa-Java
    > bridge, under LGPL. Some things do not work as expected on PPC. I know
    > I really need to understand how objc_msgSend works, but cannot find a
    > complete discussion anywhere.

    The first thing to do is read and understand the Mac OS X ABI Function
    Call Guide. Objective-C messages follow the C calling conventions.
    http://developer.apple.com/documentation/DeveloperTools/Conceptual/LowLevel
    ABI/Introduction.html


    Then the rules work like this. Note that the answers may differ for
    each architecture.

    * If the return value is stored in one x87 floating-point register,
    use objc_msgSend_fpret. This includes i386 float/double/long double
    and x86_64 long double (but not float/double). On other architectures,
    objc_msgSend_fpret is identical to objc_msgSend.

    * If the return value is stored in two x87 floating-point registers,
    use objc_msgSend_fp2ret. I think this is for i386/x86_64 _Complex long
    double only, but might also apply to a struct composed of two long
    double fields.

    * If the return value is a struct, and the struct's address is passed
    as the first parameter, use objc_msgSend_stret. Note that some small
    structs on some architectures are returned in registers instead; don't
    use objc_msgSend_stret for them.

    * If the return value is a ppc/ppc64 vector, stop. Objective-C
    messages don't support vector parameters.

    * For everything else, use objc_msgSend.

    When in doubt, write Objective-C code that returns the type you want,
    compile it, and use whichever function the generated assembly code
    chose. This is usually the way to go for structure returns, because
    each architecture has its own arcane rules about which structs are
    returned in registers and which are returned on the stack. ppc64
    struct return in particular is incomprehensible. And the compiler is
    always right; if the compiler and the documentation disagree about
    function calls, the documentation is changed.

    > * If both objc_msgSend_stret and objc_msgSend hack the stack to return
    > different sized return values, why are there 2 functions?

    For sufficiently-large structures on all architectures, the caller
    passes the address of the storage for the return value as the first
    parameter. This means the other parameters are shifted. That in turn
    means objc_msgSend and objc_msgSend_stret need to look in different
    places to find `self` and `_cmd`.

    Also, on i386, the callee pops that struct-return parameter, unlike
    the other parameters which are popped by the caller. So
    objc_msgSend_stret needs to pop the extra value when a message is sent
    to nil, and objc_msgSend doesn't.

    > * Should I call objc_msgSend_stret when returning longs on PPC?

    No. On ppc, longs are 32-bit and returned in a single register. 64-bit
    integers are returned in two registers. Both cases use objc_msgSend.

    > * Do I really need to call objc_msgSend_fpret for floating point on
    > Intel?

    Yes. If a message is sent to nil, objc_msgSend_fpret pushes a zero on
    the x87 floating-point stack for the caller to pop. objc_msgSend can't
    do that, because the caller isn't expecting to pop anything.

    --
    Greg Parker    <gparker...>    Runtime Wrangler
  • On Fri, Feb 29, 2008 at 2:46 PM, Greg Parker <gparker...> wrote:

    >
    > When in doubt, write Objective-C code that returns the type you want,
    > compile it, and use whichever function the generated assembly code
    > chose.

    What's your advice about what to do when the choice must be made at runtime?
    I'm using libffi. Does it know about the implicit first argument for struct
    returns? That is, do I take the moral equivalent of the suggestion in
    objc/objc-runtime.h, call objc_msgSend_stret() using a struct return type
    and leave the argument shuffling for ffi to handle? Or, do I call it as
    declared, with a void return type, setting up the return pointer argument
    myself and disregarding the comment in the header file?

    Also, do you know what, if any, type promotions are relevant, and whether
    libffi automatically handles them?

    I'm (slowly) updating CamelBones, replacing libffcall with libffi. Ffcall is
    fairly limited in its structure handling - structure components have to be
    known at compile time, whereas libffi's struct definition metadata can be
    assembled at run time. That capability is a must, if I'm to be able to read
    .bridgesupport metadata, hence the switch.

    sherm--
  • On Mar 1, 2008, at 1:22 AM, Sherm Pendley wrote:
    > On Fri, Feb 29, 2008 at 2:46 PM, Greg Parker <gparker...>
    > wrote:
    >
    > When in doubt, write Objective-C code that returns the type you want,
    > compile it, and use whichever function the generated assembly code
    > chose.
    >
    > What's your advice about what to do when the choice must be made at
    > runtime? I'm using libffi. Does it know about the implicit first
    > argument for struct returns? That is, do I take the moral equivalent
    > of the suggestion in objc/objc-runtime.h, call objc_msgSend_stret()
    > using a struct return type and leave the argument shuffling for ffi
    > to handle? Or, do I call it as declared, with a void return type,
    > setting up the return pointer argument myself and disregarding the
    > comment in the header file?

    libffi is smart enough to know about all of the ABI rules. If your
    ffi_types correctly describe the method's parameters, then libffi will
    marshal them properly. (If it doesn't, then that's a libffi bug.)

    However, you still need to choose the correct objc_msgSend variant.
    libffi will correctly set up the implicit struct return argument or
    not as required, but there's no way for you to know whether it's going
    to do that or not. So if your C structure is returned in registers,
    then libffi will handle it correctly but you'll crash if you use
    objc_msgSend_stret instead of objc_msgSend.

    The right way to fix this is to add new API in libobjc: given an
    ffi_type, return the correct objc_msgSend function pointer for that
    return type. I filed the feature request for that today. Until then
    you'll still need to handle that choice yourself. All of the required
    code is in libffi somewhere, but there's no way for you to call it
    directly. libffi is open source, so you could try digging through the
    implementation and copying the code you want into your program.

    > Also, do you know what, if any, type promotions are relevant, and
    > whether libffi automatically handles them?

    I don't think libffi does any type promotion.

    --
    Greg Parker    <gparker...>    Runtime Wrangler
  • On Mar 1, 2008, at 5:00 AM, Greg Parker wrote:
    > The right way to fix this is to add new API in libobjc: given an
    > ffi_type, return the correct objc_msgSend function pointer for that
    > return type. I filed the feature request for that today. Until then
    > you'll still need to handle that choice yourself. All of the
    > required code is in libffi somewhere, but there's no way for you to
    > call it directly. libffi is open source, so you could try digging
    > through the implementation and copying the code you want into your
    > program.

    Both PyObjC and RubyCocoa (and F-Script Anywhere, for that matter),
    use FFI to call objc_msgSend().  Both have extensive unit tests and,
    thus, the logic seems to be correct.

    Their licenses are liberal enough that you can lift the logic pretty
    much directly and munge for your purposes.

    b.bum
  • On Sat, Mar 1, 2008 at 8:00 AM, Greg Parker <gparker...> wrote:

    The right way to fix this is to add new API in libobjc: given an ffi_type,
    > return the correct objc_msgSend function pointer for that return type. I
    > filed the feature request for that today. Until then you'll still need to
    > handle that choice yourself. All of the required code is in libffi
    > somewhere, but there's no way for you to call it directly. libffi is open
    > source, so you could try digging through the implementation and copying the
    > code you want into your program.
    >

    Good idea. In the mean time, I've found a workaround I think I can live
    with. The call to ffi_prep_cif() will, among other things, properly
    initialize the size and alignment members of any relevant ffi_type
    structures. Getting the alignment right is the tricky part that I don't want
    to reinvent, but since ffi has already done that I can simply check the
    calculated size after calling ffi_prep_cif(), and compare that to
    sizeof(void*) to see if the return value will fit in a register.

    It's a hack, in that it hard-codes the "if it fits in a single register"
    rule, which would be better viewed as an implementation detail of libobjc.
    But it's good enough for now, and at least lets ffi handle the mechanics.
    I'll wrap this and the other selection rules into a function that I can
    easily replace with a call to the new API when (or if) that arrives.

    Thanks for the advice,

    sherm--
  • Thanks for all the help on this.

    Rococoa uses JNA, which in turns uses libffi, so I'm also spared most
    of the heavy lifting, but like Sherm, I do have to make all the
    decisions at runtime. Greg's post is the most definitive thing I've
    seen on the subject - thank you.

    >> * Should I call objc_msgSend_stret when returning longs on PPC?

    > No. On ppc, longs are 32-bit and returned in a single register. 64-bit integers are returned in two registers. Both cases use objc_msgSend.

    Ah, I had meant Java long, and therefore 64 bits. That is interesting,
    as I have a failing unit test for returning Java long using
    objc_msgSend on PPC, which I would have expected libffi to just make
    work for me. Looks like I'll have to repossess the old PowerBook from
    my eldest son.

    > Both PyObjC and RubyCocoa (and F-Script Anywhere, for that matter), use FFI to call objc_msgSend().  Both have extensive unit tests and, thus, the logic seems to be correct.

    I had gone looking in the source for RubyCocoa, but not hard enough! I
    have a similar unit test policy, but the rule seems to be, if the
    tests all pass, I don't have enough of them, or I'm not testing on
    enough platforms.

    > I can simply check the calculated size after calling ffi_prep_cif(), and compare that to sizeof(void*) to see if the return value will fit in a register.

    Is that right? 8 byte structs are returned in EAX and EDX on IA-32, so
    I'd expect to call objc_msgSend.

    Thanks again

    Duncan
  • On Monday, March 03, 2008, at 11:53AM, "Duncan McGregor" <duncan...> wrote:

    >> I can simply check the calculated size after calling ffi_prep_cif(), and compare that to sizeof(void*) to see if the return value will fit in a register.
    >
    > Is that right? 8 byte structs are returned in EAX and EDX on IA-32, so
    > I'd expect to call objc_msgSend.

    The rules are a bit more complex than this (at least on x86). Small structs are returned in registers (upto 8 bytes), but only if the size of the struct is a power of 2. That is, a 6-byte struct will not be returned in registers. I haven't checked recently if the ABI documentation reflects reality, in 10.4 days it didn't.

    Ronald
  • On Mon, Mar 3, 2008 at 6:10 AM, Ronald Oussoren <ronaldoussoren...>
    wrote:

    >
    > On Monday, March 03, 2008, at 11:53AM, "Duncan McGregor" <
    > <duncan...> wrote:
    >
    >>> I can simply check the calculated size after calling ffi_prep_cif(),
    > and compare that to sizeof(void*) to see if the return value will fit in a
    > register.
    >>
    >> Is that right? 8 byte structs are returned in EAX and EDX on IA-32, so
    >> I'd expect to call objc_msgSend.
    >
    > The rules are a bit more complex than this (at least on x86). Small
    > structs are returned in registers (upto 8 bytes), but only if the size of
    > the struct is a power of 2. That is, a 6-byte struct will not be returned in
    > registers. I haven't checked recently if the ABI documentation reflects
    > reality, in 10.4 days it didn't.

    Thanks for the tip. I shall have to go diving into libffi to see exactly
    what the rules really are then. I suspect they might be different for PPC &
    Intel, because the PPC32 docs still say that only r3 is used, for a limit of
    4 bytes.

    sherm--
  • On Sat, Mar 1, 2008 at 9:00 AM, Greg Parker <gparker...> wrote:

    libffi is smart enough to know about all of the ABI rules. If your ffi_types
    > correctly describe the method's parameters, then libffi will marshal them
    > properly. (If it doesn't, then that's a libffi bug.)
    >
    > I don't think libffi does any type promotion.
    >

    Indeed not - the sample below prints different results when run on PPC
    (including Rosetta) and i386. On PPC, only the promoted version returns the
    correct result. On Intel, both of them do. The question then, becomes -
    should libffi be handling this difference transparently?

    sherm--

    #include <stdio.h>

    #define MACOSX
    #include <ffi.h>

    char getCharFunction() {
        return 5;
    }

    int main (int argc, const char * argv[]) {
        ffi_cif cif_nopromote;
        ffi_cif cif_promote;

        long returnLongValue;
        char returnCharValue;

        if (ffi_prep_cif( &cif_nopromote,
                          FFI_DEFAULT_ABI,
                          0,
                          &ffi_type_schar,
                          NULL
                          )
            != FFI_OK ) {
            printf("%s", "Error creating ffi cif_nopromote\n");
            return 1;
        }

        if (ffi_prep_cif( &cif_promote,
                          FFI_DEFAULT_ABI,
                          0,
                          &ffi_type_slong,
                          NULL
                          )
            != FFI_OK ) {
            printf("%s", "Error creating ffi cif_promote\n");
            return 1;
        }

        ffi_call(&cif_nopromote,
                (void(*)(void))getCharFunction,
                &returnCharValue,
                NULL
                );
        printf("Return value from cif_nopromote is %d.\n", returnCharValue);

        ffi_call(&cif_promote,
                (void(*)(void))getCharFunction,
                &returnLongValue,
                NULL
                );
        printf("Return value from cif_promote is %d.\n", returnLongValue);

        return 0;
    }

    >
    >
    > --
    > Greg Parker    <gparker...>    Runtime Wrangler
    >
    >
    >
  • On Sat, Mar 1, 2008 at 9:00 AM, Greg Parker <gparker...> wrote:

    libffi is smart enough to know about all of the ABI rules. If your ffi_types
    > correctly describe the method's parameters, then libffi will marshal them
    > properly. (If it doesn't, then that's a libffi bug.)
    >
    > I don't think libffi does any type promotion.
    >

    Indeed not - the sample below prints different results when run on PPC
    (including Rosetta) and i386. On PPC, only the promoted version returns the
    correct result. On Intel, both of them do. The question then, becomes -
    should libffi be handling this difference transparently?

    sherm--

    #include <stdio.h>

    #define MACOSX
    #include <ffi.h>

    char getCharFunction() {
        return 5;
    }

    int main (int argc, const char * argv[]) {
        ffi_cif cif_nopromote;
        ffi_cif cif_promote;

        long returnLongValue;
        char returnCharValue;

        if (ffi_prep_cif( &cif_nopromote,
                          FFI_DEFAULT_ABI,
                          0,
                          &ffi_type_schar,
                          NULL
                          )
            != FFI_OK ) {
            printf("%s", "Error creating ffi cif_nopromote\n");
            return 1;
        }

        if (ffi_prep_cif( &cif_promote,
                          FFI_DEFAULT_ABI,
                          0,
                          &ffi_type_slong,
                          NULL
                          )
            != FFI_OK ) {
            printf("%s", "Error creating ffi cif_promote\n");
            return 1;
        }

        ffi_call(&cif_nopromote,
                (void(*)(void))getCharFunction,
                &returnCharValue,
                NULL
                );
        printf("Return value from cif_nopromote is %d.\n", returnCharValue);

        ffi_call(&cif_promote,
                (void(*)(void))getCharFunction,
                &returnLongValue,
                NULL
                );
        printf("Return value from cif_promote is %d.\n", returnLongValue);

        return 0;
    }

    >
    >
    > --
    > Greg Parker    <gparker...>    Runtime Wrangler
    >
    >
    >
  • returnCharValue  is not a valid argument for ffi_call

    man ffi_call

    void ffi_call(ffi_cif *cif, void (*fn)(void), void *rvalue, void
    **avalue);
      rvalue must point to storage that is sizeof(long) or larger. For
    smaller
          return value sizes, the ffi_arg or ffi_sarg integral type must
    be used to
          hold the return value

    Le 31 mars 08 à 17:34, Sherm Pendley a écrit :

    > On Sat, Mar 1, 2008 at 9:00 AM, Greg Parker <gparker...> wrote:
    >
    > libffi is smart enough to know about all of the ABI rules. If your
    > ffi_types
    >> correctly describe the method's parameters, then libffi will
    >> marshal them
    >> properly. (If it doesn't, then that's a libffi bug.)
    >>
    >> I don't think libffi does any type promotion.
    >>
    >
    > Indeed not - the sample below prints different results when run on PPC
    > (including Rosetta) and i386. On PPC, only the promoted version
    > returns the
    > correct result. On Intel, both of them do. The question then,
    > becomes -
    > should libffi be handling this difference transparently?
    >
    > sherm--
    >
    > #include <stdio.h>
    >
    > #define MACOSX
    > #include <ffi.h>
    >
    > char getCharFunction() {
    > return 5;
    > }
    >
    > int main (int argc, const char * argv[]) {
    > ffi_cif cif_nopromote;
    > ffi_cif cif_promote;
    >
    > long returnLongValue;
    > char returnCharValue;
    >
    > if (ffi_prep_cif( &cif_nopromote,
    > FFI_DEFAULT_ABI,
    > 0,
    > &ffi_type_schar,
    > NULL
    > )
    > != FFI_OK ) {
    > printf("%s", "Error creating ffi cif_nopromote\n");
    > return 1;
    > }
    >
    > if (ffi_prep_cif( &cif_promote,
    > FFI_DEFAULT_ABI,
    > 0,
    > &ffi_type_slong,
    > NULL
    > )
    > != FFI_OK ) {
    > printf("%s", "Error creating ffi cif_promote\n");
    > return 1;
    > }
    >
    > ffi_call(&cif_nopromote,
    > (void(*)(void))getCharFunction,
    > &returnCharValue,
    > NULL
    > );
    > printf("Return value from cif_nopromote is %d.\n",
    > returnCharValue);
    >
    > ffi_call(&cif_promote,
    > (void(*)(void))getCharFunction,
    > &returnLongValue,
    > NULL
    > );
    > printf("Return value from cif_promote is %d.\n", returnLongValue);
    >
    > return 0;
    > }
    >
    >
    >
    >>
    >>
    >> --
    >> Greg Parker    <gparker...>    Runtime Wrangler
    >>
    >>
    >>

    >
  • On Mar 31, 2008, at 8:34 AM, Sherm Pendley wrote:
    > On Sat, Mar 1, 2008 at 9:00 AM, Greg Parker <gparker...> wrote:
    >> libffi is smart enough to know about all of the ABI rules. If your
    >> ffi_types correctly describe the method's parameters, then libffi
    >> will marshal them properly. (If it doesn't, then that's a libffi
    >> bug.)
    >>
    >> I don't think libffi does any type promotion.
    >
    > Indeed not - the sample below prints different results when run on
    > PPC (including Rosetta) and i386. On PPC, only the promoted version
    > returns the correct result. On Intel, both of them do. The question
    > then, becomes - should libffi be handling this difference
    > transparently?

    ffi_call(3) says:
    "rvalue must point to storage that is sizeof(long) or larger. For
    smaller return value sizes, the ffi_arg or ffi_sarg integral type must
    be used to hold the return value."

    > char returnCharValue;
    >
    > ffi_call(&cif_nopromote,
    > (void(*)(void))getCharFunction,
    > &returnCharValue,
    > NULL
    > );
    > printf("Return value from cif_nopromote is %d.\n",
    > returnCharValue);

    ...which means this isn't allowed. In fact, it's probably smashing
    memory on your stack.

    --
    Greg Parker    <gparker...>    Runtime Wrangler
  • On Mon, Mar 31, 2008 at 1:03 PM, Greg Parker <gparker...> wrote:

    > On Mar 31, 2008, at 8:34 AM, Sherm Pendley wrote:
    >> On Sat, Mar 1, 2008 at 9:00 AM, Greg Parker <gparker...> wrote:
    >>>
    >>> I don't think libffi does any type promotion.
    >>
    >
    > ffi_call(3) says:
    > "rvalue must point to storage that is sizeof(long) or larger. For
    > smaller return value sizes, the ffi_arg or ffi_sarg integral type must
    > be used to hold the return value."
    > ...
    > ...which means this isn't allowed. In fact, it's probably smashing
    > memory on your stack.

    I guess I may have misunderstood the term then. That sounds like type
    promotion of the return value to me. :-)

    Sorry about the noise.

    sherm--