feat: gc (does compile, but WIP)
This commit is contained in:
256
src/vm/mod.rs
256
src/vm/mod.rs
@@ -1,9 +1,9 @@
|
||||
use std::cell::{Cell, RefCell};
|
||||
use std::cell::RefCell;
|
||||
|
||||
use gc_arena::lock::{GcRefLock, RefLock};
|
||||
use gc_arena::{Arena, Collect, DynamicRootSet, Gc, Mutation, Rootable};
|
||||
use gc_arena::{Arena, Collect, Gc, Mutation, Rootable};
|
||||
use hashbrown::{HashMap, HashSet};
|
||||
use inkwell::context::Context;
|
||||
use itertools::Itertools;
|
||||
|
||||
use crate::builtins::env;
|
||||
use crate::bytecode::{BinOp, Func as F, OpCode, OpCodes, Program, UnOp};
|
||||
@@ -28,9 +28,8 @@ type GcArena = Arena<Rootable!['gc => GcRoot<'gc>]>;
|
||||
#[collect(require_static)]
|
||||
struct ContextWrapper(Context);
|
||||
|
||||
|
||||
pub fn run(mut prog: Program) -> Result<p::Value> {
|
||||
fn new<'gc>(prog: &mut Program, mc: &'gc Mutation<'gc>) -> GcRoot<'gc> {
|
||||
let mut arena: Arena<Rootable![GcRoot<'_>]> = Arena::new(|mc| {
|
||||
let jit = Gc::new(mc, ContextWrapper(Context::create()));
|
||||
let thunks = std::mem::take(&mut prog.thunks);
|
||||
let funcs = std::mem::take(&mut prog.funcs);
|
||||
@@ -49,46 +48,113 @@ pub fn run(mut prog: Program) -> Result<p::Value> {
|
||||
GcRoot {
|
||||
vm,
|
||||
jit,
|
||||
ctxs: DynamicRootSet::new(mc)
|
||||
stack: Stack::new(),
|
||||
envs: vec![env(&vm, mc)],
|
||||
}
|
||||
}
|
||||
let arena: Arena<Rootable![GcRoot<'_>]> = Arena::new(|mc| {
|
||||
new(&mut prog, mc)
|
||||
});
|
||||
eval(prog.top_level.into_iter(), &arena, |val, vm| {
|
||||
Ok(val.to_public(vm, &mut HashSet::new()))
|
||||
eval(prog.top_level, &mut arena, |val, root, _| {
|
||||
Ok(val.to_public(&root.vm, &mut HashSet::new()))
|
||||
})
|
||||
}
|
||||
|
||||
fn eval<'gc, T, F: for<'a> FnOnce(Value<'a>, &VM<'a>) -> Result<T>>(
|
||||
opcodes: impl Iterator<Item = OpCode>,
|
||||
arena: &Arena<impl for<'a> Rootable<'a, Root = GcRoot<'a>>>,
|
||||
f: F
|
||||
pub fn eval<T, F: for<'gc> FnOnce(Value<'gc>, &mut GcRoot<'gc>, &Mutation<'gc>) -> Result<T>>(
|
||||
opcodes: Box<[OpCode]>,
|
||||
arena: &mut Arena<impl for<'gc> Rootable<'gc, Root = GcRoot<'gc>>>,
|
||||
f: F,
|
||||
) -> Result<T> {
|
||||
let mut iter = opcodes.into_iter();
|
||||
let dynroot: gc_arena::DynamicRoot<Rootable!['a => GcRefLock<'a, EvalContext<'a>>]> = arena.mutate(|mc, root| {
|
||||
root.ctxs.stash(mc, Gc::new(mc, RefLock::new( EvalContext::new(env(&root.vm, mc)))))
|
||||
});
|
||||
while let Some(opcode) = iter.next() {
|
||||
arena.mutate(|mc, root| {
|
||||
let mut ctx_mut = root.ctxs.fetch(&dynroot).borrow_mut(mc);
|
||||
let ctx = &mut *ctx_mut;
|
||||
let jmp = single_op(&root.vm, opcode, &mut ctx.stack, &mut ctx.env, mc)?;
|
||||
for _ in 0..jmp {
|
||||
iter.next().unwrap();
|
||||
let mut opcodes = opcodes.into_vec();
|
||||
opcodes.reverse();
|
||||
while let Some(opcode) = opcodes.pop() {
|
||||
arena.mutate_root(|mc, root| {
|
||||
let consq = single_op(
|
||||
&root.vm,
|
||||
opcode,
|
||||
&mut root.stack,
|
||||
root.envs.last_mut().unwrap(),
|
||||
mc,
|
||||
)?;
|
||||
match consq {
|
||||
Consq::NoOp => (),
|
||||
Consq::Jmp(step) => opcodes.resize_with(opcodes.len() - step, || unreachable!()),
|
||||
Consq::Force => {
|
||||
let thunk = root.stack.tos().as_ref().unwrap_thunk();
|
||||
let (code, env) = thunk.suspend(mc);
|
||||
let mut code = code.to_vec();
|
||||
code.reverse();
|
||||
opcodes.push(OpCode::InsertValue);
|
||||
opcodes.push(OpCode::PopEnv);
|
||||
opcodes.extend(code);
|
||||
root.envs.push(env);
|
||||
}
|
||||
Consq::PopEnv => _ = root.envs.pop().unwrap(),
|
||||
Consq::Call => {
|
||||
use Param::*;
|
||||
let arg = root.stack.pop();
|
||||
let func = root.stack.pop().unwrap_func();
|
||||
let mut env = func.env;
|
||||
env = match func.func.param.clone() {
|
||||
Ident(ident) => env.enter_arg(ident, arg, mc),
|
||||
Formals {
|
||||
formals,
|
||||
ellipsis,
|
||||
alias,
|
||||
} => {
|
||||
let arg = arg.unwrap_attr_set();
|
||||
let mut new =
|
||||
HashMap::with_capacity(formals.len() + alias.iter().len());
|
||||
if !ellipsis
|
||||
&& arg
|
||||
.iter()
|
||||
.map(|(k, _)| k)
|
||||
.sorted()
|
||||
.ne(formals.iter().map(|(k, _)| k).sorted())
|
||||
{
|
||||
todo!()
|
||||
}
|
||||
for (formal, default) in formals {
|
||||
// TODO: rec env
|
||||
let arg = arg
|
||||
.select(formal)
|
||||
.or_else(|| {
|
||||
default.map(|idx| {
|
||||
Value::Thunk(
|
||||
Thunk::new(root.vm.get_thunk(idx), mc).into(),
|
||||
)
|
||||
})
|
||||
})
|
||||
.unwrap();
|
||||
new.insert(formal, arg);
|
||||
}
|
||||
if let Some(alias) = alias {
|
||||
new.insert(alias, Value::AttrSet(arg));
|
||||
}
|
||||
env.enter_let(Gc::new(mc, new.into()), mc)
|
||||
}
|
||||
};
|
||||
root.envs.push(env);
|
||||
let mut code = func.func.opcodes.to_vec();
|
||||
code.reverse();
|
||||
opcodes.push(OpCode::PopEnv);
|
||||
opcodes.extend(code);
|
||||
}
|
||||
}
|
||||
Result::Ok(())
|
||||
})?;
|
||||
}
|
||||
arena.mutate(|mc, root| {
|
||||
let mut ctx = root.ctxs.fetch(&dynroot).borrow_mut(mc);
|
||||
assert_eq!(ctx.stack.len(), 1);
|
||||
let mut ret = ctx.stack.pop();
|
||||
ret.force(&root.vm, mc);
|
||||
f(ret, &root.vm)
|
||||
arena.mutate_root(|mc, root| {
|
||||
assert_eq!(root.stack.len(), 1);
|
||||
let ret = root.stack.pop();
|
||||
f(ret, root, mc)
|
||||
})
|
||||
}
|
||||
|
||||
enum Consq {
|
||||
Jmp(usize),
|
||||
Call,
|
||||
Force,
|
||||
PopEnv,
|
||||
NoOp,
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
fn single_op<'gc, const CAP: usize>(
|
||||
vm: &'gc VM<'gc>,
|
||||
@@ -96,39 +162,62 @@ fn single_op<'gc, const CAP: usize>(
|
||||
stack: &mut Stack<Value<'gc>, CAP>,
|
||||
env: &mut Gc<'gc, VmEnv<'gc>>,
|
||||
mc: &'gc Mutation<'gc>,
|
||||
) -> Result<usize> {
|
||||
) -> Result<Consq> {
|
||||
match opcode {
|
||||
OpCode::Illegal => panic!("illegal opcode"),
|
||||
OpCode::Const { idx } => stack.push(Value::Const(vm.get_const(idx)))?,
|
||||
OpCode::Const { idx } => {
|
||||
stack.push(match vm.get_const(idx) {
|
||||
Const::Int(x) => Value::Int(x),
|
||||
Const::Float(x) => Value::Float(x),
|
||||
Const::Bool(x) => Value::Bool(x),
|
||||
Const::String(x) => Value::String(CoW::new(x.into(), mc)),
|
||||
Const::Null => Value::Null
|
||||
})?
|
||||
},
|
||||
OpCode::LoadThunk { idx } => stack.push(Value::Thunk(Thunk::new(vm.get_thunk(idx), mc)))?,
|
||||
OpCode::CaptureEnv => stack.tos().as_ref().unwrap_thunk().capture(*env, mc),
|
||||
OpCode::ForceValue => {
|
||||
stack.tos_mut().force(vm, mc)?;
|
||||
OpCode::LoadValue { idx } => {
|
||||
stack.push(Value::Thunk(Thunk::new(vm.get_thunk(idx), mc)))?;
|
||||
stack.tos().as_ref().unwrap_thunk().capture_env(*env, mc);
|
||||
return Ok(Consq::Force);
|
||||
}
|
||||
OpCode::Jmp { step } => return Ok(step),
|
||||
OpCode::CaptureEnv => stack.tos().as_ref().unwrap_thunk().capture_env(*env, mc),
|
||||
OpCode::ForceValue => {
|
||||
if !stack.tos().is_thunk() {
|
||||
return Ok(Consq::NoOp);
|
||||
}
|
||||
let Some(val) = stack.tos().as_ref().unwrap_thunk().get_value() else {
|
||||
return Ok(Consq::Force);
|
||||
};
|
||||
stack.pop();
|
||||
stack.push(val)?;
|
||||
}
|
||||
OpCode::InsertValue => {
|
||||
let val = stack.pop();
|
||||
stack.pop().unwrap_thunk().insert_value(val.clone(), mc);
|
||||
let _ = stack.push(val);
|
||||
}
|
||||
OpCode::Jmp { step } => return Ok(Consq::Jmp(step)),
|
||||
OpCode::JmpIfFalse { step } => {
|
||||
if let Value::Const(Const::Bool(false)) = stack.pop() {
|
||||
return Ok(step);
|
||||
if let Value::Bool(false) = stack.pop() {
|
||||
return Ok(Consq::Jmp(step));
|
||||
}
|
||||
}
|
||||
OpCode::Call => {
|
||||
let arg = stack.pop();
|
||||
let func = stack.tos_mut();
|
||||
func.force(vm, mc)?;
|
||||
if func.is_func() {
|
||||
stack.push(arg)?;
|
||||
return Ok(Consq::Call);
|
||||
}
|
||||
func.call(arg, vm, mc)?;
|
||||
}
|
||||
OpCode::Func { idx } => {
|
||||
let func = vm.get_func(idx);
|
||||
let compiled: GcRefLock<'gc, Option<JITFunc<'gc>>> = Gc::new(mc, RefLock::new(None));
|
||||
stack.push(Value::Func(Gc::new(
|
||||
mc,
|
||||
Func::new(func, *env, compiled, Cell::new(0)),
|
||||
)))?;
|
||||
stack.push(Value::Func(Gc::new(mc, Func::new(func, *env, mc))))?;
|
||||
}
|
||||
OpCode::UnOp { op } => {
|
||||
use UnOp::*;
|
||||
let value = stack.tos_mut();
|
||||
value.force(vm, mc)?;
|
||||
match op {
|
||||
Neg => value.neg(),
|
||||
Not => value.not(),
|
||||
@@ -138,13 +227,11 @@ fn single_op<'gc, const CAP: usize>(
|
||||
use BinOp::*;
|
||||
let mut rhs = stack.pop();
|
||||
let lhs = stack.tos_mut();
|
||||
lhs.force(vm, mc)?;
|
||||
rhs.force(vm, mc)?;
|
||||
match op {
|
||||
Add => lhs.add(rhs),
|
||||
Add => lhs.add(rhs, mc),
|
||||
Sub => {
|
||||
rhs.neg();
|
||||
lhs.add(rhs);
|
||||
lhs.add(rhs, mc);
|
||||
}
|
||||
Mul => lhs.mul(rhs),
|
||||
Div => lhs.div(rhs)?,
|
||||
@@ -157,9 +244,8 @@ fn single_op<'gc, const CAP: usize>(
|
||||
}
|
||||
}
|
||||
OpCode::ConcatString => {
|
||||
let mut rhs = stack.pop();
|
||||
rhs.force(vm, mc)?;
|
||||
stack.tos_mut().concat_string(rhs);
|
||||
let rhs = stack.pop();
|
||||
stack.tos_mut().concat_string(rhs, mc);
|
||||
}
|
||||
OpCode::Path => {
|
||||
todo!()
|
||||
@@ -197,47 +283,38 @@ fn single_op<'gc, const CAP: usize>(
|
||||
}
|
||||
OpCode::PushDynamicAttr => {
|
||||
let val = stack.pop();
|
||||
let mut sym = stack.pop();
|
||||
sym.force(vm, mc)?.coerce_to_string();
|
||||
let sym = vm.new_sym(sym.unwrap_const().unwrap_string());
|
||||
let sym = stack.pop();
|
||||
let sym = vm.new_sym(sym.unwrap_string().as_ref());
|
||||
stack.tos_mut().push_attr(sym, val, mc);
|
||||
}
|
||||
OpCode::Select { sym } => {
|
||||
stack.tos_mut().force(vm, mc)?.select(sym, vm)?;
|
||||
stack.tos_mut().select(sym, vm)?;
|
||||
}
|
||||
OpCode::SelectOrDefault { sym } => {
|
||||
let default = stack.pop();
|
||||
stack
|
||||
.tos_mut()
|
||||
.force(vm, mc)?
|
||||
.select_with_default(sym, default)?;
|
||||
stack.tos_mut().select_with_default(sym, default)?;
|
||||
}
|
||||
OpCode::SelectDynamic => {
|
||||
let mut val = stack.pop();
|
||||
val.force(vm, mc)?;
|
||||
val.coerce_to_string();
|
||||
let sym = vm.new_sym(val.unwrap_const().unwrap_string());
|
||||
stack.tos_mut().force(vm, mc)?.select(sym, vm)?;
|
||||
let sym = vm.new_sym(val.unwrap_string().as_ref());
|
||||
stack.tos_mut().select(sym, vm)?;
|
||||
}
|
||||
OpCode::SelectDynamicOrDefault => {
|
||||
let default = stack.pop();
|
||||
let mut val = stack.pop();
|
||||
val.force(vm, mc)?;
|
||||
val.coerce_to_string();
|
||||
let sym = vm.new_sym(val.unwrap_const().unwrap_string());
|
||||
stack
|
||||
.tos_mut()
|
||||
.force(vm, mc)?
|
||||
.select_with_default(sym, default)?;
|
||||
let sym = vm.new_sym(val.unwrap_string().as_ref());
|
||||
stack.tos_mut().select_with_default(sym, default)?;
|
||||
}
|
||||
OpCode::HasAttr { sym } => {
|
||||
stack.tos_mut().force(vm, mc)?.has_attr(sym);
|
||||
stack.tos_mut().has_attr(sym);
|
||||
}
|
||||
OpCode::HasDynamicAttr => {
|
||||
let mut val = stack.pop();
|
||||
val.coerce_to_string();
|
||||
let sym = vm.new_sym(val.unwrap_const().unwrap_string());
|
||||
stack.tos_mut().force(vm, mc)?.has_attr(sym);
|
||||
let sym = vm.new_sym(val.unwrap_string().as_ref());
|
||||
stack.tos_mut().has_attr(sym);
|
||||
}
|
||||
OpCode::LookUp { sym } => {
|
||||
stack.push(
|
||||
@@ -251,13 +328,12 @@ fn single_op<'gc, const CAP: usize>(
|
||||
stack
|
||||
.pop()
|
||||
.unwrap_attr_set()
|
||||
.as_inner()
|
||||
.iter()
|
||||
.map(|(&k, v)| (k, v.clone()))
|
||||
.collect_into(&mut new);
|
||||
*env = env.enter_let(Gc::new(mc, new.into()), mc);
|
||||
}
|
||||
OpCode::LeaveLetEnv => *env = env.leave(),
|
||||
OpCode::LeaveEnv => *env = env.leave(),
|
||||
OpCode::EnterWithEnv => {
|
||||
let mut new = HashMap::new();
|
||||
stack
|
||||
@@ -267,40 +343,26 @@ fn single_op<'gc, const CAP: usize>(
|
||||
.iter()
|
||||
.map(|(&k, v)| (k, v.clone()))
|
||||
.collect_into(&mut new);
|
||||
*env = env.enter_with(Gc::new(mc, new.into()), mc);
|
||||
*env = env.enter_with(Gc::new(mc, new), mc);
|
||||
}
|
||||
OpCode::LeaveWithEnv => *env = env.leave(),
|
||||
OpCode::PopEnv => return Ok(Consq::PopEnv),
|
||||
OpCode::Assert => {
|
||||
if !stack.pop().unwrap_const().unwrap_bool() {
|
||||
if !stack.pop().unwrap_bool() {
|
||||
todo!()
|
||||
}
|
||||
}
|
||||
}
|
||||
Ok(0)
|
||||
Ok(Consq::NoOp)
|
||||
}
|
||||
|
||||
#[derive(Collect)]
|
||||
#[collect(no_drop)]
|
||||
pub struct GcRoot<'gc> {
|
||||
pub struct GcRoot<'gc, const CAP: usize = STACK_SIZE> {
|
||||
vm: Gc<'gc, VM<'gc>>,
|
||||
jit: Gc<'gc, ContextWrapper>,
|
||||
ctxs: DynamicRootSet<'gc>
|
||||
}
|
||||
|
||||
#[derive(Collect)]
|
||||
#[collect(no_drop)]
|
||||
pub struct EvalContext<'gc, const CAP: usize = STACK_SIZE> {
|
||||
stack: Stack<Value<'gc>, CAP>,
|
||||
env: Gc<'gc, VmEnv<'gc>>
|
||||
}
|
||||
|
||||
impl<'gc, const CAP: usize> EvalContext<'gc, CAP> {
|
||||
pub fn new(env: Gc<'gc, VmEnv<'gc>>) -> Self {
|
||||
Self {
|
||||
stack: Stack::new(),
|
||||
env
|
||||
}
|
||||
}
|
||||
envs: Vec<Gc<'gc, VmEnv<'gc>>>,
|
||||
}
|
||||
|
||||
#[derive(Constructor, Collect)]
|
||||
|
||||
Reference in New Issue
Block a user