Skip to content
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

Enable customizing the linkage of a platform's C runtime #1721

Merged
merged 7 commits into from
Oct 25, 2016

Conversation

alexcrichton
Copy link
Member

@alexcrichton alexcrichton commented Aug 19, 2016

Enable the compiler to select whether a target dynamically or statically links
to a platform's standard C runtime through the introduction of three orthogonal
and otherwise general purpose features, one of which will likely never become
stable and can be considered an implementation detail of std. These features do
not require the compiler or language to have intrinsic knowledge of the
existence of C runtimes.

The end result is that rustc will be able to reuse its existing standard library binaries for the MSVC and musl targets to build code that links either statically or dynamically to libc.

The design herein additionally paves the way for improved support for dllimport/dllexport, and cpu-specific features, particularly when combined with a std-aware cargo.

Rendered

alexcrichton and others added 7 commits August 18, 2016 15:29
Enable the compiler to select whether a target dynamically or statically links
to a platform's standard C runtime through the introduction of three orthogonal
and otherwise general purpose features, one of which would likely never become
stable.
@brson
Copy link
Contributor

brson commented Aug 19, 2016

cc @vadimcn @retep998
cc @Ericson2314 this has some small implications for std-aware cargo

@brson
Copy link
Contributor

brson commented Aug 19, 2016

@aturon this is strongly related to your irlo post about arch/plattform-specific configuration.

@brson
Copy link
Contributor

brson commented Aug 19, 2016

cc @japaric @KennethAdamMiller @zhangyt26 a way forward on musl dynamic linking

@Diggsey
Copy link
Contributor

Diggsey commented Aug 19, 2016

Another alternative would be to separate libstd into two crates: one of which is just a stub which calls out to the actual CRT. Instead of recompiling the whole of libstd, you just link with a different stub.

@vadimcn
Copy link
Contributor

vadimcn commented Aug 19, 2016

How would this interact with RFC #1717?
If we have these annotations,

#[link(name = "msvcrt", kind="dylib", cfg(not(target_feature = "crt-static")))]
#[link(name = "libcmt", kind="static-nobundle", cfg(target_feature = "crt-static"))]

how do we decide whether to emit dllimport's on symbols? Using the default state of that target feature?

@alexcrichton
Copy link
Member Author

@vadimcn ah this was intended to clarify that:

When dllimport or dllexport needs to be applied, it will evaluate the final compilation unit's #[cfg] directives and see if upstream #[link] directives apply or not.

Which is to say that when dllexport/dllimport need to be appplied, the #[link] annotations are resolved with their cfg.

Note, though, that this is not intended to be a general purpose mechanism. It's the unstable feature of this RFC which is never intended to become stable. It does't affect liblibc at all as well because the extern block the #[link] annotations decorate has 0 bindings.

* Another possibility would be to start storing metadata in the "target name"
along the lines of `x86_64-pc-windows-msvc+static`. This is a pretty big
design space, though, which may not play well with Cargo and build scripts, so
for now it's preferred to avoid this rabbit hole of design if possible.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🐰

@retep998
Copy link
Member

Well this satisfies my requirements of getting libc to not depend on msvcrt.lib when I want libcmt.lib instead, as well as informing build scripts and downstream crates of how the CRT is being linked so they can change what they do accordingly. So this seems like it'll work.

However, it is worth paying attention to how this feature could be extended for other options in the future.

For example choosing which winapi family you are targeting, with mutually exclusive options such as WINAPI_FAMILY_DESKTOP_APP and WINAPI_FAMILY_PC_APP that affect which functions are available to you. In particular std would have to change which functions it relies on, which means it would have to be recompiled. This would also affect which system libraries are linked to.

Another option might be the minimum version of Windows that needs to be supported, which affects which functions can be directly linked to and adding fallback code paths for certain functionality. Libstd could handle this by always supporting the lowest version possible, but if it could be recompiled it could take advantage of higher minimum version requirements to strip out code branches for older versions.

So for a target with the following features enabled

