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

Add static linking to rust #10528

Closed
wants to merge 5 commits into from

Conversation

alexcrichton
Copy link
Member

In this series of commits, I've implemented static linking for rust. The scheme I implemented was the same as my mailing list post.

The commits have more details to the nitty gritty of what went on. I've rebased this on top of my native mutex pull request (#10479), but I imagine that it will land before this lands, I just wanted to pre-emptively get all the rebase conflicts out of the way (becuase this is reorganizing building librustrt as well).

Some contentious points I want to make sure are all good:

  • I've added more "compiler chooses a default" behavior than I would like, I want to make sure that this is all very clearly outlined in the code, and if not I would like to remove behavior or make it clearer.
  • I want to make sure that the new "fancy suite" tests are ok (using make/python instead of another rust crate)

If we do indeed pursue this, I would be more than willing to write up a document describing how linking in rust works. I believe that this behavior should be very understandable, and the compiler should never hinder someone just because linking is a little fuzzy.

@huonw
Copy link
Member

huonw commented Nov 17, 2013

This doesn't appear to update the docs about the changes to crate_type, and the replacement for #[link_name]?

@alexcrichton
Copy link
Member Author

There was actually no change to the existing crate_type functionality, it was simply expanded to allow more control over the output. You're right about link_name in the docs though.

