92 Commits

Author SHA1 Message Date
Jean Boussier
38c714f577 Fix freeing of complex IMEMO/fields
Followup: https://github.com/ruby/ruby/pull/17863

The only fields that need cleanup are complex ones, and
the free function had to be updated.
2026-07-15 13:13:07 +02:00
Jean Boussier
71ee81f7d9 imemo.c: embed the st_table inside rb_fields
Followup: https://github.com/ruby/ruby/pull/17631

Now that complex `RObject` always have an IMEMO/fields, we no
longer need to have an external `st_table` to replicate the
memory layout of `RObject`.
2026-07-14 23:50:17 +02:00
Jean Boussier
e0083cffa3 Refactor RObject to use T_IMEMO/fields_obj on overflow
Instead of spilling into a raw buffer, `RObject` now spills
into an `IMEMO/fields` object like other types.

From an instance variable layout standpoint, an extended `RObject`
is now identical to a `RTypedData`, as in they both store the
reference to their `IMEMO/fields` at the same offset (`VALUE * 2`).

One positive consequence of this is that the only case where a `T_OBJECT`
needs sweeping is if a finalizer was registered.

YJIT now side exit when it need to write a new ivar into a `RObject`
that is out of space. This is unlikely to cause a performance regression
as this codepath isn't supposed to happen after warmup given `max_iv_count`
is recorded on classes, so future objects should be large enough.
Hence it's best not to waste executable memory for such codepath.

ZJIT lost support for writting into extended `RObject`.
Most of the code to support it is there it's just missing an
implementation of `RBASIC_SET_SHAPE_ID`, which recently changed
to strip some bits out of the shape (see comments).
I will leave it to the ZJIT team to implement it (sorry).

There are a few future cleanups planned that I keep for followups:

  - We can now get rid of the `ROBJECT_HEAP` flag, it's redundant with
    the shape layout bits.
  - `imemo_fields` can now embed the `st_table` when complex.

Co-Authored-By: John Hawthorn <john@hawthorn.email>
Co-authored-by: Randy Stauner <randy@r4s6.net>
2026-07-14 10:44:39 +02:00
Jean Boussier
c966f3abd7 imemo.c: refactor imemo_fields_new
Lean on `rb_newobj` to get the shape embedded capacity properly
set.
2026-07-07 13:12:35 +02:00
Jean Boussier
263f06ad49 RBASIC_SET_SHAPE_ID: also strip the layout bits
Like the capacity part, the layout part of an object shape
almost never changes. The few exceptions are:

  - On allocation.
  - On being compacted by GC.
  - When RObject oberflows.

As such it simplifies a lot of code if `RBASIC_SET_SHAPE_ID` strips
the layout bits, as we often copy the shape from IMEMO/fields to
the owner object and vice-versa.

Also change `RBASIC_SET_SHAPE_ID_WITH_CAPACITY` into
`RBASIC_SET_FULL_SHAPE_ID` so it can be used for assigning both
capacity and layout.
2026-07-07 13:12:35 +02: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
Luke Gruber
1ca2d2d471 Fix pm_compile_call for inline_new with keyword args
pm_setup_args mallocs one kw_arg buffer with references = 0. The inline_new branch then feeds that same pointer
to three callinfo constructions:

- 3789 — new_callinfo(... method_id="new" ..., kw_arg, 0) -> the opt_new ci
- 3795 — PUSH_SEND_R(... "initialize", ..., flags | VM_CALL_FCALL, kw_arg)
- 3800 — PUSH_SEND_R(... method_id="new", ..., flags, kw_arg) (fallback)

Tracing the refcount through rb_vm_ci_lookup()

1. It increments kwarg->references and allocates a fresh new_ci before the dedup st_update
2. The dedup (vm_ci_hash_cmp) compares kwarg contents, not the pointer. So if an earlier line already interned a new ci with the
identical keyword set, st_update returns that pre-existing ci (which holds a different buffer) and discards our new_ci.
3. Our kw_arg is now orphaned: references == 1, but the only holder is the discarded new_ci, which is a normal collectable imemo
4. An allocation like PUSH_INSN2 opt_new at or new_callinfo() can trigger a GC. References back to 0, kw_arg buffer freed.
5. new_callinfo() using the freed buffer, does argc += kw_arg->keyword_len (use-after-free)

