summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--test/ruby/test_zjit.rb9
-rw-r--r--zjit/src/codegen.rs28
-rw-r--r--zjit/src/hir.rs38
-rw-r--r--zjit/src/stats.rs36
4 files changed, 92 insertions, 19 deletions
diff --git a/test/ruby/test_zjit.rb b/test/ruby/test_zjit.rb
index b5bdd0d12b..1d80093d57 100644
--- a/test/ruby/test_zjit.rb
+++ b/test/ruby/test_zjit.rb
@@ -308,6 +308,15 @@ class TestZJIT < Test::Unit::TestCase
}
end
+ def test_getblockparamproxy
+ assert_compiles '1', %q{
+ def test(&block)
+ 0.then(&block)
+ end
+ test { 1 }
+ }, insns: [:getblockparamproxy]
+ end
+
def test_call_a_forwardable_method
assert_runs '[]', %q{
def test_root = forwardable
diff --git a/zjit/src/codegen.rs b/zjit/src/codegen.rs
index c338d8bc1f..ca25084d95 100644
--- a/zjit/src/codegen.rs
+++ b/zjit/src/codegen.rs
@@ -398,6 +398,7 @@ fn gen_insn(cb: &mut CodeBlock, jit: &mut JITState, asm: &mut Assembler, functio
Insn::GetGlobal { id, state } => gen_getglobal(jit, asm, *id, &function.frame_state(*state)),
&Insn::GetLocal { ep_offset, level } => gen_getlocal_with_ep(asm, ep_offset, level),
&Insn::SetLocal { val, ep_offset, level } => no_output!(gen_setlocal_with_ep(asm, opnd!(val), function.type_of(val), ep_offset, level)),
+ &Insn::GetBlockParamProxy { level, state } => gen_get_block_param_proxy(jit, asm, level, &function.frame_state(state)),
Insn::GetConstantPath { ic, state } => gen_get_constant_path(jit, asm, *ic, &function.frame_state(*state)),
Insn::SetIvar { self_val, id, val, state: _ } => no_output!(gen_setivar(asm, opnd!(self_val), *id, opnd!(val))),
Insn::SideExit { state, reason } => no_output!(gen_side_exit(jit, asm, reason, &function.frame_state(*state))),
@@ -548,6 +549,29 @@ fn gen_setlocal_with_ep(asm: &mut Assembler, val: Opnd, val_type: Type, local_ep
}
}
+fn gen_get_block_param_proxy(jit: &JITState, asm: &mut Assembler, level: u32, state: &FrameState) -> lir::Opnd {
+ // Bail out if the `&block` local variable has been modified
+ let ep = gen_get_ep(asm, level);
+ let flags = Opnd::mem(64, ep, SIZEOF_VALUE_I32 * (VM_ENV_DATA_INDEX_FLAGS as i32));
+ asm.test(flags, VM_FRAME_FLAG_MODIFIED_BLOCK_PARAM.into());
+ asm.jnz(side_exit(jit, state, SideExitReason::BlockParamProxyModified));
+
+ // This handles two cases which are nearly identical
+ // Block handler is a tagged pointer. Look at the tag.
+ // VM_BH_ISEQ_BLOCK_P(): block_handler & 0x03 == 0x01
+ // VM_BH_IFUNC_P(): block_handler & 0x03 == 0x03
+ // So to check for either of those cases we can use: val & 0x1 == 0x1
+ const _: () = assert!(RUBY_SYMBOL_FLAG & 1 == 0, "guard below rejects symbol block handlers");
+
+ // Bail ouf if the block handler is neither ISEQ nor ifunc
+ let block_handler = asm.load(Opnd::mem(64, ep, SIZEOF_VALUE_I32 * VM_ENV_DATA_INDEX_SPECVAL));
+ asm.test(block_handler, 0x1.into());
+ asm.jz(side_exit(jit, state, SideExitReason::BlockParamProxyNotIseqOrIfunc));
+
+ // Return the rb_block_param_proxy instance (GC root, so put as a number to avoid unnecessary GC tracing)
+ unsafe { rb_block_param_proxy }.as_u64().into()
+}
+
fn gen_get_constant_path(jit: &JITState, asm: &mut Assembler, ic: *const iseq_inline_constant_cache, state: &FrameState) -> Opnd {
unsafe extern "C" {
fn rb_vm_opt_getconstant_path(ec: EcPtr, cfp: CfpPtr, ic: *const iseq_inline_constant_cache) -> VALUE;
@@ -1623,12 +1647,12 @@ fn compile_iseq(iseq: IseqPtr) -> Result<Function, CompileError> {
}
/// Build a Target::SideExit for non-PatchPoint instructions
-fn side_exit(jit: &mut JITState, state: &FrameState, reason: SideExitReason) -> Target {
+fn side_exit(jit: &JITState, state: &FrameState, reason: SideExitReason) -> Target {
build_side_exit(jit, state, reason, None)
}
/// Build a Target::SideExit out of a FrameState
-fn build_side_exit(jit: &mut JITState, state: &FrameState, reason: SideExitReason, label: Option<Label>) -> Target {
+fn build_side_exit(jit: &JITState, state: &FrameState, reason: SideExitReason, label: Option<Label>) -> Target {
let mut stack = Vec::new();
for &insn_id in state.stack() {
stack.push(jit.get_opnd(insn_id));
diff --git a/zjit/src/hir.rs b/zjit/src/hir.rs
index a7fa96b5c8..b7674b6a8c 100644
--- a/zjit/src/hir.rs
+++ b/zjit/src/hir.rs
@@ -462,6 +462,8 @@ pub enum SideExitReason {
CalleeSideExit,
ObjToStringFallback,
Interrupt,
+ BlockParamProxyModified,
+ BlockParamProxyNotIseqOrIfunc,
}
impl std::fmt::Display for SideExitReason {
@@ -556,6 +558,9 @@ pub enum Insn {
GetLocal { level: u32, ep_offset: u32 },
/// Set a local variable in a higher scope or the heap
SetLocal { level: u32, ep_offset: u32, val: InsnId },
+ /// Get a special singleton instance `rb_block_param_proxy` if the block
+ /// handler for the EP specified by `level` is an ISEQ or an ifunc.
+ GetBlockParamProxy { level: u32, state: InsnId },
GetSpecialSymbol { symbol_type: SpecialBackrefSymbol, state: InsnId },
GetSpecialNumber { nth: u64, state: InsnId },
@@ -900,6 +905,7 @@ impl<'a> std::fmt::Display for InsnPrinter<'a> {
Insn::SetGlobal { id, val, .. } => write!(f, "SetGlobal :{}, {val}", id.contents_lossy()),
Insn::GetLocal { level, ep_offset } => write!(f, "GetLocal l{level}, EP@{ep_offset}"),
Insn::SetLocal { val, level, ep_offset } => write!(f, "SetLocal l{level}, EP@{ep_offset}, {val}"),
+ Insn::GetBlockParamProxy { level, .. } => write!(f, "GetBlockParamProxy l{level}"),
Insn::GetSpecialSymbol { symbol_type, .. } => write!(f, "GetSpecialSymbol {symbol_type:?}"),
Insn::GetSpecialNumber { nth, .. } => write!(f, "GetSpecialNumber {nth}"),
Insn::ToArray { val, .. } => write!(f, "ToArray {val}"),
@@ -1361,6 +1367,7 @@ impl Function {
&NewRange { low, high, flag, state } => NewRange { low: find!(low), high: find!(high), flag, state: find!(state) },
&NewRangeFixnum { low, high, flag, state } => NewRangeFixnum { low: find!(low), high: find!(high), flag, state: find!(state) },
&ArrayMax { ref elements, state } => ArrayMax { elements: find_vec!(elements), state: find!(state) },
+ &GetBlockParamProxy { level, state } => GetBlockParamProxy { level, state: find!(state) },
&SetGlobal { id, val, state } => SetGlobal { id, val: find!(val), state },
&GetIvar { self_val, id, state } => GetIvar { self_val: find!(self_val), id, state },
&LoadIvarEmbedded { self_val, id, index } => LoadIvarEmbedded { self_val: find!(self_val), id, index },
@@ -1472,6 +1479,7 @@ impl Function {
Insn::ObjToString { .. } => types::BasicObject,
Insn::AnyToString { .. } => types::String,
Insn::GetLocal { .. } => types::BasicObject,
+ Insn::GetBlockParamProxy { .. } => types::BasicObject,
// The type of Snapshot doesn't really matter; it's never materialized. It's used only
// as a reference for FrameState, which we use to generate side-exit code.
Insn::Snapshot { .. } => types::Any,
@@ -2300,6 +2308,7 @@ impl Function {
| &Insn::LoadIvarExtended { self_val, .. } => {
worklist.push_back(self_val);
}
+ &Insn::GetBlockParamProxy { state, .. } |
&Insn::GetGlobal { state, .. } |
&Insn::GetSpecialSymbol { state, .. } |
&Insn::GetSpecialNumber { state, .. } |
@@ -3466,6 +3475,11 @@ pub fn iseq_to_hir(iseq: *const rb_iseq_t) -> Result<Function, ParseError> {
let level = get_arg(pc, 1).as_u32();
fun.push_insn(block, Insn::SetLocal { val: state.stack_pop()?, ep_offset, level });
}
+ YARVINSN_getblockparamproxy => {
+ let level = get_arg(pc, 1).as_u32();
+ let exit_id = fun.push_insn(block, Insn::Snapshot { state: exit_state });
+ state.stack_push(fun.push_insn(block, Insn::GetBlockParamProxy { level, state: exit_id }));
+ }
YARVINSN_pop => { state.stack_pop()?; }
YARVINSN_dup => { state.stack_push(state.stack_top()?); }
YARVINSN_dupn => {
@@ -5816,7 +5830,16 @@ mod tests {
v5:NilClass = Const Value(nil)
v10:BasicObject = InvokeBuiltin dir_s_open, v0, v1, v2
PatchPoint NoEPEscape(open)
- SideExit UnhandledYARVInsn(getblockparamproxy)
+ v16:BasicObject = GetBlockParamProxy l0
+ CheckInterrupts
+ v19:CBool = Test v16
+ IfFalse v19, bb1(v0, v1, v2, v3, v4, v10)
+ PatchPoint NoEPEscape(open)
+ SideExit UnhandledYARVInsn(invokeblock)
+ bb1(v27:BasicObject, v28:BasicObject, v29:BasicObject, v30:BasicObject, v31:BasicObject, v32:BasicObject):
+ PatchPoint NoEPEscape(open)
+ CheckInterrupts
+ Return v32
");
}
@@ -7981,6 +8004,19 @@ mod opt_tests {
}
#[test]
+ fn test_getblockparamproxy() {
+ eval("
+ def test(&block) = tap(&block)
+ ");
+ assert_snapshot!(hir_string("test"), @r"
+ fn test@<compiled>:2:
+ bb0(v0:BasicObject, v1:BasicObject):
+ v6:BasicObject = GetBlockParamProxy l0
+ SideExit UnhandledCallType(BlockArg)
+ ");
+ }
+
+ #[test]
fn test_getinstancevariable() {
eval("
def test = @foo
diff --git a/zjit/src/stats.rs b/zjit/src/stats.rs
index bce353ec9d..67edfe3d2d 100644
--- a/zjit/src/stats.rs
+++ b/zjit/src/stats.rs
@@ -103,6 +103,8 @@ make_counters! {
exit_obj_to_string_fallback,
exit_interrupt,
exit_optional_arguments,
+ exit_block_param_proxy_modified,
+ exit_block_param_proxy_not_iseq_or_ifunc,
}
// unhanded_call_: Unhandled call types
@@ -219,22 +221,24 @@ pub fn exit_counter_ptr(reason: crate::hir::SideExitReason) -> *mut u64 {
use crate::hir::SideExitReason::*;
use crate::stats::Counter::*;
let counter = match reason {
- UnknownNewarraySend(_) => exit_unknown_newarray_send,
- UnhandledCallType(_) => exit_unhandled_call_type,
- UnknownSpecialVariable(_) => exit_unknown_special_variable,
- UnhandledHIRInsn(_) => exit_unhandled_hir_insn,
- UnhandledYARVInsn(_) => exit_unhandled_yarv_insn,
- FixnumAddOverflow => exit_fixnum_add_overflow,
- FixnumSubOverflow => exit_fixnum_sub_overflow,
- FixnumMultOverflow => exit_fixnum_mult_overflow,
- GuardType(_) => exit_guard_type_failure,
- GuardTypeNot(_) => exit_guard_type_not_failure,
- GuardBitEquals(_) => exit_guard_bit_equals_failure,
- GuardShape(_) => exit_guard_shape_failure,
- PatchPoint(_) => exit_patchpoint,
- CalleeSideExit => exit_callee_side_exit,
- ObjToStringFallback => exit_obj_to_string_fallback,
- Interrupt => exit_interrupt,
+ UnknownNewarraySend(_) => exit_unknown_newarray_send,
+ UnhandledCallType(_) => exit_unhandled_call_type,
+ UnknownSpecialVariable(_) => exit_unknown_special_variable,
+ UnhandledHIRInsn(_) => exit_unhandled_hir_insn,
+ UnhandledYARVInsn(_) => exit_unhandled_yarv_insn,
+ FixnumAddOverflow => exit_fixnum_add_overflow,
+ FixnumSubOverflow => exit_fixnum_sub_overflow,
+ FixnumMultOverflow => exit_fixnum_mult_overflow,
+ GuardType(_) => exit_guard_type_failure,
+ GuardTypeNot(_) => exit_guard_type_not_failure,
+ GuardBitEquals(_) => exit_guard_bit_equals_failure,
+ GuardShape(_) => exit_guard_shape_failure,
+ PatchPoint(_) => exit_patchpoint,
+ CalleeSideExit => exit_callee_side_exit,
+ ObjToStringFallback => exit_obj_to_string_fallback,
+ Interrupt => exit_interrupt,
+ BlockParamProxyModified => exit_block_param_proxy_modified,
+ BlockParamProxyNotIseqOrIfunc => exit_block_param_proxy_not_iseq_or_ifunc,
};
counter_ptr(counter)
}