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

Make -Clinker-plugin-lto compatible with ld64 #118377

Closed

Conversation

cormacrelf
Copy link
Contributor

Fixes (partially) #60059. Works with ld64 and ld64.lld. There are other linkers that also need some attention here.

There are also a few warnings we could emit that could make -Clinker-plugin-lto easier to use correctly, in particular for Apple toolchains:

  • warning: ignoring LTO plugin path in -Clinker-plugin-lto=<path to libLTO>, because using LLD, which will use its built-in LLVM
  • warning: using -Clinker-plugin-lto with Apple ld64, but no path to libLTO.dylib provided. LLVM versions likely incompatible
    • .help: Use -Clinker-plugin-lto=path/to/libLTO.dylib from an appropriate LLVM toolchain. See [version compatibility table]

This PR keeps it to what's necessary to make linker plugin LTO work, but I'm happy to deliver some warnings, seeing as I've just messed around with this particular code.

@rustbot
Copy link
Collaborator

rustbot commented Nov 27, 2023

r? @petrochenkov

(rustbot has picked a reviewer for you, use r? to override)

@rustbot rustbot added S-waiting-on-review Status: Awaiting review from the assignee but also interested parties. T-compiler Relevant to the compiler team, which will review and decide on the PR/issue. labels Nov 27, 2023
matthiaskrgr added a commit to matthiaskrgr/rust that referenced this pull request Nov 28, 2023
…o-wasm, r=petrochenkov

Perform LTO optimisations with wasm-ld + -Clinker-plugin-lto

Fixes (partially) rust-lang#60059. Technically, `--target wasm32-unknown-unknown -Clinker-plugin-lto` would complete without errors before, but it was not producing optimized code. At least, it may have been but it was probably not the opt-level people intended.

Similarly to rust-lang#118377, this could benefit from a warning about using an explicit libLTO path with LLD, which will ignore it and use its internal LLVM. Especially given we always use lld on wasm targets. I left the code open to that possibility rather than making it perfectly neat.
rust-timer added a commit to rust-lang-ci/rust that referenced this pull request Nov 29, 2023
Rollup merge of rust-lang#118378 - cormacrelf:bugfix/linker-plugin-lto-wasm, r=petrochenkov

Perform LTO optimisations with wasm-ld + -Clinker-plugin-lto

Fixes (partially) rust-lang#60059. Technically, `--target wasm32-unknown-unknown -Clinker-plugin-lto` would complete without errors before, but it was not producing optimized code. At least, it may have been but it was probably not the opt-level people intended.

Similarly to rust-lang#118377, this could benefit from a warning about using an explicit libLTO path with LLD, which will ignore it and use its internal LLVM. Especially given we always use lld on wasm targets. I left the code open to that possibility rather than making it perfectly neat.
@petrochenkov
Copy link
Contributor

r? compiler
I'm overloaded with reviews.

@rustbot rustbot assigned b-naber and unassigned petrochenkov Dec 4, 2023
@petrochenkov
Copy link
Contributor

r? compiler
b-naber doesn't do reviews.

@rustbot rustbot assigned cjgillot and unassigned b-naber Dec 4, 2023
@cjgillot
Copy link
Contributor

cjgillot commented Dec 9, 2023

r? compiler

@rustbot rustbot assigned davidtwco and unassigned cjgillot Dec 9, 2023
It's not worth the potential trouble if it does nothing.
Copy link
Member

@davidtwco davidtwco left a comment

Choose a reason for hiding this comment

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

I'm not especially familiar with LTO, but these changes seem reasonable to me from what I can surmise from the description and linked issue. I presume a test isn't possible for this?

cc @michaelwoerister @lqd who know more about linkers

@michaelwoerister
Copy link
Member

Thanks for the PR, @cormacrelf! Taking a closer look at #60059 (comment) has been on my todo list for a while. I'll try to make some progress here this week. I don't have access to a macOS system these days, but we can try to make sure that CI tests cover this sufficiently.

@michaelwoerister
Copy link
Member

(I've assigned myself, but don't let this discourage you from also taking a look, @lqd 🙂)

