This was previously prohibited as it wasn't handled correctly. The
reason it wasn't handled correctly is that at the point of the super
call, the refinement module didn't have the class super pointer
set correctly, so it couldn't determine the ancestor to start the
lookup for the super method to call. Fix that by lazily creating a
refinement module iclass with the class super pointer set correctly.
This is cached in a hidden hash so it's only created the first time
it is needed (similar to how singleton classes are created).
Unfortunately, storing this hidden hash in an instance variable in
the iclass failed CI due to shape_id layout mismatch errors. Storing
the hidden hash in an instance variable of the class/module itself is
possible, but that doesn't work if the class/module is frozen. So the
only option is to store this in rb_classext_struct. I didn't want to
increase the size of rb_classext_struct, so this uses the classpath
member to store the hash. That member is currently only used for
classes/modules and not for iclasses, so it shouldn't cause any
problems. Ultimately, I think the classpath entry should be moved
into the as union as that would be easier to understand. However, I
did not do so in this commit in order to avoid unnecessary churn.
By lazily creating the iclass, we don't create unnecessary iclasses.
For example, if Enumerable is refined, this only generates refinement
module iclasses for the classes using Enumerable where the refinement
method is actually called.
While working on specs for this, I ran into an infinite loop because
current_method_entry didn't correctly handle bmethods. So update
current_method_entry to handle that.
Implements [Feature #22309]
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>
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>
After 4bb1977b78 switched RCLASS_PRIME_CLASSEXT_WRITABLE_P to FL_TEST_RAW,
GCC lost the RB_FL_ABLE-derived hint that klass is not a special const,
and started warning that RCLASS_EXT_PRIME(klass) (used by the inlined
RCLASS_WRITE_SUPER in class_associate_super) could yield a near-zero
pointer. Make the existing VM_ASSERT(klass != 0) precondition visible
to the optimizer via RBIMPL_ASSUME so the warning goes away without
re-introducing the FL_TEST overhead.
Co-authored-by: Nobuyoshi Nakada <nobu@ruby-lang.org>
A `Method#super_method` chain should return the methods that `super`
would call if the method was called normally. Previously, there were
multiple problems:
* There was an infinite `super_method` loop for refined methods.
* Lookup considered the refinements activated at the call site of
`super_method`, and not all the call site of `super` inside the
method.
This tries to recreate the logic that `super` uses inside
`super_method`. It avoids the loop. It also considers the refinements
activated for the method itself, not for the caller of `super_method`,
correctly handling refinements in outer scopes of the method. This
requires avoiding the use of rb_callable_method_entry_with_refinements,
which implicitly will consider refinements activated in the caller of
`super_method`.
The added tests attempt to ensure that the `super_method` lookup chain
matches the methods that `super` calls if you call the method.
This adds an `RICLASS_FOR_REFINEMENT_P` helper method, for logic that
is used a couple times in the new code and once in
`vm_search_normal_superclass`.
FL_TEST -> FL_TEST_RAW
FL_SET -> FL_SET_RAW
I was seeing bad performance on the ruby-bench getivar-module benchmark
when using yjit. This was with other changes in a branch, but changes
totally unrelated to accessing ivars on a class or using boxes. The
change to RCLASS_PRIME_CLASSEXT_WRITABLE_P was the one that made the
difference, but I changed a couple of other FL_SET uses as well. This
change reduced the benchmark runs from 60ms to 40ms.
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]
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.
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.
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.
[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
Replace the RVALUE_SLOT_SIZE-multiplier based pool sizes with explicit
power-of-two (and near-power-of-two) slot sizes. On 64-bit this gives
12 heaps (32, 40, 64, 80, 96, 128, 160, 256, 512, 640, 768, 1024)
instead of 5, providing finer granularity and less internal
fragmentation. On 32-bit the layout is 5 heaps (32, 64, 128, 256, 512).
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.
This rewrites the class allocator search to be faster. Instead of using
RCLASS_SUPER, which is now even slower due to Box, we can scan the
superclasses list to find a class where the allocator is defined.
This also disallows allocating from an ICLASS. Previously I believe that
was only done for FrozenCore, and that was changed in
e596cf6e93.
This reverts commit 2998c8d6b9.
We need to find a better way to fix this bug. Even with this refcount
change, errors were still being seen in CI. For now we need to remove
this failing test.
Calling the usual rb_iclass_classext_free() causes SEGV because
duplicating a newer classext of iclass had set the reference from superclass
to the newer classext, but calling rb_iclass_classext_free() deletes it.
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
The st_insert in RCLASS_SET_NAMESPACE_CLASSEXT may overwrite an existing
rb_classext_t, causing it to leak memory. This commit changes it to use
st_update to free the existing one before overwriting it.
to fix inconsistent and wrong current namespace detections.
This includes:
* Moving load_path and related things from rb_vm_t to rb_namespace_t to simplify
accessing those values via namespace (instead of accessing either vm or ns)
* Initializing root_namespace earlier and consolidate builtin_namespace into root_namespace
* Adding VM_FRAME_FLAG_NS_REQUIRE for checkpoints to detect a namespace to load/require files
* Removing implicit refinements in the root namespace which was used to determine
the namespace to be loaded (replaced by VM_FRAME_FLAG_NS_REQUIRE)
* Removing namespaces from rb_proc_t because its namespace can be identified by lexical context
* Starting to use ep[VM_ENV_DATA_INDEX_SPECVAL] to store the current namespace when
the frame type is MAGIC_TOP or MAGIC_CLASS (block handlers don't exist in this case)
It is much more convenient than storing the klass, especially
when dealing with `object_id` as it allows to update the id2ref
table without having to dereference the owner, which may be
garbage at that point.
For now this doesn't change anything, but now that the table
is managed by GC, it opens the door to use RCU when in multi-ractor
mode, hence allow unsynchornized reads.
Previously we had an assertion that the method table was only set on
young objects, and a comment stating that was how it needed to be used.
I think that confused the complexity of the write barriers that may be
needed here.
* Setting an empty M_TBL never needs a write barrier
* T_CLASS and T_MODULE should always fire a write barrier to newly added
methods
* T_ICLASS only needs a write barrier to methods when
RCLASSEXT_ICLASS_IS_ORIGIN(x) && !RCLASSEXT_ICLASS_ORIGIN_SHARED_MTBL(x)
We shouldn't assume that the object being young is sufficient, because
we also need write barriers for incremental marking and it's unreliable.
Even when namespaces are enabled, only a few core classes created
during init will eventually be namespaced.
For these it's OK to allocate a 320B slot to hold the extra namespace
stuff.
But for any class created post init, we know we'll never need the
namespace and we can fit in a 160B slot.
Now that class fields have been deletated to a T_IMEMO/class_fields
when we're in multi-ractor mode, we can read and write class instance
variable in an atomic way using Read-Copy-Update (RCU).
Note when in multi-ractor mode, we always use RCU. In theory
we don't need to, instead if we ensured the field is written
before the shape is updated it would be safe.
Benchmark:
```ruby
Warning[:experimental] = false
class Foo
@foo = 1
@bar = 2
@baz = 3
@egg = 4
@spam = 5
class << self
attr_reader :foo, :bar, :baz, :egg, :spam
end
end
ractors = 8.times.map do
Ractor.new do
1_000_000.times do
Foo.bar + Foo.baz * Foo.egg - Foo.spam
end
end
end
if Ractor.method_defined?(:value)
ractors.each(&:value)
else
ractors.each(&:take)
end
```
This branch vs Ruby 3.4:
```bash
$ hyperfine -w 1 'ruby --disable-all ../test.rb' './miniruby ../test.rb'
Benchmark 1: ruby --disable-all ../test.rb
Time (mean ± σ): 3.162 s ± 0.071 s [User: 2.783 s, System: 10.809 s]
Range (min … max): 3.093 s … 3.337 s 10 runs
Benchmark 2: ./miniruby ../test.rb
Time (mean ± σ): 208.7 ms ± 4.6 ms [User: 889.7 ms, System: 6.9 ms]
Range (min … max): 202.8 ms … 222.0 ms 14 runs
Summary
./miniruby ../test.rb ran
15.15 ± 0.47 times faster than ruby --disable-all ../test.rb
```