| Age | Commit message (Collapse) | Author |
|
|
|
Before this patch, Ractor::IsolationError reported an incorrect constant
path when constant was found through `rb_const_get_0()`.
In this code, Ractor::IsolationError reported illegal access against
`M::TOPLEVEL`, where it should be `Object::TOPLEVEL`.
```ruby
TOPLEVEL = [1]
module M
def self.f
TOPLEVEL
end
end
Ractor.new { M.f }.value
```
This was because `rb_const_get_0()` built the "path" part referring to
the module/class passed to it in the first place. When a constant was
found through recursive search upwards, the module/class which the
constant was found should be reported.
This patch fixes this issue by modifying rb_const_search() to take a
VALUE pointer to be filled with the module/class where the constant was
found.
[Bug #21782]
|
|
|
|
|
|
|
|
|
|
The "EXIVAR" terminology has been replaced by "gen fields"
AKA "generic fields".
Exivar implies variable, but generic fields include more than
just variables, e.g. `object_id`.
|
|
I don't think this ever happened, but we should raise the same error
we'd raise on lookup
|
|
We rely on the GC to clear this when the GC is run on another EC than
the cache.
|
|
This fixes a bug where the gen_fields_cache could become invalid when
the last ivar was removed. Also adds more assertions.
|
|
|
|
|
|
We can't run arbitrary ruby code with the VM lock held.
|
|
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
|
|
We need to free the rb_const_entry_t we remove from the RCLASS_WRITABLE_CONST_TBL
otherwise it will leak memory.
|
|
|
|
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)
|
|
If we alias two variable multiple times, for example like this:
alias $foo $bar
alias $foo $bar
Then we will increment the counter for entry2->var->counter each time,
which is not correct because we are not adding more references to
entry2->var. This reports as a memory leak in RUBY_FREE_AT_EXIT because
entry2->var->counter will never reach 0 and thus will never be freed.
|
|
entry1 is allocated using xmalloc (through ALLOC), which returns undefined
memory. We never set the ractor_local field, so the value is undefined.
|
|
Using `assume_single_ractor_mode` we can skip all ractor safety
checks if we're in single ractor mode.
```
compare-ruby: ruby 3.5.0dev (2025-08-27T14:58:58Z merge-vm-setivar-d.. 5b749d8e53) +YJIT +PRISM [arm64-darwin24]
built-ruby: ruby 3.5.0dev (2025-08-28T21:23:38Z yjit-get-exivar 3cc21b76d4) +YJIT +PRISM [arm64-darwin24]
| |compare-ruby|built-ruby|
|:--------------------------|-----------:|---------:|
|vm_ivar_get_on_obj | 975.981| 975.772|
| | 1.00x| -|
|vm_ivar_get_on_class | 136.214| 470.912|
| | -| 3.46x|
|vm_ivar_get_on_generic | 148.315| 299.122|
| | -| 2.02x|
```
|
|
While accessing the ivars of other types is too complicated to
realistically generate the ASM for it, we can at least provide
the ivar index as to not have to lookup the shape tree every
time.
```
compare-ruby: ruby 3.5.0dev (2025-08-27T14:58:58Z merge-vm-setivar-d.. 5b749d8e53) +YJIT +PRISM [arm64-darwin24]
built-ruby: ruby 3.5.0dev (2025-08-28T17:58:32Z yjit-get-exivar efaa8c9b09) +YJIT +PRISM [arm64-darwin24]
| |compare-ruby|built-ruby|
|:--------------------------|-----------:|---------:|
|vm_ivar_get_on_obj | 930.458| 936.865|
| | -| 1.01x|
|vm_ivar_get_on_class | 134.471| 431.622|
| | -| 3.21x|
|vm_ivar_get_on_generic | 146.679| 284.408|
| | -| 1.94x|
```
Co-Authored-By: Aaron Patterson <tenderlove@ruby-lang.org>
|
|
Right now JITs don't generate any code to access ivar on types
other than T_OBJECT, but they might soon, so we must ensure
two IMEMO/fields can't have the same `shape_id` but diffent
embed/heap status.
|
|
`rb_ivar_delete` would force a new shape to be created.
And in the case of a shape exhaustion, it wouldn't handle
T_CLASS/T_MODULE correctly.
|
|
`vm_setinstancevariable` had a codepath to try to match the inline
cache for types other than T_OBJECT, but the cache population path
in `vm_setivar_slowpath` was exclusive to T_OBJECT, so `vm_setivar_default`
would never match anything.
This commit improves `vm_setivar_slowpath` so that it is capable of
filling the cache for all types, and adds a `vm_setivar_class` codepath
for `T_CLASS` and `T_MODULE`.
`vm_setivar`, `vm_setivar_default` and `vm_setivar_class` could be unified,
but based on the very explicit `NOINLINE` I assume they were split to minimize
codesize.
```
compare-ruby: ruby 3.5.0dev (2025-08-27T14:58:58Z merge-vm-setivar-d.. 5b749d8e53) +PRISM [arm64-darwin24]
built-ruby: ruby 3.5.0dev (2025-08-27T16:30:31Z setivar-cache-gene.. 4fe78ff296) +PRISM [arm64-darwin24]
| |compare-ruby|built-ruby|
|:------------------------|-----------:|---------:|
|vm_ivar_set_on_instance | 161.809| 164.688|
| | -| 1.02x|
|vm_ivar_set_on_generic | 58.769| 115.638|
| | -| 1.97x|
|vm_ivar_set_on_class | 70.034| 141.042|
| | -| 2.01x|
```
|
|
The embed layout is way more common than the heap one,
especially since WVA.
I think it makes for more readable code to inverse the
flag.
|
|
|
|
Now that T_OBJECT and T_IMEMO/fields have identical layout
a lot more codepaths can be shared.
|
|
Now that T_OBJECT and T_IMEMO/fields have identical layout
a lot more codepaths can be shared.
|
|
|
|
|
|
|
|
|
|
Now that the shape_id has been unified across all types
this helper function doesn't do much over `RBASIC_SET_SHAPE_ID`.
It still check if the write is needed, but it doesn't seem useful
in places where it's used.
|
|
With the recent changes to shapes and variables
`general_ivar_set` no longer make sense and is
just extra complexity.
Trying to have a unified logic for all 3 types of objects
made sense, but also made the code way more complex.
We should definitely try to share more code between the
3 paths though, but that can be done with functions like
`generic_shape_ivar`.
|
|
|
|
[Bug #21547]
Followup: https://github.com/ruby/ruby/pull/14201
When adding an instance variable and the IMEMO/fields need to be
larger, we allocate a new one and clear the old one.
Since the old one may still be in other ec's cache, on a hit we must
check the IMEMO/fields isn't a stale one.
|
|
There is a high likelyhood that `rb_obj_fields` is called
consecutively for the same object.
If we keep a cache of the last IMEMO/fields we interacted with,
we can save having to lookup the `gen_fields_tbl`, synchronize
the VM lock, etc.
On yjit-bench's, I instrumented the hit rate of this cache at:
- `shipit`: 38%, with 111k hits.
- `lobsters`: 59%, with 367k hits.
- `rubocop`: 100% with only 300 hits.
I also ran a micro-benchmark which shows that ivar access is:
- 1.25x faster when the cache is hit in single ractor mode.
- 2x faster when the cache is hit in multi ractor mode.
- 1.06x slower when the cache miss in single ractor mode.
- 1.01x slower when the cache miss in multi ractor mode.
```yml
prelude: |
class GenIvar < Array
def initialize(...)
super
@iv = 1
end
attr_reader :iv
end
a = GenIvar.new
b = GenIvar.new
benchmark:
hit: a.iv; a.iv; a.iv; a.iv; a.iv; a.iv; a.iv; a.iv; a.iv; a.iv; a.iv; a.iv; a.iv; a.iv; a.iv; a.iv; a.iv; a.iv; a.iv; a.iv;
miss: a.iv; b.iv; a.iv; b.iv; a.iv; b.iv; a.iv; b.iv; a.iv; b.iv; a.iv; b.iv; a.iv; b.iv; a.iv; b.iv; a.iv; b.iv; a.iv; b.iv;
```
Single ractor:
```
compare-ruby: ruby 3.5.0dev (2025-08-12T02:14:57Z master 428937a536) +YJIT +PRISM [arm64-darwin24]
built-ruby: ruby 3.5.0dev (2025-08-12T09:25:35Z gen-fields-cache 9456c35893) +YJIT +PRISM [arm64-darwin24]
warming up..
| |compare-ruby|built-ruby|
|:-----|-----------:|---------:|
|hit | 4.090M| 5.121M|
| | -| 1.25x|
|miss | 3.756M| 3.534M|
| | 1.06x| -|
```
Multi-ractor:
```
compare-ruby: ruby 3.5.0dev (2025-08-12T02:14:57Z master 428937a536) +YJIT +PRISM [arm64-darwin24]
built-ruby: ruby 3.5.0dev (2025-08-12T09:25:35Z gen-fields-cache 9456c35893) +YJIT +PRISM [arm64-darwin24]
warming up..
| |compare-ruby|built-ruby|
|:-----|-----------:|---------:|
|hit | 2.205M| 4.460M|
| | -| 2.02x|
|miss | 2.117M| 2.094M|
| | 1.01x| -|
```
|
|
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.
|
|
Similar to f3206cc79bec2fd852e81ec56de59f0a67ab32b7 but for TypedData.
It's quite common for TypedData objects to have a mix of reference in
their struct and some ivars.
Since we do happen to have 8B free in the RtypedData struct, we could
use it to keep a direct reference to the IMEMO/fields saving having
to synchronize the VM and lookup the `gen_fields_tbl` on every ivar
access.
For old school Data classes however, we don't have free space, but
this API is soft-deprecated and no longer very common.
|
|
|
|
|
|
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>
|
|
All accesses to `generic_fields_tbl_` are now encapsulated inside:
- `rb_obj_fields`
- `rb_obj_set_fields`
- `rb_obj_replace_fields`
|
|
Co-Authored-By: Étienne Barrié <etienne.barrie@gmail.com>
|
|
The assert also fails on interpreter-only runs:
https://github.com/ruby/ruby/actions/runs/16658231740/job/47148610283
|
|
This has been failing intermittently during
`test_marshal_with_ivar_and_id` for ZJIT and YJIT builds.
https://github.com/ruby/ruby/actions/runs/16655987271/job/47140970602
https://github.com/ruby/ruby/actions/runs/16656119252/job/47141409113
|
|
There's a global id_table `rb_global_tbl` that needs a lock (I used VM lock). In the future, we might use a lock-free rb_id_table if we create such a data structure.
Reproduction script that might crash or behave strangely:
```ruby
100.times do
Ractor.new do
1_000_000.times do
$stderr
$stdout
$stdin
$VERBOSE
$stderr
$stdout
$stdin
$VERBOSE
$stderr
$stdout
$stdin
$VERBOSE
end
end
end
$myglobal0 = nil;
$myglobal1 = nil;
# ... vim macros to the rescue
$myglobal100000 = nil;
```
|
|
|
|
|
|
|