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}