Refactored Table into Map for shared use in Compiler and VM. Refactored and modified direct equality for Values. Cleaned a core dump file. Builds and runs.

This commit is contained in:
paladin 2026-08-28 03:08:00 +01:00
parent 44b389a6a1
commit c10d56c0f8
5 changed files with 730 additions and 374 deletions

View file

@ -1,5 +1,5 @@
use crate::gc::{Gc, Traverse};
use crate::{Callable, RunError, Value};
use crate::{Callable, Object, 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...
@ -24,57 +24,298 @@ Notes:
*/
type Displacement = u8;
pub type Hashed = usize;
#[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 Entry {
struct KeyValue {
index: Value,
item: 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,
}
impl Default for Entry {
fn default() -> Self {
Entry {
index: Value::Nil,
item: Value::Nil,
#[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(),
}
}
}
pub fn hash(value: &Value) -> Hashed {
match value {
Value::Nil => 0,
Value::Bool(boolean) => {
if *boolean {
1
} else {
0
impl Hashes for Value {
fn hashed(&self) -> Hashed {
match self {
Value::Nil => 0,
Value::Bool(boolean) => {
if *boolean {
1
} else {
0
}
}
}
Value::String(_string, hash) => *hash,
Value::Function(callable) => match callable {
Callable::Rust(native) => Rc::as_ptr(native).addr(),
Callable::Mars(closure) => closure.addr(),
},
Value::Integer(integer) => *integer as usize,
Value::Number(number) => {
if number.is_nan() {
0
} else {
number.to_bits() as usize
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
}
}
Value::Table(table) => table.addr(),
Value::Object(object) => object.addr(),
#[cfg(feature = "vector3")]
Value::Vector(vec) => {
let mut hasher = DefaultHasher::new();
vec.as_u64vec3().hash(&mut hasher);
hasher.finish() as usize
}
}
}
@ -82,19 +323,17 @@ pub fn hash(value: &Value) -> Hashed {
// todo: displaced items don't ever get shuffled closer to their homes unless the table is resized
#[derive(Debug, Clone)]
pub struct Table {
table: Vec<Entry>,
table: Map<KeyValue>,
array: Vec<Value>,
table_bounds: std::ops::Range<usize>,
table_count: usize, // number of elements in table
pub meta: Option<Gc<Table>>,
}
impl Traverse for Table {
fn traverse(&self) {
for Entry { index, item, .. } in self.table.iter() {
if !matches!(index, Value::Nil) {
index.traverse();
item.traverse();
for Entry { entry, .. } in self.table.map.iter() {
if !entry.is_vacant() {
entry.index.traverse();
entry.value.traverse();
}
}
for item in self.array.iter() {
@ -103,174 +342,21 @@ impl Traverse for Table {
}
}
impl Equivalent<Value> for KeyValue {
fn matches(&self, other: &Value) -> bool {
self.index == *other
}
}
impl Table {
fn exchange_table(&mut self, len: usize) {
let old = std::mem::replace(&mut self.table, vec![Entry::default(); len]);
for Entry { index, item, .. } in old {
if index != Value::Nil {
self.set_table(index, item)
}
}
}
pub fn resize_table(&mut self, len: usize) {
self.exchange_table(len.max(self.table_count + (self.table_count / 3)));
self.table_bounds = 0..self.table_upper();
}
pub fn resize_array(&mut self, len: usize) {
self.array.resize(len, Value::Nil)
}
fn table_upper(&self) -> usize {
(self.table.len() / 4) * 3
}
fn table_lower(&self) -> usize {
self.table.len() / 3
}
fn ensure_table(&mut self) {
if self.table_count > self.table_upper() {
// free space is short
self.exchange_table((self.table.len() + 4) * 2);
} else if self.table_count < self.table_lower() {
// too much free space
self.exchange_table(self.table.len() / 2)
}
self.table_bounds = self.table_lower()..self.table_upper()
}
fn set_table(&mut self, index: Value, item: Value) {
#[cfg(feature = "assertions")]
assert_ne!(index, Value::Nil);
#[cfg(feature = "assertions")]
assert_ne!(item, Value::Nil);
if !self.table_bounds.contains(&self.table_count) {
// make sure the table is appropriately sized
self.ensure_table();
}
let range = self.table.len();
let home = hash(&index) % range;
if self.table[home].index == Value::Nil {
// attempt to place it directly in an empty space
// INSERT
self.table[home].home = home;
self.table[home].item = item; // home.item.soft_drop()
self.table[home].index = index;
self.table[home].displacement = self.table[home].displacement.max(0);
self.table_count += 1;
} else if self.table[home].index == index {
// attempt to replace it directly
// REPLACE
self.table[home].item = item; // home.item.soft_drop()
} else {
// attempt to replace it in a collided neighbour location
for i in 1..self.table[home].displacement + 1 {
let neighbour = &mut self.table[(home + i as usize) % range]; // todo: is mod expensive?
if neighbour.index == index {
// found where it was displaced to
// REPLACE
neighbour.item = item;
return;
}
}
// at this point it must be added, probe to place in an empty space
for j in self.table[home].displacement + 1..Displacement::MAX - 1 {
let neighbour = &mut self.table[(home + j as usize) % range];
if neighbour.index == Value::Nil {
// new empty slot hooray!
// INSERT
neighbour.home = home;
neighbour.item = item;
neighbour.index = index;
self.table[home].displacement = j;
self.table_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.table.len() {
let neighbour = &mut self.table[k];
if neighbour.index == Value::Nil && free.is_none() {
free = Some(k);
} else if neighbour.index == index {
// REPLACE
neighbour.item = item;
return;
}
}
if let Some(k) = free {
// INSERT
self.table[k].home = home;
self.table[k].index = index;
self.table[k].item = item;
self.table[home].displacement = Displacement::MAX;
self.table_count += 1;
return;
}
// must resize
#[cfg(feature = "messages")]
eprintln!("\n\tResizing...");
self.resize_table((self.table.len() + 4) * 2);
self.set_table(index, item);
}
}
fn rem_table(&mut self, index: Value) {
let range = self.table.len();
let home = hash(&index) % range;
if self.table[home].index == index {
self.table[home].index = Value::Nil;
self.table[home].item = Value::Nil; // is this necessary?
self.table_count -= 1;
} else {
if self.table[home].displacement != Displacement::MAX {
let mut largest = 0;
for i in 1..self.table[home].displacement {
let neighbour = &mut self.table[(home + i as usize) % range];
if neighbour.index == index {
neighbour.index = Value::Nil;
neighbour.item = Value::Nil;
self.table_count -= 1;
if i == self.table[home].displacement {
self.table[home].displacement = largest
}
return;
} else if neighbour.index != Value::Nil && neighbour.home == home {
largest = i;
}
}
} else {
let mut largest = 0;
let mut finished = false; // what
for k in 0..self.table.len() {
let neighbour = &mut self.table[k];
if neighbour.index == index {
neighbour.index = Value::Nil;
neighbour.item = Value::Nil;
self.table_count -= 1;
#[cfg(feature = "assertions")]
assert!(!finished);
finished = true;
}
if neighbour.home == home && neighbour.index != Value::Nil {
largest = largest.max(if k < home {
k + self.table.len() - home - 1 // ?
} else {
k - home
})
}
}
self.table[home].displacement = if largest > Displacement::MAX as usize {
Displacement::MAX
} else {
largest as Displacement
}
}
}
}
pub fn set(&mut self, index: Value, item: Value) -> Result<(), RunError> {
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 item {
match value {
Value::Nil => {
if (0..self.array.len() + 1).contains(&(index as usize)) {
if self.array.len() == index as usize {
@ -279,7 +365,7 @@ impl Table {
self.array[index as usize + 1] = Value::Nil
}
}
self.rem_table(Value::Integer(index));
self.table.rem_table(Value::Integer(index));
}
item => {
if (0..self.array.len() + 1).contains(&(index as usize)) {
@ -289,19 +375,25 @@ impl Table {
self.array[index as usize + 1] = item;
}
} else {
self.set_table(Value::Integer(index), item)
self.table.set_table(KeyValue {
index: Value::Integer(index),
value: item
})
}
}
}
Ok(())
}
Value::Nil => Err(RunError(
"Attempt to set new index of tabel with key: nil".to_string(),
"Attempt to set new index of table with key: nil".to_string(),
)),
index => Ok(self.set_table(index, item)),
index => Ok(self.table.set_table(KeyValue {
index,
value
})),
}
}
pub fn get(&self, index: Value) -> Result<Value, RunError> {
pub fn get(&mut self, index: Value) -> Result<Value, RunError> {
match index {
Value::Integer(index) => Ok(self
.array
@ -309,20 +401,11 @@ impl Table {
.unwrap_or(&Value::Nil)
.clone()),
Value::Nil => Err(RunError("Attempt to index table with key: nil".to_string())),
index => {
let location = hash(&index) % self.table.len();
let home = &self.table[location];
if home.index == index {
Ok(home.item.clone())
} else {
for i in 1..home.displacement as usize {
let neighbour = &self.table[location + i];
if neighbour.index == index {
return Ok(neighbour.item.clone());
}
}
Ok(Value::Nil)
}
index => { // todo: move this into the Map
Ok(self.table.get_table(index).unwrap_or(&KeyValue {
index: Value::Nil,
value: Value::Nil,
}).value.clone())
}
}
}
@ -331,11 +414,9 @@ impl Table {
}
pub fn new() -> Self {
Table {
table: Vec::new(),
table: Map::new(),
array: Vec::new(),
table_count: 0,
table_bounds: (0..0).into(),
meta: None,
}
}
}
}