Skip to content

Commit 5864dd3

Browse files
authored
Merge pull request #5 from brson/crt-link
Revising crt-link
2 parents fa8d2f6 + e9285f9 commit 5864dd3

File tree

1 file changed

+115
-72
lines changed

1 file changed

+115
-72
lines changed

text/0000-crt-link.md

Lines changed: 115 additions & 72 deletions
Original file line numberDiff line numberDiff line change
@@ -8,8 +8,19 @@
88

99
Enable the compiler to select whether a target dynamically or statically links
1010
to a platform's standard C runtime through the introduction of three orthogonal
11-
and otherwise general purpose features, one of which would likely never become
12-
stable.
11+
and otherwise general purpose features, one of which will likely never become
12+
stable and can be considered an implementation detail of std. These features
13+
require rustc to have no intrinsic knowledge of the existence of C runtimes.
14+
15+
The end result is that rustc will be able to reuse its existing standard library
16+
binaries for the MSVC and musl targets for building code that links either
17+
statically or dynamically to libc.
18+
19+
The design herein additionally paves the way for improved support for
20+
dllimport/dllexport and cpu-specific features, particularly when combined with a
21+
[std-aware cargo].
22+
23+
[std-aware cargo]: https://github.com/rust-lang/rfcs/pull/1133
1324

1425
# Motivation
1526
[motivation]: #motivation
@@ -22,67 +33,74 @@ however, where these decisions are not suitable. For example binaries on Alpine
2233
Linux want to link dynamically to musl and redistributable binaries on Windows
2334
are best done by linking statically to MSVCRT.
2435

25-
The actual underlying code essentially never needs to change depending on how
26-
the C runtime is being linked, just the mechanics of how it's actually all
27-
linked together. As a result it's a common desire to take the target libraries
28-
"off the shelf" and change how the C runtime is linked in as well.
29-
30-
The purpose of this RFC is to provide a cross-platform solution spanning both
31-
Cargo and the compiler which allows configuration of how the C runtime is
32-
linked. The idea is that the standard MSVC and musl targets can be used as they
33-
are today with an extra compiler flag to change how the C runtime is linked by
34-
default.
35-
36-
This RFC does *not* propose unifying how the C runtime is linked across
37-
platforms (e.g. always dynamically or always statically) but instead leaves that
38-
decision to each target.
36+
Today rustc has no mechanism for accomplishing this besides defining an entirely
37+
new target specification and distributing a build of the standard library for
38+
it. Because target specifications must be described by a target triple, and
39+
target triples have preexisting conventions into which such a scheme does not
40+
fit, we have resisted doing so.
3941

4042
# Detailed design
4143
[design]: #detailed-design
4244

43-
This RFC proposed introducing three separate features to the compiler and Cargo.
44-
When combined together they will enable the compiler to change whether the C
45-
standard library is linked dynamically or statically, but in isolation each
46-
should be useful in its own right.
47-
48-
### A `crt_link` cfg directive
49-
50-
The compiler will first define a new `crt_link` `#[cfg]` directive. This
51-
directive will behave similarly to directives like `target_os` where they're
52-
defined by the compiler for all targets. The compiler will set this value to
53-
either `"dynamic"` or `"static"` depending on how the C runtime is requested to
54-
being linked.
45+
This RFC introduces three separate features to the compiler and Cargo. When
46+
combined together they will enable the compiler to change whether the C standard
47+
library is linked dynamically or statically. In isolation each feature is a
48+
natural extension of existing features, should be useful on their own.
49+
50+
A key insight is that, for practical purposes, the object code _for the standard
51+
library_ does not need to change based on how the C runtime is being linked;
52+
though it is true that on Windows, it is _generally_ important to properly
53+
manage the use of dllimport/dllexport attributes based on the linkage type, and
54+
C code does need to be compiled with specific options based on the linkage type.
55+
So it is technically possible to produce Rust executables and dynamic libraries
56+
that either link to libc statically or dynamically from a single std binary by
57+
correctly manipulating the arguments to the linker.
58+
59+
A second insight is that there are multiple existing, unserved use cases for
60+
configuring features of the hardware architecture, underlying platform, or
61+
runtime [1], which require the entire 'world', possibly including std, to be
62+
compiled a certain way. C runtime linkage is another example of this
63+
requirement.
64+
65+
[1]: https://internals.rust-lang.org/t/pre-rfc-a-vision-for-platform-architecture-configuration-specific-apis/3502
66+
67+
From these observations we can design a cross-platform solution spanning both
68+
Cargo and the compiler by which Rust programs may link to either a dynamic or
69+
static C library, using only a single std binary. As future work it discusses
70+
how the proposed scheme scheme can be extended to rebuild std specifically for a
71+
particular C-linkage scenario, which may have minor advantages on Windows due to
72+
issues around dllimport and dllexport; and how this scheme naturally extends
73+
to recompiling std in the presence of modified CPU features.
5574