```
target_feature="sse"
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Will Cargo set the CARGO_CFG_TARGET_FEATURE variable for CPU features that are implied by the
"llvm-target" field of a target specification? For example:

Will this

cargo build --target x86_64-unknown-linux-gnu

produce this

CARGO_CFG_TARGET_FEATURE="mmx,sse,sse2,."

?

And this

RUSTFLAGS="-mmx,-sse,-sse2" cargo build --target x86_64-unknown-linux-gnu

produce this

CARGO_CFG_TARGET_FEATURE=""

?

Because, otherwise, this scheme would leave the CARGO_CFG_TARGET_FEATURE empty for both cargo build cases and that's not an accurate representation of the target CPU features.

And if Cargo will, indeed, set these implicit CPU features based on the "llvm-target", how will that
be implemented? Will Cargo call ("shell out to") a new rustc --print llvm-target-features command
created for this purpose?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Will Cargo call ("shell out to") a new rustc --print llvm-target-features command
created for this purpose?

Oh, I just saw the output of rustc --print cfg and seems like it contains the target features I mentioned above, at least sse and sse2 appear for x86_64-unknown-linux-gnu, so I guess this is not really an issue -- Cargo can just use that command.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Will Cargo set the CARGO_CFG_TARGET_FEATURE variable for CPU features that are implied by the "llvm-target" field of a target specification?

Yes

And if Cargo will, indeed, set these implicit CPU features based on the "llvm-target", how will that be implemented?

This'll be done through --print cfg. Right now the target_feature cfg is unstable so this is only available on nightly, but for this RFC we'd want to stabilize that to have everything move forward at once. If you take a look at rustup run nightly rustc --print cfg, though, you'll see target_feature in there.

Cargo already runs the compiler to learn about various things like filenames, so this'd just be another thing it'd learn about during that process.

@Ericson2314
Copy link
Contributor

Ericson2314 commented Aug 19, 2016

script invocations for this target.

```
export CARGO_CFG_TARGET_OS=linux
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Will these also be set when one calls e.g. cargo rustc -- -C target-feature=+avx? That would make these variables available for the target crate but not for its dependencies which ... might be surprising. But I guess the same already happens with cargo rustc and cfg(target_feature)?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah I think cargo rustc is the unsafe escape hatch for those sorts of things. Anything after -- is passed verbatim to rustc right?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Will these also be set when one calls e.g. cargo rustc -- -C target-feature=+avx?

No, currently only RUSTFLAGS affects how Cargo runs rustc initially.

@vadimcn
Copy link
Contributor

vadimcn commented Aug 19, 2016

When dllimport or dllexport needs to be applied, it will evaluate the final compilation unit's #[cfg] directives and see if upstream #[link] directives apply or not.

Okay, so all upstream crates should also be compiled with the same target-feature flag?

@Ericson2314
Copy link
Contributor

I think #1711 ought to be a blocker for this. Once we start passing this stuff to build scripts we will have to be much more careful about stability..

@retep998
Copy link
Member

I'll have much more to say, but can't cfg_attr be used with link? :)

I believe the idea is that this new link cfg syntax is specifically for lazy cfg evaluation. It will be evaluated at the point of linking, so the upstream crate doesn't need to know what the target-feature flag is when it was compiled, rustc will store both of those attributes with their respective cfgs and evaluate it later when linking. cfg_attr would be evaluated immediately and so would be unusable for libstd as at the time of building libstd it doesn't know how you want the crt to be linked yet.

Okay, so all upstream crates should also be compiled with the same target-feature flag?

There's no way dllimport/dllexport can be applied lazily unless you forbid translation of any function that calls extern symbols from such a lazy cfg'd link block until the final compilation unit where linking will occur.

Actually, @alexcrichton , how does this interact with dylibs? You need to know which CRT you are linking every time you are linking, and when making a Rust dylib, you are still linking stuff so you need to know which CRT to use. If statically linking the CRT in a given dylib, that means it has its own copy of the CRT which is fundamentally unique from all other dylibs. What happens if code works with a CRT object in a Rust dylib which statically linked the CRT, but that function is then inlined or monomorphized into a different dylib?

@Ericson2314
Copy link
Contributor

CC @eternaleye, you're probably interested.

@vadimcn
Copy link
Contributor

vadimcn commented Aug 19, 2016

There's no way dllimport/dllexport can be applied lazily unless you forbid translation of any function that calls extern symbols from such a lazy cfg'd link block until the final compilation unit where linking will occur.

Exactly. But I think @alexcrichton meant that for exact dllimport targetting all upstream crates would be compiled with the same target-feature flag.
Of course, this doesn't help with crates that Rust ships in a binary form, so we'll still have to rely on linker to fix things up.

What happens if code works with a CRT object in a Rust dylib which statically linked the CRT, but that function is then inlined or monomorphized into a different dylib?

I believe the CRT symbols will be resolved to the other crate's CRT. Which isn't necessarily a problem if they are all stateless functions.

