Do not assert inside rb_protect() (https://github.com/Shopify/zjit/pull/37)

This commit is contained in:
Takashi Kokubun 2025-03-06 14:32:41 -08:00
parent c5a93df555
commit 7dd15abed3
Notes: git 2025-04-18 13:48:21 +00:00
2 changed files with 214 additions and 226 deletions

View File

@ -853,13 +853,16 @@ pub use manual_defs::*;
#[cfg(test)]
pub mod test_utils {
use std::ptr::null;
use std::{ptr::null, sync::Once};
use crate::{options::init_options, rb_zjit_enabled_p, state::ZJITState};
use super::*;
pub fn with_rubyvm(mut func: impl FnMut()) {
static RUBY_VM_INIT: Once = Once::new();
/// Boot and initialize the Ruby VM for Rust testing
fn boot_rubyvm() {
// Boot the VM
unsafe {
let mut var: VALUE = Qnil;
@ -868,32 +871,47 @@ pub mod test_utils {
crate::cruby::ids::init(); // for ID! usages in tests
}
// Invoke callback through rb_protect() so exceptions don't crash the process.
// "Fun" double pointer dance to get a thin function pointer to pass through C
let mut data: &mut dyn FnMut() = &mut func;
unsafe extern "C" fn callback_wrapper(data: VALUE) -> VALUE {
// SAFETY: shorter lifetime than the data local in the caller frame
let callback: &mut &mut dyn FnMut() -> bool = unsafe { std::mem::transmute(data) };
callback();
Qnil
}
// Set up globals for convenience
ZJITState::init(init_options());
// Enable zjit_* instructions
unsafe { rb_zjit_enabled_p = true; }
}
/// Make sure the Ruby VM is set up and run a given callback with rb_protect()
pub fn with_rubyvm<T>(mut func: impl FnMut() -> T) -> T {
RUBY_VM_INIT.call_once(|| boot_rubyvm());
// Set up a callback wrapper to store a return value
let mut result: Option<T> = None;
let mut data: &mut dyn FnMut() = &mut || {
// Store the result externally
result.replace(func());
};
// Invoke callback through rb_protect() so exceptions don't crash the process.
// "Fun" double pointer dance to get a thin function pointer to pass through C
unsafe extern "C" fn callback_wrapper(data: VALUE) -> VALUE {
// SAFETY: shorter lifetime than the data local in the caller frame
let callback: &mut &mut dyn FnMut() = unsafe { std::mem::transmute(data) };
callback();
Qnil
}
let mut state: c_int = 0;
unsafe { super::rb_protect(Some(callback_wrapper), VALUE((&mut data) as *mut _ as usize), &mut state) };
// TODO(alan): there should be a way to print the exception instead of swallowing it
assert_eq!(0, state, "Exceptional unwind in callback. Ruby exception?");
result.expect("Callback did not set result")
}
/// Compile an ISeq via `RubyVM::InstructionSequence.compile`.
pub fn compile_to_iseq(program: &str) -> *const rb_iseq_t {
let wrapped_iseq = compile_to_wrapped_iseq(program);
unsafe { rb_iseqw_to_iseq(wrapped_iseq) }
with_rubyvm(|| {
let wrapped_iseq = compile_to_wrapped_iseq(program);
unsafe { rb_iseqw_to_iseq(wrapped_iseq) }
})
}
pub fn define_class(name: &str, superclass: VALUE) -> VALUE {
@ -903,8 +921,10 @@ pub mod test_utils {
/// Evaluate a given Ruby program
pub fn eval(program: &str) -> VALUE {
let wrapped_iseq = compile_to_wrapped_iseq(&unindent(program, false));
unsafe { rb_funcallv(wrapped_iseq, ID!(eval), 0, null()) }
with_rubyvm(|| {
let wrapped_iseq = compile_to_wrapped_iseq(&unindent(program, false));
unsafe { rb_funcallv(wrapped_iseq, ID!(eval), 0, null()) }
})
}
/// Get the ISeq of a specified method

View File

@ -1067,7 +1067,7 @@ mod tests {
#[track_caller]
fn assert_method_hir(method: &str, hir: &str) {
let iseq = get_method_iseq(method);
let iseq = crate::cruby::with_rubyvm(|| get_method_iseq(method));
let function = iseq_to_hir(iseq).unwrap();
assert_function_hir(function, hir);
}
@ -1081,274 +1081,242 @@ mod tests {
#[test]
fn boot_vm() {
crate::cruby::with_rubyvm(|| {
let program = "nil.itself";
let iseq = compile_to_iseq(program);
assert!(iseq_to_hir(iseq).is_ok());
});
let program = "nil.itself";
let iseq = compile_to_iseq(program);
assert!(iseq_to_hir(iseq).is_ok());
}
#[test]
fn test_putobject() {
crate::cruby::with_rubyvm(|| {
let program = "123";
let iseq = compile_to_iseq(program);
let function = iseq_to_hir(iseq).unwrap();
assert_function_hir(function, "
bb0():
v1 = Const Value(123)
v3 = Return v1
");
});
let program = "123";
let iseq = compile_to_iseq(program);
let function = iseq_to_hir(iseq).unwrap();
assert_function_hir(function, "
bb0():
v1 = Const Value(123)
v3 = Return v1
");
}
#[test]
fn test_opt_plus() {
crate::cruby::with_rubyvm(|| {
let program = "1+2";
let iseq = compile_to_iseq(program);
let function = iseq_to_hir(iseq).unwrap();
assert_function_hir(function, "
bb0():
v1 = Const Value(1)
v3 = Const Value(2)
v5 = Send v1, :+, v3
v7 = Return v5
");
});
let program = "1+2";
let iseq = compile_to_iseq(program);
let function = iseq_to_hir(iseq).unwrap();
assert_function_hir(function, "
bb0():
v1 = Const Value(1)
v3 = Const Value(2)
v5 = Send v1, :+, v3
v7 = Return v5
");
}
#[test]
fn test_setlocal_getlocal() {
crate::cruby::with_rubyvm(|| {
let program = "a = 1; a";
let iseq = compile_to_iseq(program);
let function = iseq_to_hir(iseq).unwrap();
assert_function_hir(function, "
bb0():
v0 = Const Value(nil)
v2 = Const Value(1)
v6 = Return v2
");
});
let program = "a = 1; a";
let iseq = compile_to_iseq(program);
let function = iseq_to_hir(iseq).unwrap();
assert_function_hir(function, "
bb0():
v0 = Const Value(nil)
v2 = Const Value(1)
v6 = Return v2
");
}
#[test]
fn test_merge_const() {
crate::cruby::with_rubyvm(|| {
let program = "cond = true; if cond; 3; else; 4; end";
let iseq = compile_to_iseq(program);
let function = iseq_to_hir(iseq).unwrap();
assert_function_hir(function, "
bb0():
v0 = Const Value(nil)
v2 = Const Value(true)
v6 = Test v2
v7 = IfFalse v6, bb1(v2)
v9 = Const Value(3)
v11 = Return v9
bb1(v12):
v14 = Const Value(4)
v16 = Return v14
");
});
let program = "cond = true; if cond; 3; else; 4; end";
let iseq = compile_to_iseq(program);
let function = iseq_to_hir(iseq).unwrap();
assert_function_hir(function, "
bb0():
v0 = Const Value(nil)
v2 = Const Value(true)
v6 = Test v2
v7 = IfFalse v6, bb1(v2)
v9 = Const Value(3)
v11 = Return v9
bb1(v12):
v14 = Const Value(4)
v16 = Return v14
");
}
#[test]
fn test_opt_plus_fixnum() {
crate::cruby::with_rubyvm(|| {
eval("
def test(a, b) = a + b
test(1, 2); test(1, 2)
");
assert_method_hir("test", "
bb0(v0, v1):
v5 = PatchPoint BOPRedefined(INTEGER_REDEFINED_OP_FLAG, BOP_PLUS)
v6 = GuardType v0, Fixnum
v7 = GuardType v1, Fixnum
v8 = FixnumAdd v6, v7
v10 = Return v8
");
});
eval("
def test(a, b) = a + b
test(1, 2); test(1, 2)
");
assert_method_hir("test", "
bb0(v0, v1):
v5 = PatchPoint BOPRedefined(INTEGER_REDEFINED_OP_FLAG, BOP_PLUS)
v6 = GuardType v0, Fixnum
v7 = GuardType v1, Fixnum
v8 = FixnumAdd v6, v7
v10 = Return v8
");
}
#[test]
fn test_opt_minus_fixnum() {
crate::cruby::with_rubyvm(|| {
eval("
def test(a, b) = a - b
test(1, 2); test(1, 2)
");
assert_method_hir("test", "
bb0(v0, v1):
v5 = PatchPoint BOPRedefined(INTEGER_REDEFINED_OP_FLAG, BOP_MINUS)
v6 = GuardType v0, Fixnum
v7 = GuardType v1, Fixnum
v8 = FixnumSub v6, v7
v10 = Return v8
");
});
eval("
def test(a, b) = a - b
test(1, 2); test(1, 2)
");
assert_method_hir("test", "
bb0(v0, v1):
v5 = PatchPoint BOPRedefined(INTEGER_REDEFINED_OP_FLAG, BOP_MINUS)
v6 = GuardType v0, Fixnum
v7 = GuardType v1, Fixnum
v8 = FixnumSub v6, v7
v10 = Return v8
");
}
#[test]
fn test_opt_mult_fixnum() {
crate::cruby::with_rubyvm(|| {
eval("
def test(a, b) = a * b
test(1, 2); test(1, 2)
");
assert_method_hir("test", "
bb0(v0, v1):
v5 = PatchPoint BOPRedefined(INTEGER_REDEFINED_OP_FLAG, BOP_MULT)
v6 = GuardType v0, Fixnum
v7 = GuardType v1, Fixnum
v8 = FixnumMult v6, v7
v10 = Return v8
");
});
eval("
def test(a, b) = a * b
test(1, 2); test(1, 2)
");
assert_method_hir("test", "
bb0(v0, v1):
v5 = PatchPoint BOPRedefined(INTEGER_REDEFINED_OP_FLAG, BOP_MULT)
v6 = GuardType v0, Fixnum
v7 = GuardType v1, Fixnum
v8 = FixnumMult v6, v7
v10 = Return v8
");
}
#[test]
fn test_opt_div_fixnum() {
crate::cruby::with_rubyvm(|| {
eval("
def test(a, b) = a / b
test(1, 2); test(1, 2)
");
assert_method_hir("test", "
bb0(v0, v1):
v5 = PatchPoint BOPRedefined(INTEGER_REDEFINED_OP_FLAG, BOP_DIV)
v6 = GuardType v0, Fixnum
v7 = GuardType v1, Fixnum
v8 = FixnumDiv v6, v7
v10 = Return v8
");
});
eval("
def test(a, b) = a / b
test(1, 2); test(1, 2)
");
assert_method_hir("test", "
bb0(v0, v1):
v5 = PatchPoint BOPRedefined(INTEGER_REDEFINED_OP_FLAG, BOP_DIV)
v6 = GuardType v0, Fixnum
v7 = GuardType v1, Fixnum
v8 = FixnumDiv v6, v7
v10 = Return v8
");
}
#[test]
fn test_opt_mod_fixnum() {
crate::cruby::with_rubyvm(|| {
eval("
def test(a, b) = a % b
test(1, 2); test(1, 2)
");
assert_method_hir("test", "
bb0(v0, v1):
v5 = PatchPoint BOPRedefined(INTEGER_REDEFINED_OP_FLAG, BOP_MOD)
v6 = GuardType v0, Fixnum
v7 = GuardType v1, Fixnum
v8 = FixnumMod v6, v7
v10 = Return v8
");
});
eval("
def test(a, b) = a % b
test(1, 2); test(1, 2)
");
assert_method_hir("test", "
bb0(v0, v1):
v5 = PatchPoint BOPRedefined(INTEGER_REDEFINED_OP_FLAG, BOP_MOD)
v6 = GuardType v0, Fixnum
v7 = GuardType v1, Fixnum
v8 = FixnumMod v6, v7
v10 = Return v8
");
}
#[test]
fn test_opt_eq_fixnum() {
crate::cruby::with_rubyvm(|| {
eval("
def test(a, b) = a == b
test(1, 2); test(1, 2)
");
assert_method_hir("test", "
bb0(v0, v1):
v5 = PatchPoint BOPRedefined(INTEGER_REDEFINED_OP_FLAG, BOP_EQ)
v6 = GuardType v0, Fixnum
v7 = GuardType v1, Fixnum
v8 = FixnumEq v6, v7
v10 = Return v8
");
});
eval("
def test(a, b) = a == b
test(1, 2); test(1, 2)
");
assert_method_hir("test", "
bb0(v0, v1):
v5 = PatchPoint BOPRedefined(INTEGER_REDEFINED_OP_FLAG, BOP_EQ)
v6 = GuardType v0, Fixnum
v7 = GuardType v1, Fixnum
v8 = FixnumEq v6, v7
v10 = Return v8
");
}
#[test]
fn test_opt_neq_fixnum() {
crate::cruby::with_rubyvm(|| {
eval("
def test(a, b) = a != b
test(1, 2); test(1, 2)
");
assert_method_hir("test", "
bb0(v0, v1):
v5 = PatchPoint BOPRedefined(INTEGER_REDEFINED_OP_FLAG, BOP_NEQ)
v6 = GuardType v0, Fixnum
v7 = GuardType v1, Fixnum
v8 = FixnumNeq v6, v7
v10 = Return v8
");
});
eval("
def test(a, b) = a != b
test(1, 2); test(1, 2)
");
assert_method_hir("test", "
bb0(v0, v1):
v5 = PatchPoint BOPRedefined(INTEGER_REDEFINED_OP_FLAG, BOP_NEQ)
v6 = GuardType v0, Fixnum
v7 = GuardType v1, Fixnum
v8 = FixnumNeq v6, v7
v10 = Return v8
");
}
#[test]
fn test_opt_lt_fixnum() {
crate::cruby::with_rubyvm(|| {
eval("
def test(a, b) = a < b
test(1, 2); test(1, 2)
");
assert_method_hir("test", "
bb0(v0, v1):
v5 = PatchPoint BOPRedefined(INTEGER_REDEFINED_OP_FLAG, BOP_LT)
v6 = GuardType v0, Fixnum
v7 = GuardType v1, Fixnum
v8 = FixnumLt v6, v7
v10 = Return v8
");
});
eval("
def test(a, b) = a < b
test(1, 2); test(1, 2)
");
assert_method_hir("test", "
bb0(v0, v1):
v5 = PatchPoint BOPRedefined(INTEGER_REDEFINED_OP_FLAG, BOP_LT)
v6 = GuardType v0, Fixnum
v7 = GuardType v1, Fixnum
v8 = FixnumLt v6, v7
v10 = Return v8
");
}
#[test]
fn test_opt_le_fixnum() {
crate::cruby::with_rubyvm(|| {
eval("
def test(a, b) = a <= b
test(1, 2); test(1, 2)
");
assert_method_hir("test", "
bb0(v0, v1):
v5 = PatchPoint BOPRedefined(INTEGER_REDEFINED_OP_FLAG, BOP_LE)
v6 = GuardType v0, Fixnum
v7 = GuardType v1, Fixnum
v8 = FixnumLe v6, v7
v10 = Return v8
");
});
eval("
def test(a, b) = a <= b
test(1, 2); test(1, 2)
");
assert_method_hir("test", "
bb0(v0, v1):
v5 = PatchPoint BOPRedefined(INTEGER_REDEFINED_OP_FLAG, BOP_LE)
v6 = GuardType v0, Fixnum
v7 = GuardType v1, Fixnum
v8 = FixnumLe v6, v7
v10 = Return v8
");
}
#[test]
fn test_opt_gt_fixnum() {
crate::cruby::with_rubyvm(|| {
eval("
def test(a, b) = a > b
test(1, 2); test(1, 2)
");
assert_method_hir("test", "
bb0(v0, v1):
v5 = PatchPoint BOPRedefined(INTEGER_REDEFINED_OP_FLAG, BOP_GT)
v6 = GuardType v0, Fixnum
v7 = GuardType v1, Fixnum
v8 = FixnumGt v6, v7
v10 = Return v8
");
});
eval("
def test(a, b) = a > b
test(1, 2); test(1, 2)
");
assert_method_hir("test", "
bb0(v0, v1):
v5 = PatchPoint BOPRedefined(INTEGER_REDEFINED_OP_FLAG, BOP_GT)
v6 = GuardType v0, Fixnum
v7 = GuardType v1, Fixnum
v8 = FixnumGt v6, v7
v10 = Return v8
");
}
#[test]
fn test_opt_ge_fixnum() {
crate::cruby::with_rubyvm(|| {
eval("
def test(a, b) = a >= b
test(1, 2); test(1, 2)
");
assert_method_hir("test", "
bb0(v0, v1):
v5 = PatchPoint BOPRedefined(INTEGER_REDEFINED_OP_FLAG, BOP_GE)
v6 = GuardType v0, Fixnum
v7 = GuardType v1, Fixnum
v8 = FixnumGe v6, v7
v10 = Return v8
");
});
eval("
def test(a, b) = a >= b
test(1, 2); test(1, 2)
");
assert_method_hir("test", "
bb0(v0, v1):
v5 = PatchPoint BOPRedefined(INTEGER_REDEFINED_OP_FLAG, BOP_GE)
v6 = GuardType v0, Fixnum
v7 = GuardType v1, Fixnum
v8 = FixnumGe v6, v7
v10 = Return v8
");
}
}