Fixes for uninitialised garbage collected pointers, jumps and identifiers in the compiler. Built but erroring on eq.

This commit is contained in:
Christian Lincoln 2026-08-25 21:24:34 +01:00
parent d2f47604bf
commit 44b389a6a1
5 changed files with 2922 additions and 2640 deletions

BIN
mars-49d70575ae6f08.core Normal file

Binary file not shown.

177
src/gc.rs
View file

@ -1,4 +1,4 @@
use std::alloc::{alloc, dealloc, Layout}; use std::alloc::{Layout, alloc, dealloc};
use std::any::Any; use std::any::Any;
use std::cell::{Ref, RefCell, RefMut}; use std::cell::{Ref, RefCell, RefMut};
use std::fmt::Debug; use std::fmt::Debug;
@ -24,116 +24,131 @@ in the allocator ensure location is consistent
#[derive(Debug)] #[derive(Debug)]
pub struct Gc<T: ?Sized + Traverse> { pub struct Gc<T: ?Sized + Traverse> {
it: NonNull<RefCell<Header<T>>> it: NonNull<RefCell<Header<T>>>,
} }
pub struct Agc {} pub struct Agc {}
impl<T: ?Sized + Traverse> Clone for Gc<T> { impl<T: ?Sized + Traverse> Clone for Gc<T> {
fn clone(&self) -> Self { fn clone(&self) -> Self {
Gc { it: self.it } Gc { it: self.it }
} }
} }
impl<T: ?Sized + Traverse> Traverse for Gc<T> { impl<T: ?Sized + Traverse> Traverse for Gc<T> {
fn traverse(&self) { fn traverse(&self) {
self.borrow().traverse() self.borrow().traverse()
} }
} }
impl<T: ?Sized + Traverse> PartialEq for Gc<T> { impl<T: ?Sized + Traverse> PartialEq for Gc<T> {
fn eq(&self, other: &Gc<T>) -> bool { fn eq(&self, other: &Gc<T>) -> bool {
self.it == other.it self.it == other.it
} }
} }
impl<T: ?Sized + Traverse> Gc<T> { impl<T: ?Sized + Traverse> Gc<T> {
pub fn borrow(&self) -> impl Deref<Target=T> { pub fn borrow(&self) -> impl Deref<Target = T> {
Ref::map(unsafe { // ??? Ref::map(
(*self.it.as_ptr()).borrow() unsafe {
},|it| &it.data) // ???
} (*self.it.as_ptr()).borrow()
pub fn borrow_mut(&mut self) -> impl DerefMut<Target=T> { },
RefMut::map(unsafe { |it| &it.data,
(*self.it.as_mut()).borrow_mut() )
},|it| &mut it.data) }
} pub fn borrow_mut(&mut self) -> impl DerefMut<Target = T> {
RefMut::map(unsafe { (*self.it.as_mut()).borrow_mut() }, |it| {
pub fn replace(&mut self, new: T) where T: Sized { &mut it.data
unsafe { })
(*self.it.as_mut()).borrow_mut().data = new;
} }
}
pub fn addr(&self) -> usize { pub fn replace(&mut self, new: T)
self.it.as_ptr().addr() where
} T: Sized,
{
unsafe {
(*self.it.as_mut()).borrow_mut().data = new;
}
}
pub fn addr(&self) -> usize {
self.it.as_ptr().addr()
}
} }
#[derive(Clone, Debug, Copy)] #[derive(Clone, Debug, Copy)]
enum Color { enum Color {
White, White,
Black, Black,
} }
struct Header<T: ?Sized + Traverse> { struct Header<T: ?Sized + Traverse> {
layout: Layout, layout: Layout,
color: Color, // NOT for incremental tri-color right now; white == mark-free, black == mark-keep color: Color, // NOT for incremental tri-color right now; white == mark-free, black == mark-keep
data: T data: T,
} }
pub struct Allocator { pub struct Allocator {
// An array of pointers is used here since I did not want to use a compactor and larger types // An array of pointers is used here since I did not want to use a compactor and larger types
// held on the GC (game objects) will most likely be massive anyway. // held on the GC (game objects) will most likely be massive anyway.
objects: Vec<Option<*mut RefCell<Header<dyn Traverse>>>>, objects: Vec<Option<NonNull<RefCell<Header<dyn Traverse>>>>>,
} }
impl Allocator { impl Allocator {
pub fn new() -> Allocator { pub fn new() -> Allocator {
Allocator { Allocator {
objects: Vec::new(), objects: Vec::new(),
}
}
pub fn alloc<T: Traverse + 'static>(&mut self, it: T) -> Gc<T> {
let layout = Layout::new::<RefCell<Header<T>>>();
let pointer = unsafe { alloc(layout) as *mut RefCell<Header<T>> }; // todo: maybe use a compacting GC
assert!(!pointer.is_null()); // lol idk how to fix this rn
let mut header = unsafe { pointer.as_mut() }.unwrap().borrow_mut();
header.layout = layout;
header.color = Color::Black;
header.data = it;
self.objects.push(Some(pointer as *mut RefCell<Header<dyn Traverse>>));
Gc {
it: NonNull::new(pointer).unwrap(),
}
}
fn mark_white(&mut self) {
for i in 0..self.objects.len() {
if let Some(object) = self.objects[i] {
unsafe { (*object).borrow_mut().color = Color::White };
}
}
}
fn collect(&mut self) {
self.objects.retain(|object| {
if let Some(object) = object {
let header = unsafe { (**object).borrow_mut() };
match header.color {
Color::White => {
unsafe { dealloc(*object as *mut u8,header.layout); }
true
}
Color::Black => false
} }
} else { }
false pub fn alloc<T: Traverse>(&mut self, it: T) -> Gc<T> {
} let layout = Layout::new::<RefCell<Header<T>>>();
}); let pointer = unsafe {
} let pointer = alloc(layout) as *mut RefCell<Header<T>>;
assert!(!pointer.is_null()); // lol idk how to fix this rn (gah use a result!!!)
pointer.write(RefCell::new(Header {
layout,
color: Color::Black,
data: it,
}));
pointer
}; // todo: maybe use a compacting GC
self.objects.push(NonNull::new(pointer));
Gc {
it: NonNull::new(pointer).unwrap(),
}
}
fn mark_white(&mut self) {
for i in 0..self.objects.len() {
if let Some(object) = self.objects[i] {
unsafe { (*object.as_ptr()).borrow_mut().color = Color::White };
}
}
}
fn collect(&mut self) {
self.mark_white();
self.objects.retain(|object| {
if let Some(object) = object {
let header = unsafe { (*object.as_ptr()).borrow_mut() };
match header.color {
Color::White => {
unsafe {
dealloc(object.as_ptr() as *mut u8, header.layout);
}
true
}
Color::Black => false,
}
} else {
false
}
});
}
} }
pub trait Traverse: Any + Debug { pub trait Traverse: Any + Debug {
fn traverse(&self) { // mark or grey fn traverse(&self) {
() // mark or grey
} ()
} }
}