The fix: Keep the buffer alive across the allocations in inline_new.

Fixes [Bug #22104]
2026-06-29 11:06:25 -04:00
John Hawthorn
1326e74423 Simplify imemo_callcache reference update
In the past this used an ad-hoc weakref implementation that required
checking whether the referenced object was about to be swept. We
replaced this with the actual weakref implementation from the GC so
these VALUEs should always be either live or Qundef.
2026-06-16 09:43:07 -07:00
Jean Boussier
78495ca415 imemo_tmp_buffer: skip marking when useless
In many place ALLOC_V / ALLOCV_N is used as a safer `alloca`,
and to behave like stack memory, `ALLOCV` does scan its buffer
for references using `rb_gc_mark_locations`.

The problem is that it's quite slow, and in many cases, the
temporary buffer is known not to contain any references.

e.g. `ALLOCV_N(uint32_t, buf0, len)` can't possibly contain references,
as the element size is too small.
2026-06-16 16:52:32 +02:00
Aaron Patterson
c4d09f416c
Reapply "Reserve 2 bits for expressing object layout (#17139)" (#17158)
This reverts commit ddb5055d961d970aded287cfebd07b78efee3ca7.
2026-06-01 19:30:58 +00:00
Aaron Patterson
ddb5055d96
Revert "Reserve 2 bits for expressing object layout (#17139)"
This reverts commit 63d9f090b5d9461cf0b9446e0039d9c56156b826.
2026-05-29 16:41:31 -07:00
Aaron Patterson
63d9f090b5
Reserve 2 bits for expressing object layout (#17139)
* Reserve 2 bits for expressing object layout

We would like to make instance variable reads in the JIT compiler faster
(as well as simplify the JIT implementation).  Currently, in order to
read an instance variable, we have to:

1. Test for heap object
2. Load object to a 64 bit register
3. Mask the object header
4. Bit test against the masked header
5. JNE
6. Load field

We would like to:

1. Test for heap object
2. Load object shape to a 32 bit register
3. Bit test against the shape
4. JNE
5. Load field

The way we fetch instance variables is not consistent across objects.
In order to realize our goal, we need to encode object layout inside the
shape.  If we encode object layout inside the shape, then the shape
itself will guarantee that the access pattern generated by the JIT
compiler is correct.

We should encode the following load patterns into the shape tag bits.
This way we can share shapes on transitions, but be able to
differentiate the access patterns for the JIT compiler.  In other words,
two objects can have an `@a -> @b -> @c` transition and share the same
shape, but the tag bits can differentiate the access pattern so that the
JIT compiler can be confident that the machine code is correct.

Here are the patterns:

1. Embedded/Extended T_OBJECT Instance Variables

Objects with direct references to instance variables or via malloc
buffer

2. Objects with fields_objects fields

These are Data and TypedData objects.  They have an associated axillary
imemo/fields object that stores the instance variables.  The access
pattern is `object[2] + 2`.  The fields object is the 3rd field, and the
instance variables start at +2 inside the fields object.  The fields
object itself is a Ruby object, so it contains the usual header bits +
class headers.

3. Non Boxable Classes / Modules

This is similar to Objects with fields_objects, but the fields object is
stored at a different offset.  We’re differentiating this from boxable
classes and modules because those are harder to support.

4. Other

"Other" pattern is for objects that are rare, or have
difficult-to-implement access patterns.  This includes:

* Boxable classes and modules
* Structs (for now)
* Objects that use the geniv table

Proposed shape bit layout:

```
  Current shape_id_t is 32 bits:
  31        28 27 26 25 24 23 22        19 18                         0
  +-----------+--+--+--+--+--+------------+----------------------------+
  | unused    |L1|L0|OI|FR|CX| heap index | shape tree offset          |
  +-----------+--+--+--+--+--+------------+----------------------------+
               |  |  |  |  |  |            |
               |  |  |  |  |  |            +-- bits 0-18: SHAPE_ID_OFFSET_MASK
               |  |  |  |  |  +--------------- bits 19-22: SHAPE_ID_HEAP_INDEX_MASK
               |  |  |  |  +------------------ bit 23: SHAPE_ID_FL_COMPLEX
               |  |  |  +--------------------- bit 24: SHAPE_ID_FL_FROZEN
               |  |  +------------------------ bit 25: SHAPE_ID_FL_HAS_OBJECT_ID
               +--+--------------------------- bits 26-27: SHAPE_ID_LAYOUT_MASK
```

The important part about these layout patterns is that they do not
reflect the _type_ of object, only how the object is laid out in memory.
For example, we currently treat structs as "other", but we can refactor
them to have the same layout as "Objects with fields_objects", and when
we do that they should get a different bit in the shape header.

This commit only reserves the two bits, it doesn't use them in the JIT
compiler yet.

Co-Authored-By: John Hawthorn <john@hawthorn.email>
Co-Authored-By: Max Bernstein <tekknolagi@gmail.com>

* Update gc.c

Co-authored-by: Nobuyoshi Nakada <nobu.nakada@gmail.com>

* Update shape.h

Co-authored-by: Jean Boussier <jean.boussier@gmail.com>

* fix function name

* Update shape.c

Co-authored-by: Jean Boussier <jean.boussier@gmail.com>

* fix function name

* Revert "Update shape.c"

This reverts commit 900711defc6c541a93f3393a350819ae88cf87f1.

* add comment

---------

Co-authored-by: John Hawthorn <john@hawthorn.email>
Co-authored-by: Max Bernstein <tekknolagi@gmail.com>
Co-authored-by: Nobuyoshi Nakada <nobu.nakada@gmail.com>
Co-authored-by: Jean Boussier <jean.boussier@gmail.com>
2026-05-29 15:09:34 -07:00
Matt Valentine-House
3002cea4db Fix GC Bug in imemo cdhash creation
It's possible for st_init_existing_table_with_size to trigger GC. If
that happens we need to ensure that the table entries count doesn't
contain garbage data, or we'll try and mark random stuff
2026-05-20 22:14:47 +01:00
John Hawthorn
cb9f2ba804 Use atomics for kwargs reference count
Fixes [Bug #22075]
2026-05-20 09:48:55 -07:00
Jean Boussier
d036506b15 Stop boxing cdhash offsets
Now that they're no longer a RHash instance, we don't
have to box the offsets, we can directly srore the raw
values, and stop marking them.
2026-05-18 08:58:32 +03:00
Jean Boussier
99dc513d15 Use IMEMO to store cdhash
RHash isn't a good fit for storing `cdhash` as this force to allow
arbitrary hash types into RHash, which doesn't work with AR tables.

It also cause the cdhash to be larger than needed.
2026-05-18 08:58:32 +03:00
Jean Boussier
74c3696d59 Add missing debug counter for imemo_subclasses 2026-05-17 21:09:27 +03:00
John Hawthorn
f71840a230 Replace subclasses linked list with weakref array 2026-05-13 18:36:51 -07:00
Étienne Barrié
e8a09f9ad7 Remove unused variable warning in imemo_fields_free 2026-05-07 12:31:27 +02:00
Kazuhiro NISHIYAMA
4758d1b5f3
Suppress a warning
```
compiling ../imemo.c
../imemo.c:563:20: warning: unused variable 'shape_id' [-Wunused-variable]
  563 |         shape_id_t shape_id = RBASIC_SHAPE_ID((VALUE)fields);
      |                    ^~~~~~~~
1 warning generated.
```
2026-05-07 17:47:36 +09:00
Jean Boussier
deceb9f62e Rename rb_shape_obj_* into rb_obj_shape_*
For consistency.
2026-05-06 10:10:16 +02:00
Jean Boussier
4eeec614bb shapes: Rename TOO_COMPLEX in just COMPLEX
The `too_` prefix wasn't consistently used and just make the
thing longer for no benefit.
2026-05-06 10:10:16 +02:00
Jean Boussier
678d86f5e4 shape.c: transition to complex when max_capacity is reached
Now that we have 1024B slots, we can store up to 126 fields inline.

Objects larger than this are rare if not non-existent, hence we can
get rid of the `malloc` path for imemo/fields and simply transition
to `TOO_COMPLEX`.

This additionally allows to shrink `attr_index_t` from 16 to 8B.

Note: the ZJIT "ivar on extended" tests are renamed as "complex" because
"extended" AKA malloc allocated imemo/fields no longer exists.
They're now complex fields, AKA st tables.

rb_class_allocate_instance: start as complex when over max_fields

If `RCLASS_MAX_IV_COUNT` is over `max_fields`, allocating a large
slot to end up transitioning to `TOO_COMPLEX` is wasteful.

We might as well start as complex directly.
2026-05-06 09:02:02 +02:00
Jean Boussier
289343cf0f Create imemo/fields with shape_id
Simplify the API by taking a shape_id upon creation.
2026-05-02 10:33:48 +02:00
John Hawthorn
096c9bb9e0 Use flag to determine when MEMO.u3 is a VALUE
We always know when this contains a VALUE and even fire a write barrier
for it. We should use this when marking, support compaction, and no
longer need conservative marking.
2026-04-30 11:20:26 -07:00
Jean Boussier
2432ed8904 Further simplify common use case of NEWOBJ_OF
Only few objects need to pass the starting shape.
2026-04-28 11:11:29 +09:00
Jean Boussier
e4667e5109 Refactor NEWOB_OF for the common protected use case
Almost every objects are now WB protected, with just a few
exceptions, hence we can provide a much simpler interface.

It's also much easier to find the remaining unprotected objects.
2026-04-28 11:11:29 +09:00
Jean Boussier
c202bc08e1 Simplify NEWOBJ_OF and introduce EC_NEWOBJ_OF
We only very rarely have the execution context available, so
it's preferable to provide a simpler default macro.
2026-04-28 11:11:29 +09:00
Jean Boussier
47e061277a class.c: Make cvc_tbl a managed object
[Bug #21952]

Solves the double-free or use after-free concern with boxes.
Now entries can safely be used for copy-on-write.

Also is likely necessary to make it save to read cvar from
secondary ractors, as allowed since: ab32c0e690b805cdaaf264ad4c3421696c588204
2026-04-21 16:30:14 +09:00
John Hawthorn
3a90afd1d0 Avoid atomic exchange in rb_free_tmp_buffer
Usually RB_ALLOCV_N uses alloca for small allocations, and in that case
the value is 0, and we should not need to atomic exchange it back to 0.

I'm actually not sure why we need atomic operations here anyways.
2026-04-13 08:46:06 -07:00
Jean Boussier
53099633e2 Make ruby_xfree_sized and ruby_xrealloc_sized public
[Feature #21861]
2026-04-01 08:10:07 +01:00
John Hawthorn
4b4da98832 Avoid and simplify rb_alloc_tmp_buffer_with_count
There's no reason to ever call this and the "count" value is ignored.
Because this is part of the public API I left it as a stub, but it could
likely be removed.
2026-02-24 09:39:35 -08:00
Jean Boussier
50e987bdca imemo.c: Replace ruby_xfree by ruby_sized_xfree 2026-01-31 17:54:33 +01:00
Jean Boussier
91619f0230 gc.c: Verify provided size in rb_gc_impl_free
For now the provided size is just for GC statistics, but in the future
we may want to forward it to C23's `free_sized` and passing an incorrect
size to it is undefined behavior.
2026-01-29 23:32:04 +01:00
Jean Boussier
1c7e19f961 rb_free_tmp_buffer: use ruby_sized_xfree
We know the buffer length, we might as well feed that information
back to the GC.
2026-01-16 21:11:17 +01:00
Peter Zhu
01cd9c9fad Add rb_gc_register_pinning_obj 2025-12-29 09:03:31 -05:00
Peter Zhu
56147001ec Move MEMO_NEW to imemo.c and rename to rb_imemo_memo_new 2025-12-29 09:03:31 -05:00
Peter Zhu
ade779b1e1 Implement callcache using declare weak references 2025-12-25 09:18:17 -05:00
Luke Gruber
4fb537b1ee
Make tracepoints with set_trace_func or TracePoint.new ractor local (#15468)
Before this change, GC'ing any Ractor object caused you to lose all
enabled tracepoints across all ractors (even main). Now tracepoints are
ractor-local and this doesn't happen. Internal events are still global.

Fixes [Bug #19112]
2025-12-16 14:06:55 -05:00
Nobuyoshi Nakada
2f53985da9
Revert miscommit at "Reset the cache variable before retrying"
This reverts commit 26a9e0b4e31f7b5a9cbd755e0a15823a8fa51bae partially.
2025-11-26 11:35:15 +09:00
Nobuyoshi Nakada
26a9e0b4e3
Reset the cache variable before retrying 2025-11-26 10:47:17 +09:00
Satoshi Tagomori
e84b91a292 Box: mark/move Box object referred via ENV/rb_env_t 2025-11-26 10:10:47 +09:00
Koichi Sasada
bc00c4468e use SET_SHAREABLE
to adopt strict shareable rule.

* (basically) shareable objects only refer shareable objects
* (exception) shareable objects can refere unshareable objects
  but should not leak reference to unshareable objects to Ruby world
2025-10-23 13:08:26 +09:00
Peter Zhu
3ec597f619 Fix memory leak in cloning complex imemo_fields
When we clone a complex imemo_fields, it calls creates the imemo_fields
using rb_imemo_fields_new_complex, which allocates and initializes a new
st_table. However, st_replace will directly replace any exisiting fields
in the st_table, causing it to leak.

For example, this script demonstrates the leak:

    obj = Class.new
    8.times do |i|
      obj.instance_variable_set(:"@test#{i}", nil)
      obj.remove_instance_variable(:"@test#{i}")
    end

    obj.instance_variable_set(:"@test", 1)

    10.times do
      100_000.times do
        obj.dup
      end

      puts `ps -o rss= -p #{$$}`
    end

Before:

    26320
    39296
    52320
    63136
    75520
    87008
    97856
    114800
    120864
    133504

After:

    16288
    20112
    20416
    20720
    20800
    20864
    21184
    21424
    21904
    21904
2025-09-21 08:24:21 -04:00
Peter Zhu
1663e2fbc8 Fix capacity of imemo_fields objects created from rb_imemo_fields_new_complex_tbl
The imemo_fields_new function takes a capacity in the number of fields to
preallocate. rb_imemo_fields_new_complex_tbl is using it incorrectly because
it is preallocating sizeof(struct rb_fields) number of fields.
2025-09-19 11:05:43 -04:00
Peter Zhu
477b1e79b7 Directly use rb_imemo_new in imemo_fields_new_complex
We should not assume that a complex imemo_field takes only one additional
VALUE space. This is fragile as it will break if we add additional fields
to complex imemo_field.
2025-09-19 08:46:09 -04:00
Peter Zhu
cfc5c56503 Clear out memory for newly allocated tmpbuf 2025-09-17 09:25:17 -04:00
John Hawthorn
e4f09a8c94 Remove next field and unused method from tmpbuf
These used to be used by the parser
2025-09-15 16:08:13 -07:00
Peter Zhu
7dd9c76ad4 Make imemo_tmpbuf not write-barrier protected
imemo_tmpbuf is not write-barrier protected and uses mark maybe to mark
the buffer it holds. The normal rb_imemo_new creates a write-barrier
protected object which can make the tmpbuf miss marking references.
2025-09-15 11:43:05 -04:00
Peter Zhu
1e3e04cd65 Move rb_imemo_tmpbuf_new to imemo.c 2025-09-15 11:43:05 -04:00