diff options
| author | Takashi Kokubun <takashi.kokubun@shopify.com> | 2025-09-17 12:40:41 -0700 |
|---|---|---|
| committer | GitHub <noreply@github.com> | 2025-09-17 12:40:41 -0700 |
| commit | bb3355760ad0abe3c07b7f9e37be043bad796697 (patch) | |
| tree | 9853d30751bc4a0c52a0877ece296e5b0cfd698e | |
| parent | c995faa8627fbb2509c5a35cde9e01445639fc83 (diff) | |
ZJIT: Split Insn::Const from Insn::GetBlockParamProxy (#14583)
* ZJIT: Split Insn::Const from Insn::GetBlockParamProxy
* Print [BlockParamProxy]
* Link a TODO comment to a Shopify/ruby issue
| -rw-r--r-- | zjit/src/codegen.rs | 7 | ||||
| -rw-r--r-- | zjit/src/hir.rs | 47 | ||||
| -rw-r--r-- | zjit/src/hir_type/mod.rs | 3 |
3 files changed, 29 insertions, 28 deletions
diff --git a/zjit/src/codegen.rs b/zjit/src/codegen.rs index fce16b5d93..2830b623b8 100644 --- a/zjit/src/codegen.rs +++ b/zjit/src/codegen.rs @@ -393,6 +393,7 @@ fn gen_insn(cb: &mut CodeBlock, jit: &mut JITState, asm: &mut Assembler, functio Insn::GuardType { val, guard_type, state } => gen_guard_type(jit, asm, opnd!(val), *guard_type, &function.frame_state(*state)), Insn::GuardTypeNot { val, guard_type, state } => gen_guard_type_not(jit, asm, opnd!(val), *guard_type, &function.frame_state(*state)), Insn::GuardBitEquals { val, expected, state } => gen_guard_bit_equals(jit, asm, opnd!(val), *expected, &function.frame_state(*state)), + &Insn::GuardBlockParamProxy { level, state } => no_output!(gen_guard_block_param_proxy(jit, asm, level, &function.frame_state(state))), Insn::PatchPoint { invariant, state } => no_output!(gen_patch_point(jit, asm, invariant, &function.frame_state(*state))), Insn::CCall { cfun, args, name: _, return_type: _, elidable: _ } => gen_ccall(asm, *cfun, opnds!(args)), Insn::GetIvar { self_val, id, state: _ } => gen_getivar(asm, opnd!(self_val), *id), @@ -400,7 +401,6 @@ 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))), @@ -551,7 +551,7 @@ 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 { +fn gen_guard_block_param_proxy(jit: &JITState, asm: &mut Assembler, level: u32, state: &FrameState) { // 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)); @@ -569,9 +569,6 @@ fn gen_get_block_param_proxy(jit: &JITState, asm: &mut Assembler, level: u32, st 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 { diff --git a/zjit/src/hir.rs b/zjit/src/hir.rs index c7b0f0d610..af30f60fe4 100644 --- a/zjit/src/hir.rs +++ b/zjit/src/hir.rs @@ -595,9 +595,6 @@ 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 }, @@ -679,6 +676,9 @@ pub enum Insn { GuardBitEquals { val: InsnId, expected: VALUE, state: InsnId }, /// Side-exit if val doesn't have the expected shape. GuardShape { val: InsnId, shape: ShapeId, state: InsnId }, + /// Side-exit if the block param has been modified or the block handler for the frame + /// is neither ISEQ nor ifunc, which makes it incompatible with rb_block_param_proxy. + GuardBlockParamProxy { level: u32, state: InsnId }, /// Generate no code (or padding if necessary) and insert a patch point /// that can be rewritten to a side exit when the Invariant is broken. @@ -704,7 +704,7 @@ impl Insn { | Insn::PatchPoint { .. } | Insn::SetIvar { .. } | Insn::ArrayExtend { .. } | Insn::ArrayPush { .. } | Insn::SideExit { .. } | Insn::SetGlobal { .. } | Insn::SetLocal { .. } | Insn::Throw { .. } | Insn::IncrCounter(_) - | Insn::CheckInterrupts { .. } => false, + | Insn::CheckInterrupts { .. } | Insn::GuardBlockParamProxy { .. } => false, _ => true, } } @@ -921,6 +921,7 @@ impl<'a> std::fmt::Display for InsnPrinter<'a> { Insn::GuardTypeNot { val, guard_type, .. } => { write!(f, "GuardTypeNot {val}, {}", guard_type.print(self.ptr_map)) }, Insn::GuardBitEquals { val, expected, .. } => { write!(f, "GuardBitEquals {val}, {}", expected.print(self.ptr_map)) }, &Insn::GuardShape { val, shape, .. } => { write!(f, "GuardShape {val}, {:p}", self.ptr_map.map_shape(shape)) }, + Insn::GuardBlockParamProxy { level, .. } => write!(f, "GuardBlockParamProxy l{level}"), Insn::PatchPoint { invariant, .. } => { write!(f, "PatchPoint {}", invariant.print(self.ptr_map)) }, Insn::GetConstantPath { ic, .. } => { write!(f, "GetConstantPath {:p}", self.ptr_map.map_ptr(ic)) }, Insn::CCall { cfun, args, name, return_type: _, elidable: _ } => { @@ -956,7 +957,6 @@ 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}"), @@ -1349,6 +1349,7 @@ impl Function { &GuardTypeNot { val, guard_type, state } => GuardTypeNot { val: find!(val), guard_type, state }, &GuardBitEquals { val, expected, state } => GuardBitEquals { val: find!(val), expected, state }, &GuardShape { val, shape, state } => GuardShape { val: find!(val), shape, state }, + &GuardBlockParamProxy { level, state } => GuardBlockParamProxy { level, state: find!(state) }, &FixnumAdd { left, right, state } => FixnumAdd { left: find!(left), right: find!(right), state }, &FixnumSub { left, right, state } => FixnumSub { left: find!(left), right: find!(right), state }, &FixnumMult { left, right, state } => FixnumMult { left: find!(left), right: find!(right), state }, @@ -1424,7 +1425,6 @@ 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 }, @@ -1465,7 +1465,7 @@ impl Function { | Insn::IfTrue { .. } | Insn::IfFalse { .. } | Insn::Return { .. } | Insn::Throw { .. } | Insn::PatchPoint { .. } | Insn::SetIvar { .. } | Insn::ArrayExtend { .. } | Insn::ArrayPush { .. } | Insn::SideExit { .. } | Insn::SetLocal { .. } | Insn::IncrCounter(_) - | Insn::CheckInterrupts { .. } => + | Insn::CheckInterrupts { .. } | Insn::GuardBlockParamProxy { .. } => panic!("Cannot infer type of instruction with no output: {}", self.insns[insn.0]), Insn::Const { val: Const::Value(val) } => Type::from_value(*val), Insn::Const { val: Const::CBool(val) } => Type::from_cbool(*val), @@ -1537,7 +1537,6 @@ 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, @@ -2370,7 +2369,7 @@ impl Function { | &Insn::LoadIvarExtended { self_val, .. } => { worklist.push_back(self_val); } - &Insn::GetBlockParamProxy { state, .. } | + &Insn::GuardBlockParamProxy { state, .. } | &Insn::GetGlobal { state, .. } | &Insn::GetSpecialSymbol { state, .. } | &Insn::GetSpecialNumber { state, .. } | @@ -3515,7 +3514,9 @@ pub fn iseq_to_hir(iseq: *const rb_iseq_t) -> Result<Function, ParseError> { 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 })); + fun.push_insn(block, Insn::GuardBlockParamProxy { level, state: exit_id }); + // TODO(Shopify/ruby#753): GC root, so we should be able to avoid unnecessary GC tracing + state.stack_push(fun.push_insn(block, Insn::Const { val: Const::Value(unsafe { rb_block_param_proxy }) })); } YARVINSN_pop => { state.stack_pop()?; } YARVINSN_dup => { state.stack_push(state.stack_top()?); } @@ -5942,19 +5943,20 @@ mod tests { v5:NilClass = Const Value(nil) v10:BasicObject = InvokeBuiltin dir_s_open, v0, v1, v2 PatchPoint NoEPEscape(open) - v16:BasicObject = GetBlockParamProxy l0 + GuardBlockParamProxy l0 + v17:BasicObject[BlockParamProxy] = Const Value(VALUE(0x1000)) CheckInterrupts - v19:CBool = Test v16 - IfFalse v19, bb1(v0, v1, v2, v3, v4, v10) + v20:CBool = Test v17 + IfFalse v20, bb1(v0, v1, v2, v3, v4, v10) PatchPoint NoEPEscape(open) - v26:BasicObject = InvokeBlock, v10 - v30:BasicObject = InvokeBuiltin dir_s_close, v0, v10 + v27:BasicObject = InvokeBlock, v10 + v31:BasicObject = InvokeBuiltin dir_s_close, v0, v10 CheckInterrupts - Return v26 - bb1(v36:BasicObject, v37:BasicObject, v38:BasicObject, v39:BasicObject, v40:BasicObject, v41:BasicObject): + Return v27 + bb1(v37:BasicObject, v38:BasicObject, v39:BasicObject, v40:BasicObject, v41:BasicObject, v42:BasicObject): PatchPoint NoEPEscape(open) CheckInterrupts - Return v41 + Return v42 "); } @@ -8158,11 +8160,12 @@ mod opt_tests { assert_snapshot!(hir_string("test"), @r" fn test@<compiled>:2: bb0(v0:BasicObject, v1:BasicObject): - v6:BasicObject = GetBlockParamProxy l0 - v8:BasicObject = Send v0, 0x1000, :tap, v6 - v9:BasicObject = GetLocal l0, EP@3 + GuardBlockParamProxy l0 + v7:BasicObject[BlockParamProxy] = Const Value(VALUE(0x1000)) + v9:BasicObject = Send v0, 0x1008, :tap, v7 + v10:BasicObject = GetLocal l0, EP@3 CheckInterrupts - Return v8 + Return v9 "); } diff --git a/zjit/src/hir_type/mod.rs b/zjit/src/hir_type/mod.rs index 926d6d306f..33f6ab223b 100644 --- a/zjit/src/hir_type/mod.rs +++ b/zjit/src/hir_type/mod.rs @@ -1,7 +1,7 @@ //! High-level intermediate representation types. #![allow(non_upper_case_globals)] -use crate::cruby::{Qfalse, Qnil, Qtrue, VALUE, RUBY_T_ARRAY, RUBY_T_STRING, RUBY_T_HASH, RUBY_T_CLASS, RUBY_T_MODULE}; +use crate::cruby::{rb_block_param_proxy, Qfalse, Qnil, Qtrue, RUBY_T_ARRAY, RUBY_T_CLASS, RUBY_T_HASH, RUBY_T_MODULE, RUBY_T_STRING, VALUE}; use crate::cruby::{rb_cInteger, rb_cFloat, rb_cArray, rb_cHash, rb_cString, rb_cSymbol, rb_cObject, rb_cTrueClass, rb_cFalseClass, rb_cNilClass, rb_cRange, rb_cSet, rb_cRegexp, rb_cClass, rb_cModule, rb_zjit_singleton_class_p}; use crate::cruby::ClassRelationship; use crate::cruby::get_class_name; @@ -75,6 +75,7 @@ fn write_spec(f: &mut std::fmt::Formatter, printer: &TypePrinter) -> std::fmt::R match ty.spec { Specialization::Any | Specialization::Empty => { Ok(()) }, Specialization::Object(val) if val == unsafe { rb_mRubyVMFrozenCore } => write!(f, "[VMFrozenCore]"), + Specialization::Object(val) if val == unsafe { rb_block_param_proxy } => write!(f, "[BlockParamProxy]"), Specialization::Object(val) if ty.is_subtype(types::Symbol) => write!(f, "[:{}]", ruby_sym_to_rust_string(val)), Specialization::Object(val) => write!(f, "[{}]", val.print(printer.ptr_map)), // TODO(max): Ensure singleton classes never have Type specialization |
