RFC: Support External Linkers - #3993
Conversation
Original Author: Patrick Walton. Co-authored-by: Patrick Walton <pcwalton@fb.com>
…scope sections. Supported Platforms note, expanded examples, reflow
| the Rust compiler, driven by a variety of build systems, in a way that doesn't result in | ||
| symbol conflicts when diamond dependencies are involved. | ||
|
|
||
| ### Standard library bundles |
There was a problem hiding this comment.
The way I was imagining this wouldn't special case the standard library. Rather we would introduce a new rstaticlib crate type to fill the following matrix:
| - | Rust+C ABI | C ABI only |
|---|---|---|
| statically linked | rstaticlib | staticlib |
| dynamically linked | (rust) dylib | cdylib |
So like the rust dylib crate type it would be usable as regular rust dependency. Just like the staticlib crate type it would be statically linked. And like all non-rlib crate types it can contain multiple crates and contains things like the allocator shim as appropriate.
This avoids special casing the standard library and would make it possible to use #[global_allocator] and work for no_std projects too. And in addition it would avoid the need for -Crlib-version as no ribs would need to be directly linked. And EIIs would trivially work. If necessary the compiler can insert any object files to wire up EII defaults when building the rstaticlib like would happen for (c)dylibs and staticlibs.
The way you did use this is to compile some crate as rstaticlib and have this crate depend on the standard library and have it or a dependency define #[global_allocator] if you need one. And then every other can be compiled as either rstaticlib or rlib (if you want to bundle it together with other rlibs to form another rstaticlib) with a dependency on this first rstaticlib. And then you link all rstaticlibs together as regular C static libraries.
There was a problem hiding this comment.
Am I right in thinking you're describing something similar to the staticlib-nobundle alternative described here:
https://github.com/rust-lang/rfcs/pull/3993/changes#diff-68e8cfbb8f0b236256ec7cb366e93d1a56e4296801ff0f97fea3cc7729217b9dR472-R480
But with this archive file also including the .rmeta data (and possibly other things) which an rlib contains, so that rustc would consume it a lot like an rlib when building a rust application?
In this world I think you're saying the supported workflow would look something like this:
- Build at least one crate (although probably close to 1, for performance reasons since these would be large?) as an
rstaticlib. - Build all your other rust dependencies as an
rlib - Link your application by passing in the rlibs and rstaticlibs together.
Assuming I got the above right:
I agree this would avoid special casing the standard library. In practice, if the number of rstaticlib crates being built needs to be kept low, I imagine build systems choose to build exactly one of these at the bottom of the dependency tree?
Separately I'm not currently sure how we could support being able to link multiple rstaticlibs while having them export the standard library symbols as needed for linking against rlibs.
There was a problem hiding this comment.
For step 2 you would either build all other rust dependencies as rstaticlib or if you have an EII, you build the crate that declare the EII and the one that define it as rlib and then build an rstaticlib that bundles them together (or build the one defining the EII directly as rstaticlib, bundling the crate that declares the EII).
Separately I'm not currently sure how we could support being able to link multiple rstaticlibs while having them export the standard library symbols as needed for linking against rlibs.
If one rstaticlib depends on another rstaticlib, it will assume that all crates included in that other rstaticlib don't need to be bundled into the local rstaticlib. This is the same behavior as for rust dylibs where any crates included through an upstream dylib don't get linked in locally but rather the version in the upstream dylib is used and rustc errors if two dylibs that are used as dependency together both bundle the same crate.
| echo $? | ||
| ``` | ||
|
|
||
| RFC Note: This works today by removing the `-C emit-std-bundle=yes` and `-C rlib-version=v0` flags. |
There was a problem hiding this comment.
You've already said this above, but it helps to explain what doesn't work again here:
| RFC Note: This works today by removing the `-C emit-std-bundle=yes` and `-C rlib-version=v0` flags. | |
| RFC Note: This works today by removing the `-C emit-std-bundle=yes` and `-C rlib-version=v0` flags, | |
| but it would fail if there was a diamond dependency. |
There was a problem hiding this comment.
This technique actually works today even with diamond dependencies (only the standard library builds as a staticlib, the crate builds as an rlib). This note was meant to highlight that the only implementation changes required by this RFC are compiler flags/versions for a backwards compatibility strategy.
I can expand this example to include a diamond dependency to illustrate this point?
There was a problem hiding this comment.
There's already a diamond dependency example in the RFC, so I think it's enough to just say it works here too. (And that's an important piece of info!)
| scheme for symbols. | ||
|
|
||
| *Note (non-normative):* Symbols relating to global allocation and panic handling | ||
| must not be defined in the .rlib unless the crate itself defines those symbols. |
There was a problem hiding this comment.
Does this limitation apply to all lang items?
| system linker. | ||
| * Exactly one of the core or std standard library bundles is supplied to the system | ||
| linker. This standard library bundle must have been built in a *compatible manner* | ||
| with all rlibs to be linked. |
There was a problem hiding this comment.
I think there's a subtle requirement here around lang items, which is roughly:
| with all rlibs to be linked. | |
| with all rlibs to be linked. | |
| * All used [*language items*](https://rustc-dev-guide.rust-lang.org/lang-items.html) are supplied by exactly one rlib. Ordinarily, the standard library supplies these language items. But in `no_std` builds, some may be supplied by other rlibs. |
There was a problem hiding this comment.
I think this is a sensible wording, and this probably also sets the scene for tweaking this wording for a similar requirement for EIIs(same requirement?). I'll add this if no one objects for a couple of days
| - **The CI-tested configuration.** Which compiler flags, panic strategies, | ||
| and platforms are in-scope for covering under tests. | ||
| - **Testing scope.** What should the tests check for. I.e. link test | ||
| of a diamond graph, symbol-table checks on emitted `rlib`s?, etc. |
There was a problem hiding this comment.
It would be useful to test a recursive cross-language dependency:
- a Rust crate that requires symbol A from C++ and provides symbol B
- a C++ library that requires symbol B and provides A
This is currently possible but tricky to get right with some system linkers.
There was a problem hiding this comment.
I agree it would be good to test at least one level of language weaving. At least the use case I have in mind would likely do this. Do you specifically want two libraries which depend on each other? In my experience that type of scenario is fairly rare/problematic (maybe I'm out of date there?).
I think I would pick to have a cross-language dependency tree more like:
A (C++ Executable)
|
v
B (Rust Library)
| \
v \
C (C++ Library) \
| \
+---------------> D (Rust Library)
There was a problem hiding this comment.
I think both are good tests, and they increase test coverage in slightly different ways.
I agree recursion is rare, and we might not support it initially. But either way it would be good to know its status, and make sure it doesn't regress. Recursion might also be an opportunity to improve error messages or documentation more generally.
I've seen new interop users run into it in small projects when just starting out, too.
Co-authored-by: teor <teor@riseup.net>
This RFC partially stabilises a version 0 of the
rlibformat, and defines a 'standard library bundle' mechanism with the aim to make this a supported workflow:Getting there requires a couple of practical steps, which would come if this RFC is accepted - or could happen in parallel if desirable:
rustcdocuments and supports a "build crate asrlib, link with a non-rustclinker" model as a first-class workflow, with clearly stated constraints that keep long-term support feasible.x86_64-unknown-linux-gnu.I decided to pick this up after some conversations with folks where I work, and others within the Rust Community. My understanding is that this technique is already used today by a few large companies who are also active in the Rust community. Standardising this feels like a positive step forward, which makes it easier for more projects to adopt Rust in existing C++ codebases.
It's my first RFC & I probably didn't get everything right but hopefully I'm close enough. Happy to discuss here/on Zuilip/zoom/etc as needed to keep this moving.
Credit note: Although I am submitting this RFC, and stand by every word in it, a large majority of its content comes directly from @pcwalton's pre-RFC here: https://internals.rust-lang.org/t/pre-rfc-stabilize-a-version-of-the-rlib-format/17558 which was attached to rust-lang/rust#73632
Rendered