diff --git a/packaging/dependencies.nix b/packaging/dependencies.nix index 480c4079e954..180e19433929 100644 --- a/packaging/dependencies.nix +++ b/packaging/dependencies.nix @@ -36,13 +36,10 @@ scope: { inherit stdenv; }).overrideAttrs (attrs: { - # Reduce contention on the GC allocation lock during parallel - # evaluation by handing out multiple heap blocks worth of - # objects per lock acquisition in GC_generic_malloc_many(). - # The default batch size is set via GC_MANY_BLOCKS_DEFAULT - # below and can be overridden at runtime through the - # GC_MALLOC_MANY_BLOCKS environment variable. - patches = (attrs.patches or [ ]) ++ [ ./patches/boehmgc-batch-malloc-many.patch ]; + patches = (attrs.patches or [ ]) ++ [ + ./patches/boehmgc-batch-malloc-many.patch + ./patches/boehmgc-gctest-tiny-freelists-heap-growth.patch + ]; env = (attrs.env or { }) // { # Increase the initial mark stack size to avoid stack @@ -55,6 +52,14 @@ scope: { [ "-DINITIAL_MARK_STACK_SIZE=1048576" "-DGC_MANY_BLOCKS_DEFAULT=64" + # Serve allocations up to 1520 bytes (95 granules) from + # the per-thread freelists instead of taking the global + # allocation lock. The default (25, i.e. <= 384 bytes) is + # too small for parallel evaluation: e.g. a typical + # derivation attrset (~46 attrs) is a 752-byte Bindings, + # of which nixpkgs evaluation does hundreds of thousands, + # all serialized on GC_allocate_ml. + "-DGC_TINY_FREELISTS=96" ] # For some reason that is not clear, it is wanting to use libgcc_eh which is not available. # Force this to be built with compiler-rt & libunwind over libgcc_eh works. diff --git a/packaging/patches/boehmgc-gctest-tiny-freelists-heap-growth.patch b/packaging/patches/boehmgc-gctest-tiny-freelists-heap-growth.patch new file mode 100644 index 000000000000..a7cd51d6945d --- /dev/null +++ b/packaging/patches/boehmgc-gctest-tiny-freelists-heap-growth.patch @@ -0,0 +1,38 @@ +From 7cd4f090acc0e801502f918060106bba926c7153 Mon Sep 17 00:00:00 2001 +From: Eelco Dolstra +Date: Tue, 25 Aug 2026 14:37:18 +0200 +Subject: [PATCH] Scale gctest's heap growth limit with GC_TINY_FREELISTS + +The 'unexpected heap growth' check in check_heap_stats() uses a fixed +limit that is exceeded when the collector is built with a larger +GC_TINY_FREELISTS: bigger thread-local free lists cause each thread to +retain more nearly-empty heap blocks (up to one per size class and +object kind) and increase block-level fragmentation. Scale the limit +proportionally so that gctest passes with such configurations. + +Assisted-by: Claude Fable 5 +--- + tests/test.c | 7 +++++++ + 1 file changed, 7 insertions(+) + +diff --git a/tests/test.c b/tests/test.c +index 0ae3f95b..b62699fa 100644 +--- a/tests/test.c ++++ b/tests/test.c +@@ -1753,6 +1753,13 @@ void check_heap_stats(void) + # endif + # ifdef MEMORY_SANITIZER + max_heap_sz += max_heap_sz / 4; ++# endif ++# if defined(GC_TINY_FREELISTS) && GC_TINY_FREELISTS > 25 ++ /* Larger thread-local free lists cause each thread to retain */ ++ /* more nearly-empty heap blocks (up to one per size class and */ ++ /* object kind) and increase block-level fragmentation, so */ ++ /* allow the heap to grow proportionally. */ ++ max_heap_sz = max_heap_sz / 25 * GC_TINY_FREELISTS; + # endif + max_heap_sz *= n_tests; + # if defined(USE_MMAP) || defined(MSWIN32) +-- +2.54.0 +