Redesign the engine (#56)

This commit is contained in:
Joel Wejdenstål 2022-04-13 14:03:26 +02:00 committed by GitHub
parent ddb957b16a
commit d335667d41
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
32 changed files with 1296 additions and 1531 deletions

View file

@ -8,6 +8,7 @@ logos = "0.12.0"
ariadne = "0.1.5"
hashbrown = { version = "0.12.0", default-features = false, features = ["nightly"] }
cstree = "0.11.0"
rand = "0.8.5"
[dev-dependencies]
insta = "1.12.0"
@ -20,7 +21,3 @@ debug = true
[[bench]]
name = "parse"
harness = false
[[bench]]
name = "gc"
harness = false

View file

@ -1,95 +0,0 @@
#![feature(int_log)]
use criterion::{criterion_group, criterion_main, BatchSize, Criterion, Throughput};
use zaia::engine::{
gc::{Heap, Trace},
value::{Table, Value},
};
const OBJECTS_COUNT: u64 = 100000;
const GRAPH_BREADTH: u64 = 3;
fn mark(c: &mut Criterion) {
let mut group = c.benchmark_group("mark");
let depth = OBJECTS_COUNT.log(GRAPH_BREADTH) as u64;
let actual_count = GRAPH_BREADTH.pow(depth as u32);
group.throughput(Throughput::Elements(actual_count));
group.bench_function("mark", |b| {
b.iter_batched(
|| {
fn widen(heap: &Heap, node: &mut Table, depth: u64) {
if depth == 0 {
return;
}
for i in 0..GRAPH_BREADTH {
let mut table = Table::new(heap.clone());
widen(heap, &mut table, depth - 1);
let value = Value::from_table(heap.insert(table));
node.insert(Value::from_int(i as i32), value);
}
}
let heap = Heap::new();
let mut root = Table::new(heap.clone());
widen(&heap, &mut root, depth);
let handle = heap.insert(root);
(heap, handle)
},
|(heap, handle)| {
heap.collect(
|visitor| {
unsafe {
handle.get_unchecked_mut().visit(visitor);
}
visitor.mark(handle.tagged());
},
|_| unreachable!(),
);
},
BatchSize::LargeInput,
);
});
group.finish();
}
fn sweep(c: &mut Criterion) {
let mut group = c.benchmark_group("sweep");
group.throughput(Throughput::Elements(OBJECTS_COUNT));
group.bench_function("sweep", |b| {
b.iter_batched(
|| {
let heap = Heap::new();
for i in 0..OBJECTS_COUNT {
match i % 3 {
0 | 1 => {
heap.insert_string(b"Foo Fighters!");
},
2 => {
let mut table = Table::new(heap.clone());
let i = i as i32;
table.insert(Value::from_int(i), Value::from_int(i));
table.insert(Value::from_int(i + 1), Value::from_int(i + 1));
heap.insert(table);
},
_ => unreachable!(),
}
}
heap
},
|heap| {
heap.collect(|_| (), |_| ());
},
BatchSize::LargeInput,
);
});
group.finish();
}
criterion_group!(benches, mark, sweep);
criterion_main!(benches);

View file

@ -8,7 +8,7 @@ fn criterion_benchmark(c: &mut Criterion) {
let source = fs::read_to_string("test-files/mixed.lua").unwrap();
let mut group = c.benchmark_group("parse");
let mut deferred = Vec::new();
group.throughput(Throughput::Elements(source.lines().count() as u64));
group.throughput(Throughput::Bytes(source.len() as u64));
group.bench_function("mixed.lua", |b| {
b.iter(|| parse_mixed(black_box(&source), &mut deferred));
});

View file

@ -10,4 +10,3 @@ normalize_doc_attributes = true
reorder_impl_items = true
group_imports = "StdExternalCrate"
use_field_init_shorthand = true
wrap_comments = true

69
src/engine/bytecode.rs Normal file
View file

@ -0,0 +1,69 @@
#[derive(Clone, Copy)]
pub struct Register(pub u8);
#[derive(Clone, Copy)]
pub struct Upvalue(pub u8);
#[derive(Clone, Copy)]
pub struct Chunk(pub u16);
#[derive(Clone, Copy)]
pub struct Constant(pub u16);
#[derive(Clone, Copy)]
pub struct ArgumentCount(pub u8);
#[derive(Clone, Copy)]
pub struct InstructionIndex(pub u32);
#[derive(Clone, Copy)]
pub struct Capture {
pub is_upvalue: bool,
pub slot: Register,
}
#[derive(Clone)]
#[allow(clippy::upper_case_acronyms)]
pub enum Instruction {
ISLT(Register, Register),
ISLE(Register, Register),
ISEQ(Register, Register),
ISNEQ(Register, Register),
IST(Register),
ISF(Register),
MOV(Register, Register),
NOT(Register, Register),
NEG(Register, Register),
LEN(Register, Register),
BITNOT(Register, Register),
ADD(Register, Register, Register),
SUB(Register, Register, Register),
MUL(Register, Register, Register),
DIV(Register, Register, Register),
INTDIV(Register, Register, Register),
EXP(Register, Register, Register),
MOD(Register, Register, Register),
BITAND(Register, Register, Register),
BITOR(Register, Register, Register),
BITXOR(Register, Register, Register),
LEFTSHIFT(Register, Register, Register),
RIGHTSHIFT(Register, Register, Register),
AND(Register, Register, Register),
OR(Register, Register, Register),
CONCAT(Register, Register, Register),
CLOAD(Register, Constant),
UGET(Register, Upvalue),
USET(Register, Upvalue),
FNEW(Register, Chunk, Vec<Capture>),
TNEW(Register),
TGET(Register, Register, Register),
TSET(Register, Register, Register),
GGET(Register, Register),
GSET(Register, Register),
JMP(InstructionIndex),
APUSH(Register),
APOP(Register),
ACLEAR,
CALL(Register),
RET,
}

View file

@ -1,4 +1,18 @@
pub enum Error {
UncaughtBreak,
UncaughtReturn,
pub struct Error {
message: String,
}
impl Error {
pub fn new<S>(message: S) -> Self
where
S: Into<String>,
{
Self {
message: message.into(),
}
}
pub fn message(&self) -> &str {
&self.message
}
}

View file

@ -1,130 +0,0 @@
use std::{cmp, fmt, hash};
use super::super::util;
/// # Safety
/// `PtrTag` must be implemented directly using the functions
/// found in the `encoding` submodule.
pub unsafe trait PtrTag {
fn is(x: u64) -> bool;
fn tag(x: usize) -> u64;
}
pub struct Handle<T>
where
T: ?Sized + PtrTag,
{
ptr: *mut T,
}
impl<T> Handle<T>
where
T: ?Sized + PtrTag,
{
pub fn new(ptr: *mut T) -> Self {
Handle { ptr }
}
/// # Safety
/// - Handle must point to a living instance of `T`.
pub unsafe fn get_unchecked<'a>(self) -> &'a T {
&*self.ptr
}
/// # Safety
/// - Handle must point to a living instance of `T`.
pub unsafe fn get_unchecked_mut<'a>(self) -> &'a mut T {
&mut *self.ptr
}
pub fn as_ptr(self) -> *mut T {
self.ptr
}
pub fn tagged(self) -> TaggedHandle {
let tagged = T::tag(self.ptr as *mut u8 as usize);
TaggedHandle::new(tagged)
}
}
impl<T> fmt::Debug for Handle<T>
where
T: ?Sized + PtrTag,
{
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "Handle({:p})", self.ptr)
}
}
impl<T> Clone for Handle<T>
where
T: ?Sized + PtrTag,
{
fn clone(&self) -> Self {
Handle { ptr: self.ptr }
}
}
impl<T> Copy for Handle<T> where T: ?Sized + PtrTag {}
impl<T> cmp::PartialEq for Handle<T>
where
T: ?Sized + PtrTag,
{
fn eq(&self, other: &Self) -> bool {
self.ptr == other.ptr
}
}
impl<T> cmp::Eq for Handle<T> where T: ?Sized + PtrTag {}
impl<T> hash::Hash for Handle<T>
where
T: ?Sized + PtrTag,
{
fn hash<H: hash::Hasher>(&self, state: &mut H) {
self.ptr.hash(state);
}
}
pub struct TaggedHandle {
tagged: u64,
}
impl TaggedHandle {
pub fn new(tagged: u64) -> Self {
Self { tagged }
}
pub fn value(self) -> u64 {
self.tagged
}
pub fn hash(self) -> u64 {
util::mix_u64(self.tagged as u64)
}
}
impl fmt::Debug for TaggedHandle {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "TaggedHandle({:x})", self.tagged)
}
}
impl Clone for TaggedHandle {
fn clone(&self) -> Self {
TaggedHandle {
tagged: self.tagged,
}
}
}
impl Copy for TaggedHandle {}
impl cmp::PartialEq for TaggedHandle {
fn eq(&self, other: &Self) -> bool {
self.tagged == other.tagged
}
}
impl cmp::Eq for TaggedHandle {}

View file

@ -1,50 +0,0 @@
use std::cell::Cell;
const INITIAL_THRESHOLD: usize = 128 * 1024;
const THRESHOLD_FACTOR: f32 = 1.75;
pub struct Heuristics {
allocated: Cell<usize>,
threshold: Cell<usize>,
should_collect: Cell<bool>,
}
impl Heuristics {
pub fn new() -> Self {
Self {
allocated: Cell::new(0),
threshold: Cell::new(INITIAL_THRESHOLD),
should_collect: Cell::new(false),
}
}
fn threshold(&self) -> usize {
(self.threshold.get() as f32 * THRESHOLD_FACTOR) as usize
}
pub fn adjust(&self) {
let new_threshold = self.threshold();
self.threshold.set(new_threshold);
self.should_collect.set(false);
}
fn check_collect(&self) {
if self.allocated >= self.threshold {
self.should_collect.set(true);
let new_threshold = self.threshold();
self.threshold.set(new_threshold);
}
}
pub fn update_allocated<F>(&self, f: F)
where
F: FnOnce(usize) -> usize,
{
self.allocated.update(f);
self.check_collect();
}
pub fn should_collect(&self) -> bool {
self.should_collect.get()
}
}

View file

