From b42e6f4af901b49842226e1674c7ef9c207f57c0 Mon Sep 17 00:00:00 2001 From: Amaan Qureshi Date: Mon, 15 Jun 2026 21:53:46 -0400 Subject: [PATCH] build: replace mold with the wild linker --- .cargo/config.toml | 24 ++++++++--------- .github/workflows/hotpath-profile.yml | 6 +++-- .github/workflows/rust.yml | 6 +++-- docs/README.md | 19 ++++++++------ nix/package.nix | 17 ++++++++++-- nix/shell.nix | 4 +-- scripts/clang-wild | 5 ++++ scripts/ld-wrapper | 38 --------------------------- 8 files changed, 53 insertions(+), 66 deletions(-) create mode 100755 scripts/clang-wild delete mode 100755 scripts/ld-wrapper diff --git a/.cargo/config.toml b/.cargo/config.toml index 2623c5a..9ed6285 100644 --- a/.cargo/config.toml +++ b/.cargo/config.toml @@ -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: -# +# 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"] diff --git a/.github/workflows/hotpath-profile.yml b/.github/workflows/hotpath-profile.yml index 5ebf1a9..7d59763 100644 --- a/.github/workflows/hotpath-profile.yml +++ b/.github/workflows/hotpath-profile.yml @@ -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 diff --git a/.github/workflows/rust.yml b/.github/workflows/rust.yml index 2eb3a1f..8534e96 100644 --- a/.github/workflows/rust.yml +++ b/.github/workflows/rust.yml @@ -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 diff --git a/docs/README.md b/docs/README.md index 6e08a06..daf5d85 100644 --- a/docs/README.md +++ b/docs/README.md @@ -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 diff --git a/nix/package.nix b/nix/package.nix index ba16ffd..c36cdf0 100644 --- a/nix/package.nix +++ b/nix/package.nix @@ -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; diff --git a/nix/shell.nix b/nix/shell.nix index a43cacb..b666c4c 100644 --- a/nix/shell.nix +++ b/nix/shell.nix @@ -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 diff --git a/scripts/clang-wild b/scripts/clang-wild new file mode 100755 index 0000000..be58407 --- /dev/null +++ b/scripts/clang-wild @@ -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 "$@" diff --git a/scripts/ld-wrapper b/scripts/ld-wrapper deleted file mode 100755 index a0337bb..0000000 --- a/scripts/ld-wrapper +++ /dev/null @@ -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