diff options
| -rw-r--r-- | benchmark/vm_ivar_get.yml | 67 | ||||
| -rw-r--r-- | internal/variable.h | 1 | ||||
| -rw-r--r-- | shape.h | 1 | ||||
| -rw-r--r-- | variable.c | 30 | ||||
| -rw-r--r-- | yjit/bindgen/src/main.rs | 1 | ||||
| -rw-r--r-- | yjit/src/codegen.rs | 48 | ||||
| -rw-r--r-- | yjit/src/cruby_bindings.inc.rs | 1 |
7 files changed, 124 insertions, 25 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 + end + + def 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 + + @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 < 1000000 + 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) */ @@ -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 @@ -1464,6 +1464,36 @@ rb_ivar_get(VALUE obj, ID id) } 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) { return rb_ivar_lookup(obj, id, Qnil); 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 - - // 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); - - // Push the ivar on the stack - let out_opnd = asm.stack_push(Type::Unknown); - asm.mov(out_opnd, ivar_opnd); + 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; + Opnd::mem(64, recv, offs) + } else { + // Compile time value is *not* embedded. + + // 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)); - - // Read the ivar from the extended table - let ivar_opnd = Opnd::mem(64, tbl_opnd, (SIZEOF_VALUE * ivar_index) 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)]) + }; - 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); |
