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
fix Issue 16274 - short argument passed in 16 bit register #11651
Conversation
Thanks for your pull request, @WalterBright! Bugzilla references
Testing this PR locallyIf you don't have a local development environment setup, you can use Digger to test this PR: dub run digger -- build "master + dmd#11651" |
Test? |
@WalterBright I think 1byte values should be promoted to 4byte. |
src/dmd/e2ir.d
Outdated
/* Do integral promotions. This is not necessary per the C ABI, but | ||
* some code from the C world seems to rely on it. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This description is a bit misleading. Integral promotion is a language-level concept, specified by the language spec. The use of zero/sign-extension when passing arguments in registers is something specified by the platform-specific ABI (psABI). Of course, the two are closely related. Still, this mixes concepts that describe things at different levels, and which don't always map cleanly between each other. For instance, the RISC-V psABI does zero/sign-extension to 32 bits and then sign-extension to 64 bits (for RV64), which is something that would be surprising when reasoning in terms of integral promotions.
Anyway, I just quickly checked the latest version of the x86-64 psABI and it seems that it's still omissive about the issue addressed by this PR. That's quite surprising, as this is one of the fundamental things that a psABI specifies. The only relevant thing I found was a footnote about _Bool
, saying that bits 7-1 are specified to be 0, but the others are unspecified. That is consistent with the general rule about the unused bits being unspecified. Microsoft's x86-64 ABI document is more explicit about this and says that "All integer arguments in registers are right-justified, so the callee can ignore the upper bits of the register and access only the portion of the register necessary". (Even that description is ambiguous, as it's still consistent with the upper bits being specified by some rule).
I will try to gather more information about how this is currently being handled in the real-world by various compilers. In any case, I suppose there's no major cost for this "fix", even if this has been addressed by more recent versions of the compilers and the curses library, so it probably makes sense to merge it. I just recommend rewording the comment.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thank you for doing the research on this. Unfortunately, we can't tell people to fix their C code, they'll just say "D is broken". We simply have to generate code that will work with their C code, regardless of what the spec says.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Unfortunately, we can't tell people to fix their C code, they'll just say "D is broken". We simply have to generate code that will work with their C code, regardless of what the spec says.
I agree. I was just wondering if the situation on the ground could have changed in the meanwhile. The relevant compilers could have been updated and newer versions of the libraries distributed, such that this no longer was an actual issue for reasonably recent distributions. I'll delve into this later. It seems that, at least for a while, some major compilers were disagreeing about this issue.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I didn't have time to check more deeply but it seems that GCC is conservative both on the caller side (clearing out the upper bits before calling) and the callee side (zero/sign-extending before using the arguments), while Clang is conservative on the caller side but optimistically assumes that they were already extended when using them on the callee side. I tested with versions of GCC and Clang from trunk to quite old versions, and that remained the same. This is consistent with the bug report, which was reported for OS X, so the library was probably compiled with Clang. The curses library is incorrectly implementing a function with an . I agree that the sensible solution is for DMD to also be conservative on the caller side. On the callee side we're probably safe, given that Clang has lived with its approach for a long while.int
parameter that is declared as a short
in the prototype. In theory that's undefined behavior, in practice it exposes these differences in how the ABI is handled by the various compilers
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The curses library is incorrectly implementing a function with an int parameter that is declared as a short in the prototype.
I can't see that in the code I posted, what I see is (after expanding macros)
int _nc_init_pair(SCREEN *sp, int pair, int f, int b)
{
// ...
}
int init_pair_sp (SCREEN *sp, short pair, short f, short b)
{
return _nc_init_pair(sp, pair, f, b);
}
int init_pair (short pair, short f, short b)
{
return init_pair_sp(CURRENT_SCREEN, pair, f, b);
}
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I can't see that in the code I posted, what I see is (after expanding macros)
Thanks. I was writing that bit from memory, and I probably also misdiagnosed that when I last looked at it. That further cements that we need this fix.
src/dmd/e2ir.d
Outdated
@@ -268,6 +268,43 @@ private elem *callfunc(const ref Loc loc, | |||
*/ | |||
ea.Ety = TYllong; | |||
} | |||
|
|||
/* Do integral promotions. This is not necessary per the C ABI, but | |||
* some code from the C world seems to rely on it. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes, it is necessary for the C ABI. On x86_64, all integral types smaller than int
should actually be passed as an int
. This apply to both parameters in the protoype and variadic parameters.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
My reading of the ABI doesn't make this clear. 16274 is the only case I've ever heard of where it's a problem. I also tries various cases with gcc, and on the receiving end I can't get it to rely on the upper bits.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I was reading the compiler docs: https://gcc.gnu.org/onlinedocs/gccint/Stack-Arguments.html#TARGET_005fPROMOTE_005fPROTOTYPES
For x86_64 targets, this hook is set to true.
277ac02
to
b7a1e24
Compare
@UplinkCoder they're promote to int. |
{ | ||
// provide alternate implementation of .pair() | ||
pragma(mangle, "pair") | ||
extern(C) static void pair(int a, int b, int c, int d) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
wouldn't it be much better to implement this on the C side, to test the C-ABI interop?
The point of this change is not to force arguments to become int
, it's to match C ABI, which is slightly different (imagine a platform where the C ABI is not to promote to int
).
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
and explicitly test this with some extreme values where things may go wrong (2 and 4 don't sound very difficult)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
wouldn't it be much better to implement this on the C side, to test the C-ABI interop?
- I couldn't get gcc to access those upper bits.
- Tests are better self-contained as much as practical.
- There are plenty of other tests done in C that check short and int arguments.
- Any unpredictable C ABI changes are going to cause unpredictable problems, experience shows it's a waste of time attempting to predict them. The wackiest one is the Win64 one which took a left turn at Alberqueue into bizzaro land. Nobody could have predicted that.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yeah, this test totally won't pass on ARM, SPARC, or POWER (to name three widely supported alternative targets).
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I couldn't get gcc to access those upper bits.
The original implementation of init_pair is here:
https://github.com/mirror/ncurses/blob/790a85dbd4a81d5f5d8dd02a44d84f01512ef443/ncurses/base/lib_color.c#L556-L703
Though as the host platform in the bug report is OSX, I suspect it is clang that is compiling the C code.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
- Any unpredictable C ABI changes are going to cause unpredictable problems, experience shows it's a waste of time attempting to predict them.
Isn't that the point of testcases though? To detect when things no longer work and a fix is needed?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If D's extern (C)
isn't working, not much is going to work. Here, we don't need to test that extern (C)
is working.
ba46843
to
e74987d
Compare
This is a good fix, and needs to get pulled. |
test/runnable/mars1.d
Outdated
//printf("%d %d %d %d\n", a, b, c, d); | ||
assert(a == -1); | ||
assert(b == 2); | ||
assert(c == -3); | ||
assert(d == 4); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Bad indentation?
I recommend the following changes to this PR, in decreasing order of importance:
The implementation of the test seems fine to me as is (other than running unconditionally).. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Implement seems fine, but the test is not.
Doing a quick grep for the referenced ABI hook that controls this promoting behaviour, these are the targets that look like the test will pass on:
Whereas these are targets that the test will fail on
Where the target is a primary/secondary supported platform, I've made it clear as comments. (I could post the full CPU list - there are 55 in total - but this'll just add noise to the current review). |
Since this passes all of the tests in the test suite, I suggest not worrying about it. When the tests are run on a platform where it won't work, then the test can be adjusted. There are likely quite a lot of x86 specific tests in the suite. For people who need an explanation of why that test is in the test suite, that's why there's a link in it to the Bugzilla issue which contains a link here. I often make use of this when researching why a piece of code exists. |
e74987d
to
9ebb54c
Compare
No, I do worry about it, because people test D on the platforms I listed very frequently. For example, I already know that I broke Solaris/SPARC from a patch I committed yesterday. The DMD testsuite passing is neither representative of the genuine concerns raised, nor the broader platforms that D is being ran on. I've ran a modified version of the test on a few servers.
On AArch64:
On PPC64LE
On SPARC
On x86_64
|
Based on this result I assume that you ran the test with the existing (non-extending) behaviour. I guess an interesting question is what the results are when you also apply the patch. |
The patch can't be applied, because it is specific to DMD. |
FYI @WalterBright you've already fixed at least half the issue in #11648. So using integer constants won't trigger the possible problem anymore. |
Alternative fix made in #11719. Which does promotions in the front-end using existing machinery and a new target hook so the compiler back-end can decide what it wants to do. |
dmd's backend is x86, x86-64 only so it never needs to decide what to do. |
@ibuclaw when requesting changes, please be much more specific. |
9ebb54c
to
e66eb8c
Compare
|
e66eb8c
to
bc2f238
Compare
I versioned it so the test is only for X86 and X86_64.
It's clearly a codegen issue, not a front end one.
Variadic integral promotion is required by the language spec, which is entirely different from this promotion which is an ABI thing. Doing it in the front end means changing the front end's notion of the type, which is wrong and will cause problems, such as with D's inliner. The inliner runs after the semantic pass and before the code generator, and presumes the argument types are all consistent with the parameter types. Making them inconsistent will likely cause problems. I agree the inliner really belongs in the backend, but that is a project for some other time. If you need to do this a different way for your supported targets, feel free, but this PR is the right way for DMD. |
The type doesn't change - the parameter in
It's more a violation of the DRY principle. This is just repeating what |
The argument type passed to the function call no longer matches the parameter type. This mismatch will cause problems now or later. I really do not want to mess with this kind of hack, given the number of people that work on the compiler. The frontend and the backend do repeat themselves here and there, but that's necessary to keep them distinct code bases. It's better to keep it that way. Besides, it's not like the definition of C integer promotions is going to change. |
@kinke - would it fit better in ldc if prototypes were promoted by integralPromotions, or if you duplicated its logic in the code generator? |
@ibuclaw Given the above discussion and @kinke 's reply, is it ok if we merge this and close #11719 ? @WalterBright can you please rebase so that everything is ready in case @ibuclaw accepts this? |
bc2f238
to
8b96de9
Compare
…6-bit register, against ABI