56-
For example, crates can then indicate:
57-
58-
```rust
59-
#[cfg_attr(crt_link = "static", link(name = "c", kind = "static"))]
60-
#[cfg_attr(crt_link = "dynamic", link(name = "c"))]
61-
extern {
62-
// ...
63-
}
64-
```
75+
This RFC does *not* propose unifying how the C runtime is linked across
76+
platforms (e.g. always dynamically or always statically) but instead leaves that
77+
decision to each target, and to future work.
6578

66-
This will notably be used in the `libc` crate where the linkage to the C
67-
runtime is defined.
79+
In summary the new mechanics are:
6880

69-
Finally, the compiler will *also* allow defining this attribute from the
70-
command line. For example:
81+
- Specifying C runtime linkage via `-C target-feature=+crt-static` or `-C
82+
target-feature=-crt-static`. This extends `-C target-feature` to mean not just
83+
"CPU feature" ala LLVM, but "feature of the Rust target". Several existing
84+
properties of this flag, the ability to add, with `+`, _or remove_, with `-`,
85+
the feature, as well as the automatic lowering to `cfg` values, are crucial to
86+
later aspects of the design. This target feature will be added to targets via
87+
a small extension to the compiler's target specification.
88+
- Lowering `cfg` values to Cargo build script environment variables. TODO describe
89+
key points.
90+
- Lazy link attributes. TODO. This feature is only required by std's own copy of the
91+
libc crate, since std is distributed in binary form, and it may yet be a long
92+
time before Cargo itself can rebuild std.
7193

72-
```
73-
rustc --cfg 'crt_link = "static"' foo.rs
74-
```
94+
### Specifying dynamic/static C runtime linkage
7595

76-
This will override the compiler's default definition of `crt_link` and use this
77-
one instead. Again, though, the only valid values for this directive are
78-
`"static"` and `"dynamic"`.
96+
`-C target-feature=crt-static`
7997

80-
In isolation, however, this directive is not too useful, It would still require
81-
rebuilding the `libc` crate (which the standard library links to) if the
82-
linkage to the C runtime needs to change. This is where the two other features
83-
this RFC proposes come into play though!
98+
TODO An extension to target specifications that allows custom target-features to be
99+
defined, as well as to indicate whether that feature is on by default. Most
100+
existing targets will define `crt-static`; the existing "musl" targets will
101+
enable `crt-static` by default.
84102

85-
### Forwarding `#[cfg]` to build scripts
103+
### Lowering `cfg` values to Cargo build script environment variables
86104

87105
The first feature proposed is enabling Cargo to forward `#[cfg]` directives from
88106
the compiler into build scripts. Currently the compiler supports `--print cfg`
@@ -125,17 +143,40 @@ export CARGO_CFG_DEBUG_ASSERTIONS
125143
```
126144

127145
As mentioned in the previous section, the linkage of the C standard library
128-
will be a `#[cfg]` directive defined by the compiler, and through this method
129-
build scripts will be able to learn how the C standard library is being linked.
130-
This is crucially important for the MSVC target where code needs to be compiled
131-
differently depending on how the C library is linked.
146+
will be specified as a target feature, which is lowered to a `cfg` value.
147+
One important complication here is that `cfg` values in Rust may be defined
148+
multiple times, and this is the case with target features. When a
149+
`cfg` value is defined multiple times, Cargo will create a single environment
150+
variable with a comma-seperated list of values.
151+
152+
So for a target with the following features enabled
153+
154+
```
155+
target_feature="sse"
156+
target_feature="crt-static"
157+
```
158+
159+
Cargo would convert it to the following environment variable:
160+
161+
```
162+
export CARGO_CFG_TARGET_FEATURE=sse,crt-static
163+
```
164+
165+
Through this method build scripts will be able to learn how the C standard
166+
library is being linked. This is crucially important for the MSVC target where
167+
code needs to be compiled differently depending on how the C library is linked.
132168

133169
This feature ends up having the added benefit of informing build scripts about
134170
selected CPU features as well. For example once the `target_feature` `#[cfg]`
135171
is stabilized build scripts will know whether SSE/AVX/etc are enabled features
136172
for the C code they might be compiling.
137173

