# Reproducible Builds

A build is reproducible when the same source, built with the same flags on the same target, produces byte-for-byte identical output. The intuition is that a compiler is a deterministic function from source to bytecode, so this should come for free. In practice it doesn't. Real toolchains leak non-source state into their output in ways that only show up when you diff two builds of the same commit.

Why anyone cares: reproducibility is what lets a third party verify a binary. If a project commits a prebuilt binary into its repo (see [[dependency-vendoring]]) and claims it was built from a given source, a packager can only trust that claim by rebuilding it and checking the hashes match. Without reproducibility, "here is the binary, and here is the source it came from" is unverifiable. That makes it a [[supply-chain-security|supply-chain]] property, not just a tidiness one.

## Where determinism leaks

The [[anubis-wasm-vendor-binary]] build log walks through three distinct failure modes, all hit while producing one small vendored binary.

**Embedded build metadata.** The classic one: `__DATE__` and `__TIME__` macros stamp the wall-clock time of the compile into the output. Two builds seconds apart differ because they literally record different timestamps. The fix is to never embed build time, or to pin it (`SOURCE_DATE_EPOCH` is the common convention).

**Shelling out to whatever is on `$PATH`.** clang silently invokes `wasm-opt` from `$PATH` during a WebAssembly build. Different machines have different `wasm-opt` versions — v108 on one box, v130 on another — so the "same" toolchain produces different bytes, and an old version can outright fail on newer instructions (WebAssembly Exceptions, in the Anubis case). A compiler that reaches outside its own pinned inputs isn't a pure function of its arguments anymore. The Anubis fix was to pass `--no-wasm-opt` and cut the hidden dependency.

**Codegen that depends on memory addresses.** clang's exception-handling path let raw pointer values leak into the *order* it emitted `try_table` blocks. The computation was identical; the byte ordering and the `catch_all_ref` indices were not, so builds differed by about 29 bytes. Two mitigations: disable address-space layout randomization with `setarch --addr-no-randomize` so pointers land in the same places, and record known-good sha256 checksums per architecture with CI verifying them on both x86_64 and arm64.

**Cross-architecture iteration order.** The pointer-ordering problem also differs *between* architectures, because pointer iteration order on arm64 isn't the same as on x86_64. Anubis got reproducibility within an architecture but not across — the remaining gap is an upstream LLVM bug (the iteration order isn't seeded). "Deterministic within an architecture" had to be good enough.

The general shape: a compiler is deterministic in theory, but a production toolchain pulls in the clock, `$PATH`, ASLR, and address-dependent iteration order. Each has to be pinned or recorded before the output is actually reproducible. Or, as the source put it: "You'd think that given the same bytes of input you'd get the same bytes of output. lol. lmao. No, you don't."

## Related

- [[anubis-wasm-vendor-binary]] — the worked example this page is built from
- [[dependency-vendoring]] — reproducibility is what makes a committed vendored binary trustworthy
- [[supply-chain-security]] — the trust chain reproducible builds protect
- [[abi-stability]] — a different binary-level contract; both are about what compiled artifacts guarantee