4822
src/lib.rs

File diff suppressed because it is too large Load diff

View file

@ -1,7 +1,6 @@
use std::hash::{DefaultHasher, Hash, Hasher};
use std::rc::Rc;
use crate::gc::{Gc, Traverse}; use crate::gc::{Gc, Traverse};
use crate::{Callable, RunError, Value}; use crate::{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... // 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 /* See some of Lua's notes on this table: https://www.lua.org/source/5.5/ltable.c.html
@ -25,316 +24,318 @@ Notes:
*/ */
type Displacement = u8; type Displacement = u8;
pub type Hashed = usize;
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
struct Entry { struct Entry {
index: Value, index: Value,
item: Value, item: Value,
home: usize, home: usize,
displacement: Displacement, displacement: Displacement,
} }
impl Default for Entry { impl Default for Entry {
fn default() -> Self { fn default() -> Self {
Entry { Entry {
index: Value::Nil, index: Value::Nil,
item: Value::Nil, item: Value::Nil,
home: 0, home: 0,
displacement: 0, displacement: 0,
}
} }
}
} }
pub fn hash(value: &Value) -> usize { pub fn hash(value: &Value) -> Hashed {
match value { match value {
Value::Nil => { 0 } Value::Nil => 0,
Value::Bool(boolean) => { if *boolean { 1 } else { 0 } } Value::Bool(boolean) => {
Value::String(string) => { if *boolean {
let mut hasher = DefaultHasher::new(); 1
string.hash(&mut hasher); } else {
hasher.finish() as usize 0
} }
Value::Phrase(phrase) => {
phrase.1
}
Value::Function(callable) => {
match callable {
Callable::Rust(native) => {
Rc::as_ptr(native).addr()
} }
Callable::Mars(closure) => { Value::String(_string, hash) => *hash,
closure.addr() 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::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
} }
}
} }
Value::Integer(integer) => {
*integer as usize
}
Value::Number(number) => {
if number.is_nan() {
0
} else {
number.to_bits() 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
}
}
} }
// todo: displaced items don't ever get shuffled closer to their homes unless the table is resized // todo: displaced items don't ever get shuffled closer to their homes unless the table is resized
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
pub struct Table { pub struct Table {
table: Vec<Entry>, table: Vec<Entry>,
array: Vec<Value>, array: Vec<Value>,
table_bounds: std::ops::Range<usize>, table_bounds: std::ops::Range<usize>,
table_count: usize, // number of elements in table table_count: usize, // number of elements in table
pub meta: Option<Gc<Table>> pub meta: Option<Gc<Table>>,
} }
impl Traverse for Table { impl Traverse for Table {
fn traverse(&self) { fn traverse(&self) {
for Entry { index, item, .. } in self.table.iter() { for Entry { index, item, .. } in self.table.iter() {
if !matches!(index,Value::Nil) { if !matches!(index, Value::Nil) {
index.traverse(); index.traverse();
item.traverse(); item.traverse();
} }
}
for item in self.array.iter() {
item.traverse()
}
} }
for item in self.array.iter() {
item.traverse()
}
}
} }
impl Table { impl Table {
fn exchange_table(&mut self, len: usize) { fn exchange_table(&mut self, len: usize) {
let old = std::mem::replace(&mut self.table, vec![Entry::default(); len]); let old = std::mem::replace(&mut self.table, vec![Entry::default(); len]);
for Entry { index, item, .. } in old { for Entry { index, item, .. } in old {
if index != Value::Nil { if index != Value::Nil {
self.set_table(index,item) 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; pub fn resize_table(&mut self, len: usize) {
let mut finished = false; // what self.exchange_table(len.max(self.table_count + (self.table_count / 3)));
for k in 0..self.table.len() { self.table_bounds = 0..self.table_upper();
let neighbour = &mut self.table[k]; }
if neighbour.index == index { pub fn resize_array(&mut self, len: usize) {
neighbour.index = Value::Nil; self.array.resize(len, Value::Nil)
neighbour.item = Value::Nil; }
self.table_count -= 1; fn table_upper(&self) -> usize {
#[cfg(feature = "assertions")] (self.table.len() / 4) * 3
assert!(!finished); }
finished = true; fn table_lower(&self) -> usize {
} self.table.len() / 3
if neighbour.home == home && neighbour.index != Value::Nil { }
largest = largest.max(if k < home { fn ensure_table(&mut self) {
k + self.table.len() - home - 1 // ? if self.table_count > self.table_upper() {
} else { // free space is short
k - home 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[home].displacement = if largest > Displacement::MAX as usize { self.table_bounds = self.table_lower()..self.table_upper()
Displacement::MAX }
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 { } else {
largest as Displacement // 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
pub fn set(&mut self, index: Value, item: Value) -> Result<(),RunError> { // REPLACE
match index { neighbour.item = item;
Value::Integer(index) => { // This is an integer index, try the array first return;
match item { }
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.rem_table(Value::Integer(index)); // 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 {
item => { let neighbour = &mut self.table[(home + j as usize) % range];
if (0..self.array.len() + 1).contains(&(index as usize)) { if neighbour.index == Value::Nil {
if self.array.len() == index as usize { // new empty slot hooray!
self.array.push(item) // INSERT
} else { neighbour.home = home;
self.array[index as usize + 1] = item; neighbour.item = item;
} neighbour.index = index;
} else { self.table[home].displacement = j;
self.set_table(Value::Integer(index),item) 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);
} }
Ok(())
},
Value::Nil => Err(RunError("Attempt to set new index of tabel with key: nil".to_string())),
index => Ok(self.set_table(index,item))
} }
} fn rem_table(&mut self, index: Value) {
pub fn get(&self, index: Value) -> Result<Value,RunError> { let range = self.table.len();
match index { let home = hash(&index) % range;
Value::Integer(index) => Ok(self.array.get(index as usize).unwrap_or(&Value::Nil).clone()), if self.table[home].index == index {
Value::Nil => Err(RunError("Attempt to index table with key: nil".to_string())), self.table[home].index = Value::Nil;
index => { self.table[home].item = Value::Nil; // is this necessary?
let location = hash(&index) % self.table.len(); self.table_count -= 1;
let home = &self.table[location];
if home.index == index {
Ok(home.item.clone())
} else { } else {
for i in 1..home.displacement as usize { if self.table[home].displacement != Displacement::MAX {
let neighbour = &self.table[location + i]; let mut largest = 0;
if neighbour.index == index { for i in 1..self.table[home].displacement {
return Ok(neighbour.item.clone()) 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
}
} }
}
Ok(Value::Nil)
} }
}
} }
} pub fn set(&mut self, index: Value, item: Value) -> Result<(), RunError> {
pub fn append(&mut self, item: Value) { match index {
self.array.push(item) Value::Integer(index) => {
} // This is an integer index, try the array first
pub fn new() -> Self { match item {
Table { Value::Nil => {
table: Vec::new(), if (0..self.array.len() + 1).contains(&(index as usize)) {
array: Vec::new(), if self.array.len() == index as usize {
table_count: 0, self.array.pop();
table_bounds: (0..0).into(), } else {
meta: None self.array[index as usize + 1] = Value::Nil
}
}
self.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.set_table(Value::Integer(index), item)
}
}
}
Ok(())
}
Value::Nil => Err(RunError(
"Attempt to set new index of tabel with key: nil".to_string(),
)),
index => Ok(self.set_table(index, item)),
}
} }
} pub fn get(&self, index: Value) -> Result<Value, RunError> {
} match index {
Value::Integer(index) => Ok(self
.array
.get(index as usize)
.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)
}
}
}
}
pub fn append(&mut self, item: Value) {
self.array.push(item)
}
pub fn new() -> Self {
Table {
table: Vec::new(),
array: Vec::new(),
table_count: 0,
table_bounds: (0..0).into(),
meta: None,
}
}
}

View file

@ -1 +1,5 @@
do end if true then
print("whats good")
else
print("oh no")
end