From b6d4882c05d64aa6cb5ff8761584ae910de67f21 Mon Sep 17 00:00:00 2001 From: Jean Boussier Date: Thu, 28 Aug 2025 19:29:46 +0200 Subject: [PATCH] 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 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 --- benchmark/vm_ivar_get.yml | 69 ++++++++++++++++++++++++++++++++-- internal/variable.h | 1 + shape.h | 1 + variable.c | 30 +++++++++++++++ yjit/bindgen/src/main.rs | 1 + yjit/src/codegen.rs | 44 +++++++++++----------- yjit/src/cruby_bindings.inc.rs | 1 + 7 files changed, 123 insertions(+), 24 deletions(-) diff --git a/benchmark/vm_ivar_get.yml b/benchmark/vm_ivar_get.yml index 9174af6965..1e0dad665f 100644 --- a/benchmark/vm_ivar_get.yml +++ b/benchmark/vm_ivar_get.yml @@ -1,17 +1,75 @@ prelude: | class Example def initialize + @levar = 1 @v0 = 1 @v1 = 2 @v3 = 3 - @levar = 1 end def get_value_loop sum = 0 i = 0 - while i < 1000000 + while i < 100_000 + # 10 times to de-emphasize loop overhead + sum += @levar + sum += @levar + sum += @levar + sum += @levar + sum += @levar + sum += @levar + sum += @levar + sum += @levar + sum += @levar + sum += @levar + i += 1 + end + + return sum + end + + @levar = 1 + @v0 = 1 + @v1 = 2 + @v3 = 3 + + def self.get_value_loop + sum = 0 + + i = 0 + while i < 100_000 + # 10 times to de-emphasize loop overhead + sum += @levar + sum += @levar + sum += @levar + sum += @levar + sum += @levar + sum += @levar + sum += @levar + sum += @levar + sum += @levar + sum += @levar + i += 1 + end + + return sum + end + end + + class GenExample < Time + def initialize + @levar = 1 + @v0 = 1 + @v1 = 2 + @v3 = 3 + end + + def get_value_loop + sum = 0 + + i = 0 + while i < 100_000 # 10 times to de-emphasize loop overhead sum += @levar sum += @levar @@ -31,7 +89,12 @@ prelude: | end obj = Example.new + gen = GenExample.new benchmark: - vm_ivar_get: | + vm_ivar_get_on_obj: | obj.get_value_loop + vm_ivar_get_on_class: | + Example.get_value_loop + vm_ivar_get_on_generic: | + gen.get_value_loop loop_count: 100 diff --git a/internal/variable.h b/internal/variable.h index 2cb50f66ae..d256330168 100644 --- a/internal/variable.h +++ b/internal/variable.h @@ -53,6 +53,7 @@ VALUE rb_obj_field_get(VALUE obj, shape_id_t target_shape_id); void rb_ivar_set_internal(VALUE obj, ID id, VALUE val); attr_index_t rb_ivar_set_index(VALUE obj, ID id, VALUE val); attr_index_t rb_obj_field_set(VALUE obj, shape_id_t target_shape_id, ID field_name, VALUE val); +VALUE rb_ivar_get_at(VALUE obj, attr_index_t index, ID id); RUBY_SYMBOL_EXPORT_BEGIN /* variable.c (export) */ diff --git a/shape.h b/shape.h index 1f444c545f..fdc2b3ddd6 100644 --- a/shape.h +++ b/shape.h @@ -24,6 +24,7 @@ STATIC_ASSERT(shape_id_num_bits, SHAPE_ID_NUM_BITS == sizeof(shape_id_t) * CHAR_ // index in rb_shape_tree.shape_list. Allow to access `rb_shape_t *`. // 19-21 SHAPE_ID_HEAP_INDEX_MASK // index in rb_shape_tree.capacities. Allow to access slot size. +// Always 0 except for T_OBJECT. // 22 SHAPE_ID_FL_FROZEN // Whether the object is frozen or not. // 23 SHAPE_ID_FL_HAS_OBJECT_ID diff --git a/variable.c b/variable.c index f80f1a56a6..0a5ec60c25 100644 --- a/variable.c +++ b/variable.c @@ -1463,6 +1463,36 @@ rb_ivar_get(VALUE obj, ID id) return iv; } +VALUE +rb_ivar_get_at(VALUE obj, attr_index_t index, ID id) +{ + RUBY_ASSERT(rb_is_instance_id(id)); + // Used by JITs, but never for T_OBJECT. + + switch (BUILTIN_TYPE(obj)) { + case T_OBJECT: + UNREACHABLE_RETURN(Qundef); + case T_CLASS: + case T_MODULE: + { + VALUE fields_obj = RCLASS_WRITABLE_FIELDS_OBJ(obj); + VALUE val = rb_imemo_fields_ptr(fields_obj)[index]; + + if (UNLIKELY(!rb_ractor_main_p()) && !rb_ractor_shareable_p(val)) { + rb_raise(rb_eRactorIsolationError, + "can not get unshareable values from instance variables of classes/modules from non-main Ractors"); + } + + return val; + } + default: + { + VALUE fields_obj = rb_obj_fields(obj, id); + return rb_imemo_fields_ptr(fields_obj)[index]; + } + } +} + VALUE rb_attr_get(VALUE obj, ID id) { diff --git a/yjit/bindgen/src/main.rs b/yjit/bindgen/src/main.rs index dd0cb6dbf5..c1114e8089 100644 --- a/yjit/bindgen/src/main.rs +++ b/yjit/bindgen/src/main.rs @@ -313,6 +313,7 @@ fn main() { // From yjit.c .allowlist_function("rb_object_shape_count") + .allowlist_function("rb_ivar_get_at") .allowlist_function("rb_iseq_(get|set)_yjit_payload") .allowlist_function("rb_iseq_pc_at_idx") .allowlist_function("rb_iseq_opcode_at_pc") diff --git a/yjit/src/codegen.rs b/yjit/src/codegen.rs index 10aeac2d85..f27d09d7d4 100644 --- a/yjit/src/codegen.rs +++ b/yjit/src/codegen.rs @@ -2864,7 +2864,7 @@ fn gen_get_ivar( // NOTE: This assumes T_OBJECT can't ever have the same shape_id as any other type. // too-complex shapes can't use index access, so we use rb_ivar_get for them too. - if !receiver_t_object || comptime_receiver.shape_too_complex() || megamorphic { + if !comptime_receiver.heap_object_p() || comptime_receiver.shape_too_complex() || megamorphic { // General case. Call rb_ivar_get(). // VALUE rb_ivar_get(VALUE obj, ID id) asm_comment!(asm, "call rb_ivar_get()"); @@ -2900,9 +2900,6 @@ fn gen_get_ivar( // Guard heap object (recv_opnd must be used before stack_pop) guard_object_is_heap(asm, recv, recv_opnd, Counter::getivar_not_heap); - // Compile time self is embedded and the ivar index lands within the object - let embed_test_result = comptime_receiver.embedded_p(); - let expected_shape = unsafe { rb_obj_shape_id(comptime_receiver) }; let shape_id_offset = unsafe { rb_shape_id_offset() }; let shape_opnd = Opnd::mem(SHAPE_ID_NUM_BITS as u8, recv, shape_id_offset); @@ -2931,28 +2928,33 @@ fn gen_get_ivar( asm.mov(out_opnd, Qnil.into()); } Some(ivar_index) => { - if embed_test_result { - // See ROBJECT_FIELDS() from include/ruby/internal/core/robject.h + let ivar_opnd = if receiver_t_object { + if comptime_receiver.embedded_p() { + // See ROBJECT_FIELDS() from include/ruby/internal/core/robject.h - // Load the variable - let offs = ROBJECT_OFFSET_AS_ARY as i32 + (ivar_index * SIZEOF_VALUE) as i32; - let ivar_opnd = Opnd::mem(64, recv, offs); + // Load the variable + let offs = ROBJECT_OFFSET_AS_ARY as i32 + (ivar_index * SIZEOF_VALUE) as i32; + Opnd::mem(64, recv, offs) + } else { + // Compile time value is *not* embedded. - // Push the ivar on the stack - let out_opnd = asm.stack_push(Type::Unknown); - asm.mov(out_opnd, ivar_opnd); + // Get a pointer to the extended table + let tbl_opnd = asm.load(Opnd::mem(64, recv, ROBJECT_OFFSET_AS_HEAP_FIELDS as i32)); + + // Read the ivar from the extended table + Opnd::mem(64, tbl_opnd, (SIZEOF_VALUE * ivar_index) as i32) + } } else { - // Compile time value is *not* embedded. + asm_comment!(asm, "call rb_ivar_get_at()"); - // Get a pointer to the extended table - let tbl_opnd = asm.load(Opnd::mem(64, recv, ROBJECT_OFFSET_AS_HEAP_FIELDS as i32)); + // The function could raise RactorIsolationError. + jit_prepare_non_leaf_call(jit, asm); + asm.ccall(rb_ivar_get_at as *const u8, vec![recv, Opnd::UImm((ivar_index as u32).into()), Opnd::UImm(ivar_name)]) + }; - // Read the ivar from the extended table - let ivar_opnd = Opnd::mem(64, tbl_opnd, (SIZEOF_VALUE * ivar_index) as i32); - - let out_opnd = asm.stack_push(Type::Unknown); - asm.mov(out_opnd, ivar_opnd); - } + // Push the ivar on the stack + let out_opnd = asm.stack_push(Type::Unknown); + asm.mov(out_opnd, ivar_opnd); } } diff --git a/yjit/src/cruby_bindings.inc.rs b/yjit/src/cruby_bindings.inc.rs index 4cae138c95..f78354a611 100644 --- a/yjit/src/cruby_bindings.inc.rs +++ b/yjit/src/cruby_bindings.inc.rs @@ -1115,6 +1115,7 @@ extern "C" { pub fn rb_obj_shape_id(obj: VALUE) -> shape_id_t; pub fn rb_shape_get_iv_index(shape_id: shape_id_t, id: ID, value: *mut attr_index_t) -> bool; pub fn rb_shape_transition_add_ivar_no_warnings(obj: VALUE, id: ID) -> shape_id_t; + pub fn rb_ivar_get_at(obj: VALUE, index: attr_index_t, id: ID) -> VALUE; pub fn rb_gvar_get(arg1: ID) -> VALUE; pub fn rb_gvar_set(arg1: ID, arg2: VALUE) -> VALUE; pub fn rb_ensure_iv_list_size(obj: VALUE, current_len: u32, newsize: u32);