Module#initialize_copy of a class/module that another Ractor owns used to
check that its constants and fields are all shareable, and copy it if so.
That check made the copying Ractor walk (and flag) the owner's objects,
and the fields were collected into a buffer and checked one by one, so a
later value sat only in that buffer, which the owner's GC cannot see.
Raise Ractor::IsolationError instead, whatever the class holds. The
metaclass is checked as well, since its tables are copied too.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
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>
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>
If the class is uninitialized, Class#dup will set the duplicated class to
initialized.
The following code will cause an assertion error:
Class.allocate.dup.new
Assertion error is:
vm_method.c:1861: Assertion Failed: rb_get_alloc_func:allocator
ruby 4.1.0dev (2026-10-08T09:57:55Z master 95db6abe45) +PRISM [arm64-darwin27]
-- Crash Report log information --------------------------------------------
See Crash Report log file in one of the following locations:
* ~/Library/Logs/DiagnosticReports
* /Library/Logs/DiagnosticReports
for more details.
Don't forget to include the above Crash Report log file in bug reports.
-- Control frame information -----------------------------------------------
c:0002 p:0011 s:0008 e:000005 l:n b:---- r:0x0 EVAL test.rb:1 [FINISH]
c:0001 p:0000 s:0003 E:002568 l:y b:---- r:0x0 DUMMY [FINISH]
-- Ruby level backtrace information ----------------------------------------
test.rb:1:in '<main>'
-- Threading information ---------------------------------------------------
Total ractor count: 1
Ruby thread count for this ractor: 1
-- C level backtrace information -------------------------------------------
miniruby(rb_assert_failure_detail+0xf0) [0x100d5f2a4] error.c:1252
miniruby(rb_assert_failure_detail) (null):0
miniruby(rb_assert_failure_detail+0x0) [0x100d5f1b4] error.c:1228
miniruby(rb_assert_failure) (null):0
miniruby(rb_get_alloc_func.cold.1+0x28) [0x100d6e660] vm_method.c:1861
miniruby(rb_get_alloc_func.cold.3) (null):0
miniruby(rb_type+0x0) [0x100ab75c0] vm_method.c:1861
miniruby(rb_get_alloc_func) (null):0
miniruby(rb_class_alloc+0x48) [0x10095e210] object.c:2286
miniruby(vm_exec_core+0x469c) [0x100aae39c] insns.def:936
miniruby(vm_exec_loop+0x0) [0x100aa74e4] vm.c:2909
miniruby(rb_vm_exec) vm.c:2912
miniruby(rb_ec_exec_node+0xa0) [0x1008b4460] eval.c:300
miniruby(ruby_run_node+0x64) [0x1008b4364] eval.c:338
miniruby(rb_main+0x1c) [0x1007e89b0] main.c:42
miniruby(main) main.c:62
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>
If a class is allocated and has a singleton class, it is incorrectly marked
as initialized which can cause an assertion error when an instance is
allocated.
For example, the following code causes an assertion error:
c = Class.allocate
c.singleton_class
c.new
Crashes with:
vm_method.c:1861: Assertion Failed: rb_get_alloc_func:allocator
ruby 4.1.0dev (2026-10-07T14:31:40Z io-uninitialized-e.. 67f4ea55a3) +PRISM [arm64-darwin27]
-- Crash Report log information --------------------------------------------
See Crash Report log file in one of the following locations:
* ~/Library/Logs/DiagnosticReports
* /Library/Logs/DiagnosticReports
for more details.
Don't forget to include the above Crash Report log file in bug reports.
-- Control frame information -----------------------------------------------
c:0002 p:0017 s:0009 E:000780 l:n b:---- r:0x0 EVAL test.rb:3 [FINISH]
c:0001 p:0000 s:0003 E:000128 l:y b:---- r:0x0 DUMMY [FINISH]
-- Ruby level backtrace information ----------------------------------------
test.rb:3:in '<main>'
-- Threading information ---------------------------------------------------
Total ractor count: 1
Ruby thread count for this ractor: 1
-- C level backtrace information -------------------------------------------
miniruby(rb_assert_failure_detail+0xf0) [0x102b57024] error.c:1268
miniruby(rb_assert_failure_detail) (null):0
miniruby(rb_assert_failure_detail+0x0) [0x102b56f34] error.c:1244
miniruby(rb_assert_failure) (null):0
miniruby(rb_get_alloc_func.cold.1+0x28) [0x102b663e0] vm_method.c:1861
miniruby(rb_get_alloc_func.cold.3) (null):0
miniruby(rb_type+0x0) [0x1028af338] vm_method.c:1861
miniruby(rb_get_alloc_func) (null):0
miniruby(rb_class_alloc+0x48) [0x102756210] object.c:2285
miniruby(vm_exec_core+0x469c) [0x1028a6114] insns.def:936
miniruby(vm_exec_loop+0x0) [0x10289f25c] vm.c:2909
miniruby(rb_vm_exec) vm.c:2912
miniruby(rb_ec_exec_node+0xa0) [0x1026ac450] eval.c:300
miniruby(ruby_run_node+0x64) [0x1026ac354] eval.c:338
miniruby(rb_main+0x1c) [0x1025e09b0] main.c:42
miniruby(main) main.c:62
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]
rb_class_duplicate_classext builds the whole copy first and the caller only
then puts it in the class's table, so a GC in the middle collects the tables
it has made and marking the class afterwards walks into a freed object. Put
the classext in the table first instead. Marking it while it is still half
built is fine, since ZALLOC leaves every field zero.
$ RUBY_BOX=1 ruby --disable-gems -e 'GC.stress = true; module Enumerable; def m; end; end'
-e:1: [BUG] try to mark T_NONE object (obj: out-of-heap:0x..., parent: ... T_MODULE/Module Enumerable)
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
`rb_freeze_singleton_class` is an implementation detail of
`RB_OBJ_FREEZE`, so deprecate direct calls from extension libraries
before removing its public declaration in Ruby 4.2.
A singleton class belongs to the owner of the object it is attached to, and
Ractor#send(move: true) hands that object over. Without this the receiver could
define singleton methods on a moved object only when its singleton class had not
been materialized before the move. The eigenclass chain above it goes too: each
of those is attached to the class below it, so by the same rule they follow.
The move is refused when any of them holds unshareable constants or variables:
the receiver would become their owner, and so allowed to read them, while the
sender still holds the objects themselves. The check runs in the preflight pass,
before anything is mutated.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Module#initialize_copy (Class#dup etc.) of a class/module created by another
Ractor makes a copy owned by the copying Ractor. Since an owner may read the
unshareable constant and instance variable values of its own classes, a raw
table copy would hand the copier unshareable objects belonging to the source's
Ractor. Raise Ractor::IsolationError in that case.
The metaclass is checked as well: rb_singleton_class_clone_and_attach copies its
fields and constants into the copy's own metaclass, which the copier then owns.
Copying classes whose constants and fields are all shareable still works, which
gives a Ractor a way to "monkey-patch" a foreign class without mutating it: dup
it and modify the copy.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Every class/module records the id of the Ractor that created it in
rb_classext_t::owner_ractor_id; only that Ractor may modify it. 0 means the
main Ractor, so a program that never leaves it stores nothing.
An id rather than the Ractor object: ids come from a monotonic counter and are
never reused, so the comparison can never meet a dangling or recycled identity,
there is nothing for the GC to trace or move, and a class does not keep its
creator alive. It also gives the termination rule for free -- once the owner
is gone no live Ractor carries its id, so no comparison can succeed and the
class is read-only for everybody.
Singleton classes and metaclasses inherit the owner of the attached
class/module instead of the Ractor which happened to trigger their lazy
creation.
rb_class_owned_p() is inline: later commits put it on the class instance
variable and constant read paths, where it replaces what used to be a single
load of ruby_single_main_ractor. Only the non-main-owner half is out of line.
That needs rb_ractor_main_p() in internal/class.h, hence the include guard
ractor_core.h was missing.
This commit only records the ownership and introduces the
rb_class_owned_p()/rb_class_owner_check() helpers; no behavior changes yet.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
A singleton class is shareable whatever it is attached to, so it can be sent
to another Ractor, and #attached_object then handed over the object itself:
$ ruby -W0 -e 'Ractor.new(p(Object.new).singleton_class) { |o| p o.attached_object }.join'
#<Object:0x0000000120768c40>
#<Object:0x0000000120768c40> # the same object in both Ractors
Raise Ractor::IsolationError when the attached object is unshareable and
lives in another Ractor's objspace. Guarding the reader covers every way a
singleton class is made: Object#clone (rb_singleton_class_clone_and_attach)
and the metaclass of a singleton class (make_metaclass) allocate one without
going through make_singleton_class, so making singleton classes unshareable
instead would have to catch all of them.
An object's owner is known from the objspace of its page, so a single-objspace
GC (mmtk) cannot make this check; the test is skipped there.
[Bug #21649]
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
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) |
ensure_origin read the m_tbl before the first write created the box-local classext, so the origin ICLASS captured the prime m_tbl while the subclass ICLASSes referred the box-local copy made by the CoW. rb_prepend_module then missed the shared m_tbl check and inserted the prepended module after the target module instead of before it.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Prepending a module to a module that already has includers backfills an
origin iclass into each includer's ancestor chain using rb_prepend_module.
The backfilled iclass is never added to a subclasses list, so
rb_clear_method_cache can't reach it when one of the module's methods is
later redefined.
Calling super through a call site that has cached the backfilled iclass
calls the old entry, even though it should be redefined.
```ruby
module M; def foo; :m; end; end
class D; include M; end
M.prepend(Module.new { def foo; super; end })
D.new.foo # prime the super cache
M.send(:define_method, :foo) { :hooked }
D.new.foo # => got :m, expected :hooked
```
This commit makes sure that the backfilled iclass is registered in the
subclasses list.
The subclasses lists are kept only in the prime classext and thus shared
by all boxes, so a module included into a builtin class in a box was
reported as a descendant from other boxes, where the include is not in
the ancestors of the class.
Check that the ICLASS is in the ancestors of its includer as seen from
the current box. An include into a class that has no classext per box,
e.g. a class defined outside the box, stays visible because it is in the
ancestors from any box.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Module#descendants returns an array of classes and modules that have
the receiver in their ancestors; it is the inverse of Module#ancestors.
The receiver itself, singleton classes, and refinements are not included.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Since a2531ba293, class_associate_super()
maintains subclass lists only for direct T_CLASS -> T_CLASS links. When
a class that includes or prepends a module is cloned, rb_mod_init_copy()
connects the clone (or its origin) to a T_ICLASS, so the clone was never
added to its real superclass's subclass list and Class#subclasses did
not return it.
Register the clone with its superclass explicitly after the chain is
built. rb_class_superclass() can be used because the superclass table
is already up to date at this point and the superclass always exists
(the root class cannot be cloned). Clones of classes without modules
are registered by class_associate_super() as before.
[Bug #22190]
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
By moving the `rb_intern` to the caller, we allow compilers to
precompute the `strlen` part of `rb_intern` which isn't a huge win,
but happens quite a lot as part of Ruby's boot process and when
requiring native gems.
* 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>
When a class or module was cloned, rb_vm_rewrite_cref would rewrite the
CREF chain of each cloned method to point at the new class. Remove this
special case so that clone behaves consistently with all other ways of
adopting a method.
This also removes the RCLASS_CLONED flag which was used to prevent
constant inline cache sharing between cloned classes [Bug #15877], and
the vm_cref_new_use_prev helper which only existed for CREF rewriting.
[Feature #21981]
Using obj_id in classext_cow_classes is slow and also is a problem when
freeing classes because the lookup requires the EC, which is not available
if it is running on a GC thread. This will cause it to crash on MMTk.
We don't need to use the obj_id as the key in the classext_cow_classes
st_table because the class itself is pinned and won't move so we can use
it as the key instead.
This change separates the master box from the root box, as the single
master copy of boxes.
Before this change, the root box is the source of copies, and also
it runs builtin classes' code. Builtin code makes changes by
requiring files, thus the source of copies is mutable, be different
from the past one. This causes different internal states of boxes,
depending on when it is created.
After this change, all boxes are created from the master box, and
the master box never runs code, so it realizes the immutable source
of copies.
This also makes is possible to separate RubyGems (and other default
gems) from each boxes. All boxes will have their own copies of RubyGems.
RubyGems has its internal state, but it'll be separated between boxes
after this change.
Otherwise some code defining ivars on a naked object early during
program boot can cause all objects to be larger than needed.
This seem like it was always the intention, but wasn't quite
properly prevented.
Also stop updating `max_iv_count` during GC marking as it is
redundant.
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.
[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: ab32c0e690
[Bug #21952]
Shallow copying the table result in the same memory being shared
between multiple box, causing double free when one of the box
is garbage collected.
Singleton classes don't need to be iterated in the subclasses list, we
only need to know that a subclass exists so that we can use the "method
entry" path for invalidation.
Copy the allocator from superclass to subclass at class creation time,
so rb_get_alloc_func no longer needs to walk the ancestor chain.
This expands on 68ffc8db08, which did this
copy, but only for classes defined in Ruby (ex. `class Foo`). And allows
us to simple read the allocator field for any class to get the correct
value.
To keep this correct when rb_define_alloc_func is called after
subclasses already exist, propagate the new allocator down to all
subclasses. An RCLASS_ALLOCATOR_DEFINED flag distinguishes classes with
an explicitly set allocator from those that inherited one, so
propagation stops at subclasses that define their own.
Currently we maintain the subclasses list for two separate purposes (we essentially have to different relationships we're putting into the same list):
1. On a T_MODULE, we track the T_ICLASSes created to include it into
other classes. Used for method invalidation and propagating includes
on the module that happen after it's been used
2. On a T_CLASS/T_ICLASS, we track the T_CLASS/T_ICLASS which are the
immediate children of the class. We use this for method invalidation,
some cvar things, and to iterate through subclasses.
Purpose 1 does not have any issues with box, the T_ICLASS always belongs
to one specific module and that's immutable. This list can be box-global
(always use the prime classext or hoist it out) and only needs to be
pruned during free. If we care about behaviour under a particular box
(ie. the propagating includes), we should look up the current box being
modified on the ICLASS itself.
Purpose 2 is more complicated. It currently tracks the immediate
children, the T_CLASS or T_ICLASS whose super points back. Because super
is per-box and is mutable (include/prepend insert ICLASSes into the
chain) we need to update the list on include/prepend, entries must be
per-box, and we can have multiple entries per-box. *I propose we
simplify this by no longer tracking the immediate subclass*, but instead
tracking the T_CLASS -> ... -> T_CLASS relationship, ie. the inverse of
rb_class_superclass. That relationship is the same across all boxes and
immutable after Class creation.
As a special case the ICLASS for refinements are also added to the
purpose 2 list (on T_CLASS). As those ICLASS do not chain to an eventual
leaf T_CLASS.
When we need to find the classes which have included a module, we can
use the module subclasses list to find the ICLASS and then use
RCLASS_INCLUDER. If we needed to iterate all T_ICLASS, we could then
walk up the CLASS_SUPER chain, but I didn't find anywhere we needed to
do that.
The following code:
```ruby
x = Object.new
sc1 = x.singleton_class
sc2 = sc1.singleton_class
x.freeze
```
Would freeze sc1 but not sc2, even though sc1 would be frozen.
Handle this by walking the class pointer chain for the object.
If the class is a singleton class, and it isn't frozen, and the
attached object for the singleton class is the object, the
singleton class should be frozen, and we move to the next iteration.
Fixes [Bug #20319]
Without this change, classes (including iclass) are allocated
as un-boxable classes after initializing user boxes (after starting
script evaluation). Under this situation, iclasses are created as
un-boxabled class when core modules are included by a class in the
root box, then it causes problems because it's in the root box but
it can't have multiple classexts.
This change makes it possible to allocate boxable classes even after
initializing user boxes. Classes create in the root box will be
boxable, and those can have 2 or more classexts.
Since singleton classes are created lazily, we need to make sure that
we lock around their creation. Unfortunately, that means we need to
lock around every shareable object's call to `singleton_class`,
including classes and modules.
Safe multi-ractor subclass list mutation
We need to lock around mutation and accesses of a class's subclasses
list. Unfortunately we also need to do this when creating singleton
classes, as the singleton class does need to go into `super`'s
subclasses list for CC invalidation purposes.