Skip to content

Commit 1478e57

Browse files
committed
optimize
1 parent 5aff73d commit 1478e57

File tree

3 files changed

+75
-97
lines changed

3 files changed

+75
-97
lines changed

crates/luars/src/lua_value/lua_table.rs

Lines changed: 12 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -87,21 +87,22 @@ impl LuaTable {
8787
}
8888

8989
/// Hash function for LuaValue - optimized for speed
90+
/// Uses identity hash for GC objects (string/table/function ID) and value hash for primitives
9091
#[inline(always)]
9192
fn hash_key(key: &LuaValue, size: usize) -> usize {
92-
if size == 0 {
93-
return 0;
94-
}
95-
96-
// Use XOR of both words for good distribution
97-
// This is faster than type-checking and works for all value types
98-
let raw = key.primary ^ key.secondary;
99-
100-
// Simple but effective mixing (from FxHash)
101-
let hash = raw.wrapping_mul(0x517cc1b727220a95);
93+
// Combine primary and secondary for best distribution
94+
// For strings: primary has tag|id, secondary is 0 -> hash of id
95+
// For integers: primary has tag, secondary has value -> hash of value
96+
// For floats: similar to integers
97+
// XOR gives good mixing without branch
98+
let raw = key.primary.wrapping_add(key.secondary);
99+
100+
// Fibonacci hashing - excellent distribution for sequential IDs
101+
// Golden ratio: 2^64 / phi ≈ 0x9e3779b97f4a7c15
102+
let hash = raw.wrapping_mul(0x9e3779b97f4a7c15);
102103

103104
// Fast modulo using bitmask (size is power of 2)
104-
(hash as usize) & (size - 1)
105+
(hash >> 32) as usize & (size - 1)
105106
}
106107

107108
/// Find a node with the given key, returns Some(index) if found

crates/luars/src/lua_value/lua_value.rs

