-
Notifications
You must be signed in to change notification settings - Fork 12.7k
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
Using LTO while building as static library causes conflicts #44322
Comments
I don't think linking two rust static libraries into the same binary is actively supported today, see also @alexcrichton's #44283 (comment) in another issue. Still marking as a bug for the moment since it works without LTO. |
@CryZe can you provide a script or repository to reproduce this? In theory linking together two staticlibs with LTO should work, although there may be bugs preventing it from doing so. In the long run you probably don't want to do this, though, as each Rust library you link in will bring in a new copy of the standard library, which ideally everyone would share. |
Triage: Hey @CryZe , are you still seeing this? Alex asked for a reproduction a while ago, if we can't reproduce, then we can't fix this :) |
Alright I made a repository here: https://github.com/CryZe/multi-lib-lto-rust-bug-repro
|
I am also investigating this issue. I did a bunch of tests, and it looks like lto = true is the only way to avoid having the __rdl_alloc symbols defined without name mangling. lto = false or lto = "off" both get them defined and cause the conflict. While my primary concern is with iOS, I did notice that the multi-lib-lto-rust-bug-repro doesn't link on macOS because of a symbol conflict on _rust_eh_personality. This symbol specifically doesn't seem to be affected by the lto option :/ |
@CryZe did you get any further on this? We are desperate for a workaround to make it work on iOS, where using shared libraries is not an option :( |
I came up with a "solution" that is wrong on so many levels, but at least makes the @CryZe reproduction sample link:
This effectively builds the first library, extracts the object files, does a search & replace on "rust_eh_personality" to rename it to "rust_eh_personaliti", and rebuilds the static library from the object files. By forcing the symbol change on one of the two libraries, the symbol conflict is avoided. Now this is obviously a very ugly hack, and I don't know the impact of forcing a symbol name change on rust_eh_personality. Technically, does it need to keep this name, or could it be name mangled like the rest? |
After digging through some older tickets, I found #43415 and it mentioned a different result when building in debug vs release. I modified the reproduction sample to build in debug, and there is no linker issue. It does significantly change the layout of internal object files and where the symbols are (extracting the .a with 'ar' to see where symbols are). One thing I noticed is that the linker doesn't complain about duplicated symbols if they are identical, and inside an object file of the same name (and possibly identical in totality I'm guessing). When building in debug, rust_eh_personality is defined in the same object file for both lib1 and lib2: panic_unwind-bf192f19d9586a96.panic_unwind.67c9yyht-cgu.0.rcgu.o When built in release, rust_eh_personality is defined in lib1-93605d7bc2291864.lib1.1hx46jnd-cgu.0.rcgu.o and lib2-3edec0329c3e4919.lib2.64tkgp8c-cgu.0.rcgu.o respectively. We decompiled the symbols to see if they were identical, and they are in our case (empty function stubs) but the linker sees them as different. I'm pretty sure that if rust_eh_personality was compiled inside an object file of the same name like what happens when you build in debug, the linker problem would go away. This would still mean that in order to get things to build and link correctly, one should use exactly the same version of Rust to avoid issues, but it would still be a much better option that being unable to link multiple static libraries together. |
Nevermind about building as debug as a way to get it working, as it fails the same way as in release mode when using panic="abort":
|
@alexcrichton @TimNN any idea what affects the compilation of the rust_eh_personality symbol specifically? There is this: https://github.com/rust-lang/rust/blob/master/src/librustc_codegen_ssa/back/symbol_export.rs#L118 I'm trying to figure out if there is a way to get this symbol definition built as part of a separate object file, like what happens when it gets built as part of panic_unwind.o (+ the name mangling). I am pretty sure this would get rid of the duplicated symbol issue once and for all. It may seem like a hack, but it would actually work quite well. The recommendation would be to build with the same version of Rust to avoid potential issues with symbols that have slight differences, but this is acceptable in my mind. It is very similar to building static libraries against a compatible MSVC static runtime on Windows, except we're talking about the Rust core libraries here. This type of issue needs to be fixed for cases where shared libraries are not a suitable option, like iOS. It is only going to become more common as more C libraries get rewritten to use Rust under the hood, causing conflicts when two of such libraries get linked together. |
I also experience this bug when linking against two Rust staticlibs (i.e. librsvg and rav1e) which was built with "fat" LTO enabled. I could workaround this with: LDFLAGS="-Wl,--allow-multiple-definition" But it's something I'd rather avoid. fwiw, the above mentioned link is now 404. It was probably linking to:
|
FWIW, I had hoped that PR #92845 would fix this, but it seems that this issue is still reproducible with the latest nightly. $ git clone https://github.com/CryZe/multi-lib-lto-rust-bug-repro.git
$ cd multi-lib-lto-rust-bug-repro/
$ make
gcc main.c -Llib1/target/release/ -Llib2/target/release/ -llib1 -llib2 -lpthread -ldl
lib2/target/release//liblib2.a(lib2-f2e7454a5f282da7.lib2.2398f8e6-cgu.1.rcgu.o): In function `rust_eh_personality':
/rustc/bc4b39c271bbd36736cbf1c0a1ac23d5df38d365/library/std/src/personality/gcc.rs:244: multiple definition of `rust_eh_personality'
lib1/target/release//liblib1.a(lib1-bfa64102e7d38c18.lib1.c0e19170-cgu.1.rcgu.o):/rustc/bc4b39c271bbd36736cbf1c0a1ac23d5df38d365/library/std/src/personality/gcc.rs:244: first defined here
lib2/target/release//liblib2.a(lib2-f2e7454a5f282da7.lib2.2398f8e6-cgu.1.rcgu.o):(.init_array.00099+0x0): multiple definition of `std::sys::unix::args::imp::ARGV_INIT_ARRAY'
lib1/target/release//liblib1.a(lib1-bfa64102e7d38c18.lib1.c0e19170-cgu.1.rcgu.o):(.init_array.00099+0x0): first defined here |
I was just going to report the same thing after running into it and making a testcase. Funny enough, my testcase is almost the same as @CryZe's. This definitely still happens with latest stable and nightly as of today. Depending on what the Rust staticlibs are doing, there are also a couple of other symbols that are conflicting:
This is a bit of a problem for GStreamer now that we started shipping plugins that are written in Rust. Both shared ( The Using Alternatively, disabling LTO for the |
Results are the same: upstream: SELECT hex(BLAKE3('bar')): F2E897EED7D206CD855D441598FA521ABC75AA96953E97C030C9612C30C1293D llvm: SELECT hex(BLAKE3('bar')): F2E897EED7D206CD855D441598FA521ABC75AA96953E97C030C9612C30C1293D Query for benchmark: SELECT ignore(BLAKE3(materialize('Lorem ipsum dolor sit amet, consectetur adipiscing elit'))) FROM numbers(1000000000) FORMAT `Null` upstream : Elapsed: 2.494 sec. Processed 31.13 million rows, 249.08 MB (12.48 million rows/s., 99.86 MB/s.) upstream + rust lto: ~14.00 million rows/s llvm : Elapsed: 3.053 sec. Processed 43.24 million rows, 345.88 MB (14.16 million rows/s., 113.28 MB/s.) And note, that now, integrating_rust_libraries.md became deprecated. P.S. LLVM implementation had been choosen over Rust + LTO, because there are issues with linking multiple Rust libraries together with LTO: - https://alanwu.space/post/symbol-hygiene/ - rust-lang/rust#44322
Leveraging Rust in OCaml community is a growing trend nowadays, and we're running into the same problem. Individual OCaml libraries are dragging statically compiled lto-enabled
|
See also #104707. If you are able to use dynamically linked libraries, the cdylib crate type doesn't have this issue. |
Unfortunately OCaml supports only C stubs as Actually after setting |
I see. It is really unfortunate that |
This issue seems to be taking a while to get fixed, so I thought I'd share an ugly+hacky workaround that worked for us at GStreamer: rename the symbols. The proper way to do that involves using something like |
If you build your Rust project as a static library to link it into a C codebase, you usually want to crank up the optimizations, as also indicated by the librsvg blogpost (towards the end):
https://people.gnome.org/~federico/blog/librsvg-build-infrastructure.html
This is all fine, until you want to link in another library built with Rust, which is very likely if you want to incrementally introduce Rust into your codebase. At that point the linker will start complaining about collisions caused by symbols defined by the standard library:
This only happens if you set
lto = true
.The text was updated successfully, but these errors were encountered: