Skip to content

Commit b6d4882

Browse files
byroottenderlove
andcommitted
YJIT: getinstancevariable cache indexes for types other than T_OBJECT
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 efaa8c9) +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>
1 parent b1dbcd3 commit b6d4882

File tree

7 files changed

+124
-25
lines changed

7 files changed

+124
-25
lines changed

benchmark/vm_ivar_get.yml

Lines changed: 65 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,75 @@
11
prelude: |
22
class Example
33
def initialize
4+
@levar = 1
45
@v0 = 1
56
@v1 = 2
67
@v3 = 3
8+
end
9+
10+
def get_value_loop
11+
sum = 0
12+
13+
i = 0
14+
while i < 100_000
15+
# 10 times to de-emphasize loop overhead
16+
sum += @levar
17+
sum += @levar
18+
sum += @levar
19+
sum += @levar
20+
sum += @levar
21+
sum += @levar
22+
sum += @levar
23+
sum += @levar
24+
sum += @levar
25+
sum += @levar
26+
i += 1
27+
end
28+
29+
return sum
30+
end
31+
32+
@levar = 1
33+
@v0 = 1
34+
@v1 = 2
35+
@v3 = 3
36+
37+
def self.get_value_loop
38+
sum = 0
39+
40+
i = 0
41+
while i < 100_000
42+
# 10 times to de-emphasize loop overhead
43+
sum += @levar
44+
sum += @levar
45+
sum += @levar
46+
sum += @levar
47+
sum += @levar
48+
sum += @levar
49+
sum += @levar
50+
sum += @levar
51+
sum += @levar
52+
sum += @levar
53+
i += 1
54+
end
55+
56+
return sum
57+
end
58+
end
59+
60+
class GenExample < Time
61+
def initialize
762
@levar = 1
63+
@v0 = 1
64+
@v1 = 2
65+
@v3 = 3
866
end
967
1068
def get_value_loop
1169
sum = 0
1270
1371
i = 0
14-
while i < 1000000
72+
while i < 100_000
1573
# 10 times to de-emphasize loop overhead
1674
sum += @levar
1775
sum += @levar
@@ -31,7 +89,12 @@ prelude: |
3189
end
3290
3391
obj = Example.new
92+
gen = GenExample.new
3493
benchmark:
35-
vm_ivar_get: |
94+
vm_ivar_get_on_obj: |
3695
obj.get_value_loop
96+
vm_ivar_get_on_class: |
97+
Example.get_value_loop
98+
vm_ivar_get_on_generic: |
99+
gen.get_value_loop
37100
loop_count: 100

internal/variable.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,7 @@ VALUE rb_obj_field_get(VALUE obj, shape_id_t target_shape_id);
5353
void rb_ivar_set_internal(VALUE obj, ID id, VALUE val);
5454
attr_index_t rb_ivar_set_index(VALUE obj, ID id, VALUE val);
5555
attr_index_t rb_obj_field_set(VALUE obj, shape_id_t target_shape_id, ID field_name, VALUE val);
56+
VALUE rb_ivar_get_at(VALUE obj, attr_index_t index, ID id);
5657

5758
RUBY_SYMBOL_EXPORT_BEGIN
5859
/* variable.c (export) */

shape.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ STATIC_ASSERT(shape_id_num_bits, SHAPE_ID_NUM_BITS == sizeof(shape_id_t) * CHAR_
2424
// index in rb_shape_tree.shape_list. Allow to access `rb_shape_t *`.
2525
// 19-21 SHAPE_ID_HEAP_INDEX_MASK
2626
// index in rb_shape_tree.capacities. Allow to access slot size.
27+
// Always 0 except for T_OBJECT.
2728
// 22 SHAPE_ID_FL_FROZEN
2829
// Whether the object is frozen or not.
2930
// 23 SHAPE_ID_FL_HAS_OBJECT_ID

variable.c

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1463,6 +1463,36 @@ rb_ivar_get(VALUE obj, ID id)
14631463
return iv;
14641464
}
14651465

