support concurrent immix GC - #61215
Conversation
3cbef9a to
8db1d73
Compare
|
I'd like some documentation on the memory model assumptions of the pre barrier, particularly with multiple concurrent writers. |
|
I think this change implies that all pointer references need to become |
|
Not for this PR, but these barrier can be made faster using our asymmetric thread fences. |
|
In general, I'm not exactly sure how SATB works in the presence of weak atomic stores. It seems that Java and Go (https://research.swtch.com/gomm) don't support as many weak atomics as C++/julia (specifically it looks like they only have Sequential consistent atomics). If this is in fact true, we will likely need some input from @steveblackburn on whether it is possible to support concurrent GC within our current memory model. (If it isn't, we may be able to strengthen atomics of pointers to sequential consistent within the runtime and before we finalize our writebarriers) |
|
Some notes from today's call:
|
|
@Keno can you give the updated analyzegc a check? |
It's on the right track, but as written in this PR is a little too slop-y and not quite correct. |
fbd3837 to
c3f295e
Compare
fe0a24d to
578efba
Compare
Relocate the stock GC write barrier so that `jl_gc_wb(parent, newval)` runs before the field store rather than after it. Every write-barrier call site across the runtime and the codegen now emit their barrier ahead of the store The stock generational barrier reads only the parent and new-value GC tags and never the old field, so moving it ahead of the (non-safepointing) store is behavior-neutral for the stock collector while establishing the pre-write barrier interface that a concurrent collector requires. Add `jl_gc_write`/`jl_gc_write_atomic` helper macros that perform the barrier before the store, and make stock `jl_gc_wb` treat a `NULL` child as a no-op, since the barrier now also fires when a field is cleared. This should make #61215 a lot easier to land. Coauthored by Fable 5 --------- Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
cfbcd9f to
697a811
Compare
|
The deletion barrier is just a normal write barrier. I guess we could have a specific "deletion barrier", but not sure it gains much. |
- 69d4bef support concurrent immix - bc2a5e7 fix makefile - 569a277 Update mmtk to allow pinning roots in concurrent ix. Move SweepVMSpecific to process_weak_refs. - ca3b463 fixes - 4a5cd1a Update mmtk-core. Test ConcurrentImmix in CI - 0173f1c Turn off log for CI debug builds - c1106e1 Support sticky immix with pre write barrier - 0d7c5a9 Remove MMTK_PLAN env var before initialization - 1e29150 Use wrapping_sub in gc trigger to avoid overflow - c5c9469 Update FFI. Snapshot gcstacks during initial marking pause. - 25ec8a5 Conditional compilation for scan gc stacks based on plans - e70ec7c Adapt to dynamic side metadata address (JuliaLang#299) - 754fb76 Update mmtk-core - 6781260 Declare side metadata base address in the Julia repo (JuliaLang#306) - 64795a9 Cargo fmt - 5d3bb0a mmtk: flush mutator from binding's scan_roots_in_mutator_thread - 0c82f93 Treat weak ref as strong in concurrent marking - 2ba17c0 Remove all_tasks. Lazily scan gcstacks before a task resumes - 54b70bf No longer snapshot current task. Fix a race condition for scanning a task and snapshotting a task in the resume hook. - ef97cc3 Remove sticky immix pre write - 8d3d882 Fix style check - 9747ebe Reduce the scope of locks for stack snapshots
Add a per-ptls heap.all_tasks small_arraylist that records every task allocated on a thread (not just those holding a pooled stack, as live_tasks does). Tasks are pushed on creation in jl_new_task and jl_init_root_task, and the stock GC sweeps the list during stack-pool sweeping, freeing it alongside live_tasks. This lets a concurrent collector enumerate all tasks during a collection without racing with task creation or destruction. Co-authored-by: Yi Lin <qinsoon@gmail.com> Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Wire up MMTK_PLAN=ConcurrentImmix through Make.inc and the deps/mmtk_julia.mk binding path so the build selects the matching libmmtk_julia variant, and add the MMTK_PLAN_CONCURRENTIMMIX dispatch to the write-barrier header. ConcurrentImmix uses a SATB barrier. Because write barriers are now emitted before the store everywhere (both in the runtime and codegen), the inlined log-bit check works unchanged: when the parent's log bit is set the slow path snapshots its still-current fields, so there is no pre/post barrier distinction. The debugging-only direct-call helper now uses the pre entry point, which is correct for both StickyImmix and ConcurrentImmix. On the runtime side, initialise and sweep the per-thread all_tasks list in the MMTk collector so concurrent marking can enumerate tasks, and disable concurrent marking while generating output until the remaining concurrent-marking issues are resolved. Co-authored-by: Yi Lin <qinsoon@gmail.com> Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
A SATB collector must observe every overwrite of a tracked pointer slot, including pure clears, and must not skip the barrier based on the new value. Master's JuliaLang#62063 placed the insertion barrier before the store but keeps several generational-only optimizations that drop the barrier when SATB still needs it. Close those gaps: Codegen (cgutils.cpp / codegen.cpp): * Do not skip the barrier when the stored value is permalloc (boxed store and the inline-struct multibarrier) — the overwritten old value must still be snapshotted, and types are permalloc. * Emit a deletion barrier before clearing a slot in memoryrefunset! and before overwriting the current task's scope. LLVM passes: * llvm-final-gc-lowering-stock.cpp: treat a NULL child as a no-op (skip its tag load) so a cleared slot's barrier is safe for the stock plan. * llvm-late-gc-lowering.cpp / llvm-julia-licm.cpp: stop eliding/hoisting the barrier. These are disabled unconditionally for now (TEMP); they must be re-gated to fire only for SATB plans once the plan flag is threaded into the codegen passes. Runtime (C): * Add deletion barriers before the clears in jl_memoryrefunset, jl_array_del_end, jl_eqtable_pop, and the method-cache reset in gf.c. Co-authored-by: Yi Lin <qinsoon@gmail.com> Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
for concurrent GC
|
@qinsoon are you handling the updates here or should I? |
Can you help on the comments about write barriers? For the rest, I can fix those. |
2249dbc to
d951e7e
Compare
There was a problem hiding this comment.
Some notes:
jl_restore_excstacklooks like it has another deletion that needs a barrierCargo.tomlseems to be carried over from an old branch.- WeakRefs I assume are intentionally strong until we can implement them properly (seems OK)
The task scanning in general looks a bit suspect. It looks like we need to read the ->ptls field in the object scan to determine whether the stack is available to walk, but ctx_switch can easily proceed to schedule the task for execution before that scan is complete. Did I miss some synchronization somewhere to make that safe?
Claude also seems to think the side-table for stack snapshots should be unnecessary, after some discussion. It thinks we can use object_probable_write to enqueue it like a normal SATB barrier. Is that true?
we scan any running task for concurrent GCs
|
ea4d006 seems to cause some issues -- will need to look at it tomorrow. |
topolarity
left a comment
There was a problem hiding this comment.
Triggers, etc. will need some follow-up but this looks good to merge to me.
Let's get some CI coverage going so that we can build confidence that we're not missing some important concurrency bugs.
Nice work here @oscardssmith and @qinsoon - this is an exciting merge
This just merged as part of JuliaLang/julia#61215 Now to get it covered in CI and solve the bugs so that it can stay green.
Very WIP. Requires mmtk/mmtk-julia#300