@ -1,346 +0,0 @@
mod handle;
mod heuristics;
mod set;
mod trace;
use std::{alloc, cell::RefCell, ptr, rc::Rc};
pub use handle::{Handle, PtrTag, TaggedHandle};
use heuristics::Heuristics;
use set::ObjectSet;
pub use trace::{Trace, Visitor};
use super::value::{encoding, ByteString, Function, Table, Userdata};
pub struct Heap {
internal: Rc<HeapInternal>,
}
impl Heap {
#[allow(clippy::new_without_default)]
pub fn new() -> Self {
Heap {
internal: Rc::new(HeapInternal::new()),
}
}
pub fn insert<T>(&self, value: T) -> Handle<T>
where
T: PtrTag,
{
self.internal.insert(value)
}
pub fn insert_string(&self, bytes: &[u8]) -> Handle<ByteString> {
self.internal.insert_string(bytes)
}
/// # Safety
/// - Handle must point to a living instance of `T`.
pub unsafe fn destroy(&self, handle: TaggedHandle) {
self.internal.destroy(handle);
}
pub fn collect<F1, F2>(&self, trace: F1, finalize: F2)
where
F1: FnOnce(&mut Visitor),
F2: FnMut(TaggedHandle),
{
self.internal.collect(trace, finalize);
}
pub fn should_collect(&self) -> bool {
self.internal.heuristics.should_collect()
}
}
impl Clone for Heap {
fn clone(&self) -> Self {
Heap {
internal: self.internal.clone(),
}
}
}
struct Tree {
objects: ObjectSet,
visitor: Visitor,
}
impl Tree {
fn collect<F1, F2>(&mut self, heap: &HeapInternal, trace: F1, mut finalize: F2)
where
F1: FnOnce(&mut Visitor),
F2: FnMut(TaggedHandle),
{
trace(&mut self.visitor);
for object in self.visitor.unmarked(&self.objects) {
finalize(object);
self.objects.remove(object);
unsafe {
heap.destroy(object);
}
}
self.visitor.reset();
}
}
struct HeapInternal {
heuristics: Heuristics,
tree: RefCell<Tree>,
}
impl HeapInternal {
fn new() -> Self {
let tree = RefCell::new(Tree {
objects: ObjectSet::new(),
visitor: Visitor::new(),
});
Self {
heuristics: Heuristics::new(),
tree,
}
}
fn insert<T>(&self, value: T) -> Handle<T>
where
T: PtrTag,
{
let ptr = Box::into_raw(Box::new_in(value, self));
let handle = Handle::new(ptr);
self.tree.borrow_mut().objects.insert(handle.tagged());
handle
}
fn insert_string(&self, bytes: &[u8]) -> Handle<ByteString> {
let len = bytes.len() as u32;
let layout = ByteString::layout(len);
unsafe {
let ptr = alloc::Allocator::allocate(self, layout).unwrap().as_ptr() as *mut ByteString;
ByteString::initialize_into(ptr, len);
ptr::copy_nonoverlapping(bytes.as_ptr(), (&mut *ptr).offset(0), len as usize);
let handle = Handle::new(ptr);
self.tree.borrow_mut().objects.insert(handle.tagged());
handle
}
}
unsafe fn destroy(&self, handle: TaggedHandle) {
let tagged = handle.value();
match tagged {
_ if encoding::is_string(tagged) => {
let ptr = encoding::get_string(tagged) as *mut ByteString;
let len = (*ptr).len();
let layout = ByteString::layout(len as u32);
let ptr_nn = ptr::NonNull::new_unchecked(ptr as *mut u8);
alloc::Allocator::deallocate(self, ptr_nn, layout);
},
_ if encoding::is_table(tagged) => {
let ptr = encoding::get_table(tagged) as *mut Table;
Box::from_raw_in(ptr, self);
},
_ if encoding::is_function(tagged) => {
let ptr = encoding::get_function(tagged) as *mut Function;
Box::from_raw_in(ptr, self);
},
_ if encoding::is_userdata(tagged) => {
let ptr = encoding::get_userdata(tagged) as *mut Userdata;
Box::from_raw_in(ptr, self);
},
_ => panic!("unknown pointer type {:b}", tagged),
}
}
fn collect<F1, F2>(&self, trace: F1, finalize: F2)
where
F1: FnOnce(&mut Visitor),
F2: FnMut(TaggedHandle),
{
self.tree.borrow_mut().collect(self, trace, finalize);
self.heuristics.adjust();
}
}
unsafe impl alloc::Allocator for Heap {
fn allocate(&self, layout: alloc::Layout) -> Result<ptr::NonNull<[u8]>, alloc::AllocError> {
self.internal.allocate(layout)
}
unsafe fn deallocate(&self, ptr: ptr::NonNull<u8>, layout: alloc::Layout) {
self.internal.deallocate(ptr, layout)
}
unsafe fn grow(
&self,
ptr: ptr::NonNull<u8>,
old_layout: alloc::Layout,
new_layout: alloc::Layout,
) -> Result<ptr::NonNull<[u8]>, alloc::AllocError> {
self.internal.grow(ptr, old_layout, new_layout)
}
unsafe fn grow_zeroed(
&self,
ptr: ptr::NonNull<u8>,
old_layout: alloc::Layout,
new_layout: alloc::Layout,
) -> Result<ptr::NonNull<[u8]>, alloc::AllocError> {
self.internal.grow_zeroed(ptr, old_layout, new_layout)
}
unsafe fn shrink(
&self,
ptr: ptr::NonNull<u8>,
old_layout: alloc::Layout,
new_layout: alloc::Layout,
) -> Result<ptr::NonNull<[u8]>, alloc::AllocError> {
self.internal.shrink(ptr, old_layout, new_layout)
}
}
unsafe impl alloc::Allocator for HeapInternal {
fn allocate(&self, layout: alloc::Layout) -> Result<ptr::NonNull<[u8]>, alloc::AllocError> {
self.heuristics.update_allocated(|x| x + layout.size());
alloc::Global.allocate(layout)
}
unsafe fn deallocate(&self, ptr: ptr::NonNull<u8>, layout: alloc::Layout) {
self.heuristics.update_allocated(|x| x - layout.size());
alloc::Global.deallocate(ptr, layout)
}
unsafe fn grow(
&self,
ptr: ptr::NonNull<u8>,
old_layout: alloc::Layout,
new_layout: alloc::Layout,
) -> Result<ptr::NonNull<[u8]>, alloc::AllocError> {
self.heuristics
.update_allocated(|x| x + new_layout.size() - old_layout.size());
alloc::Global.grow(ptr, old_layout, new_layout)
}
unsafe fn grow_zeroed(
&self,
ptr: ptr::NonNull<u8>,
old_layout: alloc::Layout,
new_layout: alloc::Layout,
) -> Result<ptr::NonNull<[u8]>, alloc::AllocError> {
self.heuristics
.update_allocated(|x| x + new_layout.size() - old_layout.size());
alloc::Global.grow_zeroed(ptr, old_layout, new_layout)
}
unsafe fn shrink(
&self,
ptr: ptr::NonNull<u8>,
old_layout: alloc::Layout,
new_layout: alloc::Layout,
) -> Result<ptr::NonNull<[u8]>, alloc::AllocError> {
self.heuristics
.update_allocated(|x| x + new_layout.size() - old_layout.size());
alloc::Global.shrink(ptr, old_layout, new_layout)
}
}
impl Drop for HeapInternal {
fn drop(&mut self) {
let tree = self.tree.borrow();
tree.objects.iter().for_each(|object| unsafe {
self.destroy(object);
});
}
}
#[cfg(test)]
mod tests {
use super::{
super::value::{Table, Value},
Heap,
};
use crate::engine::gc::Trace;
#[test]
fn collect_no_trace() {
let heap = Heap::new();
let table1 = Table::new(heap.clone());
let table2 = Table::new(heap.clone());
heap.insert(table1);
heap.insert(table2);
let mut ctr = 0;
heap.collect(|_| (), |_| ctr += 1);
assert_eq!(ctr, 2);
}
#[test]
fn collect_mark_direct() {
let heap = Heap::new();
let table1 = Table::new(heap.clone());
let table2 = Table::new(heap.clone());
let table3 = Table::new(heap.clone());
let table4 = Table::new(heap.clone());
let handle1 = heap.insert(table1).tagged();
heap.insert(table2);
heap.insert(table3);
heap.insert(table4);
let mut ctr = 0;
heap.collect(|visitor| visitor.mark(handle1), |_| ctr += 1);
assert_eq!(ctr, 3);
}
#[test]
fn collect_mark_indirect() {
let heap = Heap::new();
let table1 = Table::new(heap.clone());
let table2 = Table::new(heap.clone());
let table3 = Table::new(heap.clone());
let handle1 = heap.insert(table1);
let handle2 = heap.insert(table2);
let handle3 = heap.insert(table3);
let k1 = Value::from_int(3);
let k2 = Value::from_int(5);
let tab = unsafe { handle1.get_unchecked_mut() };
tab.insert(k1, Value::from_table(handle2));
tab.insert(k2, Value::from_table(handle3));
let mut ctr = 0;
macro_rules! collect {
() => {{
heap.collect(
|visitor| {
let handle = handle1.tagged();
visitor.mark(handle);
tab.visit(visitor);
},
|_| ctr += 1,
);
}};
}
collect!();
assert_eq!(ctr, 0);
tab.remove(k1);
tab.remove(k2);
collect!();
assert_eq!(ctr, 2);
}
}

View file

@ -1,69 +0,0 @@
use hashbrown::{hash_map, HashMap};
use super::TaggedHandle;
pub struct ObjectSet {
set: HashMap<TaggedHandle, (), ()>,
}
impl ObjectSet {
#[allow(clippy::new_without_default)]
pub fn new() -> Self {
Self {
set: HashMap::with_hasher(()),
}
}
fn entry_mut(
&mut self,
handle: TaggedHandle,
) -> hash_map::RawEntryMut<'_, TaggedHandle, (), ()> {
let hash = handle.hash();
self.set
.raw_entry_mut()
.from_hash(hash, |other| handle.value() == other.value())
}
pub fn insert(&mut self, handle: TaggedHandle) {
if let hash_map::RawEntryMut::Vacant(entry) = self.entry_mut(handle) {
let hash = handle.hash();
entry.insert_with_hasher(hash, handle, (), |handle| handle.hash());
} else {
unreachable!()
}
}
pub fn remove(&mut self, handle: TaggedHandle) {
if let hash_map::RawEntryMut::Occupied(entry) = self.entry_mut(handle) {
entry.remove();
return;
}
unreachable!()
}
pub fn contains(&self, handle: TaggedHandle) -> bool {
let hash = handle.hash();
self.set
.raw_entry()
.from_hash(hash, |other| handle.value() == other.value())
.is_some()
}
pub fn iter(&self) -> impl Iterator<Item = TaggedHandle> + '_ {
self.set.keys().copied()
}
pub fn difference<'a>(&'a self, other: &'a Self) -> impl Iterator<Item = TaggedHandle> + 'a {
self.set
.keys()
.filter(|handle| !other.contains(**handle))
.copied()
}
pub fn clear(&mut self) {
self.set.clear();
}
}

View file

