Bug 9931 – Mac OS X ABI not followed when returning structs for extern (C)

Status
RESOLVED
Resolution
FIXED
Severity
critical
Priority
P2
Component
dmd
Product
D
Version
D2
Platform
x86
OS
Mac OS X
Creation time
2013-04-14T09:25:00Z
Last change time
2015-06-09T05:11:41Z
Assigned to
nobody
Creator
doob

Comments

Comment #0 by doob — 2013-04-14T09:25:06Z
This code: http://pastebin.com/U5XdFfDq Will result in a bus error when compiled as 32bit. Three ways the bus error won't happen: * If I compile as 64bit everything works fine * If the function "foo" is moved inline in the "main" function everything works fine * If I store the return value of "fp" in "foo" in a temporary variable and then return it Mac OS X 10.8.2.
Comment #1 by code — 2013-04-14T09:53:35Z
You can't cast the "..." part o a C vararg function away. You also don't pass a pointer to the struct return to objc_msgSend_stret, which is the first parameter.
Comment #2 by doob — 2013-04-14T11:43:05Z
You're supposed to invoke the functions like that. The objc_msgSend functions need to be casted to the correct signature. http://stackoverflow.com/questions/8405737/how-do-i-return-a-struct-value-from-a-runtime-defined-class-method-under-arc This works perfectly fine in C: http://pastebin.com/cTfn0PJS
Comment #3 by code — 2013-04-14T16:56:04Z
NSRect foo (id screen) { alias extern (C) NSRect function (id, SEL) frameFp; auto fp = cast(frameFp) &objc_msgSend_stret; return fp(screen, sel_registerName("visibleFrame".ptr)); } - You omit the pointer to the return value, that might accidently work on some ABIs because a hidden pointer to the return value is passed as first argument. What's the problem with this? NSRect foo(id screen) { NSRect res; objc_msgSend_stret(&res, screen, sel_registerName("visibleFrame")); return res; }
Comment #4 by doob — 2013-04-14T23:38:54Z
(In reply to comment #3) > NSRect foo (id screen) > { > alias extern (C) NSRect function (id, SEL) frameFp; > auto fp = cast(frameFp) &objc_msgSend_stret; > return fp(screen, sel_registerName("visibleFrame".ptr)); > } > > - You omit the pointer to the return value, that might accidently work on some > ABIs because a hidden pointer to the return value is passed as first argument. I don't know why it behaves like this but that's how you're supposed to invoke the function. This is the Objective-C runtime and it's kind of special. > What's the problem with this? > > NSRect foo(id screen) > { > NSRect res; > objc_msgSend_stret(&res, screen, sel_registerName("visibleFrame")); > return res; > } On Mac OS X 10.6.3 it segfaults. I haven't tried on 10.8.2 yet. It also segfaults if I cast it to: extern (C) void function (NSRect*, id, SEL). BTW, the original example doesn't _not_ cause a bus error on Mac OS X 10.6.3.
Comment #5 by doob — 2013-04-16T00:10:33Z
Here's one of my post from the discussion of this issue at the newsgroup: http://forum.dlang.org/thread/[email protected] I've done some investigation about how the "objc_msgSend" family of functions work, including "objc_msgSend_stret". For those who don't know they're are part of the Objective-C runtime used for calling Objective-C methods. These functions are _not_ regular C functions, they're completely implemented using assembly. What they basically do is looking up the Objective-C method to call and just calls it (some caching is involved as well). What's special about this is that it won't mess with any resister or stack used for passing function arguments or sending back return values. What happens when it has found the correct Objective-C method is that it will just jump to the function. All registers and the stack are already correct, from the call to the objc_msgSend itself. I assume that is way the objc_msgSend method need to be cast to the correct signature before calling it. We don't want to use the ABI for variadic functions (as declared by objc_msgSend), we want the ABI used for the target function, that is, the Objective-C method. An Objective-C method is implement like a regular C function, following the ABI of the platform. It has to take at least these two parameters: void foo (id self, SEL _cmd); "self" would be the object we're calling the method on. "_cmd" is the selector (method) being called. Then what's objc_msgSend_stret used for. The objc_msgSend_stret function is used for returning structs that are too large to fit in the register. This is completely dependent on the platform ABI. I don't know if this helps. Some info about how objc_msgSend works: http://www.friday.com/bbum/2009/12/18/objc_msgsend-part-1-the-road-map/ Some info about why objc_msgSend_stret exists: http://www.sealiesoftware.com/blog/archive/2008/10/30/objc_explain_objc_msgSend_stret.html
Comment #6 by doob — 2013-04-16T12:28:13Z
I think I found smaller test case for this problem, without the casts and Objective-C runtime functions: http://forum.dlang.org/thread/[email protected]
Comment #7 by code — 2013-04-16T17:57:26Z
Seems like OSX deviates from the SysV IA-32 ABI for memory struct returns. The callee does NOT return the hidden pointer in EAX. Instead the caller has to use the value passed as argument. 0x0000267c <D4test3barFZS4test3Foo+20>: call 0x26a0 <foo> 0x00002681 <D4test3barFZS4test3Foo+25>: add $0xc,%esp 0x00002684 <D4test3barFZS4test3Foo+28>: mov %eax,%esi // <- EAX is trashed 0x00002686 <D4test3barFZS4test3Foo+30>: mov -0x4(%ebp),%edi 0x00002689 <D4test3barFZS4test3Foo+33>: movsl %ds:(%esi),%es:(%edi) 0x0000268a <D4test3barFZS4test3Foo+34>: movsl %ds:(%esi),%es:(%edi) 0x0000268b <D4test3barFZS4test3Foo+35>: movsl %ds:(%esi),%es:(%edi)
Comment #8 by doob — 2013-04-16T23:38:53Z
(In reply to comment #7) > Seems like OSX deviates from the SysV IA-32 ABI for memory struct returns. > The callee does NOT return the hidden pointer in EAX. > Instead the caller has to use the value passed as argument. I don't know if this is what you're but the the ABI documentation says: "When a function returns a structure or union larger than 8 bytes, the caller passes a pointer to appropriate storage as the first argument to the function." And: "The called function returns structures according to their aligned size. * Structures 1 or 2 bytes in size are placed in EAX. * Structures 4 or 8 bytes in size are placed in: EAX and EDX. * Structures of other sizes are placed at the address supplied by the caller. For example, the C++ language occasionally forces the compiler to return a value in memory when it would normally be returned in registers. See “Passing Arguments” for more information." http://developer.apple.com/library/mac/#documentation/DeveloperTools/Conceptual/LowLevelABI/130-IA-32_Function_Calling_Conventions/IA32.html#//apple_ref/doc/uid/TP40002492-SW4
Comment #9 by john.loughran.colvin — 2013-04-17T00:04:52Z
(In reply to comment #7) > 0x0000267c <D4test3barFZS4test3Foo+20>: call 0x26a0 <foo> > 0x00002681 <D4test3barFZS4test3Foo+25>: add $0xc,%esp > 0x00002684 <D4test3barFZS4test3Foo+28>: mov %eax,%esi // <- EAX is trashed > 0x00002686 <D4test3barFZS4test3Foo+30>: mov -0x4(%ebp),%edi > 0x00002689 <D4test3barFZS4test3Foo+33>: movsl %ds:(%esi),%es:(%edi) > 0x0000268a <D4test3barFZS4test3Foo+34>: movsl %ds:(%esi),%es:(%edi) > 0x0000268b <D4test3barFZS4test3Foo+35>: movsl %ds:(%esi),%es:(%edi) You're reading it the wrong way around, at&t is src,des
Comment #10 by john.loughran.colvin — 2013-04-17T02:58:11Z
(In reply to comment #9) > (In reply to comment #7) > > 0x0000267c <D4test3barFZS4test3Foo+20>: call 0x26a0 <foo> > > 0x00002681 <D4test3barFZS4test3Foo+25>: add $0xc,%esp > > 0x00002684 <D4test3barFZS4test3Foo+28>: mov %eax,%esi // <- EAX is trashed > > 0x00002686 <D4test3barFZS4test3Foo+30>: mov -0x4(%ebp),%edi > > 0x00002689 <D4test3barFZS4test3Foo+33>: movsl %ds:(%esi),%es:(%edi) > > 0x0000268a <D4test3barFZS4test3Foo+34>: movsl %ds:(%esi),%es:(%edi) > > 0x0000268b <D4test3barFZS4test3Foo+35>: movsl %ds:(%esi),%es:(%edi) > > You're reading it the wrong way around, at&t is src,des Sorry, my mistake, I misunderstood you. Martin, you are completely correct. Dmd is presuming that the pointer to the struct is returned in eax but foo sets that to 0, as it is entitled to. Hence movs is reading from 0.
Comment #11 by doob — 2013-04-17T04:16:38Z
Raising to critical.
Comment #12 by doob — 2013-04-17T04:18:45Z
I'm still wondering though: * Why does it work on Mac OS X 10.6.3, luck? * Is using a temporary variable in "bar" a safe workaround?
Comment #13 by doob — 2013-04-17T04:20:57Z
Would "Mac OS X ABI not followed when returning structs for extern (C)" be a better title for the issue. Can I changed that?
Comment #14 by john.loughran.colvin — 2013-04-17T04:55:37Z
(In reply to comment #12) > I'm still wondering though: > > * Why does it work on Mac OS X 10.6.3, luck? http://forum.dlang.org/post/[email protected] > * Is using a temporary variable in "bar" a safe workaround? Based on how the codegen changes with adding a temporary variable in linux, it should be safe. Disassembly from OS X could confirm that. BUT: That's only for this particular example, with a particular set of compiler setting. It's exactly the sort of thing optimisers mess around with. Personally I would not risk it.
Comment #15 by john.loughran.colvin — 2013-04-17T04:56:05Z
(In reply to comment #13) > Would "Mac OS X ABI not followed when returning structs for extern (C)" be a > better title for the issue. Can I changed that? Much better. Changed it.
Comment #16 by doob — 2013-04-17T05:17:13Z
(In reply to comment #14) > Based on how the codegen changes with adding a temporary variable in linux, it > should be safe. Disassembly from OS X could confirm that. > > BUT: > > That's only for this particular example, with a particular set of compiler > setting. It's exactly the sort of thing optimisers mess around with. Personally > I would not risk it. I suspected that. The thing is that I'm stuck with a D1 code base. I want to finished that before I port it to D2. So I'm hoping that a fix for this will be applied to the D1 branch as well. Otherwise I guess I'll have to avoid any optimizations until I've ported it to D2.
Comment #17 by doob — 2013-04-17T05:17:36Z
(In reply to comment #15) > Much better. Changed it. Cool, thanks.
Comment #18 by doob — 2013-04-23T01:37:46Z
If it's not clear, this issue happens for both 32 and 64bit. Also, if I implement the C function in D with extern (C) it won't segfault. This would indicate that DMD does something wrong in this case as well.
Comment #19 by code — 2013-04-23T17:56:10Z
(In reply to comment #18) > If it's not clear, this issue happens for both 32 and 64bit. > > Also, if I implement the C function in D with extern (C) it won't segfault. > This would indicate that DMD does something wrong in this case as well. It doesn't do anything wrong, it just sets EAX to contain the hidden pointer before returning. This is required by the SysV IA-32 ABI but apparently not on OSX.
Comment #20 by doob — 2013-04-23T23:30:49Z
(In reply to comment #19) > It doesn't do anything wrong, it just sets EAX to contain the hidden pointer > before returning. This is required by the SysV IA-32 ABI but apparently not on > OSX. First, Mac OS X uses a modified version of the SysV IA-32 ABI (there's a link a couple of posts up). Second, If an extern (C) function doesn't behave the same as a C function, in regards to ABI and calling conventions, I would say that the compiler is doing something wrong.
Comment #21 by bugzilla — 2013-04-25T23:09:41Z
(In reply to comment #0) > This code: > > http://pastebin.com/U5XdFfDq Here's the code from pastebin. I'd prefer small examples to be posted here rather than linked to. -------------------------------------------- version (D_LP64) alias double CGFloat; else alias float CGFloat; struct NSRect { NSPoint origin; NSSize size; } public struct NSPoint { public CGFloat x;// = 0.0; public CGFloat y;// = 0.0; } struct NSSize { public CGFloat width;// = 0.0; public CGFloat height;// = 0.0; } alias char* SEL; alias objc_class* Class; alias objc_object* id; struct objc_object { Class isa; } struct objc_class; extern (C) { Class objc_getClass (in char* name); id objc_msgSend (id theReceiver, SEL theSelector, ...); void objc_msgSend_stret(void* stretAddr, id theReceiver, SEL theSelector, ...); SEL sel_registerName (in char* str); } extern (C) int printf(in char*, ...); NSRect foo (id screen) { alias extern (C) NSRect function (id, SEL) frameFp; auto fp = cast(frameFp) &objc_msgSend_stret; return fp(screen, sel_registerName("visibleFrame".ptr)); } int main () { auto cls = objc_getClass("NSScreen".ptr); alias extern (C) id function (id, SEL) screenFp; auto screen = (cast(screenFp)&objc_msgSend)(cast(id) cls, sel_registerName("mainScreen".ptr)); auto frame = foo(screen); printf("x=%f y=%f width=%f height=%f\n".ptr, frame.origin.x, frame.origin.y, frame.size.width, frame.size.height); return 0; }
Comment #22 by doob — 2013-04-25T23:35:21Z
(In reply to comment #21) > Here's the code from pastebin. I'd prefer small examples to be posted here > rather than linked to. Fair enough. Here's the latest test case. It doesn't depend on any external functions or casts: C code: struct Foo { int a; int b; int c; }; typedef struct Foo Foo; Foo foo (int a) { Foo f; f.a = 1; f.b = 2; f.c = 3; return f; } D code: struct Foo { int a; int b; int c; } extern (C) Foo foo (int a); Foo bar () { return foo(0); } extern (C) int printf(in char*, ...); void main () { auto frame = bar(); printf("a=%d b=%d c=%d\n".ptr, frame.a, frame.b, frame.c); }
Comment #23 by bugzilla — 2013-04-26T00:10:24Z
Comment #24 by github-bugzilla — 2013-04-26T08:50:36Z
Commits pushed to master at https://github.com/D-Programming-Language/dmd https://github.com/D-Programming-Language/dmd/commit/a2c2ddc1be1890e48ac579a77037fb76bc322572 fix Issue 9931 - Mac OS X ABI not followed when returning structs for extern (C) https://github.com/D-Programming-Language/dmd/commit/5167cc1165b7d37c76817ab6c8d353963cf572a8 Merge pull request #1938 from WalterBright/fix9931 fix Issue 9931 - Mac OS X ABI not followed when returning structs for extern (C)
Comment #25 by github-bugzilla — 2013-04-26T11:05:39Z
Commit pushed to dmd-1.x at https://github.com/D-Programming-Language/dmd https://github.com/D-Programming-Language/dmd/commit/3646e40bc1ebb451c361e4f6c624dd5a1172ef08 Merge pull request #1938 from WalterBright/fix9931 fix Issue 9931 - Mac OS X ABI not followed when returning structs for extern (C)
Comment #26 by doob — 2013-04-27T03:56:14Z
There's still problem with 64bit. It doesn't segfault now but the values of the returned struct is completely wrong. Example: a=-2077579799 b=32767 c=1517257856 The disassembly for the 64bit C version looks like this (all functions in the same object file) : main: Relocation information (__TEXT,__text) 4 entries address pcrel length extern type scattered symbolnum/value 000000ef True long True BRANCH False _printf 000000c2 True long True BRANCH False _bar 000000ab True long True SIGNED False L_.str 0000005b True long True BRANCH False _foo Relocation information (__LD,__compact_unwind) 3 entries address pcrel length extern type scattered symbolnum/value 00000040 False quad False UNSIGND False 1 (__TEXT,__text) 00000020 False quad False UNSIGND False 1 (__TEXT,__text) 00000000 False quad False UNSIGND False 1 (__TEXT,__text) (__TEXT,__text) section _foo: 0000000000000000 pushq %rbp 0000000000000001 movq %rsp,%rbp 0000000000000004 movl %edi,0xec(%rbp) 0000000000000007 movl $0x00000001,0xe0(%rbp) 000000000000000e movl $0x00000002,0xe4(%rbp) 0000000000000015 movl $0x00000003,0xe8(%rbp) 000000000000001c movq 0xe0(%rbp),%rax 0000000000000020 movq %rax,0xf0(%rbp) 0000000000000024 movl $0x00000003,0xf8(%rbp) 000000000000002b movl 0xf0(%rbp),%edi 000000000000002e movl 0xf4(%rbp),%ecx 0000000000000031 movl 0xf8(%rbp),%edx 0000000000000034 movl %edx,0xd8(%rbp) 0000000000000037 movl %ecx,0xd4(%rbp) 000000000000003a movl %edi,0xd0(%rbp) 000000000000003d movq 0xd0(%rbp),%rax 0000000000000041 movl 0xd8(%rbp),%edx 0000000000000044 popq %rbp 0000000000000045 ret 0000000000000046 nopw %cs:_foo(%rax,%rax) _bar: 0000000000000050 pushq %rbp 0000000000000051 movq %rsp,%rbp 0000000000000054 subq $0x30,%rsp 0000000000000058 xorl %edi,%edi 000000000000005a callq _foo 000000000000005f movl %edx,0xe8(%rbp) 0000000000000062 movq %rax,0xe0(%rbp) 0000000000000066 movl 0xe0(%rbp),%edx 0000000000000069 movl 0xe4(%rbp),%edi 000000000000006c movl 0xe8(%rbp),%ecx 000000000000006f movl %ecx,0xf8(%rbp) 0000000000000072 movl %edi,0xf4(%rbp) 0000000000000075 movl %edx,0xf0(%rbp) 0000000000000078 movl 0xf0(%rbp),%ecx 000000000000007b movl 0xf4(%rbp),%edx 000000000000007e movl 0xf8(%rbp),%edi 0000000000000081 movl %edi,0xd8(%rbp) 0000000000000084 movl %edx,0xd4(%rbp) 0000000000000087 movl %ecx,0xd0(%rbp) 000000000000008a movq 0xd0(%rbp),%rax 000000000000008e movl 0xd8(%rbp),%edx 0000000000000091 addq $0x30,%rsp 0000000000000095 popq %rbp 0000000000000096 ret 0000000000000097 nopw _foo(%rax,%rax) _main: 00000000000000a0 pushq %rbp 00000000000000a1 movq %rsp,%rbp 00000000000000a4 subq $0x40,%rsp 00000000000000a8 leaq L_.str(%rip),%rax 00000000000000af movl $_foo,0xfc(%rbp) 00000000000000b6 movl %edi,0xf8(%rbp) 00000000000000b9 movq %rsi,0xf0(%rbp) 00000000000000bd movq %rax,0xc8(%rbp) 00000000000000c1 callq _bar 00000000000000c6 movl %edx,0xd8(%rbp) 00000000000000c9 movq %rax,0xd0(%rbp) 00000000000000cd movl 0xd0(%rbp),%edx 00000000000000d0 movl 0xd4(%rbp),%edi 00000000000000d3 movl 0xd8(%rbp),%ecx 00000000000000d6 movl %ecx,0xe8(%rbp) 00000000000000d9 movl %edi,0xe4(%rbp) 00000000000000dc movl %edx,0xe0(%rbp) 00000000000000df movl 0xe0(%rbp),%esi 00000000000000e2 movl 0xe4(%rbp),%edx 00000000000000e5 movl 0xe8(%rbp),%ecx 00000000000000e8 movq 0xc8(%rbp),%rdi 00000000000000ec movb $_foo,%al 00000000000000ee callq _printf 00000000000000f3 movl $_foo,%ecx 00000000000000f8 movl %eax,0xc4(%rbp) 00000000000000fb movl %ecx,%eax 00000000000000fd addq $0x40,%rsp 0000000000000101 popq %rbp 0000000000000102 ret
Comment #27 by code — 2013-04-27T06:48:42Z
> * If I compile as 64bit everything works fine > There's still problem with 64bit. Can you be a little more precise? Does it fail for the code in comment 22? > all functions in the same object file So everything in C or D?
Comment #28 by doob — 2013-04-27T07:15:30Z
(In reply to comment #27) > Can you be a little more precise? > Does it fail for the code in comment 22? Yes, the code in comment 22. > So everything in C or D? The disassembly is in C. Below is the disassembly for the D version of the "bar" function from comment 22: _D4main3barFZS4main3Foo: 0000000100000b6c pushq %rbp 0000000100000b6d movq %rsp,%rbp 0000000100000b70 subq $0x20,%rsp 0000000100000b74 movq %rdi,0xf8(%rbp) 0000000100000b78 xorl %esi,%esi 0000000100000b7a leaq 0xe8(%rbp),%rdi 0000000100000b7e callq _foo 0000000100000b83 leaq 0xe8(%rbp),%rsi 0000000100000b87 movq 0xf8(%rbp),%rdi 0000000100000b8b movsq (%esi),(%edi) 0000000100000b8d movsb (%esi),(%edi) 0000000100000b8e movsb (%esi),(%edi) 0000000100000b8f movsb (%esi),(%edi) 0000000100000b90 movsb (%esi),(%edi) 0000000100000b91 movq 0xf8(%rbp),%rax 0000000100000b95 leave 0000000100000b96 ret 0000000100000b97 nopl 0x00(%rax,%rax)
Comment #29 by verylonglogin.reg — 2014-07-26T23:33:23Z
(In reply to Jacob Carlborg from comment #26) > There's still problem with 64bit. Opened Issue 13207. Lets mark this issue as RESOLVED or change type to x64 and mark than one as DUPLICATE.
Comment #30 by verylonglogin.reg — 2014-07-26T23:55:03Z
I'd prefer to close this one in favor Issue 13207 because: * that issues is about System V AMD64 ABI which is not Mac OS X personal one (unless I'm not aware of some tricks) * that issues is about dmd producing different code for structs with same size and different content. Correct if I'm wrong.