Skip to content

Commit efb2ba3

Browse files
XrXrk0kubun
authored andcommitted
HIR printing: Assign stable address to pointers when first seen
This gives output stability while still showing address equality and inequality. This mapping mechanism is only turned on for FunctionPrinter, so implementation of Display and Debug can share the same code but not go through the mapping process. Outside of `cfg!(test)` we don't want and need to stabilize the addresses.
1 parent a5330af commit efb2ba3

File tree

2 files changed

+172
-35
lines changed

2 files changed

+172
-35
lines changed

zjit/src/hir.rs

Lines changed: 146 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
use crate::{
55
cruby::*, options::get_option, hir_type::types::Fixnum, options::DumpHIR, profile::get_or_create_iseq_payload
66
};
7-
use std::{collections::{HashMap, HashSet}, slice::Iter};
7+
use std::{cell::RefCell, collections::{HashMap, HashSet}, ffi::c_void, mem::{align_of, size_of}, ptr, slice::Iter};
88
use crate::hir_type::{Type, types};
99

1010
#[derive(Copy, Clone, Ord, PartialOrd, Eq, PartialEq, Hash, Debug)]
@@ -43,16 +43,30 @@ fn write_vec<T: std::fmt::Display>(f: &mut std::fmt::Formatter, objs: &Vec<T>) -
4343

4444
impl std::fmt::Display for VALUE {
4545
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
46-
match self {
46+
self.print(&PtrPrintMap::identity()).fmt(f)
47+
}
48+
}
49+
50+
impl VALUE {
51+
pub fn print(self, ptr_map: &PtrPrintMap) -> VALUEPrinter {
52+
VALUEPrinter { inner: self, ptr_map }
53+
}
54+
}
55+
56+
/// Print adaptor for [`VALUE`]. See [`PtrPrintMap`].
57+
pub struct VALUEPrinter<'a> {
58+
inner: VALUE,
59+
ptr_map: &'a PtrPrintMap,
60+
}
61+
62+
impl<'a> std::fmt::Display for VALUEPrinter<'a> {
63+
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
64+
match self.inner {
4765
val if val.fixnum_p() => write!(f, "{}", val.as_fixnum()),
48-
&Qnil => write!(f, "nil"),
49-
&Qtrue => write!(f, "true"),
50-
&Qfalse => write!(f, "false"),
51-
// For tests, we want to check HIR snippets textually. Addresses change between runs,
52-
// making tests fail. Instead, pick an arbitrary hex value to use as a "pointer" so we
53-
// can check the rest of the HIR.
54-
_ if cfg!(test) => write!(f, "VALUE(0xffffffffffffffff)"),
55-
val => write!(f, "VALUE({:#X?})", val.as_ptr::<u8>()),
66+
Qnil => write!(f, "nil"),
67+
Qtrue => write!(f, "true"),
68+
Qfalse => write!(f, "false"),
69+
val => write!(f, "VALUE({:p})", self.ptr_map.map_ptr(val.as_ptr::<VALUE>())),
5670
}
5771
}
5872
}
@@ -140,9 +154,88 @@ pub enum Const {
140154

141155
impl std::fmt::Display for Const {
142156
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
143-
match self {
144-
Const::Value(val) => write!(f, "Value({val})"),
145-
_ => write!(f, "{self:?}"),
157+
self.print(&PtrPrintMap::identity()).fmt(f)
158+
}
159+
}
160+
161+
impl Const {
162+
fn print<'a>(&'a self, ptr_map: &'a PtrPrintMap) -> ConstPrinter<'a> {
163+
ConstPrinter { inner: self, ptr_map }
164+
}
165+
}
166+
167+
/// Print adaptor for [`Const`]. See [`PtrPrintMap`].
168+
struct ConstPrinter<'a> {
169+
inner: &'a Const,
170+
ptr_map: &'a PtrPrintMap,
171+
}
172+
173+
impl<'a> std::fmt::Display for ConstPrinter<'a> {
174+
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
175+
match self.inner {
176+
Const::Value(val) => write!(f, "Value({})", val.print(self.ptr_map)),
177+
Const::CPtr(val) => write!(f, "CPtr({:p})", self.ptr_map.map_ptr(val)),
178+
_ => write!(f, "{:?}", self.inner),
179+
}
180+
}
181+
}
182+
183+
/// For output stability in tests, we assign each pointer with a stable
184+
/// address the first time we see it. This mapping is off by default;
185+
/// set [`PtrPrintMap::map_ptrs`] to switch it on.
186+
///
187+
/// Because this is extra state external to any pointer being printed, a
188+
/// printing adapter struct that wraps the pointer along with this map is
189+
/// required to make use of this effectly. The [`std::fmt::Display`]
190+
/// implementation on the adapter struct can then be reused to implement
191+
/// `Display` on the inner type with a default [`PtrPrintMap`], which
192+
/// does not perform any mapping.
193+
pub struct PtrPrintMap {
194+
inner: RefCell<PtrPrintMapInner>,
195+
map_ptrs: bool,
196+
}
197+
198+
struct PtrPrintMapInner {
199+
map: HashMap<*const c_void, *const c_void>,
200+
next_ptr: *const c_void,
201+
}
202+
203+
impl PtrPrintMap {
204+
/// Return a mapper that maps the pointer to itself.
205+
pub fn identity() -> Self {
206+
Self {
207+
map_ptrs: false,
208+
inner: RefCell::new(PtrPrintMapInner {
209+
map: HashMap::default(), next_ptr:
210+
ptr::without_provenance(0x1000) // Simulate 4 KiB zero page
211+
})
212+
}
213+
}
214+
}
215+
216+
impl PtrPrintMap {
217+
/// Map a pointer for printing
218+
fn map_ptr<T>(&self, ptr: *const T) -> *const T {
219+
// When testing, address stability is not a concern so print real address to enable code
220+
// reuse
221+
if !self.map_ptrs {
222+
return ptr;
223+
}
224+
225+
use std::collections::hash_map::Entry::*;
226+
let ptr = ptr.cast();
227+
let inner = &mut *self.inner.borrow_mut();
228+
match inner.map.entry(ptr) {
229+
Occupied(entry) => entry.get().cast(),
230+
Vacant(entry) => {
231+
// Pick a fake address that is suitably aligns for T and remember it in the map
232+
let mapped = inner.next_ptr.wrapping_add(inner.next_ptr.align_offset(align_of::<T>()));
233+
entry.insert(mapped);
234+
235+
// Bump for the next pointer
236+
inner.next_ptr = mapped.wrapping_add(size_of::<T>());
237+
mapped.cast()
238+
}
146239
}
147240
}
148241
}
@@ -241,15 +334,20 @@ impl Block {
241334
struct FunctionPrinter<'a> {
242335
fun: &'a Function,
243336
display_snapshot: bool,
337+
ptr_map: PtrPrintMap,
244338
}
245339

