Skip to content

Commit cef60e9

Browse files
authored
YJIT: Fallback send instructions to vm_sendish (#8106)
1 parent c4e893c commit cef60e9

File tree

7 files changed

+178
-7
lines changed

7 files changed

+178
-7
lines changed

test/ruby/test_yjit.rb

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -548,7 +548,7 @@ def foo &blk
548548

549549
def test_getblockparamproxy
550550
# Currently two side exits as OPTIMIZED_METHOD_TYPE_CALL is unimplemented
551-
assert_compiles(<<~'RUBY', insns: [:getblockparamproxy], exits: { opt_send_without_block: 2 })
551+
assert_compiles(<<~'RUBY', insns: [:getblockparamproxy])
552552
def foo &blk
553553
p blk.call
554554
p blk.call
@@ -607,7 +607,7 @@ def jit_method
607607

608608
def test_send_kwargs
609609
# For now, this side-exits when calls include keyword args
610-
assert_compiles(<<~'RUBY', result: "2#a:1,b:2/A", exits: {opt_send_without_block: 1})
610+
assert_compiles(<<~'RUBY', result: "2#a:1,b:2/A")
611611
def internal_method(**kw)
612612
"#{kw.size}##{kw.keys.map { |k| "#{k}:#{kw[k]}" }.join(",")}"
613613
end
@@ -647,7 +647,7 @@ def jit_method
647647

648648
def test_send_kwargs_splat
649649
# For now, this side-exits when calling with a splat
650-
assert_compiles(<<~'RUBY', result: "2#a:1,b:2/B", exits: {opt_send_without_block: 1})
650+
assert_compiles(<<~'RUBY', result: "2#a:1,b:2/B")
651651
def internal_method(**kw)
652652
"#{kw.size}##{kw.keys.map { |k| "#{k}:#{kw[k]}" }.join(",")}"
653653
end

vm_exec.h

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -169,10 +169,20 @@ default: \
169169
#define THROW_EXCEPTION(exc) return (VALUE)(exc)
170170
#endif
171171

172+
// Run the interpreter from the JIT
173+
#define VM_EXEC(ec, val) do { \
174+
if (val == Qundef) { \
175+
VM_ENV_FLAGS_SET(ec->cfp->ep, VM_FRAME_FLAG_FINISH); \
176+
val = vm_exec(ec); \
177+
} \
178+
} while (0)
179+
180+
// Run the JIT from the interpreter
172181
#define JIT_EXEC(ec, val) do { \
173182
rb_jit_func_t func; \
174183
if (val == Qundef && (func = jit_compile(ec))) { \
175184
val = func(ec, ec->cfp); \
185+
RESTORE_REGS(); /* fix cfp for tailcall */ \
176186
if (ec->tag->state) THROW_EXCEPTION(val); \
177187
} \
178188
} while (0)

vm_insnhelper.c

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5528,6 +5528,42 @@ vm_sendish(
55285528
return val;
55295529
}
55305530

5531+
VALUE
5532+
rb_vm_send(rb_execution_context_t *ec, rb_control_frame_t *reg_cfp, CALL_DATA cd, ISEQ blockiseq)
5533+
{
5534+
VALUE bh = vm_caller_setup_arg_block(ec, GET_CFP(), cd->ci, blockiseq, false);
5535+
VALUE val = vm_sendish(ec, GET_CFP(), cd, bh, mexp_search_method);
5536+
VM_EXEC(ec, val);
5537+
return val;
5538+
}
5539+
5540+
VALUE
5541+
rb_vm_opt_send_without_block(rb_execution_context_t *ec, rb_control_frame_t *reg_cfp, CALL_DATA cd)
5542+
{
5543+
VALUE bh = VM_BLOCK_HANDLER_NONE;
5544+
VALUE val = vm_sendish(ec, GET_CFP(), cd, bh, mexp_search_method);
5545+
VM_EXEC(ec, val);
5546+
return val;
5547+
}
5548+
5549+
VALUE
5550+
rb_vm_invokesuper(rb_execution_context_t *ec, rb_control_frame_t *reg_cfp, CALL_DATA cd, ISEQ blockiseq)
5551+
{
5552+
VALUE bh = vm_caller_setup_arg_block(ec, GET_CFP(), cd->ci, blockiseq, true);
5553+
VALUE val = vm_sendish(ec, GET_CFP(), cd, bh, mexp_search_super);
5554+
VM_EXEC(ec, val);
5555+
return val;
5556+
}
5557+
5558+
VALUE
5559+
rb_vm_invokeblock(rb_execution_context_t *ec, rb_control_frame_t *reg_cfp, CALL_DATA cd)
5560+
{
5561+
VALUE bh = VM_BLOCK_HANDLER_NONE;
5562+
VALUE val = vm_sendish(ec, GET_CFP(), cd, bh, mexp_search_invokeblock);
5563+
VM_EXEC(ec, val);
5564+
return val;
5565+
}
5566+
55315567
/* object.c */
55325568
VALUE rb_nil_to_s(VALUE);
55335569
VALUE rb_true_to_s(VALUE);

