ZJIT: Side-exit into the interpreter on unknown opcodes

No need to bail out of compilation completely; we can compile all the
code up until that point.
This commit is contained in:
Max Bernstein 2025-05-23 13:27:19 -04:00 committed by Takashi Kokubun
parent 2a951f62e1
commit 0c29ff8e8f
Notes: git 2025-05-23 20:33:04 +00:00

View File

@ -402,6 +402,9 @@ pub enum Insn {
/// 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.
PatchPoint(Invariant),
/// Side-exit into the interpreter.
SideExit { state: InsnId },
}
impl Insn {
@ -411,7 +414,7 @@ impl Insn {
Insn::ArraySet { .. } | Insn::Snapshot { .. } | Insn::Jump(_)
| Insn::IfTrue { .. } | Insn::IfFalse { .. } | Insn::Return { .. }
| Insn::PatchPoint { .. } | Insn::SetIvar { .. } | Insn::ArrayExtend { .. }
| Insn::ArrayPush { .. } => false,
| Insn::ArrayPush { .. } | Insn::SideExit { .. } => false,
_ => true,
}
}
@ -560,6 +563,7 @@ impl<'a> std::fmt::Display for InsnPrinter<'a> {
Insn::ToNewArray { val, .. } => write!(f, "ToNewArray {val}"),
Insn::ArrayExtend { left, right, .. } => write!(f, "ArrayExtend {left}, {right}"),
Insn::ArrayPush { array, val, .. } => write!(f, "ArrayPush {array}, {val}"),
Insn::SideExit { .. } => write!(f, "SideExit"),
insn => { write!(f, "{insn:?}") }
}
}
@ -915,6 +919,7 @@ impl Function {
&ToNewArray { val, state } => ToNewArray { val: find!(val), state },
&ArrayExtend { left, right, state } => ArrayExtend { left: find!(left), right: find!(right), state },
&ArrayPush { array, val, state } => ArrayPush { array: find!(array), val: find!(val), state },
&SideExit { state } => SideExit { state },
}
}
@ -941,7 +946,7 @@ impl Function {
Insn::ArraySet { .. } | Insn::Snapshot { .. } | Insn::Jump(_)
| Insn::IfTrue { .. } | Insn::IfFalse { .. } | Insn::Return { .. }
| Insn::PatchPoint { .. } | Insn::SetIvar { .. } | Insn::ArrayExtend { .. }
| Insn::ArrayPush { .. } =>
| Insn::ArrayPush { .. } | Insn::SideExit { .. } =>
panic!("Cannot infer type of instruction with no output"),
Insn::Const { val: Const::Value(val) } => Type::from_value(*val),
Insn::Const { val: Const::CBool(val) } => Type::from_cbool(*val),
@ -1518,6 +1523,7 @@ impl Function {
worklist.push_back(val);
worklist.push_back(state);
}
Insn::SideExit { state } => worklist.push_back(state),
}
}
// Now remove all unnecessary instructions
@ -1791,7 +1797,6 @@ pub enum CallType {
#[derive(Debug, PartialEq)]
pub enum ParseError {
StackUnderflow(FrameState),
UnknownOpcode(String),
UnknownNewArraySend(String),
UnhandledCallType(CallType),
}
@ -2243,7 +2248,12 @@ pub fn iseq_to_hir(iseq: *const rb_iseq_t) -> Result<Function, ParseError> {
let val = state.stack_pop()?;
fun.push_insn(block, Insn::SetIvar { self_val, id, val, state: exit_id });
}
_ => return Err(ParseError::UnknownOpcode(insn_name(opcode as usize))),
_ => {
// Unknown opcode; side-exit into the interpreter
let exit_id = fun.push_insn(block, Insn::Snapshot { state: exit_state });
fun.push_insn(block, Insn::SideExit { state: exit_id });
break; // End the block
}
}
if insn_idx_to_block.contains_key(&insn_idx) {
@ -2547,7 +2557,7 @@ mod tests {
let iseq = crate::cruby::with_rubyvm(|| get_method_iseq(method));
unsafe { crate::cruby::rb_zjit_profile_disable(iseq) };
let result = iseq_to_hir(iseq);
assert!(result.is_err(), "Expected an error but succesfully compiled to HIR");
assert!(result.is_err(), "Expected an error but succesfully compiled to HIR: {}", FunctionPrinter::without_snapshot(&result.unwrap()));
assert_eq!(result.unwrap_err(), reason);
}
@ -3102,7 +3112,12 @@ mod tests {
eval("
def test = super()
");
assert_compile_fails("test", ParseError::UnknownOpcode("invokesuper".into()))
assert_method_hir("test", expect![[r#"
fn test:
bb0():
v1:BasicObject = PutSelf
SideExit
"#]]);
}
#[test]
@ -3110,7 +3125,12 @@ mod tests {
eval("
def test = super
");
assert_compile_fails("test", ParseError::UnknownOpcode("invokesuper".into()))
assert_method_hir("test", expect![[r#"
fn test:
bb0():
v1:BasicObject = PutSelf
SideExit
"#]]);
}
#[test]
@ -3118,7 +3138,12 @@ mod tests {
eval("
def test(...) = super(...)
");
assert_compile_fails("test", ParseError::UnknownOpcode("invokesuperforward".into()))
assert_method_hir("test", expect![[r#"
fn test:
bb0(v0:BasicObject):
v2:BasicObject = PutSelf
SideExit
"#]]);
}
// TODO(max): Figure out how to generate a call with OPT_SEND flag
@ -3128,7 +3153,12 @@ mod tests {
eval("
def test(a) = foo **a, b: 1
");
assert_compile_fails("test", ParseError::UnknownOpcode("putspecialobject".into()))
assert_method_hir("test", expect![[r#"
fn test:
bb0(v0:BasicObject):
v2:BasicObject = PutSelf
SideExit
"#]]);
}
#[test]
@ -3144,7 +3174,12 @@ mod tests {
eval("
def test(...) = foo(...)
");
assert_compile_fails("test", ParseError::UnknownOpcode("sendforward".into()))
assert_method_hir("test", expect![[r#"
fn test:
bb0(v0:BasicObject):
v2:BasicObject = PutSelf
SideExit
"#]]);
}
#[test]