Copy link
Member

@michaelwoerister michaelwoerister left a comment

Choose a reason for hiding this comment

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

First of all, thanks a lot for looking into this, @cormacrelf! The kind of research done in #60059 (comment) is really helpful.

The changes look good to me -- but I have no way of testing this locally. I think we should try to add regression tests for this. We have a test that invokes Clang + LLD, but none that uses ld or ld64. A test similar to this one but with -Clinker-plugin-lto instead of -Clto should do the trick. However, I'm wondering how we can mitigate the fact, that ld64's libLTO.dylib is probably too old for rustc's LLVM version...

arg.push(plugin_path);
self.linker_arg(&arg);
// Note that LLD silently ignores these flags.
// It will always use the LLVM it was built with to perform LTO.
Copy link
Member

Choose a reason for hiding this comment

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

Is this documented somewhere? If so, it would be good to add a link.

I'm not sure if we should emit a warning. It could be super helpful -- but it could also be annoying if you know it's fine but for some reason cannot get rid of the commandline argument.

@cormacrelf
Copy link
Contributor Author

No worries! I have started doing that on long-standing issues with lots of people butting their head against the problem. It has been good.

I agree about regression tests. These flags are going to be load-bearing for some big codebases.

As for libLTO, it looks like needs-matching-clang can ensure we have a clang, and we can inject LLVM_LIBLTO to the makefile environment the same way we inject CLANG and LLVM_BIN_DIR. Something something in the compiletest codebase. I'll have a look.

@michaelwoerister
Copy link
Member

As for libLTO, it looks like needs-matching-clang can ensure we have a clang, and we can inject LLVM_LIBLTO to the makefile environment the same way we inject CLANG and LLVM_BIN_DIR. Something something in the compiletest codebase. I'll have a look.

Excellent idea! So, do we basically expect that -Clinker-plugin-lto will never work unless providing a rustc-compatible libLTO.dylib? I'm wondering if we could ship it with rustc. It looks rather big though (~100MB for libLTO.dylib in LLVM 17)

@bjorn3
Copy link
Member

bjorn3 commented Dec 15, 2023

It will work if you use an LLVM/clang version at least as new as rustc for linking. https://doc.rust-lang.org/rustc/linker-plugin-lto.html#toolchain-compatibility lists the compatibility table, but it seems like it hasn't been updated for a while.

@michaelwoerister
Copy link
Member

It will work if you use an LLVM/clang version at least as new as rustc for linking.

Yes, this observation makes me think that that's rarely the case 🙂:

By the way the LLVM shipped with XCode is almost always too old to load rustc produced bitcode files, so if it seems to work without using the linker plugin bundled with rustc, you aren't actually doing linker plugin LTO.

@cormacrelf
Copy link
Contributor Author

That's a consideration for the docs and the real world users, but what version of clang is used for compiletest? If we know it to be always recent, then using its libLTO in the integration test is on the cards.

@bjorn3
Copy link
Member

bjorn3 commented Dec 15, 2023

but what version of clang is used for compiletest?

If RUSTBUILD_FORCE_CLANG_BASED_TESTS is set (which we do in CI), we build clang too when building LLVM. And I believe we only enable the linker plugin lto tests when a clang version matching the LLVM used by rustc is available. We don't ship this clang we build with RUSTBUILD_FORCE_CLANG_BASED_TESTS though. And it is only set for the x86_64-gnu-debug CI job.

@michaelwoerister
Copy link
Member

@rust-lang/infra, how realistic is it that we build clang (or at least libLTO.dylib) on macOS, so that we can have integration tests that make sure rustc-driven linker-plugin-LTO works on that platform? I think the clang build would be cached in the docker image alongside the rest of LLVM, right?

@Kobzol
Copy link
Contributor

Kobzol commented Dec 18, 2023

macOS jobs don't run inside (our) Docker images, so we'd have to figure out some caching on Github Actions directly (AFAIK). I have created a Zulip topic for this.

@michaelwoerister
Copy link
Member

According to the Zulip discussion, our macOS builders will download their own version of clang and it seems reasonable that we can match that to the LLVM version of the rustc being built. @cormacrelf, do you know if that would help? If we can verify that that downloaded clang will invoke ld64 (instead of e.g. LLD), we should be in a position to regression test that rustc passes the correct arguments to the linker, right?

@michaelwoerister
Copy link
Member

If we can verify that that downloaded clang will invoke ld64

I guess that should be part of the test: make rustc print the linker invocation and check that it's ld64

@cormacrelf
Copy link
Contributor Author

We can write some great tests if we get a recent clang and libLTO, that would be fantastic. Clang 16 and 17 should both work, I think, but 17 to be on the safe side. If there are complications upgrading clang, we could at least test the flags work against rust-lld's Darwin mode without it, but we wouldn't be able to do a smoke test for Rust + C cross language inlining, and actual ld64 support would be untested.

make rustc print the linker invocation and check that it's ld64

Rustc will invoke clang++. And while the linker itself is known as "ld64" to distinguish from the GNU linker, it is invoked by clang as ld, so this technique doesn't go very far in general.

I suppose you want to guard against clang switching its default linker to LLD? I doubt that it will, but we can also just tell clang++ to use an absolute path to a specific linker and pin that down. I forget the incantation for that, it's --ld-path or something.

And may as well also run it with ld64.lld from the same clang build. Heck, you could even run it with a script that invokes rust-lld -flavor darwin "$@". Both of those will work even without a libLTO.

@michaelwoerister
Copy link
Member

We'll need to find out how we can get access to that version of clang within run-make tests. This looks promising:

run_clang_based_tests_with: matches.opt_str("run-clang-based-tests-with"),

@michaelwoerister
Copy link
Member

I think the way to go here is to make the clang version used for these kinds of tests configurable via the compiler's config.toml (i.e. add an option to bootstrap). The option would then look something like:

enum ClangUsedForCrossLangTesting {
    /// Don't run the tests (or fail if RUSTBUILD_FORCE_CLANG_BASED_TESTS is set)
    None,
    /// An externally provided clang version. (we could try to do some sanity checking of versions)
    External(Path),
    /// Build clang from the LLVM sources used by the compiler
    BuildMatching, 
}

Then the various builders could define what they want to do. If we allow an externally provided clang version, we should be able to run the tests on more builders.

@cormacrelf, are you up for looking into this?

@Dylan-DPC Dylan-DPC added S-waiting-on-author Status: This is awaiting some action (such as code changes or more information) from the author. and removed S-waiting-on-review Status: Awaiting review from the assignee but also interested parties. labels Feb 18, 2024
@Congyuwang
Copy link

Congyuwang commented Mar 1, 2024

According to LLVM@17 ld64.lld --help:

lto_library is obsolete.

-lto_library <path> Obsolete. LLD supports LTO directly, without using an external dylib.

As of the optimization levels, there are multiple options for different purposes.

--lto-CGO<cgopt-level>  Set codegen optimization level for LTO (default: 2)
--lto-O<opt-level>      Set optimization level for LTO (default: 2)

-O <value>  Optimize output file size

-O * is only for file size optimization.

Perhaps, -plugin-opt=O* should be converted to --lto-CGO*.

@bors
Copy link
Contributor

bors commented Jul 1, 2024

☔ The latest upstream changes (presumably #127216) made this pull request unmergeable. Please resolve the merge conflicts.

@Dylan-DPC
Copy link
Member

@cormacrelf any updates on this? thanks

@Dylan-DPC
Copy link
Member

Closing this as inactive. Feel free to reöpen this pr or create a new pr if you get the time to work on this. Thanks

@Dylan-DPC Dylan-DPC closed this Sep 29, 2024
@Dylan-DPC Dylan-DPC added S-inactive Status: Inactive and waiting on the author. This is often applied to closed PRs. and removed S-waiting-on-author Status: This is awaiting some action (such as code changes or more information) from the author. labels Sep 29, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
S-inactive Status: Inactive and waiting on the author. This is often applied to closed PRs. T-compiler Relevant to the compiler team, which will review and decide on the PR/issue.
Projects
None yet
Development

Successfully merging this pull request may close these issues.