yjit.c

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1122,6 +1122,20 @@ rb_yjit_assert_holding_vm_lock(void)
11221122
ASSERT_vm_locking();
11231123
}
11241124

1125+
// The number of stack slots that vm_sendish() pops for send and invokesuper.
1126+
size_t
1127+
rb_yjit_sendish_sp_pops(const struct rb_callinfo *ci)
1128+
{
1129+
return 1 - sp_inc_of_sendish(ci); // + 1 to ignore return value push
1130+
}
1131+
1132+
// The number of stack slots that vm_sendish() pops for invokeblock.
1133+
size_t
1134+
rb_yjit_invokeblock_sp_pops(const struct rb_callinfo *ci)
1135+
{
1136+
return 1 - sp_inc_of_invokeblock(ci); // + 1 to ignore return value push
1137+
}
1138+
11251139
// Primitives used by yjit.rb
11261140
VALUE rb_yjit_stats_enabled_p(rb_execution_context_t *ec, VALUE self);
11271141
VALUE rb_yjit_trace_exit_locations_enabled_p(rb_execution_context_t *ec, VALUE self);

yjit/bindgen/src/main.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -325,6 +325,8 @@ fn main() {
325325
.allowlist_function("rb_yjit_icache_invalidate")
326326
.allowlist_function("rb_optimized_call")
327327
.allowlist_function("rb_yjit_assert_holding_vm_lock")
328+
.allowlist_function("rb_yjit_sendish_sp_pops")
329+
.allowlist_function("rb_yjit_invokeblock_sp_pops")
328330

329331
// from vm_sync.h
330332
.allowlist_function("rb_vm_barrier")

yjit/src/codegen.rs

Lines changed: 111 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -6428,6 +6428,38 @@ fn gen_struct_aset(
64286428
Some(EndBlock)
64296429
}
64306430

6431+
// Generate code that calls a method with dynamic dispatch
6432+
fn gen_send_dynamic<F: Fn(&mut Assembler) -> Opnd>(
6433+
jit: &mut JITState,
6434+
asm: &mut Assembler,
6435+
cd: *const rb_call_data,
6436+
sp_pops: usize,
6437+
vm_sendish: F,
6438+
) -> Option<CodegenStatus> {
6439+
// Our frame handling is not compatible with tailcall
6440+
if unsafe { vm_ci_flag((*cd).ci) } & VM_CALL_TAILCALL != 0 {
6441+
return None;
6442+
}
6443+
6444+
// Save PC and SP to prepare for dynamic dispatch
6445+
jit_prepare_routine_call(jit, asm);
6446+
6447+
// Pop arguments and a receiver
6448+
asm.stack_pop(sp_pops);
6449+
6450+
// Dispatch a method
6451+
let ret = vm_sendish(asm);
6452+
6453+
// Push the return value
6454+
let stack_ret = asm.stack_push(Type::Unknown);
6455+
asm.mov(stack_ret, ret);
6456+
6457+
// Fix the interpreter SP deviated by vm_sendish
6458+
asm.mov(Opnd::mem(64, CFP, RUBY_OFFSET_CFP_SP), SP);
6459+
6460+
Some(KeepCompiling)
6461+
}
6462+
64316463
fn gen_send_general(
64326464
jit: &mut JITState,
64336465
asm: &mut Assembler,
@@ -6909,33 +6941,84 @@ fn gen_opt_send_without_block(
69096941
asm: &mut Assembler,
69106942
ocb: &mut OutlinedCb,
69116943
) -> Option<CodegenStatus> {
6944+
// Generate specialized code if possible
69126945
let cd = jit.get_arg(0).as_ptr();
6946+
if let Some(status) = gen_send_general(jit, asm, ocb, cd, None) {
6947+
return Some(status);
6948+
}
69136949

6914-
gen_send_general(jit, asm, ocb, cd, None)
6950+
// Otherwise, fallback to dynamic dispatch using the interpreter's implementation of send
6951+
gen_send_dynamic(jit, asm, cd, unsafe { rb_yjit_sendish_sp_pops((*cd).ci) }, |asm| {
6952+
extern "C" {
6953+
fn rb_vm_opt_send_without_block(ec: EcPtr, cfp: CfpPtr, cd: VALUE) -> VALUE;
6954+
}
6955+
asm.ccall(
6956+
rb_vm_opt_send_without_block as *const u8,
6957+
vec![EC, CFP, (cd as usize).into()],
6958+
)
6959+
})
69156960
}
69166961

69176962
fn gen_send(
69186963
jit: &mut JITState,
69196964
asm: &mut Assembler,
69206965
ocb: &mut OutlinedCb,
69216966
) -> Option<CodegenStatus> {
6967+
// Generate specialized code if possible
69226968
let cd = jit.get_arg(0).as_ptr();
69236969
let block = jit.get_arg(1).as_optional_ptr();
6924-
return gen_send_general(jit, asm, ocb, cd, block);
6970+
if let Some(status) = gen_send_general(jit, asm, ocb, cd, block) {
6971+
return Some(status);
6972+
}
6973+
6974+
// Otherwise, fallback to dynamic dispatch using the interpreter's implementation of send
6975+
let blockiseq = jit.get_arg(1).as_iseq();
6976+
gen_send_dynamic(jit, asm, cd, unsafe { rb_yjit_sendish_sp_pops((*cd).ci) }, |asm| {
6977+
extern "C" {
6978+
fn rb_vm_send(ec: EcPtr, cfp: CfpPtr, cd: VALUE, blockiseq: IseqPtr) -> VALUE;
6979+
}
6980+
asm.ccall(
6981+
rb_vm_send as *const u8,
6982+
vec![EC, CFP, (cd as usize).into(), VALUE(blockiseq as usize).into()],
6983+
)
6984+
})
69256985
}
69266986

69276987
fn gen_invokeblock(
69286988
jit: &mut JITState,
69296989
asm: &mut Assembler,
69306990
ocb: &mut OutlinedCb,
6991+
) -> Option<CodegenStatus> {
6992+
// Generate specialized code if possible
6993+
let cd = jit.get_arg(0).as_ptr();
6994+
if let Some(status) = gen_invokeblock_specialized(jit, asm, ocb, cd) {
6995+
return Some(status);
6996+
}
6997+
6998+
// Otherwise, fallback to dynamic dispatch using the interpreter's implementation of send
6999+
gen_send_dynamic(jit, asm, cd, unsafe { rb_yjit_invokeblock_sp_pops((*cd).ci) }, |asm| {
7000+
extern "C" {
7001+
fn rb_vm_invokeblock(ec: EcPtr, cfp: CfpPtr, cd: VALUE) -> VALUE;
7002+
}
7003+
asm.ccall(
7004+
rb_vm_invokeblock as *const u8,
7005+
vec![EC, CFP, (cd as usize).into()],
7006+
)
7007+
})
7008+
}
7009+
7010+
fn gen_invokeblock_specialized(
7011+
jit: &mut JITState,
7012+
asm: &mut Assembler,
7013+
ocb: &mut OutlinedCb,
7014+
cd: *const rb_call_data,
69317015
) -> Option<CodegenStatus> {
69327016
if !jit.at_current_insn() {
69337017
defer_compilation(jit, asm, ocb);
69347018
return Some(EndBlock);
69357019
}
69367020

69377021
// Get call info
6938-
let cd = jit.get_arg(0).as_ptr();
69397022
let ci = unsafe { get_call_data_ci(cd) };
69407023
let argc: i32 = unsafe { vm_ci_argc(ci) }.try_into().unwrap();
69417024
let flags = unsafe { vm_ci_flag(ci) };
@@ -7065,7 +7148,31 @@ fn gen_invokesuper(
70657148
asm: &mut Assembler,
70667149
ocb: &mut OutlinedCb,
70677150
) -> Option<CodegenStatus> {
7068-
let cd: *const rb_call_data = jit.get_arg(0).as_ptr();
7151+
// Generate specialized code if possible
7152+
let cd = jit.get_arg(0).as_ptr();
7153+
if let Some(status) = gen_invokesuper_specialized(jit, asm, ocb, cd) {
7154+
return Some(status);
7155+
}
7156+
7157+
// Otherwise, fallback to dynamic dispatch using the interpreter's implementation of send
7158+
let blockiseq = jit.get_arg(1).as_iseq();
7159+
gen_send_dynamic(jit, asm, cd, unsafe { rb_yjit_sendish_sp_pops((*cd).ci) }, |asm| {
7160+
extern "C" {
7161+
fn rb_vm_invokesuper(ec: EcPtr, cfp: CfpPtr, cd: VALUE, blockiseq: IseqPtr) -> VALUE;
7162+
}
7163+
asm.ccall(
7164+
rb_vm_invokesuper as *const u8,
7165+
vec![EC, CFP, (cd as usize).into(), VALUE(blockiseq as usize).into()],
7166+
)
7167+
})
7168+
}
7169+
7170+
fn gen_invokesuper_specialized(
7171+
jit: &mut JITState,
7172+
asm: &mut Assembler,
7173+
ocb: &mut OutlinedCb,
7174+
cd: *const rb_call_data,
7175+
) -> Option<CodegenStatus> {
70697176
let block: Option<IseqPtr> = jit.get_arg(1).as_optional_ptr();
70707177

70717178
// Defer compilation so we can specialize on class of receiver

yjit/src/cruby_bindings.inc.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1336,4 +1336,6 @@ extern "C" {
13361336
line: ::std::os::raw::c_int,
13371337
);
13381338
pub fn rb_yjit_assert_holding_vm_lock();
1339+
pub fn rb_yjit_sendish_sp_pops(ci: *const rb_callinfo) -> usize;
1340+
pub fn rb_yjit_invokeblock_sp_pops(ci: *const rb_callinfo) -> usize;
13391341
}

0 commit comments

Comments
 (0)