use crate::runtime::heap::{Gc, GcHeader, Heap, Marker};
use crate::runtime::value::{RawVal, Value, f2i_exact, raw};
#[derive(Clone, Copy, PartialEq, Eq, Debug)]
pub enum TableError {
NilIndex,
NanIndex,
InvalidNext,
Overflow,
}
pub(crate) const MAX_ASIZE: usize = 1 << 27;
pub mod jit_layout {
use super::Node;
use crate::runtime::Table;
pub const TABLE_NODES_OFFSET: usize = std::mem::offset_of!(Table, nodes);
pub const NODE_KEY_OFFSET: usize = std::mem::offset_of!(Node, key);
pub const NODE_VAL_OFFSET: usize = std::mem::offset_of!(Node, val);
pub const SIZEOF_NODE: usize = std::mem::size_of::<Node>();
const _: () = {
assert!(std::mem::size_of::<Box<[Node]>>() == 2 * std::mem::size_of::<usize>());
assert!(NODE_KEY_OFFSET == 0);
assert!(NODE_VAL_OFFSET == 16);
assert!(SIZEOF_NODE >= 32);
};
}
#[derive(Clone, Copy)]
pub(crate) struct Node {
key: Value,
val: Value,
next: i32,
dead_key: bool,
}
const NONE: i32 = -1;
impl Node {
const EMPTY: Node = Node {
key: Value::Nil,
val: Value::Nil,
next: NONE,
dead_key: false,
};
}
#[allow(dead_code)]
pub(crate) mod meta_bits {
pub const OCCUPIED_BIT: u16 = 0b1000_0000_0000_0000;
pub const TOMBSTONE_BIT: u16 = 0b0100_0000_0000_0000;
pub const PSL_MASK: u16 = 0b0011_1111_1111_1111;
pub const PSL_MAX: u16 = PSL_MASK;
pub const EMPTY: u16 = 0;
#[inline(always)]
pub fn is_occupied(m: u16) -> bool {
(m & OCCUPIED_BIT) != 0
}
#[inline(always)]
pub fn is_tombstone(m: u16) -> bool {
(m & TOMBSTONE_BIT) != 0
}
#[inline(always)]
pub fn is_live(m: u16) -> bool {
(m & (OCCUPIED_BIT | TOMBSTONE_BIT)) == OCCUPIED_BIT
}
#[inline(always)]
pub fn psl(m: u16) -> u16 {
m & PSL_MASK
}
#[inline(always)]
pub fn pack(psl: u16, tomb: bool) -> u16 {
debug_assert!(psl <= PSL_MAX);
let mut m = OCCUPIED_BIT | (psl & PSL_MASK);
if tomb {
m |= TOMBSTONE_BIT;
}
m
}
}
pub(crate) const INLINE_ASIZE: u64 = 2;
pub(crate) const INLINE_U64S: usize = INLINE_ASIZE as usize + INLINE_ASIZE.div_ceil(8) as usize;
#[repr(C)]
pub struct Table {
#[allow(dead_code)]
pub(crate) hdr: GcHeader,
pub array_ptr: *mut u8,
pub(crate) slab: Box<[u64]>,
pub asize: u64,
pub(crate) inline_storage: [u64; INLINE_U64S],
pub(crate) nodes: Box<[Node]>,
pub(crate) lastfree: u32,
pub(crate) keys: Box<[Value]>,
pub(crate) vals: Box<[Value]>,
pub(crate) meta: Box<[u16]>,
pub(crate) tombstones: u32,
pub(crate) iter_depth: u32,
pub metatable: Option<Gc<Table>>,
#[allow(dead_code)]
pub(crate) flags: u8,
}
unsafe impl Send for Table {}
unsafe impl Sync for Table {}
impl Table {
pub(crate) fn new(hdr: GcHeader) -> Table {
Table {
hdr,
array_ptr: std::ptr::null_mut(),
slab: Box::new([]),
asize: 0,
inline_storage: [0; INLINE_U64S],
nodes: Box::new([]),
lastfree: 0,
keys: Box::new([]),
vals: Box::new([]),
meta: Box::new([]),
tombstones: 0,
iter_depth: 0,
metatable: None,
flags: 0,
}
}
#[inline]
pub(crate) fn init_array_ptr(&mut self) {
self.array_ptr = self.inline_storage.as_mut_ptr() as *mut u8;
}
#[inline(always)]
pub(crate) fn atags(&self) -> &[u8] {
let n = self.asize as usize;
if n == 0 {
return &[];
}
unsafe {
let ptr = self.array_ptr.add(n * 8);
std::slice::from_raw_parts(ptr, n)
}
}
#[inline(always)]
pub(crate) fn atags_mut(&mut self) -> &mut [u8] {
let n = self.asize as usize;
if n == 0 {
return &mut [];
}
unsafe {
let ptr = self.array_ptr.add(n * 8);
std::slice::from_raw_parts_mut(ptr, n)
}
}
#[inline(always)]
pub(crate) fn avals(&self) -> &[RawVal] {
let n = self.asize as usize;
if n == 0 {
return &[];
}
unsafe { std::slice::from_raw_parts(self.array_ptr as *const RawVal, n) }
}
#[inline(always)]
pub(crate) fn avals_mut(&mut self) -> &mut [RawVal] {
let n = self.asize as usize;
if n == 0 {
return &mut [];
}
unsafe { std::slice::from_raw_parts_mut(self.array_ptr as *mut RawVal, n) }
}
fn alloc_slab(asize: usize) -> Box<[u64]> {
if asize == 0 {
return Box::new([]);
}
let avals_u64s = asize;
let atags_u64s = asize.div_ceil(8);
let total = avals_u64s + atags_u64s;
vec![0u64; total].into_boxed_slice()
}
pub fn metatable(&self) -> Option<Gc<Table>> {
self.metatable
}
pub fn set_metatable(&mut self, mt: Option<Gc<Table>>) {
self.metatable = mt;
}
pub(crate) fn internal_bytes(&self) -> usize {
let n = self.asize as usize;
let array_external = if n > INLINE_ASIZE as usize {
n + n * std::mem::size_of::<RawVal>()
} else {
0
};
let soa_external = self.keys.len() * std::mem::size_of::<Value>()
+ self.vals.len() * std::mem::size_of::<Value>()
+ self.meta.len() * std::mem::size_of::<u16>();
array_external + self.nodes.len() * std::mem::size_of::<Node>() + soa_external
}
fn asize(&self) -> usize {
self.asize as usize
}
fn aget(&self, idx: usize) -> Value {
unsafe {
Value::pack(
*self.atags().get_unchecked(idx),
*self.avals().get_unchecked(idx),
)
}
}
fn aset(&mut self, idx: usize, v: Value) {
let (t, b) = v.unpack();
unsafe {
*self.atags_mut().get_unchecked_mut(idx) = t;
*self.avals_mut().get_unchecked_mut(idx) = b;
}
}
pub fn get(&self, key: Value) -> Value {
match key {
Value::Int(i) => self.get_int(i),
Value::Float(f) => match f2i_exact(f) {
Some(i) => self.get_int(i),
None => {
if f.is_nan() {
Value::Nil
} else {
self.get_hash(key)
}
}
},
Value::Nil => Value::Nil,
k => self.get_hash(k),
}
}
pub fn get_int(&self, i: i64) -> Value {
if i >= 1 && (i as u64) <= self.asize() as u64 {
return self.aget(i as usize - 1);
}
self.get_hash(Value::Int(i))
}
#[inline]
pub fn get_str(&self, key: crate::runtime::Gc<crate::runtime::string::LuaStr>) -> Value {
self.get_hash(Value::Str(key))
}
fn get_hash(&self, k: Value) -> Value {
match self.find_node(k) {
Some(idx) => self.nodes[idx].val,
None => Value::Nil,
}
}
#[allow(dead_code)]
pub(crate) fn find_node_idx(&self, k: Value) -> Option<usize> {
self.find_node(k)
}
#[allow(dead_code)]
pub(crate) fn node_val_at(&self, idx: usize) -> Option<Value> {
self.nodes.get(idx).map(|n| n.val)
}
#[allow(dead_code)]
pub(crate) fn nodes_capacity(&self) -> usize {
self.nodes.len()
}
fn find_node(&self, k: Value) -> Option<usize> {
if self.nodes.is_empty() {
return None;
}
let mut idx = self.main_position(k);
loop {
let n = &self.nodes[idx];
if !n.dead_key && n.key.raw_eq(k) {
return Some(idx);
}
if n.next == NONE {
return None;
}
idx = n.next as usize;
}
}
pub fn set(&mut self, heap: &mut Heap, key: Value, val: Value) -> Result<(), TableError> {
let k = normalize_set_key(key)?;
self.set_norm(heap, k, val)
}
pub fn try_set_existing(&mut self, key: Value, val: Value) -> bool {
let k = match normalize_set_key(key) {
Ok(k) => k,
Err(_) => return false,
};
if let Value::Int(i) = k
&& i >= 1
&& (i as u64) <= self.asize() as u64
{
let idx = i as usize - 1;
let tag = unsafe { *self.atags().get_unchecked(idx) };
if tag != raw::NIL {
if val.is_nil() {
self.clear_existing_slot(k);
} else {
self.aset(idx, val);
}
return true;
}
return false;
}
if let Some(idx) = self.find_node(k)
&& !self.nodes[idx].val.is_nil()
{
if val.is_nil() {
self.clear_existing_slot(k);
} else {
self.nodes[idx].val = val;
}
return true;
}
false
}
fn clear_existing_slot(&mut self, k: Value) {
if let Value::Int(i) = k
&& i >= 1
&& (i as u64) <= self.asize() as u64
{
self.aset(i as usize - 1, Value::Nil);
return;
}
if let Some(idx) = self.find_node(k) {
self.nodes[idx].val = Value::Nil;
}
}
pub fn set_int(&mut self, heap: &mut Heap, i: i64, val: Value) -> Result<(), TableError> {
self.set_norm(heap, Value::Int(i), val)
}
fn set_norm(&mut self, heap: &mut Heap, k: Value, v: Value) -> Result<(), TableError> {
if let Value::Int(i) = k
&& i >= 1
&& (i as u64) <= self.asize() as u64
{
if v.is_nil() {
self.clear_existing_slot(k);
} else {
self.aset(i as usize - 1, v);
}
return Ok(());
}
if let Some(idx) = self.find_node(k) {
if v.is_nil() {
self.clear_existing_slot(k);
} else {
self.nodes[idx].val = v;
}
return Ok(());
}
if v.is_nil() {
return Ok(()); }
self.insert_new(heap, k, v)
}
fn insert_new(&mut self, heap: &mut Heap, k: Value, v: Value) -> Result<(), TableError> {
if self.nodes.is_empty() {
self.rehash(heap, k)?;
return self.set_norm(heap, k, v);
}
let mp = self.main_position(k);
if self.nodes[mp].key.is_nil() && !self.nodes[mp].dead_key {
self.nodes[mp] = Node {
key: k,
val: v,
next: NONE,
dead_key: false,
};
return Ok(());
}
let Some(free) = self.free_pos() else {
self.rehash(heap, k)?;
return self.set_norm(heap, k, v);
};
if self.nodes[mp].dead_key {
let preserved_next = self.nodes[mp].next;
self.nodes[mp] = Node {
key: k,
val: v,
next: preserved_next,
dead_key: false,
};
return Ok(());
}
let other_mp = self.main_position(self.nodes[mp].key);
if other_mp != mp {
let mut prev = other_mp;
while self.nodes[prev].next != mp as i32 {
prev = self.nodes[prev].next as usize;
}
self.nodes[prev].next = free as i32;
self.nodes[free] = self.nodes[mp];
self.nodes[mp] = Node {
key: k,
val: v,
next: NONE,
dead_key: false,
};
} else {
self.nodes[free] = Node {
key: k,
val: v,
next: self.nodes[mp].next,
dead_key: false,
};
self.nodes[mp].next = free as i32;
}
Ok(())
}
fn free_pos(&mut self) -> Option<usize> {
while self.lastfree > 0 {
self.lastfree -= 1;
let n = &self.nodes[self.lastfree as usize];
if n.key.is_nil() && !n.dead_key {
return Some(self.lastfree as usize);
}
}
None
}
fn rehash(&mut self, heap: &mut Heap, pending: Value) -> Result<(), TableError> {
let mut nums = [0usize; 65];
let mut int_keys = 0usize;
let mut total = 1; if let Value::Int(i) = pending
&& i >= 1
{
nums[ceil_log2(i as u64)] += 1;
int_keys += 1;
}
let atags = self.atags();
for (i, &tag) in atags.iter().enumerate() {
if tag != raw::NIL {
nums[ceil_log2(i as u64 + 1)] += 1;
int_keys += 1;
total += 1;
}
}
for n in self.nodes.iter() {
if !n.val.is_nil() {
total += 1;
if let Value::Int(i) = n.key
&& i >= 1
{
nums[ceil_log2(i as u64)] += 1;
int_keys += 1;
}
}
}
let mut new_asize = 0usize;
let mut in_array = 0usize;
let mut a = 0usize;
let mut two_to_i = 1usize;
let mut i = 0usize;
while int_keys > two_to_i / 2 {
a += nums[i];
if a > two_to_i / 2 {
new_asize = two_to_i;
in_array = a;
}
i += 1;
match two_to_i.checked_mul(2) {
Some(n) => two_to_i = n,
None => break,
}
}
if new_asize > MAX_ASIZE {
return Err(TableError::Overflow);
}
let hash_entries = total - in_array;
if hash_entries > MAX_ASIZE {
return Err(TableError::Overflow);
}
self.resize(heap, new_asize, hash_entries);
Ok(())
}
pub(crate) fn resize(&mut self, heap: &mut Heap, new_asize: usize, hash_entries: usize) {
let before = self.internal_bytes();
let old_asize = self.asize as usize;
let mut old_pairs: Vec<(u8, RawVal)> = Vec::with_capacity(old_asize);
if old_asize > 0 {
let avals_base = self.array_ptr as *const RawVal;
let atags_base = unsafe { self.array_ptr.add(old_asize * 8) as *const u8 };
for i in 0..old_asize {
let tag = unsafe { *atags_base.add(i) };
let val = unsafe { *avals_base.add(i) };
old_pairs.push((tag, val));
}
}
let old_nodes = std::mem::take(&mut self.nodes);
self.asize = new_asize as u64;
if new_asize <= INLINE_ASIZE as usize {
for slot in self.inline_storage.iter_mut() {
*slot = 0;
}
self.array_ptr = self.inline_storage.as_mut_ptr() as *mut u8;
self.slab = Box::new([]);
} else {
self.slab = Self::alloc_slab(new_asize);
self.array_ptr = self.slab.as_mut_ptr() as *mut u8;
}
let hsize = if hash_entries == 0 {
0
} else {
hash_entries.next_power_of_two()
};
self.nodes = vec![Node::EMPTY; hsize].into_boxed_slice();
self.lastfree = hsize as u32;
let after = self.internal_bytes();
heap.apply_bytes_delta(before, after);
for (i, (tag, val)) in old_pairs.into_iter().enumerate() {
if tag != raw::NIL {
let v = unsafe { Value::pack(tag, val) };
let _ = self.set_norm(heap, Value::Int(i as i64 + 1), v);
}
}
for n in old_nodes.iter() {
if !n.val.is_nil() {
let _ = self.set_norm(heap, n.key, n.val);
}
}
}
fn main_position(&self, k: Value) -> usize {
debug_assert!(!self.nodes.is_empty());
hash_key(k) as usize & (self.nodes.len() - 1)
}
#[allow(clippy::len_without_is_empty)]
pub fn len(&self) -> i64 {
let asize = self.asize();
let atags = self.atags();
if asize > 0 && atags[asize - 1] == raw::NIL {
let (mut lo, mut hi) = (0usize, asize);
while hi - lo > 1 {
let m = lo + (hi - lo) / 2;
if atags[m - 1] == raw::NIL {
hi = m;
} else {
lo = m;
}
}
return lo as i64;
}
if self.nodes.is_empty() {
return asize as i64;
}
let mut lo = asize as i64;
let mut hi = lo + 1;
while !self.get_int(hi).is_nil() {
lo = hi;
match hi.checked_mul(2) {
Some(n) => hi = n,
None => {
let mut i = 1i64;
while !self.get_int(i).is_nil() {
i += 1;
}
return i - 1;
}
}
}
while hi - lo > 1 {
let m = lo + (hi - lo) / 2;
if self.get_int(m).is_nil() {
hi = m;
} else {
lo = m;
}
}
lo
}
pub fn next(&self, key: Value) -> Result<Option<(Value, Value)>, TableError> {
let start = match key {
Value::Nil => 0,
k => {
let k = match k {
Value::Float(f) => match f2i_exact(f) {
Some(i) => Value::Int(i),
None => k,
},
k => k,
};
if let Value::Int(i) = k
&& i >= 1
&& (i as u64) <= self.asize() as u64
{
i as usize
} else {
match self.find_node(k) {
Some(idx) => self.asize() + idx + 1,
None => return Err(TableError::InvalidNext),
}
}
}
};
let atags = self.atags();
for i in start..self.asize() {
if atags[i] != raw::NIL {
return Ok(Some((Value::Int(i as i64 + 1), self.aget(i))));
}
}
let hstart = start.saturating_sub(self.asize());
for (idx, n) in self.nodes.iter().enumerate().skip(hstart) {
if !n.val.is_nil() {
let _ = idx;
return Ok(Some((n.key, n.val)));
}
}
Ok(None)
}
pub(crate) fn weak_mode(&self) -> (bool, bool) {
let Some(mt) = self.metatable else {
return (false, false);
};
for n in mt.nodes.iter() {
if let (Value::Str(k), Value::Str(mode)) = (n.key, n.val)
&& k.as_bytes() == b"__mode"
{
let b = mode.as_bytes();
return (b.contains(&b'k'), b.contains(&b'v'));
}
}
(false, false)
}
pub(crate) fn refs_contain_unmarked_coro(&self) -> bool {
use crate::runtime::heap::header_is_marked;
let atags = self.atags();
let avals = self.avals();
for (i, &tag) in atags.iter().enumerate() {
if tag == raw::CORO {
let p = unsafe { avals[i].co } as *mut crate::runtime::heap::GcHeader;
if !header_is_marked(p) {
return true;
}
}
}
for n in self.nodes.iter() {
if let Value::Coro(co) = n.key {
if !header_is_marked(co.as_ptr() as *mut crate::runtime::heap::GcHeader) {
return true;
}
}
if let Value::Coro(co) = n.val {
if !header_is_marked(co.as_ptr() as *mut crate::runtime::heap::GcHeader) {
return true;
}
}
}
false
}
pub(crate) fn trace(&self, m: &mut Marker) {
let (wk, wv) = self.weak_mode();
if wk || wv {
m.weak.push(self as *const Table as *mut Table);
}
let ephemeron = wk && !wv && !m.no_ephemeron;
if ephemeron {
m.ephemeron.push(self as *const Table as *mut Table);
}
if !wv {
let atags = self.atags();
let avals = self.avals();
for (i, &tag) in atags.iter().enumerate() {
if raw::is_gc(tag) {
m.value(unsafe { Value::pack(tag, avals[i]) });
}
}
}
for n in self.nodes.iter() {
if !wk {
m.value(n.key);
}
if !wv && !ephemeron {
m.value(n.val);
}
}
if let Some(mt) = self.metatable {
m.value(Value::Table(mt));
}
}
pub(crate) fn converge_ephemeron(&self, alive: &dyn Fn(Value) -> bool, m: &mut Marker) -> bool {
let mut changed = false;
for n in self.nodes.iter() {
if !n.val.is_nil() && alive(n.key) {
changed |= m.value(n.val);
}
}
changed
}
pub(crate) fn clear_weak(
&mut self,
wk: bool,
wv: bool,
is_dead: &dyn Fn(Value) -> bool,
mark_string: &dyn Fn(Value),
) {
if wv {
let n = self.asize as usize;
for i in 0..n {
let tag = self.atags()[i];
if raw::is_gc(tag) {
let v = unsafe { Value::pack(tag, self.avals()[i]) };
if is_dead(v) {
self.atags_mut()[i] = raw::NIL;
self.avals_mut()[i] = RawVal::NIL;
} else {
mark_string(v);
}
}
}
}
for n in self.nodes.iter_mut() {
if n.val.is_nil() {
continue;
}
let key_dead = wk && is_dead(n.key);
let val_dead = wv && is_dead(n.val);
if key_dead || val_dead {
n.val = Value::Nil;
if matches!(
n.key,
Value::Table(_)
| Value::Closure(_)
| Value::Native(_)
| Value::Coro(_)
| Value::Userdata(_)
| Value::Str(_)
) {
n.key = Value::Nil;
n.dead_key = true;
}
} else {
if wk {
mark_string(n.key);
}
if wv {
mark_string(n.val);
}
}
}
}
}
#[allow(dead_code)] pub(crate) const SOA_INITIAL_CAP: usize = 4;
#[allow(dead_code)]
const SOA_LOAD_NUM: usize = 3;
#[allow(dead_code)]
const SOA_LOAD_DEN: usize = 4;
#[allow(dead_code)]
const SOA_TOMB_NUM: usize = 1;
#[allow(dead_code)]
const SOA_TOMB_DEN: usize = 4;
#[allow(dead_code)] impl Table {
#[inline]
pub(crate) fn soa_cap(&self) -> usize {
self.meta.len()
}
#[cfg(test)]
pub(crate) fn soa_live_count(&self) -> usize {
self.meta.iter().filter(|&&m| meta_bits::is_live(m)).count()
}
#[inline]
fn soa_occupied_count(&self) -> usize {
self.meta
.iter()
.filter(|&&m| meta_bits::is_occupied(m))
.count()
}
pub(crate) fn soa_find_slot(&self, k: Value) -> Option<usize> {
let cap = self.meta.len();
if cap == 0 {
return None;
}
let mask = cap - 1;
let mut idx = (hash_key(k) as usize) & mask;
for _ in 0..cap {
let m = self.meta[idx];
if !meta_bits::is_occupied(m) {
return None;
}
if !meta_bits::is_tombstone(m) && self.keys[idx].raw_eq(k) {
return Some(idx);
}
idx = (idx + 1) & mask;
}
None
}
fn soa_rehash_to(&mut self, heap: &mut Heap, new_cap: usize) -> Result<(), TableError> {
debug_assert!(new_cap.is_power_of_two() && new_cap > 0);
let before = self.internal_bytes();
let mut survivors: Vec<(Value, Value)> = Vec::with_capacity(self.meta.len());
for i in 0..self.meta.len() {
if meta_bits::is_live(self.meta[i]) {
survivors.push((self.keys[i], self.vals[i]));
}
}
let mut cap = new_cap;
loop {
if cap > MAX_ASIZE {
return Err(TableError::Overflow);
}
self.keys = vec![Value::Nil; cap].into_boxed_slice();
self.vals = vec![Value::Nil; cap].into_boxed_slice();
self.meta = vec![meta_bits::EMPTY; cap].into_boxed_slice();
self.tombstones = 0;
let mut overflowed = false;
for (k, v) in survivors.iter().copied() {
if self.soa_place_known_absent(k, v).is_err() {
overflowed = true;
break;
}
}
if !overflowed {
break;
}
cap = cap.checked_mul(2).ok_or(TableError::Overflow)?;
}
let after = self.internal_bytes();
heap.apply_bytes_delta(before, after);
Ok(())
}
fn soa_place_known_absent(&mut self, k: Value, v: Value) -> Result<usize, (Value, Value)> {
let cap = self.meta.len();
debug_assert!(cap > 0);
let mask = cap - 1;
let landing = (hash_key(k) as usize) & mask;
let mut idx = landing;
let mut cur_psl: u16 = 0;
let mut cur_key = k;
let mut cur_val = v;
let mut placed_at: Option<usize> = None;
for _ in 0..cap {
let m = self.meta[idx];
if !meta_bits::is_occupied(m) || meta_bits::is_tombstone(m) {
if meta_bits::is_tombstone(m) {
self.tombstones = self.tombstones.saturating_sub(1);
}
self.meta[idx] = meta_bits::pack(cur_psl, false);
self.keys[idx] = cur_key;
self.vals[idx] = cur_val;
return Ok(placed_at.unwrap_or(idx));
}
let stored_psl = meta_bits::psl(m);
if cur_psl > stored_psl {
std::mem::swap(&mut cur_key, &mut self.keys[idx]);
std::mem::swap(&mut cur_val, &mut self.vals[idx]);
self.meta[idx] = meta_bits::pack(cur_psl, false);
if placed_at.is_none() {
placed_at = Some(idx);
}
cur_psl = stored_psl;
}
idx = (idx + 1) & mask;
cur_psl = cur_psl.saturating_add(1);
if cur_psl > meta_bits::PSL_MAX {
return Err((cur_key, cur_val));
}
}
Err((cur_key, cur_val))
}
fn soa_grow_if_needed(&mut self, heap: &mut Heap) -> Result<(), TableError> {
if self.iter_depth > 0 {
return Ok(());
}
let cap = self.meta.len();
if cap == 0 {
return self.soa_rehash_to(heap, SOA_INITIAL_CAP);
}
let occupied = self.soa_occupied_count();
if occupied * SOA_LOAD_DEN >= cap * SOA_LOAD_NUM {
let new_cap = cap.checked_mul(2).ok_or(TableError::Overflow)?;
return self.soa_rehash_to(heap, new_cap);
}
if self.tombstones as usize * SOA_TOMB_DEN >= cap * SOA_TOMB_NUM {
return self.soa_rehash_to(heap, cap);
}
Ok(())
}
pub(crate) fn soa_insert(
&mut self,
heap: &mut Heap,
k: Value,
v: Value,
) -> Result<(), TableError> {
debug_assert!(!matches!(k, Value::Nil));
if let Some(idx) = self.soa_find_slot(k) {
self.vals[idx] = v;
return Ok(());
}
self.soa_grow_if_needed(heap)?;
match self.soa_place_known_absent(k, v) {
Ok(_) => Ok(()),
Err(homeless) => {
let cap = self.meta.len();
let new_cap = cap.checked_mul(2).ok_or(TableError::Overflow)?;
self.soa_rehash_with_extra(heap, new_cap, homeless)
}
}
}
fn soa_rehash_with_extra(
&mut self,
heap: &mut Heap,
new_cap: usize,
extra: (Value, Value),
) -> Result<(), TableError> {
let before = self.internal_bytes();
let mut survivors: Vec<(Value, Value)> = Vec::with_capacity(self.meta.len() + 1);
for i in 0..self.meta.len() {
if meta_bits::is_live(self.meta[i]) {
survivors.push((self.keys[i], self.vals[i]));
}
}
if !survivors.iter().any(|(k, _)| k.raw_eq(extra.0)) {
survivors.push(extra);
}
let mut cap = new_cap;
loop {
if cap > MAX_ASIZE {
return Err(TableError::Overflow);
}
self.keys = vec![Value::Nil; cap].into_boxed_slice();
self.vals = vec![Value::Nil; cap].into_boxed_slice();
self.meta = vec![meta_bits::EMPTY; cap].into_boxed_slice();
self.tombstones = 0;
let mut overflowed = false;
for (k, v) in survivors.iter().copied() {
if self.soa_place_known_absent(k, v).is_err() {
overflowed = true;
break;
}
}
if !overflowed {
break;
}
cap = cap.checked_mul(2).ok_or(TableError::Overflow)?;
}
let after = self.internal_bytes();
heap.apply_bytes_delta(before, after);
Ok(())
}
pub(crate) fn soa_get(&self, k: Value) -> Value {
match self.soa_find_slot(k) {
Some(idx) => self.vals[idx],
None => Value::Nil,
}
}
pub(crate) fn soa_delete(&mut self, k: Value) -> bool {
if let Some(idx) = self.soa_find_slot(k) {
let psl = meta_bits::psl(self.meta[idx]);
self.meta[idx] = meta_bits::pack(psl, true);
self.keys[idx] = Value::Nil;
self.vals[idx] = Value::Nil;
self.tombstones = self.tombstones.saturating_add(1);
true
} else {
false
}
}
}
fn normalize_set_key(key: Value) -> Result<Value, TableError> {
match key {
Value::Nil => Err(TableError::NilIndex),
Value::Float(f) => match f2i_exact(f) {
Some(i) => Ok(Value::Int(i)),
None if f.is_nan() => Err(TableError::NanIndex),
None => Ok(key),
},
k => Ok(k),
}
}
fn hash_key(k: Value) -> u64 {
match k {
Value::Int(i) => i as u64, Value::Float(f) => mix64(f.to_bits()),
Value::Bool(b) => b as u64 + 1,
Value::Str(s) => s.hash() as u64,
Value::Table(t) => mix64(t.as_ptr() as u64),
Value::Closure(c) => mix64(c.as_ptr() as u64),
Value::Native(n) => mix64(n.as_ptr() as u64),
Value::Coro(co) => mix64(co.as_ptr() as u64),
Value::Userdata(u) => mix64(u.as_ptr() as u64),
Value::LightUserdata(p) => mix64(p as u64),
Value::Nil => 0, }
}
fn mix64(mut x: u64) -> u64 {
x ^= x >> 30;
x = x.wrapping_mul(0xbf58_476d_1ce4_e5b9);
x ^= x >> 27;
x = x.wrapping_mul(0x94d0_49bb_1331_11eb);
x ^ (x >> 31)
}
fn ceil_log2(k: u64) -> usize {
(u64::BITS - (k - 1).leading_zeros()) as usize
}
impl Table {
pub fn ensure_array(&mut self, heap: &mut Heap, n: usize) {
if n > self.asize() {
let hash_entries = self.nodes.iter().filter(|nd| !nd.val.is_nil()).count();
self.resize(heap, n, hash_entries);
}
}
}
impl Table {
pub fn ensure_hash(&mut self, heap: &mut Heap, n: usize) {
let entries = self.nodes.iter().filter(|nd| !nd.val.is_nil()).count();
if n > self.nodes.len() {
self.resize(heap, self.asize(), n.max(entries));
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::runtime::heap::Heap;
fn with_table(f: impl FnOnce(&mut Heap, &mut Table)) {
let mut heap = Heap::new();
let t = heap.new_table();
f(&mut heap, unsafe { t.as_mut() });
}
fn assert_is_border(t: &Table, n: i64) {
if n == 0 {
assert!(t.get_int(1).is_nil(), "border 0 but t[1] non-nil");
} else {
assert!(!t.get_int(n).is_nil(), "border {n} but t[{n}] is nil");
assert!(
t.get_int(n + 1).is_nil(),
"border {n} but t[{}] non-nil",
n + 1
);
}
}
#[test]
#[allow(clippy::assertions_on_constants)]
#[cfg(target_pointer_width = "64")]
fn phase_1i_b_node_layout_pinned() {
use jit_layout::*;
assert_eq!(std::mem::size_of::<Box<[Node]>>(), 16);
assert_eq!(NODE_KEY_OFFSET, 0);
assert_eq!(NODE_VAL_OFFSET, 16);
assert!(SIZEOF_NODE >= 32);
let b: Box<[Node]> = vec![Node::EMPTY; 4].into_boxed_slice();
let raw_ptr = b.as_ptr();
let raw_len = b.len();
let words: [usize; 2] = unsafe { std::mem::transmute_copy(&b) };
assert_eq!(words[0], raw_ptr as usize, "fat-ptr low word = data ptr");
assert_eq!(words[1], raw_len, "fat-ptr high word = len");
drop(b);
}
#[test]
fn sequence_grows_into_array() {
with_table(|heap, t| {
for i in 1..=1000 {
let _ = t.set_int(heap, i, Value::Int(i * 10));
}
for i in 1..=1000 {
assert!(t.get_int(i).raw_eq(Value::Int(i * 10)));
}
assert_eq!(t.len(), 1000);
});
}
#[test]
fn string_and_mixed_keys() {
with_table(|heap, t| {
let k1 = Value::Str(heap.intern(b"alpha"));
let k2 = Value::Str(heap.intern(b"beta"));
t.set(heap, k1, Value::Int(1)).unwrap();
t.set(heap, k2, Value::Int(2)).unwrap();
t.set(heap, Value::Bool(true), Value::Int(3)).unwrap();
t.set(heap, Value::Int(-5), Value::Int(4)).unwrap();
let k1b = Value::Str(heap.intern(b"alpha"));
assert!(t.get(k1b).raw_eq(Value::Int(1)));
assert!(t.get(k2).raw_eq(Value::Int(2)));
assert!(t.get(Value::Bool(true)).raw_eq(Value::Int(3)));
assert!(t.get(Value::Int(-5)).raw_eq(Value::Int(4)));
assert!(t.get(Value::Str(heap.intern(b"gamma"))).is_nil());
});
}
#[test]
fn float_keys_normalize_to_int() {
with_table(|heap, t| {
t.set(heap, Value::Float(2.0), Value::Int(22)).unwrap();
assert!(t.get(Value::Int(2)).raw_eq(Value::Int(22)));
t.set(heap, Value::Int(3), Value::Int(33)).unwrap();
assert!(t.get(Value::Float(3.0)).raw_eq(Value::Int(33)));
t.set(heap, Value::Float(-0.0), Value::Int(0)).unwrap();
assert!(t.get(Value::Int(0)).raw_eq(Value::Int(0)));
t.set(heap, Value::Float(0.5), Value::Int(55)).unwrap();
assert!(t.get(Value::Float(0.5)).raw_eq(Value::Int(55)));
assert!(t.get(Value::Int(0)).raw_eq(Value::Int(0)));
});
}
#[test]
fn bad_keys() {
with_table(|heap, t| {
assert_eq!(
t.set(heap, Value::Nil, Value::Int(1)),
Err(TableError::NilIndex)
);
assert_eq!(
t.set(heap, Value::Float(f64::NAN), Value::Int(1)),
Err(TableError::NanIndex)
);
assert!(t.get(Value::Nil).is_nil());
assert!(t.get(Value::Float(f64::NAN)).is_nil());
});
}
#[test]
fn delete_and_reinsert() {
with_table(|heap, t| {
let k = Value::Str(heap.intern(b"k"));
t.set(heap, k, Value::Int(1)).unwrap();
t.set(heap, k, Value::Nil).unwrap();
assert!(t.get(k).is_nil());
t.set(heap, k, Value::Int(2)).unwrap();
assert!(t.get(k).raw_eq(Value::Int(2)));
let k2 = Value::Str(heap.intern(b"k2"));
t.set(heap, k2, Value::Nil).unwrap();
assert!(t.get(k2).is_nil());
});
}
#[test]
fn borders_with_holes() {
with_table(|heap, t| {
let _ = t.set_int(heap, 1, Value::Int(1));
let _ = t.set_int(heap, 2, Value::Int(2));
assert_eq!(t.len(), 2);
t.set_int(heap, 2, Value::Nil).unwrap();
assert_is_border(t, t.len());
let _ = t.set_int(heap, 1_000_000, Value::Int(1));
assert_is_border(t, t.len());
});
}
#[test]
fn len_on_empty_and_hash_only() {
with_table(|heap, t| {
assert_eq!(t.len(), 0);
let xk = Value::Str(heap.intern(b"x"));
t.set(heap, xk, Value::Int(1)).unwrap();
assert_eq!(t.len(), 0);
});
}
#[test]
fn next_iterates_everything_exactly_once() {
with_table(|heap, t| {
let mut expected = 0i64;
for i in 1..=64 {
let _ = t.set_int(heap, i, Value::Int(i));
expected += i;
}
for i in 0..32 {
let k = Value::Str(heap.intern(format!("s{i}").as_bytes()));
t.set(heap, k, Value::Int(1000 + i)).unwrap();
expected += 1000 + i;
}
t.set(heap, Value::Float(2.5), Value::Int(7)).unwrap();
expected += 7;
let mut sum = 0i64;
let mut count = 0;
let mut key = Value::Nil;
while let Some((k, v)) = t.next(key).unwrap() {
let Value::Int(x) = v else {
panic!("bad value")
};
sum += x;
count += 1;
key = k;
}
assert_eq!(count, 64 + 32 + 1);
assert_eq!(sum, expected);
});
}
#[test]
fn next_skips_nil_values_and_rejects_alien_keys() {
with_table(|heap, t| {
let _ = t.set_int(heap, 1, Value::Int(1));
let _ = t.set_int(heap, 3, Value::Int(3));
let k = Value::Str(heap.intern(b"gone"));
t.set(heap, k, Value::Int(9)).unwrap();
t.set(heap, k, Value::Nil).unwrap();
let mut seen = Vec::new();
let mut key = Value::Nil;
while let Some((k, v)) = t.next(key).unwrap() {
let Value::Int(x) = v else { panic!() };
seen.push(x);
key = k;
}
assert_eq!(seen, vec![1, 3]);
let alien = Value::Str(heap.intern(b"never"));
assert!(matches!(t.next(alien), Err(TableError::InvalidNext)));
assert!(t.next(k).is_ok());
});
}
#[test]
fn collision_relocation_keeps_chains_intact() {
with_table(|heap, t| {
for i in 0..512 {
let _ = t.set_int(heap, -i, Value::Int(i));
}
for i in 0..512 {
assert!(t.get_int(-i).raw_eq(Value::Int(i)), "lost key {}", -i);
}
});
}
fn replay_chain(heap: &mut Heap, ops: &[(Value, Value)]) -> *mut Table {
let t = heap.new_table();
let tref = unsafe { t.as_mut() };
for (k, v) in ops.iter().copied() {
tref.set(heap, k, v).unwrap();
}
t.as_ptr()
}
fn replay_soa(heap: &mut Heap, ops: &[(Value, Value)]) -> *mut Table {
let t = heap.new_table();
let tref = unsafe { t.as_mut() };
for (k, v) in ops.iter().copied() {
tref.soa_insert(heap, k, v).unwrap();
}
t.as_ptr()
}
#[test]
fn c3_soa_equivalence_string_keys() {
let mut heap = Heap::new();
let mut ops = Vec::new();
for i in 0..40 {
let k = Value::Str(heap.intern(format!("key_{i:03}").as_bytes()));
ops.push((k, Value::Int(i * 7)));
}
let chain = unsafe { &*replay_chain(&mut heap, &ops) };
let soa = unsafe { &*replay_soa(&mut heap, &ops) };
for (k, _) in &ops {
let cv = chain.get(*k);
let sv = soa.soa_get(*k);
assert!(
cv.raw_eq(sv),
"SoA vs chain mismatch on key — chain={:?} soa={:?}",
cv,
sv,
);
}
let absent = Value::Str(heap.intern(b"never"));
assert!(chain.get(absent).is_nil());
assert!(soa.soa_get(absent).is_nil());
}
#[test]
fn c3_soa_equivalence_negative_int_keys() {
let mut heap = Heap::new();
let mut ops = Vec::new();
for i in 0..256 {
let k = Value::Int(-i);
ops.push((k, Value::Int(i)));
}
let chain = unsafe { &*replay_chain(&mut heap, &ops) };
let soa = unsafe { &*replay_soa(&mut heap, &ops) };
for (k, _) in &ops {
let cv = chain.get(*k);
let sv = soa.soa_get(*k);
assert!(cv.raw_eq(sv), "SoA mismatch on key {:?}", k);
}
}
#[test]
fn c3_soa_equivalence_mixed_keys_with_updates() {
let mut heap = Heap::new();
let kstr = Value::Str(heap.intern(b"x"));
let kint = Value::Int(42);
let kbool = Value::Bool(true);
let ops: Vec<(Value, Value)> = vec![
(kstr, Value::Int(1)),
(kint, Value::Int(2)),
(kbool, Value::Int(3)),
(kstr, Value::Int(11)), (kint, Value::Int(22)), (kbool, Value::Int(33)), ];
let chain = unsafe { &*replay_chain(&mut heap, &ops) };
let soa = unsafe { &*replay_soa(&mut heap, &ops) };
for k in [kstr, kint, kbool] {
assert!(chain.get(k).raw_eq(soa.soa_get(k)));
}
}
#[test]
fn c3_soa_equivalence_delete_then_read() {
let mut heap = Heap::new();
let mut ops_insert = Vec::new();
for i in 0..30 {
let k = Value::Str(heap.intern(format!("d_key_{i:03}").as_bytes()));
ops_insert.push((k, Value::Int(i * 11)));
}
let chain = unsafe { &mut *replay_chain(&mut heap, &ops_insert) };
let soa = unsafe { &mut *replay_soa(&mut heap, &ops_insert) };
let mut deleted: Vec<Value> = Vec::new();
for (i, (k, _)) in ops_insert.iter().enumerate() {
if i % 3 == 0 {
chain.set(&mut heap, *k, Value::Nil).unwrap();
let was_present = soa.soa_delete(*k);
assert!(was_present, "soa_delete miss on inserted key {:?}", k);
deleted.push(*k);
}
}
for (k, v) in &ops_insert {
let cv = chain.get(*k);
let sv = soa.soa_get(*k);
assert!(
cv.raw_eq(sv),
"delete/read mismatch on key {:?} — chain={:?} soa={:?}",
k,
cv,
sv,
);
if deleted.iter().any(|d| d.raw_eq(*k)) {
assert!(cv.is_nil(), "deleted key {:?} chain non-nil", k);
assert!(sv.is_nil(), "deleted key {:?} soa non-nil", k);
} else {
assert!(cv.raw_eq(*v), "live key {:?} chain val drift", k);
}
}
let absent = Value::Str(heap.intern(b"never_d"));
assert!(!soa.soa_delete(absent));
}
#[test]
fn c3_soa_delete_then_reinsert_uses_tombstone() {
let mut heap = Heap::new();
let t = heap.new_table();
let tref = unsafe { t.as_mut() };
let k = Value::Str(heap.intern(b"reinsert_target"));
tref.soa_insert(&mut heap, k, Value::Int(100)).unwrap();
assert!(tref.soa_get(k).raw_eq(Value::Int(100)));
let pre_tombs = tref.tombstones;
assert!(tref.soa_delete(k));
assert!(tref.tombstones == pre_tombs + 1);
assert!(tref.soa_get(k).is_nil());
tref.soa_insert(&mut heap, k, Value::Int(200)).unwrap();
assert!(tref.soa_get(k).raw_eq(Value::Int(200)));
assert_eq!(tref.tombstones, pre_tombs);
}
#[test]
fn c3_soa_grows_under_load_pressure() {
let mut heap = Heap::new();
let t = heap.new_table();
let tref = unsafe { t.as_mut() };
for i in 0..1024 {
let k = Value::Str(heap.intern(format!("entry_{i:05}").as_bytes()));
tref.soa_insert(&mut heap, k, Value::Int(i)).unwrap();
}
for i in 0..1024 {
let k = Value::Str(heap.intern(format!("entry_{i:05}").as_bytes()));
let v = tref.soa_get(k);
assert!(
v.raw_eq(Value::Int(i)),
"SoA lost key entry_{:05} — got {:?}",
i,
v,
);
}
assert!(tref.soa_live_count() == 1024);
assert!(
tref.soa_cap() >= 2048,
"SoA cap = {} after 1024 inserts — load gate didn't grow",
tref.soa_cap(),
);
}
#[test]
fn rehash_redistributes_into_array() {
with_table(|heap, t| {
for i in (1..=256).rev() {
let _ = t.set_int(heap, i, Value::Int(i));
}
assert_eq!(t.len(), 256);
for i in 1..=256 {
assert!(t.get_int(i).raw_eq(Value::Int(i)));
}
});
}
}