You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Part of #2. Complements — does not replace — #7 (native PE/COFF) and #8 (native Mach-O).
Proposal
reld v0 is three linkers in one binary. Linux links natively via the inherited wild core (working today). Windows and macOS link via a bridge: reld dispatches to the fastest available open-source linker for that format, so reld is invocable through -Clinker/-fuse-ld= on all three OSes now, and the published benchmark graph gets three platforms of data instead of one.
The native backends (#7, #8) are measured in months. Until they land, reld on Windows/macOS exits with bail!, the benchmark covers one OS, and all cross-platform CI plumbing remains unproven. The bridge fixes all three cheaply, and when a native backend lands it replaces the bridge behind the same dispatch point — with the benchmark immediately showing the native-vs-bridge delta on the day of the switch.
Investigation: fastest open-source linker per platform
Platform
Choice
Why
Windows PE/COFF
lld-link (LLD COFF driver)
Default bridge. Ships inside every Rust toolchain as rust-lld — zero build/install cost, Apache-2.0 WITH LLVM-exception. The fastest broadly-available OSS COFF linker.
Optional bridge + benchmark competitor. ~50% faster link on multi-GB-debug-info projects, fully MSVC-compatible CLI, real PDB output. x64-Windows-only, younger codebase — alternative, not default.
macOS Mach-O
ld64.lld (LLD Mach-O driver)
The only viable option. mold's Mach-O backend was removed (the commercial sold fork was discontinued when mold 2.0 went MIT); zld is archived and itself recommends ld64.lld; Apple's ld-prime is faster but closed-source. Also inside rust-lld.
Linux ELF
wild core (inherited)
Native already. No bridge.
The pivotal fact:rust-lld is one binary containing all three drivers selected by flavor (ld.lld / lld-link / ld64.lld), and it is already installed with every rustc. The bridge is therefore: locate rust-lld (or system lld), exec the right flavor with argv passed through, propagate exit status and diagnostics. No LLVM build, no C++ FFI, no vendored source.
There is no production-grade Rust COFF or Mach-O linker to vendor instead — wild's Mach-O backend is a feature-gated skeleton with 40 todo!()s (#8), and nothing else exists. Subprocess delegation to lld is the entire realistic option space for "runs everywhere this quarter."
What already exists in-tree (verified)
Dispatch is done (D2): argv[0] multi-call + -flavor parsing live in crates/reld-core/src/args.rs (from_flavor ~line 239, from_executable_name ~line 251); a reld-link binary already builds via crates/reld/src/bin/reld-link.rs.
The plug-in points are single lines:crates/reld-core/src/lib.rs:267 — Args::Coff(_) => bail!("COFF linking is not implemented until Phase 3") — and the Mach-O arm beside it at line 266. The bridge replaces these two bail!s.
The benchmark n/a is hardcoded:crates/reld-testkit/src/bin/reld-bench.rs:127 prints n/a unconditionally; default competitor set ["bfd","lld","mold","wild"] (line ~70) is Linux-only; the workflow .github/workflows/benchmark-stats.yml runs on a single ubuntu-24.04 runner; history.jsonl lines (ci/benchmark_stats.py:329) carry no OS/target key, so multi-platform history needs a schema bump.
Cross-platform groundwork already present: probe_linker degrades missing linkers to n/a; .exe naming and -fPIC are cfg!(windows)-guarded; chart-font lookup already knows Windows/macOS paths.
Relationship to locked decisions — read this before objecting
docs/plan/01-DECISIONS.md D6 says an external tool dependency in the hot path "defeats the entire premise of a fast dev-loop linker." The bridge deliberately and temporarily violates that rationale, and says so out loud. Resolution:
The bridge starts as scaffolding — it lights up CI, toolchain integration, and the benchmark on three platforms before the native backends exist — but the dispatch architecture is permanent: reld is a router over multiple engines. The native wild-core engine becomes the default for a platform the day its backend lands (P3: PE/COFF backend, MinGW (windows-gnu) ABI #7/P4: Mach-O backend, aarch64-apple-darwin #8); bridge engines remain behind it for what the native engine will never do (LTO, release-fidelity links — see B8) and as the differential-test oracle.
Every benchmark datum produced while bridging is labeledmode: "bridge:lld" in the JSON and in the graph caption. A bridge number presented as a reld number would violate the project's honesty policy (README claim 4, DESIGN §6). We are publishing "reld orchestrating lld," and the graph will say exactly that.
D1 (in-tree backends only) is untouched — the bridge adds no backend; it adds a delegation arm ahead of the backend dispatch.
Design decisions (proposed answers)
B1 — subprocess, not liblld in-process. In-process saves ~10–30 ms of Windows process spawn but costs an LLVM build dependency and C++ FFI. Not worth it for scaffolding. The spawn overhead is disclosed in the graph caption instead.
B2 — discovery order:RELD_BRIDGE_LINKER env override → rust-lld in the active toolchain (rustc --print sysroot → lib/rustlib/<host>/bin/) → lld-link/ld64.lld on PATH → hard error naming exactly what to install (until BR-5, when the embedded payload becomes the final fallback and this error can no longer occur). Never silently fall back to MSVC link.exe or Apple ld — closed-source tools would poison benchmark comparability and mask discovery bugs.
B3 — flag surface: pass-through. lld already speaks each platform's native dialect (MSVC link.exe args for COFF, ld64 args for Mach-O), which is the same dialect reld's dispatch already selects per D2. reld intercepts only its own diagnostics/flags; unknown-flag policy is lld's.
B4 — bridge output and CI gates: differential/corpus harnesses run against the bridge on Windows/macOS from day one. That validates the harness on those OSes and produces the reference oracle the native backends will be diffed against. Gate scripts must never conflate "bridge green" with "P3: PE/COFF backend, MinGW (windows-gnu) ABI #7/P4: Mach-O backend, aarch64-apple-darwin #8 done."
B5 — exit-status and diagnostic fidelity: propagate the child's exit code verbatim; stream stderr through unmodified with a single reld: delegating to <linker> (bridge mode) note-level line so a user always knows which engine linked.
Distribution: self-installing engine payloads
The bridge must not depend on the user having a Rust toolchain or lld on PATH. Instead, reld ships as a payload binary: each per-platform reld build embeds a compressed archive (zstd) of that platform's bridge engine(s), self-extracted on first run into a versioned cache. reld is then a single-file install that links out of the box on every OS.
B6 — payload, with discovery as override. Embedded payload is the guarantee; RELD_BRIDGE_LINKER / sysroot / PATH discovery (B2) becomes an override order searched before falling back to the payload, so power users can pin their own engine. Embedding beats download-on-first-run: works offline, no supply-chain surface at runtime, checksummable at build time. Payloads are sourced from official upstream release artifacts (llvm.org LLVM releases or the rust-lld shipped in rustc dist tarballs), pinned by version + sha256 in-repo. Per-platform builds embed only their own platform's engines (~20–30 MB compressed for lld) — not all three platforms.
B7 — versioned cache layout.~/.reld/engines/<platform>/<engine>/<engine-version>/ (e.g. ~/.reld/engines/x86_64-pc-windows-msvc/lld/19.1.7/). Each reld release carries a manifest pinning exactly which engine versions it uses, keyed by content hash — so a reld update that improves only one engine extracts only that engine, and older reld versions keep working against their pinned versions. Engines coexist by default (they are tens of MB; correctness beats tidiness); reld cache prune removes engines no installed manifest references. Extraction is atomic — extract to a temp dir, fsync, rename into place — because parallel build systems spawn many linker processes at once and first run will race with itself; losers of the rename just use the winner's copy.
B8 — flag-based engine routing. The router inspects the link request and picks the engine, not just the platform:
-flto / /GL / -Clto → route to lld (which has real LTO support) even on Linux where the native engine exists. This upgrades DESIGN's "LTO flags are rejected with a clear diagnostic" to "LTO links are delegated" — strictly better for the user, still zero LTO code in reld.
Release-shaped links (e.g. explicit --engine=lld, or future heuristics) → full-fidelity engine; dev-loop links → fastest engine for that platform.
Every delegation prints the one-line reld: engine=<name> (<reason>) note (B5), and the benchmark records which engine produced each number (B3's mode field generalizes to engine).
Phases
BR-1 — bridge module + replace the two bail!s(small) reld-core gains a bridge module (discovery per B2, exec, status propagation). lib.rs:266-267 route Coff/MachO args to it. Linux path untouched. Gate: existing Linux CI stays green; on a dev Windows box reld-link /?-style smoke passes through to lld-link.
BR-2 — Windows CI proof(medium) windows-latest job: cargo build of a nontrivial crate with -Clinker=reld (msvc target) produces a running .exe. Bridge-discovery unit tests (env override, sysroot lookup, PATH, error text). Optional: radlink smoke behind RELD_BRIDGE_LINKER.
BR-3 — macOS CI proof(medium)
Same on macos-latest (arm64): cargo build -Clinker=reld → binary runs. Note ld64.lld performs ad-hoc signing itself, so D6 is deferred, not solved. Gate: binary executes on the runner with default Gatekeeper.
BR-4 — benchmark goes three-platform(medium)
Workflow becomes a 3-OS matrix. Per-OS competitor sets: Linux bfd/lld/mold/wild, Windows link.exe/lld-link (+radlink optional), macOS ld64/ld64.lld. reld-bench.rs:127 replaced with a real reld invocation (bridge mode on Win/mac, native on Linux). Schema bump: SCHEMA_VERSION=2, results/history lines gain target and mode (native | bridge:lld | bridge:radlink); renderer produces per-OS charts or a faceted chart; README caption documents bridge labeling.
BR-5 — payload packaging + versioned cache(medium)
Embed per-platform zstd engine archives at build time (build script keyed off target triple); first-run atomic extraction into ~/.reld/engines/... per B7; manifest + reld cache prune; RELD_BRIDGE_LINKER/discovery kept as override per B6. Gate: on a clean runner with no rustup and no lld on PATH, a single downloaded reld binary links a program on all three OSes.
BR-6 — flag-based engine routing (LTO etc.)(small)
Router recognizes LTO flags and --engine= and routes per B8; benchmark mode field generalizes to engine. Gate: -Clto build succeeds via delegation on Linux while non-LTO links still use the native engine.
BR-7 — competitor definition + native handoff(small, standing)
The bridge columns define the number the native backends must beat. When #7/#8 land: flip default to native, keep bridge as --bridge fallback and as the differential-test reference linker. Benchmark keeps both series across the transition so the graph shows the delta.
Non-goals
Vendoring LLVM/lld (or radlink) source into the repo — engines are embedded as prebuilt, checksummed binary payloads (B6), never built from source in our tree
Beating anything on speed while bridging — bridge ≈ lld + spawn overhead, by construction
PDB (D5) and code-signing internals (D6) — inherited from lld for free while bridging; still owed by the native backends
Part of #2. Complements — does not replace — #7 (native PE/COFF) and #8 (native Mach-O).
Proposal
reld v0 is three linkers in one binary. Linux links natively via the inherited wild core (working today). Windows and macOS link via a bridge: reld dispatches to the fastest available open-source linker for that format, so
reldis invocable through-Clinker/-fuse-ld=on all three OSes now, and the published benchmark graph gets three platforms of data instead of one.The native backends (#7, #8) are measured in months. Until they land, reld on Windows/macOS exits with
bail!, the benchmark covers one OS, and all cross-platform CI plumbing remains unproven. The bridge fixes all three cheaply, and when a native backend lands it replaces the bridge behind the same dispatch point — with the benchmark immediately showing the native-vs-bridge delta on the day of the switch.Investigation: fastest open-source linker per platform
lld-link(LLD COFF driver)rust-lld— zero build/install cost, Apache-2.0 WITH LLVM-exception. The fastest broadly-available OSS COFF linker.ld64.lld(LLD Mach-O driver)soldfork was discontinued when mold 2.0 went MIT);zldis archived and itself recommends ld64.lld; Apple'sld-primeis faster but closed-source. Also inside rust-lld.The pivotal fact:
rust-lldis one binary containing all three drivers selected by flavor (ld.lld/lld-link/ld64.lld), and it is already installed with every rustc. The bridge is therefore: locate rust-lld (or system lld), exec the right flavor with argv passed through, propagate exit status and diagnostics. No LLVM build, no C++ FFI, no vendored source.There is no production-grade Rust COFF or Mach-O linker to vendor instead — wild's Mach-O backend is a feature-gated skeleton with 40
todo!()s (#8), and nothing else exists. Subprocess delegation to lld is the entire realistic option space for "runs everywhere this quarter."What already exists in-tree (verified)
-flavorparsing live incrates/reld-core/src/args.rs(from_flavor~line 239,from_executable_name~line 251); areld-linkbinary already builds viacrates/reld/src/bin/reld-link.rs.crates/reld-core/src/lib.rs:267—Args::Coff(_) => bail!("COFF linking is not implemented until Phase 3")— and the Mach-O arm beside it at line 266. The bridge replaces these twobail!s.n/ais hardcoded:crates/reld-testkit/src/bin/reld-bench.rs:127printsn/aunconditionally; default competitor set["bfd","lld","mold","wild"](line ~70) is Linux-only; the workflow.github/workflows/benchmark-stats.ymlruns on a singleubuntu-24.04runner;history.jsonllines (ci/benchmark_stats.py:329) carry no OS/target key, so multi-platform history needs a schema bump.probe_linkerdegrades missing linkers ton/a;.exenaming and-fPICarecfg!(windows)-guarded; chart-font lookup already knows Windows/macOS paths.Relationship to locked decisions — read this before objecting
docs/plan/01-DECISIONS.mdD6 says an external tool dependency in the hot path "defeats the entire premise of a fast dev-loop linker." The bridge deliberately and temporarily violates that rationale, and says so out loud. Resolution:mode: "bridge:lld"in the JSON and in the graph caption. A bridge number presented as a reld number would violate the project's honesty policy (README claim 4, DESIGN §6). We are publishing "reld orchestrating lld," and the graph will say exactly that.Design decisions (proposed answers)
RELD_BRIDGE_LINKERenv override →rust-lldin the active toolchain (rustc --print sysroot→lib/rustlib/<host>/bin/) →lld-link/ld64.lldonPATH→ hard error naming exactly what to install (until BR-5, when the embedded payload becomes the final fallback and this error can no longer occur). Never silently fall back to MSVClink.exeor Appleld— closed-source tools would poison benchmark comparability and mask discovery bugs.link.exeargs for COFF, ld64 args for Mach-O), which is the same dialect reld's dispatch already selects per D2. reld intercepts only its own diagnostics/flags; unknown-flag policy is lld's.reld: delegating to <linker> (bridge mode)note-level line so a user always knows which engine linked.Distribution: self-installing engine payloads
The bridge must not depend on the user having a Rust toolchain or lld on PATH. Instead, reld ships as a payload binary: each per-platform reld build embeds a compressed archive (zstd) of that platform's bridge engine(s), self-extracted on first run into a versioned cache. reld is then a single-file install that links out of the box on every OS.
RELD_BRIDGE_LINKER/ sysroot / PATH discovery (B2) becomes an override order searched before falling back to the payload, so power users can pin their own engine. Embedding beats download-on-first-run: works offline, no supply-chain surface at runtime, checksummable at build time. Payloads are sourced from official upstream release artifacts (llvm.org LLVM releases or therust-lldshipped in rustc dist tarballs), pinned by version + sha256 in-repo. Per-platform builds embed only their own platform's engines (~20–30 MB compressed for lld) — not all three platforms.~/.reld/engines/<platform>/<engine>/<engine-version>/(e.g.~/.reld/engines/x86_64-pc-windows-msvc/lld/19.1.7/). Each reld release carries a manifest pinning exactly which engine versions it uses, keyed by content hash — so a reld update that improves only one engine extracts only that engine, and older reld versions keep working against their pinned versions. Engines coexist by default (they are tens of MB; correctness beats tidiness);reld cache pruneremoves engines no installed manifest references. Extraction is atomic — extract to a temp dir, fsync, rename into place — because parallel build systems spawn many linker processes at once and first run will race with itself; losers of the rename just use the winner's copy.-flto//GL/-Clto→ route to lld (which has real LTO support) even on Linux where the native engine exists. This upgrades DESIGN's "LTO flags are rejected with a clear diagnostic" to "LTO links are delegated" — strictly better for the user, still zero LTO code in reld.--engine=lld, or future heuristics) → full-fidelity engine; dev-loop links → fastest engine for that platform.reld: engine=<name> (<reason>)note (B5), and the benchmark records which engine produced each number (B3'smodefield generalizes toengine).Phases
BR-1 — bridge module + replace the two
bail!s (small)reld-coregains abridgemodule (discovery per B2, exec, status propagation).lib.rs:266-267route Coff/MachO args to it. Linux path untouched. Gate: existing Linux CI stays green; on a dev Windows boxreld-link /?-style smoke passes through to lld-link.BR-2 — Windows CI proof (medium)
windows-latestjob:cargo buildof a nontrivial crate with-Clinker=reld(msvc target) produces a running.exe. Bridge-discovery unit tests (env override, sysroot lookup, PATH, error text). Optional: radlink smoke behindRELD_BRIDGE_LINKER.BR-3 — macOS CI proof (medium)
Same on
macos-latest(arm64):cargo build -Clinker=reld→ binary runs. Note ld64.lld performs ad-hoc signing itself, so D6 is deferred, not solved. Gate: binary executes on the runner with default Gatekeeper.BR-4 — benchmark goes three-platform (medium)
Workflow becomes a 3-OS matrix. Per-OS competitor sets: Linux
bfd/lld/mold/wild, Windowslink.exe/lld-link(+radlink optional), macOSld64/ld64.lld.reld-bench.rs:127replaced with a real reld invocation (bridge mode on Win/mac, native on Linux). Schema bump:SCHEMA_VERSION=2, results/history lines gaintargetandmode(native|bridge:lld|bridge:radlink); renderer produces per-OS charts or a faceted chart; README caption documents bridge labeling.BR-5 — payload packaging + versioned cache (medium)
Embed per-platform zstd engine archives at build time (build script keyed off target triple); first-run atomic extraction into
~/.reld/engines/...per B7; manifest +reld cache prune;RELD_BRIDGE_LINKER/discovery kept as override per B6. Gate: on a clean runner with no rustup and no lld on PATH, a single downloaded reld binary links a program on all three OSes.BR-6 — flag-based engine routing (LTO etc.) (small)
Router recognizes LTO flags and
--engine=and routes per B8; benchmarkmodefield generalizes toengine. Gate:-Cltobuild succeeds via delegation on Linux while non-LTO links still use the native engine.BR-7 — competitor definition + native handoff (small, standing)
The bridge columns define the number the native backends must beat. When #7/#8 land: flip default to native, keep bridge as
--bridgefallback and as the differential-test reference linker. Benchmark keeps both series across the transition so the graph shows the delta.Non-goals