1466+
VALUE
1467+
rb_ivar_get_at(VALUE obj, attr_index_t index, ID id)
1468+
{
1469+
RUBY_ASSERT(rb_is_instance_id(id));
1470+
// Used by JITs, but never for T_OBJECT.
1471+
1472+
switch (BUILTIN_TYPE(obj)) {
1473+
case T_OBJECT:
1474+
UNREACHABLE_RETURN(Qundef);
1475+
case T_CLASS:
1476+
case T_MODULE:
1477+
{
1478+
VALUE fields_obj = RCLASS_WRITABLE_FIELDS_OBJ(obj);
1479+
VALUE val = rb_imemo_fields_ptr(fields_obj)[index];
1480+
1481+
if (UNLIKELY(!rb_ractor_main_p()) && !rb_ractor_shareable_p(val)) {
1482+
rb_raise(rb_eRactorIsolationError,
1483+
"can not get unshareable values from instance variables of classes/modules from non-main Ractors");
1484+
}
1485+
1486+
return val;
1487+
}
1488+
default:
1489+
{
1490+
VALUE fields_obj = rb_obj_fields(obj, id);
1491+
return rb_imemo_fields_ptr(fields_obj)[index];
1492+
}
1493+
}
1494+
}
1495+
14661496
VALUE
14671497
rb_attr_get(VALUE obj, ID id)
14681498
{

yjit/bindgen/src/main.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -313,6 +313,7 @@ fn main() {
313313

314314
// From yjit.c
315315
.allowlist_function("rb_object_shape_count")
316+
.allowlist_function("rb_ivar_get_at")
316317
.allowlist_function("rb_iseq_(get|set)_yjit_payload")
317318
.allowlist_function("rb_iseq_pc_at_idx")
318319
.allowlist_function("rb_iseq_opcode_at_pc")

yjit/src/codegen.rs

Lines changed: 25 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -2864,7 +2864,7 @@ fn gen_get_ivar(
28642864

28652865
// NOTE: This assumes T_OBJECT can't ever have the same shape_id as any other type.
28662866
// too-complex shapes can't use index access, so we use rb_ivar_get for them too.
2867-
if !receiver_t_object || comptime_receiver.shape_too_complex() || megamorphic {
2867+
if !comptime_receiver.heap_object_p() || comptime_receiver.shape_too_complex() || megamorphic {
28682868
// General case. Call rb_ivar_get().
28692869
// VALUE rb_ivar_get(VALUE obj, ID id)
28702870
asm_comment!(asm, "call rb_ivar_get()");
@@ -2900,9 +2900,6 @@ fn gen_get_ivar(
29002900
// Guard heap object (recv_opnd must be used before stack_pop)
29012901
guard_object_is_heap(asm, recv, recv_opnd, Counter::getivar_not_heap);
29022902

2903-
// Compile time self is embedded and the ivar index lands within the object
2904-
let embed_test_result = comptime_receiver.embedded_p();
2905-
29062903
let expected_shape = unsafe { rb_obj_shape_id(comptime_receiver) };
29072904
let shape_id_offset = unsafe { rb_shape_id_offset() };
29082905
let shape_opnd = Opnd::mem(SHAPE_ID_NUM_BITS as u8, recv, shape_id_offset);
@@ -2931,28 +2928,33 @@ fn gen_get_ivar(
29312928
asm.mov(out_opnd, Qnil.into());
29322929
}
29332930
Some(ivar_index) => {
2934-
if embed_test_result {
2935-
// See ROBJECT_FIELDS() from include/ruby/internal/core/robject.h
2936-
2937-
// Load the variable
2938-
let offs = ROBJECT_OFFSET_AS_ARY as i32 + (ivar_index * SIZEOF_VALUE) as i32;
2939-
let ivar_opnd = Opnd::mem(64, recv, offs);
2940-
2941-
// Push the ivar on the stack
2942-
let out_opnd = asm.stack_push(Type::Unknown);
2943-
asm.mov(out_opnd, ivar_opnd);
2931+
let ivar_opnd = if receiver_t_object {
2932+
if comptime_receiver.embedded_p() {
2933+
// See ROBJECT_FIELDS() from include/ruby/internal/core/robject.h
2934+
2935+
// Load the variable
2936+
let offs = ROBJECT_OFFSET_AS_ARY as i32 + (ivar_index * SIZEOF_VALUE) as i32;
2937+
Opnd::mem(64, recv, offs)
2938+
} else {
2939+
// Compile time value is *not* embedded.
2940+
2941+
// Get a pointer to the extended table
2942+
let tbl_opnd = asm.load(Opnd::mem(64, recv, ROBJECT_OFFSET_AS_HEAP_FIELDS as i32));
2943+
2944+
// Read the ivar from the extended table
2945+
Opnd::mem(64, tbl_opnd, (SIZEOF_VALUE * ivar_index) as i32)
2946+
}
29442947
} else {
2945-
// Compile time value is *not* embedded.
2948+
asm_comment!(asm, "call rb_ivar_get_at()");
29462949

2947-
// Get a pointer to the extended table
2948-
let tbl_opnd = asm.load(Opnd::mem(64, recv, ROBJECT_OFFSET_AS_HEAP_FIELDS as i32));
2949-
2950-
// Read the ivar from the extended table
2951-
let ivar_opnd = Opnd::mem(64, tbl_opnd, (SIZEOF_VALUE * ivar_index) as i32);
2950+
// The function could raise RactorIsolationError.
2951+
jit_prepare_non_leaf_call(jit, asm);
2952+
asm.ccall(rb_ivar_get_at as *const u8, vec![recv, Opnd::UImm((ivar_index as u32).into()), Opnd::UImm(ivar_name)])
2953+
};
29522954

2953-
let out_opnd = asm.stack_push(Type::Unknown);
2954-
asm.mov(out_opnd, ivar_opnd);
2955-
}
2955+
// Push the ivar on the stack
2956+
let out_opnd = asm.stack_push(Type::Unknown);
2957+
asm.mov(out_opnd, ivar_opnd);
29562958
}
29572959
}
29582960

yjit/src/cruby_bindings.inc.rs

Lines changed: 1 addition & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)