Skip to content

Commit ebaf56c

Browse files
committed
YJIT: Implement getblockparam
This implements the getblockparam instruction. There are two cases we need to handle depending on whether or not VM_FRAME_FLAG_MODIFIED_BLOCK_PARAM is set in the environment flag. When the modified flag is unset, we need to call rb_vm_bh_to_procval to get a proc from our passed block, save the proc in the environment, and set the modified flag. In the case that the modified flag is set we are able to just use the existing proc in the environment. One quirk of this is that we need to call jit_prepare_routine_call early and ensure we update PC and SP regardless of the branch taken, so that we have a consistent SP offset at the start of the next instruction. We considered using a chain guard to generate these two paths separately, but decided against it because it's very common to see both and the modified case is basically a subset of the instructions in the unmodified case. This includes tests for both getblockparam and getblockparamproxy which was previously missing a test.
1 parent 14ae97d commit ebaf56c

File tree

4 files changed

+123
-0
lines changed

4 files changed

+123
-0
lines changed

test/ruby/test_yjit.rb

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -483,6 +483,32 @@ def foo(obj)
483483
RUBY
484484
end
485485

486+
def test_getblockparam
487+
assert_compiles(<<~'RUBY', insns: [:getblockparam])
488+
def foo &blk
489+
2.times do
490+
blk
491+
end
492+
end
493+
494+
foo {}
495+
foo {}
496+
RUBY
497+
end
498+
499+
def test_getblockparamproxy
500+
# Currently two side exits as OPTIMIZED_METHOD_TYPE_CALL is unimplemented
501+
assert_compiles(<<~'RUBY', insns: [:getblockparamproxy], exits: { opt_send_without_block: 2 })
502+
def foo &blk
503+
p blk.call
504+
p blk.call
505+
end
506+
507+
foo { 1 }
508+
foo { 2 }
509+
RUBY
510+
end
511+
486512
def test_getivar_opt_plus
487513
assert_no_exits(<<~RUBY)
488514
class TheClass

