-
Notifications
You must be signed in to change notification settings - Fork 1.6k
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
Define a Rust ABI #600
Comments
This comment has been minimized.
This comment has been minimized.
See #1675 for some motivation for this feature (implementing plugins, that is plugins for Rust programs, not for the compiler). |
Another motivation is the ability to ship shared libraries which could be reused by multiple applications on disk (reducing bandwith usage on update, reducing disk usage) and in RAM (through shared .text pages, reducing RAM usage). |
It would also make Linux distributions more happy as it would allow usage of shared libraries. Which apart from memory reduction in different ways, also make handling of security issues (or otherwise important bugs) simpler. You only have to update the code in a single place and rebuild that, instead of having to fix lots of copies of the code in different versions and rebuild everything. |
Shared libraries can be cool provided that there is a way to work around similar libraries accomplishing something in different ways such as openssl & libressl. There could also be value with making a way to allow a switch in-between static & dynamic libraries that can be set by the person compiling the crate. (possible flag?) |
In my opinion, it's very hard to take Rust seriously as a replacement systems programming language if it's not possible in any reasonably sane manner to build applications linking to libraries with a safe, stable ABI. There are a lot of good reasons for supporting shared libraries, not the least of which is building fully-featured systems for more resource constrained devices (like your average SBC or mobile device). Not having shared libraries really blows up the disk utilization in places where it's not cheap. @jpakkane describes this really well in his blog post where he conducts an experiment to prove this problem. |
Note that C++ still doesn't have a stable ABI, and it took C decades to get one. |
(two quotes from two different people) Note that rust does support shared libraries. What it doesn't support is mixing them from different compiler toolchains. Some linux distros already do this with Rust; since they have one global |
Rust also supports exporting functions with stable ABI just fine, as, for example, this shows. |
While that is true, implementations (GCC, clang, MSVC at least) have a (somewhat) defined ABI and it only changes every now and then. With Rust there is no defined ABI at all and things might break in incompatible ways with any change in the compiler, a library you're using or your code, and you can't know when this is the case as the ABI is in no way defined (sure, you could look at the compiler code, but that could also change any moment).
The problem is not only about compiler versions, but as written above about knowing what you can change in your code without breaking your ABI. And crates generally tracking their ABI in one way or another. Otherwise you can't use shared libraries created from crates in any reasonable way other than recompiling everything (and assuming ABI changed) whenever something changes. |
At least on Linux, everyone has pretty much settled on the Itanium C++ ABI. But even with the compiler locked down, it still requires very careful maintenance by a library author who hopes to export a stable ABI. Check out these KDE policies, for instance. Rust crates would have many of the same challenges in presenting a stable ABI. Plus I think this is actually compounded by not having the separation between headers and source, so it's harder to actually tell what is reachable code. It's much more than just |
Had a long term crazy idea to solve this. Define a stable format for MIR and distribute all Rust binaries/shared libraries in that format. Package managers have a post-install step to translate the MIR into executables or Because of monomorphization and unboxed types, you need to still need to relink all the MIR files when a dependency is updated. Similarily, updating the backend compiler requires all the MIR files to be recompiled. However, assuming we can push more and more optimisation passes into MIR rather than llvm, the time spent in the backend should be reduced to something acceptable. If you want to push it even further, keep everything in MIR form and use miri (or a JIT version of it) to run them. Frequently used files can be linked and persisted to disk. And we've just reinvented the JVM/CLR/Webassembly. |
For the purposes of this discussion that would require that every single Rust crate only exports a C ABI. Which is not going to happen. |
Try linking to any interface that uses |
Somewhat off-topic (we're not talking about C++ here), but as long as you stay in a compatible release series of those there is no problem. And with the correct compiler switches you can also e.g. also build your C++ code with gcc 7 against a library that was built with gcc 4.8 and uses/exposes std::string in its API. The second part seems unnecessary but nice and useful (and a lot of work), but if the first part would be true for Rust that would be a big improvement already: a defined ABI, which might change for a new release whenever necessary |
Quote from GCC about ABI compatibility (as a note to myself):
|
Relying on libraries to correctly maintain binary compatibility is just an easily avoidable safety hazard. What is wrong with just letting the package repository rebuild dependent code? (To be clear, in this scenario using shared libraries is a given. Shared libraries and ABI stability are independent issues.) And before someone argues about update download size, I must note that differential updates are not that difficult (no pun intended). In fact, if reproducible builds are done well, the resulting binary should be identical unless the library ABI has actually changed. |
Because that assumes rebuilding is cheap. While it might be true for small projects, it's a fairly expensive and crappy process when you have long chains of things in big projects. And it also makes it impossible to rely on Rust for actual systems programming because pure Rust libraries cannot be relied on for any given period of time. |
What exactly can't be relied on? Systems programming is my area of interest and I don't see what you mean. |
To get an understanding of how much rebuilding and interdependencies there actually are in a full blown Linux distro, please read this blog post. It talks about static linking so it is not directly related to this discussion but useful to get a sense of scale.
Well as an example on Debian there are 2906 packages that depend on GLib 2.0. Many more depend in it indirectly. |
Fair enough. But you don't actually need stable ABI for any of that. The ABI can change between rustc versions, but different builds on the same version are still compatible. So your distro only needs to rebuild stuff whenever they bump rustc version, which I assume is not gonna be often for a typical distro. |
At least in Fedora and Mageia, you'd be wrong about that. We bump Rust almost right after the new version arrives. |
How should I, as someone who values tools that use Rust and does not enjoy recompiling over 400(!) libraries every time rustc is bumped, inform the language team that a stable ABI would be important to me? How should we, a Linux distribution that would like to ship Rust packages, inform the language team that a stable ABI would be important to us? That six week release cycles for Rust mean we have multiple rustc bumps between releases of our distro, which means we're probably going to be sitting on 1.36 for the next six months until we have the time to bump up and rebuild everything multiple times? I want to like Rust, I want to start writing Rust, but I can't without some form of stability. |
There are no current plans to introduce anything resembling a wholesale stable ABI either for some new |
Since a part of the community seems to want stable ABI very badly and the language team is opposed to the idea, maybe there's some way to have the cake and eat it? If ABI compatibility can be efficiently checked via software, Rust could define ABI revisions. If an ABI change is needed, the revision would simply be bumped - I suppose this wouldn't happen too often because Rust is already bent on stable API compatibility. While this indeed requires some would, it would mean that the language team can change the ABI whenever they want and the distro packagers can reuse the builds whenever they can, saving disk space, network traffic and electricity. |
Is there any reason to expect that any two rustc versions to have compatible ABIs? I'd kinda assume niches get altered in every single recent rustc version. Also, the current ABI has serious problems like lacking NRVO. We should not impose any friction on these improvements. In the longer run, there are afaik no great proposals for optimized dynamic linking in modern languages like Rust, OCaML, Haskell, or even C++, so expect 5+ years of radical ABI churn whenever people really take an interest in that problem. |
OCaml allows dynamically loading and linking essentially arbitrary code, with the only restriction being that every module the plugin depends on has to be either (a) inside the plugin, or (b) inside the host application, and the hash of the module interface has to match. This preserves type safety. However, OCaml's dynamic loading mechanism is in a position that's significantly easier to handle than Rust's because OCaml doesn't have monomorphization and it has an uniform value representation, and as a result dynamic loading in OCaml is not mutually exclusive with full type erasure; the types are only (indirectly) present in the interface hash. |
(Not speaking for the Rust team) I would suggest that instead of adding more comments to this issue here, it would seem more likely to lead to results to handle the different, orthogonal issues that were discussed here (that all in one way or another are related to a Rust ABI) into separate issues. Many of them can be solved without first defining a stable Rust ABI, and can be tackled independent of that. And hopefully bring us at the same closer to actually being able to work on the general "Rust ABI" problem in the future. |
For clarity, Is this:
a "Rust should never have a stable ABI" or a "we're still nowhere near ready to make a stable ABI" kind of opposition? |
It is my opinion (as a language team member but not speaking for the team as a whole) that Rust should never have a stable ABI for the default representation |
@burdges The current calling convention is RVO-oriented (except for things like returning the variants of Also, if anyone wants to see my informal take on this: https://twitter.com/eddyb_r/status/1166953126928277505 It's close to "we're still nowhere near ready to make a stable ABI" but also IMO, a lot of the stuff around the idea of a "stable ABI" is not thought through that well, or even outright misguided. If you start from regarding C as an utter failure on pretty much all fronts other than its popularity, you might find better ways to do things. But yeah it's far off, likely involving programming languages and tooling very different from what we're used to, especially in the systems programming / low-level areas. |
With an interop library you always need a "serialization" and "deserialization" step to convert your types to some If we had a |
@Diggsey You could probably prototype parts of the Swift approach (i.e. adding a bit of indirection to hide potential differences, while trying to minimize overhead) outside of the language. You don't need full "serialization", just enough to provide access without relying on any assumptions. For example, We don't want to bake anything like stable ABIs into the standard library because that puts hard limits on what we can do with the implementation, whereas any experiments in the ecosystem could thrive and go through many iterations, with just proc macros and traits. Since you mention serialization, here's an analogy: a stable ABI being used by the standard library is like a stable serialization framework that's baked into every single type supporting it and you can't version it. There's only one way data is represented in memory, and Rust is already having trouble taking advantage of that representation, due to the compilation model. |
GCC had to deal with this for C++11, and they ended up forking stuff with ABI tags. I mention this as a cautionary tale. |
Interesting, it sounds like "Define a Rust ABI" actually means roughly two-ish things: If you want broader dynamic linking, then you pass some If you want to "Rust OS", then you want some There is an extended version of this second form where you specify I'd think |
Given that Rust already lets you switch between a bunch of different ABIs with Also consider that since most functions are only crate-internal, the issue of making sure the ABI is a good one isn't as pressing as C++, where everything is public by default. |
@Serentty Besides other reasons, the current rules can't be frozen because they don't exist. If an RFC for a specific ABI is presented, with details for all supported targets, that might work out. Even if you have such an ABI, it won't be used by libstd types, just like the C one isn't. |
(looks like eddyb and I were writing these replies at the same time)
This expresses a common misunderstanding that "the current unstable Rust ABI" is something that is already fully implemented in a well-defined, well-understood way that we know how to support to everyone's satisfaction. A large chunk of the work here is achieving consensus on what a "Rust ABI" is even supposed to be, and that whatever it's supposed to be would even be a desirable thing to stabilize. There are already loads of comments in this thread expressing disagreement over what it is or reasons why it's undesirable to ever stabilize any version of it. |
Okay, so if there's no rigid documentation for how the current ABI works, then that is indeed a problem that prevents it from being frozen.
Because the C ABI is still quite limited. Not supporting trait objects is a fairly big hurdle, for example. |
I just heard from a friend that trait pointers are currently an unstable feature in the C ABI. That actually brings it close to what I would want in a Rust ABI anyway. The only thing really left bothering me is that the standard library can't easily be shipped as separate form the executable, which would be a real bandwidth-saver over CDNs, but because of generics that's probably not entirely possible anyway. |
For reference, a blog post by @Gankra: How Swift Achieved Dynamic Linking Where Rust Couldn't |
Hi everyone! I'm interested in rust compiler development. Could anyone point me to code or a spec for how the abi works in rust as of today? |
Hi guys, sorry to bother you. May I ask if the latest version of |
Nope. And that will probably never be added, so that the language can continue to grow |
Right now, Rust has no defined ABI. That may or may not be something we want eventually.
The text was updated successfully, but these errors were encountered: