feat: gc (does compile, but WIP)

This commit is contained in:
2025-05-27 21:08:59 +08:00
parent 319c12c1f4
commit c3ace28af1
20 changed files with 696 additions and 575 deletions

View File

@@ -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)]