From 7633299c5080b2b37b134ce3c1c82491d127d4d2 Mon Sep 17 00:00:00 2001 From: Alan Wu Date: Wed, 26 Jul 2023 18:12:46 -0400 Subject: [PATCH] YJIT: getblockparamproxy for when block is a Proc --- yjit/bindgen/src/main.rs | 1 + yjit/src/codegen.rs | 56 ++++++++++++++++++++++++++++------ yjit/src/cruby_bindings.inc.rs | 1 + yjit/src/stats.rs | 6 +++- 4 files changed, 54 insertions(+), 10 deletions(-) diff --git a/yjit/bindgen/src/main.rs b/yjit/bindgen/src/main.rs index 5bda8b471b..461786b8aa 100644 --- a/yjit/bindgen/src/main.rs +++ b/yjit/bindgen/src/main.rs @@ -441,6 +441,7 @@ fn main() { .allowlist_function("rb_method_basic_definition_p") .allowlist_function("rb_yjit_array_len") .allowlist_function("rb_obj_class") + .allowlist_function("rb_obj_is_proc") // We define VALUE manually, don't import it .blocklist_type("VALUE") diff --git a/yjit/src/codegen.rs b/yjit/src/codegen.rs index 81798fdd43..f65ffefe5a 100644 --- a/yjit/src/codegen.rs +++ b/yjit/src/codegen.rs @@ -7784,19 +7784,19 @@ fn gen_getblockparamproxy( return Some(EndBlock); } - // A mirror of the interpreter code. Checking for the case - // where it's pushing rb_block_param_proxy. - // EP level let level = jit.get_arg(1).as_u32(); // Peek at the block handler so we can check whether it's nil let comptime_handler = jit.peek_at_block_handler(level); - // When a block handler is present, it should always be a GC-guarded - // pointer (VM_BH_ISEQ_BLOCK_P) - if comptime_handler.as_u64() != 0 && comptime_handler.as_u64() & 0x3 != 0x1 { - incr_counter!(gbpp_not_gc_guarded); + // Filter for the 3 cases we currently handle + if !(comptime_handler.as_u64() == 0 || // no block given + comptime_handler.as_u64() & 0x3 == 0x1 || // iseq block (no associated GC managed object) + unsafe { rb_obj_is_proc(comptime_handler) }.test() // block is a Proc + ) { + // Missing the symbol case, where we basically need to call Symbol#to_proc at runtime + gen_counter_incr(asm, Counter::gbpp_unsupported_type); return None; } @@ -7818,7 +7818,12 @@ fn gen_getblockparamproxy( Opnd::mem(64, ep_opnd, SIZEOF_VALUE_I32 * VM_ENV_DATA_INDEX_SPECVAL) ); - // Specialize compilation for the case where no block handler is present + // Use block handler sample to guide specialization... + // NOTE: we use jit_chain_guard() in this decision tree, and since + // there are only 3 cases, it should never reach the depth limit use + // the exit counter we pass to it. + // + // No block given if comptime_handler.as_u64() == 0 { // Bail if there is a block handler asm.cmp(block_handler, Opnd::UImm(0)); @@ -7833,7 +7838,7 @@ fn gen_getblockparamproxy( ); jit_putobject(asm, Qnil); - } else { + } else if comptime_handler.as_u64() & 0x3 == 0x1 { // Block handler is a tagged pointer. Look at the tag. 0x03 is from VM_BH_ISEQ_BLOCK_P(). let block_handler = asm.and(block_handler, 0x3.into()); @@ -7854,6 +7859,39 @@ fn gen_getblockparamproxy( let top = asm.stack_push(Type::BlockParamProxy); asm.mov(top, Opnd::const_ptr(unsafe { rb_block_param_proxy }.as_ptr())); + } else if unsafe { rb_obj_is_proc(comptime_handler) }.test() { + // The block parameter is a Proc + c_callable! { + // We can't hold values across C calls due to a backend limitation, + // so we'll use this thin wrapper around rb_obj_is_proc(). + fn is_proc(object: VALUE) -> VALUE { + if unsafe { rb_obj_is_proc(object) }.test() { + // VM_BH_TO_PROC() is the identify function. + object + } else { + Qfalse + } + } + } + + // Simple predicate, no need to jit_prepare_routine_call() + let proc_or_false = asm.ccall(is_proc as _, vec![block_handler]); + + // Guard for proc + asm.cmp(proc_or_false, Qfalse.into()); + jit_chain_guard( + JCC_JE, + jit, + asm, + ocb, + SEND_MAX_DEPTH, + Counter::gbpp_block_handler_not_proc, + ); + + let top = asm.stack_push(Type::Unknown); + asm.mov(top, proc_or_false); + } else { + unreachable!("absurd given initial filtering"); } jump_to_next_insn(jit, asm, ocb); diff --git a/yjit/src/cruby_bindings.inc.rs b/yjit/src/cruby_bindings.inc.rs index 506120f3f0..7c4ecc5143 100644 --- a/yjit/src/cruby_bindings.inc.rs +++ b/yjit/src/cruby_bindings.inc.rs @@ -1099,6 +1099,7 @@ extern "C" { pub fn rb_hash_aref(hash: VALUE, key: VALUE) -> VALUE; pub fn rb_hash_aset(hash: VALUE, key: VALUE, val: VALUE) -> VALUE; pub fn rb_hash_bulk_insert(argc: ::std::os::raw::c_long, argv: *const VALUE, hash: VALUE); + pub fn rb_obj_is_proc(recv: VALUE) -> VALUE; pub fn rb_sym2id(obj: VALUE) -> ID; pub fn rb_id2sym(id: ID) -> VALUE; pub fn rb_intern(name: *const ::std::os::raw::c_char) -> ID; diff --git a/yjit/src/stats.rs b/yjit/src/stats.rs index c790a76f1b..5da15198b1 100644 --- a/yjit/src/stats.rs +++ b/yjit/src/stats.rs @@ -355,11 +355,15 @@ make_counters! { expandarray_not_array, expandarray_rhs_too_small, + // getblockparam gbp_wb_required, - gbpp_not_gc_guarded, + + // getblockparamproxy + gbpp_unsupported_type, gbpp_block_param_modified, gbpp_block_handler_not_none, gbpp_block_handler_not_iseq, + gbpp_block_handler_not_proc, branchif_interrupted, branchunless_interrupted,