Skip to content

Commit 6aede11

Browse files
committed
optimize table set peformance
1 parent 31c95cf commit 6aede11

2 files changed

Lines changed: 77 additions & 128 deletions

File tree

crates/luars/src/lua_value/lua_table/native_table.rs

Lines changed: 13 additions & 49 deletions
Original file line numberDiff line numberDiff line change
@@ -490,13 +490,15 @@ impl NativeTable {
490490
if (*node).key_tt == LUA_VSHRSTR
491491
&& short_string_ptr_eq((*node).key_string_ptr(), key_ptr)
492492
{
493+
// Value update: HOK path
493494
if (*node).val_tt != LUA_VNIL {
494495
(*node).set_value_parts(value, tt);
495496
return ShortStrSetResult::Done {
496497
new_key: false,
497498
mem_delta: 0,
498499
};
499500
}
501+
// Reactivate dead key
500502
return if tt == LUA_VNIL {
501503
ShortStrSetResult::Done {
502504
new_key: false,
@@ -523,62 +525,24 @@ impl NativeTable {
523525
};
524526
}
525527

526-
if (*mp).key_tt == LUA_VNIL {
527-
(*mp).set_key(key);
528-
(*mp).set_value_parts(value, tt);
529-
(*mp).next = 0;
530-
return ShortStrSetResult::Done {
531-
new_key: true,
532-
mem_delta: 0,
533-
};
534-
}
535-
if (*mp).val_tt == LUA_VNIL {
528+
// Key NOT FOUND in chain. Return FinishNode / FinishNewKey WITHOUT
529+
// writing the value: the caller must check __newindex BEFORE calling
530+
// finish_shortstr_set to commit the write. This is the C Lua 5.5
531+
// behavior: luaH_psetshortstr only writes for HOK; new keys are
532+
// handled by luaV_finishset after the metatable check.
533+
534+
// mp empty or dead: the caller's finish_shortstr_set writes key+value.
535+
if (*mp).key_tt == LUA_VNIL || (*mp).val_tt == LUA_VNIL {
536536
return ShortStrSetResult::FinishNode {
537537
new_key: true,
538538
node_index: self.node_index(mp),
539539
};
540540
}
541541

542-
let othern = self.mainposition_from_node(mp);
543-
if othern != mp {
544-
if let Some(free_node) = self.getfreepos() {
545-
let mut prev = othern;
546-
while prev.offset((*prev).next as isize) != mp {
547-
prev = prev.offset((*prev).next as isize);
548-
}
549-
(*prev).next = Self::node_offset(prev, free_node);
550-
*free_node = *mp;
551-
if (*free_node).next != 0 {
552-
(*free_node).next += Self::node_offset(free_node, mp);
553-
}
554-
(*mp).set_key(key);
555-
(*mp).set_value_parts(value, tt);
556-
(*mp).next = 0;
557-
return ShortStrSetResult::Done {
558-
new_key: true,
559-
mem_delta: 0,
560-
};
561-
}
562-
return ShortStrSetResult::FinishNewKey;
563-
}
564-
565-
if let Some(free_node) = self.getfreepos() {
566-
(*free_node).set_key(key);
567-
(*free_node).set_value_parts(value, tt);
568-
if (*mp).next != 0 {
569-
(*free_node).next =
570-
Self::node_offset(free_node, mp.offset((*mp).next as isize));
571-
} else {
572-
(*free_node).next = 0;
573-
}
574-
(*mp).next = Self::node_offset(mp, free_node);
575-
return ShortStrSetResult::Done {
576-
new_key: true,
577-
mem_delta: 0,
578-
};
579-
}
542+
// Displacement or collision insertion needed — delegate to
543+
// insert_new_shortstr_no_rehash (called via FinishNewKey).
544+
ShortStrSetResult::FinishNewKey
580545
}
581-
ShortStrSetResult::FinishNewKey
582546
}
583547

584548
#[inline(always)]

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

Lines changed: 64 additions & 79 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,6 @@
1818

1919
use crate::{
2020
Instruction, LuaResult, LuaValue, OpCode,
21-
gc::TablePtr,
2221
lua_vm::{
2322
LuaState, StkId, TmKind,
2423
call_info::CallInfo,
@@ -253,6 +252,8 @@ pub(crate) fn op_get_field(
253252
// ── SET operations ──────────────────────────────────────────────
254253

255254
/// SetTabUp: UpValue[A][K[B]:shortstring] := RK(C)
255+
///
256+
/// Lua 5.5 style: fast set first, metatable deferred to fallback.
256257
#[inline]
257258
pub(crate) fn op_set_tabup(
258259
lua_state: &mut LuaState,
@@ -269,79 +270,78 @@ pub(crate) fn op_set_tabup(
269270
let upvalue_ptr = unsafe { *ci.upvalue_ptrs.add(a as usize) };
270271
let upval_value = upvalue_ptr.as_ref().data.get_value_ref();
271272
let key = k_val(constants, b);
272-
debug_assert!(
273-
key.is_short_string(),
274-
"GetTabUp key must be short string for fast path"
275-
);
276-
let mut known_newindex_miss = false;
277-
let mut meta = TablePtr::null();
273+
debug_assert!(key.is_short_string(), "SetTabUp key must be short string");
274+
275+
// Unified value extraction (RKC in C)
276+
let (rc_ref, rc_is_collectable) = if instr.get_k() {
277+
let v = k_val(constants, c);
278+
(v, v.is_collectable())
279+
} else {
280+
let stk = (*base_stk).offset(c as usize);
281+
(stk.get_ref(), stk.is_collectable())
282+
};
283+
284+
// luaV_fastset: fast set first, no metatable check
278285
if upval_value.is_table() {
279286
let table = upval_value.hvalue_mut();
280287
let table_ptr = upval_value.table_ptr_raw();
281288
let gc_ptr = upval_value.as_gc_ptr_unchecked();
282-
meta = table.meta_ptr();
289+
let pset_result = table.impl_table.pset_shortstr(key, rc_ref);
290+
291+
// HOK: existing key, value written
292+
if pset_result.is_hok() {
293+
if rc_is_collectable {
294+
lua_state.gc_barrier_back(gc_ptr);
295+
}
296+
return Ok(());
297+
}
298+
299+
// Fallback: check metatable
300+
let meta = table.meta_ptr();
283301
if meta.is_null() || meta.as_mut_ref().data.no_tm(TmKind::NewIndex.into()) {
284-
let (new_key, delta, is_collectable) = if instr.get_k() {
285-
let rc_value = k_val(constants, c);
286-
let pset_result = table.impl_table.pset_shortstr(key, rc_value);
287-
let (new_key, delta) =
288-
table
289-
.impl_table
290-
.finish_shortstr_set(key, rc_value, pset_result);
291-
(new_key, delta, rc_value.is_collectable())
292-
} else {
293-
let rc = (*base_stk).offset(c as usize);
294-
let pset_result = table.impl_table.pset_shortstr(key, rc.get_ref());
295-
let (new_key, delta) =
296-
table
297-
.impl_table
298-
.finish_shortstr_set(key, rc.get_ref(), pset_result);
299-
(new_key, delta, rc.is_collectable())
300-
};
302+
let (new_key, delta) = table
303+
.impl_table
304+
.finish_shortstr_set(key, rc_ref, pset_result);
301305
if new_key {
302306
table.invalidate_tm_cache();
303307
}
304308
if delta != 0 {
305309
lua_state.gc_track_table_resize(table_ptr, delta);
306310
}
307-
if is_collectable {
311+
if rc_is_collectable {
308312
lua_state.gc_barrier_back(gc_ptr);
309313
}
310314
return Ok(());
311-
} else {
312-
let rc_value = if instr.get_k() {
313-
k_val(constants, c)
314-
} else {
315-
(*base_stk).offset(c as usize).get_ref()
316-
};
317-
if table.impl_table.set_existing_shortstr(key, rc_value) {
318-
if rc_value.is_collectable() {
319-
lua_state.gc_barrier_back(gc_ptr);
320-
}
321-
return Ok(());
322-
}
323-
known_newindex_miss = true;
324315
}
325-
}
326316

327-
let upval_value = *upval_value;
328-
let rc = if instr.get_k() {
329-
*k_val(constants, c)
330-
} else {
331-
(*base_stk).offset(c as usize).get()
332-
};
333-
ci.save_pc(pc);
334-
lua_state.set_top_raw(ci.top as usize);
335-
if known_newindex_miss {
336-
if call_newindex_tm_fast(lua_state, ci, upval_value, meta, *key, rc)? {
317+
// Has __newindex
318+
if table.impl_table.set_existing_shortstr(key, rc_ref) {
319+
if rc_is_collectable {
320+
lua_state.gc_barrier_back(gc_ptr);
321+
}
322+
return Ok(());
323+
}
324+
let upval = *upval_value;
325+
let rc = *rc_ref;
326+
ci.save_pc(pc);
327+
lua_state.set_top_raw(ci.top as usize);
328+
if call_newindex_tm_fast(lua_state, ci, upval, meta, *key, rc)? {
337329
*base_stk = ci.base_stk;
338330
updatetrap!(trap, lua_state);
339331
return Ok(());
340332
}
341-
finishset_fallback(lua_state, ci, &upval_value, key, rc, true)?;
342-
} else {
343-
finishset_fallback(lua_state, ci, &upval_value, key, rc, false)?;
333+
finishset_fallback(lua_state, ci, &upval, key, rc, true)?;
334+
*base_stk = ci.base_stk;
335+
updatetrap!(trap, lua_state);
336+
return Ok(());
344337
}
338+
339+
// Not a table
340+
let upval = *upval_value;
341+
let rc = *rc_ref;
342+
ci.save_pc(pc);
343+
lua_state.set_top_raw(ci.top as usize);
344+
finishset_fallback(lua_state, ci, &upval, key, rc, false)?;
345345
*base_stk = ci.base_stk;
346346
updatetrap!(trap, lua_state);
347347
Ok(())
@@ -549,45 +549,31 @@ pub(crate) fn op_set_i(
549549
let b = instr.get_b() as i64;
550550
let c = instr.get_c();
551551

552-
// Hot path: table with no __newindex metamethod, key in array range
553552
if ra.is_table() {
554553
let table = ra.hvalue_mut();
555554
let table_ptr = ra.as_table_ptr();
556555
let gc_ptr = ra.as_gc_ptr();
557556
let meta = table.meta_ptr();
558557
if meta.is_null() || meta.as_mut_ref().data.no_tm(TmKind::NewIndex.into()) {
559-
if !instr.get_k() {
560-
let rc = base.offset(c as usize);
561-
if table.impl_table.fast_seti(b, rc.get()) {
562-
if rc.is_collectable() {
563-
lua_state.gc_barrier_back(gc_ptr);
564-
}
565-
return Ok(());
566-
}
567-
568-
let delta = table.impl_table.set_int_slow(b, rc.get());
569-
if delta != 0 {
570-
lua_state.gc_track_table_resize(table_ptr, delta);
571-
}
572-
if rc.is_collectable() {
573-
lua_state.gc_barrier_back(gc_ptr);
574-
}
575-
return Ok(());
576-
}
558+
let (rc_val, rc_is_collectable) = if instr.get_k() {
559+
let v = *k_val(constants, c);
560+
(v, v.is_collectable())
561+
} else {
562+
let stk = base.offset(c as usize);
563+
(stk.get(), stk.is_collectable())
564+
};
577565

578-
let rc = *k_val(constants, c);
579-
if table.impl_table.fast_seti(b, rc) {
580-
if rc.is_collectable() {
566+
if table.impl_table.fast_seti(b, rc_val) {
567+
if rc_is_collectable {
581568
lua_state.gc_barrier_back(gc_ptr);
582569
}
583570
return Ok(());
584571
}
585-
586-
let delta = table.impl_table.set_int_slow(b, rc);
572+
let delta = table.impl_table.set_int_slow(b, rc_val);
587573
if delta != 0 {
588574
lua_state.gc_track_table_resize(table_ptr, delta);
589575
}
590-
if rc.is_collectable() {
576+
if rc_is_collectable {
591577
lua_state.gc_barrier_back(gc_ptr);
592578
}
593579
return Ok(());
@@ -603,7 +589,6 @@ pub(crate) fn op_set_i(
603589
}
604590
return Ok(());
605591
}
606-
// Fall through to finishset fallback (known miss)
607592
let rb = LuaValue::integer(b);
608593
ci.save_pc(pc);
609594
lua_state.set_top_raw(ci.top as usize);

0 commit comments

Comments
 (0)