diff options
| author | Kevin Menard <kevin@nirvdrum.com> | 2026-01-21 14:07:20 -0500 |
|---|---|---|
| committer | GitHub <noreply@github.com> | 2026-01-21 19:07:20 +0000 |
| commit | cfa97af7e1426c76b769495ef4b1689be3b0a685 (patch) | |
| tree | 4f126e62323ea778984edeba3c490006a11248cd | |
| parent | 6d0b47de86eef3a396ff2bf6eea731d2fb778ded (diff) | |
ZJIT: Introduce `GetLEP` HIR instruction (#15917)
This PR is a follow-up to #15816. There, I introduced the `GuardSuperMethodEntry` HIR instruction and that needed the LEP. The LEP was also used by `GetBlockHandler`. Consequently, the codegen for `invokesuper` ended up loading the LEP twice. By introducing a new HIR instruction, we can load the LEP once and use it in both `GetBlockHandler` and `GuardSuperMethodEntry`.
I also updated `IsBlockGiven`, which conditionally loaded the LEP. To ensure we only use `GetLEP` in the cases we need it, I lifted most of the `IsBlockGiven` handler to HIR. As an added benefit, this addressed a TODO that @tekknolagi had written: when `block_given?` is called outside of a method we can rewrite to a constant `false`.
We could use `GetLEP` in the handling of `Defined`, but that looked a bit more involved and I wanted to keep this PR focused, so I'm suggesting we handle that as future work.
| -rw-r--r-- | zjit/src/codegen.rs | 26 | ||||
| -rw-r--r-- | zjit/src/cruby_methods.rs | 10 | ||||
| -rw-r--r-- | zjit/src/hir.rs | 60 | ||||
| -rw-r--r-- | zjit/src/hir/opt_tests.rs | 33 |
4 files changed, 78 insertions, 51 deletions
diff --git a/zjit/src/codegen.rs b/zjit/src/codegen.rs index 7c867cd6b4..d777002e31 100644 --- a/zjit/src/codegen.rs +++ b/zjit/src/codegen.rs @@ -532,8 +532,8 @@ fn gen_insn(cb: &mut CodeBlock, jit: &mut JITState, asm: &mut Assembler, functio Insn::GuardNotShared { recv, state } => gen_guard_not_shared(jit, asm, opnd!(recv), &function.frame_state(*state)), &Insn::GuardLess { left, right, state } => gen_guard_less(jit, asm, opnd!(left), opnd!(right), &function.frame_state(state)), &Insn::GuardGreaterEq { left, right, state } => gen_guard_greater_eq(jit, asm, opnd!(left), opnd!(right), &function.frame_state(state)), - &Insn::GuardSuperMethodEntry { cme, state } => no_output!(gen_guard_super_method_entry(jit, asm, cme, &function.frame_state(state))), - Insn::GetBlockHandler => gen_get_block_handler(jit, asm), + &Insn::GuardSuperMethodEntry { lep, cme, state } => no_output!(gen_guard_super_method_entry(jit, asm, opnd!(lep), cme, &function.frame_state(state))), + Insn::GetBlockHandler { lep } => gen_get_block_handler(asm, opnd!(lep)), Insn::PatchPoint { invariant, state } => no_output!(gen_patch_point(jit, asm, invariant, &function.frame_state(*state))), Insn::CCall { cfunc, recv, args, name, return_type: _, elidable: _ } => gen_ccall(asm, *cfunc, *name, opnd!(recv), opnds!(args)), // Give up CCallWithFrame for 7+ args since asm.ccall() supports at most 6 args (recv + args). @@ -576,11 +576,12 @@ fn gen_insn(cb: &mut CodeBlock, jit: &mut JITState, asm: &mut Assembler, functio &Insn::GuardShape { val, shape, state } => gen_guard_shape(jit, asm, opnd!(val), shape, &function.frame_state(state)), Insn::LoadPC => gen_load_pc(asm), Insn::LoadEC => gen_load_ec(), + Insn::GetLEP => gen_get_lep(jit, asm), Insn::LoadSelf => gen_load_self(), &Insn::LoadField { recv, id, offset, return_type } => gen_load_field(asm, opnd!(recv), id, offset, return_type), &Insn::StoreField { recv, id, offset, val } => no_output!(gen_store_field(asm, opnd!(recv), id, offset, opnd!(val), function.type_of(val))), &Insn::WriteBarrier { recv, val } => no_output!(gen_write_barrier(asm, opnd!(recv), opnd!(val), function.type_of(val))), - &Insn::IsBlockGiven => gen_is_block_given(jit, asm), + &Insn::IsBlockGiven { lep } => gen_is_block_given(asm, opnd!(lep)), Insn::ArrayInclude { elements, target, state } => gen_array_include(jit, asm, opnds!(elements), opnd!(target), &function.frame_state(*state)), Insn::ArrayPackBuffer { elements, fmt, buffer, state } => gen_array_pack_buffer(jit, asm, opnds!(elements), opnd!(fmt), opnd!(buffer), &function.frame_state(*state)), &Insn::DupArrayInclude { ary, target, state } => gen_dup_array_include(jit, asm, ary, opnd!(target), &function.frame_state(state)), @@ -688,16 +689,10 @@ fn gen_defined(jit: &JITState, asm: &mut Assembler, op_type: usize, obj: VALUE, } /// Similar to gen_defined for DEFINED_YIELD -fn gen_is_block_given(jit: &JITState, asm: &mut Assembler) -> Opnd { - let local_iseq = unsafe { rb_get_iseq_body_local_iseq(jit.iseq) }; - if unsafe { rb_get_iseq_body_type(local_iseq) } == ISEQ_TYPE_METHOD { - let lep = gen_get_lep(jit, asm); - let block_handler = asm.load(Opnd::mem(64, lep, SIZEOF_VALUE_I32 * VM_ENV_DATA_INDEX_SPECVAL)); - asm.cmp(block_handler, VM_BLOCK_HANDLER_NONE.into()); - asm.csel_e(Qfalse.into(), Qtrue.into()) - } else { - Qfalse.into() - } +fn gen_is_block_given(asm: &mut Assembler, lep: Opnd) -> Opnd { + let block_handler = asm.load(Opnd::mem(64, lep, SIZEOF_VALUE_I32 * VM_ENV_DATA_INDEX_SPECVAL)); + asm.cmp(block_handler, VM_BLOCK_HANDLER_NONE.into()); + asm.csel_e(Qfalse.into(), Qtrue.into()) } fn gen_unbox_fixnum(asm: &mut Assembler, val: Opnd) -> Opnd { @@ -803,11 +798,11 @@ fn gen_guard_greater_eq(jit: &JITState, asm: &mut Assembler, left: Opnd, right: fn gen_guard_super_method_entry( jit: &JITState, asm: &mut Assembler, + lep: Opnd, cme: *const rb_callable_method_entry_t, state: &FrameState, ) { asm_comment!(asm, "guard super method entry"); - let lep = gen_get_lep(jit, asm); let ep_me_opnd = Opnd::mem(64, lep, SIZEOF_VALUE_I32 * VM_ENV_DATA_INDEX_ME_CREF); let ep_me = asm.load(ep_me_opnd); asm.cmp(ep_me, Opnd::UImm(cme as u64)); @@ -815,9 +810,8 @@ fn gen_guard_super_method_entry( } /// Get the block handler from ep[VM_ENV_DATA_INDEX_SPECVAL] at the local EP (LEP). -fn gen_get_block_handler(jit: &JITState, asm: &mut Assembler) -> Opnd { +fn gen_get_block_handler(asm: &mut Assembler, lep: Opnd) -> Opnd { asm_comment!(asm, "get block handler from LEP"); - let lep = gen_get_lep(jit, asm); asm.load(Opnd::mem(64, lep, SIZEOF_VALUE_I32 * VM_ENV_DATA_INDEX_SPECVAL)) } diff --git a/zjit/src/cruby_methods.rs b/zjit/src/cruby_methods.rs index 357c8b0c12..8121b0065f 100644 --- a/zjit/src/cruby_methods.rs +++ b/zjit/src/cruby_methods.rs @@ -318,8 +318,14 @@ fn inline_kernel_itself(_fun: &mut hir::Function, _block: hir::BlockId, recv: hi fn inline_kernel_block_given_p(fun: &mut hir::Function, block: hir::BlockId, _recv: hir::InsnId, args: &[hir::InsnId], _state: hir::InsnId) -> Option<hir::InsnId> { let &[] = args else { return None; }; - // TODO(max): In local iseq types that are not ISEQ_TYPE_METHOD, rewrite to Constant false. - Some(fun.push_insn(block, hir::Insn::IsBlockGiven)) + + let local_iseq = unsafe { rb_get_iseq_body_local_iseq(fun.iseq()) }; + if unsafe { rb_get_iseq_body_type(local_iseq) } == ISEQ_TYPE_METHOD { + let lep = fun.push_insn(block, hir::Insn::GetLEP); + Some(fun.push_insn(block, hir::Insn::IsBlockGiven { lep })) + } else { + Some(fun.push_insn(block, hir::Insn::Const { val: hir::Const::Value(Qfalse) })) + } } fn inline_array_aref(fun: &mut hir::Function, block: hir::BlockId, recv: hir::InsnId, args: &[hir::InsnId], state: hir::InsnId) -> Option<hir::InsnId> { diff --git a/zjit/src/hir.rs b/zjit/src/hir.rs index 6bbf477c94..fc071e3d67 100644 --- a/zjit/src/hir.rs +++ b/zjit/src/hir.rs @@ -802,7 +802,7 @@ pub enum Insn { GetConstantPath { ic: *const iseq_inline_constant_cache, state: InsnId }, /// Kernel#block_given? but without pushing a frame. Similar to [`Insn::Defined`] with /// `DEFINED_YIELD` - IsBlockGiven, + IsBlockGiven { lep: InsnId }, /// Test the bit at index of val, a Fixnum. /// Return Qtrue if the bit is set, else Qfalse. FixnumBitCheck { val: InsnId, index: u8 }, @@ -849,6 +849,10 @@ pub enum Insn { /// Set a class variable `id` to `val` SetClassVar { id: ID, val: InsnId, ic: *const iseq_inline_cvar_cache_entry, state: InsnId }, + /// Get the EP of the ISeq of the containing method, or "local level", skipping over block-level EPs. + /// Equivalent of GET_LEP() macro. + GetLEP, + /// Own a FrameState so that instructions can look up their dominating FrameState when /// generating deopt side-exits and frame reconstruction metadata. Does not directly generate /// any code. @@ -1012,9 +1016,9 @@ pub enum Insn { GuardLess { left: InsnId, right: InsnId, state: InsnId }, /// Side-exit if the method entry at ep[VM_ENV_DATA_INDEX_ME_CREF] doesn't match the expected CME. /// Used to ensure super calls are made from the expected method context. - GuardSuperMethodEntry { cme: *const rb_callable_method_entry_t, state: InsnId }, + GuardSuperMethodEntry { lep: InsnId, cme: *const rb_callable_method_entry_t, state: InsnId }, /// Get the block handler from ep[VM_ENV_DATA_INDEX_SPECVAL] at the local EP (LEP). - GetBlockHandler, + GetBlockHandler { lep: 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. @@ -1131,6 +1135,7 @@ impl Insn { Insn::DefinedIvar { .. } => effects::Any, Insn::LoadPC { .. } => Effect::read_write(abstract_heaps::PC, abstract_heaps::Empty), Insn::LoadEC { .. } => effects::Empty, + Insn::GetLEP { .. } => effects::Empty, Insn::LoadSelf { .. } => Effect::read_write(abstract_heaps::Frame, abstract_heaps::Empty), Insn::LoadField { .. } => Effect::read_write(abstract_heaps::Other, abstract_heaps::Empty), Insn::StoreField { .. } => effects::Any, @@ -1510,11 +1515,11 @@ impl<'a> std::fmt::Display for InsnPrinter<'a> { Insn::GuardNotShared { recv, .. } => write!(f, "GuardNotShared {recv}"), Insn::GuardLess { left, right, .. } => write!(f, "GuardLess {left}, {right}"), Insn::GuardGreaterEq { left, right, .. } => write!(f, "GuardGreaterEq {left}, {right}"), - Insn::GuardSuperMethodEntry { cme, .. } => write!(f, "GuardSuperMethodEntry {:p}", self.ptr_map.map_ptr(cme)), - Insn::GetBlockHandler => write!(f, "GetBlockHandler"), + Insn::GuardSuperMethodEntry { lep, cme, .. } => write!(f, "GuardSuperMethodEntry {lep}, {:p}", self.ptr_map.map_ptr(cme)), + Insn::GetBlockHandler { lep } => write!(f, "GetBlockHandler {lep}"), 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::IsBlockGiven => { write!(f, "IsBlockGiven") }, + Insn::IsBlockGiven { lep } => { write!(f, "IsBlockGiven {lep}") }, Insn::FixnumBitCheck {val, index} => { write!(f, "FixnumBitCheck {val}, {index}") }, Insn::CCall { cfunc, recv, args, name, return_type: _, elidable: _ } => { write!(f, "CCall {recv}, :{}@{:p}", name.contents_lossy(), self.ptr_map.map_ptr(cfunc))?; @@ -1562,6 +1567,7 @@ impl<'a> std::fmt::Display for InsnPrinter<'a> { Insn::GetIvar { self_val, id, .. } => write!(f, "GetIvar {self_val}, :{}", id.contents_lossy()), Insn::LoadPC => write!(f, "LoadPC"), Insn::LoadEC => write!(f, "LoadEC"), + Insn::GetLEP => write!(f, "GetLEP"), Insn::LoadSelf => write!(f, "LoadSelf"), &Insn::LoadField { recv, id, offset, return_type: _ } => write!(f, "LoadField {recv}, :{}@{:p}", id.contents_lossy(), self.ptr_map.map_offset(offset)), &Insn::StoreField { recv, id, offset, val } => write!(f, "StoreField {recv}, :{}@{:p}, {val}", id.contents_lossy(), self.ptr_map.map_offset(offset)), @@ -1969,6 +1975,10 @@ impl Function { } } + pub fn iseq(&self) -> *const rb_iseq_t { + self.iseq + } + // Add an instruction to the function without adding it to any block fn new_insn(&mut self, insn: Insn) -> InsnId { let id = InsnId(self.insns.len()); @@ -2119,7 +2129,6 @@ impl Function { result@(Const {..} | Param | GetConstantPath {..} - | IsBlockGiven | PatchPoint {..} | PutSpecialObject {..} | GetGlobal {..} @@ -2128,6 +2137,7 @@ impl Function { | EntryPoint {..} | LoadPC | LoadEC + | GetLEP | LoadSelf | IncrCounterPtr {..} | IncrCounter(_)) => result.clone(), @@ -2173,8 +2183,9 @@ impl Function { &GuardNotShared { recv, state } => GuardNotShared { recv: find!(recv), state }, &GuardGreaterEq { left, right, state } => GuardGreaterEq { left: find!(left), right: find!(right), state }, &GuardLess { left, right, state } => GuardLess { left: find!(left), right: find!(right), state }, - &GuardSuperMethodEntry { cme, state } => GuardSuperMethodEntry { cme, state }, - &GetBlockHandler => GetBlockHandler, + &GuardSuperMethodEntry { lep, cme, state } => GuardSuperMethodEntry { lep: find!(lep), cme, state }, + &GetBlockHandler { lep } => GetBlockHandler { lep: find!(lep) }, + &IsBlockGiven { lep } => IsBlockGiven { lep: find!(lep) }, &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 }, @@ -2446,7 +2457,7 @@ impl Function { Insn::Defined { pushval, .. } => Type::from_value(*pushval).union(types::NilClass), Insn::DefinedIvar { pushval, .. } => Type::from_value(*pushval).union(types::NilClass), Insn::GetConstantPath { .. } => types::BasicObject, - Insn::IsBlockGiven => types::BoolExact, + Insn::IsBlockGiven { .. } => types::BoolExact, Insn::FixnumBitCheck { .. } => types::BoolExact, Insn::ArrayMax { .. } => types::BasicObject, Insn::ArrayInclude { .. } => types::BoolExact, @@ -2457,6 +2468,7 @@ impl Function { Insn::GetIvar { .. } => types::BasicObject, Insn::LoadPC => types::CPtr, Insn::LoadEC => types::CPtr, + Insn::GetLEP => types::CPtr, Insn::LoadSelf => types::BasicObject, &Insn::LoadField { return_type, .. } => return_type, Insn::GetSpecialSymbol { .. } => types::BasicObject, @@ -2468,7 +2480,7 @@ impl Function { Insn::AnyToString { .. } => types::String, Insn::GetLocal { rest_param: true, .. } => types::ArrayExact, Insn::GetLocal { .. } => types::BasicObject, - Insn::GetBlockHandler => types::RubyValue, + Insn::GetBlockHandler { .. } => types::RubyValue, // 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, @@ -3413,10 +3425,15 @@ impl Function { }); // Guard that we're calling `super` from the expected method context. - self.push_insn(block, Insn::GuardSuperMethodEntry { cme: current_cme, state }); + let lep = self.push_insn(block, Insn::GetLEP); + self.push_insn(block, Insn::GuardSuperMethodEntry { + lep, + cme: current_cme, + state + }); // Guard that no block is being passed (implicit or explicit). - let block_handler = self.push_insn(block, Insn::GetBlockHandler); + let block_handler = self.push_insn(block, Insn::GetBlockHandler { lep }); self.push_insn(block, Insn::GuardBitEquals { val: block_handler, expected: Const::Value(VALUE(VM_BLOCK_HANDLER_NONE as usize)), @@ -4357,14 +4374,17 @@ impl Function { | &Insn::EntryPoint { .. } | &Insn::LoadPC | &Insn::LoadEC + | &Insn::GetLEP | &Insn::LoadSelf | &Insn::GetLocal { .. } - | &Insn::GetBlockHandler | &Insn::PutSpecialObject { .. } - | &Insn::IsBlockGiven | &Insn::IncrCounter(_) | &Insn::IncrCounterPtr { .. } => {} + &Insn::GetBlockHandler { lep } + | &Insn::IsBlockGiven { lep } => { + worklist.push_back(lep); + } &Insn::PatchPoint { state, .. } | &Insn::CheckInterrupts { state } | &Insn::GetConstantPath { ic: _, state } => { @@ -4589,12 +4609,15 @@ impl Function { worklist.push_back(val); } &Insn::GuardBlockParamProxy { state, .. } | - &Insn::GuardSuperMethodEntry { state, .. } | &Insn::GetGlobal { state, .. } | &Insn::GetSpecialSymbol { state, .. } | &Insn::GetSpecialNumber { state, .. } | &Insn::ObjectAllocClass { state, .. } | &Insn::SideExit { state, .. } => worklist.push_back(state), + &Insn::GuardSuperMethodEntry { lep, state, .. } => { + worklist.push_back(lep); + worklist.push_back(state); + } &Insn::UnboxFixnum { val } => worklist.push_back(val), &Insn::FixnumAref { recv, index } => { worklist.push_back(recv); @@ -5099,17 +5122,18 @@ impl Function { | Insn::PutSpecialObject { .. } | Insn::LoadField { .. } | Insn::GetConstantPath { .. } - | Insn::IsBlockGiven + | Insn::IsBlockGiven { .. } | Insn::GetGlobal { .. } | Insn::LoadPC | Insn::LoadEC + | Insn::GetLEP | Insn::LoadSelf | Insn::Snapshot { .. } | Insn::Jump { .. } | Insn::EntryPoint { .. } | Insn::GuardBlockParamProxy { .. } | Insn::GuardSuperMethodEntry { .. } - | Insn::GetBlockHandler + | Insn::GetBlockHandler { .. } | Insn::PatchPoint { .. } | Insn::SideExit { .. } | Insn::IncrCounter { .. } diff --git a/zjit/src/hir/opt_tests.rs b/zjit/src/hir/opt_tests.rs index 9fa622dd2c..b7595f1b27 100644 --- a/zjit/src/hir/opt_tests.rs +++ b/zjit/src/hir/opt_tests.rs @@ -2653,10 +2653,11 @@ mod hir_opt_tests { PatchPoint NoSingletonClass(Object@0x1000) PatchPoint MethodRedefined(Object@0x1000, block_given?@0x1008, cme:0x1010) v19:HeapObject[class_exact*:Object@VALUE(0x1000)] = GuardType v6, HeapObject[class_exact*:Object@VALUE(0x1000)] - v20:BoolExact = IsBlockGiven + v20:CPtr = GetLEP + v21:BoolExact = IsBlockGiven v20 IncrCounter inline_cfunc_optimized_send_count CheckInterrupts - Return v20 + Return v21 "); } @@ -2679,7 +2680,7 @@ mod hir_opt_tests { PatchPoint NoSingletonClass(Object@0x1000) PatchPoint MethodRedefined(Object@0x1000, block_given?@0x1008, cme:0x1010) v19:HeapObject[class_exact*:Object@VALUE(0x1000)] = GuardType v6, HeapObject[class_exact*:Object@VALUE(0x1000)] - v20:BoolExact = IsBlockGiven + v20:FalseClass = Const Value(false) IncrCounter inline_cfunc_optimized_send_count CheckInterrupts Return v20 @@ -10943,12 +10944,13 @@ mod hir_opt_tests { Jump bb2(v4) bb2(v6:BasicObject): PatchPoint MethodRedefined(A@0x1000, foo@0x1008, cme:0x1010) - GuardSuperMethodEntry 0x1038 - v18:RubyValue = GetBlockHandler - v19:FalseClass = GuardBitEquals v18, Value(false) - v20:BasicObject = SendWithoutBlockDirect v6, :foo (0x1040) + v17:CPtr = GetLEP + GuardSuperMethodEntry v17, 0x1038 + v19:RubyValue = GetBlockHandler v17 + v20:FalseClass = GuardBitEquals v19, Value(false) + v21:BasicObject = SendWithoutBlockDirect v6, :foo (0x1040) CheckInterrupts - Return v20 + Return v21 "); } @@ -10986,17 +10988,18 @@ mod hir_opt_tests { Jump bb2(v5, v6) bb2(v8:BasicObject, v9:BasicObject): PatchPoint MethodRedefined(A@0x1000, foo@0x1008, cme:0x1010) - GuardSuperMethodEntry 0x1038 - v27:RubyValue = GetBlockHandler - v28:FalseClass = GuardBitEquals v27, Value(false) - v29:BasicObject = SendWithoutBlockDirect v8, :foo (0x1040), v9 + v26:CPtr = GetLEP + GuardSuperMethodEntry v26, 0x1038 + v28:RubyValue = GetBlockHandler v26 + v29:FalseClass = GuardBitEquals v28, Value(false) + v30:BasicObject = SendWithoutBlockDirect v8, :foo (0x1040), v9 v17:Fixnum[1] = Const Value(1) PatchPoint MethodRedefined(Integer@0x1048, +@0x1050, cme:0x1058) - v32:Fixnum = GuardType v29, Fixnum - v33:Fixnum = FixnumAdd v32, v17 + v33:Fixnum = GuardType v30, Fixnum + v34:Fixnum = FixnumAdd v33, v17 IncrCounter inline_cfunc_optimized_send_count CheckInterrupts - Return v33 + Return v34 "); } |
