Skip to content

spec: abi: clarify calling convention for other architectures other than x86 #3120

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 1 commit into
base: master
Choose a base branch
from

Conversation

ljmf00
Copy link
Member

@ljmf00 ljmf00 commented Nov 9, 2021

Observing the assembly generated by the following source file in either DMD and
LDC:

extern (C) void ccall( size_t a, size_t b, size_t c );
extern (D) void dcall( size_t a, size_t b, size_t c );

//...

ccall( a, b, c ); // RDI, RSI, RDX
dcall( a, b, c ); // RDX, RSI, RDI

The parameters are passed in the reverse order in functions with extern(D)
linkage. Furthermore, on x86 the calling convention seems to be what is
described by the current subsections, not only matching Windows x86, but also
appears to be the same behaviour on System V ABI.

Fixes #20204 .

Signed-off-by: Luís Ferreira [email protected]


For more context: https://godbolt.org/z/sWz4x37bb

…han x86

Observing the assembly generated by the following source file in either DMD and
LDC:

```d
extern (C) void ccall( size_t a, size_t b, size_t c );
extern (D) void dcall( size_t a, size_t b, size_t c );

//...

ccall( a, b, c ); // RDI, RSI, RDX
dcall( a, b, c ); // RDX, RSI, RDI
```

The parameters are passed in the reverse order in functions with `extern(D)`
linkage. Furthermore, on x86 the calling convention seems to be what is
described by the current subsections, not only matching Windows x86, but also
appears to be the same behaviour on System V ABI.

Fixes #20204 .

Signed-off-by: Luís Ferreira <[email protected]>
@dlang-bot
Copy link
Contributor

Thanks for your pull request, @ljmf00!

Bugzilla references

Auto-close Bugzilla Severity Description
20204 normal need to fix ABI about registers using

@ljmf00 ljmf00 changed the title spec: abi: clarify calling convention for other architectures other t… spec: abi: clarify calling convention for other architectures other than x86 Nov 9, 2021
Except that the extern (D) calling convention for Windows x86 is described here.
)
$(P Functions with $(D extern (C)) linkage matches the C calling
convention used by the supported C compiler on the host system.)
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I still think that used by the supported C compiler on the host system is to vague


$(P Functions with $(D extern (D)) linkage matches the calling
convention used by $(D extern (C)) functions with the parameters
pushed in the reverse order, except for 32-bit x86 architecture, which
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not really, parameters are just evaluated from left to right.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you elaborate? Look into the godbolt link and issue attached to the PR description for context. Maybe I expressed myself wrongly.

Copy link
Member

@ibuclaw ibuclaw Nov 10, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The real intent is left to right evaluation of parameters.

Dmd passing arguments in reverse is just an implementation detail, because Walter doesn't want to use too many scratch registers to hold intermediate values before calling the function.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think it is an implementation detail. If the specification is correct, then the compiler is behaving wrongly. I interpret implementation details as different implementation behaviour that doesn't affect specification conformance.

In practice, if I try to link a function generated by DMD with another binary generated by a compiler that generates a conformant convention call, bad things may happen, if the order matters, because the registers are reversed and therefore the parameters are passed wrongly.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

GDC is spec conformant. ;-)

https://godbolt.org/z/57KheoTKW

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

GDC is spec conformant. ;-)

godbolt.org/z/57KheoTKW

That is good to know and makes sense. So only LDC/DMD is wrong. What about the 32-bit x86 on GDC? Since the spec only mentions Windows, it is also conformant. I don't know about Windows x86 since I don't use Windows, but should we deviate from the standard calling convention there too?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

GCC only supports MinGW or Cygwin, so I only follow what is the ABI for that target.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

GCC only supports MinGW or Cygwin, so I only follow what is the ABI for that target.

Doesn't MinGW try to mimic the same ABI as Windows? Anyway, it is not so explicit that way, so GDC seems to conform with it, only DMD and LDC do not. See dlang/dmd#13287 where we can discuss the problem more in-depth and a possible fix for this.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

extern(C) code may be compatible, but MinGW uses Itanium ABI for C++, not MSVC.

@kinke
Copy link
Contributor

kinke commented Nov 9, 2021

I've always regarded this as an ABI spec violation of both DMD and LDC, unfortunately not that simple to fix mainly because of existing DMD-style inline asm in druntime/Phobos and user code. If given the choice, I'd definitely fix the implementation rather than the spec.

@ljmf00
Copy link
Member Author

ljmf00 commented Nov 9, 2021

I've always regarded this as an ABI spec violation of both DMD and LDC, unfortunately not that simple to fix mainly because of existing DMD-style inline asm in druntime/Phobos and user code. If given the choice, I'd definitely fix the implementation rather than the spec.

I agree with you because it doesn't make much sense. I don't get the rationale for the x86 special case either. On the other hand, changing the implementation make binaries incompatible leading to a huge breaking change with old shared objects.

I'm kinda divided here, to be honest, but inclined to the "clean way", which is to fix the implementation.

@kinke
Copy link
Contributor

kinke commented Nov 11, 2021

changing the implementation make binaries incompatible leading to a huge breaking change with old shared objects

That's not that big a deal, we have had numerous breaking ABI changes in the past, so binaries compiled with different compiler versions aren't expected to be compatible.

The only real problem I see is that efforts should probably be synchronized across DMD and LDC, in order to tell people that from D 2.100 on (or so), the params aren't reversed for extern(D) anymore (except for 32-bit x86), for both DMD and LDC. So if they've been accessing parameter registers directly in DMD-style inline asm, they need to adapt those, but it'll work with both DMD and LDC at least.

I'm more than happy to finally do this for LDC (I've had a go at this some 5 years ago already).

@kinke
Copy link
Contributor

kinke commented Nov 11, 2021

This parameter reversal has been a PITA at multiple occasions, e.g., dlang/dmd#11630.

@ljmf00
Copy link
Member Author

ljmf00 commented Nov 11, 2021

changing the implementation make binaries incompatible leading to a huge breaking change with old shared objects

That's not that big a deal, we have had numerous breaking ABI changes in the past, so binaries compiled with different compiler versions aren't expected to be compatible.

The only real problem I see is that efforts should probably be synchronized across DMD and LDC, in order to tell people that from D 2.100 on (or so), the params aren't reversed for extern(D) anymore (except for 32-bit x86), for both DMD and LDC. So if they've been accessing parameter registers directly in DMD-style inline asm, they need to adapt those, but it'll work with both DMD and LDC at least.

I'm more than happy to finally do this for LDC (I've had a go at this some 5 years ago already).

Sure, I can see what I can do about it. I'm not into deep backend stuff like target codegen. What about the 32-bit x86? Why is the calling convention different there too?

This parameter reversal has been a PITA at multiple occasions, e.g., dlang/dmd#11630.

At a first glance, it appears to me that this is being fixed in the wrong place. This parameter reverse order behaviour appears to be intentional on the IR generation: https://github.com/dlang/dmd/blob/master/src/dmd/e2ir.d#L5261

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants