summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorKevin Menard <kevin@nirvdrum.com>2026-01-21 14:07:20 -0500
committerGitHub <noreply@github.com>2026-01-21 19:07:20 +0000
commitcfa97af7e1426c76b769495ef4b1689be3b0a685 (patch)
tree4f126e62323ea778984edeba3c490006a11248cd
parent6d0b47de86eef3a396ff2bf6eea731d2fb778ded (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.rs26
-rw-r--r--zjit/src/cruby_methods.rs10
-rw-r--r--zjit/src/hir.rs60
-rw-r--r--zjit/src/hir/opt_tests.rs33
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
");
}