@@ -8,7 +8,6 @@ use crate::LuaVM;
88use crate :: lua_vm:: LuaResult ;
99use std:: any:: Any ;
1010use std:: cell:: RefCell ;
11- use std:: collections:: HashMap ;
1211use std:: fmt;
1312use std:: hash:: Hasher ;
1413use std:: rc:: Rc ;
@@ -44,103 +43,124 @@ pub use lua_value::{
4443} ;
4544
4645/// Multi-return values from Lua functions
47- /// OPTIMIZED: Use inline storage for common single-value case
46+ /// OPTIMIZED: Compact enum representation (32 bytes)
47+ /// - Empty: no return values
48+ /// - Single: one value (no heap allocation, most common case)
49+ /// - Many: 2+ values stored in Vec (heap allocation only when needed)
4850#[ derive( Debug , Clone ) ]
49- pub struct MultiValue {
50- // Inline storage for 0-2 values (covers 99% of cases without heap allocation)
51- pub inline : [ LuaValue ; 2 ] ,
52- // Count of values stored inline (0, 1, or 2)
53- pub inline_count : u8 ,
54- // Only used when > 2 values
55- pub overflow : Option < Vec < LuaValue > > ,
51+ pub enum MultiValue {
52+ Empty ,
53+ Single ( LuaValue ) ,
54+ Many ( Vec < LuaValue > ) ,
5655}
5756
5857impl MultiValue {
5958 #[ inline( always) ]
6059 pub fn empty ( ) -> Self {
61- MultiValue {
62- inline : [ LuaValue :: nil ( ) , LuaValue :: nil ( ) ] ,
63- inline_count : 0 ,
64- overflow : None ,
65- }
60+ MultiValue :: Empty
6661 }
6762
6863 #[ inline( always) ]
6964 pub fn single ( value : LuaValue ) -> Self {
70- MultiValue {
71- inline : [ value, LuaValue :: nil ( ) ] ,
72- inline_count : 1 ,
73- overflow : None ,
74- }
65+ MultiValue :: Single ( value)
7566 }
7667
7768 #[ inline( always) ]
7869 pub fn two ( v1 : LuaValue , v2 : LuaValue ) -> Self {
79- MultiValue {
80- inline : [ v1, v2] ,
81- inline_count : 2 ,
82- overflow : None ,
83- }
70+ MultiValue :: Many ( vec ! [ v1, v2] )
8471 }
8572
8673 pub fn multiple ( values : Vec < LuaValue > ) -> Self {
87- let len = values. len ( ) ;
88- if len == 0 {
89- Self :: empty ( )
90- } else if len == 1 {
91- Self :: single ( values. into_iter ( ) . next ( ) . unwrap ( ) )
92- } else if len == 2 {
93- let mut iter = values. into_iter ( ) ;
94- Self :: two ( iter. next ( ) . unwrap ( ) , iter. next ( ) . unwrap ( ) )
95- } else {
96- MultiValue {
97- inline : [ LuaValue :: nil ( ) , LuaValue :: nil ( ) ] ,
98- inline_count : 0 ,
99- overflow : Some ( values) ,
100- }
74+ match values. len ( ) {
75+ 0 => MultiValue :: Empty ,
76+ 1 => MultiValue :: Single ( values. into_iter ( ) . next ( ) . unwrap ( ) ) ,
77+ _ => MultiValue :: Many ( values) ,
10178 }
10279 }
10380
10481 #[ inline( always) ]
10582 pub fn all_values ( self ) -> Vec < LuaValue > {
106- if let Some ( v) = self . overflow {
107- v
108- } else {
109- match self . inline_count {
110- 0 => Vec :: new ( ) ,
111- 1 => vec ! [ self . inline[ 0 ] ] ,
112- 2 => vec ! [ self . inline[ 0 ] , self . inline[ 1 ] ] ,
113- _ => Vec :: new ( ) ,
114- }
83+ match self {
84+ MultiValue :: Empty => Vec :: new ( ) ,
85+ MultiValue :: Single ( v) => vec ! [ v] ,
86+ MultiValue :: Many ( v) => v,
11587 }
11688 }
11789
11890 /// Get count of return values (optimized, no allocation)
11991 #[ inline( always) ]
12092 pub fn len ( & self ) -> usize {
121- if let Some ( ref v ) = self . overflow {
122- v . len ( )
123- } else {
124- self . inline_count as usize
93+ match self {
94+ MultiValue :: Empty => 0 ,
95+ MultiValue :: Single ( _ ) => 1 ,
96+ MultiValue :: Many ( v ) => v . len ( ) ,
12597 }
12698 }
12799
128100 /// Check if empty
129101 #[ inline( always) ]
130102 pub fn is_empty ( & self ) -> bool {
131- self . inline_count == 0 && self . overflow . is_none ( )
103+ matches ! ( self , MultiValue :: Empty )
132104 }
133105
134106 /// Get first value (common case, optimized)
135107 #[ inline( always) ]
136108 pub fn first ( & self ) -> Option < LuaValue > {
137- if let Some ( ref v) = self . overflow {
138- v. first ( ) . copied ( )
139- } else if self . inline_count > 0 {
140- Some ( self . inline [ 0 ] )
141- } else {
142- None
109+ match self {
110+ MultiValue :: Empty => None ,
111+ MultiValue :: Single ( v) => Some ( * v) ,
112+ MultiValue :: Many ( v) => v. first ( ) . copied ( ) ,
113+ }
114+ }
115+
116+ /// Get second value
117+ #[ inline( always) ]
118+ pub fn second ( & self ) -> Option < LuaValue > {
119+ match self {
120+ MultiValue :: Empty | MultiValue :: Single ( _) => None ,
121+ MultiValue :: Many ( v) => v. get ( 1 ) . copied ( ) ,
122+ }
123+ }
124+
125+ /// Get value at index (0-based)
126+ #[ inline( always) ]
127+ pub fn get ( & self , index : usize ) -> Option < LuaValue > {
128+ match self {
129+ MultiValue :: Empty => None ,
130+ MultiValue :: Single ( v) => {
131+ if index == 0 {
132+ Some ( * v)
133+ } else {
134+ None
135+ }
136+ }
137+ MultiValue :: Many ( v) => v. get ( index) . copied ( ) ,
138+ }
139+ }
140+
141+ /// Copy values to a slice, filling with nil if needed
142+ /// Returns the number of values actually copied (before nil fill)
143+ #[ inline( always) ]
144+ pub fn copy_to_slice ( & self , dest : & mut [ LuaValue ] ) -> usize {
145+ let count = self . len ( ) . min ( dest. len ( ) ) ;
146+ match self {
147+ MultiValue :: Empty => { }
148+ MultiValue :: Single ( v) => {
149+ if !dest. is_empty ( ) {
150+ dest[ 0 ] = * v;
151+ }
152+ }
153+ MultiValue :: Many ( v) => {
154+ for ( i, val) in v. iter ( ) . take ( dest. len ( ) ) . enumerate ( ) {
155+ dest[ i] = * val;
156+ }
157+ }
158+ }
159+ // Fill remaining with nil
160+ for slot in dest. iter_mut ( ) . skip ( count) {
161+ * slot = LuaValue :: nil ( ) ;
143162 }
163+ count
144164 }
145165}
146166
@@ -425,109 +445,6 @@ impl Chunk {
425445 }
426446}
427447
428- /// String interning pool for short strings (Lua 5.4 optimization)
429- /// Short strings (≤ LUAI_MAXSHORTLEN) are interned to save memory and speed up comparisons
430- pub struct StringPool {
431- /// Maximum length for short strings (typically 40 bytes in Lua)
432- max_short_len : usize ,
433- /// Interned short strings - key is the string content, value is Rc
434- pool : HashMap < String , Rc < LuaString > > ,
435- }
436-
437- impl StringPool {
438- /// Create a new string pool with default max short length
439- pub fn new ( ) -> Self {
440- Self :: with_max_len ( 40 )
441- }
442-
443- /// Create a new string pool with custom max short length
444- pub fn with_max_len ( max_short_len : usize ) -> Self {
445- StringPool {
446- max_short_len,
447- pool : HashMap :: new ( ) ,
448- }
449- }
450-
451- /// Intern a string. If it's short and already exists, return the cached version.
452- /// Otherwise create a new string.
453- pub fn intern ( & mut self , s : String ) -> Rc < LuaString > {
454- if s. len ( ) <= self . max_short_len {
455- // Short string: check pool first
456- if let Some ( existing) = self . pool . get ( & s) {
457- return Rc :: clone ( existing) ;
458- }
459-
460- // Not in pool: create and insert
461- let lua_str = Rc :: new ( LuaString :: new ( s. clone ( ) ) ) ;
462- self . pool . insert ( s, Rc :: clone ( & lua_str) ) ;
463- lua_str
464- } else {
465- // Long string: don't intern, create directly
466- Rc :: new ( LuaString :: new ( s) )
467- }
468- }
469-
470- /// Get statistics about the string pool
471- pub fn stats ( & self ) -> ( usize , usize ) {
472- let count = self . pool . len ( ) ;
473- let bytes: usize = self . pool . keys ( ) . map ( |s| s. len ( ) ) . sum ( ) ;
474- ( count, bytes)
475- }
476-
477- /// Clear the string pool (useful for testing or memory cleanup)
478- pub fn clear ( & mut self ) {
479- self . pool . clear ( ) ;
480- }
481- }
482-
483- impl Default for StringPool {
484- fn default ( ) -> Self {
485- Self :: new ( )
486- }
487- }
488-
489- #[ cfg( test) ]
490- mod string_pool_tests {
491- use super :: * ;
492-
493- #[ test]
494- fn test_short_string_interning ( ) {
495- let mut pool = StringPool :: new ( ) ;
496-
497- let s1 = pool. intern ( "hello" . to_string ( ) ) ;
498- let s2 = pool. intern ( "hello" . to_string ( ) ) ;
499-
500- // Should be the same Rc instance
501- assert ! ( Rc :: ptr_eq( & s1, & s2) ) ;
502- assert_eq ! ( pool. stats( ) . 0 , 1 ) ; // Only 1 unique string
503- }
504-
505- #[ test]
506- fn test_long_string_no_interning ( ) {
507- let mut pool = StringPool :: with_max_len ( 10 ) ;
508-
509- let long_str = "a" . repeat ( 50 ) ;
510- let s1 = pool. intern ( long_str. clone ( ) ) ;
511- let s2 = pool. intern ( long_str) ;
512-
513- // Long strings are NOT interned
514- assert ! ( !Rc :: ptr_eq( & s1, & s2) ) ;
515- assert_eq ! ( pool. stats( ) . 0 , 0 ) ; // No strings in pool
516- }
517-
518- #[ test]
519- fn test_multiple_short_strings ( ) {
520- let mut pool = StringPool :: new ( ) ;
521-
522- let _ = pool. intern ( "foo" . to_string ( ) ) ;
523- let _ = pool. intern ( "bar" . to_string ( ) ) ;
524- let _ = pool. intern ( "foo" . to_string ( ) ) ; // Duplicate
525- let _ = pool. intern ( "baz" . to_string ( ) ) ;
526-
527- assert_eq ! ( pool. stats( ) . 0 , 3 ) ; // 3 unique strings
528- }
529- }
530-
531448#[ cfg( test) ]
532449mod value_tests {
533450 use super :: * ;
@@ -579,5 +496,9 @@ mod value_tests {
579496
580497 // LuaValue should be 16 bytes (enum discriminant + largest variant)
581498 assert_eq ! ( size_of:: <LuaValue >( ) , 16 ) ;
499+
500+ // MultiValue should be 24 bytes (Many variant: Vec<LuaValue> 24 bytes, Single is 16+tag)
501+ // Down from 64 bytes in original struct - 62.5% reduction!
502+ assert_eq ! ( size_of:: <super :: MultiValue >( ) , 24 ) ;
582503 }
583504}
0 commit comments