Lines changed: 21 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -405,28 +405,30 @@ impl LuaValue {
405405
// ============ Equality ============
406406

407407
/// Raw equality (no metamethods)
408-
/// OPTIMIZED: For interned types (string/table/etc), primary contains ID - no secondary check needed
408+
/// OPTIMIZED: Branchless comparison for common case, only branch for float NaN handling
409409
#[inline(always)]
410410
pub fn raw_equal(&self, other: &Self) -> bool {
411-
// Fast path: if primary differs, they're not equal (different type or ID)
412-
if self.primary != other.primary {
413-
return false;
414-
}
415-
// Primary matches
416-
// Only INTEGER and FLOAT store value in secondary
417-
// INTEGER: primary is just TAG_INTEGER, value in secondary
418-
// FLOAT: primary is just TAG_FLOAT, value bits in secondary + NaN handling
419-
// All other types: ID or value encoded in primary, secondary unused or always 0
420-
let tag = self.primary & TAG_MASK;
421-
if tag == TAG_INTEGER {
422-
self.secondary == other.secondary
423-
} else if tag == TAG_FLOAT {
424-
let a = f64::from_bits(self.secondary);
425-
let b = f64::from_bits(other.secondary);
426-
a == b
411+
// Compare both primary and secondary
412+
// This is branchless and correct for:
413+
// - Strings/Tables/Functions: primary has tag|id, secondary is 0
414+
// - Integers: primary is TAG_INTEGER, secondary has value
415+
// - Booleans: primary is TAG_TRUE or TAG_FALSE, secondary is 0
416+
// - Nil: primary is TAG_NIL, secondary is 0
417+
//
418+
// Special case: Float - IEEE 754 says NaN != NaN, but bit comparison would say equal
419+
// We handle this with a single branch check
420+
if self.primary == other.primary && self.secondary == other.secondary {
421+
// Fast path: bits match
422+
// Need to verify this isn't two NaNs comparing equal
423+
if (self.primary & TAG_MASK) == TAG_FLOAT {
424+
// For floats, use proper IEEE comparison
425+
let a = f64::from_bits(self.secondary);
426+
a == a // Returns false if NaN
427+
} else {
428+
true
429+
}
427430
} else {
428-
// String, Table, Function, etc - primary already contains unique ID
429-
true
431+
false
430432
}
431433
}
432434

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

Lines changed: 42 additions & 67 deletions
Original file line numberDiff line numberDiff line change
@@ -75,21 +75,22 @@ pub fn exec_gettable(vm: &mut LuaVM, instr: u32, frame_ptr: *mut LuaCallFrame, b
7575
(table, key)
7676
};
7777

78-
// FAST PATH: Direct table access for common case (integer key, no metatable)
78+
// FAST PATH: Direct table access for common case
7979
if let Some(table_id) = table_value.as_table_id() {
8080
// SAFETY: table_id is valid because it came from as_table_id()
8181
let lua_table = unsafe { vm.object_pool.get_table_unchecked(table_id) };
8282

83-
// Try integer key fast path first
84-
if let Some(i) = key_value.as_integer() {
85-
if let Some(val) = lua_table.get_int(i) {
86-
unsafe { *vm.register_stack.get_unchecked_mut(*base_ptr + a) = val };
87-
return Ok(());
88-
}
89-
}
83+
// Check key type to choose optimal path
84+
// Integer keys may be in array part, other keys are in hash part
85+
let result = if let Some(i) = key_value.as_integer() {
86+
// Integer key: try array first, then hash
87+
lua_table.get_int(i).or_else(|| lua_table.get_from_hash(&key_value))
88+
} else {
89+
// Non-integer key: direct hash lookup (most common for string keys)
90+
lua_table.get_from_hash(&key_value)
91+
};
9092

91-
// Try hash lookup
92-
if let Some(val) = lua_table.get_from_hash(&key_value) {
93+
if let Some(val) = result {
9394
if !val.is_nil() {
9495
unsafe { *vm.register_stack.get_unchecked_mut(*base_ptr + a) = val };
9596
return Ok(());
@@ -259,44 +260,35 @@ pub fn exec_seti(vm: &mut LuaVM, instr: u32, frame_ptr: *mut LuaCallFrame, base_
259260

260261
/// GETFIELD A B C
261262
/// R[A] := R[B][K[C]:string]
263+
/// OPTIMIZED: Uses cached constants_ptr for direct constant access
262264
#[inline(always)]
263265
pub fn exec_getfield(vm: &mut LuaVM, instr: u32, frame_ptr: *mut LuaCallFrame, base_ptr: &mut usize) -> LuaResult<()> {
264266
let a = Instruction::get_a(instr) as usize;
265267
let b = Instruction::get_b(instr) as usize;
266268
let c = Instruction::get_c(instr) as usize;
267269

268-
let func_value = unsafe { (*frame_ptr).function_value };
269-
270-
// Get key constant using new API
271-
let Some(func_id) = func_value.as_function_id() else {
272-
return Err(vm.error("Not a Lua function".to_string()));
273-
};
274-
let Some(func_ref) = vm.object_pool.get_function(func_id) else {
275-
return Err(vm.error("Invalid function ID".to_string()));
276-
};
277-
let Some(key_value) = func_ref.chunk.constants.get(c).copied() else {
278-
return Err(vm.error(format!("Invalid constant index: {}", c)));
279-
};
280-
281-
let table_value = vm.register_stack[*base_ptr + b];
270+
// FAST PATH: Direct constant access via cached pointer (like GETTABUP)
271+
let key_value = unsafe { *(*frame_ptr).constants_ptr.add(c) };
272+
let table_value = unsafe { *vm.register_stack.get_unchecked(*base_ptr + b) };
282273

283274
// FAST PATH: Direct hash access for tables without metatable
284275
if let Some(table_id) = table_value.as_table_id() {
285-
if let Some(table_ref) = vm.object_pool.get_table(table_id) {
286-
// Use optimized hash-only lookup (GETFIELD always uses string keys, never integers)
287-
if let Some(val) = table_ref.get_from_hash(&key_value) {
288-
if !val.is_nil() {
289-
vm.register_stack[*base_ptr + a] = val;
290-
return Ok(());
291-
}
292-
}
276+
// OPTIMIZED: Use unchecked table access
277+
let table_ref = unsafe { vm.object_pool.get_table_unchecked(table_id) };
293278

294-
// Check if no metatable - can return nil directly
295-
if table_ref.get_metatable().is_none() {
296-
vm.register_stack[*base_ptr + a] = LuaValue::nil();
279+
// GETFIELD always uses string keys - direct hash lookup
280+
if let Some(val) = table_ref.get_from_hash(&key_value) {
281+
if !val.is_nil() {
282+
unsafe { *vm.register_stack.get_unchecked_mut(*base_ptr + a) = val };
297283
return Ok(());
298284
}
299285
}
286+
287+
// Check if no metatable - can return nil directly
288+
if table_ref.get_metatable().is_none() {
289+
unsafe { *vm.register_stack.get_unchecked_mut(*base_ptr + a) = LuaValue::nil() };
290+
return Ok(());
291+
}
300292
}
301293

302294
// Slow path: Use metamethod handling
@@ -306,61 +298,44 @@ pub fn exec_getfield(vm: &mut LuaVM, instr: u32, frame_ptr: *mut LuaCallFrame, b
306298

307299
// IMPORTANT: Re-read base_ptr after metamethod call in case frames changed
308300
*base_ptr = unsafe { (*frame_ptr).base_ptr };
309-
vm.register_stack[*base_ptr + a] = value;
301+
unsafe { *vm.register_stack.get_unchecked_mut(*base_ptr + a) = value };
310302

311303
Ok(())
312304
}
313305

314306
/// SETFIELD A B C k
315307
/// R[A][K[B]:string] := RK(C)
308+
/// OPTIMIZED: Uses cached constants_ptr for direct constant access
316309
#[inline(always)]
317310
pub fn exec_setfield(vm: &mut LuaVM, instr: u32, frame_ptr: *mut LuaCallFrame, base_ptr: &mut usize) -> LuaResult<()> {
318311
let a = Instruction::get_a(instr) as usize;
319312
let b = Instruction::get_b(instr) as usize;
320313
let c = Instruction::get_c(instr) as usize;
321314
let k = Instruction::get_k(instr);
322315

323-
// CRITICAL: Read all values BEFORE any metamethod calls
324-
// because metamethods can modify the register stack
316+
// FAST PATH: Direct access via cached pointers
325317
let (table_value, key_value, set_value) = unsafe {
326-
let func_value = (*frame_ptr).function_value;
327-
328-
// Get key constant using new API
329-
let Some(func_id) = func_value.as_function_id() else {
330-
return Err(vm.error("Not a Lua function".to_string()));
331-
};
332-
let Some(func_ref) = vm.object_pool.get_function(func_id) else {
333-
return Err(vm.error("Invalid function ID".to_string()));
334-
};
335-
let Some(key) = func_ref.chunk.constants.get(b).copied() else {
336-
return Err(vm.error(format!("Invalid constant index: {}", b)));
337-
};
338-
339-
let table = vm.register_stack[*base_ptr + a];
340-
318+
let table = *vm.register_stack.get_unchecked(*base_ptr + a);
319+
let key = *(*frame_ptr).constants_ptr.add(b);
341320
let value = if k {
342-
let Some(constant) = func_ref.chunk.constants.get(c).copied() else {
343-
return Err(vm.error(format!("Invalid constant index: {}", c)));
344-
};
345-
constant
321+
*(*frame_ptr).constants_ptr.add(c)
346322
} else {
347-
vm.register_stack[*base_ptr + c]
323+
*vm.register_stack.get_unchecked(*base_ptr + c)
348324
};
349-
350325
(table, key, value)
351326
};
352327

353328
// FAST PATH: Direct table access without metamethod check for common case
354329
if let Some(table_id) = table_value.as_table_id() {
355-
if let Some(table_ref) = vm.object_pool.get_table_mut(table_id) {
356-
// Quick check: no metatable means no __newindex to worry about
357-
if table_ref.get_metatable().is_none() {
358-
// Ultra-fast path: direct set without any metamethod checks
359-
table_ref.raw_set(key_value.clone(), set_value.clone());
330+
// OPTIMIZED: Use unchecked access
331+
let table_ref = unsafe { vm.object_pool.get_table_unchecked(table_id) };
360332

361-
// Note: GC barrier is handled lazily during collection
362-
return Ok(());
363-
}
333+
// Quick check: no metatable means no __newindex to worry about
334+
if table_ref.get_metatable().is_none() {
335+
let table_ref = unsafe { vm.object_pool.get_table_mut_unchecked(table_id) };
336+
// Ultra-fast path: direct set without any metamethod checks
337+
table_ref.raw_set(key_value, set_value);
338+
return Ok(());
364339
}
365340
}
366341

0 commit comments

Comments
 (0)