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.
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
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);
}
> * 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.