Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
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
document ABI compatibility #115476
document ABI compatibility #115476
Changes from 1 commit
281d8cc
044d057
52d22ea
8f03a55
File filter
Filter by extension
Conversations
Jump to
There are no files selected for viewing
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.
It isn't just about function pointers. If a function is declared
external "ABI"
in Rust then the called function must have that ABI. Or if the function is declared justextern
orextern "C"
then it must have the default ABI (C compilers let you override the ABI of a function, or the function may be written in assembly language specifically for one calling convention).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.
Where do we document and guarantee what one has to do when linking multiple Rust objects together? Do we even support that with
extern "Rust"
functions?We have to put these docs somewhere, and function pointers are the only way to trigger these issues inside the language (without exotic things such as
dlopen
or manually linking things together), so I figured this would make sense. If you can think of a better place where we can put this, I can move 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.
I understand from seeing other issues why you see "inside the language" as a useful initial scope because that's what seems to matter for the SIMD stuff. I don't object to that.
IME people are much more likely to run into ABI issues in cross-language situations since there are no guardrails at all, so I hope we at least are open to solving the issue for cross-language cases too. Perhaps that means adding similar language to the documentation of
extern
and then factoring out the commonality to some top-level section of the language reference, in a future PR.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.
Fully agreed. It's also a much less defined space, you basically have to define "which C type and ABI is compatible with which Rust type and ABI" (and then potentially also which C++/Julia/whatever type, though I guess we can rely on those languages saying which C types their types are compatible with). This depends on the target and honestly I'm quickly out of my depth for those questions. ABI still surprises me with new nightmares every few weeks, so I'm sure there's more to come.
I do hope someone will pick this up eventually.
Of course we are, I didn't want to give any indication to the contrary! It's just not the problem I want to solve right now. It's not on my own personal todo list either, at the moment.
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 case I run into the most is where the code that uses some feature (e.g. vector registers) is written in another language that doesn't have
target_feature
(e.g. assembly language .S files) and that assumes that all CPU-supported features are available to use (e.g. vector registers). When porting to systems where this isn't the case (e.g. x86-64-unknown-none) I end up needing to audit all theextern "C"
declarations.The other case I've run into is where I am porting assembly code (.S files) from Linux to Apple targets, where the assembly code uses registers (e.g. r18) that aren't safe to use on Apple targets.
In both cases, no function pointers are involved and there's no use of
target_feature
(and also there are no vector types in the function signature).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.
For this PR we are strictly only concerned with Rust-to-Rust calls. I agree that Rust-to-other/other-to-Rust calls are important, but they are also a huge topic. Let's not scope creep this PR, please.
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 don't object to that idea. But this specific sentence is very misleading since it says "ABI compatibility as a concern only arises in" two situations when that's actually not true.
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.
That's fair. I adjusted the wording.
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.
Sadly this target-feature hell means that we have to be much less definite when we say "if you care about this you are doing something outlandish" :/
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 am not sure what you mean by "successfully passes the pointer." "calling the pointer" doesn't make sense to me either. Did you mean "calling the function through the pointer"?
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.
There's no UB from passing the function pointer around.
IOW, if the caller has signature
fn(fn())
, and the callee has signaturefn(fn(i32) -> i32)
, then the function call itself is completely well-defined. Caller and callee are ABI compatible even though the only argument of this function has a different type in the two signatures.Or to be completely concrete, this code does not have UB.
Of course if the callee actually calls the function pointer that it was given as argument, then the fact that the signatures are different matters.
When
f: fn()
, thenf()
is "calling the function pointer". At least that's how I would call that operation. You seem to call it "calling the function through the pointer".With data pointers we say that we read and write the pointer, we don't always spell out "read and write the memory through the pointer". I am following the same principle for function pointers.
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 point here is a bit confusing since the question "are two fn ptr types A and B ABI-compatible" is ambiguous:
fn(A)
with a caller-side signature of typefn(B)
"Usually when we say, e.g., "
u32
andNonZeroU32
are ABI-compatible", we mean the latter, but when the two types in questions are themselves function pointers then the terminology becomes unclear.I'd be happy for suggestions for how to word this more clearly.
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.
First, I admit I don't think it is good to guarantee anything about transmuting function pointers with incompatible signatures, as I don't understand the motivation driving us to make such guarantees. So one way to clarify things would be to just have one set of rules for ABI compatibility / transmutation for function types for now, and then maybe follow up later with a proposal to guarantee transmutations of incompatible function types work.
If you reserve "ABI compatibility" to be strictly about function calls, function declarations, function definitions, and function pointers, then we could have a separate term "transmutable, e.g. "NonZeroU32 is transmutable to u32" (where "transmutable" means the result of the transmutation is well-defined), and we could have a separate term "A is argument-compatible with B" to talk about where an argument of type A can be passed for a function parameter of type B. And hopefully we would have rules like "Any type A that is transmutable to B is argument-compatible with B." Then you could use "argument-compatible" as part of the definition of ABI-compatibility, in particular each argument in a function-call must be argument-compatible with corresponding parameter in the function's definition (and later, declaration), and (target_feature stuff, etc.).
In other words, don't use "ABI-compatible" and "ABI compatibility" terms to talk about argument/parameter compatibility.
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 think it would be really strange to say that
*const i32
and*mut u8
are ABI-compatible, butfn()
andfn(i32)
are not. Both are just pointer types with different pointee information. It'd be very surprising to require the function signatures to be ABI compatible in order to have the pointers be ABI compatible.That's a very bad term for this situation.
i32
is transmutable tou32
but they are not ABI compatible. The PR explicitly talks about this.For better or worse, "ABI compatible" is already in common use for this concept, in questions like "are
u32
andchar
ABI compaible?" I think it's not a bad term, it's only a bit annoying for this specific case of ABI compatibility of function pointer types.I have updated the text to use an example instead of remaining so abstract, I hope that helps.
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.
It could be declared stable and relied on for code that is
#[cfg]
'd for a specific target. Though it sounds like we aren't declaring any of those stable right now.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 could, but this PR for now takes the stance that we shouldn't do 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.
I don't see why we shouldn't, but also not opposed to leaving this until we find a use case.
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.
One obvious case would be
usize
vsu64
on 64 bit platforms (andu32
,u16
etc).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.
That sounds more like a case we might want to add to the list:
usize
is compatible with theuN
type of the same size; and similar forisize
.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.
Which wording would you propose here? "There might be other stable things but we won't tell you which" is useless. And we certainly don't want to promise "anything that's incidentally ABI-compat on some target will remain ABI-compat on that target". So I think we only have two options:
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.
@chorman0773 can you double-checking this new section, does this sound accurate?
@rust-lang/opsem please also take a look.
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.
Does this mean we need to declare the target features in every
extern "C"
declaration?In general it would be helpful to generalize the discussion here to also handle cases where pointers are not involved but instead
extern "ABI"
is used to declare a non-Rust function that is then called from Rust.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.
As mentioned above I strongly want to avoid scope creep to non-Rust calls here. That's a much more complicated discussion.