138-
### "Lazy Linking"
174+
After this change, the gcc-rs crate will be modified to check for the
175+
`CARGO_CFG_TARGET_FEATURE` directive, and parse it into a list of enabled
176+
features. If the `crt-static` feature is not enabled it will compile C code with
177+
`/MD`. Otherwise if the value is `static` it will compile code with `/MT`.
178+
179+
### Lazy link attributes
139180

140181
The final feature that will be added to the compiler is the ability to "lazily"
141182
link a native library depending on values of `#[cfg]` at compile time of
@@ -172,12 +213,12 @@ First, the `libc` crate will be modified to contain blocks along the lines of:
172213
```rust
173214
cfg_if! {
174215
if #[cfg(target_env = "musl")] {
175-
#[link(name = "c", cfg(crt_link = "static"), kind = "static")]
176-
#[link(name = "c", cfg(crt_link = "dynamic"))]
216+
#[link(name = "c", cfg(target_feature = "crt-static"), kind = "static")]
217+
#[link(name = "c", cfg(not(target_feature = "crt-static")))]
177218
extern {}
178219
} else if #[cfg(target_env = "msvc")] {
179-
#[link(name = "msvcrt", cfg(crt_link = "dynamic"))]
180-
#[link(name = "libcmt", cfg(crt_link = "static"))]
220+
#[link(name = "msvcrt", cfg(not(target_feature = "crt-static")))]
221+
#[link(name = "libcmt", cfg(target_feature = "crt-static"))]
181222
extern {}
182223
} else {
183224
// ...
@@ -191,23 +232,18 @@ CRT is linked dynamically, however, then the library named `c` will be linked
191232
dynamically. Similarly for MSVC, a static CRT implies linking to `libcmt` and a
192233
dynamic CRT implies linking to `msvcrt` (as we do today).
193234

194-
After this change, the gcc-rs crate will be modified to check for the
195-
`CARGO_CFG_CRT_LINK` directive. If it is not present or value is `dynamic`, then
196-
it will compile C code with `/MD`. Otherwise if the value is `static` it will
197-
compile code with `/MT`.
198-
199235
Finally, an example of compiling for MSVC linking statically to the C runtime
200236
would look like:
201237

202238
```
203-
RUSTFLAGS='--cfg crt_link="static"' cargo build --target x86_64-pc-windows-msvc
239+
RUSTFLAGS='-C target-feature=+crt-static' cargo build --target x86_64-pc-windows-msvc
204240
```
205241

206242
and similarly, compiling for musl but linking dynamically to the C runtime would
207243
look like:
208244

209245
```
210-
RUSTFLAGS='--cfg crt_link="dynamic"' cargo build --target x86_64-unknown-linux-musl
246+
RUSTFLAGS='-C target-feature=-crt-static' cargo build --target x86_64-unknown-linux-musl
211247
```
212248

213249
### Future work
@@ -218,16 +254,17 @@ that it's somewhat cumbersome to select the non-default linkage of the CRT.
218254
Similarly, however, it's cumbersome to select target CPU features which are not
219255
the default, and these two situations are very similar. Eventually it's intended
220256
that there's an ergonomic method for informing the compiler and Cargo of all
221-
"compilation codegen options" over the usage of `RUSTFLAGS` today. It's assume
222-
that configuration of `crt_link` will be included in this ergonomic
223-
configuration as well.
257+
"compilation codegen options" over the usage of `RUSTFLAGS` today.
224258

225259
Furthermore, it would have arguably been a "more correct" choice for Rust to by
226260
default statically link to the CRT on MSVC rather than dynamically. While this
227261
would be a breaking change today due to how C components are compiled, if this
228262
RFC is implemented it should not be a breaking change to switch the defaults in
229263
the future.
230264

265+
TODO: discuss how this could with std-aware cargo to apply dllimport/export correctly
266+
to the standard library's code-generation.
267+
231268
# Drawbacks
232269
[drawbacks]: #drawbacks
233270

@@ -265,4 +302,10 @@ the future.
265302
# Unresolved questions
266303
[unresolved]: #unresolved-questions
267304

268-
None, yet.
305+
* What happens during the `cfg` to environment variable conversion for values
306+
that contain commas? It's an unusual corner case, and build scripts should not
307+
depend on such values, but it needs to be handled sanely.
308+
309+
* Is it really true that lazy linking is only needed by std's libc? What about
310+
in a world where we distribute more precompiled binaries than just std?
311+

0 commit comments

Comments
 (0)