@retep998
Copy link
Member

I believe the CRT symbols will be resolved to the other crate's CRT. Which isn't necessarily a problem if they are all stateless functions.

Fortunately in the case of libstd it is all stateless functions. However anyone who uses CRT state in their own Rust crates has to be keenly aware that if someone uses the static CRT with their crate, it will completely screw up their code.


The default of `crt-static` will be different depending on the target. For
example `x86_64-unknown-linux-musl` will have it on by default, whereas
`arm-unknown-linux-musleabi` will have it turned off by default.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I there any particular reason why some musl targets will be statically linked by default and some not? If it's just historical then I think we should change it for consistency.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes as we've added them over time they've addressed different use cases. The plan is to not change any of them yet, as that would be a breaking change. Each target (event with the same C library) can choose whether it's static or dynamic by default.

If the crt-static option becomes more ergonomic and ubiquitous we can consider changing defaults in the future, but at this time the fact that it's a breaking change prevents us from doing so.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I there any particular reason why some musl targets will be statically linked by default and some not?

The mips(el)-musl targets, for instance, can't produce statically linked binaries because static linking depends on libunwind and libunwind doesn't support mips (yet).

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Right, that's why it would make sense to use dynamic linking by default so all musl targets can be consistent.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

To reiterate, we can't do that because it's a breaking change. There are many users relying on the fact that musl is a static target today. We can consider changing this all in the far future, but it is an explicitly stated non-goal of this RFC to attempt to perform any kind of unification of how the CRT is linked on various platforms.


```rust
cfg_if! {
if #[cfg(target_env = "musl")] {
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just to confirm: this shouldn't be limited to musl. glibc (and it's variants) have (some) support for static linking too.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes if support is added to get that working, we can encode that here. The specifics of that, however, are out of scope of this RFC. It's intended though that there's enough plumbing here that one could imagine:

RUSTFLAGS='-C target-feature=+crt-static' cargo build --target x86_64-unknown-linux-gnu

to one day work!

@retep998
Copy link
Member

retep998 commented Oct 6, 2016

I just had someone trying to get some Rust code to link against the debug CRT, so would this be easily extendable to that as well?

@alexcrichton
Copy link
Member Author

Yeah my guess is we could either infer that from -g or add a new -C target-feature for that

@retep998
Copy link
Member

retep998 commented Oct 6, 2016

I'd rather not infer it from -g because using debug info with the normal non-debug CRT is a really common and standard thing to do.

@vadimcn
Copy link
Contributor

vadimcn commented Oct 11, 2016

@rfcbot reviewed

1 similar comment
@michaelwoerister
Copy link
Member

@rfcbot reviewed

@michaelwoerister
Copy link
Member

Can we use something more explicit than cfg (e.g. lazy_cfg) for lazy linking that indicates that something special is going on here? Since the feature is going to stay unstable forever, it doesn't have to be as ergonomic as something more exposed to the end user.

@alexcrichton alexcrichton added the final-comment-period Will be merged/postponed/closed in ~10 calendar days unless new substational objections are raised. label Oct 18, 2016
@rfcbot
Copy link
Collaborator

rfcbot commented Oct 18, 2016

All relevant subteam members have reviewed. No concerns remain.

@alexcrichton
Copy link
Member Author

@michaelwoerister sure yeah, I don't mind basically picking anything for that in the time being.

@alexcrichton
Copy link
Member Author

🔔 This RFC is now entering its week-long final comment period for merging 🔔

@Ericson2314
Copy link
Contributor

Ericson2314 commented Oct 19, 2016

This isn't a concern, but to my knowledge nothing is blocking stdlib deps, and @japaric has done excellent work making std actually buildable by cargo in practice, so perhaps "Lazy link attributes" won't even need to be implemented?

I figure its best to keep them in the RFC, and then (r)evaluate as things are implemented.

@rfcbot
Copy link
Collaborator

rfcbot commented Oct 25, 2016

It has been one week since all blocks to the FCP were resolved.

@alexcrichton
Copy link
Member Author

Looks like no new concerns arose during FCP, so merging! Thanks again for the discussion everyone!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
A-cfg Conditional compilation related proposals & ideas A-linkage Proposals relating to the linking step. final-comment-period Will be merged/postponed/closed in ~10 calendar days unless new substational objections are raised. T-dev-tools Relevant to the development tools team, which will review and decide on the RFC.
Projects
None yet
Development

Successfully merging this pull request may close these issues.