458 Commits

Author SHA1 Message Date
Peter Zhu
3f8fe4499e Remove useless if statement around RUBY_GC_HEAP_FREE_SLOTS 2026-07-31 09:31:12 -04:00
Peter Zhu
a2ddafb4f6 [ruby/mmtk] Add obj_need_obj_free_p
We don't need to call obj_free on certain types of objects anymore including
T_FLOAT, T_RATIONAL, T_COMPLEX, and T_OBJECT. It makes freeing those objects
faster.

Benchmark:

    i = 0
    while i < 10_000_000
      Object.new
      i += 1
    end

Before:

    Time (mean ± σ):      1.435 s ±  0.013 s    [User: 1.706 s, System: 1.970 s]
    Range (min … max):    1.418 s …  1.461 s    10 runs

After:

    Time (mean ± σ):      1.230 s ±  0.027 s    [User: 1.335 s, System: 1.743 s]
    Range (min … max):    1.206 s …  1.294 s    10 runs

https://github.com/ruby/mmtk/commit/52f01ba90f
2026-07-30 13:04:53 +00:00
Peter Zhu
d84e57fb38 [ruby/mmtk] Implement large object space
https://github.com/ruby/mmtk/commit/b458664cb1
2026-07-29 19:02:54 +00:00
Peter Zhu
3ae87b772b [ruby/mmtk] Remove MMTK_MAX_OBJ_SIZE
https://github.com/ruby/mmtk/commit/2f925b352a
2026-07-29 19:02:54 +00:00
Peter Zhu
0edb2095b6 Fix double GC during stress
During GC stress, we already run GC in newobj_alloc so running it again
in newobj_bump_pointer_miss will run it again.
2026-07-25 09:09:54 -04:00
Peter Zhu
62f325a955 Fix GC.stress for object allocation
GC stress was broken for object allocation.
2026-07-23 09:32:06 -04:00
Hiroshi SHIBATA
7740e4b7bc Suppress cl.exe C4003 warning in RUBY_DTRACE_GC_HOOK
cl.exe's traditional preprocessor passes __VA_ARGS__ to a nested macro
as a single argument, so the multi-argument probe triggers:

    gc/default/default.c(4395): warning C4003: not enough arguments for function-like macro invocation 'RUBY_DTRACE_GC_SWEEP_PAGE'

Expand the probe invocation through an identity macro so that the
arguments are re-scanned and split properly.
2026-07-17 14:13:55 +09:00
John Hawthorn
f7eca0d41e Batch cleanup geniv before sweep
Previously we needed to check every object to determine whether or not
it might have an entry in the geniv table.

This commit adds a function, rb_gc_vm_weak_table_essential_p, which
allows gc.c to decide which tables to always handle in batch.

MMTK will continue to call rb_gc_vm_weak_table_foreach on all tables.
This only changes the default GC to sweep the "essential" tables first,
but continue to call rb_gc_obj_free_vm_weak_references on other objects.

This makes the geniv table "essental", as it's usually small and needs
to be tested on every type of object. It leaves the fstring table,
symbol table, callinfo table, and overloaded CME table as-is because
those tables are (mostly) larger and are only needed by specific object
types.
2026-07-15 13:54:14 -07:00
Shugo Maeda
8bcc8101be gc: remember re-embedded objects when compaction changes slot size
When gc_move relocates an object to a different slot size,
rb_gc_obj_changed_slot_size may re-embed it: the fields move from the
fields_obj into the object itself and the object references them
directly.  The write-barrier history for those (possibly young) values
lived on the discarded fields_obj, so an old unremembered object then
violates the O->Y invariant, which gc_verify_internal_consistency
reports as a WB miss.  Remember the object on such moves.

