8
8
9
9
Enable the compiler to select whether a target dynamically or statically links
10
10
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
13
24
14
25
# Motivation
15
26
[ motivation ] : #motivation
@@ -22,67 +33,74 @@ however, where these decisions are not suitable. For example binaries on Alpine
22
33
Linux want to link dynamically to musl and redistributable binaries on Windows
23
34
are best done by linking statically to MSVCRT.
24
35
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.
39
41
40
42
# Detailed design
41
43
[ design ] : #detailed-design
42
44
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.
55
74
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.
65
78
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:
68
80
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.
71
93
72
- ```
73
- rustc --cfg 'crt_link = "static"' foo.rs
74
- ```
94
+ ### Specifying dynamic/static C runtime linkage
75
95
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 `
79
97
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.
84
102
85
- ### Forwarding ` #[ cfg] ` to build scripts
103
+ ### Lowering ` cfg ` values to Cargo build script environment variables
86
104
87
105
The first feature proposed is enabling Cargo to forward ` #[cfg] ` directives from
88
106
the compiler into build scripts. Currently the compiler supports ` --print cfg `
@@ -125,17 +143,40 @@ export CARGO_CFG_DEBUG_ASSERTIONS
125
143
```
126
144
127
145
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.
132
168
133
169
This feature ends up having the added benefit of informing build scripts about
134
170
selected CPU features as well. For example once the ` target_feature ` ` #[cfg] `
135
171
is stabilized build scripts will know whether SSE/AVX/etc are enabled features
136
172
for the C code they might be compiling.
137
173
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
139
180
140
181
The final feature that will be added to the compiler is the ability to "lazily"
141
182
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:
172
213
``` rust
173
214
cfg_if! {
174
215
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 " ) ))]
177
218
extern {}
178
219
} 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" ))]
181
222
extern {}
182
223
} else {
183
224
// ...
@@ -191,23 +232,18 @@ CRT is linked dynamically, however, then the library named `c` will be linked
191
232
dynamically. Similarly for MSVC, a static CRT implies linking to ` libcmt ` and a
192
233
dynamic CRT implies linking to ` msvcrt ` (as we do today).
193
234
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
-
199
235
Finally, an example of compiling for MSVC linking statically to the C runtime
200
236
would look like:
201
237
202
238
```
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
204
240
```
205
241
206
242
and similarly, compiling for musl but linking dynamically to the C runtime would
207
243
look like:
208
244
209
245
```
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
211
247
```
212
248
213
249
### Future work
@@ -218,16 +254,17 @@ that it's somewhat cumbersome to select the non-default linkage of the CRT.
218
254
Similarly, however, it's cumbersome to select target CPU features which are not
219
255
the default, and these two situations are very similar. Eventually it's intended
220
256
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.
224
258
225
259
Furthermore, it would have arguably been a "more correct" choice for Rust to by
226
260
default statically link to the CRT on MSVC rather than dynamically. While this
227
261
would be a breaking change today due to how C components are compiled, if this
228
262
RFC is implemented it should not be a breaking change to switch the defaults in
229
263
the future.
230
264
265
+ TODO: discuss how this could with std-aware cargo to apply dllimport/export correctly
266
+ to the standard library's code-generation.
267
+
231
268
# Drawbacks
232
269
[ drawbacks ] : #drawbacks
233
270
@@ -265,4 +302,10 @@ the future.
265
302
# Unresolved questions
266
303
[ unresolved ] : #unresolved-questions
267
304
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