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