mirror of
https://github.com/ruby/ruby.git
synced 2026-01-27 04:24:23 +00:00
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 <tenderlove@ruby-lang.org>
This commit is contained in:
parent
b1dbcd3ce3
commit
b6d4882c05
@ -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
|
||||
|
||||
@ -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) */
|
||||
|
||||
1
shape.h
1
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
|
||||
|
||||
30
variable.c
30
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)
|
||||
{
|
||||
|
||||
@ -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")
|
||||
|
||||
@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
1
yjit/src/cruby_bindings.inc.rs
generated
1
yjit/src/cruby_bindings.inc.rs
generated
@ -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);
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user