Marge Bot pushed to branch master at Glasgow Haskell Compiler / GHC Commits: c406135c by Andreas Klebinger at 2026-10-01T06:57:28-04:00 RTS: Update flag parsing/handling: Using 0 as argument to any of these flags now resets the flags setting to default: - ``-n0`` disables nursery chunking. - ``-K0`` (or a bare ``-K``) removes the stack size limit. - ``-M0`` removes the heap size limit. - ``-H0`` resets the suggested heap size, including the nursery scaling set by ``-H``. - ``-AL0`` makes the large object allocation limit follow ``-A`` again. GC Generations are now limited to 64. -n larger than -A now emits a warning and disables chunking. Fixes #27830 #27829 - - - - - 10 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/all.T - testsuite/tests/rts/rtsflags001.stderr-ws-32 - testsuite/tests/rts/rtsflags001.stderr-ws-64 Changes: ===================================== changelog.d/T27830-rts-flag-bounds ===================================== @@ -0,0 +1,17 @@ +section: rts +synopsis: Update RTS flag handling: It now respects user set -n values, allows resetting of more settings to defaults and gives more accurate bounds in errors. +description: + Using 0 as argument to any of these flags now resets the flags setting to default: + + - ``-n0`` disables nursery chunking. + - ``-K0`` (or a bare ``-K``) removes the stack size limit. + - ``-M0`` removes the heap size limit. + - ``-H0`` resets the suggested heap size, including the nursery scaling set by ``-H``. + - ``-AL0`` makes the large object allocation limit follow ``-A`` again. + + A nursery chunk size (``-n``) larger than the allocation area (``-A``) now prints a + warning and disables nursery chunking. Previously every nursery was enlarged to the chunk size. + + The number of generations (``-G``) is now limited to 64. +issues: #27830 #27829 +mrs: !16713 ===================================== docs/users_guide/runtime_control.rst ===================================== @@ -493,7 +493,7 @@ performance. Sets the limit on the total size of "large objects" (objects larger than about 3KB) that can be allocated before a GC is triggered. By default this limit is the same as the :rts-flag:`-A <-A - ⟨size⟩>` value. + ⟨size⟩>` value. ``-AL0`` restores the default. Large objects are not allocated from the normal allocation area set by the ``-A`` flag, which is why there is a separate limit for @@ -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 @@ -731,6 +732,9 @@ performance. the default small ``-A`` value is suboptimal, as it can be in programs that create large amounts of long-lived data. + ``-H0`` resets both the size suggestion, and dynamic scaling enablied by a + bare ``-H``. + .. rts-flag:: -I ⟨seconds⟩ :default: 0.3 seconds in the threaded runtime, 0 in the non-threaded runtime @@ -886,7 +890,7 @@ performance. of the program. The only reason for having this option is to stop the heap growing without bound and filling up all the available swap space, which at the least will result in the program being summarily - killed by the operating system. + killed by the operating system. ``-M0`` resets any limit set by ``-M<size>``. The maximum heap size also affects other garbage collection parameters: when the amount of live data in the heap exceeds a ===================================== rts/RtsFlags.c ===================================== @@ -101,7 +101,8 @@ static int openStatsFile ( char *filename, const char *FILENAME_FMT, FILE **file_ret); static StgWord64 decodeSize ( - const char *flag, uint32_t offset, StgWord64 min, StgWord64 max); + const char *flag, uint32_t offset, StgWord64 min, StgWord64 max, + bool allow_zero); static double parseDouble ( const char *arg, bool *error); @@ -141,8 +142,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 +156,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); @@ -331,7 +332,7 @@ usage_text[] = { " --copying-gc", " Selects the copying garbage collector to manage all generations.", "", -" -K<size> Sets the maximum stack size (default: 80% of the heap)", +" -K<size> Sets the maximum stack size (0 = unlimited, default: 80% of the heap)", " e.g.: -K32k -K512k -K8M", " -ki<size> Sets the initial thread stack size (default 1k) e.g.: -ki4k -ki2m", " -kc<size> Sets the stack chunk size (default 32k)", @@ -349,9 +350,9 @@ usage_text[] = { " memory controlled by this factor (higher is slower). Setting the factor", " to 0 means memory is not returned.", " (default 4.0)", -" -n<size> Allocation area chunk size (0 = disabled, default: 0)", +" -n<size> Allocation area chunk size (0 = disabled, default: 0, 4m for -A >= 16m)", " -O<size> Sets the minimum size of the old generation (default 1M)", -" -M<size> Sets the maximum heap size (default unlimited) e.g.: -M256k -M1G", +" -M<size> Sets the maximum heap size (0 = unlimited, default unlimited) e.g.: -M256k -M1G", " -H<size> Sets the minimum heap size (default 0M) e.g.: -H24m -H1G", " -xb<addr> Sets the address from which a suitable start for the heap memory", " will be searched from. This is useful if the default address", @@ -1239,19 +1240,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, true) / 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, false) / 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, 2*BLOCK_SIZE, ((StgWord64) HS_INT32_MAX) * BLOCK_SIZE, true) / BLOCK_SIZE; break; @@ -1301,9 +1302,14 @@ error = true; case 'K': OPTION_UNSAFE; - RtsFlags.GcFlags.maxStkSize = - decodeSize(rts_argv[arg], 2, 0, UINT32_MAX) - / sizeof(W_); + // -K and -K0 mean unlimited. + if (rts_argv[arg][2] == '\0') { + RtsFlags.GcFlags.maxStkSize = 0; + } else { + RtsFlags.GcFlags.maxStkSize = + decodeSize(rts_argv[arg], 2, sizeof(W_), (StgWord64) UINT32_MAX * sizeof(W_), true) + / sizeof(W_); + } break; case 'k': @@ -1311,22 +1317,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_), false) / 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_), false) / 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_), false) / 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_), false) / sizeof(W_); break; } @@ -1336,10 +1342,10 @@ error = true; OPTION_UNSAFE; if (0 == strncmp("grace=", rts_argv[arg] + 2, 6)) { RtsFlags.GcFlags.heapLimitGrace = - decodeSize(rts_argv[arg], 8, BLOCK_SIZE, HS_WORD_MAX); + decodeSize(rts_argv[arg], 8, BLOCK_SIZE, HS_WORD_MAX, false); } 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, true) / BLOCK_SIZE; // user give size in *bytes* but "maxHeapSize" is in // *blocks* @@ -1388,8 +1394,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, false); break; case 'H': @@ -1398,8 +1405,12 @@ 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, true) / BLOCK_SIZE); + // -H0 resets to the default of no suggestion. + if (RtsFlags.GcFlags.heapSizeSuggestion == 0) { + RtsFlags.GcFlags.heapSizeSuggestionAuto = false; + } } break; @@ -1407,7 +1418,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, false) / BLOCK_SIZE); break; @@ -1852,14 +1863,14 @@ error = true; case 'q': OPTION_UNSAFE; RtsFlags.GcFlags.allocLimitGrace - = decodeSize(rts_argv[arg], 3, BLOCK_SIZE, HS_INT_MAX) + = decodeSize(rts_argv[arg], 3, BLOCK_SIZE, HS_INT_MAX, false) / BLOCK_SIZE; break; case 'r': OPTION_UNSAFE; RtsFlags.GcFlags.addressSpaceSize - = decodeSize(rts_argv[arg], 3, MBLOCK_SIZE, HS_WORD64_MAX); + = decodeSize(rts_argv[arg], 3, MBLOCK_SIZE, HS_WORD64_MAX, false); break; default: @@ -1994,9 +2005,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("warning: nursery chunk size (-n) is bigger than minimum alloc area size (-A), " + "disabling nursery chunking"); + RtsFlags.GcFlags.nurseryChunkSize = 0; } if (RtsFlags.ParFlags.parGcLoadBalancingGen == ~0u) { @@ -2144,10 +2171,14 @@ static void initStatsFile (FILE *f) /* ----------------------------------------------------------------------------- * decodeSize: parse a string containing a size, like 300K or 1.2M + * + * The result must lie within [min, max]. If allow_zero is set an explicitly + * given zero (e.g. "-M0") is accepted as well. -------------------------------------------------------------------------- */ static StgWord64 -decodeSize(const char *flag, uint32_t offset, StgWord64 min, StgWord64 max) +decodeSize(const char *flag, uint32_t offset, StgWord64 min, StgWord64 max, + bool allow_zero) { const char *s; StgDouble m; @@ -2206,10 +2237,14 @@ 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); + // Only a explicit zero-digit is accepted for allow_zero. + bool explicit_zero = allow_zero && *s != '\0' && m == 0; + if (m < 0 || (val < min && !explicit_zero) || val > max) { + if (allow_zero && min > 0) { + errorBelch("error in RTS option %s: size outside allowed range (0 or %" FMT_Word64 " - %" FMT_Word64 ")", flag, (StgWord64)min, (StgWord64)max); + } else { + 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/all.T ===================================== @@ -171,7 +171,7 @@ test('T3424', # Test for out-of-range heap size test('rtsflags001', [ only_ways(['normal']), exit_code(1), - extra_run_opts('+RTS -H0m -RTS'), + extra_run_opts('+RTS -H1k -RTS'), js_skip # JS backend uses its own rts. ], compile_and_run, ['']) ===================================== 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 -H1k: size outside allowed range (0 or 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 -H1k: size outside allowed range (0 or 4096 - 17592186040320) View it on GitLab: https://gitlab.haskell.org/ghc/ghc/-/commit/c406135cc3aa0379caa402e16e1a1ac2... -- View it on GitLab: https://gitlab.haskell.org/ghc/ghc/-/commit/c406135cc3aa0379caa402e16e1a1ac2... 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