@ -1,41 +0,0 @@
use super::{handle::TaggedHandle, set::ObjectSet};
pub trait Trace {
fn visit(&self, visitor: &mut Visitor);
}
pub struct Visitor {
marked: ObjectSet,
stale: Vec<TaggedHandle>,
}
impl Visitor {
#[allow(clippy::new_without_default)]
pub fn new() -> Self {
Self {
marked: ObjectSet::new(),
stale: Vec::new(),
}
}
pub fn mark(&mut self, handle: TaggedHandle) {
self.marked.insert(handle);
}
pub fn unmarked<'a>(
&'a mut self,
objects: &ObjectSet,
) -> impl Iterator<Item = TaggedHandle> + 'a {
self.stale.extend(objects.difference(&self.marked));
self.stale.iter().copied()
}
pub fn is_marked(&self, handle: TaggedHandle) -> bool {
self.marked.contains(handle)
}
pub fn reset(&mut self) {
self.marked.clear();
self.stale.clear();
}
}

View file

@ -0,0 +1,23 @@
use std::collections::HashSet;
use super::object::ObjectPtr;
pub struct Deck {
tracked: HashSet<ObjectPtr>,
}
impl Deck {
pub fn new() -> Self {
Self {
tracked: HashSet::new(),
}
}
pub fn track(&mut self, ptr: ObjectPtr) {
self.tracked.insert(ptr);
}
pub fn roots(&mut self) -> impl Iterator<Item = ObjectPtr> + '_ {
self.tracked.drain()
}
}

View file

@ -0,0 +1,143 @@
use std::{
alloc::{alloc, dealloc, Layout},
collections::HashMap,
mem,
ptr,
time,
};
use super::{
object::{ObjectHeader, ObjectPtr, ObjectType},
EdenGCContext,
};
const EDEN_INITIAL_SIZE: usize = 1024 * 64;
fn layout(size: usize) -> Layout {
unsafe { Layout::from_size_align_unchecked(size, 1) }
}
pub struct Eden {
allocation_size: usize,
limit: usize,
count: usize,
base: *mut u8,
cursor: *mut u8,
}
impl Eden {
pub fn new() -> Self {
let layout = layout(EDEN_INITIAL_SIZE);
let base = unsafe { alloc(layout) };
Self {
allocation_size: EDEN_INITIAL_SIZE,
limit: EDEN_INITIAL_SIZE,
count: 0,
base,
cursor: base,
}
}
pub(super) fn allocate(
&mut self,
ctx: &mut EdenGCContext,
size: u32,
ty: ObjectType,
id: u32,
) -> ObjectPtr {
// Calculate total size.
let alloc_start = self.cursor;
let total_size = mem::size_of::<ObjectHeader>() + size as usize;
// Make sure we have enough space eden memory for this allocation
let used = unsafe { self.cursor.sub(self.base as usize) as usize };
if used + total_size <= self.limit {
self.evacuate(ctx);
}
// Advance the eden cursor
self.cursor = unsafe { self.cursor.add(total_size) };
// Write the size of the allocation into the header
let header = alloc_start as *mut ObjectHeader;
unsafe { *header = ObjectHeader { ty, size, id } };
// Compute the payload pointer by skipping past the header
let ptr = unsafe { alloc_start.add(mem::size_of::<ObjectHeader>()) };
self.count += 1;
ObjectPtr::new(ptr)
}
pub fn contains_object(&self, ptr: ObjectPtr) -> bool {
let ptr = ptr.raw() as usize;
ptr > self.base as usize || ptr < self.cursor as usize
}
fn evacuate(&mut self, ctx: &mut EdenGCContext) {
let mut relocated = HashMap::new();
let relocate = &mut |src: ObjectPtr| {
// Only evacuate objects inside of the eden region.
if !self.contains_object(src) {
return src;
}
// Check if we've already relocated this object.
if let Some(dst) = relocated.get(&src) {
return *dst;
}
// Relocate the object.
unsafe {
let header = &*src.header();
let dst = ctx.old.allocate(header.size, header.ty, header.id);
ptr::copy_nonoverlapping(src.raw(), dst.raw(), header.size as usize);
relocated.insert(src, dst);
dst
}
};
// Start execution timing.
let start = time::Instant::now();
// Trace VM roots.
(ctx.constraint)(relocate);
// Trace tracked objected in the old generation.
for root in ctx.deck.roots() {
unsafe {
let object = &mut *root.virt();
object.evacuate(relocate);
}
}
// Stop the timer.
let elapsed = start.elapsed();
// Calculate the new eden size.
let eden_size = unsafe { self.cursor.sub(self.base as usize) as usize };
let new_eden_size = ctx.pacer.step_eden(
eden_size,
ctx.old.size(),
elapsed,
self.count,
relocated.len(),
);
// Expand the eden region if needed.
if new_eden_size > self.allocation_size {
let new_allocation_size = new_eden_size.next_power_of_two();
let old_layout = layout(self.allocation_size);
unsafe { dealloc(self.base, old_layout) };
let layout = layout(new_allocation_size);
self.base = unsafe { alloc(layout) };
self.allocation_size = new_allocation_size;
}
// Reset allocator state.
self.limit = new_eden_size;
self.count = 0;
self.cursor = self.base;
}
}

View file

@ -0,0 +1,71 @@
mod deck;
mod eden;
mod object;
mod old;
mod optimizer;
mod pacer;
use std::{alloc::Layout, collections::HashSet, time::Duration};
use deck::Deck;
use eden::Eden;
pub use object::{Object, ObjectPtr, ObjectType};
use old::Old;
use pacer::Pacer;
const LARGE_OBJECT_THRESHOLD: usize = 4 * 1024;
const MAX_PAUSE: Duration = Duration::from_millis(100);
pub type GCConstraint = dyn FnMut(&mut dyn FnMut(ObjectPtr) -> ObjectPtr);
pub struct InternalHeap {
pacer: Pacer,
eden: Eden,
deck: Deck,
old: Old,
large_objects: HashSet<(*mut u8, Layout)>,
constraint: Box<GCConstraint>,
}
impl InternalHeap {
pub fn new() -> InternalHeap {
InternalHeap {
pacer: Pacer::new(MAX_PAUSE),
eden: Eden::new(),
deck: Deck::new(),
old: Old::new(),
large_objects: HashSet::new(),
constraint: Box::new(|_| ()),
}
}
pub(super) fn allocate(&mut self, size: u32, ty: ObjectType) -> ObjectPtr {
let mut ctx = EdenGCContext {
pacer: &mut self.pacer,
deck: &mut self.deck,
old: &mut self.old,
constraint: &mut self.constraint,
};
self.eden.allocate(&mut ctx, size, ty, rand::random())
}
pub fn barrier(&mut self, target: ObjectPtr, child: ObjectPtr) {
if !self.eden.contains_object(target) || self.eden.contains_object(child) {
return;
}
self.deck.track(target);
}
pub fn set_constraint(&mut self, constraint: Box<GCConstraint>) {
self.constraint = constraint;
}
}
struct EdenGCContext<'a> {
pacer: &'a mut Pacer,
deck: &'a mut Deck,
old: &'a mut Old,
constraint: &'a mut GCConstraint,
}

View file

@ -0,0 +1,78 @@
use std::mem;
use super::super::super::value::{ByteString, Function, Upvalue, Userdata};
use crate::engine::value::Table2;
pub trait Object {
fn evacuate(&mut self, visitor: &mut dyn FnMut(ObjectPtr) -> ObjectPtr);
}
#[derive(Clone, Copy)]
pub struct ObjectHeader {
pub size: u32,
pub id: u32,
pub ty: ObjectType,
}
#[derive(Clone, Copy, PartialEq, Eq, Hash)]
pub struct ObjectPtr {
ptr: *mut u8,
}
impl ObjectPtr {
pub fn new(ptr: *mut u8) -> Self {
Self { ptr }
}
pub fn raw(self) -> *mut u8 {
self.ptr
}
pub fn header(self) -> *mut ObjectHeader {
let offset = mem::size_of::<ObjectHeader>();
unsafe { self.ptr.sub(offset) as *mut ObjectHeader }
}
pub fn from<T>(ptr: *mut T) -> Self
where
T: Object,
{
ObjectPtr::new(ptr as *mut u8)
}
pub fn cast<T>(self) -> *mut T
where
T: Object,
{
self.ptr as *mut T
}
#[allow(clippy::not_unsafe_ptr_arg_deref)]
pub fn from_header_ptr(header: *mut ObjectHeader) -> Self {
let ptr = unsafe { header.add(1) as *mut u8 };
Self { ptr }
}
pub fn virt(self) -> *mut dyn Object {
unsafe {
let header = &*self.header();
match header.ty {
ObjectType::String => self.cast::<ByteString>(),
ObjectType::Userdata => self.cast::<Userdata>(),
ObjectType::Function => self.cast::<Function>(),
ObjectType::Table => self.cast::<Table2>(),
ObjectType::Upvalue => self.cast::<Upvalue>(),
}
}
}
}
#[derive(Clone, Copy)]
pub enum ObjectType {
Table,
String,
Function,
Userdata,
Upvalue,
}

View file

@ -0,0 +1,41 @@
use std::{
alloc::{alloc, Layout},
collections::LinkedList,
mem,
};
use super::object::{ObjectHeader, ObjectPtr, ObjectType};
fn layout(size: usize) -> Layout {
unsafe { Layout::from_size_align_unchecked(size, 1) }
}
pub struct Old {
objects: LinkedList<ObjectPtr>,
size: usize,
}
impl Old {
pub fn new() -> Self {
Self {
objects: LinkedList::new(),
size: 0,
}
}
pub(super) fn allocate(&mut self, size: u32, ty: ObjectType, id: u32) -> ObjectPtr {
let total_size = size as usize + mem::size_of::<ObjectHeader>();
let layout = layout(total_size);
unsafe {
let ptr = alloc(layout) as *mut ObjectHeader;
(*ptr) = ObjectHeader { size, ty, id };
ObjectPtr::from_header_ptr(ptr)
}
}
pub fn size(&self) -> usize {
self.size
}
}

View file

