22//
33// Key Design Principles:
44// 1. LuaValueV2 stores type tag + object ID (no pointers - Vec may relocate)
5- // 2. All GC objects accessed via ID lookup in Arena
6- // 3. ChunkedArena uses fixed-size chunks - never reallocates existing data!
5+ // 2. All GC objects accessed via ID lookup in Arena/SlotMap
6+ // 3. slotmap provides O(1) access with Vec-based storage (no chunking overhead)
77// 4. No Rc/RefCell overhead - direct access via &mut self
88// 5. GC headers embedded in objects for mark-sweep
99//
1010// Memory Layout:
11- // - ChunkedArena stores objects in fixed-size chunks (Box<[Option<T>; CHUNK_SIZE]>)
12- // - Each chunk is allocated once and never moved
13- // - New chunks are added as needed, existing chunks stay in place
14- // - This eliminates Vec resize overhead and improves cache locality
11+ // - SlotMap stores objects in flat Vec with generational keys
12+ // - O(1) insert, access, and remove operations
13+ // - Arena still used for some types for compatibility
1514
1615use crate :: lua_value:: { Chunk , LuaThread , LuaUserdata } ;
1716use crate :: { LuaString , LuaTable , LuaValue } ;
17+ use slotmap:: { new_key_type, SlotMap } ;
1818use std:: hash:: Hash ;
1919use std:: rc:: Rc ;
2020
21+ // Define custom key types for slotmap
22+ new_key_type ! {
23+ /// Key for upvalues in the slotmap
24+ pub struct UpvalueKey ;
25+ }
26+
2127// ============ GC Header ============
2228
2329/// GC object header - embedded in every GC-managed object
@@ -47,9 +53,17 @@ pub struct TableId(pub u32);
4753#[ repr( transparent) ]
4854pub struct FunctionId ( pub u32 ) ;
4955
50- #[ derive( Clone , Copy , PartialEq , Eq , Hash , Debug , Default ) ]
51- #[ repr( transparent) ]
52- pub struct UpvalueId ( pub u32 ) ;
56+ /// UpvalueId now wraps slotmap's UpvalueKey for O(1) access
57+ /// The slotmap key provides generational safety and direct Vec indexing
58+ #[ derive( Clone , Copy , PartialEq , Eq , Hash , Debug ) ]
59+ pub struct UpvalueId ( pub UpvalueKey ) ;
60+
61+ impl Default for UpvalueId {
62+ fn default ( ) -> Self {
63+ // Create a null/invalid key - this should never be used for actual lookups
64+ UpvalueId ( UpvalueKey :: default ( ) )
65+ }
66+ }
5367
5468#[ derive( Clone , Copy , PartialEq , Eq , Hash , Debug , Default ) ]
5569#[ repr( transparent) ]
@@ -123,6 +137,25 @@ impl GcUpvalue {
123137 _ => None ,
124138 }
125139 }
140+
141+ /// Set closed upvalue value directly without checking state
142+ /// SAFETY: Must only be called when upvalue is in Closed state
143+ #[ inline( always) ]
144+ pub unsafe fn set_closed_value_unchecked ( & mut self , value : LuaValue ) {
145+ if let UpvalueState :: Closed ( ref mut v) = self . state {
146+ * v = value;
147+ }
148+ }
149+
150+ /// Get closed value reference directly without Option
151+ /// SAFETY: Must only be called when upvalue is in Closed state
152+ #[ inline( always) ]
153+ pub unsafe fn get_closed_value_ref_unchecked ( & self ) -> & LuaValue {
154+ match & self . state {
155+ UpvalueState :: Closed ( v) => v,
156+ _ => unsafe { std:: hint:: unreachable_unchecked ( ) } ,
157+ }
158+ }
126159}
127160
128161/// String with embedded GC header
@@ -354,11 +387,12 @@ impl<T> Default for Arena<T> {
354387
355388/// High-performance object pool for the Lua VM
356389/// All objects are stored in typed arenas and accessed by ID
357- pub struct ObjectPoolV2 {
390+ pub struct ObjectPool {
358391 pub strings : Arena < GcString > ,
359392 pub tables : Arena < GcTable > ,
360393 pub functions : Arena < GcFunction > ,
361- pub upvalues : Arena < GcUpvalue > ,
394+ /// Upvalues use SlotMap for O(1) access (no chunking overhead)
395+ pub upvalues : SlotMap < UpvalueKey , GcUpvalue > ,
362396 pub userdata : Arena < LuaUserdata > ,
363397 pub threads : Arena < GcThread > ,
364398
@@ -586,13 +620,13 @@ impl StringInternTable {
586620 }
587621}
588622
589- impl ObjectPoolV2 {
623+ impl ObjectPool {
590624 pub fn new ( ) -> Self {
591625 let mut pool = Self {
592626 strings : Arena :: with_capacity ( 256 ) ,
593627 tables : Arena :: with_capacity ( 64 ) ,
594628 functions : Arena :: with_capacity ( 32 ) ,
595- upvalues : Arena :: with_capacity ( 32 ) ,
629+ upvalues : SlotMap :: with_capacity_and_key ( 32 ) , // SlotMap for O(1) access
596630 userdata : Arena :: new ( ) ,
597631 threads : Arena :: with_capacity ( 8 ) ,
598632 string_intern : StringInternTable :: with_capacity ( 256 ) ,
@@ -1037,15 +1071,15 @@ impl ObjectPoolV2 {
10371071 self . functions . get_mut ( id. 0 )
10381072 }
10391073
1040- // ==================== Upvalue Operations ====================
1074+ // ==================== Upvalue Operations (using SlotMap) ====================
10411075
10421076 #[ inline]
10431077 pub fn create_upvalue_open ( & mut self , stack_index : usize ) -> UpvalueId {
10441078 let gc_uv = GcUpvalue {
10451079 header : GcHeader :: default ( ) ,
10461080 state : UpvalueState :: Open { stack_index } ,
10471081 } ;
1048- UpvalueId ( self . upvalues . alloc ( gc_uv) )
1082+ UpvalueId ( self . upvalues . insert ( gc_uv) )
10491083 }
10501084
10511085 #[ inline]
@@ -1054,7 +1088,7 @@ impl ObjectPoolV2 {
10541088 header : GcHeader :: default ( ) ,
10551089 state : UpvalueState :: Closed ( value) ,
10561090 } ;
1057- UpvalueId ( self . upvalues . alloc ( gc_uv) )
1091+ UpvalueId ( self . upvalues . insert ( gc_uv) )
10581092 }
10591093
10601094 #[ inline( always) ]
@@ -1074,6 +1108,13 @@ impl ObjectPoolV2 {
10741108 self . upvalues . get_mut ( id. 0 )
10751109 }
10761110
1111+ /// Get mutable upvalue without bounds checking
1112+ /// SAFETY: id must be a valid UpvalueId
1113+ #[ inline( always) ]
1114+ pub unsafe fn get_upvalue_mut_unchecked ( & mut self , id : UpvalueId ) -> & mut GcUpvalue {
1115+ unsafe { self . upvalues . get_unchecked_mut ( id. 0 ) }
1116+ }
1117+
10771118 // ==================== Userdata Operations ====================
10781119
10791120 #[ inline]
@@ -1164,7 +1205,7 @@ impl ObjectPoolV2 {
11641205 . filter ( |( _, gf) | !gf. header . marked )
11651206 . map ( |( id, _) | id)
11661207 . collect ( ) ;
1167- let upvalues_to_free: Vec < u32 > = self
1208+ let upvalues_to_free: Vec < UpvalueKey > = self
11681209 . upvalues
11691210 . iter ( )
11701211 . filter ( |( _, gu) | !gu. header . marked )
@@ -1193,7 +1234,7 @@ impl ObjectPoolV2 {
11931234 self . functions . free ( id) ;
11941235 }
11951236 for id in upvalues_to_free {
1196- self . upvalues . free ( id) ;
1237+ self . upvalues . remove ( id) ;
11971238 }
11981239 for id in threads_to_free {
11991240 self . threads . free ( id) ;
@@ -1204,7 +1245,7 @@ impl ObjectPoolV2 {
12041245 self . strings . shrink_to_fit ( ) ;
12051246 self . tables . shrink_to_fit ( ) ;
12061247 self . functions . shrink_to_fit ( ) ;
1207- self . upvalues . shrink_to_fit ( ) ;
1248+ // SlotMap doesn't have shrink_to_fit, skip upvalues
12081249 self . threads . shrink_to_fit ( ) ;
12091250 self . string_intern . shrink_to_fit ( ) ;
12101251 }
@@ -1228,7 +1269,7 @@ impl ObjectPoolV2 {
12281269
12291270 #[ inline]
12301271 pub fn remove_upvalue ( & mut self , id : UpvalueId ) {
1231- self . upvalues . free ( id. 0 ) ;
1272+ self . upvalues . remove ( id. 0 ) ;
12321273 }
12331274
12341275 #[ inline]
@@ -1269,7 +1310,7 @@ impl ObjectPoolV2 {
12691310 }
12701311}
12711312
1272- impl Default for ObjectPoolV2 {
1313+ impl Default for ObjectPool {
12731314 fn default ( ) -> Self {
12741315 Self :: new ( )
12751316 }
@@ -1318,7 +1359,7 @@ mod tests {
13181359
13191360 #[ test]
13201361 fn test_string_interning ( ) {
1321- let mut pool = ObjectPoolV2 :: new ( ) ;
1362+ let mut pool = ObjectPool :: new ( ) ;
13221363
13231364 let id1 = pool. create_string ( "hello" ) ;
13241365 let id2 = pool. create_string ( "hello" ) ;
@@ -1336,7 +1377,7 @@ mod tests {
13361377
13371378 #[ test]
13381379 fn test_table_operations ( ) {
1339- let mut pool = ObjectPoolV2 :: new ( ) ;
1380+ let mut pool = ObjectPool :: new ( ) ;
13401381
13411382 let tid = pool. create_table ( 4 , 4 ) ;
13421383
@@ -1360,14 +1401,15 @@ mod tests {
13601401 assert_eq ! ( std:: mem:: size_of:: <StringId >( ) , 4 ) ;
13611402 assert_eq ! ( std:: mem:: size_of:: <TableId >( ) , 4 ) ;
13621403 assert_eq ! ( std:: mem:: size_of:: <FunctionId >( ) , 4 ) ;
1363- assert_eq ! ( std:: mem:: size_of:: <UpvalueId >( ) , 4 ) ;
1404+ // UpvalueId is now 8 bytes (slotmap key with version)
1405+ assert_eq ! ( std:: mem:: size_of:: <UpvalueId >( ) , 8 ) ;
13641406 }
13651407
13661408 #[ test]
13671409 fn test_string_interning_many_strings ( ) {
13681410 // Test that many different strings with potential hash collisions
13691411 // are all stored correctly
1370- let mut pool = ObjectPoolV2 :: new ( ) ;
1412+ let mut pool = ObjectPool :: new ( ) ;
13711413 let mut ids = Vec :: new ( ) ;
13721414
13731415 // Create 1000 different strings
@@ -1398,7 +1440,7 @@ mod tests {
13981440 #[ test]
13991441 fn test_string_interning_similar_strings ( ) {
14001442 // Test strings that might have similar hashes
1401- let mut pool = ObjectPoolV2 :: new ( ) ;
1443+ let mut pool = ObjectPool :: new ( ) ;
14021444
14031445 let strings = vec ! [
14041446 "a" , "b" , "c" , "aa" , "ab" , "ba" , "bb" , "aaa" , "aab" , "aba" , "abb" , "baa" , "bab" , "bba" ,
0 commit comments