This makes `RBobject` `4B` larger on 32 bit systems
but simplifies the implementation a lot.
[Feature #21353]
Co-authored-by: Jean Boussier <byroot@ruby-lang.org>
We should get the object ID for finalizers in rb_gc_impl_define_finalizer
instead of when we create the finalizer job in make_final_job because
when we are in multi-Ractor mode, object ID needs to walk the references
which allocates an identity hash table. We cannot allocate in make_final_job
because it is in a MMTk worker thread.
922f22a690
The previous implementation assumed `RBasic` size is `2 * sizeof(VALUE)`,
might as well not make assumption and use a proper `sizeof`.
Co-Authored-By: John Hawthorn <john@hawthorn.email>
This commit allows building YJIT and ZJIT simultaneously, a "combo
build". Previously, `./configure --enable-yjit --enable-zjit` failed. At
runtime, though, only one of the two can be enabled at a time.
Add a root Cargo workspace that contains both the yjit and zjit crate.
The common Rust build integration mechanisms are factored out into
defs/jit.mk.
Combo YJIT+ZJIT dev builds are supported; if either JIT uses
`--enable-*=dev`, both of them are built in dev mode.
The combo build requires Cargo, but building one JIT at a time with only
rustc in release build remains supported.
After fork we reset to single ractor mode (which IMO we shouldn't do,
but it requires more work to fix) and so we need to add the pending
object counts back to the main heap.
And get rid of the `obj_to_id_tbl`
It's no longer needed, the `object_id` is now stored inline
in the object alongside instance variables.
We still need the inverse table in case `_id2ref` is invoked, but
we lazily build it by walking the heap if that happens.
The `object_id` concern is also no longer a GC implementation
concern, but a generic implementation.
Co-Authored-By: Matt Valentine-House <matt@eightbitraptor.com>
Ivars will longer be the only thing stored inline
via shapes, so keeping the `iv_index` and `ivptr` names
would be confusing.
Instance variables won't be the only thing stored inline
via shapes, so keeping the `ivptr` name would be confusing.
`field` encompass anything that can be stored in a VALUE array.
Similarly, `gen_ivtbl` becomes `gen_fields_tbl`.
Currently the count of allocated object for a heap is incremented
without regards to parallelism which leads to incorrect counts.
By maintaining a local counter in the ractor newobj cache, and only
syncing atomically with some granularity, we can improve the correctness
without increasing contention.
The allocated object count is also synced when the ractor is freed.
Co-authored-by: Jean Boussier <jean.boussier@gmail.com>
In cb1ea54bbf I added one more
metadata flag, but didn't notice `RB_GC_OBJECT_METADATA_ENTRY_COUNT`
had to be incremented.
This should fix ASAN builds.
Interestingly, bdb25959fb already
caused the count to be off by one, so I had to increment it by
2.
Given that the currently planned ractor local GC implementation
performance will heavilly be influenced by the number of shareable
objects it would be valuable to be able to know how many of them
are in the heap.
`objspace->finalizer_table` must be synchronized,
otherwise concurrent insertion from multiple ractors
will cause a crash.
Repro:
```ruby
ractors = 5.times.map do |i|
Ractor.new do
100_000.times.map do
o = Object.new
ObjectSpace.define_finalizer(o, ->(id) {})
o
end
end
end
ractors.each(&:take)
```
This inverse table is only useful if `ObjectSpace._id2ref` is used,
which is extremely rare. The only notable exception is the `drb` gem
and even then it has an option not to rely on `_id2ref`.
So if we assume this table will never be looked up, we can just
not maintain it, and if it turns out `_id2ref` is called, we
can lock the VM and re-build it.
```
compare-ruby: ruby 3.5.0dev (2025-04-10T09:44:40Z master 684cfa42d7) +YJIT +PRISM [arm64-darwin24]
built-ruby: ruby 3.5.0dev (2025-04-10T10:13:43Z lazy-id-to-obj d3aa9626cc) +YJIT +PRISM [arm64-darwin24]
warming up..
| |compare-ruby|built-ruby|
|:----------|-----------:|---------:|
|baseline | 26.364M| 25.974M|
| | 1.01x| -|
|object_id | 10.293M| 14.202M|
| | -| 1.38x|
```
[Bug #21214]
If we allocate objects where one heap holds transient objects and another
holds long lived objects, then the heap with transient objects will grow
along the heap with long lived objects, causing higher memory usage.
For example, we can see this issue in this script:
def allocate_small_object = []
def allocate_large_object = Array.new(10)
arys = Array.new(1_000_000) do
# Allocate 10 small transient objects
10.times { allocate_small_object }
# Allocate 1 large object that is persistent
allocate_large_object
end
pp GC.stat
pp GC.stat_heap
Before this change:
heap_live_slots: 2837243
{0 =>
{slot_size: 40,
heap_eden_pages: 1123,
heap_eden_slots: 1838807},
2 =>
{slot_size: 160,
heap_eden_pages: 2449,
heap_eden_slots: 1001149},
}
After this change:
heap_live_slots: 1094474
{0 =>
{slot_size: 40,
heap_eden_pages: 58,
heap_eden_slots: 94973},
2 =>
{slot_size: 160,
heap_eden_pages: 2449,
heap_eden_slots: 1001149},
}
[Bug #20271]
[Bug #20267]
[Bug #20255]
`rb_obj_alloc(RBASIC_CLASS(obj))` will always allocate from the basic
40B pool, so if `obj` is larger than `40B`, we'll create a corrupted
object when we later copy the shape_id.
Instead we can use the same logic than ractor copy, which is
to use `rb_obj_clone`, and later ask the GC to free the original
object.
We then must turn it into a `T_OBJECT`, because otherwise
just changing its class to `RactorMoved` leaves a lot of
ways to keep using the object, e.g.:
```
a = [1, 2, 3]
Ractor.new{}.send(a, move: true)
[].concat(a) # Should raise, but wasn't.
```
If it turns out that `rb_obj_clone` isn't performant enough
for some uses, we can always have carefully crafted specialized
paths for the types that would benefit from it.
Was reading some assembly and noticed the dead branches generated for
FL_TEST(). Just a quick basic pass to change the obvious places; there
may be other opportunities.
Moving object_id dumping from ObjectSpace to the GC flags allows ObjectSpace
to not assume the FL_SEEN_OBJ_ID flag and instead move it to the responsibility
of the GC.