124 Commits

Author SHA1 Message Date
John Hawthorn
32e6dc0f31 Speed up class allocator search
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
e596cf6e93dbf121e197cccfec8a69902e00eda3.
2025-12-11 09:53:10 -08:00
Satoshi Tagomori
9eafeaed67 Box: Free rb_classext_t struct for a box when the box is GCed 2025-12-02 23:49:49 +09:00
Luke Gruber
148fde2754
Revert "ns_subclasses refcount accesses need to be atomic (#15083)" (#15138)
This reverts commit 2998c8d6b99ec49925ebea42198b29c3e27b34a7.

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.
2025-11-11 02:52:43 +00:00
John Hawthorn
6238b6f53e Remove unused subclass methods 2025-11-10 15:55:38 -08:00
Satoshi Tagomori
d2a587c791 renaming internal data structures and functions from namespace to box 2025-11-07 13:14:54 +09:00
Satoshi Tagomori
aaa1234702 update referenced filenames from namespace to box 2025-11-07 13:14:54 +09:00
Luke Gruber
2998c8d6b9
ns_subclasses refcount accesses need to be atomic (#15083)
We were seeing errors like:

```
* thread #8, stop reason = EXC_BAD_ACCESS (code=1, address=0x803)
  * frame #0: 0x00000001001fe944 ruby`rb_st_lookup(tab=0x00000000000007fb, key=1, value=0x00000001305b7490) at st.c:1066:22
    frame #1: 0x000000010002d658 ruby`remove_class_from_subclasses [inlined] class_get_subclasses_for_ns(tbl=0x00000000000007fb, ns_id=1) at class.c:604:9
    frame #2: 0x000000010002d650 ruby`remove_class_from_subclasses(tbl=0x00000000000007fb, ns_id=1, klass=4754039232) at class.c:620:34
    frame #3: 0x000000010002c8a8 ruby`rb_class_classext_free_subclasses(ext=0x000000011b5ce1d8, klass=4754039232, replacing=<unavailable>) at class.c:700:9
    frame #4: 0x000000010002c760 ruby`rb_class_classext_free(klass=4754039232, ext=0x000000011b5ce1d8, is_prime=true) at class.c:105:5
    frame #5: 0x00000001000e770c ruby`classext_free(ext=<unavailable>, is_prime=<unavailable>, namespace=<unavailable>, arg=<unavailable>) at gc.c:1231:5 [artificial]
    frame #6: 0x000000010002d178 ruby`rb_class_classext_foreach(klass=<unavailable>, func=(ruby`classext_free at gc.c:1228), arg=0x00000001305b75c0) at class.c:518:5
    frame #7: 0x00000001000e745c ruby`rb_gc_obj_free(objspace=0x000000012500c400, obj=4754039232) at gc.c:1282:9
    frame #8: 0x00000001000e70d4 ruby`gc_sweep_plane(objspace=0x000000012500c400, heap=<unavailable>, p=4754039232, bitset=4095, ctx=0x00000001305b76e8) at default.c:3482:21
    frame #9: 0x00000001000e6e9c ruby`gc_sweep_page(objspace=0x000000012500c400, heap=0x000000012500c540, ctx=0x00000001305b76e8) at default.c:3567:13
    frame #10: 0x00000001000e51d0 ruby`gc_sweep_step(objspace=0x000000012500c400, heap=0x000000012500c540) at default.c:3848:9
    frame #11: 0x00000001000e1880 ruby`gc_continue [inlined] gc_sweep_continue(objspace=0x000000012500c400, sweep_heap=0x000000012500c540) at default.c:3931:13
    frame #12: 0x00000001000e1754 ruby`gc_continue(objspace=0x000000012500c400, heap=0x000000012500c540) at default.c:2037:9
    frame #13: 0x00000001000e10bc ruby`newobj_cache_miss [inlined] heap_prepare(objspace=0x000000012500c400, heap=0x000000012500c540) at default.c:2056:5
    frame #14: 0x00000001000e1074 ruby`newobj_cache_miss [inlined] heap_next_free_page(objspace=0x000000012500c400, heap=0x000000012500c540) at default.c:2280:9
    frame #15: 0x00000001000e106c ruby`newobj_cache_miss(objspace=0x000000012500c400, cache=0x0000600001b00300, heap_idx=2, vm_locked=false) at default.c:2387:38
    frame #16: 0x00000001000e0d28 ruby`newobj_alloc(objspace=<unavailable>, cache=<unavailable>, heap_idx=<unavailable>, vm_locked=<unavailable>) at default.c:2411:15 [artificial]
    frame #17: 0x00000001000d7214 ruby`newobj_of [inlined] rb_gc_impl_new_obj(objspace_ptr=<unavailable>, cache_ptr=<unavailable>, klass=<unavailable>, flags=<unavailable>, wb_protected=<unavailable>, alloc_size=<unavailable>) at default.c:2490:15
    frame #18: 0x00000001000d719c ruby`newobj_of(cr=<unavailable>, klass=4313971728, flags=258, wb_protected=<unavailable>, size=<unavailable>) at gc.c:995:17
    frame #19: 0x00000001000d73ec ruby`rb_wb_protected_newobj_of(ec=<unavailable>, klass=<unavailable>, flags=<unavailable>, size=<unavailable>) at gc.c:1044:12 [artificial]
    frame #20: 0x0000000100032d34 ruby`class_alloc0(type=<unavailable>, klass=4313971728, namespaceable=<unavailable>) at class.c:803:5
```
2025-11-06 16:32:20 -05:00
Satoshi Tagomori
f53b8194cd Stop deleting the reference from superclass when replacing classext.
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.
2025-10-26 17:49:39 +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
4a23b6a89f Fix memory leak in RCLASS_SET_NAMESPACE_CLASSEXT
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.
2025-10-21 18:42:17 -04:00
Peter Zhu
cd42096f5a Move rb_class_classext_free to class.c 2025-10-21 18:42:17 -04:00
Satoshi Tagomori
2548c476a3 Add namespace debug methods and assertions 2025-10-07 14:19:26 +09:00
Satoshi Tagomori
4f47327287 Update current namespace management by using control frames and lexical contexts
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)
2025-09-29 01:15:38 +09:00
John Hawthorn
feb8331673 Atomic CC table set in cache_callable_method_entry 2025-08-21 09:17:31 +02:00
Jean Boussier
10aa4134d4 imemo_fields: store owner object in RBasic.klass
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.
2025-08-13 19:53:18 +02:00
Jean Boussier
f2a7e48dea Make RClass.cc_table a managed object
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.
2025-08-01 10:42:04 +02:00
John Hawthorn
9256442615 Cleanup M_TBL workarounds and comments
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.
2025-07-23 14:33:55 -07:00
Jean Boussier
517c106709 imemo_fields_set: save copying when reassigning a variable
If we still fit in the existing imemo/fields object we can
update it atomically, saving a reallocation.
2025-07-03 09:20:22 +02:00
Jean Boussier
32ee3fab0a Shink RClass when it is known they can't be namespaced
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.
2025-06-23 10:04:58 +01:00
Jean Boussier
ea4a53c595 Avoid creating namespace table for classes that can't be namespaced. 2025-06-23 10:04:58 +01:00
Jean Boussier
96a0c2065a Mark RClass instance that may be namespaced with RCLASS_NAMESPACEABLE 2025-06-23 10:04:58 +01:00
Jean Boussier
164486a954 Refactor rb_imemo_fields_new to not assume T_CLASS 2025-06-17 15:28:05 +02:00
Jean Boussier
fb68721f63 Rename imemo_class_fields -> imemo_fields 2025-06-17 15:28:05 +02:00
Jean Boussier
9e839d3c0e Optimize benchmark/vm_ivar_of_class
```
compare-ruby: ruby 3.5.0dev (2025-06-17T08:45:40Z master e9d35671d2) +PRISM [arm64-darwin24]
last_commit=[ruby/json] Fix a typo
built-ruby: ruby 3.5.0dev (2025-06-17T09:27:05Z opt-getivar-for-cl.. ed1d7cd778) +PRISM [arm64-darwin24]

|                      |compare-ruby|built-ruby|
|:---------------------|-----------:|---------:|
|vm_ivar_of_class_set  |     12.306M|   13.957M|
|                      |           -|     1.13x|
|vm_ivar_of_class      |     16.167M|   24.029M|
|                      |           -|     1.49x|
```
2025-06-17 13:00:31 +02:00
Jean Boussier
a74c385208 Make setting and accessing class ivars lock-free
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
```
2025-06-12 14:55:13 +02:00
Jean Boussier
8b5ac5abf2 Fix class instance variable inside namespaces
Now that classes fields are delegated to an object with its own
shape_id, we no longer need to mark all classes as TOO_COMPLEX.
2025-06-12 13:43:29 +02:00
Jean Boussier
3abdd4241f Turn rb_classext_t.fields into a T_IMEMO/class_fields
This behave almost exactly as a T_OBJECT, the layout is entirely
compatible.

This aims to solve two problems.

First, it solves the problem of namspaced classes having
a single `shape_id`. Now each namespaced classext
has an object that can hold the namespace specific
shape.

Second, it open the door to later make class instance variable
writes atomics, hence be able to read class variables
without locking the VM.
In the future, in multi-ractor mode, we can do the write
on a copy of the `fields_obj` and then atomically swap it.

Considerations:

  - Right now the `RClass` shape_id is always synchronized,
    but with namespace we should likely mark classes that have
    multiple namespace with a specific shape flag.
2025-06-12 07:58:16 +02:00
Jean Boussier
95201299fd Refactor the last references to rb_shape_t
The type isn't opaque because Ruby isn't often compiled with LTO,
so for optimization purpose it's better to allow as much inlining
as possible.

However ideally only `shape.c` and `shape.h` should deal with
the actual struct, and everything else should just deal with opaque
`shape_id_t`.
2025-06-11 16:38:38 +02:00
Jean Boussier
625d6a9cbb Get rid of frozen shapes.
Instead `shape_id_t` higher bits contain flags, and the first one
tells whether the shape is frozen.

This has multiple benefits:
  - Can check if a shape is frozen with a single bit check instead of
    dereferencing a pointer.
  - Guarantees it is always possible to transition to frozen.
  - This allow reclaiming `FL_FREEZE` (not done yet).

The downside is you have to be careful to preserve these flags
when transitioning.
2025-06-04 07:59:20 +02:00
John Hawthorn
6a62a46c3c Read {max_iv,variation}_count from prime classext
MAX_IV_COUNT is a hint which determines the size of variable width
allocation we should use for a given class. We don't need to scope this
by namespace, if we end up with larger builtin objects on some
namespaces that isn't a user-visible problem, just extra memory use.

Similarly variation_count is used to track if a given object has had too
many branches in shapes it has used, and to use too_complex when that
happens. That's also just a hint, so we can use the same value across
namespaces without it being visible to users.

Previously variation_count was being incremented (written to) on the
RCLASS_EXT_READABLE ext, which seems incorrect if we wanted it to be
different across namespaces
2025-05-29 16:02:07 -04:00
John Hawthorn
d1343e12d2 Use flag for RCLASS_IS_INITIALIZED
Previously we used a flag to set whether a module was uninitialized.
When checked whether a class was initialized, we first had to check that
it had a non-zero superclass, as well as that it wasn't BasicObject.

With the advent of namespaces, RCLASS_SUPER is now an expensive
operation, and though we could just check for the prime superclass, we
might as well take this opportunity to use a flag so that we can perform
the initialized check with as few instructions as possible.

It's possible in the future that we could prevent uninitialized classes
from being available to the user, but currently there are a few ways to
do that.
2025-05-28 11:44:07 -04:00
John Hawthorn
f483befd90 Add shape_id to RBasic under 32 bit
This makes `RBobject` `4B` larger on 32 bit systems
but simplifies the implementation a lot.

[Feature #21353]

Co-authored-by: Jean Boussier <byroot@ruby-lang.org>
2025-05-26 10:31:54 +02:00
Nobuyoshi Nakada
aad9fa2853
Use RB_VM_LOCKING 2025-05-25 15:22:43 +09:00
John Hawthorn
4f9f2243e9 Stricter assert for RCLASS_ALLOCATOR
I'd like to make this only valid to T_CLASS also, but currently it is
called in some places for T_ICLASS and expected to return 0.
2025-05-23 10:33:48 -07:00
John Hawthorn
05cdcfcefd Only call RCLASS_SET_ALLOCATOR on T_CLASS objects
It's invalid to set an allocator on a T_ICLASS or T_MODULE, as those use
the other fields from the union.
2025-05-23 10:33:48 -07:00
John Hawthorn
11ad7f5f47 Don't use namespaced classext for superclasses
Superclasses can't be modified by user code, so do not need namespace
indirection. For example Object.superclass is always BasicObject, no
matter what modules are included onto it.
2025-05-23 10:22:24 -07:00
Jean Boussier
9400119702 Fix object_id for classes and modules in namespace context
Given classes and modules have a different set of fields in every
namespace, we can't store the object_id in fields for them.

Given that some space was freed in `RClass` we can store it there
instead.
2025-05-14 10:26:48 +02:00
Jean Boussier
130d6aaef2 Reclaim one VALUE from rb_classext_t by shrinking super_classdepth
By making `super_classdepth` `uint16_t`, classes and modules can
now fit in 160B slots again.

The downside of course is that before `super_classdepth` was large
enough we never had to care about overflow, as you couldn't
realistically create enough classes to ever go over it.

With this change, while it is stupid, you could realistically
create an ancestor chain containing 65k classes and modules.
2025-05-14 10:17:03 +02:00
Jean Boussier
2ca8769443 Reclaim one VALUE from rb_classext_t
The `includer` field is only used for `T_ICLASS`, so by moving
it into the existing union we can save one `VALUE` per class
and module.
2025-05-13 14:55:39 +02:00
Satoshi Tagomori
f0b41ef669 Describe the basic documents of Namespace 2025-05-11 23:32:50 +09:00
Satoshi Tagomori
8ecc04dc04 Delete code for debugging namespace 2025-05-11 23:32:50 +09:00
Satoshi Tagomori
90e5ce6132 Rename RCLASS_EXT() macro to RCLASS_EXT_PRIME() to prevent using it wrongly
The macro RCLASS_EXT() accesses the prime classext directly, but it can be
valid only in a limited situation when namespace is enabled.
So, to prevent using RCLASS_EXT() in the wrong way, rename the macro and
let the developer check it is ok to access the prime classext or not.
2025-05-11 23:32:50 +09:00
Satoshi Tagomori
ff790c759e Compact prime classext readable/writable flags
To make RClass size smaller, move flags of prime classext readable/writable to:
 readable - use ns_classext_tbl is NULL or not (if NULL, it's readable)
 writable - use FL_USER2 of RBasic flags
2025-05-11 23:32:50 +09:00
Satoshi Tagomori
5ee1ec313a initialize method tables before any GC chance 2025-05-11 23:32:50 +09:00
Satoshi Tagomori
f24ba27d6d avoid calling ZALLOC after NEWOBJ_OF for RClass: need to return RClass not promoted 2025-05-11 23:32:50 +09:00
Yusuke Endoh
8d3cd4301a Remove unnecessary prototype declarations
```
internal/class.h:158:20: warning: ‘RCLASS_SET_CLASSEXT_TABLE’ declared ‘static’ but never defined [-Wunused-function]
  158 | static inline void RCLASS_SET_CLASSEXT_TABLE(VALUE obj, st_table *tbl);
      |                    ^~~~~~~~~~~~~~~~~~~~~~~~~
internal/class.h:271:20: warning: ‘RCLASS_WRITE_SUBCLASSES’ declared ‘static’ but never defined [-Wunused-function]
  271 | static inline void RCLASS_WRITE_SUBCLASSES(VALUE klass, rb_subclass_anchor_t *anchor);
      |                    ^~~~~~~~~~~~~~~~~~~~~~~
```
2025-05-11 23:32:50 +09:00
Satoshi Tagomori
382645d440 namespace on read 2025-05-11 23:32:50 +09:00
Jean Boussier
3f7c0af051 Rename rb_shape_obj_too_complex -> rb_shape_obj_too_complex_p 2025-05-09 10:22:51 +02:00
Jean Boussier
334ebba221 Rename rb_shape_get_shape_by_id -> RSHAPE 2025-05-09 10:22:51 +02:00
Jean Boussier
0ea210d1ea Rename ivptr -> fields, next_iv_index -> next_field_index
Ivars will longer be the only thing stored inline
via shapes, so keeping the `iv_index` and `ivptr` names
would be confusing.

Instance variables won't be the only thing stored inline
via shapes, so keeping the `ivptr` name would be confusing.

`field` encompass anything that can be stored in a VALUE array.

Similarly, `gen_ivtbl` becomes `gen_fields_tbl`.
2025-05-08 07:58:05 +02:00