Skip to content

Commit aaaacf2

Browse files
authored
Backport OCaml ocaml#13736 to 5.3.0-semgrep (#26)
## Summary Backport [ocaml#13736](ocaml#13736) to `5.3.0-semgrep`. Synchronous major collections advance the major GC work counter without corresponding allocation. After `Gc.major`, `Gc.full_major`, or `Gc.compact`, the work counter can therefore remain far ahead of the allocation counter, suppressing subsequent automatic major GC slices until allocation catches up. On a large heap this can cause rapid heap growth or OOM after an explicit collection. The upstream fix resets the allocation and work counters to the same logical clock after synchronous major GC work. This is relevant to Semgrep because its memory-limit handler calls `Gc.compact` before reraising an OOM or memory-limit exception. ## Backport Notes The three runtime changes apply to OCaml 5.3 without adaptation and match upstream ocaml#13736: - Add `caml_reset_major_pacing`. - Invoke it after explicit major collection work, including each `Gc.full_major` and `Gc.compact` cycle. - Omit the conflicting upstream `Changes` entry, since this is a Semgrep-fork backport. No compiler or Semgrep-specific code is changed. ## Validation - Clean Flambda bootstrap of the exact branch: `./configure --enable-flambda && make -j8` - Confirmed the resulting compiler reports `flambda: true`. - Compiled an identical post-compaction pacing workload with unpatched and patched 5.3 runtimes. The workload compacts a 320 MiB live heap, drops it, and then performs short-lived major allocations: - Unpatched: reached the 768 MiB guard after 17,900 iterations; heap reached 801 MiB with only 2 automatic major cycles. - Patched: completed 100,000 iterations; final heap returned to 0 MiB with 10,169 automatic major cycles.
2 parents 73ddd5e + 0990350 commit aaaacf2

3 files changed

Lines changed: 32 additions & 0 deletions

File tree

runtime/caml/major_gc.h

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,11 @@ void caml_darken_cont(value);
4242
void caml_mark_root(value, value*);
4343
void caml_empty_mark_stack(void);
4444
void caml_finish_major_cycle(int force_compaction);
45+
/* Reset any internal accounting the GC uses to set collection pacing.
46+
* For use at times when we have disturbed the usual pacing, for
47+
* example, after any synchronous major collection.
48+
*/
49+
void caml_reset_major_pacing(void);
4550
#ifdef DEBUG
4651
int caml_mark_stack_is_empty(void);
4752
#endif

runtime/gc_ctrl.c

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -242,6 +242,7 @@ static caml_result gc_major_res(int force_compaction)
242242
caml_gc_log ("Major GC cycle requested");
243243
caml_empty_minor_heaps_once();
244244
caml_finish_major_cycle(force_compaction);
245+
caml_reset_major_pacing();
245246
caml_result result = caml_process_pending_actions_res();
246247
CAML_EV_END(EV_EXPLICIT_GC_MAJOR);
247248
return result;
@@ -262,6 +263,7 @@ static caml_result gc_full_major_res(void)
262263
currently-unreachable object to be collected. */
263264
for (int i = 0; i < 3; i++) {
264265
caml_finish_major_cycle(0);
266+
caml_reset_major_pacing();
265267
caml_result res = caml_process_pending_actions_res();
266268
if (caml_result_is_exception(res)) return res;
267269
}
@@ -297,6 +299,7 @@ CAMLprim value caml_gc_compaction(value v)
297299
why this needs three iterations. */
298300
for (int i = 0; i < 3; i++) {
299301
caml_finish_major_cycle(i == 2);
302+
caml_reset_major_pacing();
300303
result = caml_process_pending_actions_res();
301304
if (caml_result_is_exception(result)) break;
302305
}

runtime/major_gc.c

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -576,6 +576,30 @@ static inline intnat diffmod (uintnat x1, uintnat x2)
576576
return (intnat) (x1 - x2);
577577
}
578578

579+
/* Reset the work and alloc counters to be equal to each other, by
580+
* setting them both equal to the "larger" (in the wrapping-around
581+
* sense we are using here for work_counter and alloc_counter).
582+
*
583+
* For use at times when we have disturbed the major GC from its usual
584+
* pacing and tempo, for example, after any synchronous major
585+
* collection.
586+
*/
587+
588+
void caml_reset_major_pacing(void)
589+
{
590+
bool res;
591+
do {
592+
uintnat alloc = atomic_load(&alloc_counter);
593+
uintnat work = atomic_load(&work_counter);
594+
uintnat target = alloc;
595+
if (diffmod(work, alloc) > 0) {
596+
target = work;
597+
}
598+
res = (atomic_compare_exchange_strong(&alloc_counter, &alloc, target) &&
599+
atomic_compare_exchange_strong(&work_counter, &work, target));
600+
} while (!res);
601+
}
602+
579603
/* The [log_events] parameter is used to disable writing to the ring for two
580604
reasons:
581605
1. To prevent spamming the ring with numerous events generated during

0 commit comments

Comments
 (0)