Skip to main content

pipa/compiler/
ic.rs

1use crate::object::shape::{Shape, ShapeId};
2use std::ptr::NonNull;
3
4#[derive(Clone, Copy)]
5struct InlineCacheRead {
6    shape_id: u32,
7    offset: u32,
8    proto_ptr: usize,
9}
10
11impl InlineCacheRead {
12    #[inline(always)]
13    const fn empty() -> Self {
14        Self {
15            shape_id: u32::MAX,
16            offset: 0,
17            proto_ptr: 0,
18        }
19    }
20}
21
22#[derive(Clone, Copy)]
23struct InlineCacheWrite {
24    shape_id: u32,
25    offset: u32,
26    new_shape: usize,
27    no_accessor: u8,
28}
29
30impl InlineCacheWrite {
31    #[inline(always)]
32    const fn empty() -> Self {
33        Self {
34            shape_id: u32::MAX,
35            offset: 0,
36            new_shape: 0,
37            no_accessor: 0,
38        }
39    }
40}
41
42unsafe impl Send for InlineCacheWrite {}
43unsafe impl Sync for InlineCacheWrite {}
44
45const IC_POLY: usize = 2;
46
47#[derive(Clone, Debug)]
48pub struct InlineCache {
49    reads: [InlineCacheRead; IC_POLY],
50
51    write: InlineCacheWrite,
52}
53
54impl std::fmt::Debug for InlineCacheRead {
55    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
56        write!(
57            f,
58            "ICRead(shape={}, off={}, proto={})",
59            self.shape_id as usize, self.offset, self.proto_ptr
60        )
61    }
62}
63
64impl std::fmt::Debug for InlineCacheWrite {
65    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
66        write!(
67            f,
68            "ICWrite(shape={}, off={}, new_shape={})",
69            self.shape_id as usize, self.offset, self.new_shape
70        )
71    }
72}
73
74impl InlineCache {
75    pub fn new() -> Self {
76        InlineCache {
77            reads: [InlineCacheRead::empty(); IC_POLY],
78            write: InlineCacheWrite::empty(),
79        }
80    }
81
82    #[inline(always)]
83    pub fn get(&self, shape_id: ShapeId) -> Option<(u32, Option<usize>)> {
84        let sid = shape_id.0 as u32;
85        let r0 = &self.reads[0];
86        if r0.shape_id == sid {
87            return Some((
88                r0.offset,
89                if r0.proto_ptr == 0 {
90                    None
91                } else {
92                    Some(r0.proto_ptr)
93                },
94            ));
95        }
96        let r1 = &self.reads[1];
97        if r1.shape_id == sid {
98            return Some((
99                r1.offset,
100                if r1.proto_ptr == 0 {
101                    None
102                } else {
103                    Some(r1.proto_ptr)
104                },
105            ));
106        }
107        None
108    }
109
110    #[inline(always)]
111    pub fn get_transition(&self, shape_id: ShapeId) -> Option<(u32, *const Shape)> {
112        let w = &self.write;
113        if w.shape_id == shape_id.0 as u32 {
114            Some((w.offset, w.new_shape as *const Shape))
115        } else {
116            None
117        }
118    }
119
120    pub fn insert(&mut self, shape_id: ShapeId, offset: u32, proto_ptr: Option<usize>) {
121        self.reads[1] = self.reads[0];
122        self.reads[0] = InlineCacheRead {
123            shape_id: shape_id.0 as u32,
124            offset,
125            proto_ptr: proto_ptr.unwrap_or(0),
126        };
127    }
128
129    pub fn insert_transition_null(&mut self, shape_id: ShapeId, offset: u32) {
130        self.write = InlineCacheWrite {
131            shape_id: shape_id.0 as u32,
132            offset,
133            new_shape: 0,
134            no_accessor: 0,
135        };
136    }
137
138    pub fn insert_transition(
139        &mut self,
140        pre_shape_id: ShapeId,
141        offset: u32,
142        new_shape: NonNull<Shape>,
143    ) {
144        self.write = InlineCacheWrite {
145            shape_id: pre_shape_id.0 as u32,
146            offset,
147            new_shape: new_shape.as_ptr() as usize,
148            no_accessor: 0,
149        };
150    }
151}
152
153impl Default for InlineCache {
154    fn default() -> Self {
155        Self::new()
156    }
157}
158
159#[derive(Clone, Debug)]
160pub struct InlineCacheTable {
161    caches: Vec<InlineCache>,
162}
163
164impl InlineCacheTable {
165    pub fn new() -> Self {
166        InlineCacheTable { caches: Vec::new() }
167    }
168
169    #[inline(always)]
170    pub fn ensure_capacity(&mut self, len: usize) {
171        if self.caches.len() < len {
172            let new_len = (self.caches.len() * 2).max(len);
173            self.caches.resize(new_len, InlineCache::new());
174        }
175    }
176
177    pub fn preallocate(&mut self, len: usize) {
178        if self.caches.len() < len {
179            self.caches.resize(len, InlineCache::new());
180        }
181    }
182
183    #[inline(always)]
184    pub fn get(&self, pc: usize) -> Option<&InlineCache> {
185        self.caches.get(pc)
186    }
187
188    #[inline(always)]
189    pub fn get_mut(&mut self, pc: usize) -> Option<&mut InlineCache> {
190        self.caches.get_mut(pc)
191    }
192
193    #[inline(always)]
194    pub fn get_own_fast(&self, pc: usize, shape_id: usize) -> Option<u32> {
195        if let Some(ic) = self.caches.get(pc) {
196            let r = ic.reads[0];
197            if r.shape_id == shape_id as u32 && r.proto_ptr == 0 {
198                return Some(r.offset);
199            }
200        }
201        None
202    }
203
204    #[inline(always)]
205    pub fn get_reads0_fast(&self, pc: usize, shape_id: usize) -> Option<(u32, usize)> {
206        if let Some(ic) = self.caches.get(pc) {
207            let r = ic.reads[0];
208            if r.shape_id == shape_id as u32 {
209                return Some((r.offset, r.proto_ptr));
210            }
211        }
212        None
213    }
214
215    #[inline(always)]
216    pub fn get_reads0_values(&self, pc: usize, shape_id: usize) -> (bool, u32, usize) {
217        if let Some(ic) = self.caches.get(pc) {
218            let r = &ic.reads[0];
219            if r.shape_id == shape_id as u32 {
220                return (true, r.offset, r.proto_ptr);
221            }
222        }
223        (false, 0, 0)
224    }
225
226    #[inline(always)]
227    pub fn get_reads123_values(&self, pc: usize, shape_id: usize) -> (bool, u32, usize) {
228        if let Some(ic) = self.caches.get(pc) {
229            let sid = shape_id as u32;
230            let r1 = ic.reads[1];
231            if r1.shape_id == sid {
232                return (true, r1.offset, r1.proto_ptr);
233            }
234        }
235        (false, 0, 0)
236    }
237
238    #[inline(always)]
239    pub fn set_own_fast(&self, pc: usize, shape_id: usize) -> Option<u32> {
240        if let Some(ic) = self.caches.get(pc) {
241            let w = &ic.write;
242            if w.shape_id == shape_id as u32 && w.new_shape == 0 {
243                return Some(w.offset);
244            }
245        }
246        None
247    }
248
249    #[inline(always)]
250    pub fn get_write_no_accessor(&self, pc: usize, shape_id: ShapeId) -> bool {
251        if let Some(ic) = self.caches.get(pc) {
252            let w = &ic.write;
253            if w.shape_id == shape_id.0 as u32 && w.no_accessor != 0 {
254                return true;
255            }
256        }
257        false
258    }
259
260    #[inline(always)]
261    pub fn set_write_no_accessor(&mut self, pc: usize) {
262        if let Some(ic) = self.caches.get_mut(pc) {
263            ic.write.no_accessor = 1;
264        }
265    }
266
267    #[inline(always)]
268    pub fn get_global_cache(&self, pc: usize, global_shape_id: usize, atom_id: u32) -> Option<u32> {
269        if let Some(ic) = self.caches.get(pc) {
270            let r = &ic.reads[0];
271
272            if r.shape_id == global_shape_id as u32 && r.proto_ptr == atom_id as usize + 1 {
273                return Some(r.offset);
274            }
275        }
276        None
277    }
278
279    #[inline(always)]
280    pub fn insert_global_cache(
281        &mut self,
282        pc: usize,
283        global_shape_id: usize,
284        offset: u32,
285        atom_id: u32,
286    ) {
287        self.ensure_capacity(pc + 1);
288        if let Some(ic) = self.caches.get_mut(pc) {
289            ic.reads[0] = InlineCacheRead {
290                shape_id: global_shape_id as u32,
291                offset,
292                proto_ptr: atom_id as usize + 1,
293            };
294        }
295    }
296}
297
298impl Default for InlineCacheTable {
299    fn default() -> Self {
300        Self::new()
301    }
302}
303
304#[cfg(test)]
305mod tests {
306    use super::*;
307    use crate::object::shape::ShapeId;
308    use std::sync::atomic::{AtomicUsize, Ordering};
309
310    fn test_shape_id() -> ShapeId {
311        static NEXT_ID: AtomicUsize = AtomicUsize::new(1);
312        ShapeId(NEXT_ID.fetch_add(1, Ordering::Relaxed))
313    }
314
315    #[test]
316    fn test_inline_cache_basic() {
317        let mut ic = InlineCache::new();
318        let shape_id = test_shape_id();
319
320        assert!(ic.get(shape_id).is_none());
321
322        ic.insert(shape_id, 42, None);
323        assert_eq!(ic.get(shape_id), Some((42, None)));
324    }
325
326    #[test]
327    fn test_inline_cache_multiple_shapes() {
328        let mut ic = InlineCache::new();
329        let shape1 = test_shape_id();
330        let shape2 = test_shape_id();
331
332        ic.insert(shape1, 10, None);
333        assert_eq!(ic.get(shape1), Some((10, None)));
334
335        ic.insert(shape2, 20, None);
336        assert_eq!(ic.get(shape1), Some((10, None)));
337        assert_eq!(ic.get(shape2), Some((20, None)));
338    }
339
340    #[test]
341    fn test_inline_cache_eviction() {
342        let mut ic = InlineCache::new();
343        let mut shapes = Vec::new();
344
345        for i in 0..IC_POLY {
346            let s = test_shape_id();
347            shapes.push(s);
348            ic.insert(s, i as u32, None);
349        }
350
351        for (i, &s) in shapes.iter().enumerate() {
352            assert_eq!(ic.get(s), Some((i as u32, None)));
353        }
354
355        let new_shape = test_shape_id();
356        ic.insert(new_shape, 100, None);
357        assert_eq!(ic.get(new_shape), Some((100, None)));
358
359        assert_eq!(ic.get(shapes[0]), None);
360
361        for i in 1..IC_POLY {
362            assert_eq!(ic.get(shapes[i]), Some((i as u32, None)));
363        }
364    }
365
366    #[test]
367    fn test_inline_cache_prototype() {
368        let mut ic = InlineCache::new();
369        let shape_id = test_shape_id();
370        let proto = 0x1234usize;
371
372        ic.insert(shape_id, 7, Some(proto));
373        assert_eq!(ic.get(shape_id), Some((7, Some(proto))));
374    }
375}