summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorTakashi Kokubun <takashi.kokubun@shopify.com>2025-09-17 12:40:41 -0700
committerGitHub <noreply@github.com>2025-09-17 12:40:41 -0700
commitbb3355760ad0abe3c07b7f9e37be043bad796697 (patch)
tree9853d30751bc4a0c52a0877ece296e5b0cfd698e
parentc995faa8627fbb2509c5a35cde9e01445639fc83 (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.rs7
-rw-r--r--zjit/src/hir.rs47
-rw-r--r--zjit/src/hir_type/mod.rs3
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