44use 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 } ;
88use 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
4444impl 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
141155impl 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 {
241334struct FunctionPrinter < ' a > {
242335 fun : & ' a Function ,
243336 display_snapshot : bool ,
337+ ptr_map : PtrPrintMap ,
244338}
245339
246340impl < ' 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}
0 commit comments