Andreas Klebinger pushed to branch wip/apk/rts-flag-fixes at Glasgow Haskell Compiler / GHC Commits: 5204beb0 by Andreas Klebinger at 2026-09-21T10:31:43+00:00 RTS flag: Change setting handling and allowed maximums. GC Generations: Now unconditionally bounded at 64. It was already bounded to 64 for the non-moving RTS but the bound is new for the threaded RTS. Nursery chunking: Instead of overwriting a user set value now we always check if a setting was explicitly set. If so we try to respect it. Max value for RTS flags during parsing: When using decodeSize we now use more accurate maximums. Fixing a number of bugs where we used word size based maximums unconditionally for 32bit fields. GC Generations: We only ever supported 64 generations in the non-threaded RTS so I made this the new explicit maximum. Fixes #27830 #27829 - - - - - 9 changed files: - + changelog.d/T27830-rts-flag-bounds - docs/users_guide/runtime_control.rst - rts/RtsFlags.c - rts/include/rts/Flags.h - rts/include/rts/storage/GC.h - rts/sm/GC.c - rts/sm/Storage.c - testsuite/tests/rts/rtsflags001.stderr-ws-32 - testsuite/tests/rts/rtsflags001.stderr-ws-64 Changes: ===================================== changelog.d/T27830-rts-flag-bounds ===================================== @@ -0,0 +1,5 @@ +section: rts +synopsis: Fix bugs in RTS flags handling: RTS Flags now respects -n even with large -A args and errors give more accurate bounds on valid value. +issues: #27830 #27829 +mrs: !16713 + ===================================== docs/users_guide/runtime_control.rst ===================================== @@ -635,10 +635,11 @@ performance. Set the number of generations used by the garbage collector. The default of 2 seems to be good, but the garbage - collector can support any number of generations. Anything larger + collector can in theory support any number of generations. Anything larger than about 4 is probably not a good idea unless your program runs for a *long* time, because the oldest generation will hardly ever - get collected. + get collected. Currently the RTS hardcodes a limit of 64 generations which + seems plenty. Specifying 1 generation with ``+RTS -G1`` gives you a simple 2-space collector, as you would expect. In a 2-space collector, the :rts-flag:`-A ===================================== rts/RtsFlags.c ===================================== @@ -141,8 +141,8 @@ void initRtsFlagsDefaults(void) if (maxStkSize == 0) maxStkSize = 8 * 1024 * 1024; // GcFlags.maxStkSiz is 32-bit, so we need to cap to prevent overflow (#17019) - else if (maxStkSize > UINT32_MAX * sizeof(W_)) - maxStkSize = UINT32_MAX * sizeof(W_); + else if (maxStkSize > (StgWord64) UINT32_MAX * sizeof(W_)) + maxStkSize = (StgWord64) UINT32_MAX * sizeof(W_); RtsFlags.GcFlags.statsFile = NULL; RtsFlags.GcFlags.giveStats = NO_GC_STATS; @@ -155,7 +155,7 @@ void initRtsFlagsDefaults(void) /* -A default. See #16499 for a discussion about the tradeoffs */ RtsFlags.GcFlags.minAllocAreaSize = (4 * 1024 * 1024) / BLOCK_SIZE; RtsFlags.GcFlags.largeAllocLim = 0; /* defaults to minAllocAreasize */ - RtsFlags.GcFlags.nurseryChunkSize = 0; + RtsFlags.GcFlags.nurseryChunkSize = -1; /* -1: Nothing set by user, normalized to off by default, 0: off explicitly, <n>: explicit size*/ RtsFlags.GcFlags.minOldGenSize = (1024 * 1024) / BLOCK_SIZE; /* -O default */ RtsFlags.GcFlags.maxHeapSize = 0; /* off by default */ RtsFlags.GcFlags.heapLimitGrace = (1024 * 1024); @@ -1239,19 +1239,19 @@ error = true; if (rts_argv[arg][2] == 'L') { RtsFlags.GcFlags.largeAllocLim = decodeSize(rts_argv[arg], 3, 2*BLOCK_SIZE, - HS_INT_MAX) / BLOCK_SIZE; + (StgWord64) HS_WORD32_MAX * BLOCK_SIZE) / BLOCK_SIZE; } else { // minimum two blocks in the nursery, so that we have one // to grab for allocate(). RtsFlags.GcFlags.minAllocAreaSize = decodeSize(rts_argv[arg], 2, 2*BLOCK_SIZE, - HS_INT_MAX) / BLOCK_SIZE; + (StgWord64) HS_WORD32_MAX * BLOCK_SIZE) / BLOCK_SIZE; } break; case 'n': OPTION_UNSAFE; RtsFlags.GcFlags.nurseryChunkSize - = decodeSize(rts_argv[arg], 2, 2*BLOCK_SIZE, HS_INT_MAX) + = decodeSize(rts_argv[arg], 2, 0, ((StgWord64) HS_INT32_MAX) * BLOCK_SIZE) / BLOCK_SIZE; break; @@ -1302,7 +1302,7 @@ error = true; case 'K': OPTION_UNSAFE; RtsFlags.GcFlags.maxStkSize = - decodeSize(rts_argv[arg], 2, 0, UINT32_MAX) + decodeSize(rts_argv[arg], 2, 0, (StgWord64) UINT32_MAX * sizeof(W_)) / sizeof(W_); break; @@ -1311,22 +1311,22 @@ error = true; switch(rts_argv[arg][2]) { case 'c': RtsFlags.GcFlags.stkChunkSize = - decodeSize(rts_argv[arg], 3, sizeof(W_), HS_WORD_MAX) + decodeSize(rts_argv[arg], 3, sizeof(W_), (StgWord64)HS_WORD32_MAX * sizeof(W_)) / sizeof(W_); break; case 'b': RtsFlags.GcFlags.stkChunkBufferSize = - decodeSize(rts_argv[arg], 3, sizeof(W_), HS_WORD_MAX) + decodeSize(rts_argv[arg], 3, sizeof(W_), (StgWord64)HS_WORD32_MAX * sizeof(W_)) / sizeof(W_); break; case 'i': RtsFlags.GcFlags.initialStkSize = - decodeSize(rts_argv[arg], 3, sizeof(W_), HS_WORD_MAX) + decodeSize(rts_argv[arg], 3, sizeof(W_), (StgWord64)HS_WORD32_MAX * sizeof(W_)) / sizeof(W_); break; default: RtsFlags.GcFlags.initialStkSize = - decodeSize(rts_argv[arg], 2, sizeof(W_), HS_WORD_MAX) + decodeSize(rts_argv[arg], 2, sizeof(W_), (StgWord64)HS_WORD32_MAX * sizeof(W_)) / sizeof(W_); break; } @@ -1339,7 +1339,7 @@ error = true; decodeSize(rts_argv[arg], 8, BLOCK_SIZE, HS_WORD_MAX); } else { RtsFlags.GcFlags.maxHeapSize = - decodeSize(rts_argv[arg], 2, BLOCK_SIZE, HS_WORD_MAX) + decodeSize(rts_argv[arg], 2, BLOCK_SIZE, (StgWord64) HS_WORD32_MAX * BLOCK_SIZE) / BLOCK_SIZE; // user give size in *bytes* but "maxHeapSize" is in // *blocks* @@ -1388,8 +1388,9 @@ error = true; #endif case 'G': OPTION_UNSAFE; - RtsFlags.GcFlags.generations = - decodeSize(rts_argv[arg], 2, 1, HS_INT_MAX); + // Capped at 64 by hardcoded array size in non-threaded RTS. + RtsFlags.GcFlags.generations = (uint32_t) + decodeSize(rts_argv[arg], 2, 1, GC_MAX_GENERATIONS); break; case 'H': @@ -1398,7 +1399,7 @@ error = true; RtsFlags.GcFlags.heapSizeSuggestionAuto = true; } else { RtsFlags.GcFlags.heapSizeSuggestion = (uint32_t) - (decodeSize(rts_argv[arg], 2, BLOCK_SIZE, HS_WORD_MAX) + (decodeSize(rts_argv[arg], 2, BLOCK_SIZE, (StgWord64) HS_WORD32_MAX * BLOCK_SIZE) / BLOCK_SIZE); } break; @@ -1407,7 +1408,7 @@ error = true; OPTION_UNSAFE; RtsFlags.GcFlags.minOldGenSize = (uint32_t)(decodeSize(rts_argv[arg], 2, BLOCK_SIZE, - HS_WORD_MAX) + (StgWord64) HS_WORD32_MAX * BLOCK_SIZE) / BLOCK_SIZE); break; @@ -1994,9 +1995,25 @@ static void normaliseRtsOpts (void) RtsFlags.GcFlags.minAllocAreaSize = RtsFlags.GcFlags.maxHeapSize; } - // If we have -A16m or larger, use -n4m. - if (RtsFlags.GcFlags.minAllocAreaSize >= (16*1024*1024) / BLOCK_SIZE) { - RtsFlags.GcFlags.nurseryChunkSize = (4*1024*1024) / BLOCK_SIZE; + // If no explicit size was given, and we have -A16m or larger, use -n4m. + if (RtsFlags.GcFlags.nurseryChunkSize == -1) { + if (RtsFlags.GcFlags.minAllocAreaSize >= (16*1024*1024) / BLOCK_SIZE) { + RtsFlags.GcFlags.nurseryChunkSize = (4*1024*1024) / BLOCK_SIZE; + } else { + RtsFlags.GcFlags.nurseryChunkSize = 0; + } + } + else if ( RtsFlags.GcFlags.nurseryChunkSize > 0 && RtsFlags.GcFlags.nurseryChunkSize < 2) { + errorBelch("nursery chunk size (-n) must be at least %" FMT_Word " large.", (W_)(2 * BLOCK_SIZE)); + errorBelch("Disabling nursery chunking"); + RtsFlags.GcFlags.nurseryChunkSize = 0; + } + // If the user gave a chunk size respect it, unless it's larger than + // minimum allocation area. + else if ( (StgWord64) RtsFlags.GcFlags.nurseryChunkSize > (StgWord64) RtsFlags.GcFlags.minAllocAreaSize) { + errorBelch("nursery chunk size (-n) is bigger than minimum alloc area size (-A)"); + errorBelch("Disabling nursery chunking"); + RtsFlags.GcFlags.nurseryChunkSize = 0; } if (RtsFlags.ParFlags.parGcLoadBalancingGen == ~0u) { @@ -2207,9 +2224,7 @@ decodeSize(const char *flag, uint32_t offset, StgWord64 min, StgWord64 max) val = (StgWord64)m; if (m < 0 || val < min || val > max) { - // printf doesn't like 64-bit format specs on Windows - // apparently, so fall back to unsigned long. - errorBelch("error in RTS option %s: size outside allowed range (%" FMT_Word " - %" FMT_Word ")", flag, (W_)min, (W_)max); + errorBelch("error in RTS option %s: size outside allowed range (%" FMT_Word64 " - %" FMT_Word64 ")", flag, (StgWord64)min, (StgWord64)max); stg_exit(EXIT_FAILURE); } ===================================== rts/include/rts/Flags.h ===================================== @@ -45,7 +45,7 @@ typedef struct _GC_FLAGS { uint32_t maxHeapSize; /* in *blocks* */ uint32_t minAllocAreaSize; /* in *blocks* */ uint32_t largeAllocLim; /* in *blocks* */ - uint32_t nurseryChunkSize; /* in *blocks* */ + int32_t nurseryChunkSize; /* in *blocks*, int to distinguish default off, vs explicit off */ uint32_t minOldGenSize; /* in *blocks* */ uint32_t heapSizeSuggestion; /* in *blocks* */ bool heapSizeSuggestionAuto; @@ -80,7 +80,7 @@ typedef struct _GC_FLAGS { * to handle the exception before we * raise it again. */ - StgWord heapLimitGrace; /* units: *blocks* + StgWord heapLimitGrace; /* units: *bytes* * After a HeapOverflow exception has * been raised, how much extra space is * given to the thread to handle the ===================================== rts/include/rts/storage/GC.h ===================================== @@ -52,6 +52,8 @@ // ie. StgWord. Note that we have had some tricky int overflows in a // couple of cases caused by using ints rather than longs (e.g. #5086) +#define GC_MAX_GENERATIONS 64 + typedef StgWord memcount; typedef struct nursery_ { ===================================== rts/sm/GC.c ===================================== @@ -154,7 +154,7 @@ static Condition gc_exit_leave_now_cv; #else // THREADED_RTS // Must match the alignment of gen_workspace. -StgWord8 the_gc_thread[sizeof(gc_thread) + 64 * sizeof(gen_workspace)] +StgWord8 the_gc_thread[sizeof(gc_thread) + GC_MAX_GENERATIONS * sizeof(gen_workspace)] ATTRIBUTE_ALIGNED(GEN_WORKSPACE_ALIGNMENT); #endif // THREADED_RTS ===================================== rts/sm/Storage.c ===================================== @@ -273,7 +273,7 @@ void storageAddCapabilities (uint32_t from, uint32_t to) if (RtsFlags.GcFlags.nurseryChunkSize == 0) { new_n_nurseries = to; } else { - memcount total_alloc = to * RtsFlags.GcFlags.minAllocAreaSize; + memcount total_alloc = to * (size_t) RtsFlags.GcFlags.minAllocAreaSize; new_n_nurseries = stg_max(to, total_alloc / RtsFlags.GcFlags.nurseryChunkSize); } ===================================== testsuite/tests/rts/rtsflags001.stderr-ws-32 ===================================== @@ -1 +1 @@ -rtsflags001: error in RTS option -H0m: size outside allowed range (4096 - 4294967295) +rtsflags001: error in RTS option -H0m: size outside allowed range (4096 - 17592186040320) ===================================== testsuite/tests/rts/rtsflags001.stderr-ws-64 ===================================== @@ -1 +1 @@ -rtsflags001: error in RTS option -H0m: size outside allowed range (4096 - 18446744073709551615) +rtsflags001: error in RTS option -H0m: size outside allowed range (4096 - 17592186040320) View it on GitLab: https://gitlab.haskell.org/ghc/ghc/-/commit/5204beb0a77d0cc2d5bc0023497a568f... -- View it on GitLab: https://gitlab.haskell.org/ghc/ghc/-/commit/5204beb0a77d0cc2d5bc0023497a568f... You're receiving this email because of your account on gitlab.haskell.org. Manage all notifications: https://gitlab.haskell.org/-/profile/notifications | Help: https://gitlab.haskell.org/help