Skip to content

Upgrade to nixos-25.11 - #131

Open
jonathanlking wants to merge 9 commits into
nh2:masterfrom
jonathanlking:feature/nixos-25.11
Open

Upgrade to nixos-25.11#131
jonathanlking wants to merge 9 commits into
nh2:masterfrom
jonathanlking:feature/nixos-25.11

Conversation

@jonathanlking

@jonathanlking jonathanlking commented May 18, 2026

Copy link
Copy Markdown
Collaborator

Bring static-haskell-nix up to nixos-25.11, with GHC 9.8.4 as the new default compiler. The submodule bump itself is one small commit, but several adaptations were necessary along the way to keep survey -A working building, plus several genuinely substantive fixes for problems that only surface against the 25.11 toolchain.

The mechanical changes:

  • Bump the nixpkgs submodule to the nixos-25.11 tip.
  • Move the default compiler from ghc965 to ghc984, and refresh the defaultCabalPackageVersionComingWithGhc map. Each entry was verified against the actual Cabal-* directory in the installed GHC.
  • Remove integer-simple support entirely. Modern GHCs no longer ship the integer-simple library, leaving the conditional unreachable.
  • Disable pslSupport and http3Support on curl. libpsl's AC_CHECK_LIB doesn't pass transitive libunistring/libidn2 on the static link line, and nghttp3/ngtcp2 don't ship a static archive in our overlay.
  • Follow nixpkgs's rename of lzma to xz.

