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

{i386,x86_64}-apple-ios targets pass too many thousands of arguments to the linker #52699

Open
gnzlbg opened this issue Jul 25, 2018 · 12 comments
Labels
A-linkage Area: linking into static, shared libraries and binaries C-bug Category: This is a bug. O-ios Operating system: iOS O-macos Operating system: macOS O-x86_32 Target: x86 processors, 32 bit (like i686-*) O-x86_64 Target: x86-64 processors (like x86_64-*) T-compiler Relevant to the compiler team, which will review and decide on the PR/issue.

Comments

@gnzlbg
Copy link
Contributor

gnzlbg commented Jul 25, 2018

To reproduce:

git clone https://github.com/rust-lang-nursery/packed_simd
cd packed_simd
# Get Rust target:
rustup target add x86_64-apple-ios
# Build cargo runner:
export RUSTFLAGS='-C link-args=-mios-simulator-version-min=7.0'
rustc ./ci/deploy_and_run_on_ios_simulator.rs -o ios_cargo_runner --verbose
export CARGO_TARGET_X86_64_APPLE_IOS_RUNNER=$(pwd)/ios_cargo_runner
# Build library:
cargo build --target=x86_64-apple-ios
# Build and run tests:
cargo test --target=x86_64-apple-ios

Produces this output (too long to put it here, gist: https://gist.github.com/gnzlbg/4ff458a32ebd56e4d8930aaf766178b4). Linking fails. This is rust-lang-nursery/packed_simd issue: rust-lang/packed_simd#26

cc @michaelwoerister @alexcrichton this might be related to incremental compilation, I see a lot of rcgus.

@alexcrichton
Copy link
Member

Hm yes so incremental means that the maximal number of cgus are used, where if you have a crate with N modules it basically translates to N (ish) cgus (afaik at least). As to why that ends up failing the linker... I'm not sure! I could be that an OS limit for the command line is silently blown and while rustc may handle it something else like clang isn't handling it?

@gnzlbg
Copy link
Contributor Author

gnzlbg commented Jul 25, 2018

where if you have a crate with N modules it basically translates to N (ish) cgus (afaik at least)

The crate has ~2500 modules when compiled with cfg(test)... resulting in ~2500 cgus that must be linked together...

@kennytm
Copy link
Member

kennytm commented Jul 25, 2018

getconf ARG_MAX on macOS is 256 KiB, whereas this linker command is 419 KiB in total.

We should switch the argument file (cc @args.txt) if the total size of arguments is too long.

(This means we apply Command::very_likely_to_exceed_some_spawn_limit to macOS as well.)

@kennytm kennytm added A-linkage Area: linking into static, shared libraries and binaries O-macos Operating system: macOS C-bug Category: This is a bug. T-compiler Relevant to the compiler team, which will review and decide on the PR/issue. labels Jul 25, 2018
@alexcrichton
Copy link
Member

Actually I think this is one of the reasons that this takes so long to compile, we're actually already falling back to an @-file! Enabling some debug logs I get:

 INFO 2018-07-26T01:24:52Z: rustc_codegen_llvm::back::link: command line to linker was too big: Argument list too long (os error 7)
 INFO 2018-07-26T01:24:52Z: rustc_codegen_llvm::back::link: falling back to passing arguments to linker via an @-file
  INFO 2018-07-26T01:24:52Z: rustc_codegen_llvm::back::link: invoking linker "cc" "@/var/folders/_k/gjskx0jj207ccwd0tdxq5nb00000gn/T/rustc3HTPIr/linker-arguments"

(full logs)

I'm not really sure what's going on here :(

Maybe we're blowing some limit in the OSX linker? We probably shouldn't be passing thousands of objects anyway

@alecmocatta
Copy link
Contributor

Not all lds support @args syntax – the macOS ld being one of them I believe. Hence gcc or clang is having to pass the arguments to ld at the command line exhaustively, and is thus itself likely bumping into the ARG_MAX limit.

@kennytm
Copy link
Member

kennytm commented Jul 26, 2018

Interesting. macOS's ld does allow using a file as input though:

     -filelist file[,dirname]
                 Specifies that the linker should link the files listed in
                 file.  This is an alternative to listing the files on the com-
                 mand line.  The file names are listed one per line separated
                 only by newlines. (Spaces and tabs are assumed to be part of
                 the file name.)  If the optional directory name, dirname is
                 specified, it is prepended to each name in the list file.

@gnzlbg
Copy link
Contributor Author

gnzlbg commented Jul 26, 2018

I wonder, why I am hitting this when using --target=i386-apple-ios but not when using --target=x86_64-apple-darwin ? Aren't they using the same linker ?

@alecmocatta
Copy link
Contributor

@kennytm Good spot. This is a guess from a quick look, but it looks like clang puts the first n contiguous filenames into the filelist (response file in its jargon); the remainder are passed as args as normal. My guess is that the filename in -Wl,-syslibroot /Applications/Xcode.app/Contents/Developer/Platforms/iPhoneSimulator.platform/Developer/SDKs/iPhoneSimulator11.4.sdk is being put into the filelist; the -L /Users/acrichton/.rustup/toolchains/nightly-x86_64-apple-darwin/lib/rustlib/x86_64-apple-ios/lib is stopping additions to the filelist; and all the remaining thousands of files are passed as args as normal?

As clang and gcc both pass thru the -filelist, perhaps we could create it before giving it to them? Indeed, gcc doesn't seem to ever create a filelist itself so to use that as the driver of apple's ld would presumably not work if the command was too long.

@michaelwoerister
Copy link
Member

The crate has ~2500 modules when compiled with cfg(test)

Depending on whether those modules contain generic code too, it might even be ~5000 cgus.

In theory it should be possible to limit the number of cgus created by incremental compilation, at the expensive of less accurate cache utilization.

@steveklabnik
Copy link
Member

Triage: lots of changes have happened, not sure if this reproduces or not

@workingjubilee workingjubilee added O-ios Operating system: iOS O-x86 O-x86_64 Target: x86-64 processors (like x86_64-*) labels Mar 2, 2023
@bjorn3
Copy link
Member

bjorn3 commented Jul 13, 2023

Removing

// We mostly only care about Windows in this method, on Unix the limits
// can be gargantuan anyway so we're pretty unlikely to hit them
if cfg!(unix) {
return false;
}
should probably fix this.

@keith
Copy link
Contributor

keith commented Jul 27, 2023

Also note macOS bumped the limit a lot a few years ago, but recent versions of ld64 also support passing a params file so if we could rely on users having the newer versions that would be fine too

@Noratrieb Noratrieb added O-x86_32 Target: x86 processors, 32 bit (like i686-*) and removed O-x86-all labels Oct 25, 2023
bors added a commit to rust-lang-ci/rust that referenced this issue Feb 18, 2025
Make `#[used]` work when linking with `ld64`

To make `#[used]` work in static libraries, we use the `symbols.o` trick introduced in rust-lang#95604.

However, the linker shipped with Xcode, ld64, works a bit differently from other linkers; in particular, [it completely ignores undefined symbols by themselves](https://github.com/apple-oss-distributions/ld64/blob/ld64-954.16/src/ld/parsers/macho_relocatable_file.cpp#L2455-L2468), and only consider them if they have relocations (something something atoms something fixups, I don't know the details).

So to make the `symbols.o` file work on ld64, we need to actually insert a relocation. That's kinda cumbersome to do though, since the relocation must be valid, and hence must point to a valid piece of machine code, and is hence very architecture-specific.

Fixes rust-lang#133491, see that for investigation.

---

Another option would be to pass `-u _foo` to the final linker invocation. This has the problem that `-u` causes the linker to not be able to dead-strip the symbol, which is undesirable. (If we did this, we would possibly also want to do it by putting the arguments in a file by itself, and passing that file via ``@`,` e.g. ``@undefined_symbols.txt`,` similar to rust-lang#52699, though that [is only supported since Xcode 12](https://developer.apple.com/documentation/xcode-release-notes/xcode-12-release-notes#Linking), and I'm not sure we wanna bump that).

Various other options that are probably all undesirable as they affect link time performance:
- Pass `-all_load` to the linker.
- Pass `-ObjC` to the linker (the Objective-C support in the linker has different code paths that load more of the binary), and instrument the binaries that contain `#[used]` symbols.
- Pass `-force_load` to libraries that contain `#[used]` symbols.

Failed attempt: Embed `-u _foo` in the object file with `LC_LINKER_OPTION`, akin to rust-lang#121293. Doesn't work, both because `ld64` doesn't read that from archive members unless it already has a reason to load the member (which is what this PR is trying to make it do), and because `ld64` only support the `-l`, `-needed-l`, `-framework` and `-needed_framework` flags in there.

---

TODO:
- [x] Support all Apple architectures.
- [x] Ensure that this works regardless of the actual type of the symbol.
- [x] Write up more docs.
- [x] Wire up a few proper tests.

`@rustbot` label O-apple
bors added a commit to rust-lang-ci/rust that referenced this issue Feb 24, 2025
Make `#[used]` work when linking with `ld64`

To make `#[used]` work in static libraries, we use the `symbols.o` trick introduced in rust-lang#95604.

However, the linker shipped with Xcode, ld64, works a bit differently from other linkers; in particular, [it completely ignores undefined symbols by themselves](https://github.com/apple-oss-distributions/ld64/blob/ld64-954.16/src/ld/parsers/macho_relocatable_file.cpp#L2455-L2468), and only consider them if they have relocations (something something atoms something fixups, I don't know the details).

So to make the `symbols.o` file work on ld64, we need to actually insert a relocation. That's kinda cumbersome to do though, since the relocation must be valid, and hence must point to a valid piece of machine code, and is hence very architecture-specific.

Fixes rust-lang#133491, see that for investigation.

---

Another option would be to pass `-u _foo` to the final linker invocation. This has the problem that `-u` causes the linker to not be able to dead-strip the symbol, which is undesirable. (If we did this, we would possibly also want to do it by putting the arguments in a file by itself, and passing that file via ``@`,` e.g. ``@undefined_symbols.txt`,` similar to rust-lang#52699, though that [is only supported since Xcode 12](https://developer.apple.com/documentation/xcode-release-notes/xcode-12-release-notes#Linking), and I'm not sure we wanna bump that).

Various other options that are probably all undesirable as they affect link time performance:
- Pass `-all_load` to the linker.
- Pass `-ObjC` to the linker (the Objective-C support in the linker has different code paths that load more of the binary), and instrument the binaries that contain `#[used]` symbols.
- Pass `-force_load` to libraries that contain `#[used]` symbols.

Failed attempt: Embed `-u _foo` in the object file with `LC_LINKER_OPTION`, akin to rust-lang#121293. Doesn't work, both because `ld64` doesn't read that from archive members unless it already has a reason to load the member (which is what this PR is trying to make it do), and because `ld64` only support the `-l`, `-needed-l`, `-framework` and `-needed_framework` flags in there.

---

TODO:
- [x] Support all Apple architectures.
- [x] Ensure that this works regardless of the actual type of the symbol.
- [x] Write up more docs.
- [x] Wire up a few proper tests.

`@rustbot` label O-apple
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
A-linkage Area: linking into static, shared libraries and binaries C-bug Category: This is a bug. O-ios Operating system: iOS O-macos Operating system: macOS O-x86_32 Target: x86 processors, 32 bit (like i686-*) O-x86_64 Target: x86-64 processors (like x86_64-*) T-compiler Relevant to the compiler team, which will review and decide on the PR/issue.
Projects
None yet
Development

No branches or pull requests

10 participants