Skip to content

Commit 9877f3a

Browse files
authored
YJIT: Inline basic Ruby methods (#8855)
* YJIT: Inline basic Ruby methods * YJIT: Fix "InsnOut operand made it past register allocation" checktype should not generate a useless instruction.
1 parent a294bb8 commit 9877f3a

File tree

5 files changed

+83
-4
lines changed

5 files changed

+83
-4
lines changed

bootstraptest/test_yjit.rb

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4196,3 +4196,24 @@ def foo(a, b)
41964196

41974197
# Integer multiplication and overflow (minimized regression test from test-basic)
41984198
assert_equal '8515157028618240000', %q{2128789257154560000 * 4}
4199+
4200+
# Inlined method calls
4201+
assert_equal 'nil', %q{
4202+
def putnil = nil
4203+
def entry = putnil
4204+
entry.inspect
4205+
}
4206+
assert_equal '1', %q{
4207+
def putobject_1 = 1
4208+
def entry = putobject_1
4209+
entry
4210+
}
4211+
assert_equal 'false', %q{
4212+
def putobject(_unused_arg1) = false
4213+
def entry = putobject(nil)
4214+
entry
4215+
}
4216+
assert_equal 'true', %q{
4217+
def entry = yield
4218+
entry { true }
4219+
}

yjit.rb

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -302,6 +302,9 @@ def _print_stats(out: $stderr) # :nodoc:
302302
out.puts "num_send_polymorphic: " + format_number_pct(13, stats[:num_send_polymorphic], stats[:num_send])
303303
out.puts "num_send_megamorphic: " + format_number_pct(13, stats[:send_megamorphic], stats[:num_send])
304304
out.puts "num_send_dynamic: " + format_number_pct(13, stats[:num_send_dynamic], stats[:num_send])
305+
out.puts "num_send_inline: " + format_number_pct(13, stats[:num_send_inline], stats[:num_send])
306+
out.puts "num_send_leaf_builtin: " + format_number_pct(13, stats[:num_send_leaf_builtin], stats[:num_send])
307+
out.puts "num_send_known_cfunc: " + format_number_pct(13, stats[:num_send_known_cfunc], stats[:num_send])
305308
if stats[:num_send_x86_rel32] != 0 || stats[:num_send_x86_reg] != 0
306309
out.puts "num_send_x86_rel32: " + format_number(13, stats[:num_send_x86_rel32])
307310
out.puts "num_send_x86_reg: " + format_number(13, stats[:num_send_x86_reg])

yjit/src/codegen.rs

Lines changed: 50 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2752,7 +2752,6 @@ fn gen_checktype(
27522752
if let RUBY_T_STRING | RUBY_T_ARRAY | RUBY_T_HASH = type_val {
27532753
let val_type = asm.ctx.get_opnd_type(StackOpnd(0));
27542754
let val = asm.stack_pop(1);
2755-
let val = asm.load(val);
27562755

27572756
// Check if we know from type information
27582757
match val_type.known_value_type() {
@@ -2770,6 +2769,7 @@ fn gen_checktype(
27702769

27712770
let ret = asm.new_label("ret");
27722771

2772+
let val = asm.load(val);
27732773
if !val_type.is_heap() {
27742774
// if (SPECIAL_CONST_P(val)) {
27752775
// Return Qfalse via REG1 if not on heap
@@ -5417,6 +5417,7 @@ fn gen_send_cfunc(
54175417
if let Some(known_cfunc_codegen) = codegen_p {
54185418
if known_cfunc_codegen(jit, asm, ocb, ci, cme, block, argc, recv_known_klass) {
54195419
assert_eq!(expected_stack_after, asm.ctx.get_stack_size() as i32);
5420+
gen_counter_incr(asm, Counter::num_send_known_cfunc);
54205421
// cfunc codegen generated code. Terminate the block so
54215422
// there isn't multiple calls in the same block.
54225423
jump_to_next_insn(jit, asm, ocb);
@@ -5852,6 +5853,31 @@ fn gen_send_bmethod(
58525853
gen_send_iseq(jit, asm, ocb, iseq, ci, frame_type, Some(capture.ep), cme, block, flags, argc, None)
58535854
}
58545855

5856+
/// Return the ISEQ's return value if it consists of only putnil/putobject and leave.
5857+
fn iseq_get_return_value(iseq: IseqPtr) -> Option<VALUE> {
5858+
// Expect only two instructions and one possible operand
5859+
let iseq_size = unsafe { get_iseq_encoded_size(iseq) };
5860+
if !(2..=3).contains(&iseq_size) {
5861+
return None;
5862+
}
5863+
5864+
// Get the first two instructions
5865+
let first_insn = iseq_opcode_at_idx(iseq, 0);
5866+
let second_insn = iseq_opcode_at_idx(iseq, insn_len(first_insn as usize));
5867+
5868+
// Extract the return value if known
5869+
if second_insn != YARVINSN_leave {
5870+
return None;
5871+
}
5872+
match first_insn {
5873+
YARVINSN_putnil => Some(Qnil),
5874+
YARVINSN_putobject => unsafe { Some(*rb_iseq_pc_at_idx(iseq, 1)) },
5875+
YARVINSN_putobject_INT2FIX_0_ => Some(VALUE::fixnum_from_usize(0)),
5876+
YARVINSN_putobject_INT2FIX_1_ => Some(VALUE::fixnum_from_usize(1)),
5877+
_ => None,
5878+
}
5879+
}
5880+
58555881
fn gen_send_iseq(
58565882
jit: &mut JITState,
58575883
asm: &mut Assembler,
@@ -6112,8 +6138,6 @@ fn gen_send_iseq(
61126138
if let (None, Some(builtin_info), true, false) = (block, builtin_func, builtin_attrs & BUILTIN_ATTR_LEAF != 0, opt_send_call) {
61136139
let builtin_argc = unsafe { (*builtin_info).argc };
61146140
if builtin_argc + 1 < (C_ARG_OPNDS.len() as i32) {
6115-
asm_comment!(asm, "inlined leaf builtin");
6116-
61176141
// We pop the block arg without using it because:
61186142
// - the builtin is leaf, so it promises to not `yield`.
61196143
// - no leaf builtins have block param at the time of writing, and
@@ -6126,6 +6150,9 @@ fn gen_send_iseq(
61266150
asm.stack_pop(1);
61276151
}
61286152

6153+
asm_comment!(asm, "inlined leaf builtin");
6154+
gen_counter_incr(asm, Counter::num_send_leaf_builtin);
6155+
61296156
// Skip this if it doesn't trigger GC
61306157
if builtin_attrs & BUILTIN_ATTR_NO_GC == 0 {
61316158
// The callee may allocate, e.g. Integer#abs on a Bignum.
@@ -6152,10 +6179,29 @@ fn gen_send_iseq(
61526179
// Note: assuming that the leaf builtin doesn't change local variables here.
61536180
// Seems like a safe assumption.
61546181

6155-
return Some(KeepCompiling);
6182+
// Let guard chains share the same successor
6183+
jump_to_next_insn(jit, asm, ocb);
6184+
return Some(EndBlock);
61566185
}
61576186
}
61586187

6188+
// Inline simple ISEQs whose return value is known at compile time
6189+
if let (Some(value), None, false) = (iseq_get_return_value(iseq), block_arg_type, opt_send_call) {
6190+
asm_comment!(asm, "inlined simple ISEQ");
6191+
gen_counter_incr(asm, Counter::num_send_inline);
6192+
6193+
// Pop receiver and arguments
6194+
asm.stack_pop(argc as usize + if captured_opnd.is_some() { 0 } else { 1 });
6195+
6196+
// Push the return value
6197+
let stack_ret = asm.stack_push(Type::from(value));
6198+
asm.mov(stack_ret, value.into());
6199+
6200+
// Let guard chains share the same successor
6201+
jump_to_next_insn(jit, asm, ocb);
6202+
return Some(EndBlock);
6203+
}
6204+
61596205
// Stack overflow check
61606206
// Note that vm_push_frame checks it against a decremented cfp, hence the multiply by 2.
61616207
// #define CHECK_VM_STACK_OVERFLOW0(cfp, sp, margin)

yjit/src/cruby.rs

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -252,6 +252,12 @@ pub fn iseq_pc_to_insn_idx(iseq: IseqPtr, pc: *mut VALUE) -> Option<u16> {
252252
unsafe { pc.offset_from(pc_zero) }.try_into().ok()
253253
}
254254

255+
/// Given an ISEQ pointer and an instruction index, return an opcode.
256+
pub fn iseq_opcode_at_idx(iseq: IseqPtr, insn_idx: u32) -> u32 {
257+
let pc = unsafe { rb_iseq_pc_at_idx(iseq, insn_idx) };
258+
unsafe { rb_iseq_opcode_at_pc(iseq, pc) as u32 }
259+
}
260+
255261
/// Opaque execution-context type from vm_core.h
256262
#[repr(C)]
257263
pub struct rb_execution_context_struct {

yjit/src/stats.rs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -480,6 +480,9 @@ make_counters! {
480480
num_send_x86_rel32,
481481
num_send_x86_reg,
482482
num_send_dynamic,
483+
num_send_inline,
484+
num_send_leaf_builtin,
485+
num_send_known_cfunc,
483486

484487
num_getivar_megamorphic,
485488
num_setivar_megamorphic,

0 commit comments

Comments
 (0)