The substantive fixes:

  • Switch the postgres-libpq C dependency from the full postgresql_17 server to the standalone libpq derivation that nixpkgs split out in 25.11. The smaller closure dodges the systemd/kerberos/pam/llvm-for-JIT build inputs, and also dodges the LLVM-bitcode-in-.a-archives that postgresql_17's -flto build path emits (gold can't link bitcode).

  • Work around a GHC bug where the per-package merged HS<pkg>-<ver>-<hash>.o "ghci library" objects lose the 32-byte alignment of AVX2 SIMD constants in .rodata.cst32. The cause is driver/utils/merge_sections.ld's .rodata wildcard catching them. The symptom is that GHC's RTS linker maps the defective object at TemplateHaskell-evaluation time, and vmovdqa faults on the misaligned constant. The workaround patches the linker script to preserve .rodata.cst32 (and .cst64) as their own output sections, and also deletes the merged .o files after install so the RTS linker uses the per-archive members instead. Either change alone is enough, kept together as defence in depth. Upstream commit https://gitlab.haskell.org/ghc/ghc/-/commit/53038ea9 (post-9.12) removes the merge step entirely. Once on a GHC that contains that commit, this workaround should be dropped. A self-contained reproducer is attached as a comment below.

  • Centralise the libpq / openssl static-link wiring inside postgresql-libpq-pkgconfig, the Haskell wrapper sub-package that nixpkgs's configuration-nix.nix routes every postgresql-libpq consumer through. Two pieces work together: an addPkgconfigDepend openssl so pkg-config --static --libs libpq can resolve libpq.pc's Requires.private, and a prePatch that injects extra-libraries: pq, pgcommon, pgport, ssl, crypto into the .cabal so GHC's package-walking aggregation propagates the deps to every transitive consumer's link. pq first, because ld walks static archives left-to-right. This replaces the addStaticLinkerFlagsWithPkgconfig wrappers that the survey previously applied to each libpq consumer. The same change adds a postgresql override (gssSupport/curlSupport off) for packages whose closures pull in the full postgresql_17 for tests (e.g. via postgresql-simple's addTestToolDepends), and adds postgrest, hasql-notifications, squeal-postgresql to the working set's pkgsMusl section.

  • Build openssl and ncurses in both shared and static form via new statify_openssl / statify_ncurses helpers, matching the existing statify_zlib / statify_curl_including_exe convention.

Verified:

  • nix-build survey -A working instantiates and builds.

  • The resulting postgrest binary is fully statically linked:

    $ out=$(nix-build survey -A working.postgrest.bin --no-out-link)
    $ file "$out/bin/postgrest"
    ... ELF 64-bit LSB executable, x86-64, ..., statically linked, stripped
    $ ldd  "$out/bin/postgrest"
            not a dynamic executable
    

    Smoke-tested against a docker postgres:17 instance with a minimal anon-role schema, serving GET requests over HTTP and returning the expected JSON.

Happy to take feedback on any of this.

jonathanlking and others added 2 commits May 13, 2026 09:51
Bumps the bundled nixpkgs submodule from the previously-recorded
ede5282 (24.05-era) to nixos-25.11's tip at 8fd9daa3, matching the
nixpkgs commit used to test the survey changes earlier in this branch.

With this bump, callers that don't pass `--arg normalPkgs` get the
correct (25.11) nixpkgs by default, and the survey is testable
standalone:

    nix-build survey/default.nix -j 8 -A pkgs.libpq \
      --arg compiler '"ghc984"'
GHC 9.8.4 is the only viable option for aarch64-musl because it boots
from ghc984Binary which has an aarch64-musl Alpine bindist. GHC 9.6.7
is blocked because its boot chain requires ghc902Binary which lacks
aarch64-musl.

Remove GHC versions no longer in nixpkgs 25.11 (ghc8107, ghc902,
ghc928, ghc963, ghc965) from the Cabal version map.

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
@jonathanlking

Copy link
Copy Markdown
Collaborator Author

Reproducer for the GHC ghci-library AVX2 alignment SIGSEGV that the fixGhc change in this PR works around.

# A minimal, self-contained reproducer for the GHC "ghci library" AVX2
# alignment SIGSEGV. It pins nixpkgs at the `nixos-25.11` tip and builds
# against _vanilla_ `pkgs.haskell.compiler.ghc984`, with no
# static-haskell-nix, overlays, or musl in sight. The only nudge it gives
# the compiler is to flip two `.override` arguments that, between them,
# drive GHC's TemplateHaskell-evaluation path into the misaligned
# merged-`.o` regime:
#
#     enableShared           = false   # produce only .a, no .so
#     enableRelocatedStaticLibs = true # build .a as -fPIC + ld -r
#
# Under that combination, GHC's build emits per-package merged
# `HS<pkg>-<ver>-<hash>.o` "ghci library" objects via
# `ld -r -T driver/utils/merge_sections.ld`. That linker script catches
# `.rodata.cst32` under its `.rodata` wildcard rule, and loses the
# 32-byte alignment of the AVX2 SIMD constants gcc emits there. At
# TemplateHaskell-evaluation time the RTS linker dutifully maps the
# defective `HSbytestring-*.o`, and `vmovdqa` then faults against the
# now-misaligned constant whenever the AVX2 fast path runs (bytestring's
# `isValidUtf8` over inputs of >= 128 bytes, where `big_strides = len / 128`).
#
# Expected outcome: this derivation _fails to build_, with GHC dying at
# `EXIT=139` (SIGSEGV) while compiling `Main.hs`. Dropping the input size
# to 127 in `Splice.hs` (below the AVX2 threshold) lets it through.
#
# Worth noting: bytestring's `isValidUtf8`, and with it the AVX2 fast
# path, only landed in 0.11.2.0 (PR #423, November 2021; adapted from
# `text`'s simdjson-based work). Only GHCs bundling bytestring
# >= 0.11.2.0 can stumble on the defect via this code path; the
# linker-script bug itself is much older, but stayed harmless until
# AVX2 code arrived that performs aligned loads against the constants
# gcc emits into `.rodata.cst32`.
#
# Usage:
#
#     # Save this file as `default.nix` in some directory; referred to
#     # below as `./avx2-repro`.
#
#     # Reproduce the segfault with a vanilla GHC (the default):
#     nix-build ./avx2-repro
#
#     # Verify a fix by passing in a patched GHC, e.g. the one produced by
#     # static-haskell-nix's `survey` (when run from a static-haskell-nix
#     # checkout):
#     nix-build ./avx2-repro \
#       --arg ghc '(import ./survey { compiler = "ghc984"; useArchiveFilesForTemplateHaskell = true; }).pkgs.ghc'
#
# Nothing in this derivation depends on the rest of static-haskell-nix,
# so it should be suitable for filing upstream (e.g. at
# gitlab.haskell.org/ghc).

{
  # Pinned to the tip of nixos-25.11 at the time of writing.
  nixpkgsRev ? "8fd9daa3db09ced9700431c5b7ad0e8ba199b575",
  nixpkgsSha256 ? "1i4bkzy1siavmxaskp49lgi9s02gam6crb0d0abbbjmsyl39jbsf",
  nixpkgsSrc ? builtins.fetchTarball {
    url = "https://github.com/NixOS/nixpkgs/archive/${nixpkgsRev}.tar.gz";
    sha256 = nixpkgsSha256;
  },

  pkgs ? import nixpkgsSrc { overlays = []; },

  # Default: a vanilla glibc GHC 9.8.4 with the two overrides that nudge
  # its TH-eval path into the "load merged ghci-library .o via the RTS
  # linker" regime, which is where the alignment bug surfaces. The bug
  # itself is independent of libc / target triple; it lives in
  # `driver/utils/merge_sections.ld`. Pass in a different GHC (e.g. one
  # patched against `merge_sections.ld`, such as the GHC produced by
  # static-haskell-nix's `survey`) to confirm a fix.
  ghc ? pkgs.haskell.compiler.ghc984.override {
    enableShared             = false;
    enableRelocatedStaticLibs = true;
  },
}:

let
  spliceHs = pkgs.writeText "Splice.hs" ''
    {-# LANGUAGE TemplateHaskell #-}
    {-# LANGUAGE BangPatterns #-}
    module Splice (s) where
    import Language.Haskell.TH
    import qualified Data.ByteString as B

    -- The TH splice forces `B.isValidUtf8` over a 128-byte buffer at
    -- compile time. 128 is the AVX2 fast-path threshold in bytestring's
    -- `cbits/is-valid-utf8.c` (`big_strides = len / 128`). Drop the size
    -- to 127 and the splice never reaches `vmovdqa` at all.
    s :: Q Exp
    s = let !r = B.isValidUtf8 (B.replicate 128 65) in [| r |]
  '';

  mainHs = pkgs.writeText "Main.hs" ''
    {-# LANGUAGE TemplateHaskell #-}
    module Main where
    import Splice (s)
    main :: IO ()
    main = print $(s)
  '';
in
pkgs.stdenv.mkDerivation {
  pname = "ghci-library-avx2-alignment-repro";
  version = "0";

  dontUnpack = true;

  nativeBuildInputs = [ ghc ];

  buildPhase = ''
    runHook preBuild

    cp ${spliceHs} Splice.hs
    cp ${mainHs}   Main.hs

    ghc --make \
      -hide-all-packages \
      -package base \
      -package bytestring \
      -package template-haskell \
      -XHaskell2010 \
      Main.hs

    # Sanity: the resulting program should print "True".
    ./Main

    runHook postBuild
  '';

  installPhase = ''
    runHook preInstall
    mkdir -p $out/bin
    install -Dm755 ./Main $out/bin/ghci-library-avx2-alignment-repro
    runHook postInstall
  '';

  meta = {
    description =
      "Tiny TemplateHaskell program that segfaults GHC's RTS linker "
      + "when GHC's ghci-library merge_sections.ld bug is unpatched.";
  };
}

jonathanlking and others added 3 commits May 18, 2026 18:30
integer-simple has been replaced by ghc-bignum with enableNativeBignum
in nixpkgs 25.11. Remove the integer-simple parameter and all
conditional code paths that depended on it (~10 locations):
- buildPlatformHaskellPackagesWithFixedCabal
- stackageExecutables
- setupGhcOverlay
- add_integer-simple_if_needed helper (deleted entirely)
- blaze-textual handleIntegerSimple
- cryptonite integer-gmp flag
- scientific/x509-validation conditional dontCheck
- statify gmp/libffi lib-dirs guards

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
Disable pslSupport and http3Support in the curl override:
- libpsl: configure's AC_CHECK_LIB link test does `-lpsl` without the
  transitive deps (libunistring, libidn2) needed for static linking
- nghttp3/ngtcp2: new HTTP/3 deps in nixpkgs 25.11 lack static libs
  in our overlay

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
nixos-25.11 dropped the `pkgs.lzma` attribute alias; the liblzma C
library now lives at `pkgs.xz`. The `sdl2-image` static-link rule was
referring to it by the old name (via `with final;`), so on nixos-25.11
that evaluation would fail with "attribute 'lzma' missing".

The pkg-config name is unchanged (`liblzma`); this is purely an
attribute rename.
@jonathanlking
jonathanlking force-pushed the feature/nixos-25.11 branch from d478f59 to a474187 Compare May 18, 2026 17:32
nixpkgs split `libpq` out as a top-level package some time after the
postgresql-server LTO/JIT codegen change. The old `postgresql_14`
override here pulled in the full server (systemd, kerberos, pam,
llvm-for-JIT) just to get libpq, and on nixos-25.11's pkgsMusl the
server's `.a` archives now contain LLVM IR bitcode that gold can't
link.

Switch the overlay to override `previous.libpq` directly:

  * `gssSupport = false` keeps the existing reason from before
    (`gss_init_sec_context` not available on static builds).
  * `curlSupport = false` disables PG18's libcurl-backed OAuth
    client. Avoiding the dlopen-loaded `libpq-oauth-*.so` keeps the
    closure smaller; we don't need OAuth.
  * `openssl = previous.openssl` builds libpq against the un-overridden
    (shared) openssl, not the static openssl this overlay installs.
    With static openssl, libpq's link `-lssl -lcrypto` would resolve
    against the static archive and pull OpenSSL's
    `ossl_init_register_atexit_ossl_` (and its `atexit` /
    `pthread_exit` calls) directly into `libpq.so`. PG18's
    `libpq-refs-stamp` policy check in
    `src/interfaces/libpq/Makefile` then rejects the build with
    "libpq must not be calling any function which invokes exit".
    Linking shared openssl keeps those references inside `libssl.so`,
    matching upstream `pkgsMusl.libpq`.
  * `postInstall = ""` clears libpq.nix's default `rm -f $out/lib/*.a`
    so the static archive is retained. nixpkgs PR #519070 adds
    `dontDisableStatic` support to libpq.nix; once merged, this should
    be removed.

The `final.postgresql` references in `haskellLibsReadyForStaticLinkingOverlay`
(passed to `addStaticLinkerFlagsWithPkgconfig` for `--libs libpq` pkg-config
queries) are switched to `final.libpq` so they resolve directly against the
new attribute, not the un-overridden pkgsMusl postgresql_17.
When GHC is built with `-split-sections`, both the make-based and
hadrian build systems invoke `ld -r` with the linker script
`driver/utils/merge_sections.ld` (in GHC's source tree since at
least 9.0) to produce a per-package merged `HS<pkg>-<ver>-<hash>.o`
("ghci library") object that GHC's RTS linker loads at TH-eval time.
That linker script catches `.rodata.cst32` under the `.rodata`
wildcard, which loses the 32-byte alignment of AVX2 SIMD constants
emitted by gcc into that section. The merged `.o` is therefore
defective: any code that does an aligned 256-bit load against one of
those constants (e.g. bytestring's `isValidUtf8` AVX2 path on inputs
>= 128 bytes) faults at TH-eval time when the RTS linker happens to
map `.rodata` at a non-32-aligned address. Whether the fault
actually fires depends on load-time placement: in some
configurations the constants coincidentally land on a 32-byte
boundary and the defect stays latent.

We make two changes -- either on its own should be enough to prevent
the crash:

  1. `postPatch` patches `merge_sections.ld` to preserve
     `.rodata.cst32` (and `.cst64`) as their own output sections.
     This is a fix for the underlying bug, included to describe the
     cause. It is guarded on file existence so a future GHC that
     drops `merge_sections.ld` is unaffected.

  2. `postFixup` deletes the merged `HS<pkg>-<ver>-<hash>.o` files
     after install. The RTS linker then loads the `.a` archive
     members instead, where the alignment is preserved per-object.
     This is what removes the problem from the toolchain we ship.

The second change has the same effect as upstream GHC commit
https://gitlab.haskell.org/ghc/ghc/-/commit/53038ea9 which (post-9.12)
removes the ghci-library merge step entirely: in both cases no merged
`.o` is loaded, so the RTS linker uses the `.a` archive members. Drop
this `lib.pipe` step once the toolchain moves to a GHC that includes
that commit (>= 9.14).
Two fixes in `survey/default.nix`:

(1) `postgresql-libpq-pkgconfig`: get libpq's extra C libs onto the
    static link line of every consumer.

    - `addPkgconfigDepend final.openssl`: puts `libssl.pc`/`libcrypto.pc`
      on `PKG_CONFIG_PATH` and propagates openssl's lib dir to
      transitive consumers' `--extra-lib-dirs`.
    - `prePatch` injecting `extra-libraries: pq, pgcommon, pgport, ssl,
      crypto` into the `.cabal`. `pq` first, because ld processes static
      archives left-to-right and needs libpq's unresolved references on
      the table before pulling pgcommon/pgport/ssl/crypto objects.

    Routed centrally because nixpkgs's `configuration-nix.nix` wires
    every `postgresql-libpq` consumer through this sub-package.

(2) `postgresql = previous.postgresql.override { gssSupport = false;
    curlSupport = false; openssl = previous.openssl; }`.

    nixpkgs's Haskell overlay drags the full `postgresql_17` into the
    closure of `postgresql-simple` (test-tool dep). Default
    `gssSupport = true` builds static `krb5-1.22.1`, which fails with a
    `master_keyblock` multiple-definition error (pre-GCC-10 `-fno-common`
    regression). Shared openssl is needed to pass the same
    `libpq-refs-stamp` policy check we already work around for the
    standalone `libpq`.

Obsoletes `addStaticLinkerFlagsWithPkgconfig` wrapper for
libpq/openssl consumers.

Postgrest now builds again and verified end-to-end against a real PG 17
server.
@jonathanlking
jonathanlking force-pushed the feature/nixos-25.11 branch from a474187 to c08bdb1 Compare May 18, 2026 20:57
@jonathanlking

Copy link
Copy Markdown
Collaborator Author

End-to-end smoke test for the static postgrest. Run from a static-haskell-nix checkout on this branch; needs docker and nix-build available.

# 1. Build + verify static linkage
out=$(nix-build survey -A working.postgrest.bin --no-out-link)
file "$out/bin/postgrest"   # statically linked, stripped
ldd  "$out/bin/postgrest"   # not a dynamic executable

# 2. Start postgres, apply a minimal anon-role schema
docker run --rm -d --name pgrest-smoke -p 55432:5432 \
  -e POSTGRES_PASSWORD=postgres -e POSTGRES_DB=test postgres:17
until docker exec pgrest-smoke pg_isready -U postgres >/dev/null 2>&1; do sleep 1; done

docker exec -i pgrest-smoke psql -U postgres -d test <<'SQL'
CREATE ROLE web_anon NOLOGIN;
CREATE SCHEMA api;
CREATE TABLE api.things (id serial primary key, name text);
GRANT USAGE ON SCHEMA api TO web_anon;
GRANT SELECT ON api.things TO web_anon;
INSERT INTO api.things(name) VALUES ('hello'), ('world');
SQL

# 3. Run postgrest, query, clean up
PGRST_DB_URI=postgres://postgres:postgres@localhost:55432/test \
PGRST_DB_SCHEMAS=api PGRST_DB_ANON_ROLE=web_anon PGRST_SERVER_PORT=3000 \
  "$out/bin/postgrest" &
PGRST_PID=$!
until curl -fsS http://localhost:3000/things >/dev/null 2>&1; do sleep 1; done

curl -fsS http://localhost:3000/things
# Expected: [{"id":1,"name":"hello"}, {"id":2,"name":"world"}]

kill "$PGRST_PID"; docker stop pgrest-smoke

@jonathanlking
jonathanlking marked this pull request as ready for review May 18, 2026 21:12
@jonathanlking
jonathanlking requested a review from nh2 May 19, 2026 07:50
Replace single-form `openssl = previous.openssl.override { static = true; };`
and the inline `final.ncurses.override { enableStatic = true; }` with
dual-form derivations that ship both `.so` and `.a` files from a single
output. Pattern: build the static variant separately, copy its `.a` files
into the shared build's `postInstall` (see `test/openssl-both-form-repro/`
and `test/ncurses-both-form-repro/` for standalone reproducers of the
upstream constraint).

Factored as `statify_openssl` and `statify_ncurses` helpers alongside
the existing `statify_zlib` and `statify_curl_including_exe`, matching
the survey's established convention for "produce both shared and static
in one derivation".

`ncurses` is called with `previous.ncurses`, matching that convention.
`openssl` has to be passed `pkgsDontDisableStatic.openssl` instead:
the helper's inner `.override` triggers a dependency walk that, through
`previous`, reaches `curl -> openssl` and ultimately resolves back to
`final.openssl` (the dual-form override under construction), producing
an infinite recursion. ncurses's dep walk doesn't touch any package
overridden by `archiveFilesOverlay`, so it stays cycle-free under
`previous`.

Drop the `openssl = previous.openssl;` workarounds previously needed in
the libpq and postgresql overrides. With shared `libssl.so` available in
`final.openssl`, libpq.so links it dynamically and passes PG18's
`libpq-refs-stamp` policy check without dragging openssl's
`atexit`/`pthread_exit` symbols in.

Verified by building `working.postgrest` end-to-end against a docker
PG 17 instance with a minimal anon-role schema; the resulting statically
linked `postgrest` serves `GET /things` correctly.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant