-
Notifications
You must be signed in to change notification settings - Fork 13.4k
document ABI compatibility #115476
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
Changes from all commits
281d8cc
044d057
52d22ea
8f03a55
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -1480,7 +1480,7 @@ mod prim_ref {} | |
/// | ||
/// ### Casting to and from integers | ||
/// | ||
/// You cast function pointers directly to integers: | ||
/// You can cast function pointers directly to integers: | ||
/// | ||
/// ```rust | ||
/// let fnptr: fn(i32) -> i32 = |x| x+2; | ||
|
@@ -1506,6 +1506,114 @@ mod prim_ref {} | |
/// Note that all of this is not portable to platforms where function pointers and data pointers | ||
/// have different sizes. | ||
/// | ||
/// ### ABI compatibility | ||
/// | ||
/// Generally, when a function is declared with one signature and called via a function pointer with | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. It isn't just about function pointers. If a function is declared There was a problem hiding this comment. Choose a reason for hiding this commentThe 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 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 There was a problem hiding this comment. Choose a reason for hiding this commentThe 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 There was a problem hiding this comment. Choose a reason for hiding this commentThe 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. |
||
/// a different signature, the two signatures must be *ABI-compatible* or else calling the function | ||
/// via that function pointer is Undefined Behavior. ABI compatibility is a lot stricter than merely | ||
/// having the same memory layout; for example, even if `i32` and `f32` have the same size and | ||
/// alignment, they might be passed in different registers and hence not be ABI-compatible. | ||
/// | ||
/// ABI compatibility as a concern only arises in code that alters the type of function pointers, | ||
/// code that imports functions via `extern` blocks, and in code that combines `#[target_feature]` | ||
/// with `extern fn`. Altering the type of function pointers is wildly unsafe (as in, a lot more | ||
/// unsafe than even [`transmute_copy`][mem::transmute_copy]), and should only occur in the most | ||
/// exceptional circumstances. Most Rust code just imports functions via `use`. `#[target_feature]` | ||
/// is also used rarely. So, most likely you do not have to worry about ABI compatibility. | ||
/// | ||
/// But assuming such circumstances, what are the rules? For this section, we are only considering | ||
/// the ABI of direct Rust-to-Rust calls, not linking in general -- once functions are imported via | ||
/// `extern` blocks, there are more things to consider that we do not go into here. | ||
/// | ||
/// For two signatures to be considered *ABI-compatible*, they must use a compatible ABI string, | ||
/// must take the same number of arguments, the individual argument types and the return types must | ||
/// be ABI-compatible, and the target feature requirements must be met (see the subsection below for | ||
/// the last point). The ABI string is declared via `extern "ABI" fn(...) -> ...`; note that | ||
/// `fn name(...) -> ...` implicitly uses the `"Rust"` ABI string and `extern fn name(...) -> ...` | ||
/// implicitly uses the `"C"` ABI string. | ||
/// | ||
/// The ABI strings are guaranteed to be compatible if they are the same, or if the caller ABI | ||
/// string is `$X-unwind` and the callee ABI string is `$X`, where `$X` is one of the following: | ||
/// "C", "aapcs", "fastcall", "stdcall", "system", "sysv64", "thiscall", "vectorcall", "win64". | ||
/// | ||
/// The following types are guaranteed to be ABI-compatible: | ||
/// | ||
/// - `*const T`, `*mut T`, `&T`, `&mut T`, `Box<T>` (specifically, only `Box<T, Global>`), and | ||
/// `NonNull<T>` are all ABI-compatible with each other for all `T`. They are also ABI-compatible | ||
/// with each other for _different_ `T` if they have the same metadata type (`<T as | ||
/// Pointee>::Metadata`). | ||
/// - `usize` is ABI-compatible with the `uN` integer type of the same size, and likewise `isize` is | ||
/// ABI-compatible with the `iN` integer type of the same size. | ||
RalfJung marked this conversation as resolved.
Show resolved
Hide resolved
|
||
/// - Any two `fn` (function pointer) types are ABI-compatible with each other if they have the same | ||
/// ABI string or the ABI string only differs in a trailing `-unwind`, independent of the rest of | ||
/// their signature. (This means you can pass `fn()` to a function expecting `fn(i32)`, and the | ||
/// call will be valid ABI-wise. The callee receives the result of transmuting the function pointer | ||
/// from `fn()` to `fn(i32)`; that transmutation is itself a well-defined operation, it's just | ||
/// almost certainly UB to later call that function pointer.) | ||
/// - Any two types with size 0 and alignment 1 are ABI-compatible. | ||
/// - A `repr(transparent)` type `T` is ABI-compatible with its unique non-trivial field, i.e., the | ||
/// unique field that doesn't have size 0 and alignment 1 (if there is such a field). | ||
/// - `i32` is ABI-compatible with `NonZeroI32`, and similar for all other integer types with their | ||
/// matching `NonZero*` type. | ||
/// - If `T` is guaranteed to be subject to the [null pointer | ||
/// optimization](option/index.html#representation), then `T` and `Option<T>` are ABI-compatible. | ||
Mark-Simulacrum marked this conversation as resolved.
Show resolved
Hide resolved
tmandry marked this conversation as resolved.
Show resolved
Hide resolved
|
||
/// | ||
/// Furthermore, ABI compatibility satisfies the following general properties: | ||
/// | ||
/// - Every type is ABI-compatible with itself. | ||
/// - If `T1` and `T2` are ABI-compatible, then two `repr(C)` types that only differ because one | ||
/// field type was changed from `T1` to `T2` are ABI-compatible. | ||
/// - If `T1` and `T2` are ABI-compatible and `T2` and `T3` are ABI-compatible, then so are `T1` and | ||
/// `T3` (i.e., ABI-compatibility is transitive). | ||
/// - If `T1` and `T2` are ABI-compatible, then so are `T2` and `T1` (i.e., ABI-compatibility is | ||
/// symmetric). | ||
/// | ||
/// More signatures can be ABI-compatible on specific targets, but that should not be relied upon | ||
/// since it is not portable and not a stable guarantee. | ||
Comment on lines
+1571
to
+1572
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. It could be declared stable and relied on for code that is There was a problem hiding this comment. Choose a reason for hiding this commentThe 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 commentThe 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 commentThe reason will be displayed to describe this comment to others. Learn more. One obvious case would be There was a problem hiding this comment. Choose a reason for hiding this commentThe 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: There was a problem hiding this comment. Choose a reason for hiding this commentThe 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:
|
||
/// | ||
/// Noteworthy cases of types *not* being ABI-compatible in general are: | ||
/// * `bool` vs `u8`, and `i32` vs `u32`: on some targets, the calling conventions for these types | ||
/// differ in terms of what they guarantee for the remaining bits in the register that are not | ||
/// used by the value. | ||
/// * `i32` vs `f32` are not compatible either, as has already been mentioned above. | ||
/// * `struct Foo(u32)` and `u32` are not compatible (without `repr(transparent)`) since structs are | ||
/// aggregate types and often passed in a different way than primitives like `i32`. | ||
/// | ||
/// Note that these rules describe when two completely known types are ABI-compatible. When | ||
/// considering ABI compatibility of a type declared in another crate (including the standard | ||
/// library), consider that any type that has a private field or the `#[non_exhaustive]` attribute | ||
/// may change its layout as a non-breaking update unless documented otherwise -- so for instance, | ||
/// even if such a type is a 1-ZST or `repr(transparent)` right now, this might change with any | ||
/// library version bump. | ||
/// | ||
/// If the declared signature and the signature of the function pointer are ABI-compatible, then the | ||
/// function call behaves as if every argument was [`transmute`d][mem::transmute] from the | ||
/// type in the function pointer to the type at the function declaration, and the return value is | ||
/// [`transmute`d][mem::transmute] from the type in the declaration to the type in the | ||
/// pointer. All the usual caveats and concerns around transmutation apply; for instance, if the | ||
/// function expects a `NonNullI32` and the function pointer uses the ABI-compatible type | ||
/// `Option<NonNullI32>`, and the value used for the argument is `None`, then this call is Undefined | ||
/// Behavior since transmuting `None::<NonNullI32>` to `NonNullI32` violates the non-null | ||
/// requirement. | ||
/// | ||
/// #### Requirements concerning target features | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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. |
||
/// | ||
/// Under some conditions, the signature used by the caller and the callee can be ABI-incompatible | ||
/// even if the exact same ABI string and types are being used. As an example, the | ||
/// `std::arch::x86_64::__m256` type has a different `extern "C"` ABI when the `avx` feature is | ||
/// enabled vs when it is not enabled. | ||
/// | ||
/// Therefore, to ensure ABI compatibility when code using different target features is combined | ||
/// (such as via `#[target_feature]`), we further require that one of the following conditions is | ||
/// met: | ||
/// | ||
/// - The function uses the `"Rust"` ABI string (which is the default without `extern`). | ||
/// - Caller and callee are using the exact same set of target features. For the callee we consider | ||
/// the features enabled (via `#[target_feature]` and `-C target-feature`/`-C target-cpu`) at the | ||
/// declaration site; for the caller we consider the features enabled at the call site. | ||
/// - Neither any argument nor the return value involves a SIMD type (`#[repr(simd)]`) that is not | ||
/// behind a pointer indirection (i.e., `*mut __m256` is fine, but `(i32, __m256)` is not). | ||
/// | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Does this mean we need to declare the target features in every In general it would be helpful to generalize the discussion here to also handle cases where pointers are not involved but instead There was a problem hiding this comment. Choose a reason for hiding this commentThe 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. |
||
/// ### Trait implementations | ||
/// | ||
/// In this documentation the shorthand `fn (T₁, T₂, …, Tₙ)` is used to represent non-variadic | ||
|
Uh oh!
There was an error while loading. Please reload this page.