1use std::collections::HashMap;
8
9const MAX_POLYMORPHIC_ENTRIES: usize = 4;
11
12#[derive(Debug, Clone, Copy, PartialEq, Eq)]
14pub enum ICState {
15 Uninitialized,
17 Monomorphic,
19 Polymorphic,
21 Megamorphic,
23}
24
25#[derive(Debug, Clone)]
27pub enum FeedbackSlot {
28 Uninitialized,
29 Call(CallFeedback),
30 Property(PropertyFeedback),
31 Arithmetic(ArithmeticFeedback),
32 Method(MethodFeedback),
33}
34
35#[derive(Debug, Clone)]
37pub struct CallFeedback {
38 pub state: ICState,
39 pub targets: Vec<CallTarget>,
40 pub total_calls: u64,
41}
42
43#[derive(Debug, Clone, PartialEq)]
44pub struct CallTarget {
45 pub function_id: u16,
46 pub count: u64,
47}
48
49#[derive(Debug, Clone)]
51pub struct PropertyFeedback {
52 pub state: ICState,
53 pub entries: Vec<PropertyCacheEntry>,
54}
55
56pub const RECEIVER_TYPED_OBJECT: u8 = 0;
59pub const RECEIVER_HASHMAP: u8 = 1;
60
61#[derive(Debug, Clone, PartialEq)]
62pub struct PropertyCacheEntry {
63 pub schema_id: u64,
64 pub field_idx: u16,
65 pub field_type_tag: u16,
66 pub hit_count: u64,
67 pub receiver_kind: u8,
69}
70
71#[derive(Debug, Clone)]
73pub struct ArithmeticFeedback {
74 pub state: ICState,
75 pub type_pairs: Vec<ArithmeticTypePair>,
76}
77
78#[derive(Debug, Clone, PartialEq)]
79pub struct ArithmeticTypePair {
80 pub left_tag: u8,
81 pub right_tag: u8,
82 pub count: u64,
83}
84
85#[derive(Debug, Clone)]
87pub struct MethodFeedback {
88 pub state: ICState,
89 pub entries: Vec<MethodCacheEntry>,
90}
91
92#[derive(Debug, Clone, PartialEq)]
93pub struct MethodCacheEntry {
94 pub receiver_kind: u8,
95 pub method_name_id: u32,
96 pub handler_ptr: usize,
97 pub hit_count: u64,
98}
99
100#[derive(Debug, Clone)]
102pub struct FeedbackVector {
103 pub function_id: u16,
104 pub slots: HashMap<usize, FeedbackSlot>,
105 pub generation: u32,
106}
107
108fn next_state(current: ICState, entry_count: usize) -> ICState {
110 match current {
111 ICState::Uninitialized => ICState::Monomorphic,
112 ICState::Monomorphic => {
113 if entry_count <= 1 {
114 ICState::Monomorphic
115 } else {
116 ICState::Polymorphic
117 }
118 }
119 ICState::Polymorphic => {
120 if entry_count > MAX_POLYMORPHIC_ENTRIES {
121 ICState::Megamorphic
122 } else {
123 ICState::Polymorphic
124 }
125 }
126 ICState::Megamorphic => ICState::Megamorphic,
127 }
128}
129
130impl FeedbackVector {
131 pub fn new(function_id: u16) -> Self {
133 Self {
134 function_id,
135 slots: HashMap::new(),
136 generation: 0,
137 }
138 }
139
140 pub fn rebase(&mut self, base_offset: usize) {
149 if base_offset == 0 {
150 return;
151 }
152 let old_slots = std::mem::take(&mut self.slots);
153 for (offset, slot) in old_slots {
154 if offset >= base_offset {
155 self.slots.insert(offset - base_offset, slot);
156 }
157 }
158 }
159
160 pub fn merge_at_offset(&mut self, other: &FeedbackVector, offset: usize) {
166 for (ip, slot) in &other.slots {
167 self.slots.insert(ip + offset, slot.clone());
168 }
169 }
170
171 pub fn record_call(&mut self, offset: usize, target_function_id: u16) {
173 let slot = self
174 .slots
175 .entry(offset)
176 .or_insert(FeedbackSlot::Uninitialized);
177
178 match slot {
179 FeedbackSlot::Uninitialized => {
180 *slot = FeedbackSlot::Call(CallFeedback {
181 state: ICState::Monomorphic,
182 targets: vec![CallTarget {
183 function_id: target_function_id,
184 count: 1,
185 }],
186 total_calls: 1,
187 });
188 }
189 FeedbackSlot::Call(fb) => {
190 fb.total_calls += 1;
191 if let Some(target) = fb
192 .targets
193 .iter_mut()
194 .find(|t| t.function_id == target_function_id)
195 {
196 target.count += 1;
197 } else if fb.state != ICState::Megamorphic {
198 fb.targets.push(CallTarget {
199 function_id: target_function_id,
200 count: 1,
201 });
202 fb.state = next_state(fb.state, fb.targets.len());
203 } else {
204 fb.state = ICState::Megamorphic;
206 }
207 }
208 _ => {}
209 }
210 }
211
212 pub fn record_property(
216 &mut self,
217 offset: usize,
218 schema_id: u64,
219 field_idx: u16,
220 field_type_tag: u16,
221 receiver_kind: u8,
222 ) {
223 let slot = self
224 .slots
225 .entry(offset)
226 .or_insert(FeedbackSlot::Uninitialized);
227
228 match slot {
229 FeedbackSlot::Uninitialized => {
230 *slot = FeedbackSlot::Property(PropertyFeedback {
231 state: ICState::Monomorphic,
232 entries: vec![PropertyCacheEntry {
233 schema_id,
234 field_idx,
235 field_type_tag,
236 hit_count: 1,
237 receiver_kind,
238 }],
239 });
240 }
241 FeedbackSlot::Property(fb) => {
242 if let Some(entry) = fb
243 .entries
244 .iter_mut()
245 .find(|e| e.schema_id == schema_id && e.receiver_kind == receiver_kind)
246 {
247 entry.hit_count += 1;
248 } else if fb.state != ICState::Megamorphic {
249 fb.entries.push(PropertyCacheEntry {
250 schema_id,
251 field_idx,
252 field_type_tag,
253 hit_count: 1,
254 receiver_kind,
255 });
256 fb.state = next_state(fb.state, fb.entries.len());
257 }
258 }
259 _ => {}
260 }
261 }
262
263 pub fn record_arithmetic(&mut self, offset: usize, left_tag: u8, right_tag: u8) {
265 let slot = self
266 .slots
267 .entry(offset)
268 .or_insert(FeedbackSlot::Uninitialized);
269
270 match slot {
271 FeedbackSlot::Uninitialized => {
272 *slot = FeedbackSlot::Arithmetic(ArithmeticFeedback {
273 state: ICState::Monomorphic,
274 type_pairs: vec![ArithmeticTypePair {
275 left_tag,
276 right_tag,
277 count: 1,
278 }],
279 });
280 }
281 FeedbackSlot::Arithmetic(fb) => {
282 if let Some(pair) = fb
283 .type_pairs
284 .iter_mut()
285 .find(|p| p.left_tag == left_tag && p.right_tag == right_tag)
286 {
287 pair.count += 1;
288 } else if fb.state != ICState::Megamorphic {
289 fb.type_pairs.push(ArithmeticTypePair {
290 left_tag,
291 right_tag,
292 count: 1,
293 });
294 fb.state = next_state(fb.state, fb.type_pairs.len());
295 }
296 }
297 _ => {}
298 }
299 }
300
301 pub fn record_method(
303 &mut self,
304 offset: usize,
305 receiver_kind: u8,
306 method_name_id: u32,
307 handler_ptr: usize,
308 ) {
309 let slot = self
310 .slots
311 .entry(offset)
312 .or_insert(FeedbackSlot::Uninitialized);
313
314 match slot {
315 FeedbackSlot::Uninitialized => {
316 *slot = FeedbackSlot::Method(MethodFeedback {
317 state: ICState::Monomorphic,
318 entries: vec![MethodCacheEntry {
319 receiver_kind,
320 method_name_id,
321 handler_ptr,
322 hit_count: 1,
323 }],
324 });
325 }
326 FeedbackSlot::Method(fb) => {
327 if let Some(entry) = fb.entries.iter_mut().find(|e| {
328 e.receiver_kind == receiver_kind && e.method_name_id == method_name_id
329 }) {
330 entry.hit_count += 1;
331 } else if fb.state != ICState::Megamorphic {
332 fb.entries.push(MethodCacheEntry {
333 receiver_kind,
334 method_name_id,
335 handler_ptr,
336 hit_count: 1,
337 });
338 fb.state = next_state(fb.state, fb.entries.len());
339 }
340 }
341 _ => {}
342 }
343 }
344
345 pub fn get_slot(&self, offset: usize) -> Option<&FeedbackSlot> {
347 self.slots.get(&offset)
348 }
349
350 pub fn is_monomorphic(&self, offset: usize) -> bool {
352 match self.slots.get(&offset) {
353 Some(FeedbackSlot::Call(fb)) => fb.state == ICState::Monomorphic,
354 Some(FeedbackSlot::Property(fb)) => fb.state == ICState::Monomorphic,
355 Some(FeedbackSlot::Arithmetic(fb)) => fb.state == ICState::Monomorphic,
356 Some(FeedbackSlot::Method(fb)) => fb.state == ICState::Monomorphic,
357 _ => false,
358 }
359 }
360
361 pub fn reset(&mut self) {
363 self.slots.clear();
364 self.generation += 1;
365 }
366
367 pub fn slot_count(&self) -> usize {
369 self.slots
370 .values()
371 .filter(|s| !matches!(s, FeedbackSlot::Uninitialized))
372 .count()
373 }
374
375 pub fn monomorphic_ratio(&self) -> f64 {
381 let total = self.slot_count();
382 if total == 0 {
383 return 0.0;
384 }
385 let mono_count = self
386 .slots
387 .values()
388 .filter(|s| match s {
389 FeedbackSlot::Call(fb) => fb.state == ICState::Monomorphic,
390 FeedbackSlot::Property(fb) => fb.state == ICState::Monomorphic,
391 FeedbackSlot::Arithmetic(fb) => fb.state == ICState::Monomorphic,
392 FeedbackSlot::Method(fb) => fb.state == ICState::Monomorphic,
393 FeedbackSlot::Uninitialized => false,
394 })
395 .count();
396 mono_count as f64 / total as f64
397 }
398}
399
400#[cfg(test)]
401mod tests {
402 use super::*;
403
404 #[test]
405 fn test_new_creates_empty_vector() {
406 let fv = FeedbackVector::new(42);
407 assert_eq!(fv.function_id, 42);
408 assert!(fv.slots.is_empty());
409 assert_eq!(fv.generation, 0);
410 }
411
412 #[test]
413 fn test_record_call_first_observation_becomes_monomorphic() {
414 let mut fv = FeedbackVector::new(0);
415 fv.record_call(10, 1);
416
417 let slot = fv.get_slot(10).unwrap();
418 match slot {
419 FeedbackSlot::Call(fb) => {
420 assert_eq!(fb.state, ICState::Monomorphic);
421 assert_eq!(fb.targets.len(), 1);
422 assert_eq!(fb.targets[0].function_id, 1);
423 assert_eq!(fb.targets[0].count, 1);
424 assert_eq!(fb.total_calls, 1);
425 }
426 _ => panic!("expected Call slot"),
427 }
428 }
429
430 #[test]
431 fn test_record_call_same_target_stays_monomorphic() {
432 let mut fv = FeedbackVector::new(0);
433 fv.record_call(10, 1);
434 fv.record_call(10, 1);
435 fv.record_call(10, 1);
436
437 let slot = fv.get_slot(10).unwrap();
438 match slot {
439 FeedbackSlot::Call(fb) => {
440 assert_eq!(fb.state, ICState::Monomorphic);
441 assert_eq!(fb.targets.len(), 1);
442 assert_eq!(fb.targets[0].count, 3);
443 assert_eq!(fb.total_calls, 3);
444 }
445 _ => panic!("expected Call slot"),
446 }
447 }
448
449 #[test]
450 fn test_record_call_different_target_becomes_polymorphic() {
451 let mut fv = FeedbackVector::new(0);
452 fv.record_call(10, 1);
453 fv.record_call(10, 2);
454
455 let slot = fv.get_slot(10).unwrap();
456 match slot {
457 FeedbackSlot::Call(fb) => {
458 assert_eq!(fb.state, ICState::Polymorphic);
459 assert_eq!(fb.targets.len(), 2);
460 }
461 _ => panic!("expected Call slot"),
462 }
463 }
464
465 #[test]
466 fn test_record_call_five_targets_becomes_megamorphic() {
467 let mut fv = FeedbackVector::new(0);
468 for i in 0..5 {
469 fv.record_call(10, i);
470 }
471
472 let slot = fv.get_slot(10).unwrap();
473 match slot {
474 FeedbackSlot::Call(fb) => {
475 assert_eq!(fb.state, ICState::Megamorphic);
476 assert_eq!(fb.targets.len(), 5);
477 }
478 _ => panic!("expected Call slot"),
479 }
480
481 fv.record_call(10, 99);
483 match fv.get_slot(10).unwrap() {
484 FeedbackSlot::Call(fb) => {
485 assert_eq!(fb.state, ICState::Megamorphic);
486 assert_eq!(fb.targets.len(), 5);
487 assert_eq!(fb.total_calls, 6);
488 }
489 _ => panic!("expected Call slot"),
490 }
491 }
492
493 #[test]
494 fn test_record_property_state_transitions() {
495 let mut fv = FeedbackVector::new(0);
496
497 fv.record_property(20, 100, 0, 1, 0);
499 match fv.get_slot(20).unwrap() {
500 FeedbackSlot::Property(fb) => {
501 assert_eq!(fb.state, ICState::Monomorphic);
502 assert_eq!(fb.entries.len(), 1);
503 }
504 _ => panic!("expected Property slot"),
505 }
506
507 fv.record_property(20, 100, 0, 1, 0);
509 match fv.get_slot(20).unwrap() {
510 FeedbackSlot::Property(fb) => {
511 assert_eq!(fb.state, ICState::Monomorphic);
512 assert_eq!(fb.entries[0].hit_count, 2);
513 }
514 _ => panic!("expected Property slot"),
515 }
516
517 fv.record_property(20, 200, 1, 2, 0);
519 match fv.get_slot(20).unwrap() {
520 FeedbackSlot::Property(fb) => {
521 assert_eq!(fb.state, ICState::Polymorphic);
522 assert_eq!(fb.entries.len(), 2);
523 }
524 _ => panic!("expected Property slot"),
525 }
526
527 fv.record_property(20, 300, 2, 3, 0);
529 fv.record_property(20, 400, 3, 4, 0);
530 fv.record_property(20, 500, 4, 5, 0);
531 match fv.get_slot(20).unwrap() {
532 FeedbackSlot::Property(fb) => {
533 assert_eq!(fb.state, ICState::Megamorphic);
534 assert_eq!(fb.entries.len(), 5);
535 }
536 _ => panic!("expected Property slot"),
537 }
538
539 fv.record_property(20, 600, 5, 6, 0);
541 match fv.get_slot(20).unwrap() {
542 FeedbackSlot::Property(fb) => {
543 assert_eq!(fb.state, ICState::Megamorphic);
544 assert_eq!(fb.entries.len(), 5);
545 }
546 _ => panic!("expected Property slot"),
547 }
548 }
549
550 #[test]
551 fn test_record_arithmetic_state_transitions() {
552 let mut fv = FeedbackVector::new(0);
553
554 fv.record_arithmetic(30, 1, 1);
556 match fv.get_slot(30).unwrap() {
557 FeedbackSlot::Arithmetic(fb) => {
558 assert_eq!(fb.state, ICState::Monomorphic);
559 assert_eq!(fb.type_pairs.len(), 1);
560 }
561 _ => panic!("expected Arithmetic slot"),
562 }
563
564 fv.record_arithmetic(30, 1, 1);
566 match fv.get_slot(30).unwrap() {
567 FeedbackSlot::Arithmetic(fb) => {
568 assert_eq!(fb.state, ICState::Monomorphic);
569 assert_eq!(fb.type_pairs[0].count, 2);
570 }
571 _ => panic!("expected Arithmetic slot"),
572 }
573
574 fv.record_arithmetic(30, 1, 2);
576 match fv.get_slot(30).unwrap() {
577 FeedbackSlot::Arithmetic(fb) => {
578 assert_eq!(fb.state, ICState::Polymorphic);
579 assert_eq!(fb.type_pairs.len(), 2);
580 }
581 _ => panic!("expected Arithmetic slot"),
582 }
583
584 fv.record_arithmetic(30, 2, 2);
586 fv.record_arithmetic(30, 3, 3);
587 fv.record_arithmetic(30, 4, 4);
588 match fv.get_slot(30).unwrap() {
589 FeedbackSlot::Arithmetic(fb) => {
590 assert_eq!(fb.state, ICState::Megamorphic);
591 assert_eq!(fb.type_pairs.len(), 5);
592 }
593 _ => panic!("expected Arithmetic slot"),
594 }
595 }
596
597 #[test]
598 fn test_record_method_state_transitions() {
599 let mut fv = FeedbackVector::new(0);
600
601 fv.record_method(40, 10, 100, 0xDEAD);
603 match fv.get_slot(40).unwrap() {
604 FeedbackSlot::Method(fb) => {
605 assert_eq!(fb.state, ICState::Monomorphic);
606 assert_eq!(fb.entries.len(), 1);
607 assert_eq!(fb.entries[0].handler_ptr, 0xDEAD);
608 }
609 _ => panic!("expected Method slot"),
610 }
611
612 fv.record_method(40, 10, 100, 0xDEAD);
614 match fv.get_slot(40).unwrap() {
615 FeedbackSlot::Method(fb) => {
616 assert_eq!(fb.state, ICState::Monomorphic);
617 assert_eq!(fb.entries[0].hit_count, 2);
618 }
619 _ => panic!("expected Method slot"),
620 }
621
622 fv.record_method(40, 20, 100, 0xBEEF);
624 match fv.get_slot(40).unwrap() {
625 FeedbackSlot::Method(fb) => {
626 assert_eq!(fb.state, ICState::Polymorphic);
627 assert_eq!(fb.entries.len(), 2);
628 }
629 _ => panic!("expected Method slot"),
630 }
631
632 fv.record_method(40, 30, 100, 0xCAFE);
634 fv.record_method(40, 40, 100, 0xF00D);
635 fv.record_method(40, 50, 100, 0xBAAD);
636 match fv.get_slot(40).unwrap() {
637 FeedbackSlot::Method(fb) => {
638 assert_eq!(fb.state, ICState::Megamorphic);
639 assert_eq!(fb.entries.len(), 5);
640 }
641 _ => panic!("expected Method slot"),
642 }
643 }
644
645 #[test]
646 fn test_is_monomorphic() {
647 let mut fv = FeedbackVector::new(0);
648
649 assert!(!fv.is_monomorphic(10));
651
652 fv.record_call(10, 1);
654 assert!(fv.is_monomorphic(10));
655
656 fv.record_call(10, 2);
658 assert!(!fv.is_monomorphic(10));
659
660 fv.record_property(20, 100, 0, 1, 0);
662 assert!(fv.is_monomorphic(20));
663
664 fv.record_arithmetic(30, 1, 1);
666 assert!(fv.is_monomorphic(30));
667
668 fv.record_method(40, 10, 100, 0xDEAD);
670 assert!(fv.is_monomorphic(40));
671 }
672
673 #[test]
674 fn test_reset_clears_and_increments_generation() {
675 let mut fv = FeedbackVector::new(7);
676 fv.record_call(10, 1);
677 fv.record_property(20, 100, 0, 1, 0);
678 assert_eq!(fv.slots.len(), 2);
679 assert_eq!(fv.generation, 0);
680
681 fv.reset();
682 assert!(fv.slots.is_empty());
683 assert_eq!(fv.generation, 1);
684 assert_eq!(fv.function_id, 7);
685
686 fv.reset();
687 assert_eq!(fv.generation, 2);
688 }
689}