build: replace mold with the wild linker

This commit is contained in:
Amaan Qureshi 2026-06-15 21:53:46 -04:00
commit b42e6f4af9
No known key found for this signature in database
GPG key ID: 2171751AD2A2739A
8 changed files with 53 additions and 66 deletions

View file

@ -1,16 +1,16 @@
# Use a linker wrapper that invokes mold then strips junk sections with objcopy.
# mold cannot discard .eh_frame/.dynstr/.comment via linker scripts, so we do
# it as a post-link step.
# See:
# <https://github.com/rui314/mold?tab=readme-ov-file#how-to-use>
# Link the tier-1 Linux arches with the wild linker, driven by clang via the
# scripts/clang-wild wrapper.
#
# Binary-specific link flags live in microfetch/build.rs via cargo:rustc-link-arg-bin
# so they only affect the final binary and don't break proc-macro or build-script linking.
[target.'cfg(target_os = "linux")']
linker = "scripts/ld-wrapper"
rustflags = [
# Suppress .eh_frame emission from our own codegen (does not cover compiler_builtins;
# those remnants are removed by the linker wrapper via objcopy post-link)
"-C",
"force-unwind-tables=no",
]
# Suppress .eh_frame emission from our own codegen.
rustflags = ["-C", "force-unwind-tables=no"]
[target.x86_64-unknown-linux-gnu]
linker = "scripts/clang-wild"
rustflags = ["-C", "force-unwind-tables=no"]
[target.aarch64-unknown-linux-gnu]
linker = "scripts/clang-wild"
rustflags = ["-C", "force-unwind-tables=no"]

View file

@ -19,8 +19,10 @@ jobs:
with:
rustflags: ""
- name: Make Mold the default linker
uses: rui314/setup-mold@v1
- name: Install the wild linker
uses: wild-linker/action@0.9.0
- name: Put wild on PATH
run: echo "$RUNNER_TEMP/wild-install" >> "$GITHUB_PATH"
- name: Create metrics directory
run: mkdir -p /tmp/metrics

View file

@ -73,8 +73,10 @@ jobs:
if: ${{ !matrix.build_std }}
run: rustup target add ${{ matrix.target }}
- name: "Make Mold the default linker"
uses: rui314/setup-mold@v1
- name: "Install the wild linker"
uses: wild-linker/action@0.9.0
- name: "Put wild on PATH"
run: echo "$RUNNER_TEMP/wild-install" >> "$GITHUB_PATH"
- name: "Setup cross-compilation toolchain"
uses: taiki-e/setup-cross-toolchain-action@v1

View file

@ -59,7 +59,8 @@ welcome to use it on your system: it is pretty _[fast](#benchmarks)_...
- Respects [`NO_COLOR` spec](https://no-color.org/)
- Funny [^1]
[^1]: I don't know how else to describe the (unhealthy) amount of handwritten
[^1]:
I don't know how else to describe the (unhealthy) amount of handwritten
assembly that was written in order to make Microfetch faster.
## Motivation
@ -234,7 +235,7 @@ interested in Microfetch tailored to their distributions.
## Customizing
You can't*
You can't\*
### Really?
@ -302,17 +303,19 @@ A Nix flake is provided. You may use `nix develop` to get started. Direnv users
may instead run `direnv allow` to get a complete environment with shell
integration.
Non-Nix user will need `cargo`, `clang` and `mold` installed on their system to
build Microfetch. As Mold seems to yield _slightly_ better results than the
default linker, it has been set as the default in `.cargo/config.toml` for
x86-64 Linux. You may override those defaults using the `RUSTFLAGS` environment
variable. For example:
Non-Nix users will need `cargo`, `clang` and [`wild`] on their `PATH` to build
Microfetch. `wild` is faster than the default linker and emits a tighter binary,
so it is set as the linker (via `clang`) in `.cargo/config.toml` for x86-64 and
aarch64 Linux. To use a different linker, override it with the `RUSTFLAGS`
environment variable. For example:
```sh
# Use ld instead of Mold
# Use ld instead of wild
$ RUSTFLAGS="-C linker=/path/to/ld.lld" cargo build
```
[`wild`]: https://github.com/wild-linker/wild
## Thanks
Huge thanks to everyone who took the time to make pull requests or nag me in

View file

@ -2,12 +2,20 @@
lib,
rustPlatform,
llvm,
clang,
wild,
}: let
pname = "microfetch";
toml = (lib.importTOML ../Cargo.toml).workspace.package;
inherit (toml) version;
inherit (llvm) stdenv;
# wild + clang are only used on Linux tier-1 arches
hasWild =
stdenv.hostPlatform.isLinux
&& (stdenv.hostPlatform.isx86_64 || stdenv.hostPlatform.isAarch64);
in
rustPlatform.buildRustPackage.override {inherit (llvm) stdenv;} (finalAttrs: {
rustPlatform.buildRustPackage.override {inherit stdenv;} (finalAttrs: {
__structuredAttrs = true;
inherit pname version;
@ -21,12 +29,17 @@ in
(s + /.cargo)
(s + /crates)
(s + /microfetch)
(s + /scripts/ld-wrapper)
(s + /Cargo.lock)
(s + /Cargo.toml)
];
};
nativeBuildInputs = lib.optionals hasWild [wild clang];
env = lib.optionalAttrs hasWild {
RUSTFLAGS = "-Cforce-unwind-tables=no -Clinker=${clang}/bin/clang -Clink-arg=--ld-path=${wild}/bin/wild";
};
cargoLock.lockFile = "${finalAttrs.src}/Cargo.lock";
enableParallelBuilding = true;
buildNoDefaultFeatures = true;

View file

@ -2,7 +2,7 @@
mkShell,
cargo,
rustc,
mold,
wild,
clang,
rust-analyzer,
rustfmt,
@ -16,7 +16,7 @@ mkShell {
nativeBuildInputs = [
cargo
rustc
mold
wild
clang
rust-analyzer

5
scripts/clang-wild Executable file
View file

@ -0,0 +1,5 @@
#!/usr/bin/env sh
# Link via clang+wild. Wild sits behind the linker (not a rustflag) so a
# cross-toolchain that overrides the linker drops it instead of choking on
# --ld-path. Needs clang and wild on PATH.
exec clang --ld-path=wild "$@"

View file

@ -1,38 +0,0 @@
#!/usr/bin/env sh
# Invoke mold, then strip junk sections from the output binary with objcopy.
# This (more or less) removes sections that mold cannot discard itself, such as:
# - .eh_frame / .eh_frame_hdr - unwind tables from compiler_builtins
# - .dynstr - mold emits this, even for fully static binaries
# - .comment - compiler version string
#
# We forward everything to mold via -fuse-ld, then post-process the output in place.
set -eu
# Locate the output file and detect static linking
IS_STATIC=0
OUTPUT=""
prev=""
for arg in "$@"; do
case "$arg" in
-static) IS_STATIC=1 ;;
esac
if [ "$prev" = "-o" ]; then
OUTPUT="$arg"
fi
prev="$arg"
done
# Invoke mold via the cc driver, forward all original arguments
cc -fuse-ld=mold "$@"
# Only strip sections from fully static binaries.
# Dynamic executables (i.e. build scripts, proc-macros) need .dynstr at runtime.
if [ "$IS_STATIC" = 1 ] && [ -n "$OUTPUT" ] && [ -f "$OUTPUT" ]; then
objcopy \
--remove-section=.eh_frame \
--remove-section=.eh_frame_hdr \
--remove-section=.dynstr \
--remove-section=.comment \
"$OUTPUT"
fi