ZJIT: Include GC object dump when seeing dead objects

Strictly more info than just the builtin_type from `assert_ne!`.

Old:

    assertion `left != right` failed: ZJIT should only see live objects
      left: 0
     right: 0

New:

    ZJIT saw a dead object. T_type=0, out-of-heap:0x0000000110d4bb40

Also, the new `VALUE::obj_info` is more flexible for print debugging than the
dump_info() it replaces. It now allows you to use it as part of a `format!`
string instead of always printing to stderr for you.
This commit is contained in:
Alan Wu 2025-10-14 21:45:10 -04:00
parent 8d43867802
commit 5bda42e4de
Notes: git 2025-10-15 02:35:22 +00:00
3 changed files with 32 additions and 8 deletions

View File

@ -82,7 +82,7 @@ fn main() {
.allowlist_type("ruby_rstring_flags")
// This function prints info about a value and is useful for debugging
.allowlist_function("rb_obj_info_dump")
.allowlist_function("rb_raw_obj_info")
.allowlist_function("ruby_init")
.allowlist_function("ruby_init_stack")

View File

@ -90,7 +90,7 @@
use std::convert::From;
use std::ffi::{c_void, CString, CStr};
use std::fmt::{Debug, Formatter};
use std::fmt::{Debug, Display, Formatter};
use std::os::raw::{c_char, c_int, c_uint};
use std::panic::{catch_unwind, UnwindSafe};
@ -400,10 +400,27 @@ pub enum ClassRelationship {
NoRelation,
}
/// A print adapator for debug info about a [VALUE]. Includes info
/// the GC knows about the handle. Example: `println!("{}", value.obj_info());`.
pub struct ObjInfoPrinter(VALUE);
impl Display for ObjInfoPrinter {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
use std::mem::MaybeUninit;
const BUFFER_SIZE: usize = 0x100;
let mut buffer: MaybeUninit<[c_char; BUFFER_SIZE]> = MaybeUninit::uninit();
let info = unsafe {
rb_raw_obj_info(buffer.as_mut_ptr().cast(), BUFFER_SIZE, self.0);
CStr::from_ptr(buffer.as_ptr().cast()).to_string_lossy()
};
write!(f, "{info}")
}
}
impl VALUE {
/// Dump info about the value to the console similarly to rp(VALUE)
pub fn dump_info(self) {
unsafe { rb_obj_info_dump(self) }
/// Get a printer for raw debug info from `rb_obj_info()` about the value.
pub fn obj_info(self) -> ObjInfoPrinter {
ObjInfoPrinter(self)
}
/// Return whether the value is truthy or falsy in Ruby -- only nil and false are falsy.
@ -507,8 +524,11 @@ impl VALUE {
pub fn class_of(self) -> VALUE {
if !self.special_const_p() {
let builtin_type = self.builtin_type();
assert_ne!(builtin_type, RUBY_T_NONE, "ZJIT should only see live objects");
assert_ne!(builtin_type, RUBY_T_MOVED, "ZJIT should only see live objects");
assert!(
builtin_type != RUBY_T_NONE && builtin_type != RUBY_T_MOVED,
"ZJIT saw a dead object. T_type={builtin_type}, {}",
self.obj_info()
);
}
unsafe { rb_yarv_class_of(self) }

View File

@ -839,7 +839,6 @@ unsafe extern "C" {
pub fn rb_ivar_set(obj: VALUE, name: ID, val: VALUE) -> VALUE;
pub fn rb_ivar_defined(obj: VALUE, name: ID) -> VALUE;
pub fn rb_attr_get(obj: VALUE, name: ID) -> VALUE;
pub fn rb_obj_info_dump(obj: VALUE);
pub fn rb_class_allocate_instance(klass: VALUE) -> VALUE;
pub fn rb_obj_equal(obj1: VALUE, obj2: VALUE) -> VALUE;
pub fn rb_reg_new_ary(ary: VALUE, options: ::std::os::raw::c_int) -> VALUE;
@ -872,6 +871,11 @@ unsafe extern "C" {
cfp: *const rb_control_frame_t,
) -> *const rb_callable_method_entry_t;
pub fn rb_obj_info(obj: VALUE) -> *const ::std::os::raw::c_char;
pub fn rb_raw_obj_info(
buff: *mut ::std::os::raw::c_char,
buff_size: usize,
obj: VALUE,
) -> *const ::std::os::raw::c_char;
pub fn rb_ec_stack_check(ec: *mut rb_execution_context_struct) -> ::std::os::raw::c_int;
pub fn rb_gc_writebarrier_remember(obj: VALUE);
pub fn rb_shape_id_offset() -> i32;