yjit/bindgen/src/main.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -210,6 +210,7 @@ fn main() {
210210
.opaque_type("rb_execution_context_.*")
211211
.blocklist_type("rb_control_frame_struct")
212212
.opaque_type("rb_control_frame_struct")
213+
.allowlist_function("rb_vm_bh_to_procval")
213214

214215
// From yjit.c
215216
.allowlist_function("rb_iseq_(get|set)_yjit_payload")

yjit/src/codegen.rs

Lines changed: 93 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5573,6 +5573,98 @@ fn gen_getblockparamproxy(
55735573
KeepCompiling
55745574
}
55755575

5576+
fn gen_getblockparam(
5577+
jit: &mut JITState,
5578+
ctx: &mut Context,
5579+
cb: &mut CodeBlock,
5580+
ocb: &mut OutlinedCb,
5581+
) -> CodegenStatus {
5582+
// EP level
5583+
let level = jit_get_arg(jit, 1).as_u32();
5584+
5585+
// Save the PC and SP because we might allocate
5586+
jit_prepare_routine_call(jit, ctx, cb, REG0);
5587+
5588+
// A mirror of the interpreter code. Checking for the case
5589+
// where it's pushing rb_block_param_proxy.
5590+
let side_exit = get_side_exit(jit, ocb, ctx);
5591+
5592+
// Load environment pointer EP from CFP
5593+
gen_get_ep(cb, REG1, level);
5594+
5595+
// Bail when VM_ENV_FLAGS(ep, VM_FRAME_FLAG_MODIFIED_BLOCK_PARAM) is non zero
5596+
let flag_check = mem_opnd(
5597+
64,
5598+
REG1,
5599+
(SIZEOF_VALUE as i32) * (VM_ENV_DATA_INDEX_FLAGS as i32),
5600+
);
5601+
// FIXME: This is testing bits in the same place that the WB check is testing.
5602+
// We should combine these at some point
5603+
test(
5604+
cb,
5605+
flag_check,
5606+
uimm_opnd(VM_FRAME_FLAG_MODIFIED_BLOCK_PARAM.into()),
5607+
);
5608+
5609+
// If the frame flag has been modified, then the actual proc value is
5610+
// already in the EP and we should just use the value.
5611+
let frame_flag_modified = cb.new_label("frame_flag_modified".to_string());
5612+
jnz_label(cb, frame_flag_modified);
5613+
5614+
// This instruction writes the block handler to the EP. If we need to
5615+
// fire a write barrier for the write, then exit (we'll let the
5616+
// interpreter handle it so it can fire the write barrier).
5617+
// flags & VM_ENV_FLAG_WB_REQUIRED
5618+
let flags_opnd = mem_opnd(
5619+
64,
5620+
REG1,
5621+
SIZEOF_VALUE as i32 * VM_ENV_DATA_INDEX_FLAGS as i32,
5622+
);
5623+
test(cb, flags_opnd, imm_opnd(VM_ENV_FLAG_WB_REQUIRED.into()));
5624+
5625+
// if (flags & VM_ENV_FLAG_WB_REQUIRED) != 0
5626+
jnz_ptr(cb, side_exit);
5627+
5628+
// Load the block handler for the current frame
5629+
// note, VM_ASSERT(VM_ENV_LOCAL_P(ep))
5630+
mov(
5631+
cb,
5632+
C_ARG_REGS[1],
5633+
mem_opnd(
5634+
64,
5635+
REG1,
5636+
(SIZEOF_VALUE as i32) * (VM_ENV_DATA_INDEX_SPECVAL as i32),
5637+
),
5638+
);
5639+
5640+
// Convert the block handler in to a proc
5641+
// call rb_vm_bh_to_procval(const rb_execution_context_t *ec, VALUE block_handler)
5642+
mov(cb, C_ARG_REGS[0], REG_EC);
5643+
call_ptr(cb, REG0, rb_vm_bh_to_procval as *const u8);
5644+
5645+
// Load environment pointer EP from CFP (again)
5646+
gen_get_ep(cb, REG1, level);
5647+
5648+
// Set the frame modified flag
5649+
or(cb, flag_check, uimm_opnd(VM_FRAME_FLAG_MODIFIED_BLOCK_PARAM.into()));
5650+
5651+
// Write the value at the environment pointer
5652+
let idx = jit_get_arg(jit, 0).as_i32();
5653+
let offs = -(SIZEOF_VALUE as i32 * idx);
5654+
mov(cb, mem_opnd(64, REG1, offs), RAX);
5655+
5656+
cb.write_label(frame_flag_modified);
5657+
5658+
// Push the proc on the stack
5659+
let stack_ret = ctx.stack_push(Type::Unknown);
5660+
mov(cb, RAX, mem_opnd(64, REG1, offs));
5661+
mov(cb, stack_ret, RAX);
5662+
5663+
cb.link_labels();
5664+
5665+
KeepCompiling
5666+
}
5667+
55765668
fn gen_invokebuiltin(
55775669
jit: &mut JITState,
55785670
ctx: &mut Context,
@@ -5743,6 +5835,7 @@ fn get_gen_fn(opcode: VALUE) -> Option<InsnGenFn> {
57435835
OP_JUMP => Some(gen_jump),
57445836

57455837
OP_GETBLOCKPARAMPROXY => Some(gen_getblockparamproxy),
5838+
OP_GETBLOCKPARAM => Some(gen_getblockparam),
57465839
OP_OPT_SEND_WITHOUT_BLOCK => Some(gen_opt_send_without_block),
57475840
OP_SEND => Some(gen_send),
57485841
OP_INVOKESUPER => Some(gen_invokesuper),

yjit/src/cruby_bindings.inc.rs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -588,6 +588,9 @@ pub const VM_ENV_FLAG_ESCAPED: vm_frame_env_flags = 4;
588588
pub const VM_ENV_FLAG_WB_REQUIRED: vm_frame_env_flags = 8;
589589
pub const VM_ENV_FLAG_ISOLATED: vm_frame_env_flags = 16;
590590
pub type vm_frame_env_flags = u32;
591+
extern "C" {
592+
pub fn rb_vm_bh_to_procval(ec: *const rb_execution_context_t, block_handler: VALUE) -> VALUE;
593+
}
591594
extern "C" {
592595
pub fn rb_vm_frame_method_entry(
593596
cfp: *const rb_control_frame_t,

0 commit comments

Comments
 (0)