11// vm/cache.rs
2-
3- use super :: types:: { Val , DictMap } ;
2+ use super :: types:: { Val , eq_vals_deep} ;
43use crate :: modules:: parser:: OpCode ;
54use alloc:: { vec, vec:: Vec } ;
65use hashbrown:: HashMap ;
@@ -20,41 +19,65 @@ pub enum FastOp {
2019}
2120
2221/*
23- Inline Cache
24- Per-frame type pair recorder that promotes stable ops after threshold hits .
22+ Opcode Cache
23+ Per-frame slot combining inline-cache type recording with adaptive hot-path rewriting. Collocates both tiers per instruction to avoid split cache lines .
2524*/
2625
2726const CACHE_THRESH : u8 = 8 ;
27+ const HOT_THRESH : u32 = 1_000 ;
2828
2929#[ derive( Clone ) ]
30- struct Slot { hits : u8 , ta : u8 , tb : u8 , fast : Option < FastOp > }
31- impl Slot { fn empty ( ) -> Self { Self { hits : 0 , ta : 0 , tb : 0 , fast : None } } }
30+ struct CacheSlot {
31+ ic_hits : u8 , ta : u8 , tb : u8 ,
32+ ic_fast : Option < FastOp > ,
33+ hot_count : u32 ,
34+ hot_fast : Option < FastOp > ,
35+ }
36+ impl CacheSlot { fn empty ( ) -> Self { Self { ic_hits : 0 , ta : 0 , tb : 0 , ic_fast : None , hot_count : 0 , hot_fast : None } } }
3237
33- pub struct InlineCache { slots : Vec < Slot > }
38+ pub struct OpcodeCache { slots : Vec < CacheSlot > }
3439
35- impl InlineCache {
36- pub fn new ( n : usize ) -> Self { Self { slots : vec ! [ Slot :: empty( ) ; n] } }
40+ impl OpcodeCache {
41+ pub fn new ( n : usize ) -> Self { Self { slots : vec ! [ CacheSlot :: empty( ) ; n] } }
3742
38- pub fn record ( & mut self , ip : usize , opcode : & OpCode , ta : u8 , tb : u8 ) -> Option < FastOp > {
39- let s = self . slots . get_mut ( ip) ? ;
43+ pub fn record ( & mut self , ip : usize , opcode : & OpCode , ta : u8 , tb : u8 ) {
44+ let Some ( s ) = self . slots . get_mut ( ip) else { return } ;
4045 if s. ta == ta && s. tb == tb {
41- s. hits = s. hits . saturating_add ( 1 ) ;
42- if s. hits >= CACHE_THRESH && s. fast . is_none ( ) {
43- s. fast = match ( opcode, ta, tb) {
44- ( OpCode :: Add , 1 , 1 ) => Some ( FastOp :: AddInt ) , ( OpCode :: Add , 2 , 2 ) => Some ( FastOp :: AddFloat ) ,
45- ( OpCode :: Add , 5 , 5 ) => Some ( FastOp :: AddStr ) , ( OpCode :: Sub , 1 , 1 ) => Some ( FastOp :: SubInt ) ,
46- ( OpCode :: Sub , 2 , 2 ) => Some ( FastOp :: SubFloat ) , ( OpCode :: Mul , 1 , 1 ) => Some ( FastOp :: MulInt ) ,
47- ( OpCode :: Mul , 2 , 2 ) => Some ( FastOp :: MulFloat ) , ( OpCode :: Lt , 1 , 1 ) => Some ( FastOp :: LtInt ) ,
48- ( OpCode :: Lt , 2 , 2 ) => Some ( FastOp :: LtFloat ) , ( OpCode :: Eq , 1 , 1 ) => Some ( FastOp :: EqInt ) ,
49- ( OpCode :: Eq , 5 , 5 ) => Some ( FastOp :: EqStr ) , _ => None ,
50- } ;
46+ s. ic_hits = s. ic_hits . saturating_add ( 1 ) ;
47+ if s. ic_hits >= CACHE_THRESH && s. ic_fast . is_none ( ) {
48+ s. ic_fast = Self :: specialize ( opcode, ta, tb) ;
49+ }
50+ if s. ic_fast . is_some ( ) {
51+ s. hot_count += 1 ;
52+ if s. hot_count == HOT_THRESH { s. hot_fast = s. ic_fast ; }
5153 }
52- } else { * s = Slot { hits : 1 , ta, tb, fast : None } ; }
53- s. fast
54+ } else {
55+ * s = CacheSlot { ta, tb, ic_hits : 1 , ..CacheSlot :: empty ( ) } ;
56+ }
57+ }
58+
59+ #[ inline] pub fn get_fast ( & self , ip : usize ) -> Option < FastOp > {
60+ self . slots . get ( ip) . and_then ( |s| s. hot_fast . or ( s. ic_fast ) )
61+ }
62+
63+ pub fn invalidate ( & mut self , ip : usize ) {
64+ if let Some ( s) = self . slots . get_mut ( ip) { * s = CacheSlot :: empty ( ) ; }
65+ }
66+
67+ pub fn specialized_count ( & self ) -> usize {
68+ self . slots . iter ( ) . filter ( |s| s. hot_fast . is_some ( ) ) . count ( )
5469 }
5570
56- pub fn get ( & self , ip : usize ) -> Option < FastOp > { self . slots . get ( ip) . and_then ( |s| s. fast ) }
57- pub fn invalidate ( & mut self , ip : usize ) { if let Some ( s) = self . slots . get_mut ( ip) { * s = Slot :: empty ( ) ; } }
71+ fn specialize ( opcode : & OpCode , ta : u8 , tb : u8 ) -> Option < FastOp > {
72+ match ( opcode, ta, tb) {
73+ ( OpCode :: Add , 1 , 1 ) => Some ( FastOp :: AddInt ) , ( OpCode :: Add , 2 , 2 ) => Some ( FastOp :: AddFloat ) ,
74+ ( OpCode :: Add , 5 , 5 ) => Some ( FastOp :: AddStr ) , ( OpCode :: Sub , 1 , 1 ) => Some ( FastOp :: SubInt ) ,
75+ ( OpCode :: Sub , 2 , 2 ) => Some ( FastOp :: SubFloat ) , ( OpCode :: Mul , 1 , 1 ) => Some ( FastOp :: MulInt ) ,
76+ ( OpCode :: Mul , 2 , 2 ) => Some ( FastOp :: MulFloat ) , ( OpCode :: Lt , 1 , 1 ) => Some ( FastOp :: LtInt ) ,
77+ ( OpCode :: Lt , 2 , 2 ) => Some ( FastOp :: LtFloat ) , ( OpCode :: Eq , 1 , 1 ) => Some ( FastOp :: EqInt ) ,
78+ ( OpCode :: Eq , 5 , 5 ) => Some ( FastOp :: EqStr ) , _ => None ,
79+ }
80+ }
5881}
5982
6083/*
@@ -66,47 +89,17 @@ const TPL_THRESH: u32 = 4;
6689
6790struct TplEntry { args : Vec < Val > , result : Val , hits : u32 }
6891
69- fn eq_vals_heap ( a : Val , b : Val , heap : & super :: types:: HeapPool ) -> bool {
70- use super :: types:: HeapObj ;
71- if !a. is_heap ( ) || !b. is_heap ( ) { return a. 0 == b. 0 ; }
72- // Content-based Val comparison for cache lookups, recursing into collections.
73- match ( heap. get ( a) , heap. get ( b) ) {
74- ( HeapObj :: BigInt ( x) , HeapObj :: BigInt ( y) ) => x. cmp ( y) == core:: cmp:: Ordering :: Equal ,
75- ( HeapObj :: Str ( x) , HeapObj :: Str ( y) ) => x == y,
76- ( HeapObj :: Tuple ( x) , HeapObj :: Tuple ( y) ) => eq_seq ( x, y, |a, b| eq_vals_heap ( a, b, heap) ) ,
77- ( HeapObj :: List ( x) , HeapObj :: List ( y) ) => eq_seq ( & x. borrow ( ) , & y. borrow ( ) , |a, b| eq_vals_heap ( a, b, heap) ) ,
78- ( HeapObj :: Set ( x) , HeapObj :: Set ( y) ) => eq_set ( & x. borrow ( ) , & y. borrow ( ) , |a, b| eq_vals_heap ( a, b, heap) ) ,
79- ( HeapObj :: Dict ( x) , HeapObj :: Dict ( y) ) => eq_dict ( & x. borrow ( ) , & y. borrow ( ) , |a, b| eq_vals_heap ( a, b, heap) ) ,
80- _ => false
81- }
82- }
83-
84- pub ( super ) fn eq_seq ( a : & [ Val ] , b : & [ Val ] , eq : impl Fn ( Val , Val ) ->bool ) -> bool {
85- a. len ( ) == b. len ( ) && a. iter ( ) . zip ( b) . all ( |( x, y) | eq ( * x, * y) )
86- }
87- pub ( super ) fn eq_set ( a : & [ Val ] , b : & [ Val ] , eq : impl Fn ( Val , Val ) ->bool ) -> bool {
88- a. len ( ) == b. len ( ) && a. iter ( ) . all ( |x| b. iter ( ) . any ( |y| eq ( * x, * y) ) )
89- }
90- pub ( super ) fn eq_dict ( a : & DictMap , b : & DictMap , eq : impl Fn ( Val , Val ) ->bool ) -> bool {
91- a. len ( ) == b. len ( ) && a. iter ( ) . all ( |( k, v) | b. get ( k) . map_or ( false , |v2| eq ( * v, * v2) ) )
92- }
93-
9492pub struct Templates { map : HashMap < usize , Vec < TplEntry > > }
9593
9694impl Templates {
9795 pub fn new ( ) -> Self { Self { map : HashMap :: new ( ) } }
9896
99- /*
100- Value-Equality Lookup
101- Finds cached result by content comparison instead of pointer identity.
102- */
103-
10497 pub fn lookup ( & self , fi : usize , args : & [ Val ] , heap : & super :: types:: HeapPool ) -> Option < Val > {
10598 self . map . get ( & fi) ?. iter ( )
10699 . find ( |e| {
107100 e. hits >= TPL_THRESH
108101 && e. args . len ( ) == args. len ( )
109- && e. args . iter ( ) . zip ( args) . all ( |( a, b) | eq_vals_heap ( * a, * b, heap) )
102+ && e. args . iter ( ) . zip ( args) . all ( |( a, b) | eq_vals_deep ( * a, * b, heap) )
110103 } )
111104 . map ( |e| e. result )
112105 }
@@ -115,7 +108,7 @@ impl Templates {
115108 let v = self . map . entry ( fi) . or_default ( ) ;
116109 if let Some ( e) = v. iter_mut ( ) . find ( |e| {
117110 e. args . len ( ) == args. len ( )
118- && e. args . iter ( ) . zip ( args) . all ( |( a, b) | eq_vals_heap ( * a, * b, heap) )
111+ && e. args . iter ( ) . zip ( args) . all ( |( a, b) | eq_vals_deep ( * a, * b, heap) )
119112 } ) {
120113 e. hits += 1 ; e. result = result;
121114 } else if v. len ( ) < 256 {
@@ -126,29 +119,4 @@ impl Templates {
126119 pub fn count ( & self ) -> usize {
127120 self . map . values ( ) . flat_map ( |v| v. iter ( ) ) . filter ( |e| e. hits >= TPL_THRESH ) . count ( )
128121 }
129- }
130-
131- /*
132- Adaptive Engine
133- Rewrites hot instructions with specialized overlays after one thousand executions.
134- */
135-
136- const HOT_THRESH : u32 = 1_000 ;
137-
138- pub struct Adaptive { counts : Vec < u32 > , overlay : Vec < Option < FastOp > > }
139-
140- impl Adaptive {
141- pub fn new ( n : usize ) -> Self { Self { counts : vec ! [ 0 ; n] , overlay : vec ! [ None ; n] } }
142- pub fn tick ( & mut self , ip : usize ) -> bool {
143- if let Some ( c) = self . counts . get_mut ( ip) { * c += 1 ; * c == HOT_THRESH } else { false }
144- }
145- pub fn rewrite ( & mut self , ip : usize , f : FastOp ) {
146- if let Some ( s) = self . overlay . get_mut ( ip) { * s = Some ( f) ; }
147- }
148- pub fn get ( & self , ip : usize ) -> Option < FastOp > { self . overlay . get ( ip) . and_then ( |o| * o) }
149- pub fn deopt ( & mut self , ip : usize ) {
150- if let Some ( s) = self . overlay . get_mut ( ip) { * s = None ; }
151- if let Some ( c) = self . counts . get_mut ( ip) { * c = 0 ; }
152- }
153- pub fn count ( & self ) -> usize { self . overlay . iter ( ) . filter ( |o| o. is_some ( ) ) . count ( ) }
154122}
0 commit comments