Data.new turned positional arguments into a keyword Hash and called
initialize with it. When initialize is the default Data#initialize and
every member is given, that call can only store the members and freeze
the instance, so do that directly. A TracePoint on c_call no longer sees
Data#initialize for such calls.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
struct_alloc() sizes an instance by the member count of its class, so the
instance's own length can be used instead of looking the hidden
__members__ ivar up on the class again.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
rb_struct_pos hands an out-of-range numeric index back through LEN2NUM, which makes a Bignum past the Fixnum range, and invalid_struct_pos then treated it as a member name and raised NameError.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Once rb_len_t widens this changes the `long *` output parameters of rb_range_beg_len(), rb_str_subpos(), rb_arithmetic_sequence_beg_len_step(), ruby_enc_find_basename() and ruby_enc_find_extname(), so extensions passing `long *` to them will need to be updated. The extension guides list these signatures verbatim and follow along.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
For small-enough Struct instances (<= 256 members, to avoid generating too much code), inline the initialize method.
This makes creating and initializing Structs very very fast and also gives more information to the load-store pass since the process is less opaque.
It's mostly just a frozen-ness check and a bunch of stores.
```
-------------------- ------------ ------------ -------------- -------------
bench master (ms) branch (ms) branch 1st itr master/branch
lee 129.1 ± 1.3% 117.8 ± 1.4% 1.060 1.096
struct-new-no-escape 38.5 ± 1.8% 19.8 ± 1.3% 1.393 1.949
-------------------- ------------ ------------ -------------- -------------
```
(Following https://github.com/ruby/ruby/pull/18835 )
We can use `ObjectAllocClass` for Structs because they are basically
a `__members__` ivar lookup for sizing plus the default allocator.
Requires #18888 to make sure the constructor does not raise.
We should not ever hit this from Ruby code; user code cannot modify
ivars that do not start with @.
This makes `num_members` leaf and therefore Struct class allocation also
leaf.
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>
`rb_gc_obj_slot_size` has to check the page metadata to
get the slot size, which likely cause cache misses.
`rb_obj_shape_slot_size` does simple arithmetic using
only a few shape bits.
```
compare-ruby: ruby 4.1.0dev (2026-07-08T03:48:16Z master 01c5b2e890) +PRISM [arm64-darwin25]
built-ruby: ruby 4.1.0dev (2026-07-08T10:17:41Z gc-changed-slot-size 08029b0f7c) +PRISM [arm64-darwin25]
warming up.
```
| |compare-ruby|built-ruby|
|:-----------------------|-----------:|---------:|
|binary_concat_embedded | 5.262M| 5.707M|
| | -| 1.08x|
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.
RSTRUCT_LEN / RSTRUCT_GET / RSTRUCT_SET all existing in two
versions, one public that does type and frozens checks
and one private that doesn't.
The problem is that this is error prone because the public version
is always accessible, but the private one require to include
`internal/struct.h`. So you may have some code that rely on the
public version, and later on the private header is included and
changes the behavior.
This already led to introducing a bug in YJIT & ZJIT:
https://github.com/ruby/ruby/pull/15835
Anonymous memberless Structs and Data were returning `#<struct >` and
`#<data >` with a trailing space. Now they return `#<struct>` and
`#<data>` to match attrless class behavior and look a bit more compact.
We don't set RSTRUCT_GEN_FIELDS when RCLASS_MAX_IV_COUNT(klass) != 0, so
we need to set RSTRUCT_SET_FIELDS_OBJ to 0 otherwise it may have an
invalid value and crash.
[Bug #21710]
- struct.c: `struct_alloc`
It is possible for a `NEWOBJ` tracepoint call back to write fields
into a newly allocated object before `struct_alloc` had the time
to set the `RSTRUCT_GEN_FIELDS` flags and such.
Hence we can't blindly initialize the `fields_obj` reference to `0`
we first need to check no fields were added yet.
- object.c: `rb_class_allocate_instance`
Similarly, if a `NEWOBJ` tracepoint tries to set fields on the object,
the `shape_id` must already be set, as it's required on T_OBJECT to
know where to write fields.
`NEWOBJ_OF` had to be refactored to accept a `shape_id`.
Now that we no longer explicitly set the first three elements, we need
to ensure the object is in a state safe for GC before we call
struct_heap_alloc, which may GC.
It's not rare for structs to have additional ivars, hence are one
of the most common, if not the most common type in the `gen_fields_tbl`.
This can cause Ractor contention, but even in single ractor mode
means having to do a hash lookup to access the ivars, and increase
GC work.
Instead, unless the struct is perfectly right sized, we can store
a reference to the associated IMEMO/fields object right after the
last struct member.
```
compare-ruby: ruby 3.5.0dev (2025-08-06T12:50:36Z struct-ivar-fields-2 9a30d141a1) +PRISM [arm64-darwin24]
built-ruby: ruby 3.5.0dev (2025-08-06T12:57:59Z struct-ivar-fields-2 2ff3ec237f) +PRISM [arm64-darwin24]
warming up.....
| |compare-ruby|built-ruby|
|:---------------------|-----------:|---------:|
|member_reader | 590.317k| 579.246k|
| | 1.02x| -|
|member_writer | 543.963k| 527.104k|
| | 1.03x| -|
|member_reader_method | 213.540k| 213.004k|
| | 1.00x| -|
|member_writer_method | 192.657k| 191.491k|
| | 1.01x| -|
|ivar_reader | 403.993k| 569.915k|
| | -| 1.41x|
```
Co-Authored-By: Étienne Barrié <etienne.barrie@gmail.com>
Previously it would bypass the `FL_ABLE` check, but
since shapes introduction, it started having a different
behavior than `OBJ_FREEZE`, as it would onyl set the `FL_FREEZE`
flag, but not update the shape.
I have no indication of this causing a bug yet, but it seems
like a trap waiting to happen.
This `st_table` is used to both mark and pin classes
defined from the C API. But `vm->mark_object_ary` already
does both much more efficiently.
Currently a Ruby process starts with 252 rooted classes,
which uses `7224B` in an `st_table` or `2016B` in an `RArray`.
So a baseline of 5kB saved, but since `mark_object_ary` is
preallocated with `1024` slots but only use `405` of them,
it's a net `7kB` save.
`vm->mark_object_ary` is also being refactored.
Prior to this changes, `mark_object_ary` was a regular `RArray`, but
since this allows for references to be moved, it was marked a second
time from `rb_vm_mark()` to pin these objects.
This has the detrimental effect of marking these references on every
minors even though it's a mostly append only list.
But using a custom TypedData we can save from having to mark
all the references on minor GC runs.
Addtionally, immediate values are now ignored and not appended
to `vm->mark_object_ary` as it's just wasted space.
[Bug #20311]
`rb_define_class_under` assumes it's called from C and that the
reference might be held in a C global variable, so it adds the
class to the VM root.
In the case of `Struct.new('Name')` it's wasteful and make
the struct immortal.