In general, there appears to be very little documentation about rust's linkage model, and I would like to change that. I'm not sure where the best place for this documentation is. I would imagine it would be doc/tutorial-linkage.md because it doesn't really belong in the rust tutorial (except for a mention) and it doesn't really belong in the language manual (it's predominately not a language thing.

That being said, I'd like to verify that this is the direction that we wish to pursue before writing up that document. I'm perfectly ok with blocking this for me to write that document, however.

@huonw
Copy link
Member

huonw commented Nov 17, 2013

There's tutorial-ffi, which would presumably cover the new #[link] one. (But yeah, no point writing the docs until we know that this is the right path.)

@alexcrichton
Copy link
Member Author

I have hit a snag in getting make check to pass. We have the following conundrum:

  • libfoo is only available in dynamic form, and hence links to libstd dynamically
  • Rust prefers static linking, hence links statically to libstd
  • When I build a program linking to libfoo, this generates two versions of the runtime

This is causing programs to crash because one runtime is initialized and the other isn't. This is a more general problem, however, as it doesn't only apply to libstd (anything using conditions would be affected).

This isn't a rust-specific problem, and in my experimentation with the linker I was unable to produce a binary which had the expected behavior. Additionally, this is a subset of the larger problem of guaranteeing that there is only one copy of a library in a final product. The compiler currently has no means to generate a dynamic library or executable with the guarantee that each upstream library will appear exactly once. We currently have this invariant because everything is linked dynamically, but this is not the case with static linking.

With this in mind, I do not believe that favoring static linking is the right thing to do at this time. I'm going to change this patch to require dynamic linking when creating a dynamic library, and add a -Z prefer-static for preferring static versions of a library when creating an executable. I will also for now print a warning stating the possible hazards of static linking an executable.

I believe that we can work around this issue, but it requires more infrastructure than i believe that we possess today. I have not given this a large amount of thought, but all I can come up with is that this requires some complicated analysis which likely isn't worth it (this is "just linking" after all).

I'm a little up in the air on whether to close this pull request because of this issue or not. I personally want to see this feature working in the compiler, but I'm uncomfortable with the amount of thought which I have put into this (I do not believe it is sufficiently enough). I will discuss this more and see what comes of it.

@lucab
Copy link
Contributor

lucab commented Nov 19, 2013

I may have got your last comment wrong, but it looks like what you want is actually to do a single linking pass with mixed static/dynamic objs (libfoo.so, libstd.a), have all occurences of libstd symbols with global binding, and mark one of the copy weak and the other strong (choosing whether you prefer the one coming from the static libstd or the dynamic one).
IMHO this is at least how it's done with ELF, not sure about other formats.

@alexcrichton
Copy link
Member Author

I believe that we have reasons to not mix static/dynamic libraries right now. I can't think of a fantastic way to guarantee that you only ever have one copy of a library. As implemented, all result artifacts are required to be all-static or all-dynamic for this reason.

What do you mean by a global binding? I'm not sure I've heard of how that works.

@lucab
Copy link
Contributor

lucab commented Nov 19, 2013

I agree with your assessment.
Speaking of binding scopes, it is basically fine-tuning of symbol visibility in ELF objects. See all the possible value of ST_BIND and your elf.h. I never played such tricks with the linker before, though.

@alexcrichton
Copy link
Member Author

I have updated this to no longer use the -r option passed to ld. Turns out that it was mangling our exception handling information, meaning that no destructors were getting run (tests didn't pick this up because they still prefer dynamic linking).

I have added an appropriate test, and we no longer mangle object files at all after generation. They're simply shipped through the system in archives until the final destination link step.

@emberian
Copy link
Member

👍, but another set of eyeballs would be nice.

@pcwalton
Copy link
Contributor

@alexcrichton Do you think this is OK to review and merge?

@alexcrichton
Copy link
Member Author

I believe it is, yes.


fn run_ar(sess: Session, args: &str, cwd: Option<&Path>,
paths: &[&Path]) -> ProcessOutput {
let ar = sess.opts.ar.clone().unwrap_or(~"ar");
Copy link
Contributor

Choose a reason for hiding this comment

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

nit: this will be slightly faster if you don't unconditionally allocate ~"ar"

@pcwalton
Copy link
Contributor

r=me modulo comments.

This commit implements the support necessary for generating both intermediate
and result static rust libraries. This is an implementation of my thoughts in
https://mail.mozilla.org/pipermail/rust-dev/2013-November/006686.html.

When compiling a library, we still retain the "lib" option, although now there
are "rlib", "staticlib", and "dylib" as options for crate_type (and these are
stackable). The idea of "lib" is to generate the "compiler default" instead of
having too choose (although all are interchangeable). For now I have left the
"complier default" to be a dynamic library for size reasons.

Of the rust libraries, lib{std,extra,rustuv} will bootstrap with an
rlib/dylib pair, but lib{rustc,syntax,rustdoc,rustpkg} will only be built as a
dynamic object. I chose this for size reasons, but also because you're probably
not going to be embedding the rustc compiler anywhere any time soon.

Other than the options outlined above, there are a few defaults/preferences that
are now opinionated in the compiler:

* If both a .dylib and .rlib are found for a rust library, the compiler will
  prefer the .rlib variant. This is overridable via the -Z prefer-dynamic option
* If generating a "lib", the compiler will generate a dynamic library. This is
  overridable by explicitly saying what flavor you'd like (rlib, staticlib,
  dylib).
* If no options are passed to the command line, and no crate_type is found in
  the destination crate, then an executable is generated

With this change, you can successfully build a rust program with 0 dynamic
dependencies on rust libraries. There is still a dynamic dependency on
librustrt, but I plan on removing that in a subsequent commit.

This change includes no tests just yet. Our current testing
infrastructure/harnesses aren't very amenable to doing flavorful things with
linking, so I'm planning on adding a new mode of testing which I believe belongs
as a separate commit.

Closes rust-lang#552
This infrastructure is meant to support runnings tests that involve various
interesting interdependencies about the types of crates being linked or possibly
interacting with C libraries. The goal of these make tests is to not restrict
them to a particular test runner, but allow each test to run its own tests.

To this end, there is a new src/test/run-make directory which has sub-folders of
tests. Each test requires a `Makefile`, and running the tests constitues simply
running `make` inside the directory. The new target is `check-stageN-rmake`.

These tests will have the destination directory (as TMPDIR) and the local rust
compiler (as RUSTC) passed along to them. There is also some helpful
cross-platform utilities included in src/test/run-make/tools.mk to aid with
compiling C programs and running them.

The impetus for adding this new test suite is to allow various interesting forms
of testing rust linkage. All of the tests initially added are various flavors of
compiling Rust and C with one another as well as just making sure that rust
linkage works in general.

Closes rust-lang#10434
In rust-lang#10422, I didn't actually test to make sure that the '-Z gen-crate-map'
option was usable before I implemented it. The crate map was indeed generated
when '-Z gen-crate-map' was specified, but the I/O factory slot was empty
because of an extra check in trans about filling in that location.

This commit both fixes that location, and checks in a "fancy test" which does
lots of fun stuff. The test will use the rustc library to compile a rust crate,
and then compile a C program to link against that crate and run the C program.
To my knowledge this is the first test of its kind, so it's a little ad-hoc, but
it seems to get the job done. We could perhaps generalize running tests like
this, but for now I think it's fine to have this sort of functionality tucked
away in a test.
bors added a commit that referenced this pull request Nov 30, 2013
In this series of commits, I've implemented static linking for rust. The scheme I implemented was the same as my [mailing list post](https://mail.mozilla.org/pipermail/rust-dev/2013-November/006686.html).

The commits have more details to the nitty gritty of what went on. I've rebased this on top of my native mutex pull request (#10479), but I imagine that it will land before this lands, I just wanted to pre-emptively get all the rebase conflicts out of the way (becuase this is reorganizing building librustrt as well).

Some contentious points I want to make sure are all good:

* I've added more "compiler chooses a default" behavior than I would like, I want to make sure that this is all very clearly outlined in the code, and if not I would like to remove behavior or make it clearer.
* I want to make sure that the new "fancy suite" tests are ok (using make/python instead of another rust crate)

If we do indeed pursue this, I would be more than willing to write up a document describing how linking in rust works. I believe that this behavior should be very understandable, and the compiler should never hinder someone just because linking is a little fuzzy.
@bors bors closed this Nov 30, 2013
@alexcrichton alexcrichton deleted the static-linking-v2 branch December 1, 2013 00:05
bors added a commit that referenced this pull request Dec 3, 2013
This registers new snapshots after the landing of #10528, and then goes on to tweak the build process to build a monolithic `rustc` binary for use in future snapshots. This mainly involved dropping the dynamic dependency on `librustllvm`, so that's now built as a static library (with a dynamically generated rust file listing LLVM dependencies).

This currently doesn't actually make the snapshot any smaller (24MB => 23MB), but I noticed that the executable has 11MB of metadata so once progress is made on #10740 we should have a much smaller snapshot.

There's not really a super-compelling reason to distribute just a binary because we have all the infrastructure for dealing with a directory structure, but to me it seems "more correct" that a snapshot compiler is just a `rustc` binary.
@rofl0r
Copy link

rofl0r commented Jan 11, 2014

@alexcrichton do you have some numbers for statically linked binaries (filesize) ?
maybe for a hello world, and some more involved programs.

@emberian
Copy link
Member

@rofl0r: hello world is 2.5M. With -Z lto it's 1.7M.

flip1995 pushed a commit to flip1995/rust that referenced this pull request Apr 6, 2023
Clear with drain

changelog: [`clear_with_drain`]: Add new lint

Fixes rust-lang#9339
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

7 participants