Skip to content

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

3 Commits
 
 
 
 
 
 
 
 
 
 

Repository files navigation

ghc-build

Reproducible builds of the GHC we use: an upstream GHC release plus the patches in patches/, packaged as a ghcup-installable bindist.

Two variants are built from the same source tree:

variant GHC Dynamic what it's for
dynamic YES matches a stock upstream bindist
static NO Cabal stops emitting -dynamic-too, worth ~⅓ of compile time on a large codebase

Patches

  • 0001-rts-track-per-thread-CPU-time.patch — adds GHC.Stats.getMyThreadCPUTime (from commit 07d60149 on a GHC 9.2.7 branch). Every variant carries it.

Patches are exported from branches predating GHC 9.4's includes/rts/include/ move; build.sh rewrites those paths when the tree has rts/include.

Building

cabal install alex happy          # plus: build-essential autoconf automake python3 xz-utils libgmp-dev libnuma-dev
./build.sh --flavour static -j16  # ~15 min on 32 cores

Output lands in dist/ with a .sha256 beside it. Install it with:

ghcup install ghc -u <url-of-tarball> 9.6.4 --set

CI builds both variants in an ubuntu:22.04 container — that has to match the oldest glibc the compiler will run on, or it won't start there. Pushing a ghc-* tag attaches both tarballs to a GitHub release; workflow_dispatch builds them as artifacts without releasing.

Bumping GHC

Add the source checksum from https://downloads.haskell.org/~ghc/<version>/SHA256SUMS to the case in build.sh (or pass --ghc-src-sha256), then re-check that each patch still applies.

Using the static variant

Removing -dynamic-too measurably helps. Measured on one large library (~2900 modules, heavy Template Haskell), -O0, dynamic → static:

workers wall CPU
-j4 225.2s → 146.1s 780s → 492s
-j8 155.7s → 105.6s 940s → 610s
-j16 135s → 94.5s 1390s → 927s

with its test suite still green. Note static -j8 beats dynamic -j16 on wall clock using less than half the CPU.

The catch: with no dynamic libraries, Template Haskell loads dependency object code through GHC's own RTS linker instead of the system dynamic linker, and that linker is weaker. Expect to need per-package workarounds:

  • flags: {bitvec: {simd: false}} — otherwise unknown symbol '__cpu_model', a libgcc CPU-feature builtin.
  • ghc-options: {zstd: -optc-DZSTD_TRACE=0} — otherwise an undefined weak symbol (ZSTD_trace_compress_begin) that the RTS linker won't resolve to 0.
  • +RTS -xm20000000 -RTS in the GHC options of anything downstream of a very large library — otherwise loadObj "HS<pkg>...o": failed, because the linker can't find low-address room to mmap the merged library object (~388 MB in our case). Building the library itself is fine: that loads individual module objects, not the merged one.
  • Any dependency whose C code leans on weak or libgcc symbols fails the same way, at Template Haskell time, until someone adds a flag for it.

-xm is a hint, not a fix: it works because that address happened to have room. It is sensitive to the library object growing, to how many dependency objects were loaded first, and to the RTS heap flags on the same command line. The durable fix is splitting a library that large so no single merged object is.

Untested with the static variant: stack ghci, executable components, and HLS. Only stack build and stack test were validated.

About

Reproducible builds of the patched GHC we use at Restaumatic (per-thread CPU time RTS patch; dynamic and static flavours)

Resources

Stars

Watchers

Forks

Releases

Packages

Contributors

Languages