Commit Graph
924 Commits
Author SHA1 Message Date
Edouard CHINandYaroslav Markin cc57f9c818 Include ivar name and class name in the Ractor isolation error message
- ### Problem

  When a Ractor isolation error happens due to setting an ivar on a
  class, the message doesn't include the name of the ivar and neither
  the name of the class. It makes it difficult to track down where the
  isolation problem comes from.

  I'd like the message to be similar as what we get when a Ractor
  access an unshareable ivar.

  This is the current behaviour

  ```ruby
  class Foo
    def self.foo
      @foo ||= "test"
    end
  end

  Ractor.new do
    Foo.foo # can not set instance variables of classes/modules by non-main Ractors
  end.join

  Foo.foo

  Ractor.new do
    Foo.foo # can not get unshareable values from instance variables of classes/modules from non-main Ractors (@foo from Foo)
  end.join
  ```

  ### Solution

  Modify the error message to include the ivar name and the class/module name.

Co-authored-by: Yaroslav Markin <yaroslav@markin.net>
2026-10-22 17:51:23 -04:00
Koichi SasadaandClaude Opus 5.5 5771bd7a39 Read another Ractor's constant entries under the VM lock
rb_const_remove deleted the entry from the constant table without the VM
lock and then freed it, while a Ractor that does not own the class looks
the table up under the VM lock but reads the entry after releasing it.
Reading a constant while its owner removes it could crash in the table
lookup or return garbage (an Integer, Float or false that was never
assigned).

Delete under the VM lock, and have the readers that may not own the class
copy the entry while holding it (rb_const_lookup_copy).  Class#dup and
the singleton class clone walk the original's table under the lock for
the same reason; the Class#dup check collects the candidates under the
lock and runs rb_ractor_shareable_p after it.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-22 05:49:20 +09:00
Yusuke EndohandClaude Opus 5.5 0cb32950e4 Assert that a constant entry of Qundef holds an autoload
A constant entry whose value is Qundef always holds the autoload
registration, so check its type only by VM_ASSERT instead of handling
other cases.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-14 01:53:36 +09:00
Yusuke EndohandClaude Opus 5.5 cb08906bfa Explain how other boxes get the autoloaded constant
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-14 01:53:36 +09:00
Yusuke EndohandJeremy Evans 8b630aad6a Update variable.c
Co-authored-by: Jeremy Evans <code@jeremyevans.net>
2026-10-14 01:53:36 +09:00
Yusuke EndohandClaude Opus 5.5 dbd0e0174c Rename autoload_data() to autoload_entry()
It returns an autoload_const, not an autoload_data.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-14 01:53:36 +09:00
Yusuke EndohandClaude Opus 5.5 6d68304c38 Keep autoload registrations in the constant entries
An autoload registration has been kept in a per-module table under the
hidden instance variable __autoload__, in addition to the Qundef entry
in the constant table.  With Ruby::Box, the constant table of a class is
copied entry by entry for each box, but the instance variables are
copied shallowly, so all the boxes shared one autoload table.

Once an autoload registered in the master box (e.g., Ruby::Prism) is
resolved in one box, the registration was removed from the shared
table, while the boxes that have their own copy of the class kept their
Qundef entries.  There, the constant was neither defined nor
autoloadable, and referring to it raised NameError.  Also, autoloads of
the same name registered in two boxes overwrote each other.

Keep the registration in the Qundef constant entry instead (in a union
with the file, which is moved to the registration), so each box has its
own registration with its own constant table, and the two never get out
of sync.

As a dup'ed module now shares the registration (instead of the table),
check that the mutex is locked to see if someone else is loading it, so
that the registration is not considered available after the feature is
loaded.

