Repository navigation
gc: add VGC - concurrent tri-color mark-and-sweep GC translated from Go - #26739
Conversation
Translate Go's runtime garbage collector to V, available via `-gc vgc`. The VGC implements Go's core GC algorithms: - Size-class based allocation with 67 classes (8B-32KB), translated from Go's runtime/sizeclasses.go - Per-thread allocation caches (mcache equivalent) for lock-free fast path - Central free lists (mcentral equivalent) with span management - Arena-based heap with mmap/VirtualAlloc OS memory backend - Tri-color concurrent mark-and-sweep (mgc.go, mgcmark.go, mgcsweep.go) - Conservative root scanning of thread stacks - Parallel marking using spawn (V threads) - translated from Go's gcDrain - Concurrent sweeping in background spawn worker - Write barrier support (Dijkstra-style insertion barrier) - GC pacing with GOGC-equivalent trigger (heap doubling heuristic) - Tiny allocator for small noscan objects (<16 bytes) New files: - thirdparty/vgc/vgc_platform.h: Platform abstractions (atomics, mmap, spinlocks, size class tables, bitmap ops) with TCC/MSVC/GCC support - vlib/builtin/vgc_d_vgc.c.v: Core types, heap, spans, allocation - vlib/builtin/vgc_gc_d_vgc.c.v: Mark, sweep, work queues, orchestration - vlib/builtin/builtin_d_vgc.c.v: Compile-define anchor file Modified files: - vlib/v2/pref/pref.v: Add GarbageCollectionMode enum with vgc option - vlib/v2/gen/cleanc/fn.v: Inject vgc_init() in main for v2 backend - vlib/v/pref/pref.v: Add .vgc variant to GarbageCollectionMode - vlib/v/gen/c/cmain.v: Inject vgc_init() in main for v1 backend - vlib/builtin/allocation.c.v: Add $if vgc ? branches to malloc/free/etc - vlib/builtin/builtin_notd_gcboehm.c.v: Delegate to VGC when active Usage: v -gc vgc myprogram.v https://claude.ai/code/session_01X5Zy77TKA9GLAwzTS8AhWM
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: f91da827e0
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| } | ||
| if g.pref.gc_mode == .vgc { | ||
| g.writeln('\t// VGC initialization (concurrent tri-color mark-and-sweep)') | ||
| g.writeln('\tvgc_init();') |
There was a problem hiding this comment.
Emit the correct mangled VGC init symbol
In the v1 C backend main prologue this emits vgc_init();, but non-method functions in module builtin are emitted with the builtin__ C prefix (see c_fn_name behavior in vlib/v/gen/c/fn.v), so the generated symbol is builtin__vgc_init. With -gc vgc on the v1 backend, this produces an undefined vgc_init at link time instead of initializing the collector.
Useful? React with 👍 / 👎.
| unsafe { | ||
| vgc_heap.caches[idx].registered = true | ||
| vgc_heap.caches[idx].stack_hi = usize(C.vgc_get_sp()) | ||
| vgc_heap.caches[idx].stack_lo = vgc_heap.caches[idx].stack_hi - usize(8) * 1024 * 1024 // estimate 8MB stack |
There was a problem hiding this comment.
Scan the active stack segment for root discovery
Thread registration records stack_hi as the current SP and then sets stack_lo to stack_hi - 8MB, while root scanning later walks [stack_lo, stack_hi]. On the usual downward-growing stacks (x86_64/aarch64 targets), live frames are above the current SP, so this range misses actual roots and can let reachable objects be collected.
Useful? React with 👍 / 👎.
| C.vgc_atomic_store_u32(&vgc_heap.gc_stop_flag, 0) | ||
| C.vgc_atomic_fence() |
There was a problem hiding this comment.
Preserve tri-color invariant when mutators resume
The collector resumes mutators before marking is finished, but this revision has no call sites for vgc_write_barrier (only its definition exists), so pointer writes performed during concurrent mark are not shaded. That breaks the tri-color invariant and can cause newly referenced live objects to remain white and be swept.
Useful? React with 👍 / 👎.
| uint32_t size = (uint32_t)(i + 1) * 8; | ||
| while (cls < 67 && vgc_class_sizes[cls] < size) { | ||
| cls++; | ||
| } | ||
| vgc_s2c8[i] = (uint8_t)cls; |
There was a problem hiding this comment.
Fix off-by-one in size-to-class lookup tables
The size-class table is initialized with (i + 1)-scaled sizes stored at index i, while lookup uses shifted indices whose first used entry is 1, not 0. This shifts requests into larger classes than necessary (and the same pattern appears in vgc_s2c128), leading to systematic over-allocation and wrong class selection for small objects.
Useful? React with 👍 / 👎.
- Fix C.vgc_mutex_lock/unlock: rename param from `lock` (V keyword) to `lk` - Remove unused `heap_size` variable in vgc_heap_usage() - Revert v1 gen/c/cmain.v and v/pref/pref.v changes - only v2 is needed https://claude.ai/code/session_01X5Zy77TKA9GLAwzTS8AhWM
- Replace `spawn` with C.vgc_start_thread() (pthread/CreateThread) to fix builtin -> sync.threads -> builtin import cycle - Add vgc_start_thread() to platform header for direct OS thread creation - Add v1 compiler support: pref.v (.vgc enum), cmain.v (builtin__vgc_init call) - Fix @sizeof -> sizeof, add @[markused] for vgc_init - Reduce heap struct array sizes to prevent stack overflow on init - Add bench/bench_gc.v benchmark comparing VGC vs Boehm GC Benchmark results (gcc -prod): VGC: small=98-371ms, tree=283-1677ms, array=96-417ms, heap=1.5GB Boehm: small=63-100ms, tree=139-177ms, array=30-50ms, heap=43MB VGC is functional but needs optimization for memory usage and GC pause times. https://claude.ai/code/session_01X5Zy77TKA9GLAwzTS8AhWM
Major performance fixes bringing VGC within 1.2-1.4x of Boehm: - Add address map (vgc_addr_map) for O(1) pointer-to-arena lookup, replacing linear arena scan in vgc_is_heap_ptr/vgc_find_span - Add global arena bounds (vgc_arena_lo/hi) for fast pointer rejection during conservative stack scanning - Skip noscan objects in mark work queue (don't scan objects that can't contain pointers - this is the V compiler advantage) - Fix heap_live to track actual object bytes, not page bytes - Add span recycling: empty spans go to per-page-count free pool, pages decommitted via MADV_DONTNEED, reused on next alloc - Byte-level bitmap scan in alloc (skip 0xFF full bytes) - Byte-level sweep using popcount (alloc & ~mark = garbage) - Reset heap_live from mark bits after GC (fixes drift) - Amortize GC trigger check (only on span fill, not every alloc) - Fix heap_usage to report actual in-use span pages Benchmark (gcc -prod, steady state iterations 2-5): VGC: small=242ms, tree=352ms, array=84ms, heap=128MB Boehm: small=200ms, tree=280ms, array=85ms, heap=43MB Ratio: 1.2x 1.3x 1.0x 3x https://claude.ai/code/session_01X5Zy77TKA9GLAwzTS8AhWM
Generate per-struct pointer bitmaps at compile time (vgc_ptrmap) so the GC can scan only known pointer offsets instead of every word. Adds HEAP_vgc macro, vgc_malloc_typed/vgc_memdup_typed allocators, and precise scan path in vgc_drain_mark_work. Falls back to conservative scanning for complex types (interfaces, sumtypes, nested pointer structs). https://claude.ai/code/session_01X5Zy77TKA9GLAwzTS8AhWM
Translate Go's runtime garbage collector to V, available via
-gc vgc.The VGC implements Go's core GC algorithms:
Go's runtime/sizeclasses.go
New files:
spinlocks, size class tables, bitmap ops) with TCC/MSVC/GCC support
Modified files:
Usage: v -gc vgc myprogram.v
https://claude.ai/code/session_01X5Zy77TKA9GLAwzTS8AhWM