From 5bda42e4dec9106fb310c30178c1283ff92ebfa2 Mon Sep 17 00:00:00 2001 From: Alan Wu Date: Tue, 14 Oct 2025 21:45:10 -0400 Subject: [PATCH] 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. --- zjit/bindgen/src/main.rs | 2 +- zjit/src/cruby.rs | 32 ++++++++++++++++++++++++++------ zjit/src/cruby_bindings.inc.rs | 6 +++++- 3 files changed, 32 insertions(+), 8 deletions(-) diff --git a/zjit/bindgen/src/main.rs b/zjit/bindgen/src/main.rs index b40986c0f6..975a2d9157 100644 --- a/zjit/bindgen/src/main.rs +++ b/zjit/bindgen/src/main.rs @@ -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") diff --git a/zjit/src/cruby.rs b/zjit/src/cruby.rs index 1f514787f1..1a2dce03ed 100644 --- a/zjit/src/cruby.rs +++ b/zjit/src/cruby.rs @@ -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) } diff --git a/zjit/src/cruby_bindings.inc.rs b/zjit/src/cruby_bindings.inc.rs index 56b569e064..f18c0035ee 100644 --- a/zjit/src/cruby_bindings.inc.rs +++ b/zjit/src/cruby_bindings.inc.rs @@ -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;