[Bug #22305]

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-14 01:53:36 +09:00
Yusuke EndohandClaude Opus 5.5 dfbcdda939 Set autoloaded constants in the box that registered the autoload
The constants of an autoload were set in the box that triggered it.  For
an autoload registered in the master box (e.g., Ruby::Prism), this
defined the constant only in the first box that accessed it, and the
other boxes got NameError.

Set the constants in the registering box instead.  For the master box,
they are set in the prime classext, which is shared by all boxes; a box
that has its own copy of the class (e.g., the one that triggered the
autoload) takes the value from the prime one.

[Bug #22305]

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-14 01:53:36 +09:00
Hiroshi SHIBATAandClaude Opus 5 975c2a45f1 Use rb_len_t for Array and String lengths in the VM
Argument counts stay `int` or `long`. The int range checks in proc.c and compile.c now depend on SIZEOF_VALUE so that they are kept once rb_len_t widens.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-10-09 14:00:15 +09:00
Luke Gruber bec5e51c85 Fix Module/Class#dup of frozen object
This code should not raise an error:
```
@var = Class.new.freeze.dup # unfrozen copy
@var.instance_variable_set(:@test, 123)
```

It was raising `Ractor::IsolationError` because of a bug in `rb_fields_tbl_copy`.

This patch also fixes the following assertion failure in debug mode:

```
Module.new.freeze.singleton_class.instance_variable_set("@a", 1)
```

Now we get the proper behavior:

```
can't modify frozen Class: #<Class:#<Module:0x00000001094d9900>> (FrozenError)
```

Fixes [Bug #22378]
2026-09-25 14:53:06 -04:00
Hiroshi SHIBATAandClaude Opus 5 2e31300060 Run trace_var hooks for assignments in the box gvar table
Under RUBY_BOX=1 an assignment in a user box, the main box included, only
updates the box-local table and never reaches the global entry, so hooks
registered with trace_var never run.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-24 17:14:06 +09:00
Hiroshi SHIBATAandClaude Opus 5.5 f7839b5228 Look up aliased globals in defined? by the shared ID in a box
rb_gvar_set and rb_gvar_get key the box table by var->id, so defined? on an aliased name looked for a key that is never written and returned nil.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-24 16:04:59 +09:00
Hiroshi SHIBATAandClaude Opus 5 ac0debb174 Fix global variable aliases in a box
Under RUBY_BOX=1 the box-local gvar table is keyed by the name used for
access, so `alias $b $a` gives the two names separate slots and they
stop being synonyms.  Key it by the ID the shared rb_global_variable
was created with instead.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-24 16:04:59 +09:00
Hiroshi SHIBATA ba91f18409 Fix defined? for global variables assigned in a box
Assignment in a box only writes to box->gvar_tbl and leaves the global
entry with rb_gvar_undef_getter, so defined?($foo) returned nil while
$foo itself returned the assigned value. mkmf's have_devel? stops its
own recursion with `unless defined? $have_devel`, so extconf.rb blew the
stack under RUBY_BOX=1. Reading an undefined gvar no longer caches nil
into the box table, which keeps a key there meaning "defined here".
2026-09-18 06:49:20 +09:00
John Hawthorn 73055acd57 Forbid setting ivars on Ractor shareable objects
Previously we had a check that objects which were shareable, but not
frozen on a non-main Ractor would raise an exception if instance
variables were read. This prevented a non-main Ractor reading
non-shareable objects.

This changes the behaviour to forbid all writes to shareable objects
(even when unfrozen). This is done by re-purposing the frozen flag on
the object shape to reflect (FL_FROZEN || FL_SHAREABLE).

With IV writes forbidden on all shareable objects whether or not they
are frozen (with the exception of classes and modules), we can also
remove the check against reading instance variables, since no unsafe
instance variables can be set.

[Bug #22291]
2026-09-16 12:26:11 -07:00
Luke Gruber a00df45480 Add assertions that interrupts aren't checked when VM lock taken. (#18832) 2026-09-15 16:23:10 +12:00
Koichi SasadaandClaude Opus 5 5cd116303d Remove functions nothing calls
None of these has a caller, a declaration in any header, or a place in
libruby's dynamic symbol table, so an extension cannot reach them either.

* rb_vm_opt_newarray_pack: left over from cb9510f539, which moved the
  instruction to the _buffer form.
* rb_autoload: a wrapper around rb_autoload_str, deprecated and dropped
  from intern.h in Ruby 2.3 [Feature #11664].
* rb_big_fdiv: a wrapper around rb_big_fdiv_double, unused since Bignum
  stopped defining #fdiv.
* rb_io_unbuffered: a wrapper around rb_io_synchronized, which is what it
  was renamed to before 1.8.0.
* rb_ractor_hooks: an accessor for cr->pub.hooks; only its sibling
  rb_ractor_targeted_hooks is used.
* rb_assert_sig: a leftover debugging aid.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-11 11:42:08 +09:00
Koichi SasadaandClaude Opus 5 f8485f7d26 Ractor: make freeze and set_temporary_name owner-only
Requested by matz on [Feature #22226]: a non-owner freezing somebody else's
class is a hole, because it lets a third party make a class permanently
unmodifiable.  Naming is the same kind of change to the class itself.

Module#freeze checks only when it would actually change something, so an
already frozen class stays a no-op for every Ractor and idempotent .freeze
calls keep working.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-10 16:02:03 +09:00
Koichi SasadaandClaude Fable 5 6b9a29a13d Ractor: bring class variables under the class owner
A class variable is shared across the inheritance chain and the class it is
stored in can migrate (cvar_overtaken), so the deciding Ractor is the owner of
the class the variable is actually stored in, not of the receiver it was looked
up through.  That is a single well-defined owner at any moment, it is the same
single writer the rest of that class's fields already have, and it keeps the
cross-boundary writes out: main cannot write into a Ractor's class, and a
Ractor cannot write into main's.

Two holes turned up on the way:

- Module#remove_class_variable had no ownership check at all, so any Ractor
  could delete a class variable out of a foreign class.

- The class variable inline caches keyed the fast path on rb_ractor_main_p(),
  and the fast path writes straight into ic->entry->class_value without going
  through rb_cvar_set.  Main running a method of a foreign class therefore
  skipped the ownership check entirely.  The condition is now the owner of
  class_value, matching what the instance variable fast paths already do -- and
  a non-main owner gets the fast path it could not reach before.

cvar_overtaken() no longer deletes the duplicated entry from a class owned by
another Ractor: that deletion is reachable from read paths (rb_cvar_find), so
doing it there would be a cross-Ractor write, and it is only clean-up.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-10 16:02:03 +09:00
Koichi SasadaandClaude Fable 5 2e5bf429e4 Ractor: restrict class/module modification to the creator Ractor
Only the Ractor which created a class/module may modify it: define, remove or
undefine methods, alias, include/prepend, refine, define or remove constants,
register an autoload, and set the class's own instance variables.  Reading is
unchanged.  This replaces the assorted "main Ractor only" rules with one
creator-based rule, and in exchange a Ractor gets full use of the classes it
creates itself -- unshareable constant and instance variable values included,
which even the main Ractor could not do for its own classes before.

Three paths reach a class's tables without going through rb_class_modify_check,
so they get the check of their own: Module#refine (which writes its refinement
tables into the receiver as well as into the refined class), Class#initialize
(which writes the superclass of a not-yet-initialized class, and had no frozen
check either), and Module#ruby2_keywords.

The constant inline cache has to move with the rule.  Its hit condition was
"shareable value, or we are the main Ractor", which was sound only because a
non-main Ractor could not have an unshareable constant in the first place.  Now
it can, so an owner priming the cache would let any other Ractor read that value
straight out of it, never reaching the owner check in vm_get_ev_const.  The
entry records the Ractor which filled it instead -- the one that passed the
check -- which also gives a non-main owner a fast path it never had.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-10 16:02:03 +09:00
Luke Gruber c329f36bea Add and use rb_gc_update_moved(VALUE *obj) which conditionally assigns
Using the `container->obj = rb_gc_location(container->obj)` pattern dirties pages unconditionally.
If we're in a forked process, this is bad for CoW. If the object hasn't moved, don't reassign.
2026-09-08 21:43:48 -04:00
Luke Gruber 3a0ec78790 Don't create CCs for hook methods called from C
Calling these hook methods shouldn't create CCs because these are not hot methods.
These CCs were only looked up in the gccct table (global call cache lookup table)
for rb_funcall and friends.

For one of our apps after bootup we reduced CCs significantly:

|                       | A control  | B (your commit) | B − A                 |
|-----------------------|------------|-----------------|-----------------------|
| callcaches (after GC) | 705,957    | 651,747         | −54,210 (−7.7%)       |
| cc tables             | 46,361     | 43,051          | −3,310                |
| total cc memory       | 86,441,345 | 79,823,142      | −6,618,203 (−6.3 MB)  |
2026-09-04 10:33:08 -04:00
Hiroshi SHIBATA 13c99ffab9 Fix autoload's require dispatch under Ruby::Box
With RUBY_BOX=1, firing an autoload invoked Ruby::Box#require on the
registered box, bypassing any Kernel#require decoration (RubyGems,
Zeitwerk, ...) prepended in that box. Zeitwerk's implicit namespaces
rely on the decorated require, so a plain Rails app failed with
LoadError on the first request. Call require on the box's top self in
a frame running in that box instead, which dispatches the decorated
method and still loads the feature into the box that registered the
autoload.
2026-08-31 08:18:42 +09:00
Koichi SasadaandClaude Opus 5 9d3b43fda9 Ractor: do not leak generic_fields_lock when the insert raises
Adding a generic-fields entry takes the VM-wide generic_fields_lock and then
calls st_insert, which mallocs when the table grows.  With this Ractor's GC
disabled the allocation cannot fall back to a collection, so it can raise
NoMemoryError -- and the raise skipped both gf_unlock() and the matching
rb_gc_local_enable().  A leaked generic_fields_lock hangs every later access
to generic fields, including the marking of any Ractor's local GC, so an
otherwise recoverable NoMemoryError turned into a deadlock.  Unwind through
a tag instead.

While here, drop three things that no longer match the code: a basket
payload comment describing a generic-ivar map field that was removed, the
unused st_table placeholder left in ractor_basket_new, and two comments in
variable.c still describing the per-Ractor generic-fields tables that the
single global table replaced.

This commit also carries the mechanical translation of the Japanese comments
in these two files, which was in progress in the same working tree.

Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-07 16:27:53 +09:00
Koichi Sasada 84faabfd19 GC: per-Ractor local GC — generic fields (single global table)
Keep the generic-ivar host-to-fields_obj mapping in one global table,
serializing mutators with a dedicated lock.  A local GC marks only the
values living in its own objspace; the global GC marks the values of
live keys in the weak pass that runs after marking.  This removes the
table-ownership transitions that used to happen on shareable promotion,
on send, and on absorb.
2026-08-07 16:27:53 +09:00
Nobuyoshi Nakada 2d616a9bb0 Remove unused autoload table copier
Remove the copier left unused when class fields cloning replaced raw
table copying in commit 3abdd4241f.
2026-07-29 11:42:03 +09:00
Jean BoussierandJohn Hawthorn 40f105bd55 Change RStruct to match SHAPE_ID_LAYOUT_EXTENDED
THe downside is that we now always reserve one 8B reference for the
IMEMO/fields, even for structs that never have any ivars.

But it is an acceptable tradeoff given that ivars on structs aren't
rare.

Co-Authored-By: John Hawthorn <john@hawthorn.email>
2026-07-21 08:30:12 +02:00
Jean Boussier 74bd057f9c Get rid of ROBJECT_FIELDS_HASH 2026-07-15 20:52:47 +02:00
Jean Boussier efb3a73445 rb_imemo_fields_ptr: assume embedded 2026-07-15 20:52:47 +02:00
Jean Boussier 6cd755deab variable.c: generalize use of rb_obj_fields()
Numerous codepath have the same `switch/case` with very little
differing logic compared to `rb_obj_fields`, might as well
use it everywhere and share more code.
2026-07-15 19:09:20 +02:00
Jean Boussier 86951165b8 Get rid of ROBJECT_HEAP flag
This flag is now entirely redundant with the shape_id layout bits.
2026-07-15 15:43:28 +02:00
Jean Boussier 4bd94ccbbd variable.c: only filter out object_id, not all internal ivars 2026-07-14 23:50:17 +02:00
Jean Boussier 944d936d7c variable.c: Refactor too_complex evacuation paths
Now share more code between the various cases:

  - Whether to copy all fields or just ivars
  - Whether to add or remove some capacity.
2026-07-14 23:50:17 +02:00
Jean Boussier 24337e3cbe Refactor rb_obj_copy_ivar
Share more code with `rb_copy_generic_ivar`.
2026-07-14 23:50:17 +02:00
Jean Boussier 6c23075086 rb_copy_generic_ivar: cleanup goto statement
It used to be that multiple paths would need to clear
the generic ivar reference, but it's no longer the case.
2026-07-14 23:50:17 +02:00
Jean Boussier 6f0548a0bd Simplify rb_obj_copy_ivar and rb_copy_generic_ivar
By handling complex shapes in `rb_shape_rebuild` we can merge
the two complex cases (when `src` is complex and when `dest`
becomes complex).
2026-07-14 23:50:17 +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
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 2c574ca7e2 Symbol#to_s now returns a frozen string.
[Feature #22137]

It has returned a chilled string, hence emitting deprecation
warnings for two versions.
2026-07-10 10:02:00 +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
John HawthornandLuke Gruber daffaaee73 Mark most TypedData types as THREAD_SAFE_FREE
Set RUBY_TYPED_THREAD_SAFE_FREE on TypedData types whose dfree function
is trivially safe, and only frees its own memory

Types not yet marked as THREAD_SAFE_FREE:
- id2ref_tbl_type: sets the process-global id2ref_tbl to NULL.
- mutex_data_type: unlinks itself from another thread's keeping_mutexes list.
- autoload_data_type: deletes nodes from a shared intrusive list.
- autoload_const_type: deletes itself from a shared list.
- rb_cont_data_type: mutates the shared fiber pool and the global first_jit_cont list.
- rb_fiber_data_type: delegates to cont_free.
- FiberPoolDataType: mutates shared fiber pool free-lists.
- ractor_data_type: reconfigures global VM event-hook flags and call caches.
- exported_object_registry: frees a global table under the VM lock.
- rb_box_data_type (Box::Entry): unlinks classext from other live class and module objects.
- box_ext_cleanup_type: dereferences another String object and calls unlink.
- monitor_data_type: uses the default free, so the flag has no effect.

Co-authored-by: Luke Gruber <luke.gruber@shopify.com>
2026-06-23 13:30:26 -07:00
Aaron Patterson c4d09f416c Reapply "Reserve 2 bits for expressing object layout (#17139)" (#17158)
This reverts commit ddb5055d96.
2026-06-01 19:30:58 +00:00
Aaron Patterson ddb5055d96 Revert "Reserve 2 bits for expressing object layout (#17139)"
This reverts commit 63d9f090b5.
2026-05-29 16:41:31 -07:00
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
John Hawthorn 594ae9f073 Give legacy T_DATA the same layout as TypedData
This allows us to treat the two types of T_DATA the same way when
reading instance variables.

Non-TypedData T_DATA are deprecated, so making them faster isn't
particularly important, but it is helpful to avoid needing to test for
TypedData and adding another special case.
2026-05-27 16:18:43 -07:00
Peter Zhu 6bc1aa31cc [Bug #22072] Fix rb_cvar_set for multi-Ractor
rb_cvar_set breaks the RCLASS_CVC_TBL because it creates a new cvc_tbl but
forgets to copy the contents over. This causes the following script to
crash:

    r = Ractor.new {}

    module Foo
      def self.foo = @@foo
    end

    Foo.class_variable_set(:@@foo, nil)
    Foo.class_variable_set(:@@bar, nil)

    puts Foo.foo

Crashes with:

    test.rb:4: [BUG] should have cvar cache entry
    ruby 4.1.0dev (2026-05-16T21:11:21Z define-obj-has-suf.. 642cfc59a7) +PRISM [x86_64-linux]

    -- Control frame information -----------------------------------------------
    c:0003 p:0003 s:0012 e:000011 l:y b:0001 r:(nil) METHOD test.rb:4
    c:0002 p:0036 s:0008 E:001118 l:n b:---- r:(nil) EVAL   test.rb:10 [FINISH]
    c:0001 p:0000 s:0003 E:0013c0 l:y b:---- r:(nil) DUMMY  [FINISH]

    -- Ruby level backtrace information ----------------------------------------
    test.rb:10:in '<main>'
    test.rb:4:in 'foo'

    -- Threading information ---------------------------------------------------
    Total ractor count: 1
    Ruby thread count for this ractor: 1

    -- C level backtrace information -------------------------------------------
    miniruby(rb_print_backtrace+0x24) [0x5701425120eb] vm_dump.c:1108
    miniruby(rb_vm_bugreport+0x374) [0x5701425128f2] vm_dump.c:1456
    miniruby(rb_bug_without_die_internal+0xa0) [0x5701422ad332] error.c:1107
    miniruby(rb_bug+0xb5) [0x5701422ad4de] error.c:1125
    miniruby(update_classvariable_cache+0xd1) [0x5701424d159c] vm_insnhelper.c:1501
    miniruby(vm_getclassvariable+0xd1) [0x5701424d16e4] vm_insnhelper.c:1532
    miniruby(vm_exec_core+0xfa6) [0x5701424e6fa9] insns.def:243
    miniruby(rb_vm_exec+0x140) [0x570142504532] vm.c:2805
    miniruby(rb_iseq_eval_main+0x3d) [0x5701425054c8] vm.c:3101
    miniruby(rb_ec_exec_node+0x128) [0x5701422b914a] eval.c:284
    miniruby(ruby_run_node+0x8d) [0x5701422b92be] eval.c:322
    miniruby(rb_main+0x4c) [0x5701421ccd8e] main.c:42
    miniruby(main+0x4b) [0x5701421ccdef] main.c:62
2026-05-17 09:09:53 -04: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