Skip to content

Commit fc679f6

Browse files
committed
update
1 parent d2a6917 commit fc679f6

File tree

4 files changed

+157
-243
lines changed

4 files changed

+157
-243
lines changed

crates/luars/src/lib.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ pub use compiler::Compiler;
2020
pub use ffi::FFIState;
2121
pub use gc::*;
2222
pub use lib_registry::LibraryRegistry;
23-
pub use lua_value::{Chunk, LuaFunction, LuaString, LuaTable, LuaValue};
23+
pub use lua_value::{Chunk, LuaFunction, LuaString, LuaTable, LuaValue, MultiValue};
2424
pub use lua_vm::{Instruction, LuaResult, LuaVM, OpCode};
2525
use std::rc::Rc;
2626

crates/luars/src/lua_value/mod.rs

Lines changed: 82 additions & 161 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,6 @@ use crate::LuaVM;
88
use crate::lua_vm::LuaResult;
99
use std::any::Any;
1010
use std::cell::RefCell;
11-
use std::collections::HashMap;
1211
use std::fmt;
1312
use std::hash::Hasher;
1413
use 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

5857
impl 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)]
532449
mod 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

Comments
 (0)