ZJIT: Side-exit into the interpreter on unknown opcodes
authorMax Bernstein <[email protected]>
Fri, 23 May 2025 17:27:19 +0000 (23 13:27 -0400)
committerTakashi Kokubun <[email protected]>
Fri, 23 May 2025 20:32:49 +0000 (23 13:32 -0700)
No need to bail out of compilation completely; we can compile all the
code up until that point.

zjit/src/hir.rs

index a247b54..6029350 100644 (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]