246340
impl<'a> FunctionPrinter<'a> {
247-
fn without_snapshot(fun: &'a Function) -> FunctionPrinter<'a> {
248-
FunctionPrinter { fun, display_snapshot: false }
341+
fn without_snapshot(fun: &'a Function) -> Self {
342+
let mut ptr_map = PtrPrintMap::identity();
343+
ptr_map.map_ptrs = true;
344+
Self { fun, display_snapshot: false, ptr_map }
249345
}
250346

251347
fn with_snapshot(fun: &'a Function) -> FunctionPrinter<'a> {
252-
FunctionPrinter { fun, display_snapshot: true }
348+
let mut printer = Self::without_snapshot(fun);
349+
printer.display_snapshot = true;
350+
printer
253351
}
254352
}
255353

@@ -652,7 +750,7 @@ impl<'a> std::fmt::Display for FunctionPrinter<'a> {
652750
write!(f, "{sep}{param}")?;
653751
let insn_type = fun.type_of(*param);
654752
if !insn_type.is_subtype(types::Empty) {
655-
write!(f, ":{insn_type}")?;
753+
write!(f, ":{}", insn_type.print(&self.ptr_map))?;
656754
}
657755
sep = ", ";
658756
}
@@ -669,11 +767,11 @@ impl<'a> std::fmt::Display for FunctionPrinter<'a> {
669767
if insn_type.is_subtype(types::Empty) {
670768
write!(f, "{insn_id} = ")?;
671769
} else {
672-
write!(f, "{insn_id}:{insn_type} = ")?;
770+
write!(f, "{insn_id}:{} = ", insn_type.print(&self.ptr_map))?;
673771
}
674772
}
675773
match insn {
676-
Insn::Const { val } => { write!(f, "Const {val}")?; }
774+
Insn::Const { val } => { write!(f, "Const {}", val.print(&self.ptr_map))?; }
677775
Insn::Param { idx } => { write!(f, "Param {idx}")?; }
678776
Insn::NewArray { count } => { write!(f, "NewArray {count}")?; }
679777
Insn::ArraySet { array, idx, val } => { write!(f, "ArraySet {array}, {idx}, {val}")?; }
@@ -693,8 +791,7 @@ impl<'a> std::fmt::Display for FunctionPrinter<'a> {
693791
// For tests, we want to check HIR snippets textually. Addresses change
694792
// between runs, making tests fail. Instead, pick an arbitrary hex value to
695793
// use as a "pointer" so we can check the rest of the HIR.
696-
let blockiseq = if cfg!(test) { "0xffffffffffffffff".into() } else { format!("{blockiseq:?}") };
697-
write!(f, "Send {self_val}, {blockiseq}, :{}", call_info.method_name)?;
794+
write!(f, "Send {self_val}, {:p}, :{}", self.ptr_map.map_ptr(blockiseq), call_info.method_name)?;
698795
for arg in args {
699796
write!(f, ", {arg}")?;
700797
}
@@ -1509,7 +1606,7 @@ mod tests {
15091606
eval("def test = [1, 2, 3]");
15101607
assert_method_hir("test", "
15111608
bb0():
1512-
v1:ArrayExact[VALUE(0xffffffffffffffff)] = Const Value(VALUE(0xffffffffffffffff))
1609+
v1:ArrayExact[VALUE(0x1000)] = Const Value(VALUE(0x1000))
15131610
v2:ArrayExact = ArrayDup v1
15141611
Return v2
15151612
");
@@ -1522,7 +1619,7 @@ mod tests {
15221619
eval("def test = \"hello\"");
15231620
assert_method_hir("test", "
15241621
bb0():
1525-
v1:StringExact[VALUE(0xffffffffffffffff)] = Const Value(VALUE(0xffffffffffffffff))
1622+
v1:StringExact[VALUE(0x1000)] = Const Value(VALUE(0x1000))
15261623
v2:StringExact = StringCopy { val: InsnId(1) }
15271624
Return v2
15281625
");
@@ -1533,7 +1630,7 @@ mod tests {
15331630
eval("def test = 999999999999999999999999999999999999");
15341631
assert_method_hir("test", "
15351632
bb0():
1536-
v1:Bignum[VALUE(0xffffffffffffffff)] = Const Value(VALUE(0xffffffffffffffff))
1633+
v1:Bignum[VALUE(0x1000)] = Const Value(VALUE(0x1000))
15371634
Return v1
15381635
");
15391636
}
@@ -1543,7 +1640,7 @@ mod tests {
15431640
eval("def test = 1.5");
15441641
assert_method_hir("test", "
15451642
bb0():
1546-
v1:Flonum[VALUE(0xffffffffffffffff)] = Const Value(VALUE(0xffffffffffffffff))
1643+
v1:Flonum[VALUE(0x1000)] = Const Value(VALUE(0x1000))
15471644
Return v1
15481645
");
15491646
}
@@ -1553,7 +1650,7 @@ mod tests {
15531650
eval("def test = 1.7976931348623157e+308");
15541651
assert_method_hir("test", "
15551652
bb0():
1556-
v1:HeapFloat[VALUE(0xffffffffffffffff)] = Const Value(VALUE(0xffffffffffffffff))
1653+
v1:HeapFloat[VALUE(0x1000)] = Const Value(VALUE(0x1000))
15571654
Return v1
15581655
");
15591656
}
@@ -1563,7 +1660,7 @@ mod tests {
15631660
eval("def test = :foo");
15641661
assert_method_hir("test", "
15651662
bb0():
1566-
v1:StaticSymbol[VALUE(0xffffffffffffffff)] = Const Value(VALUE(0xffffffffffffffff))
1663+
v1:StaticSymbol[VALUE(0x1000)] = Const Value(VALUE(0x1000))
15671664
Return v1
15681665
");
15691666
}
@@ -1927,8 +2024,29 @@ mod tests {
19272024
");
19282025
assert_method_hir("test", "
19292026
bb0(v0:BasicObject):
1930-
v3:BasicObject = Send v0, 0xffffffffffffffff, :each
2027+
v3:BasicObject = Send v0, 0x1000, :each
19312028
Return v3
19322029
");
19332030
}
2031+
2032+
#[test]
2033+
fn different_objects_get_addresses() {
2034+
eval("def test = unknown_method([0], [1], '2', '2')");
2035+
2036+
// The 2 string literals have the same address because they're deduped.
2037+
assert_method_hir("test", "
2038+
bb0():
2039+
v1:BasicObject = PutSelf
2040+
v3:ArrayExact[VALUE(0x1000)] = Const Value(VALUE(0x1000))
2041+
v4:ArrayExact = ArrayDup v3
2042+
v6:ArrayExact[VALUE(0x1008)] = Const Value(VALUE(0x1008))
2043+
v7:ArrayExact = ArrayDup v6
2044+
v9:StringExact[VALUE(0x1010)] = Const Value(VALUE(0x1010))
2045+
v10:StringExact = StringCopy { val: InsnId(9) }
2046+
v12:StringExact[VALUE(0x1010)] = Const Value(VALUE(0x1010))
2047+
v13:StringExact = StringCopy { val: InsnId(12) }
2048+
v15:BasicObject = SendWithoutBlock v1, :unknown_method, v4, v7, v10, v13
2049+
Return v15
2050+
");
2051+
}
19342052
}

zjit/src/hir_type/mod.rs

Lines changed: 26 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
use crate::cruby::{Qfalse, Qnil, Qtrue, VALUE, RUBY_T_ARRAY, RUBY_T_STRING, RUBY_T_HASH};
33
use crate::cruby::{rb_cInteger, rb_cFloat, rb_cArray, rb_cHash, rb_cString, rb_cSymbol, rb_cObject, rb_cTrueClass, rb_cFalseClass, rb_cNilClass};
44
use crate::cruby::ClassRelationship;
5+
use crate::hir::PtrPrintMap;
56

67
#[derive(Copy, Clone, Debug, PartialEq)]
78
/// Specialization of the type. If we know additional information about the object, we put it here.
@@ -74,10 +75,11 @@ fn get_class_name(class: Option<VALUE>) -> String {
7475
}).unwrap_or_else(|| "Unknown".to_string())
7576
}
7677

77-
fn write_spec(f: &mut std::fmt::Formatter, ty: Type) -> std::fmt::Result {
78+
fn write_spec(f: &mut std::fmt::Formatter, printer: &TypePrinter) -> std::fmt::Result {
79+
let ty = printer.inner;
7880
match ty.spec {
7981
Specialization::Any | Specialization::Empty => { Ok(()) },
80-
Specialization::Object(val) => write!(f, "[{val}]"),
82+
Specialization::Object(val) => write!(f, "[{}]", val.print(printer.ptr_map)),
8183
Specialization::Type(val) => write!(f, "[class:{}]", get_class_name(Some(val))),
8284
Specialization::TypeExact(val) => write!(f, "[class_exact:{}]", get_class_name(Some(val))),
8385
Specialization::Int(val) if ty.is_subtype(types::CBool) => write!(f, "[{}]", val != 0),
@@ -94,16 +96,23 @@ fn write_spec(f: &mut std::fmt::Formatter, ty: Type) -> std::fmt::Result {
9496
}
9597
}
9698

97-
impl std::fmt::Display for Type {
99+
/// Print adaptor for [`Type`]. See [`PtrPrintMap`].
100+
pub struct TypePrinter<'a> {
101+
inner: Type,
102+
ptr_map: &'a PtrPrintMap,
103+
}
104+
105+
impl<'a> std::fmt::Display for TypePrinter<'a> {
98106
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
107+
let ty = self.inner;
99108
for (name, pattern) in bits::AllBitPatterns {
100-
if self.bits == pattern {
109+
if ty.bits == pattern {
101110
write!(f, "{name}")?;
102-
return write_spec(f, *self);
111+
return write_spec(f, self);
103112
}
104113
}
105114
assert!(bits::AllBitPatterns.is_sorted_by(|(_, left), (_, right)| left > right));
106-
let mut bits = self.bits;
115+
let mut bits = ty.bits;
107116
let mut sep = "";
108117
for (name, pattern) in bits::AllBitPatterns {
109118
if bits == 0 { break; }
@@ -114,7 +123,13 @@ impl std::fmt::Display for Type {
114123
}
115124
}
116125
assert_eq!(bits, 0, "Should have eliminated all bits by iterating over all patterns");
117-
write_spec(f, *self)
126+
write_spec(f, self)
127+
}
128+
}
129+
130+
impl std::fmt::Display for Type {
131+
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
132+
self.print(&PtrPrintMap::identity()).fmt(f)
118133
}
119134
}
120135

@@ -362,6 +377,10 @@ impl Type {
362377
fn is_immediate(&self) -> bool {
363378
self.is_subtype(types::Immediate)
364379
}
380+
381+
pub fn print(self, ptr_map: &PtrPrintMap) -> TypePrinter {
382+
TypePrinter { inner: self, ptr_map }
383+
}
365384
}
366385

367386
#[cfg(test)]

0 commit comments

Comments
 (0)