Skip to content

Commit b6f4b53

Browse files
authored
ZJIT: Specialize monomorphic GetIvar (ruby#14388)
Specialize monomorphic `GetIvar` into: * `GuardType(HeapObject)` * `GuardShape` * `LoadIvarEmbedded` or `LoadIvarExtended` This requires profiling self for `getinstancevariable` (it's not on the operand stack). This also optimizes `GetIvar`s that happen as a result of inlining `attr_reader` and `attr_accessor`. Also move some (newly) shared JIT helpers into jit.c.
1 parent fc4f8c8 commit b6f4b53

16 files changed

Lines changed: 384 additions & 65 deletions

File tree

insns.def

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -212,6 +212,7 @@ getinstancevariable
212212
(VALUE val)
213213
/* Ractor crashes when it accesses class/module-level instances variables. */
214214
// attr bool leaf = false; /* has IVAR_ACCESSOR_SHOULD_BE_MAIN_RACTOR() */
215+
// attr bool zjit_profile = true;
215216
{
216217
val = vm_getinstancevariable(GET_ISEQ(), GET_SELF(), id, ic);
217218
}

jit.c

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,12 @@
1414
#include "iseq.h"
1515
#include "internal/gc.h"
1616

17+
// Field offsets for the RObject struct
18+
enum robject_offsets {
19+
ROBJECT_OFFSET_AS_HEAP_FIELDS = offsetof(struct RObject, as.heap.fields),
20+
ROBJECT_OFFSET_AS_ARY = offsetof(struct RObject, as.ary),
21+
};
22+
1723
unsigned int
1824
rb_iseq_encoded_size(const rb_iseq_t *iseq)
1925
{
@@ -454,3 +460,9 @@ rb_set_cfp_sp(struct rb_control_frame_struct *cfp, VALUE *sp)
454460
{
455461
cfp->sp = sp;
456462
}
463+
464+
bool
465+
rb_jit_shape_too_complex_p(shape_id_t shape_id)
466+
{
467+
return rb_shape_too_complex_p(shape_id);
468+
}

test/ruby/test_zjit.rb

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1162,6 +1162,36 @@ def test() = @foo
11621162
}
11631163
end
11641164

1165+
def test_getinstancevariable_miss
1166+
assert_compiles '[1, 1, 4]', %q{
1167+
class C
1168+
def foo
1169+
@foo
1170+
end
1171+
1172+
def foo_then_bar
1173+
@foo = 1
1174+
@bar = 2
1175+
end
1176+
1177+
def bar_then_foo
1178+
@bar = 3
1179+
@foo = 4
1180+
end
1181+
end
1182+
1183+
o1 = C.new
1184+
o1.foo_then_bar
1185+
result = []
1186+
result << o1.foo
1187+
result << o1.foo
1188+
o2 = C.new
1189+
o2.bar_then_foo
1190+
result << o2.foo
1191+
result
1192+
}
1193+
end
1194+
11651195
def test_setinstancevariable
11661196
assert_compiles '1', %q{
11671197
def test() = @foo = 1

yjit.c

Lines changed: 0 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -39,12 +39,6 @@
3939

4040
#include <errno.h>
4141

42-
// Field offsets for the RObject struct
43-
enum robject_offsets {
44-
ROBJECT_OFFSET_AS_HEAP_FIELDS = offsetof(struct RObject, as.heap.fields),
45-
ROBJECT_OFFSET_AS_ARY = offsetof(struct RObject, as.ary),
46-
};
47-
4842
// Field offsets for the RString struct
4943
enum rstring_offsets {
5044
RUBY_OFFSET_RSTRING_LEN = offsetof(struct RString, len)
@@ -758,12 +752,6 @@ rb_object_shape_count(void)
758752
return ULONG2NUM((unsigned long)rb_shapes_count());
759753
}
760754

761-
bool
762-
rb_yjit_shape_too_complex_p(shape_id_t shape_id)
763-
{
764-
return rb_shape_too_complex_p(shape_id);
765-
}
766-
767755
bool
768756
rb_yjit_shape_obj_too_complex_p(VALUE obj)
769757
{

yjit/bindgen/src/main.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -99,7 +99,6 @@ fn main() {
9999
.allowlist_function("rb_shape_get_iv_index")
100100
.allowlist_function("rb_shape_transition_add_ivar_no_warnings")
101101
.allowlist_function("rb_yjit_shape_obj_too_complex_p")
102-
.allowlist_function("rb_yjit_shape_too_complex_p")
103102
.allowlist_function("rb_yjit_shape_capacity")
104103
.allowlist_function("rb_yjit_shape_index")
105104
.allowlist_var("SHAPE_ID_NUM_BITS")
@@ -351,11 +350,12 @@ fn main() {
351350
.allowlist_function("rb_yjit_invokeblock_sp_pops")
352351
.allowlist_function("rb_yjit_set_exception_return")
353352
.allowlist_function("rb_yjit_str_concat_codepoint")
354-
.allowlist_type("robject_offsets")
355353
.allowlist_type("rstring_offsets")
356354

357355
// From jit.c
358356
.allowlist_function("rb_assert_holding_vm_lock")
357+
.allowlist_function("rb_jit_shape_too_complex_p")
358+
.allowlist_type("robject_offsets")
359359

360360
// from vm_sync.h
361361
.allowlist_function("rb_vm_barrier")

yjit/src/codegen.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3103,7 +3103,7 @@ fn gen_set_ivar(
31033103

31043104
// If the VM ran out of shapes, or this class generated too many leaf,
31053105
// it may be de-optimized into OBJ_TOO_COMPLEX_SHAPE (hash-table).
3106-
new_shape_too_complex = unsafe { rb_yjit_shape_too_complex_p(next_shape_id) };
3106+
new_shape_too_complex = unsafe { rb_jit_shape_too_complex_p(next_shape_id) };
31073107
if new_shape_too_complex {
31083108
Some((next_shape_id, None, 0_usize))
31093109
} else {

yjit/src/cruby_bindings.inc.rs

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

zjit.c

Lines changed: 0 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -337,12 +337,6 @@ rb_zjit_print_exception(void)
337337
rb_warn("Ruby error: %"PRIsVALUE"", rb_funcall(exception, rb_intern("full_message"), 0));
338338
}
339339

340-
bool
341-
rb_zjit_shape_obj_too_complex_p(VALUE obj)
342-
{
343-
return rb_shape_obj_too_complex_p(obj);
344-
}
345-
346340
enum {
347341
RB_INVALID_SHAPE_ID = INVALID_SHAPE_ID,
348342
};

zjit/bindgen/src/main.rs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -111,7 +111,6 @@ fn main() {
111111
.allowlist_function("rb_shape_id_offset")
112112
.allowlist_function("rb_shape_get_iv_index")
113113
.allowlist_function("rb_shape_transition_add_ivar_no_warnings")
114-
.allowlist_function("rb_zjit_shape_obj_too_complex_p")
115114
.allowlist_var("SHAPE_ID_NUM_BITS")
116115

117116
// From ruby/internal/intern/object.h
@@ -367,6 +366,8 @@ fn main() {
367366

368367
// From jit.c
369368
.allowlist_function("rb_assert_holding_vm_lock")
369+
.allowlist_function("rb_jit_shape_too_complex_p")
370+
.allowlist_type("robject_offsets")
370371

371372
// from vm_sync.h
372373
.allowlist_function("rb_vm_barrier")

zjit/src/codegen.rs

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -405,6 +405,9 @@ fn gen_insn(cb: &mut CodeBlock, jit: &mut JITState, asm: &mut Assembler, functio
405405
&Insn::ToArray { val, state } => { gen_to_array(jit, asm, opnd!(val), &function.frame_state(state)) },
406406
&Insn::DefinedIvar { self_val, id, pushval, .. } => { gen_defined_ivar(asm, opnd!(self_val), id, pushval) },
407407
&Insn::ArrayExtend { left, right, state } => { no_output!(gen_array_extend(jit, asm, opnd!(left), opnd!(right), &function.frame_state(state))) },
408+
&Insn::GuardShape { val, shape, state } => gen_guard_shape(jit, asm, opnd!(val), shape, &function.frame_state(state)),
409+
&Insn::LoadIvarEmbedded { self_val, id, index } => gen_load_ivar_embedded(asm, opnd!(self_val), id, index),
410+
&Insn::LoadIvarExtended { self_val, id, index } => gen_load_ivar_extended(asm, opnd!(self_val), id, index),
408411
&Insn::ArrayMax { state, .. }
409412
| &Insn::FixnumDiv { state, .. }
410413
| &Insn::FixnumMod { state, .. }
@@ -716,6 +719,38 @@ fn gen_array_extend(jit: &mut JITState, asm: &mut Assembler, left: Opnd, right:
716719
asm_ccall!(asm, rb_ary_concat, left, right);
717720
}
718721

722+
fn gen_guard_shape(jit: &mut JITState, asm: &mut Assembler, val: Opnd, shape: ShapeId, state: &FrameState) -> Opnd {
723+
let shape_id_offset = unsafe { rb_shape_id_offset() };
724+
let val = asm.load(val);
725+
let shape_opnd = Opnd::mem(SHAPE_ID_NUM_BITS as u8, val, shape_id_offset);
726+
asm.cmp(shape_opnd, Opnd::UImm(shape.0 as u64));
727+
asm.jne(side_exit(jit, state, SideExitReason::GuardShape(shape)));
728+
val
729+
}
730+
731+
fn gen_load_ivar_embedded(asm: &mut Assembler, self_val: Opnd, id: ID, index: u16) -> Opnd {
732+
// See ROBJECT_FIELDS() from include/ruby/internal/core/robject.h
733+
734+
asm_comment!(asm, "Load embedded ivar id={} index={}", id.contents_lossy(), index);
735+
let offs = ROBJECT_OFFSET_AS_ARY as i32 + (SIZEOF_VALUE * index as usize) as i32;
736+
let self_val = asm.load(self_val);
737+
let ivar_opnd = Opnd::mem(64, self_val, offs);
738+
asm.load(ivar_opnd)
739+
}
740+
741+
fn gen_load_ivar_extended(asm: &mut Assembler, self_val: Opnd, id: ID, index: u16) -> Opnd {
742+
asm_comment!(asm, "Load extended ivar id={} index={}", id.contents_lossy(), index);
743+
// Compile time value is *not* embedded.
744+
745+
// Get a pointer to the extended table
746+
let self_val = asm.load(self_val);
747+
let tbl_opnd = asm.load(Opnd::mem(64, self_val, ROBJECT_OFFSET_AS_HEAP_FIELDS as i32));
748+
749+
// Read the ivar from the extended table
750+
let ivar_opnd = Opnd::mem(64, tbl_opnd, (SIZEOF_VALUE * index as usize) as i32);
751+
asm.load(ivar_opnd)
752+
}
753+
719754
/// Compile an interpreter entry block to be inserted into an ISEQ
720755
fn gen_entry_prologue(asm: &mut Assembler, iseq: IseqPtr) {
721756
asm_comment!(asm, "ZJIT entry point: {}", iseq_get_location(iseq, 0));
@@ -1270,6 +1305,12 @@ fn gen_guard_type(jit: &mut JITState, asm: &mut Assembler, val: lir::Opnd, guard
12701305

12711306
asm.cmp(klass, Opnd::Value(expected_class));
12721307
asm.jne(side_exit);
1308+
} else if guard_type.bit_equal(types::HeapObject) {
1309+
let side_exit = side_exit(jit, state, GuardType(guard_type));
1310+
asm.cmp(val, Opnd::Value(Qfalse));
1311+
asm.je(side_exit.clone());
1312+
asm.test(val, (RUBY_IMMEDIATE_MASK as u64).into());
1313+
asm.jnz(side_exit);
12731314
} else {
12741315
unimplemented!("unsupported type: {guard_type}");
12751316
}

0 commit comments

Comments
 (0)