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:
parent
2a951f62e1
commit
0c29ff8e8f
Notes:
git
2025-05-23 20:33:04 +00:00
@ -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]
|
||||
|
Loading…
x
Reference in New Issue
Block a user