@ -0,0 +1,65 @@
const LEARNING_RATE: f32 = 0.1;
const MOMENTUM: f32 = 0.8;
/// [`ConvexOptimizer`] is an optimizer than finds a local minimum for an
/// unknown convex function using an adaptive gradient descent algorithm.
pub struct ConvexOptimizer {
x: f32,
prev_x: f32,
prev_y: f32,
prev_change: f32,
}
impl ConvexOptimizer {
/// Create a new optimizer with an initial guess of `x` and a threshold of
/// when to stop optimization based on the change of `x`.
pub fn new(x: f32) -> Self {
Self {
x,
prev_x: 0.0,
prev_y: 0.0,
prev_change: 0.0,
}
}
/// Calculate the absolute change in the function input since the last step.
fn x_diff(&self) -> f32 {
self.x - self.prev_x
}
/// Approximate the derivative using the derivative definition.
fn gradient(&self, y: f32) -> f32 {
(y - self.prev_y) / (self.x_diff() + 1e-8)
}
/// Calculate an absolute base change based on the approximate gradient.
fn change(&self, y: f32) -> f32 {
LEARNING_RATE * self.gradient(y)
}
/// Accelerate the base change using previous momentum.
fn accelerated_change(&self, y: f32) -> f32 {
self.change(y) + MOMENTUM * self.prev_change
}
/// Repredict the change using Nesterov's method.
fn predicted_change(&self, y: f32) -> f32 {
let change = self.accelerated_change(y);
change + MOMENTUM * (change - self.prev_change)
}
/// Step the optimizer forward by one iteration.
/// Accepts the `y` value for the previous `x` value and yields a new `x`-value.
pub fn step(&mut self, y: f32) -> f32 {
// Compute the new change.
let change = self.predicted_change(y);
// Update internal state with new data.
self.prev_x = self.x;
self.x -= change;
self.prev_y = y;
self.prev_change = change;
self.x
}
}

View file

@ -0,0 +1,78 @@
use std::{cmp, time::Duration};
use super::optimizer::ConvexOptimizer;
const EDEN_SIZE_MINIMUM: usize = 1024 * 16;
fn smoothed(current: f32, new: f32) -> f32 {
(current * 2.0 + new) / 3.0
}
pub struct Pacer {
/// `max_pause` is the maximum GC pause time that we should allow in
/// seconds.
max_pause: f32,
/// `evacuation_rate` is the evacuation speed from the eden region in bytes
/// per second. This is stored in the form of an exponential moving
/// average in order to smooth out various extreme values caused by the
/// environment.
evacuation_rate: f32,
/// `eden_optimizer` is a convex optimizer used to find the optimal eden
/// size.
eden_optimizer: ConvexOptimizer,
}
impl Pacer {
pub fn new(max_pause: Duration) -> Self {
Self {
max_pause: max_pause.as_secs_f32(),
evacuation_rate: 0.0,
eden_optimizer: ConvexOptimizer::new(EDEN_SIZE_MINIMUM as f32),
}
}
pub fn adjust_max_pause(&mut self, max_pause: Duration) {
self.max_pause = max_pause.as_secs_f32();
}
/// `step_eden` collects eden evacuation metrics and recommends the new size
/// of the eden region. The algorithm attempts to adapts to runtime
/// conditions of the existing system by dynamically modifying internal
/// tuning parameters based on previous collection metrics to minimize
/// the runtime of the next collection and hit latency goals.
pub fn step_eden(
&mut self,
eden_size: usize,
heap_size: usize,
elapsed: Duration,
objects: usize,
survivors: usize,
) -> usize {
// Calculate the evacauation rate. This is smoothed to prevent
// fluctuations in the metrics from throwing off the maximum eden size
// which could potentially cause us to miss latency goals during the next cycle.
let observed_evacuation_rate = eden_size as f32 / elapsed.as_secs_f32();
self.evacuation_rate = smoothed(self.evacuation_rate, observed_evacuation_rate);
// Measure the amount of work that was required for this collection and
// step the optimizer to produce a new initial eden size.
let observed_evacuation_survivors = survivors as f32 / objects as f32;
let evacuation_work = observed_evacuation_survivors * eden_size as f32;
let mut size = self.eden_optimizer.step(evacuation_work) as usize;
// Constrict size so that we always evacuate within the time limit.
let latency = (self.max_pause * self.evacuation_rate * 0.9) as usize;
size = cmp::min(size, latency);
// Limit eden size to a proportion of the heap size which is generally more
// stable. This is done to prevent the eden size from growing extremely
// large in comparison to heap which may be unexpected.
let proportional = heap_size / 4;
size = cmp::min(size, proportional);
// Bound the eden size to a minimum.
cmp::max(size, EDEN_SIZE_MINIMUM)
}
}

76
src/engine/gc2/mod.rs Normal file
View file

@ -0,0 +1,76 @@
mod internal;
use std::{cell::RefCell, ptr};
use internal::InternalHeap;
pub use internal::{Object, ObjectPtr, ObjectType};
use super::value::{ByteString, Function, Table2, Upvalue};
pub struct Heap {
internal: RefCell<InternalHeap>,
}
impl Heap {
#[allow(clippy::new_without_default)]
pub fn new() -> Heap {
Heap {
internal: RefCell::new(InternalHeap::new()),
}
}
pub fn allocate(&self, size: u32, ty: ObjectType) -> ObjectPtr {
self.internal.borrow_mut().allocate(size, ty)
}
pub fn allocate_string(&self, bytes: &[u8]) -> ObjectPtr {
let len = bytes.len() as u32;
let layout = ByteString::layout(len);
unsafe {
let ptr = self.allocate(layout.size() as u32, ObjectType::String);
let raw = ptr.cast::<ByteString>();
ByteString::initialize_into(raw, len);
ptr::copy_nonoverlapping(bytes.as_ptr(), (&mut *raw).offset(0), len as usize);
ptr
}
}
pub fn allocate_table(&self, capacity: u32) -> ObjectPtr {
let layout = Table2::layout(capacity);
unsafe {
let ptr = self.allocate(layout.size() as u32, ObjectType::Table);
let raw = ptr.cast::<Table2>();
Table2::initialize_into(raw, capacity);
ptr
}
}
pub fn allocate_function(
&self,
chunk: u32,
upvalue_len: u32,
upvalues: &mut dyn Iterator<Item = ObjectPtr>,
) -> ObjectPtr {
let layout = Function::layout(upvalue_len);
unsafe {
let ptr = self.allocate(layout.size() as u32, ObjectType::Function);
let raw = ptr.cast::<Function>();
Function::initialize_into(raw, chunk, upvalues);
ptr
}
}
pub(crate) fn allocate_upvalue(&self, location: usize) -> ObjectPtr {
let layout = Upvalue::layout();
unsafe {
let ptr = self.allocate(layout.size() as u32, ObjectType::Upvalue);
let raw = ptr.cast::<Upvalue>();
Upvalue::initialize_into(raw, location);
ptr
}
}
}

View file

@ -1,5 +1,6 @@
mod bytecode;
mod error;
pub mod gc;
pub mod gc2;
mod util;
pub mod value;
pub mod vm;

View file

