Mars/src/table.rs

447 lines
15 KiB
Rust

use crate::gc::{Gc, Traverse};
use crate::vm::{Callable, RunError, Value};
use std::rc::Rc;
// I've already spent 2 months on this interpreter, and I'm tired, so I've cut a few corners...
/* See some of Lua's notes on this table: https://www.lua.org/source/5.5/ltable.c.html
This table uses an 'open-addressed' hashmap https://en.wikipedia.org/wiki/Open_addressing
where collisions are handled by 'linear-probing' and something I call 'displacement'.
Motivation:
Afaik, without having some mechanism that records or ensures known proximity between a displaced element
that collided with other(s) in the hashmap and it's original home (at element's hash's index),
the entire array may have to be checked just to see if an element is present. How can we fix this?
Solution:
Lua uses Brent's method, which I couldn't find a concrete explanation of, here I'm just recording
the maximum displacement that will need to be linearly-probed from the home entry to be certain of
any 'home' element's presence in the greater array.
Notes:
- If this value becomes large, the load factor is probably high and there would be a resize.
- Values would only need to be shuffled around closer to their 'home' during a resize.
- It is unlikely the displacement would large value without a resize amending that problem.
*/
type Displacement = u8;
#[cfg(feature = "bighash")]
pub type Hashed = u64;
#[cfg(not(feature = "bighash"))]
pub type Hashed = u32;
pub trait Equivalent<T>: Sized {
fn matches(&self, other: &T) -> bool;
}
impl<T> Equivalent<T> for T
where
T: PartialEq,
{
fn matches(&self, other: &T) -> bool {
self == other
}
}
pub trait Hashes {
fn hashed(&self) -> Hashed;
}
pub trait TableEntry: Equivalent<Self> + Hashes {
fn new_vacant() -> Self;
fn is_vacant(&self) -> bool;
}
#[derive(Debug, Clone)]
struct KeyValue {
index: Value,
value: Value,
}
impl Equivalent<Self> for KeyValue {
fn matches(&self, other: &Self) -> bool {
self.index == other.index
}
}
impl Hashes for KeyValue {
fn hashed(&self) -> Hashed {
self.index.hashed()
}
}
impl TableEntry for KeyValue {
fn new_vacant() -> Self {
KeyValue {
index: Value::Nil,
value: Value::Nil,
}
}
fn is_vacant(&self) -> bool {
matches!(
self,
KeyValue {
index: Value::Nil,
..
}
)
}
}
#[derive(Debug, Clone)]
struct Entry<E> {
entry: E,
home: usize,
displacement: Displacement,
}
#[derive(Clone, Debug)]
pub struct Map<E: TableEntry + Clone> {
// default representing vacancy!
map: Vec<Entry<E>>,
map_bounds: std::ops::Range<usize>,
map_count: usize, // number of elements in table
}
impl<E: TableEntry + Clone> Map<E> {
pub(crate) fn string_cache() { // todo: this isn't a clean way, maybe make a generic Map and use a specific one for the cache and the rest of the language
}
fn exchange_table(&mut self, len: usize) {
let old = std::mem::replace(
&mut self.map,
vec![
Entry {
entry: E::new_vacant(),
home: 0,
displacement: 0,
};
len
],
);
for Entry { entry, .. } in old {
if entry.is_vacant() {
self.set_table(entry)
}
}
}
pub fn resize_table(&mut self, len: usize) {
self.exchange_table(len.max(self.map_count + (self.map_count / 3)));
self.map_bounds = 0..self.table_upper();
}
fn table_upper(&self) -> usize {
(self.map.len() / 4) * 3
}
fn table_lower(&self) -> usize {
self.map.len() / 3
}
fn ensure_table(&mut self) {
if self.map_count + 1 > self.table_upper() {
// free space is short
self.exchange_table((self.map.len() + 4) * 2);
} else if self.map_count < self.table_lower() {
// too much free space
self.exchange_table(self.map.len() / 2)
}
self.map_bounds = self.table_lower()..self.table_upper()
}
pub(crate) fn set_table(&mut self, entry: E) {
#[cfg(feature = "assertions")]
assert!(!entry.is_vacant());
if !self.map_bounds.contains(&(self.map_count + 1)) {
// make sure the table is appropriately sized
self.ensure_table();
}
let range = self.map.len();
let home = entry.hashed() as usize % range;
if self.map[home].entry.is_vacant() {
// attempt to place it directly in an empty space
// INSERT
self.map[home].home = home;
self.map[home].entry = entry;
self.map[home].displacement = self.map[home].displacement.max(0);
self.map_count += 1;
} else if self.map[home].entry.matches(&entry) {
// attempt to replace it directly
// REPLACE
self.map[home].entry = entry; // home.item.soft_drop()
} else {
// attempt to replace it in a collided neighbour location
for i in 1..self.map[home].displacement + 1 {
let neighbour = &mut self.map[(home + i as usize) % range]; // todo: is mod expensive?
if neighbour.entry.matches(&entry) {
// found where it was displaced to
// REPLACE
neighbour.entry = entry;
return;
}
}
// at this point it must be added, probe to place in an empty space
for j in self.map[home].displacement + 1..Displacement::MAX - 1 {
let neighbour = &mut self.map[(home + j as usize) % range];
if neighbour.entry.is_vacant() {
// new empty slot hooray!
// INSERT
neighbour.entry = entry;
neighbour.home = home;
self.map[home].displacement = j;
self.map_count += 1;
return;
}
}
// impossible but this still needs to be complete
#[cfg(feature = "messages")]
eprintln!("Large table collision, are the hashes ok?\n\tBrute-force probing...");
let mut free: Option<usize> = None;
for k in 0..self.map.len() {
let neighbour = &mut self.map[k];
if neighbour.entry.is_vacant() && free.is_none() {
free = Some(k);
} else if neighbour.entry.matches(&entry) {
// REPLACE
neighbour.entry = entry;
return;
}
}
if let Some(k) = free {
// INSERT
self.map[k].home = home;
self.map[k].entry = entry;
self.map[home].displacement = Displacement::MAX;
self.map_count += 1;
return;
}
// must resize
#[cfg(feature = "messages")]
eprintln!("\n\tResizing...");
self.resize_table((self.map.len() + 4) * 2);
self.set_table(entry);
}
}
pub(crate) fn rem_table<K>(&mut self, index: K)
where
E: Equivalent<K>,
K: Hashes,
{
if self.map.len() == 0 {
return;
}
let range = self.map.len();
let home = index.hashed() as usize % range;
if self.map[home].entry.matches(&index) {
self.map[home].entry = E::new_vacant();
self.map_count -= 1;
} else {
if self.map[home].displacement != Displacement::MAX {
let mut largest = 0;
for i in 1..self.map[home].displacement {
let neighbour = &mut self.map[(home + i as usize) % range];
if neighbour.entry.matches(&index) {
neighbour.entry = E::new_vacant();
self.map_count -= 1;
if i == self.map[home].displacement {
self.map[home].displacement = largest
}
return;
} else if !neighbour.entry.is_vacant() && neighbour.home == home {
largest = i;
}
}
} else {
let mut largest = 0;
let mut finished = false; // what
for k in 0..self.map.len() {
let neighbour = &mut self.map[k];
if neighbour.entry.matches(&index) {
neighbour.entry = E::new_vacant();
self.map_count -= 1;
#[cfg(feature = "assertions")]
assert!(!finished);
finished = true;
}
if neighbour.home == home && !neighbour.entry.is_vacant() {
largest = largest.max(if k < home {
k + self.map.len() - home - 1 // ?
} else {
k - home
})
}
}
self.map[home].displacement = if largest > Displacement::MAX as usize {
Displacement::MAX
} else {
largest as Displacement
}
}
}
}
pub(crate) fn get_table<K>(&mut self, index: K) -> Option<&E>
where
E: Equivalent<K>,
K: Hashes,
{
if self.map.len() == 0 {
return None;
}
let location = index.hashed() as usize % self.map.len();
let home = &self.map[location];
if home.entry.matches(&index) {
Some(&home.entry)
} else {
for i in 1..home.displacement as usize {
let neighbour = &self.map[location + i];
if neighbour.entry.matches(&index) {
return Some(&neighbour.entry);
}
}
None
}
}
pub(crate) fn new() -> Map<E> {
Map {
map: Vec::new(),
map_count: 0,
map_bounds: (0..0).into(),
}
}
}
impl Hashes for Value {
fn hashed(&self) -> Hashed {
match self {
Value::Nil => 0,
Value::Bool(boolean) => {
if *boolean {
1
} else {
0
}
}
Value::CachedString(cached) => cached.1,
Value::String(_string, hash) => *hash,
Value::Function(callable) => match callable {
Callable::Rust(native) => Rc::as_ptr(native).addr() as Hashed,
Callable::Mars(closure) => closure.addr() as Hashed,
},
Value::Integer(integer) => *integer as Hashed,
Value::Number(number) => {
if number.is_nan() {
0
} else {
number.to_bits() as Hashed
}
}
Value::Table(table) => table.addr() as Hashed,
Value::Object(object) => object.addr() as Hashed,
#[cfg(feature = "vector3")]
Value::Vector(vec) => {
let mut hasher = DefaultHasher::new();
vec.as_u64vec3().hash(&mut hasher);
hasher.finish() as usize
}
}
}
}
// todo: displaced items don't ever get shuffled closer to their homes unless the table is resized
#[derive(Debug, Clone)]
pub struct Table {
table: Map<KeyValue>,
array: Vec<Value>,
pub meta: Option<Gc<Table>>,
}
impl Traverse for Table {
fn traverse(&self) {
for Entry { entry, .. } in self.table.map.iter() {
if !entry.is_vacant() {
entry.index.traverse();
entry.value.traverse();
}
}
for item in self.array.iter() {
item.traverse()
}
}
}
impl Equivalent<Value> for KeyValue {
fn matches(&self, other: &Value) -> bool {
self.index == *other
}
}
impl Table {
pub fn resize_array(&mut self, len: usize) {
self.array.resize(len, Value::Nil)
}
pub fn set(&mut self, index: Value, value: Value) -> Result<(), RunError> {
match index {
Value::Integer(index) => {
// This is an integer index, try the array first
match value {
Value::Nil => {
if (0..self.array.len() + 1).contains(&(index as usize)) {
if self.array.len() == index as usize {
self.array.pop();
} else {
self.array[index as usize + 1] = Value::Nil
}
}
self.table.rem_table(Value::Integer(index));
}
item => {
if (0..self.array.len() + 1).contains(&(index as usize)) {
if self.array.len() == index as usize {
self.array.push(item)
} else {
self.array[index as usize + 1] = item;
}
} else {
self.table.set_table(KeyValue {
index: Value::Integer(index),
value: item,
})
}
}
}
Ok(())
}
Value::Nil => Err(RunError(
"Attempt to set new index of table with key: nil".to_string(),
)),
index => Ok(self.table.set_table(KeyValue { index, value })),
}
}
pub fn get(&mut self, index: Value) -> Result<Value, RunError> {
match index {
Value::Integer(index) => Ok(self
.array
.get(index as usize - 1)
.unwrap_or(&Value::Nil)
.clone()),
Value::Nil => Err(RunError("Attempt to index table with key: nil".to_string())),
index => {
// todo: move this into the Map
Ok(self
.table
.get_table(index)
.unwrap_or(&KeyValue {
index: Value::Nil,
value: Value::Nil,
})
.value
.clone())
}
}
}
pub fn append(&mut self, item: Value) {
self.array.push(item)
}
pub fn new() -> Self {
Table {
table: Map::new(),
array: Vec::new(),
meta: None,
}
}
}