Skip to content

Commit 2f3ac9c

Browse files
committed
improve upvalue
1 parent 8a2f701 commit 2f3ac9c

3 files changed

Lines changed: 58 additions & 38 deletions

File tree

crates/luars/src/lua_value/mod.rs

Lines changed: 32 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -419,8 +419,7 @@ impl PartialEq for LuaString {
419419
}
420420

421421
/// Inline storage for upvalue pointers — avoids heap allocation for 0-2 upvalues.
422-
/// Used as a temporary construction helper in handle_closure.
423-
/// Converted to Box<[UpvaluePtr]> for storage in LuaFunction (branchless deref).
422+
/// Stored directly in LuaFunction: 0-2 upvalues are inline, 3+ use Box<[UpvaluePtr]>.
424423
pub enum UpvalueStore {
425424
Empty,
426425
One(UpvaluePtr),
@@ -453,6 +452,30 @@ impl UpvalueStore {
453452
}
454453
}
455454

455+
/// Get upvalue pointers as a slice (branchless for Many, single match for 0-2).
456+
/// Called once per function entry — branch cost is negligible.
457+
#[inline(always)]
458+
pub fn as_slice(&self) -> &[UpvaluePtr] {
459+
match self {
460+
UpvalueStore::Empty => &[],
461+
UpvalueStore::One(p) => std::slice::from_ref(p),
462+
UpvalueStore::Two(a) => a.as_slice(),
463+
UpvalueStore::Many(v) => v,
464+
}
465+
}
466+
467+
/// Get mutable upvalue pointers as a slice.
468+
/// Used by debug.upvaluejoin (rare).
469+
#[inline(always)]
470+
pub fn as_mut_slice(&mut self) -> &mut [UpvaluePtr] {
471+
match self {
472+
UpvalueStore::Empty => &mut [],
473+
UpvalueStore::One(p) => std::slice::from_mut(p),
474+
UpvalueStore::Two(a) => a.as_mut_slice(),
475+
UpvalueStore::Many(v) => v,
476+
}
477+
}
478+
456479
#[inline(always)]
457480
pub fn len(&self) -> usize {
458481
match self {
@@ -466,14 +489,14 @@ impl UpvalueStore {
466489

467490
pub struct LuaFunction {
468491
chunk: Rc<Chunk>,
469-
upvalue_ptrs: Box<[UpvaluePtr]>,
492+
upvalue_ptrs: UpvalueStore,
470493
}
471494

472495
impl LuaFunction {
473496
pub fn new(chunk: Rc<Chunk>, upvalue_ptrs: UpvalueStore) -> Self {
474497
LuaFunction {
475498
chunk,
476-
upvalue_ptrs: upvalue_ptrs.into_boxed_slice(),
499+
upvalue_ptrs,
477500
}
478501
}
479502

@@ -483,17 +506,17 @@ impl LuaFunction {
483506
&self.chunk
484507
}
485508

486-
/// Get cached upvalues (direct pointers for fast access)
487-
/// Box<[T]> deref is branchless — no match overhead
509+
/// Get upvalue pointers as a slice.
510+
/// Called once per 'startfunc iteration (function entry) — branch is negligible.
488511
#[inline(always)]
489512
pub fn upvalues(&self) -> &[UpvaluePtr] {
490-
&self.upvalue_ptrs
513+
self.upvalue_ptrs.as_slice()
491514
}
492515

493-
/// Get mutable access to cached upvalues for updating pointers
516+
/// Get mutable access to upvalue pointers (used by debug.upvaluejoin)
494517
#[inline(always)]
495518
pub fn upvalues_mut(&mut self) -> &mut [UpvaluePtr] {
496-
&mut self.upvalue_ptrs
519+
self.upvalue_ptrs.as_mut_slice()
497520
}
498521
}
499522

crates/luars/src/lua_vm/execute/closure_handler.rs

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -38,14 +38,15 @@ pub fn handle_closure(
3838
// Create closure from child prototype
3939
handle_closure_internal(lua_state, base, a, bx, chunk, &upvalue_ptrs)?;
4040

41-
let a = instr.get_a() as usize;
41+
// Save PC for GC, then set top to ra+1 for GC scan scope (like C Lua's checkGC).
42+
// Use set_top_raw: stack is already grown by push_lua_frame, so no bounds check needed.
4243
let new_top = base + a + 1;
4344
lua_state.set_frame_pc(frame_idx, pc as u32);
44-
lua_state.set_top(new_top)?;
45+
lua_state.set_top_raw(new_top);
4546
lua_state.check_gc()?;
4647

4748
let frame_top = lua_state.get_call_info(frame_idx).top;
48-
lua_state.set_top(frame_top)?;
49+
lua_state.set_top_raw(frame_top);
4950
Ok(())
5051
}
5152

crates/luars/src/lua_vm/lua_state.rs

Lines changed: 22 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,6 @@
22
// Represents a single thread/coroutine execution context
33
// Multiple LuaStates can share the same LuaVM (global_State)
44

5-
use std::collections::HashMap;
65
use std::rc::Rc;
76

87
use crate::lua_value::{LuaUserdata, LuaValue, LuaValueKind, LuaValuePtr, UpvalueStore};
@@ -45,9 +44,9 @@ pub struct LuaState {
4544
call_depth: usize,
4645

4746
/// Open upvalues - upvalues pointing to stack locations
48-
/// Uses HashMap for O(1) lookup by stack index (unlike Lua's sorted linked list)
49-
/// Also maintains a sorted Vec(higher indices first) for efficient traversal during close operations
50-
open_upvalues_map: HashMap<usize, UpvaluePtr>,
47+
/// Sorted Vec (higher stack indices first) for efficient lookup and close traversal.
48+
/// Linear scan is faster than HashMap for typical 0-5 open upvalues due to
49+
/// no hashing overhead and better cache locality. Matches C Lua's sorted list design.
5150
open_upvalues_list: Vec<UpvaluePtr>,
5251

5352
/// Error message storage (lightweight error handling)
@@ -109,7 +108,6 @@ impl LuaState {
109108
stack_top: 0, // Start with empty stack (Lua's L->top.p = L->stack)
110109
call_stack: Vec::with_capacity(call_stack_size),
111110
call_depth: 0,
112-
open_upvalues_map: HashMap::new(),
113111
open_upvalues_list: Vec::new(),
114112
error_msg: String::new(),
115113
error_object: LuaValue::nil(),
@@ -862,16 +860,12 @@ impl LuaState {
862860

863861
if count > 0 {
864862
// Process upvalues to close in-place, then drain.
865-
// First pass: close each upvalue and remove from map.
866863
for i in 0..count {
867864
let upval_ptr = self.open_upvalues_list[i];
868865
let data = &upval_ptr.as_ref().data;
869866
if data.is_open() {
870867
let stack_idx = data.get_stack_index();
871868

872-
// Remove from map (maintain consistency)
873-
self.open_upvalues_map.remove(&stack_idx);
874-
875869
// Capture value from stack
876870
let value = self
877871
.stack
@@ -1288,13 +1282,20 @@ impl LuaState {
12881282
result
12891283
}
12901284

1291-
/// Find or create an open upvalue for the given stack index
1292-
/// Uses HashMap for O(1) lookup instead of O(n) linear search
1293-
/// This is a major optimization over the naive linked list approach
1285+
/// Find or create an open upvalue for the given stack index.
1286+
/// Uses linear scan on sorted Vec — faster than HashMap for typical 0-5 open upvalues.
12941287
pub fn find_or_create_upvalue(&mut self, stack_index: usize) -> LuaResult<UpvaluePtr> {
1295-
// O(1) lookup in HashMap
1296-
if let Some(&upval_ptr) = self.open_upvalues_map.get(&stack_index) {
1297-
return Ok(upval_ptr);
1288+
// Linear scan on sorted list (descending by stack index).
1289+
// For 0-5 elements, this is faster than HashMap (no hash computation).
1290+
for &upval_ptr in &self.open_upvalues_list {
1291+
let idx = upval_ptr.as_ref().data.get_stack_index();
1292+
if idx == stack_index {
1293+
return Ok(upval_ptr);
1294+
}
1295+
if idx < stack_index {
1296+
// Passed the insertion point — not found (list is sorted descending)
1297+
break;
1298+
}
12981299
}
12991300

13001301
// Not found, create a new one
@@ -1306,17 +1307,12 @@ impl LuaState {
13061307
vm.create_upvalue_open(stack_index, ptr)?
13071308
};
13081309

1309-
// Add to HashMap for O(1) future lookups
1310-
self.open_upvalues_map.insert(stack_index, upval_ptr);
1311-
1312-
// Also add to sorted list for traversal (insert in sorted position, higher indices first)
1313-
// Collect existing upvalue IDs and their stack indices
1314-
let insert_pos = {
1315-
self.open_upvalues_list
1316-
.iter()
1317-
.position(|&ptr| ptr.as_ref().data.get_stack_index() < stack_index)
1318-
.unwrap_or(self.open_upvalues_list.len())
1319-
};
1310+
// Insert in sorted position (higher indices first)
1311+
let insert_pos = self
1312+
.open_upvalues_list
1313+
.iter()
1314+
.position(|&ptr| ptr.as_ref().data.get_stack_index() < stack_index)
1315+
.unwrap_or(self.open_upvalues_list.len());
13201316

13211317
self.open_upvalues_list.insert(insert_pos, upval_ptr);
13221318

0 commit comments

Comments
 (0)