@ -25,7 +25,7 @@ pub fn is_nil(x: u64) -> bool {
x == NIL_VALUE
}
pub fn make_nil() -> u64 {
pub const fn make_nil() -> u64 {
NIL_VALUE
}

View file

@ -1 +1,86 @@
pub struct Function {}
use std::alloc;
use super::{
super::gc2::{Object, ObjectPtr},
Value,
};
#[repr(C)]
pub struct Function {
chunk: u32,
upvalues: [ObjectPtr; 0],
}
impl Function {
pub(crate) unsafe fn initialize_into(
ptr: *mut Self,
chunk: u32,
upvalues: &mut dyn Iterator<Item = ObjectPtr>,
) {
(*ptr).chunk = chunk;
let base = (&mut (*ptr).upvalues) as *mut ObjectPtr;
for (i, upvalue) in upvalues.enumerate() {
base.add(i).write(upvalue);
}
}
pub fn layout(upvalues: u32) -> alloc::Layout {
let size =
std::mem::size_of::<u32>() + upvalues as usize * std::mem::size_of::<ObjectPtr>();
let align = std::mem::align_of::<ObjectPtr>();
alloc::Layout::from_size_align(size, align).unwrap()
}
pub fn chunk(&self) -> u32 {
self.chunk
}
pub fn get_upvalue(&self, stack: &mut [Value], key: u8) -> Value {
unsafe {
let base = (&self.upvalues) as *const ObjectPtr;
let obj = &*(*base.add(key as usize)).cast::<Upvalue>();
obj.load(stack)
}
}
pub fn set_upvalue(&mut self, stack: &mut [Value], key: u8, value: Value) {
unsafe {
let base = (&mut self.upvalues) as *mut ObjectPtr;
let obj = &*(*base.add(key as usize)).cast::<Upvalue>();
obj.set(stack, value);
}
}
}
impl Object for Function {
fn evacuate(&mut self, _visitor: &mut dyn FnMut(ObjectPtr) -> ObjectPtr) {}
}
pub struct Upvalue {
location: usize,
value: Value,
}
impl Upvalue {
pub(crate) unsafe fn initialize_into(ptr: *mut Self, location: usize) {
(*ptr).location = location;
(*ptr).value = Value::nil();
}
pub fn layout() -> alloc::Layout {
alloc::Layout::new::<Self>()
}
pub fn load(&self, stack: &mut [Value]) -> Value {
stack[self.location]
}
pub fn set(&self, stack: &mut [Value], value: Value) {
stack[self.location] = value;
}
}
impl Object for Upvalue {
fn evacuate(&mut self, _visitor: &mut dyn FnMut(ObjectPtr) -> ObjectPtr) {}
}

View file

@ -1,22 +1,18 @@
pub mod encoding;
mod function;
mod string;
mod table;
mod table2;
mod userdata;
use std::cmp::PartialEq;
use encoding::*;
pub use function::Function;
pub use function::{Function, Upvalue};
pub use string::ByteString;
pub use table::Table;
pub use table2::Table2;
pub use userdata::Userdata;
use super::{
gc::{Handle, TaggedHandle, Trace, Visitor},
util::mix_u64,
vm::ctx::Ctx,
};
use super::{gc2::ObjectPtr, Error};
#[derive(Debug, PartialEq)]
enum ValueType {
@ -46,30 +42,29 @@ macro_rules! dispatch {
match $x {
$(v if $guard(v) => $arm),*,
#[allow(unused_unsafe)]
_ => unsafe {
#[cfg(debug_assertions)]
unreachable!();
#[cfg(not(debug_assertions))]
std::hint::unreachable_unchecked();
},
_ => unreachable!(),
}
}}
}
fn int_op(iop: fn(i32, i32) -> i32, a: Value, b: Value) -> Value {
fn int_op(iop: fn(i32, i32) -> i32, a: Value, b: Value) -> Result<Value, Error> {
if a.ty() != ValueType::Int || b.ty() != ValueType::Int {
panic!("int_op: invalid types");
return Err(Error::new("int_op: invalid types"));
}
Value::from_int(iop(get_int(a.data), get_int(b.data)))
Ok(Value::from_int(iop(get_int(a.data), get_int(b.data))))
}
fn arith_op(iop: fn(i32, i32) -> Value, fop: fn(f64, f64) -> Value, a: Value, b: Value) -> Value {
fn arith_op(
iop: fn(i32, i32) -> Value,
fop: fn(f64, f64) -> Value,
a: Value,
b: Value,
) -> Result<Value, Error> {
if a.ty() == ValueType::Float || b.ty() == ValueType::Float {
fop(a.convert_float(), b.convert_float())
Ok(fop(a.convert_float()?, b.convert_float()?))
} else {
iop(a.cast_int(), b.cast_int())
Ok(iop(a.cast_int()?, b.cast_int()?))
}
}
@ -94,7 +89,7 @@ pub struct Value {
}
impl Value {
pub fn from_nil() -> Self {
pub const fn nil() -> Self {
Value { data: make_nil() }
}
@ -112,21 +107,21 @@ impl Value {
}
}
pub fn from_table(x: Handle<Table>) -> Self {
pub fn from_table2(x: ObjectPtr) -> Self {
Value {
data: make_table(x.as_ptr() as *mut u8),
data: make_table(x.raw()),
}
}
pub fn from_string(x: Handle<ByteString>) -> Self {
pub fn from_string(x: ObjectPtr) -> Self {
Value {
data: make_string(x.as_ptr() as *mut u8),
data: make_string(x.raw()),
}
}
pub fn from_function(x: *mut u8) -> Self {
pub fn from_function(x: ObjectPtr) -> Self {
Value {
data: make_function(x),
data: make_function(x.raw()),
}
}
@ -144,8 +139,24 @@ impl Value {
get_bool(self.data)
}
fn cast_table_unchecked<'a>(self) -> &'a Table {
unsafe { &*(get_table(self.data) as *const Table) }
pub fn cast_mut_table2<'a>(self) -> Option<&'a mut Table2> {
if self.ty() != ValueType::Table {
return None;
}
Some(unsafe { &mut *(get_table(self.data) as *mut Table2) })
}
pub fn cast_table_unchecked<'a>(self) -> &'a mut Table2 {
unsafe { &mut *(get_table(self.data) as *mut Table2) }
}
pub fn cast_function(self) -> Option<ObjectPtr> {
if self.ty() != ValueType::Function {
return None;
}
Some(ObjectPtr::from(get_function(self.data) as *mut Function))
}
pub fn is_truthy(self) -> bool {
@ -156,19 +167,19 @@ impl Value {
}
}
pub fn convert_float(self) -> f64 {
match self.ty() {
pub fn convert_float(self) -> Result<f64, Error> {
Ok(match self.ty() {
ValueType::Float => get_float(self.data),
ValueType::Int => get_int(self.data) as f64,
_ => panic!("cannot convert to float"),
}
_ => return Err(Error::new("cannot convert to float")),
})
}
pub fn cast_int(self) -> i32 {
match self.ty() {
pub fn cast_int(self) -> Result<i32, Error> {
Ok(match self.ty() {
ValueType::Int => get_int(self.data),
_ => panic!("value is not int"),
}
_ => return Err(Error::new("value is not int")),
})
}
fn ty(self) -> ValueType {
@ -188,36 +199,44 @@ impl Value {
Value::from_bool(self.data == other.data)
}
pub fn op_gt(self, other: Self) -> Value {
pub fn op_gt(self, other: Self) -> Result<Value, Error> {
let ty_1 = self.ty();
let ty_2 = other.ty();
if ty_1 != ty_2 {
return Value::from_bool(false);
return Ok(Value::from_bool(false));
}
Value::from_bool(match ty_1 {
Ok(Value::from_bool(match ty_1 {
ValueType::Int => get_int(self.data) > get_int(other.data),
ValueType::Float => get_float(self.data) > get_float(other.data),
ValueType::String => **self.cast_string_unchecked() > **other.cast_string_unchecked(),
_ => panic!("attempted op_gt on unsupported type: {:?}", ty_1),
})
_ =>
return Err(Error::new(&format!(
"attempted op_gt on unsupported type: {:?}",
ty_1
))),
}))
}
pub fn op_lt(self, other: Self) -> Value {
pub fn op_lt(self, other: Self) -> Result<Value, Error> {
let ty_1 = self.ty();
let ty_2 = other.ty();
if ty_1 != ty_2 {
return Value::from_bool(false);
return Ok(Value::from_bool(false));
}
Value::from_bool(match ty_1 {
Ok(Value::from_bool(match ty_1 {
ValueType::Int => get_int(self.data) < get_int(other.data),
ValueType::Float => get_float(self.data) < get_float(other.data),
ValueType::String => **self.cast_string_unchecked() < **other.cast_string_unchecked(),
_ => panic!("attempted op_lt on unsupported type: {:?}", ty_1),
})
_ =>
return Err(Error::new(&format!(
"attempted op_lt on unsupported type: {:?}",
ty_1
))),
}))
}
pub fn op_and(self, other: Self) -> Self {
@ -228,7 +247,7 @@ impl Value {
Value::from_bool(self.is_truthy() || other.is_truthy())
}
pub fn op_add(self, other: Self) -> Self {
pub fn op_add(self, other: Self) -> Result<Self, Error> {
arith_op(
|a, b| Value::from_int(a + b),
|a, b| Value::from_float(a + b),
@ -237,7 +256,7 @@ impl Value {
)
}
pub fn op_sub(self, other: Self) -> Self {
pub fn op_sub(self, other: Self) -> Result<Self, Error> {
arith_op(
|a, b| Value::from_int(a - b),
|a, b| Value::from_float(a - b),
@ -246,7 +265,7 @@ impl Value {
)
}
pub fn op_mul(self, other: Self) -> Self {
pub fn op_mul(self, other: Self) -> Result<Self, Error> {
arith_op(
|a, b| Value::from_int(a * b),
|a, b| Value::from_float(a * b),
@ -255,7 +274,7 @@ impl Value {
)
}
pub fn op_div(self, other: Self) -> Self {
pub fn op_div(self, other: Self) -> Result<Self, Error> {
arith_op(
|a, b| Value::from_float(a as f64 / b as f64),
|a, b| Value::from_float(a / b),
@ -264,7 +283,7 @@ impl Value {
)
}
pub fn op_int_div(self, other: Self) -> Self {
pub fn op_int_div(self, other: Self) -> Result<Self, Error> {
arith_op(
|a, b| Value::from_int(a / b),
|a, b| Value::from_int((a / b).floor() as i32),
@ -273,7 +292,7 @@ impl Value {
)
}
pub fn op_exp(self, other: Self) -> Self {
pub fn op_exp(self, other: Self) -> Result<Self, Error> {
arith_op(
|a, b| Value::from_int(a.pow(b as u32)),
|a, b| Value::from_float(a.powf(b)),
@ -282,7 +301,7 @@ impl Value {
)
}
pub fn op_mod(self, other: Self) -> Self {
pub fn op_mod(self, other: Self) -> Result<Self, Error> {
arith_op(
|a, b| Value::from_int(a % b),
|a, b| Value::from_float(a % b),
@ -291,23 +310,23 @@ impl Value {
)
}
pub fn op_bit_and(self, other: Self) -> Self {
pub fn op_bit_and(self, other: Self) -> Result<Self, Error> {
int_op(|a, b| a & b, self, other)
}
pub fn op_bit_or(self, other: Self) -> Self {
pub fn op_bit_or(self, other: Self) -> Result<Self, Error> {
int_op(|a, b| a | b, self, other)
}
pub fn op_lshift(self, other: Self) -> Self {
pub fn op_lshift(self, other: Self) -> Result<Self, Error> {
int_op(|a, b| a << b, self, other)
}
pub fn op_rshift(self, other: Self) -> Self {
pub fn op_rshift(self, other: Self) -> Result<Self, Error> {
int_op(|a, b| a >> b, self, other)
}
pub fn op_bit_xor(self, other: Self) -> Self {
pub fn op_bit_xor(self, other: Self) -> Result<Self, Error> {
int_op(|a, b| a ^ b, self, other)
}
@ -315,68 +334,75 @@ impl Value {
Value::from_bool(!get_bool(self.op_eq(other).data))
}
pub fn op_leq(self, other: Self) -> Self {
pub fn op_leq(self, other: Self) -> Result<Self, Error> {
let ty_1 = self.ty();
let ty_2 = other.ty();
if ty_1 != ty_2 {
return Value::from_bool(false);
return Ok(Value::from_bool(false));
}
Value::from_bool(match ty_1 {
Ok(Value::from_bool(match ty_1 {
ValueType::Int => get_int(self.data) <= get_int(other.data),
ValueType::Float => get_float(self.data) <= get_float(other.data),
ValueType::String => **self.cast_string_unchecked() <= **other.cast_string_unchecked(),
_ => panic!("attempted op_leq on unsupported type: {:?}", ty_1),
})
_ =>
return Err(Error::new(&format!(
"attempted op_leq on unsupported type: {:?}",
ty_1
))),
}))
}
pub fn op_geq(self, other: Self) -> Self {
pub fn op_geq(self, other: Self) -> Result<Self, Error> {
let ty_1 = self.ty();
let ty_2 = other.ty();
if ty_1 != ty_2 {
return Value::from_bool(false);
return Ok(Value::from_bool(false));
}
Value::from_bool(match ty_1 {
Ok(Value::from_bool(match ty_1 {
ValueType::Int => get_int(self.data) >= get_int(other.data),
ValueType::Float => get_float(self.data) >= get_float(other.data),
ValueType::String => **self.cast_string_unchecked() >= **other.cast_string_unchecked(),
_ => panic!("attempted op_geq on unsupported type: {:?}", ty_1),
})
_ =>
return Err(Error::new(&format!(
"attempted op_geq on unsupported type: {:?}",
ty_1
))),
}))
}
pub fn op_property(self, other: Self) -> Self {
pub fn op_property(self, other: Self) -> Result<Self, Error> {
if self.ty() != ValueType::Table {
panic!()
return Err(Error::new("attempted op_property on non-table"));
}
let table = self.cast_table_unchecked();
table.get(other)
Ok(table.get(other))
}
pub fn op_method(self, other: Self) -> Self {
pub fn op_method(self, other: Self) -> Result<Self, Error> {
if self.ty() != ValueType::Table {
panic!()
return Err(Error::new("attempted op_method on non-table"));
}
let table = self.cast_table_unchecked();
let value = table.get(other);
if value.ty() != ValueType::Function {
panic!()
}
value
Ok(value)
}
pub fn op_concat(self, other: Self, ctx: &Ctx) -> Self {
pub fn op_concat(
self,
other: Self,
alloc: &mut dyn FnMut(&[u8]) -> ObjectPtr,
) -> Result<Self, Error> {
let ty_1 = self.ty();
let ty_2 = other.ty();
if ty_1 != ValueType::String && ty_2 != ValueType::String {
panic!()
return Err(Error::new("attempted op_concat on non-string"));
}
let str_1 = self.cast_string_unchecked();
@ -384,24 +410,23 @@ impl Value {
let mut buf = Vec::new();
buf.extend_from_slice(str_1);
buf.extend_from_slice(str_2);
let new_str = ctx.intern(&buf);
Value::from_string(new_str)
Ok(Value::from_string(alloc(&buf)))
}
pub fn op_neg(self) -> Self {
match self.ty() {
pub fn op_neg(self) -> Result<Self, Error> {
Ok(match self.ty() {
ValueType::Int => Value::from_int(-get_int(self.data)),
ValueType::Float => Value::from_float(-get_float(self.data)),
_ => panic!(),
}
_ => return Err(Error::new("attempted op_neg on unsupported type")),
})
}
pub fn op_not(self) -> Self {
Value::from_bool(!self.is_truthy())
}
pub fn op_len(self) -> Self {
match self.ty() {
pub fn op_len(self) -> Result<Self, Error> {
Ok(match self.ty() {
ValueType::Table => {
let len = self.cast_table_unchecked().len();
Value::from_int(len as i32)
@ -410,35 +435,21 @@ impl Value {
let len = self.cast_string_unchecked().len();
Value::from_int(len as i32)
},
_ => panic!(),
}
_ => return Err(Error::new("attempted op_len on unsupported type")),
})
}
pub fn op_bit_not(self) -> Self {
pub fn op_bit_not(self) -> Result<Self, Error> {
if let ValueType::Int = self.ty() {
let x = get_int(self.data);
return Value::from_int(!x);
return Ok(Value::from_int(!x));
}
panic!()
Err(Error::new("attempted op_bit_not on unsupported type"))
}
pub fn op_hash(self) -> u64 {
mix_u64(self.data)
}
}
impl Trace for Value {
fn visit(&self, visitor: &mut Visitor) {
let handle = TaggedHandle::new(self.data);
if is_ptr(self.data) && !visitor.is_marked(handle) {
visitor.mark(handle);
if is_table(self.data) {
let table = unsafe { &mut *(get_table(self.data) as *mut Table) };
table.visit(visitor);
}
}
todo!()
// mix_u64(self.data)
}
}

View file

@ -1,6 +1,6 @@
use std::{alloc, mem::MaybeUninit, ops::Deref};
use std::{alloc, mem::MaybeUninit, ops::Deref, ptr::addr_of};
use super::{super::gc::PtrTag, encoding};
use super::super::gc2::{Object, ObjectPtr};
#[repr(C)]
pub struct ByteString {
@ -9,9 +9,7 @@ pub struct ByteString {
}
impl ByteString {
/// # Safety
/// - ptr must point to an uninitialized ByteString.
pub unsafe fn initialize_into(ptr: *mut Self, len: u32) {
pub(crate) unsafe fn initialize_into(ptr: *mut Self, len: u32) {
(*ptr).len = len;
}
@ -24,6 +22,13 @@ impl ByteString {
let align = std::mem::align_of::<u32>();
alloc::Layout::from_size_align(size, align).unwrap()
}
pub(crate) fn data<'a>(ptr: *mut Self) -> &'a [u8] {
unsafe {
let addr = addr_of!((*ptr).data) as *const u8;
std::slice::from_raw_parts(addr, (*ptr).len as usize)
}
}
}
impl Deref for ByteString {
@ -38,12 +43,6 @@ impl Deref for ByteString {
}
}
unsafe impl PtrTag for ByteString {
fn is(x: u64) -> bool {
encoding::is_string(x)
}
fn tag(x: usize) -> u64 {
encoding::make_string(x as *mut u8)
}
impl Object for ByteString {
fn evacuate(&mut self, _visitor: &mut dyn FnMut(ObjectPtr) -> ObjectPtr) {}
}

View file

@ -1,86 +0,0 @@
//! TODO(#29): Replace this with a butterfly-like structure.
use hashbrown::{hash_map, HashMap};
use super::{
super::gc::{Heap, PtrTag, Trace, Visitor},
encoding,
Value,
};
pub struct Table {
map: HashMap<Value, Value, (), Heap>,
}
impl Table {
pub fn new(heap: Heap) -> Self {
Table {
map: HashMap::with_hasher_in((), heap),
}
}
fn entry_mut(&mut self, key: Value) -> hash_map::RawEntryMut<'_, Value, Value, (), Heap> {
let hash = key.op_hash();
self.map
.raw_entry_mut()
.from_hash(hash, |other| key.op_eq(*other).cast_bool_unchecked())
}
pub fn get(&self, key: Value) -> Value {
let hash = key.op_hash();
self.map
.raw_entry()
.from_hash(hash, |other| key.op_eq(*other).cast_bool_unchecked())
.map(|(_, v)| v)
.copied()
.unwrap_or_else(Value::from_nil)
}
pub fn insert(&mut self, key: Value, value: Value) {
match self.entry_mut(key) {
hash_map::RawEntryMut::Vacant(entry) => {
let hash = key.op_hash();
entry.insert_with_hasher(hash, key, value, |key| key.op_hash());
},
hash_map::RawEntryMut::Occupied(mut entry) => {
*entry.get_mut() = value;
},
}
}
pub fn remove(&mut self, key: Value) {
if let hash_map::RawEntryMut::Occupied(entry) = self.entry_mut(key) {
entry.remove();
}
}
pub fn len(&self) -> usize {
self.map.len()
}
pub fn is_empty(&self) -> bool {
self.map.is_empty()
}
}
impl Trace for Table {
fn visit(&self, visitor: &mut Visitor) {
self.map.iter().for_each(|(key, value)| {
key.visit(visitor);
value.visit(visitor);
});
}
}
unsafe impl PtrTag for Table {
fn is(x: u64) -> bool {
encoding::is_table(x)
}
fn tag(x: usize) -> u64 {
encoding::make_table(x as *mut u8)
}
}

View file

@ -0,0 +1,94 @@
use core::ptr::addr_of_mut;
use std::{alloc, mem::MaybeUninit, ptr};
use super::{
super::gc2::{Object, ObjectPtr},
Value,
};
const LOAD_FACTOR_THRESHOLD: f32 = 0.75;
const EMPTY_SLOT: Slot = Slot {
key: Value::nil(),
value: Value::nil(),
};
#[derive(Clone, Copy)]
struct Slot {
key: Value,
value: Value,
}
macro_rules! slots {
($t:expr) => {{
let addr = addr_of_mut!((*$t).data) as *mut Slot;
&mut *ptr::slice_from_raw_parts_mut(addr, (*$t).capacity as usize)
}};
}
#[repr(C)]
pub struct Table2 {
capacity: u32,
len: u32,
data: [MaybeUninit<Slot>; 0],
}
impl Table2 {
pub(crate) unsafe fn initialize_into(ptr: *mut Self, capacity: u32) {
(*ptr).capacity = capacity;
(*ptr).len = 0;
for slot in slots!(ptr) {
*slot = EMPTY_SLOT;
}
}
pub fn layout(capacity: u32) -> alloc::Layout {
let size = std::mem::size_of::<Table2>() + capacity as usize * std::mem::size_of::<Slot>();
let align = std::mem::align_of::<Slot>();
alloc::Layout::from_size_align(size, align).unwrap()
}
fn load_factor(&self) -> f32 {
self.len as f32 / self.capacity as f32
}
fn grow(&self) {
todo!()
}
// TODO: handle deletions
pub fn set(&mut self, key: Value, value: Value) {
let mut idx = key.op_hash() as usize;
let slots = unsafe { slots!(self) };
loop {
idx %= self.capacity as usize;
let slot = &mut slots[idx];
if slot.key == Value::nil() || slot.key == key {
slot.key = key;
slot.value = value;
self.len += 1;
break;
}
idx += 1;
}
if self.load_factor() > LOAD_FACTOR_THRESHOLD {
self.grow();
}
}
pub fn get(&self, _key: Value) -> Value {
todo!()
}
pub(crate) fn len(&self) -> usize {
todo!()
}
}
impl Object for Table2 {
fn evacuate(&mut self, _visitor: &mut dyn FnMut(ObjectPtr) -> ObjectPtr) {}
}

View file

@ -1 +1,7 @@
use super::super::gc2::{Object, ObjectPtr};
pub struct Userdata {}
impl Object for Userdata {
fn evacuate(&mut self, _visitor: &mut dyn FnMut(ObjectPtr) -> ObjectPtr) {}
}

308
src/engine/vm.rs Normal file
View file

@ -0,0 +1,308 @@
use std::{
collections::{hash_map, HashMap, LinkedList},
hash::{BuildHasher, Hash, Hasher},
};
use super::{
bytecode::Instruction,
gc2::{Heap, ObjectPtr},
value::{ByteString, Table2, Value},
Error,
};
use crate::engine::value::Function;
fn intern(heap: &Heap, strings: &mut HashMap<ObjectPtr, ()>, buf: &[u8]) -> ObjectPtr {
let mut hasher = strings.hasher().build_hasher();
buf.hash(&mut hasher);
let hash = hasher.finish();
let compare = |other: &ObjectPtr| buf == ByteString::data(other.cast::<ByteString>());
let raw = strings.raw_entry_mut().from_hash(hash, compare);
match raw {
hash_map::RawEntryMut::Occupied(entry) => *entry.key(),
hash_map::RawEntryMut::Vacant(entry) => {
let ptr = heap.allocate_string(buf);
entry.insert_hashed_nocheck(hash, ptr, ());
ptr
},
}
}
pub struct VM {
heap: Heap,
global: ObjectPtr,
strings: HashMap<ObjectPtr, ()>,
chunks: HashMap<u32, Chunk>,
callstack: Vec<CallFrame>,
acall: LinkedList<Value>,
registers: Vec<Value>,
}
impl VM {
pub(crate) fn new() -> Self {
let heap = Heap::new();
let global = heap.allocate_table(0);
VM {
heap,
global,
strings: HashMap::new(),
chunks: HashMap::new(),
callstack: Vec::new(),
acall: LinkedList::new(),
registers: Vec::new(),
}
}
pub fn run(&mut self, function: ObjectPtr) -> Result<Value, Error> {
let frame = precall(&mut self.registers, function, &self.chunks, 0, 0);
self.callstack.push(frame);
self.execute_pop()
}
fn execute_pop(&mut self) -> Result<Value, Error> {
'call: loop {
if self.callstack.is_empty() {
return Ok(Value::nil());
}
let frame = self.callstack.last_mut().unwrap();
let chunk_id = unsafe { (*frame.function.cast::<Function>()).chunk() };
let chunk = &self.chunks[&chunk_id];
macro_rules! r_reg {
($reg:expr) => {{
&mut self.registers[$reg.0 as usize + frame.register_offset]
}};
}
macro_rules! reg {
($reg:expr) => {{
*r_reg!($reg)
}};
}
loop {
match &chunk.tape[frame.pc] {
Instruction::ISLT(a, b) => {
frame.cond = reg!(a).op_lt(reg!(b))?.cast_bool_unchecked();
},
Instruction::ISLE(a, b) => {
frame.cond = reg!(a).op_leq(reg!(b))?.cast_bool_unchecked();
},
Instruction::ISEQ(a, b) => {
frame.cond = reg!(a).op_eq(reg!(b)).cast_bool_unchecked();
},
Instruction::ISNEQ(a, b) => {
frame.cond = reg!(a).op_neq(reg!(b)).cast_bool_unchecked();
},
Instruction::IST(a) => {
frame.cond = reg!(a).is_truthy();
},
Instruction::ISF(a) => {
frame.cond = !reg!(a).is_truthy();
},
Instruction::MOV(a, b) => {
*r_reg!(a) = reg!(b);
},
Instruction::NOT(a, b) => {
*r_reg!(a) = reg!(b).op_not();
},
Instruction::NEG(a, b) => {
*r_reg!(a) = reg!(b).op_neg()?;
},
Instruction::LEN(a, b) => {
*r_reg!(a) = reg!(b).op_len()?;
},
Instruction::BITNOT(a, b) => {
*r_reg!(a) = reg!(b).op_bit_not()?;
},
Instruction::ADD(a, b, c) => {
*r_reg!(a) = reg!(b).op_add(reg!(c))?;
},
Instruction::SUB(a, b, c) => {
*r_reg!(a) = reg!(b).op_sub(reg!(c))?;
},
Instruction::MUL(a, b, c) => {
*r_reg!(a) = reg!(b).op_mul(reg!(c))?;
},
Instruction::DIV(a, b, c) => {
*r_reg!(a) = reg!(b).op_div(reg!(c))?;
},
Instruction::INTDIV(a, b, c) => {
*r_reg!(a) = reg!(b).op_int_div(reg!(c))?;
},
Instruction::EXP(a, b, c) => {
*r_reg!(a) = reg!(b).op_exp(reg!(c))?;
},
Instruction::MOD(a, b, c) => {
*r_reg!(a) = reg!(b).op_mod(reg!(c))?;
},
Instruction::BITAND(a, b, c) => {
*r_reg!(a) = reg!(b).op_bit_and(reg!(c))?;
},
Instruction::BITOR(a, b, c) => {
*r_reg!(a) = reg!(b).op_bit_or(reg!(c))?;
},
Instruction::BITXOR(a, b, c) => {
*r_reg!(a) = reg!(b).op_bit_xor(reg!(c))?;
},
Instruction::LEFTSHIFT(a, b, c) => {
*r_reg!(a) = reg!(b).op_lshift(reg!(c))?;
},
Instruction::RIGHTSHIFT(a, b, c) => {
*r_reg!(a) = reg!(b).op_rshift(reg!(c))?;
},
Instruction::AND(a, b, c) => {
*r_reg!(a) = reg!(b).op_and(reg!(c));
},
Instruction::OR(a, b, c) => {
*r_reg!(a) = reg!(b).op_or(reg!(c));
},
Instruction::CONCAT(a, b, c) => {
*r_reg!(a) = reg!(b).op_concat(reg!(c), &mut |buf| {
intern(&self.heap, &mut self.strings, buf)
})?;
},
Instruction::CLOAD(dst, idx) => {
*r_reg!(dst) = chunk.constants[idx.0 as usize];
},
Instruction::UGET(dst, key) =>
*r_reg!(dst) = unsafe {
(*frame.function.cast::<Function>())
.get_upvalue(&mut self.registers, key.0)
},
Instruction::USET(dst, key) => unsafe {
let value = reg!(dst);
(*frame.function.cast::<Function>()).set_upvalue(
&mut self.registers,
key.0,
value,
)
},
Instruction::FNEW(dst, chunk, captures) => {
let upvalues = &mut captures.iter().map(|capture| {
self.heap
.allocate_upvalue(capture.slot.0 as usize + frame.register_offset)
});
let function = self.heap.allocate_function(
chunk.0 as u32,
captures.len() as u32,
upvalues,
);
*r_reg!(dst) = Value::from_function(function);
},
Instruction::TNEW(dst) => {
let table = self.heap.allocate_table(0);
*r_reg!(dst) = Value::from_table2(table);
},
Instruction::TGET(table, dst, key) => {
let table = reg!(table)
.cast_mut_table2()
.ok_or_else(|| Error::new("attempted to get value from non-table"))?;
*r_reg!(dst) = table.get(reg!(key));
},
Instruction::TSET(table, key, value) => {
let table = reg!(table)
.cast_mut_table2()
.ok_or_else(|| Error::new("attempted to set key in non-table"))?;
table.set(reg!(key), reg!(value));
},
Instruction::GGET(dst, key) => {
let table = unsafe { &*self.global.cast::<Table2>() };
*r_reg!(dst) = table.get(reg!(key));
},
Instruction::GSET(key, value) => {
let table = unsafe { &mut *self.global.cast::<Table2>() };
table.set(reg!(key), reg!(value));
},
Instruction::JMP(new_pc) => {
frame.pc = new_pc.0 as usize;
continue;
},
Instruction::APUSH(src) => {
self.acall.push_front(reg!(src));
},
Instruction::APOP(dst) => {
*r_reg!(dst) = self.acall.pop_back().unwrap();
},
Instruction::ACLEAR => {
self.acall.clear();
},
Instruction::CALL(src) => {
let function = reg!(src)
.cast_function()
.ok_or_else(|| Error::new("attempted to call non-fuction"))?;
let frame = precall(
&mut self.registers,
function,
&self.chunks,
frame.register_offset,
chunk.register_count,
);
self.callstack.push(frame);
continue 'call;
},
Instruction::RET => {
self.callstack.pop();
continue 'call;
// TODO: close upvalues
},
}
frame.pc += 1;
}
}
}
}
fn precall(
registers: &mut Vec<Value>,
function: ObjectPtr,
chunks: &HashMap<u32, Chunk>,
register_offset: usize,
register_count: usize,
) -> CallFrame {
let func = unsafe { &*function.cast::<Function>() };
let chunk_id = func.chunk();
let new_chunk = &chunks[&chunk_id];
let register_offset = register_offset + register_count;
let req_reg = register_offset + 1 + new_chunk.register_count;
if req_reg > registers.len() {
let addi = registers.len() - req_reg;
registers.reserve(addi);
for _ in 0..addi {
registers.push(Value::nil());
}
}
CallFrame {
function,
register_offset,
pc: 0,
cond: false,
}
}
struct CallFrame {
function: ObjectPtr,
register_offset: usize,
pc: usize,
cond: bool,
}
struct Chunk {
tape: Vec<Instruction>,
constants: Vec<Value>,
register_count: usize,
arity: usize,
}

View file

@ -1,134 +0,0 @@
use std::{
borrow::Borrow,
cell::{Ref, RefCell},
collections::hash_map::RandomState,
};
use cstree::interning::TokenInterner;
use hashbrown::{HashMap, HashSet};
use super::super::{
gc::{Handle, Heap},
value::{ByteString, Table, Value},
};
use crate::parser::syntax::Ident;
struct CtxInternal<'a> {
global: &'a mut Table,
scope: Vec<HashMap<Handle<ByteString>, Value, RandomState>>,
heap: &'a Heap,
interner: &'a TokenInterner,
strings: &'a mut HashSet<Handle<ByteString>, RandomState>,
}
pub struct Ctx<'a> {
internal: RefCell<CtxInternal<'a>>,
}
impl<'a> Ctx<'a> {
pub fn new(
global: &'a mut Table,
heap: &'a Heap,
interner: &'a TokenInterner,
strings: &'a mut HashSet<Handle<ByteString>, RandomState>,
) -> Self {
Ctx {
internal: RefCell::new(CtxInternal {
global,
scope: vec![HashMap::with_hasher(RandomState::new())],
heap,
interner,
strings,
}),
}
}
pub fn heap(&self) -> Ref<Heap> {
Ref::map(self.internal.borrow(), |internal| internal.heap)
}
pub fn scope(&self) -> ScopeKey<'a, '_> {
let mut internal = self.internal.borrow_mut();
internal
.scope
.push(HashMap::with_hasher(RandomState::new()));
ScopeKey { ctx: self }
}
fn scope_destroy(&self) {
let mut internal = self.internal.borrow_mut();
internal.scope.pop();
}
pub fn local(&self, key: Handle<ByteString>) {
self.internal
.borrow_mut()
.scope
.last_mut()
.unwrap()
.insert(key, Value::from_nil());
}
pub fn assign(&self, key: Handle<ByteString>, value: Value) {
let mut internal = self.internal.borrow_mut();
for scope in internal.scope.iter_mut().rev() {
if scope.contains_key(&key) {
scope.insert(key, value);
return;
}
}
let key = Value::from_string(key);
internal.global.insert(key, value);
}
pub fn resolve(&self, key: Handle<ByteString>) -> Value {
let internal = self.internal.borrow();
for scope in internal.scope.iter().rev() {
if let Some(value) = scope.get(&key) {
return *value;
}
}
let key = Value::from_string(key);
internal.global.get(key)
}
pub fn intern(&self, key: &[u8]) -> Handle<ByteString> {
let mut internal = self.internal.borrow_mut();
impl Borrow<[u8]> for Handle<ByteString> {
fn borrow(&self) -> &[u8] {
unsafe { self.get_unchecked() }
}
}
if let Some(handle) = internal.strings.get(key) {
return *handle;
}
let handle = internal.heap.insert_string(key);
internal.strings.insert(handle);
handle
}
pub fn intern_ident(&self, ident: &Ident) -> Handle<ByteString> {
let internal = self.internal.borrow();
let name = ident.name(internal.interner).unwrap();
drop(internal);
self.intern(name.as_bytes())
}
}
pub struct ScopeKey<'a, 'ctx> {
ctx: &'ctx Ctx<'a>,
}
impl<'a, 'ctx> Drop for ScopeKey<'a, 'ctx> {
fn drop(&mut self) {
self.ctx.scope_destroy();
}
}

View file

@ -1,394 +0,0 @@
use std::{convert::Infallible, ops};
use super::{
super::{value::Value, Error},
ctx::Ctx,
};
use crate::parser::syntax::{
Assign,
BinaryOp,
BinaryOperator,
Break,
Decl,
Do,
Expr,
ForGen,
ForNum,
Func,
FuncCall,
FuncExpr,
Ident,
If,
Index,
Literal,
PrefixOp,
PrefixOperator,
Repeat,
Return,
Root,
Stmt,
Table,
While,
};
pub trait Eval {
fn eval(&self, ctx: &Ctx) -> Result;
}
pub enum Result<T = Value> {
Value(T),
Return(Vec<Value>),
Break,
Error(Error),
}
impl ops::Try for Result {
type Output = Value;
type Residual = Result<Infallible>;
fn from_output(value: Value) -> Self {
Result::Value(value)
}
fn branch(self) -> ops::ControlFlow<Self::Residual, Self::Output> {
match self {
Result::Value(value) => ops::ControlFlow::Continue(value),
Result::Return(value) => ops::ControlFlow::Break(Result::Return(value)),
Result::Break => ops::ControlFlow::Break(Result::Break),
Result::Error(error) => ops::ControlFlow::Break(Result::Error(error)),
}
}
}
impl ops::FromResidual for Result {
fn from_residual(residual: Result<Infallible>) -> Result {
match residual {
Result::Value(_) => panic!(),
Result::Return(value) => Result::Return(value),
Result::Break => Result::Break,
Result::Error(error) => Result::Error(error),
}
}
}
impl From<Result> for std::result::Result<Value, Error> {
fn from(result: Result) -> std::result::Result<Value, Error> {
match result {
Result::Value(value) => Ok(value),
Result::Return(_) => Err(Error::UncaughtReturn),
Result::Break => Err(Error::UncaughtBreak),
Result::Error(error) => Err(error),
}
}
}
impl Eval for Root {
fn eval(&self, ctx: &Ctx) -> Result {
for stmt in self.block() {
stmt.eval(ctx)?;
}
Result::Value(Value::from_nil())
}
}
impl Eval for Stmt {
fn eval(&self, ctx: &Ctx) -> Result {
match self {
Self::Decl(decl) => decl.eval(ctx),
Self::Assign(assign) => assign.eval(ctx),
Self::Func(func) => func.eval(ctx),
Self::Expr(expr) => expr.eval(ctx),
Self::Break(r#break) => r#break.eval(ctx),
Self::Return(r#return) => r#return.eval(ctx),
Self::Do(r#do) => r#do.eval(ctx),
Self::While(r#while) => r#while.eval(ctx),
Self::Repeat(repeat) => repeat.eval(ctx),
Self::If(r#if) => r#if.eval(ctx),
Self::ForNum(for_num) => for_num.eval(ctx),
Self::ForGen(for_gen) => for_gen.eval(ctx),
}
}
}
impl Eval for Decl {
fn eval(&self, _ctx: &Ctx) -> Result {
todo!()
}
}
impl Eval for Assign {
fn eval(&self, _ctx: &Ctx) -> Result {
todo!()
}
}
impl Eval for Func {
fn eval(&self, _ctx: &Ctx) -> Result {
todo!()
}
}
impl Eval for Expr {
fn eval(&self, ctx: &Ctx) -> Result {
match self {
Self::Ident(ident) => ident.eval(ctx),
Self::Literal(literal) => literal.eval(ctx),
Self::Func(func) => func.eval(ctx),
Self::Table(call) => call.eval(ctx),
Self::PrefixOp(prefix_op) => prefix_op.eval(ctx),
Self::BinaryOp(binary_op) => binary_op.eval(ctx),
Self::FuncCall(call) => call.eval(ctx),
Self::Index(index) => index.eval(ctx),
Self::VarArg => panic!("varargs are currently unsupported"),
}
}
}
impl Eval for Ident {
fn eval(&self, ctx: &Ctx) -> Result {
let key = ctx.intern_ident(self);
Result::Value(ctx.resolve(key))
}
}
impl Eval for Literal {
fn eval(&self, _ctx: &Ctx) -> Result {
todo!()
}
}
impl Eval for FuncExpr {
fn eval(&self, _ctx: &Ctx) -> Result {
todo!()
}
}
impl Eval for Table {
fn eval(&self, _ctx: &Ctx) -> Result {
todo!()
}
}
impl Eval for PrefixOp {
fn eval(&self, ctx: &Ctx) -> Result {
let rhs = self.rhs().unwrap().eval(ctx)?;
Result::Value(match self.op().unwrap() {
PrefixOperator::None => rhs,
PrefixOperator::Neg => rhs.op_neg(),
PrefixOperator::Not => rhs.op_not(),
PrefixOperator::Len => rhs.op_len(),
PrefixOperator::BitNot => rhs.op_bit_not(),
})
}
}
impl Eval for BinaryOp {
fn eval(&self, ctx: &Ctx) -> Result {
let lhs = self.lhs().unwrap().eval(ctx)?;
let rhs = self.rhs().unwrap().eval(ctx)?;
Result::Value(match self.op().unwrap() {
BinaryOperator::And => lhs.op_and(rhs),
BinaryOperator::Or => lhs.op_or(rhs),
BinaryOperator::Add => lhs.op_add(rhs),
BinaryOperator::Sub => lhs.op_sub(rhs),
BinaryOperator::Mul => lhs.op_mul(rhs),
BinaryOperator::Div => lhs.op_div(rhs),
BinaryOperator::IntDiv => lhs.op_int_div(rhs),
BinaryOperator::Exp => lhs.op_exp(rhs),
BinaryOperator::Mod => lhs.op_mod(rhs),
BinaryOperator::BitAnd => lhs.op_bit_and(rhs),
BinaryOperator::BitOr => lhs.op_bit_or(rhs),
BinaryOperator::LShift => lhs.op_lshift(rhs),
BinaryOperator::RShift => lhs.op_rshift(rhs),
BinaryOperator::Eq => lhs.op_eq(rhs),
BinaryOperator::BitXor => lhs.op_bit_xor(rhs),
BinaryOperator::NEq => lhs.op_neq(rhs),
BinaryOperator::LEq => lhs.op_leq(rhs),
BinaryOperator::GEq => lhs.op_geq(rhs),
BinaryOperator::Gt => lhs.op_gt(rhs),
BinaryOperator::Lt => lhs.op_lt(rhs),
BinaryOperator::Property => lhs.op_property(rhs),
BinaryOperator::Method => lhs.op_method(rhs),
BinaryOperator::Concat => lhs.op_concat(rhs, ctx),
})
}
}
impl Eval for Index {
fn eval(&self, _ctx: &Ctx) -> Result {
todo!()
}
}
impl Eval for FuncCall {
fn eval(&self, _ctx: &Ctx) -> Result {
todo!()
}
}
impl Eval for Break {
fn eval(&self, _ctx: &Ctx) -> Result {
Result::Break
}
}
impl Eval for Return {
fn eval(&self, ctx: &Ctx) -> Result {
let mut values = Vec::new();
for expr in self.exprs().unwrap() {
values.push(expr.eval(ctx)?);
}
Result::Return(values)
}
}
impl Eval for Do {
fn eval(&self, ctx: &Ctx) -> Result {
let _scope = ctx.scope();
for stmt in self.stmts() {
stmt.eval(ctx)?;
}
Result::Value(Value::from_nil())
}
}
impl Eval for While {
fn eval(&self, ctx: &Ctx) -> Result {
while self.cond().unwrap().eval(ctx)?.cast_bool_unchecked() {
let _scope = ctx.scope();
for stmt in self.block().unwrap() {
let res = stmt.eval(ctx);
if let Result::Break = res {
break;
}
res?;
}
}
Result::Value(Value::from_nil())
}
}
impl Eval for Repeat {
fn eval(&self, ctx: &Ctx) -> Result {
loop {
let _scope = ctx.scope();
for stmt in self.block().unwrap() {
let res = stmt.eval(ctx);
if let Result::Break = res {
break;
}
res?;
}
if self.cond().unwrap().eval(ctx)?.cast_bool_unchecked() {
break;
}
}
Result::Value(Value::from_nil())
}
}
impl Eval for If {
fn eval(&self, ctx: &Ctx) -> Result {
if self.cond().unwrap().eval(ctx)?.cast_bool_unchecked() {
let _scope = ctx.scope();
for stmt in self.stmts().unwrap() {
let res = stmt.eval(ctx);
if let Result::Break = res {
break;
}
res?;
}
} else if let Some(elif) = self.else_chain() {
if let Some(el_if) = elif.elseif_block() {
el_if.eval(ctx)?;
} else if let Some(el) = elif.else_block() {
let _scope = ctx.scope();
for stmt in el {
let res = stmt.eval(ctx);
if let Result::Break = res {
break;
}
res?;
}
}
}
Result::Value(Value::from_nil())
}
}
impl Eval for ForNum {
fn eval(&self, ctx: &Ctx) -> Result {
let (counter, init) = self.counter().unwrap();
let init = init.eval(ctx)?;
let end = self.end().unwrap().eval(ctx)?;
let step = if let Some(expr) = self.step() {
expr.eval(ctx)?
} else {
Value::from_int(1)
};
let _scope = ctx.scope();
let var = ctx.intern_ident(&counter);
ctx.local(var);
ctx.assign(var, init);
while ctx.resolve(var).op_eq(end).cast_bool_unchecked() {
let _scope = ctx.scope();
for stmt in self.block().unwrap() {
stmt.eval(ctx)?;
}
let value = ctx.resolve(var);
let value = value.op_add(step);
ctx.assign(var, value);
}
Result::Value(Value::from_nil())
}
}
impl Eval for ForGen {
fn eval(&self, ctx: &Ctx) -> Result {
let _scope = ctx.scope();
for target in self.targets().unwrap() {
let var = ctx.intern_ident(&target);
ctx.local(var);
}
loop {
for (target, value) in self.targets().unwrap().zip(self.values().unwrap()) {
let var = ctx.intern_ident(&target);
let value = value.eval(ctx)?;
ctx.assign(var, value);
}
let first_target = self.targets().unwrap().next().unwrap();
let first_var = ctx.intern_ident(&first_target);
if ctx.resolve(first_var) == Value::from_nil() {
break;
}
let _scope = ctx.scope();
for stmt in self.block().unwrap() {
stmt.eval(ctx)?;
}
}
Result::Value(Value::from_nil())
}
}

View file

@ -1,49 +0,0 @@
pub mod ctx;
pub mod eval;
use std::collections::hash_map::RandomState;
use cstree::interning::TokenInterner;
use ctx::Ctx;
use eval::Eval;
use hashbrown::{HashMap, HashSet};
use super::{
gc::{Handle, Heap},
value::{ByteString, Table, Value},
Error,
};
// TODO:
// - vm eval impl
// - gc root tracked values in the api
// - impl _ENV
// - handle multivalue
pub struct VM {
global: Table,
strings: HashSet<Handle<ByteString>, RandomState>,
extern_ref: HashMap<Value, usize, RandomState>,
}
impl VM {
pub fn new(heap: Heap) -> Self {
VM {
global: Table::new(heap),
strings: HashSet::with_hasher(RandomState::new()),
extern_ref: HashMap::with_hasher(RandomState::new()),
}
}
pub fn eval<T>(
&mut self,
item: &T,
heap: &Heap,
interner: &TokenInterner,
) -> Result<Value, Error>
where
T: Eval,
{
let ctx = Ctx::new(&mut self.global, heap, interner, &mut self.strings);
item.eval(&ctx).into()
}
}

View file

@ -1,6 +1,7 @@
#![feature(allocator_api)]
#![feature(cell_update)]
#![feature(try_trait_v2)]
#![feature(hash_raw_entry)]
#![allow(dead_code)]
pub mod engine;