Skip to content

support concurrent immix GC - #61215

Merged
oscardssmith merged 18 commits into
JuliaLang:masterfrom
oscardssmith:os/concurrent-immix
Aug 6, 2026
Merged

support concurrent immix GC#61215
oscardssmith merged 18 commits into
JuliaLang:masterfrom
oscardssmith:os/concurrent-immix

Conversation

@oscardssmith

Copy link
Copy Markdown
Member

Very WIP. Requires mmtk/mmtk-julia#300

@oscardssmith
oscardssmith force-pushed the os/concurrent-immix branch 2 times, most recently from 3cbef9a to 8db1d73 Compare March 4, 2026 04:09
Comment thread src/builtins.c Outdated
Comment thread src/array.c Outdated
Comment thread src/datatype.c Outdated
Comment thread src/toplevel.c Outdated
Comment thread src/rtutils.c Outdated
@Keno

Keno commented Mar 7, 2026

Copy link
Copy Markdown
Member

I'd like some documentation on the memory model assumptions of the pre barrier, particularly with multiple concurrent writers.

Comment thread src/julia.h Outdated
@Keno

Keno commented Mar 7, 2026

Copy link
Copy Markdown
Member

I think this change implies that all pointer references need to become _Atomic and the accessed with _relaxed. Otherwise it would be legal for the compiler to tear writes.

@Keno

Keno commented Mar 7, 2026

Copy link
Copy Markdown
Member

Not for this PR, but these barrier can be made faster using our asymmetric thread fences.

@giordano giordano added the GC Garbage collector label Mar 7, 2026
@oscardssmith

Copy link
Copy Markdown
Member Author

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)

@Keno

Keno commented Mar 10, 2026

Copy link
Copy Markdown
Member

Some notes from today's call:

  1. If MMTK has an initial STW that works like a STW barrier, then the SATB barrier is fine. In fact, the acqrel load on the SATB barrier can be dropped
  2. If there is no initial STW synchronization, then the acqrel that's in the there right now is insufficient, but can be made to work with the asymmetric thread fences as mentioned above:
  3. @oscardssmith raised a concern about independent writers to the same memory location with relaxed writes. On the call I said that both threads could observe the new value, but upon further consideration, I think I'm wrong here and this is guaranteed by read-write consistency.

@oscardssmith

Copy link
Copy Markdown
Member Author

@Keno can you give the updated analyzegc a check?

Comment thread src/clangsa/GCChecker.cpp Outdated
Comment thread src/clangsa/GCChecker.cpp Outdated
Comment thread src/clangsa/GCChecker.cpp Outdated
@Keno

Keno commented Mar 12, 2026

Copy link
Copy Markdown
Member

@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.

@qinsoon
qinsoon force-pushed the os/concurrent-immix branch from fbd3837 to c3f295e Compare May 12, 2026 07:51
Comment thread src/gc-wb-stock.h Outdated
@oscardssmith
oscardssmith force-pushed the os/concurrent-immix branch from fe0a24d to 578efba Compare May 25, 2026 22:12
oscardssmith added a commit that referenced this pull request Jun 17, 2026
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>
@oscardssmith
oscardssmith force-pushed the os/concurrent-immix branch from cfbcd9f to 697a811 Compare June 17, 2026 18:33
@oscardssmith
oscardssmith marked this pull request as ready for review June 17, 2026 18:54
@oscardssmith

Copy link
Copy Markdown
Member Author

The deletion barrier is just a normal write barrier. I guess we could have a specific "deletion barrier", but not sure it gains much.

qinsoon and others added 7 commits July 30, 2026 04:30
- 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>
@oscardssmith

Copy link
Copy Markdown
Member Author

@qinsoon are you handling the updates here or should I?

@qinsoon

qinsoon commented Jul 30, 2026

Copy link
Copy Markdown
Member

@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.

@JeffBezanson JeffBezanson added the GC: MMTK MMTK GC integration label Jul 30, 2026
@qinsoon
qinsoon force-pushed the os/concurrent-immix branch from 2249dbc to d951e7e Compare August 3, 2026 08:02

@topolarity topolarity left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Some notes:

  • jl_restore_excstack looks like it has another deletion that needs a barrier
  • Cargo.toml seems 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?

Comment thread src/gc-mmtk/mmtk_julia/src/scanning.rs
Comment thread src/gc-mmtk/mmtk_julia/src/gc_trigger.rs
Comment thread src/gc-mmtk/gc-mmtk.c Outdated
Comment thread src/codegen.cpp
Comment thread src/gc-mmtk/mmtk_julia/src/scanning.rs
@qinsoon

qinsoon commented Aug 6, 2026

Copy link
Copy Markdown
Member

ea4d006 seems to cause some issues -- will need to look at it tomorrow.

@topolarity topolarity left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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

@oscardssmith
oscardssmith merged commit fa2836d into JuliaLang:master Aug 6, 2026
4 of 12 checks passed
topolarity added a commit to JuliaCI/julia-buildkite that referenced this pull request Aug 6, 2026
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.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

GC: MMTK MMTK GC integration GC Garbage collector

Projects

None yet

Development

Successfully merging this pull request may close these issues.

8 participants