Remembering inside rb_gc_obj_changed_slot_size (as #17866 tried) does
not work for this path: gc_move calls it before the destination's age
bits are restored, so RVALUE_OLD_P is false there, and gc_move
overwrites the destination's remembered bits afterwards anyway.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-15 00:11:49 +09:00
Nobuyoshi Nakada
db7fba3217
Make __VA_ARGS__ check mandatory
Since we already require C99 compilers, and autoconf 2.67 or later.
`__VA_ARGS__` is always available.
2026-07-13 20:03:55 +09:00
Hiroshi SHIBATA
97fec3da7e Remove GCC 4.4 specific workarounds
Ruby requires GCC 4.9 or later as noted in configure.ac, so these
blocks guarded by __GNUC__ == 4 && __GNUC_MINOR__ == 4 have been
preprocessed away on every supported compiler for years.
2026-07-13 14:25:10 +09:00
Peter Zhu
8e9038bd3b [ruby/mmtk] Remove fixed slot size
We can use dynamic slot sizes up to MMTK_MAX_OBJ_SIZE.

https://github.com/ruby/mmtk/commit/e07cb0b63e
2026-07-10 11:18:48 +00:00
Nobuyoshi Nakada
df0622320b
[Feature #22135] Remove obsolete ObjectSpace._id2ref 2026-07-09 17:12:21 +09:00
Peter Zhu
583668d5ed Remove gc_bump_pointer_heap
It was leftover and we don't actually need it.
2026-07-08 19:30:28 -04:00
Jean Boussier
dac89892b9 gc: call rb_gc_obj_changed_slot_size for all types
Now that the shape contains the slot capacity, we should update
the shape for all objects when moving them, not just T_OBJECT.
2026-07-08 13:39:59 +02:00
Kunshan Wang
89e7b08a9f
eBPF-based timeline visualization tool (#17190) 2026-07-07 19:07:22 +09:00
Peter Zhu
d9b4ab5b40 Inline GC fastpath in ZJIT for newarray instruction
This commit implements support in ZJIT for inlining GC fast path. The GC
fast path is defined in gc_fastpath.rs for both the default GC and MMTk.

For the default GC, the fast path uses the bump pointer allocator that
was implemented in https://github.com/ruby/ruby/pull/17201. If the bump
pointer region is full, it will bail out and fall back to the slow path.

This GC fast path is used by the newarray instruction in ZJIT in the empty
array case. We can see significant improvements in the following microbenchmark,
which allocates 100 million empty arrays:

    def run(max)
      i = 0
      while i < max
        a = []
        i += 1
      end
    end

    30.times { run(2) }
    run(100_000_000)

This microbenchmark runs 1.5x faster on my machine:

    Benchmark 1: master
      Time (mean ± σ):     583.3 ms ±  10.1 ms    [User: 569.3 ms, System: 12.0 ms]
      Range (min … max):   571.9 ms … 600.0 ms    10 runs

    Benchmark 2: branch
      Time (mean ± σ):     389.5 ms ±   8.2 ms    [User: 375.0 ms, System: 12.1 ms]
      Range (min … max):   380.2 ms … 405.3 ms    10 runs

    Summary
      branch ran
        1.50 ± 0.04 times faster than master
2026-07-07 03:59:42 -04:00
YO4
4ad5d89714 remove unused rb_gc_vm_context->lock
Follow up to GH-PR #16914
2026-07-07 14:23:43 +09:00
Peter Zhu
db7a9a1d6d Dynamically determine max shape capacity
SHAPE_MAX_CAPACITY needed to be below or equal to the maximum capacity
allocatable by the GC otherwise it would crash. This commit decouples that
by adding back max_capacity in the rb_shape_tree_t and adding a new GC
API function rb_gc_impl_max_allocation_size to report the maximum size
allocatable by the GC.
2026-07-06 20:51:54 -04:00
Peter Zhu
870c8d6a50 Use shape bits to store capacity of object
This commit uses 7 shape bits to store the capacity of the object instead
of the heap ID. This allows for slot sizes with up to capacity of 127.
This removes the abstraction leak of slot sizes from the GC, which allows
more flexibility in the GC.

This implementation should currently make no difference for the default
GC as it will end up creating the same root shapes. We can see that there
is basically no change in benchmarks:

--------------  ------------  ------------  ------------  ------------  --------------  -------------  -----------------
bench            master (ms)     RSS (MiB)   branch (ms)     RSS (MiB)  branch 1st itr  master/branch  RSS master/branch
activerecord     99.6 ± 3.9%   75.5 ± 0.3%   98.9 ± 2.4%   74.0 ± 1.3%           0.919          1.007              1.021
chunky-png      328.2 ± 0.6%   83.0 ± 3.9%  336.4 ± 0.7%   87.0 ± 2.2%           0.979          0.976              0.954
erubi-rails     440.4 ± 1.7%  138.1 ± 0.3%  431.0 ± 0.5%  135.2 ± 0.0%           0.992          1.022              1.022
hexapdf         855.6 ± 0.7%  577.0 ± 0.1%  856.7 ± 0.9%  649.0 ± 2.6%           0.993          0.999              0.889
liquid-c         21.6 ± 8.2%  69.6 ± 14.7%   23.2 ± 8.4%  64.6 ± 15.4%           0.993          0.930              1.077
liquid-compile   19.9 ± 7.9%   47.0 ± 5.7%   20.2 ± 8.7%   46.9 ± 5.1%           0.925          0.987              1.001
liquid-render    54.0 ± 2.7%   55.3 ± 8.6%   55.2 ± 2.7%   55.9 ± 9.0%           0.977          0.978              0.990
lobsters        351.6 ± 0.7%  336.8 ± 0.2%  357.0 ± 0.6%  335.4 ± 0.1%           0.997          0.985              1.004
mail             47.5 ± 3.1%   75.6 ± 2.7%   46.8 ± 1.3%   72.3 ± 0.5%           0.974          1.016              1.045
psych-load      829.1 ± 1.2%   55.0 ± 0.4%  832.9 ± 0.5%   55.3 ± 0.7%           1.006          0.995              0.995
railsbench      713.8 ± 0.4%  134.6 ± 1.1%  717.9 ± 1.0%  138.2 ± 1.2%           0.992          0.994              0.974
rubocop          73.1 ± 6.0%  106.7 ± 1.7%   73.2 ± 6.1%  106.4 ± 1.4%           0.997          0.998              1.003
ruby-lsp         69.0 ± 1.2%   78.3 ± 0.2%   67.8 ± 2.3%   74.2 ± 0.0%           1.043          1.017              1.055
sequel           21.7 ± 6.8%   56.1 ± 1.6%   21.0 ± 4.4%   55.9 ± 1.1%           0.873          1.032              1.003
shipit          608.9 ± 1.4%  160.2 ± 0.5%  619.2 ± 1.4%  157.3 ± 0.2%           1.012          0.983              1.018
--------------  ------------  ------------  ------------  ------------  --------------  -------------  -----------------
2026-07-03 21:14:12 -04:00
Matt Valentine-House
71e925cc08 Convert the GC::Profiler storage to a bounded ring buffer
So it could feasibly be enabled and sampled in production
2026-07-01 11:38:09 +01:00
Peter Zhu
c048ba5e91 [ruby/mmtk] Simplify rb_gc_impl_heap_id_for_size
https://github.com/ruby/mmtk/commit/1eb7011040
2026-06-30 05:05:33 +00:00
Peter Zhu
fd12adf718 Make rb_gc_verify_internal_consistency static 2026-06-28 07:23:06 -04:00
Matt Valentine-House
45e0aaf0e3 Seperate compaction from the sweeping time
even when auto-compact is enabled.
2026-06-26 10:46:30 +01:00
Matt Valentine-House
f214b30d46 Shim hrtime for modular GC
similar to how we shim bits.h already
2026-06-26 10:46:30 +01:00
Matt Valentine-House
48516c7af6 Add compaction wall time to GC::Profiler
Assisted-By: devx/46f21e8b-0835-4833-801f-ce5e336dbf61
2026-06-26 10:46:30 +01:00
Matt Valentine-House
e1818cde3d Add mark and sweep wall times to GC::Profiler
Assisted-By: devx/46f21e8b-0835-4833-801f-ce5e336dbf61
2026-06-26 10:46:30 +01:00
Matt Valentine-House
10857e07d2 Split out STW time from total GC pause time.
GC_PAUSE_TIME == GC_STOP_TIME + GC_STW_TIME
2026-06-26 10:46:30 +01:00
Matt Valentine-House
2da4c0e821 [Doc] Document GC::Profiler.raw_data 2026-06-26 10:46:30 +01:00
Matt Valentine-House
d8bf0a28cc Add monotonic wall-clock times to GC::Profiler 2026-06-26 10:46:30 +01:00
John Hawthorn
dc16cb0518 wbcheck: rb_bug on first error on GC 2026-06-22 23:50:22 -07:00
John Hawthorn
300143e0dd wbcheck: GC before fork
This isn't essential for correctness, however it's a natural time to
investigate whether the heap is in a correct state.
2026-06-22 23:50:22 -07:00
Peter Zhu
0ef0856f00 [ruby/mmtk] Implement allocation fast path for Immix
This commit implements a fast path that inlines mmtk_post_alloc for Immix.
The benchmark results show a decent speed up in allocation performance.

    GC.disable

    i = 0
    while i < 10_000_000
      Object.new
      i += 1
    end

Before:

    Time (mean ± σ):     506.3 ms ±   5.0 ms    [User: 442.8 ms, System: 108.8 ms]
    Range (min … max):   497.8 ms … 513.2 ms    10 runs

After:

    Time (mean ± σ):     473.9 ms ±   2.4 ms    [User: 409.4 ms, System: 108.8 ms]
    Range (min … max):   470.4 ms … 478.1 ms    10 runs

https://github.com/ruby/mmtk/commit/663caa25c0
2026-06-22 07:33:48 +00:00
Earlopain
1d493d9054 [DOC] Restore some GC docs
Since modular GC was done, the GC module is missing a bunch of methods.
2026-06-21 11:02:46 +02:00
Peter Zhu
57d5247f91 [ruby/mmtk] Call rb_memerror when OOM
https://github.com/ruby/mmtk/commit/6258cfa315
2026-06-19 07:32:49 +00:00
Matt Valentine-House
6dc5b7f781 If malloc_increase reaches the limit don't immediately gc_rest
Instead lets run a single sweep step per heap and see if we can reduce
malloc_increase enough to carry on without requiring a complete sweep
finish and a big pause
2026-06-18 09:42:24 +01:00
Peter Zhu
45fd5d58f1 Fix malloc counters during GC
https://github.com/ruby/ruby/pull/16919 changed malloc counters to be
reset in `gc_sweep_finish`. However, this introduced two problems:

1. If a GC is triggered due to the `malloc_limit` being hit, then it would
   run a GC with lazy sweeping, during lazy sweeping Ruby code could call
   `xmalloc`, which would immediately finish sweeping by calling `gc_rest`
   because the `malloc_increase` is not reset.
2. When `gc_rest` is called, it immediately completes sweeping. However,
   all of the counters for the freed memory is lost because `malloc_increase`
   is reset at the end. Previously, lazy sweeping would be interleaved
   with Ruby code execution, meaning `xfree` was interleaved with `xmalloc`,
   allowing for more memory to be allocated before `malloc_increase` hits
   `malloc_limit`.

The issue can be reproduced by this script:

   live = []
   500.times do
   live << Array.new(200) { String.new(capacity: 64 * 1024) }
   end
   puts GC.stat

Before:

   {count: 235, ..., minor_gc_count: 196, major_gc_count: 39}

After:

   {count: 196, ..., minor_gc_count: 153, major_gc_count: 43}
2026-06-17 11:00:21 +01:00
Peter Zhu
49fba45570 Remove unused return value of gc_malloc_counters_snapshot 2026-06-17 15:07:50 +09:00
John Hawthorn
1128ac4d2e Only allow garbage_object_p on not-yet-swept objects 2026-06-16 09:43:07 -07:00
John Hawthorn
7f5909f73c Rename pointer_to_heap_p live_object_p 2026-06-16 09:43:07 -07:00
John Hawthorn
6b915ecb1d Stop using rb_gc_pointer_to_heap_p 2026-06-16 09:43:07 -07:00
John Hawthorn
54e673f7fc wbcheck: Implement rb_gc_impl_get_vm_context 2026-06-12 12:06:46 -07:00
John Hawthorn
a1f119fb19 Fix ASAN error on gc_sweep_register_free_slot 2026-06-11 14:07:10 -07:00
Matt Valentine-House
f7047b300f Fix Make syntax in Heredoc Makefile snippet
Co-authored-by: Nobuyoshi Nakada <nobu.nakada@gmail.com>
2026-06-11 19:10:16 +01:00
Matt Valentine-House
4fb0596b01 Enable DTrace USDT GC probes in the modular default GC
When Ruby is built with `--with-modular-gc`, the default GC module is
built with `BUILDING_MODULAR_GC` defined.

The existing code stubbed out `probes.h` and `RUBY_DTRACE_GC_HOOK` in
this build path, so any DTrace script attached against the modular
default GC saw zero GC events.

This PR enables the GC probes in the modular GC library so they fire
when Ruby is built with `--enable-dtrace --with-modular-gc`

To test: build with `./configure --enable-dtrace
--with-modular-gc=/tmp/modgc`, then `make install-modular-gc
MODULAR_GC=default`.

Probes are present in the shared library:

```console
$ sudo bpftrace -l 'usdt:/tmp/modgc/librubygc.default.so:ruby:*'
usdt:/tmp/modgc/librubygc.default.so:ruby:gc__mark__begin
usdt:/tmp/modgc/librubygc.default.so:ruby:gc__mark__end
usdt:/tmp/modgc/librubygc.default.so:ruby:gc__sweep__begin
usdt:/tmp/modgc/librubygc.default.so:ruby:gc__sweep__end
```

They fire under a GC-heavy workload (`test.rb` contains `100000.times { Object.new }; GC.start`):

```console
$ sudo bpftrace -e '
  usdt:/tmp/modgc/librubygc.default.so:ruby:gc__mark__begin  { @mark_begin++ }
  usdt:/tmp/modgc/librubygc.default.so:ruby:gc__mark__end    { @mark_end++ }
  usdt:/tmp/modgc/librubygc.default.so:ruby:gc__sweep__begin { @sweep_begin++ }
  usdt:/tmp/modgc/librubygc.default.so:ruby:gc__sweep__end   { @sweep_end++ }
' -c 'env RUBY_GC_LIBRARY=default ./miniruby --disable=gems test.rb'

@mark_begin: 2
@mark_end: 2
@sweep_begin: 18
@sweep_end: 18
```
2026-06-11 19:10:16 +01:00
Peter Zhu
4f259bc25d Switch to bump pointer allocator for default GC
This commit replaces the per-slot freelist allocator in the default GC
with a bump-pointer allocator over coalesced free regions.

Allocation bumps a per-ractor-cache cursor through a region of contiguous
free slots, advancing to the next region (and then the next page) when
exhausted. Heap selection is unchanged: a request still maps to the
smallest heap whose slot size fits, and only that heap's fixed slot size is
handed out.

Sweeping no longer links every free slot. It coalesces runs of contiguous
free slots into a per-page linked list of regions. Each region's header
(end, next) lives in the first slot.

In benchmarks, the performance results are mostly within the margin of error
between these two implementations. The reason is that while allocation
speed has increased, this change slightly negatively affects sweeping
performance due to the additional work of building the free region.

--------------  ------------  ---------  ------------  ---------  --------------  -------------  -----------------
bench            master (ms)  RSS (MiB)   branch (ms)  RSS (MiB)  branch 1st itr  master/branch  RSS master/branch
activerecord     95.4 ± 4.0%       68.4  100.4 ± 1.9%       71.3           1.021          0.950              0.958
chunky-png      327.7 ± 0.3%       77.2  328.5 ± 0.4%       79.2           0.995          0.997              0.975
erubi-rails     446.5 ± 0.9%      143.6  440.8 ± 2.1%      141.6           1.039          1.013              1.014
hexapdf         865.7 ± 2.1%      344.9  849.6 ± 0.6%      323.6           1.000          1.019              1.066
liquid-c         20.2 ± 7.2%       85.1   19.6 ± 7.8%       85.0           1.068          1.034              1.001
liquid-compile   18.8 ± 5.6%       46.2   18.8 ± 7.8%       47.8           1.005          0.996              0.968
liquid-render    53.5 ± 3.9%       63.2   51.8 ± 3.0%       64.1           1.049          1.033              0.987
lobsters        341.6 ± 2.5%      331.4  340.5 ± 0.9%      330.6           1.086          1.003              1.003
mail             45.5 ± 1.9%       81.1   45.3 ± 2.4%       76.3           0.962          1.006              1.063
psych-load      849.1 ± 1.3%       35.9  860.6 ± 1.2%       35.0           1.060          0.987              1.026
railsbench      714.5 ± 0.5%      136.2  711.3 ± 0.8%      137.6           0.988          1.005              0.989
rubocop          69.5 ± 6.6%      109.3   70.5 ± 6.6%      106.2           0.972          0.986              1.030
ruby-lsp         64.2 ± 2.0%       73.5   64.9 ± 1.5%       76.2           1.005          0.989              0.965
sequel           19.8 ± 4.2%       52.1   19.7 ± 4.3%       55.9           0.885          1.003              0.932
shipit          620.4 ± 4.1%      162.5  607.9 ± 1.4%      160.6           1.148          1.021              1.012
--------------  ------------  ---------  ------------  ---------  --------------  -------------  -----------------
2026-06-11 19:08:30 +09:00
Peter Zhu
8756ceebb9 [ruby/mmtk] Bump mmtk-core
https://github.com/ruby/mmtk/commit/b5b3a1fa9b
2026-06-10 06:42:36 +00:00
Peter Zhu
5859196197 [ruby/mmtk] Use rb_gc_obj_needs_cleanup_p
https://github.com/ruby/mmtk/commit/031785b41c
2026-06-04 02:54:35 +00:00
John Hawthorn
739ec91d5a Run FREEOBJ hook as separate step 2026-06-03 14:06:14 -07:00
Kunshan Wang
cc2547595e Ensure DTrace probes capture all GC marking events
`gc_prof_mark_timer_start` and `gc_prof_mark_timer_stop` include DTrace
hooks for the `MARK_BEGIN` and `MARK_END` events, respectively.
Previously, those probes are only triggered in `gc_marks`.  However,
`gc_marks_continue` and `gc_rest` also contain marking activities, but
are not captured by the probes.

We move the invocation of `gc_prof_mark_timer_start` and
`gc_prof_mark_timer_stop` into `gc_marking_enter` and `gc_marking_exit`
to ensure all marking activities are captured by the probes.
2026-06-01 11:00:17 -07:00