feat: lookup at downgrade time

works, but leaks memory
This commit is contained in:
2025-06-01 09:20:04 +08:00
parent 7d6168fdae
commit 20b2b6f1ef
20 changed files with 762 additions and 651 deletions

39
Cargo.lock generated
View File

@@ -23,16 +23,6 @@ version = "1.0.98"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e16d2d3311acee920a9eb8d33b8cbc1787ce4a264e85f964c2404b969bdcd487"
[[package]]
name = "archery"
version = "1.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8967cd1cc9e9e1954f644e14fbd6042fe9a37da96c52a67e44a2ac18261f8561"
dependencies = [
"static_assertions",
"triomphe",
]
[[package]]
name = "autocfg"
version = "1.1.0"
@@ -334,9 +324,7 @@ dependencies = [
"inkwell",
"itertools",
"regex",
"replace_with",
"rnix",
"rpds",
"rustyline",
"thiserror 2.0.12",
]
@@ -410,12 +398,6 @@ version = "0.8.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2b15c43186be67a4fd63bee50d0303afffcef381492ebe2c5d87f324e1b8815c"
[[package]]
name = "replace_with"
version = "0.1.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "51743d3e274e2b18df81c4dc6caf8a5b8e15dbe799e0dca05c7617380094e884"
[[package]]
name = "rnix"
version = "0.12.0"
@@ -438,15 +420,6 @@ dependencies = [
"text-size",
]
[[package]]
name = "rpds"
version = "1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a0e15515d3ce3313324d842629ea4905c25a13f81953eadb88f85516f59290a4"
dependencies = [
"archery",
]
[[package]]
name = "rustc-hash"
version = "1.1.0"
@@ -506,12 +479,6 @@ version = "1.13.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3c5e1a9a646d36c3599cd173a41282daf47c44583ad367b8e6837255952e5c67"
[[package]]
name = "static_assertions"
version = "1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f"
[[package]]
name = "syn"
version = "2.0.101"
@@ -580,12 +547,6 @@ dependencies = [
"syn",
]
[[package]]
name = "triomphe"
version = "0.1.11"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "859eb650cfee7434994602c3a68b25d77ad9e68c8a6cd491616ef86661382eb3"
[[package]]
name = "unicode-ident"
version = "1.0.18"

View File

@@ -23,13 +23,11 @@ strip = true
rnix = "0.12"
thiserror = "2.0"
itertools = "0.14"
rpds = "1.1"
derive_more = { version = "2.0", features = ["full"] }
ecow = "0.2"
regex = "1.11"
hashbrown = "0.15"
inkwell = { version = "0.6.0", features = ["llvm18-1"] }
gc-arena = { git = "https://github.com/kyren/gc-arena", rev = "d651e3b4363d525a2d502c2305bc73e291835c84", features= ["hashbrown"] }
replace_with = "0.1"
rustyline = { version = "15.0", optional = true }

View File

@@ -1,13 +1,18 @@
use gc_arena::{Gc, Mutation};
use hashbrown::HashMap;
use crate::env::{IrEnv, VmEnv};
use crate::env::VmEnv;
use crate::ir::{DowngradeContext, Ir};
use crate::ty::common::Const;
use crate::ty::internal::{AttrSet, CoW, PrimOp, Value};
use crate::vm::VM;
pub fn ir_env<'gc>(mc: &Mutation<'gc>) -> Gc<'gc, IrEnv<'gc>> {
// TODO:
IrEnv::new(Gc::new(mc, HashMap::new()), mc)
pub fn ir_env(ctx: &mut DowngradeContext) -> HashMap<usize, Ir> {
let mut map = HashMap::new();
map.insert(ctx.new_sym("true"), ctx.new_const(Const::Bool(true)).ir());
map.insert(ctx.new_sym("false"), ctx.new_const(Const::Bool(false)).ir());
map
}
pub fn vm_env<'gc>(vm: &VM, mc: &Mutation<'gc>) -> Gc<'gc, VmEnv<'gc>> {
@@ -78,15 +83,16 @@ pub fn vm_env<'gc>(vm: &VM, mc: &Mutation<'gc>) -> Gc<'gc, VmEnv<'gc>> {
map.insert(vm.new_sym(primop.name), Value::PrimOp(primop));
}
let sym = vm.new_sym("builtins");
let attrs = CoW::new_cyclic(
|this| {
map.insert(sym, Value::AttrSet(this));
AttrSet::from_inner(map)
},
mc,
);
let mut attrs = CoW::new(AttrSet::new(map), mc);
unsafe {
attrs.make_cyclic(|attrs, this| {
let _ = attrs.push_attr(sym, Value::AttrSet(this));
}, mc);
}
let builtins = Value::AttrSet(attrs);
env_map.insert(sym, builtins);
VmEnv::new(Gc::new(mc, env_map), mc)
// TODO:
// VmEnv::new(Gc::new(mc, env_map), mc)
VmEnv::new(Vec::new(), mc)
}

View File

@@ -16,6 +16,8 @@ pub enum OpCode {
Const { idx: usize },
/// load a dynamic var onto stack
LookUp { sym: usize },
/// load a var from let binding onto stack
LookUpLet { level: usize, idx: usize },
/// load a thunk lazily onto stack
LoadThunk { idx: usize },
/// load a thunk value onto stack
@@ -31,6 +33,8 @@ pub enum OpCode {
Call,
/// make a function
Func { idx: usize },
/// load a function argument
Arg { level: usize },
/// consume 1 element, assert TOS is true
Assert,
@@ -43,14 +47,14 @@ pub enum OpCode {
/// push an empty attribute set onto stack
AttrSet { cap: usize },
/// finalize the recursive attribute set at TOS
FinalizeRec,
FinalizeLet,
/// [ .. set value ] consume 1 element, push a static kv pair (`name`, `value`) into `set`
PushStaticAttr { name: usize },
/// [ .. set name value ] consume 2 elements, push a dynamic kv pair (`name`, `value`) in to `set`
PushDynamicAttr,
/// push an empty list onto stack
List,
List { cap: usize },
/// [ .. list elem ] consume 1 element, push `elem` into `list`
PushElem,
@@ -75,8 +79,6 @@ pub enum OpCode {
SelectDynamic,
/// [ .. set sym default ] select `sym` from `set` or `default`
SelectDynamicOrDefault,
/// enter the let environment of the attribute set at TOS
EnterLetEnv,
/// enter the with environment of the attribute set at TOS
EnterWithEnv,
/// exit current envrironment

View File

@@ -96,6 +96,18 @@ impl Compile for ir::Var {
}
}
impl Compile for ir::Arg {
fn compile(self, comp: &mut Compiler) {
comp.push(OpCode::Arg { level: self.level })
}
}
impl Compile for ir::LetVar {
fn compile(self, comp: &mut Compiler) {
comp.push(OpCode::LookUpLet { level: self.level, idx: self.idx });
}
}
impl Compile for ir::Thunk {
fn compile(self, comp: &mut Compiler) {
comp.push(OpCode::LoadThunk { idx: self.idx });
@@ -113,7 +125,7 @@ impl Compile for ir::Attrs {
for stc in self.stcs {
let thunk = stc.1.is_thunk();
stc.1.compile(comp);
if thunk && !self.rec {
if thunk {
comp.push(OpCode::CaptureEnv);
}
comp.push(OpCode::PushStaticAttr { name: stc.0 });
@@ -122,20 +134,17 @@ impl Compile for ir::Attrs {
let thunk = dynamic.1.is_thunk();
dynamic.0.compile_force(comp);
dynamic.1.compile(comp);
if thunk && !self.rec {
if thunk {
comp.push(OpCode::CaptureEnv);
}
comp.push(OpCode::PushDynamicAttr)
}
if self.rec {
comp.push(OpCode::FinalizeRec);
}
}
}
impl Compile for ir::List {
fn compile(self, comp: &mut Compiler) {
comp.push(OpCode::List);
comp.push(OpCode::List { cap: self.items.len() });
for item in self.items {
item.compile(comp);
comp.push(OpCode::PushElem);
@@ -375,8 +384,14 @@ impl Compile for ir::If {
impl Compile for ir::Let {
fn compile(self, comp: &mut Compiler) {
self.attrs.compile(comp);
comp.push(OpCode::EnterLetEnv);
comp.push(OpCode::List {
cap: self.bindings.len()
});
for (_, val) in self.bindings {
val.compile(comp);
comp.push(OpCode::PushElem);
}
comp.push(OpCode::FinalizeLet);
self.expr.compile(comp);
comp.push(OpCode::LeaveEnv);
}

View File

@@ -8,16 +8,17 @@ use crate::{ir::Ir, ty::internal::Value};
#[derive(Collect)]
#[collect(no_drop)]
pub struct Env<'gc, K: Hash + Eq + Collect<'gc>, V: Collect<'gc>> {
let_: Gc<'gc, LetEnv<'gc, K, V>>,
let_: Gc<'gc, LetEnv<'gc, V>>,
with: Gc<'gc, With<'gc, K, V>>,
args: Vec<V>,
last: Option<Gc<'gc, Env<'gc, K, V>>>,
}
#[derive(Collect)]
#[collect(no_drop)]
pub struct LetEnv<'gc, K: Hash + Eq + Collect<'gc>, V: Collect<'gc>> {
map: LetNode<'gc, K, V>,
last: Option<Gc<'gc, LetEnv<'gc, K, V>>>,
pub struct LetEnv<'gc, V: Collect<'gc>> {
map: Vec<V>,
last: Option<Gc<'gc, LetEnv<'gc, V>>>,
}
pub type VmEnv<'gc> = Env<'gc, usize, Value<'gc>>;
@@ -34,7 +35,6 @@ pub struct With<'gc, K: Hash + Eq + Collect<'gc>, V: Collect<'gc>> {
#[collect(no_drop)]
enum LetNode<'gc, K: Hash + Eq + Collect<'gc>, V: Collect<'gc>> {
Let(Gc<'gc, HashMap<K, V>>),
SingleArg(K, V),
MultiArg(Gc<'gc, HashMap<K, V>>),
}
@@ -46,7 +46,7 @@ pub enum Type {
}
impl<'gc, K: Hash + Eq + Clone + Collect<'gc>, V: Clone + Collect<'gc>> Env<'gc, K, V> {
pub fn new(map: Gc<'gc, HashMap<K, V>>, mc: &Mutation<'gc>) -> Gc<'gc, Self> {
pub fn new(map: Vec<V>, mc: &Mutation<'gc>) -> Gc<'gc, Self> {
Gc::new(
mc,
Self {
@@ -58,20 +58,18 @@ impl<'gc, K: Hash + Eq + Clone + Collect<'gc>, V: Clone + Collect<'gc>> Env<'gc,
last: None,
},
),
args: Vec::new(),
last: None,
},
)
}
pub fn lookup_slow(&self, symbol: &K) -> Option<&V> {
if let Some(val) = self.let_.lookup(symbol) {
return Some(val);
}
self.with.lookup(symbol)
pub fn lookup_arg(&self, level: usize) -> V {
self.args[self.args.len() - level - 1].clone()
}
pub fn lookup_let(&self, symbol: &K) -> Option<&V> {
self.let_.lookup(symbol)
pub fn lookup_let(&self, level: usize, idx: usize) -> V {
self.let_.lookup(level, idx)
}
pub fn lookup_with(&self, symbol: &K) -> Option<&V> {
@@ -82,20 +80,25 @@ impl<'gc, K: Hash + Eq + Clone + Collect<'gc>, V: Clone + Collect<'gc>> Env<'gc,
self.with.map.is_some()
}
pub fn enter_arg(self: Gc<'gc, Self>, ident: K, val: V, mc: &Mutation<'gc>) -> Gc<'gc, Self> {
#[must_use]
pub fn enter_arg(self: Gc<'gc, Self>, val: V, mc: &Mutation<'gc>) -> Gc<'gc, Self> {
let mut args = self.args.clone();
args.push(val);
Gc::new(
mc,
Env {
let_: self.let_.enter_arg(ident, val, mc),
let_: self.let_,
with: self.with,
last: Some(self),
args
},
)
}
#[must_use]
pub fn enter_let(
self: Gc<'gc, Self>,
map: Gc<'gc, HashMap<K, V>>,
map: Vec<V>,
mc: &Mutation<'gc>,
) -> Gc<'gc, Self> {
Gc::new(
@@ -103,11 +106,13 @@ impl<'gc, K: Hash + Eq + Clone + Collect<'gc>, V: Clone + Collect<'gc>> Env<'gc,
Self {
let_: self.let_.enter_let(map, mc),
with: self.with,
args: self.args.clone(),
last: Some(self),
},
)
}
#[must_use]
pub fn enter_with(
self: Gc<'gc, Self>,
map: Gc<'gc, HashMap<K, V>>,
@@ -118,6 +123,7 @@ impl<'gc, K: Hash + Eq + Clone + Collect<'gc>, V: Clone + Collect<'gc>> Env<'gc,
Env {
let_: self.let_,
with: self.with.enter(map, mc),
args: self.args.clone(),
last: Some(self),
},
)
@@ -128,53 +134,35 @@ impl<'gc, K: Hash + Eq + Clone + Collect<'gc>, V: Clone + Collect<'gc>> Env<'gc,
}
}
impl<'gc, K: Hash + Eq + Clone + Collect<'gc>, V: Clone + Collect<'gc>> LetEnv<'gc, K, V> {
pub fn new(map: Gc<'gc, HashMap<K, V>>, mc: &Mutation<'gc>) -> Gc<'gc, Self> {
impl<'gc, V: Clone + Collect<'gc>> LetEnv<'gc, V> {
pub fn new(map: Vec<V>, mc: &Mutation<'gc>) -> Gc<'gc, Self> {
Gc::new(
mc,
Self {
map: LetNode::Let(map),
map,
last: None,
},
)
}
pub fn lookup(&self, symbol: &K) -> Option<&V> {
use self::LetNode::*;
match &self.map {
Let(map) | MultiArg(map) => {
if let Some(val) = map.get(symbol) {
return Some(val);
pub fn lookup(&self, level: usize, idx: usize) -> V {
let mut cur = self;
for _ in 0..level {
let last = cur.last.unwrap();
cur = last.as_ref();
}
}
SingleArg(sym, val) => {
if sym == symbol {
return Some(val);
}
}
}
self.last.as_ref().and_then(|env| env.lookup(symbol))
}
pub fn enter_arg(self: Gc<'gc, Self>, ident: K, val: V, mc: &Mutation<'gc>) -> Gc<'gc, Self> {
Gc::new(
mc,
Self {
map: LetNode::SingleArg(ident, val),
last: Some(self),
},
)
cur.map[idx].clone()
}
pub fn enter_let(
self: Gc<'gc, Self>,
map: Gc<'gc, HashMap<K, V>>,
map: Vec<V>,
mc: &Mutation<'gc>,
) -> Gc<'gc, Self> {
Gc::new(
mc,
Self {
map: LetNode::Let(map),
map,
last: Some(self),
},
)

765
src/ir.rs

File diff suppressed because it is too large Load Diff

View File

@@ -34,6 +34,8 @@ pub struct Helpers<'ctx> {
pub eq: FunctionValue<'ctx>,
pub or: FunctionValue<'ctx>,
pub call: FunctionValue<'ctx>,
pub arg: FunctionValue<'ctx>,
pub lookup_let: FunctionValue<'ctx>,
pub lookup: FunctionValue<'ctx>,
pub force: FunctionValue<'ctx>,
}
@@ -113,6 +115,16 @@ impl<'ctx> Helpers<'ctx> {
),
None,
);
let arg = module.add_function(
"arg",
value_type.fn_type(&[ptr_int_type.into(), ptr_type.into()], false),
None,
);
let lookup_let = module.add_function(
"lookup_let",
value_type.fn_type(&[ptr_int_type.into(), ptr_int_type.into(), ptr_type.into()], false),
None,
);
let lookup = module.add_function(
"lookup",
value_type.fn_type(&[ptr_int_type.into(), ptr_type.into()], false),
@@ -142,6 +154,8 @@ impl<'ctx> Helpers<'ctx> {
execution_engine.add_global_mapping(&eq, helper_eq as _);
execution_engine.add_global_mapping(&or, helper_or as _);
execution_engine.add_global_mapping(&call, helper_call as _);
execution_engine.add_global_mapping(&arg, helper_arg as _);
execution_engine.add_global_mapping(&lookup_let, helper_lookup_let as _);
execution_engine.add_global_mapping(&lookup, helper_lookup as _);
execution_engine.add_global_mapping(&force, helper_force as _);
@@ -165,6 +179,8 @@ impl<'ctx> Helpers<'ctx> {
eq,
or,
call,
arg,
lookup_let,
lookup,
force,
}
@@ -348,9 +364,21 @@ extern "C" fn helper_call<'gc>(
}
}
extern "C" fn helper_arg(idx: usize, env: *const VmEnv) -> JITValue {
let env = unsafe { env.as_ref() }.unwrap();
let val: JITValue = env.lookup_arg(idx).into();
val
}
extern "C" fn helper_lookup_let(level: usize, idx: usize, env: *const VmEnv) -> JITValue {
let env = unsafe { env.as_ref() }.unwrap();
let val: JITValue = env.lookup_let(level, idx).into();
val
}
extern "C" fn helper_lookup(sym: usize, env: *const VmEnv) -> JITValue {
let env = unsafe { env.as_ref() }.unwrap();
let val: JITValue = env.lookup_slow(&sym).unwrap().into();
let val: JITValue = env.lookup_with(&sym).unwrap().into();
val
}
@@ -373,7 +401,7 @@ extern "C" fn helper_force<'gc>(
let (opcodes, env) = thunk.suspend(mc).unwrap();
let func = unsafe { jit.as_ref() }
.unwrap()
.compile_seq(opcodes.iter().copied(), vm)
.compile_seq(opcodes.iter().copied().rev(), vm)
.unwrap();
let val = unsafe { func.call(env.as_ref() as *const _, mc as *const _) };
thunk.insert_value(val.into(), mc);

View File

@@ -199,7 +199,7 @@ impl<'gc> JITContext<'gc> {
pub fn compile_seq(
&self,
opcodes: impl ExactSizeIterator<Item = OpCode> + DoubleEndedIterator,
mut opcodes: impl ExactSizeIterator<Item = OpCode>,
vm: &'gc VM<'gc>,
) -> Result<JITFunc<'gc>> {
let mut stack = Stack::<_, STACK_SIZE>::new();
@@ -211,7 +211,7 @@ impl<'gc> JITContext<'gc> {
let entry = self.context.append_basic_block(func_, "entry");
self.builder.position_at_end(entry);
let len = opcodes.len();
self.build_expr(&mut opcodes.rev(), vm, env, mc, &mut stack, func_, len)?;
self.build_expr(&mut opcodes, vm, env, mc, &mut stack, func_, len)?;
assert_eq!(stack.len(), 1);
let value = stack.pop();
@@ -437,6 +437,47 @@ impl<'gc> JITContext<'gc> {
_ => todo!("BinOp::{:?} not implemented in JIT", op),
}
}
OpCode::Arg { level } => stack.push(
self.builder
.build_direct_call(
self.helpers.arg,
&[
self.helpers
.ptr_int_type
.const_int(level as u64, false)
.into(),
env.into(),
],
"call_arg",
)
.unwrap()
.try_as_basic_value()
.left()
.unwrap(),
)?,
OpCode::LookUpLet { level, idx } => stack.push(
self.builder
.build_direct_call(
self.helpers.lookup_let,
&[
self.helpers
.ptr_int_type
.const_int(level as u64, false)
.into(),
self.helpers
.ptr_int_type
.const_int(idx as u64, false)
.into(),
env.into(),
],
"call_lookup_let",
)
.unwrap()
.try_as_basic_value()
.left()
.unwrap(),
)?,
OpCode::LookUp { sym } => stack.push(
self.builder
.build_direct_call(

View File

@@ -8,7 +8,6 @@ use hashbrown::{HashMap, HashSet};
use inkwell::context::Context;
use ecow::EcoString;
use rpds::vector_sync;
use crate::builtins::vm_env;
use crate::compile::compile;
@@ -77,7 +76,7 @@ macro_rules! symbol {
macro_rules! list {
($($x:tt)*) => (
Value::List(List::new(vector_sync![$($x)*]))
Value::List(List::new(vec![$($x)*]))
);
}

View File

@@ -112,3 +112,8 @@ impl PartialEq for Const {
}
impl Eq for Const {}
pub enum MaybeThunk {
Thunk(usize),
Const(Const)
}

View File

@@ -2,11 +2,10 @@ use std::ops::Deref;
use std::rc::Rc;
use derive_more::Constructor;
use gc_arena::{Collect, Gc, Mutation};
use gc_arena::Collect;
use hashbrown::{HashMap, HashSet};
use itertools::Itertools;
use crate::env::VmEnv;
use crate::vm::VM;
use super::super::public as p;
@@ -58,14 +57,6 @@ impl<'gc> AttrSet<'gc> {
self.data.get(&sym).is_some()
}
pub fn capture(&mut self, env: Gc<'gc, VmEnv<'gc>>, mc: &Mutation<'gc>) {
self.data.iter().for_each(|(_, v)| {
if let Value::Thunk(ref thunk) = v.clone() {
thunk.capture_env(env, mc);
}
})
}
pub fn update(&mut self, other: &AttrSet<'gc>) {
for (k, v) in other.data.iter() {
self.push_attr_force(*k, v.clone())

View File

@@ -2,15 +2,13 @@ use std::cell::Cell;
use gc_arena::lock::{GcRefLock, RefLock};
use gc_arena::{Collect, Gc, Mutation};
use hashbrown::HashMap;
use itertools::Itertools;
use crate::bytecode::Func as BFunc;
use crate::env::VmEnv;
use crate::error::Result;
use crate::ir;
use crate::jit::JITFunc;
use crate::ty::internal::{Thunk, Value};
use crate::ty::internal::Value;
use crate::vm::VM;
#[derive(Debug, Clone, Collect)]
@@ -75,45 +73,7 @@ impl<'gc> Func<'gc> {
vm: &'gc VM<'gc>,
mc: &Mutation<'gc>,
) -> Result<Value<'gc>> {
use Param::*;
let mut env = self.env;
env = match self.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
.as_inner()
.iter()
.map(|(k, _)| k)
.sorted()
.ne(formals.iter().map(|(k, _)| k).sorted())
{
todo!()
}
for (formal, default) in formals {
let arg = arg
.select(formal)
.or_else(|| {
default
.map(|idx| Value::Thunk(Thunk::new(vm.get_thunk(idx), mc)))
})
.unwrap();
new.insert(formal, arg);
}
if let Some(alias) = alias {
new.insert(alias, Value::AttrSet(arg));
}
env.enter_let(Gc::new(mc, new), mc)
}
};
let env = self.env.enter_arg(arg, mc);
let compiled = self
.compiled
.borrow_mut(mc)

View File

@@ -1,17 +1,15 @@
use hashbrown::HashSet;
use derive_more::Constructor;
use gc_arena::Collect;
use rpds::Vector;
use gc_arena::{Collect, Gc, Mutation};
use crate::ty::public as p;
use crate::vm::VM;
use crate::env::VmEnv;
use super::Value;
#[derive(Constructor, Clone, PartialEq)]
#[derive(Clone, PartialEq)]
pub struct List<'gc> {
data: Vector<Value<'gc>>,
data: Vec<Value<'gc>>,
}
unsafe impl<'gc> Collect<'gc> for List<'gc> {
@@ -23,22 +21,40 @@ unsafe impl<'gc> Collect<'gc> for List<'gc> {
}
impl<'gc> List<'gc> {
pub fn empty() -> Self {
pub fn new() -> Self {
List {
data: Vector::new(),
data: Vec::new(),
}
}
pub fn with_capacity(cap: usize) -> Self {
List {
data: Vec::with_capacity(cap),
}
}
pub fn push(&mut self, elem: Value<'gc>) {
self.data.push_back_mut(elem);
self.data.push(elem);
}
pub fn concat(&mut self, other: &List<'gc>) {
for elem in other.data.iter() {
self.data.push_back_mut(elem.clone());
self.data.push(elem.clone());
}
}
pub fn capture(&mut self, env: Gc<'gc, VmEnv<'gc>>, mc: &Mutation<'gc>) {
self.data.iter().for_each(|v| {
if let Value::Thunk(ref thunk) = v.clone() {
thunk.capture_env(env, mc);
}
})
}
pub fn into_inner(self) -> Vec<Value<'gc>> {
self.data
}
pub fn to_public(&self, vm: &VM<'gc>, seen: &mut HashSet<Value<'gc>>) -> p::Value {
p::Value::List(p::List::new(
self.data

View File

@@ -1,7 +1,5 @@
use std::cell::Cell;
use std::hash::Hash;
use std::marker::PhantomData;
use std::mem::MaybeUninit;
use std::ops::Deref;
use derive_more::{IsVariant, Unwrap};
@@ -35,66 +33,49 @@ pub struct CoW<'gc, T: Clone + Collect<'gc>> {
inner: Gc<'gc, CoWInner<'gc, T>>,
}
#[derive(Collect)]
#[collect(no_drop)]
struct CoWInner<'gc, T: Clone + Collect<'gc>> {
ref_count: Cell<usize>,
data: MaybeUninit<T>,
_marker: PhantomData<Cell<&'gc ()>>,
}
unsafe impl<'gc, T: Clone + Collect<'gc>> Collect<'gc> for CoWInner<'gc, T> {
fn trace<Tr: gc_arena::collect::Trace<'gc>>(&self, cc: &mut Tr) {
unsafe { self.data.assume_init_ref() }.trace(cc);
}
data: GcRefLock<'gc, T>,
}
#[allow(mutable_transmutes)]
impl<'gc, T: Clone + Collect<'gc>> CoW<'gc, T> {
pub fn new(data: T, mc: &Mutation<'gc>) -> Self {
Self {
inner: Gc::new(mc, CoWInner::new(data)),
inner: Gc::new(mc, CoWInner::new(data, mc)),
}
}
pub fn new_cyclic(datafn: impl FnOnce(Self) -> T, mc: &Mutation<'gc>) -> Self {
let inner = Gc::new(
mc,
CoWInner {
ref_count: Cell::new(1),
data: MaybeUninit::uninit(),
_marker: PhantomData,
},
);
let data = datafn(CoW { inner });
unsafe { std::mem::transmute::<_, &mut MaybeUninit<_>>(&inner.data) }.write(data);
Self { inner }
pub unsafe fn make_cyclic(&mut self, f: impl FnOnce(&mut T, Self), mc: &Mutation<'gc>) {
let inner = self.inner;
self.make_mut(|self_mut| f(self_mut, Self { inner }), mc);
self.inner.ref_count.set(self.inner.ref_count.get() + 1);
}
pub fn make_mut(&mut self, mc: &Mutation<'gc>) -> &mut T {
pub fn make_mut<U>(&mut self, f: impl FnOnce(&mut T) -> U, mc: &Mutation<'gc>) -> U {
if self.inner.ref_count.get() == 1 {
unsafe { std::mem::transmute(self.inner.data.assume_init_ref()) }
f(&mut *self.inner.data.borrow_mut(mc))
} else {
unsafe {
*self = CoW::new(self.inner.data.assume_init_ref().clone(), mc);
std::mem::transmute(self.inner.data.assume_init_ref())
}
*self = CoW::new(self.inner.data.borrow().clone(), mc);
f(&mut *self.inner.data.borrow_mut(mc))
}
}
pub fn as_ptr(&self) -> *const T {
self.as_ref() as *const T
&*self.as_ref() as *const T
}
}
impl<'gc, T: Clone + Collect<'gc>> AsRef<T> for CoW<'gc, T> {
fn as_ref(&self) -> &T {
unsafe { self.inner.data.assume_init_ref() }
pub fn as_ref(&self) -> std::cell::Ref<'_, T> {
self.inner.data.borrow()
}
}
impl<'gc, T: Clone + Collect<'gc>> Deref for CoW<'gc, T> {
type Target = T;
fn deref(&self) -> &Self::Target {
self.as_ref()
unsafe { self.as_ptr().as_ref() }.unwrap()
}
}
@@ -112,11 +93,10 @@ impl<'gc, T: Clone + Collect<'gc>> Drop for CoW<'gc, T> {
}
impl<'gc, T: Clone + Collect<'gc>> CoWInner<'gc, T> {
fn new(data: T) -> Self {
fn new(data: T, mc: &Mutation<'gc>) -> Self {
Self {
ref_count: Cell::new(1),
data: MaybeUninit::new(data),
_marker: PhantomData,
data: Gc::new(mc, RefLock::new(data))
}
}
}
@@ -153,7 +133,7 @@ impl Hash for Value<'_> {
List(x) => x.as_ptr().hash(state),
Catchable(x) => x.hash(state),
PrimOp(x) => (x.as_ref() as *const self::PrimOp).hash(state),
PartialPrimOp(x) => (x.as_ref() as *const self::PartialPrimOp).hash(state),
PartialPrimOp(x) => x.as_ptr().hash(state),
Func(x) => (x.as_ref() as *const self::Func).hash(state),
}
}
@@ -206,22 +186,6 @@ pub enum ValueAsRef<'v, 'gc> {
Func(&'v Func<'gc>),
}
#[derive(IsVariant, Unwrap)]
pub enum ValueAsMut<'v, 'gc> {
Int(i64),
Float(f64),
Bool(bool),
String(&'v str),
Null,
Thunk(&'v Thunk<'gc>),
AttrSet(&'v mut AttrSet<'gc>),
List(&'v mut List<'gc>),
Catchable(&'v str),
PrimOp(&'v PrimOp),
PartialPrimOp(&'v mut PartialPrimOp<'gc>),
Func(&'v Func<'gc>),
}
impl<'gc, 'v> Value<'gc> {
pub fn as_ref(&'v self) -> ValueAsRef<'v, 'gc> {
use Value::*;
@@ -241,25 +205,6 @@ impl<'gc, 'v> Value<'gc> {
Func(x) => R::Func(x),
}
}
pub fn as_mut(&'v mut self, mc: &Mutation<'gc>) -> ValueAsMut<'v, 'gc> {
use Value::*;
use ValueAsMut as M;
match self {
Int(x) => M::Int(*x),
Float(x) => M::Float(*x),
Bool(x) => M::Bool(*x),
String(x) => M::String(x),
Null => M::Null,
Thunk(x) => M::Thunk(x),
AttrSet(x) => M::AttrSet(x.make_mut(mc)),
List(x) => M::List(x.make_mut(mc)),
Catchable(x) => M::Catchable(x),
PrimOp(x) => M::PrimOp(x),
PartialPrimOp(x) => M::PartialPrimOp(x.make_mut(mc)),
Func(x) => M::Func(x),
}
}
}
impl<'gc> Value<'gc> {
pub fn ok(self) -> Result<Self> {
@@ -382,7 +327,7 @@ impl<'gc> Value<'gc> {
(Float(a), Float(b)) => Float(a + b),
(String(a), String(b)) => {
let mut a = a.clone();
a.make_mut(mc).push_str(b.as_str());
a.make_mut(|a| a.push_str(b.as_str()), mc);
String(a)
}
(Value::Catchable(_), _) => return,
@@ -426,7 +371,7 @@ impl<'gc> Value<'gc> {
match (self.coerce_to_string(), other.coerce_to_string()) {
(Value::String(a), Value::String(b)) => {
let mut a = a.clone();
a.make_mut(mc).push_str(b.as_str())
a.make_mut(|a| a.push_str(b.as_str()), mc);
}
(_, Value::Catchable(_)) => *self = other,
(Value::Catchable(_), _) => (),
@@ -437,7 +382,7 @@ impl<'gc> Value<'gc> {
pub fn push(&mut self, elem: Self, mc: &Mutation<'gc>) -> &mut Self {
if let Value::List(list) = self {
list.make_mut(mc).push(elem);
list.make_mut(|list| list.push(elem), mc);
} else if let Value::Catchable(_) = self {
} else if let Value::Catchable(_) = elem {
*self = elem;
@@ -454,7 +399,7 @@ impl<'gc> Value<'gc> {
}
match (self, other) {
(Value::List(a), Value::List(b)) => {
a.make_mut(mc).concat(b.as_ref());
a.make_mut(|a| a.concat(&b), mc);
}
(Value::Catchable(_), _) => (),
_ => todo!(),
@@ -463,7 +408,7 @@ impl<'gc> Value<'gc> {
pub fn push_attr(&mut self, sym: usize, val: Self, mc: &Mutation<'gc>) -> &mut Self {
if let Value::AttrSet(attrs) = self {
attrs.make_mut(mc).push_attr(sym, val);
attrs.make_mut(|attrs| attrs.push_attr(sym, val), mc)
} else if let Value::Catchable(_) = self {
} else if let Value::Catchable(_) = val {
*self = val
@@ -480,7 +425,7 @@ impl<'gc> Value<'gc> {
}
match (self, other) {
(Value::AttrSet(a), Value::AttrSet(b)) => {
a.make_mut(mc).update(b.as_ref());
a.make_mut(|a| a.update(&b), mc)
}
(Value::Catchable(_), _) => (),
_ => todo!(),
@@ -556,7 +501,7 @@ impl<'gc> Value<'gc> {
Int(x) => Value::Const(Const::Int(*x)),
Float(x) => Value::Const(Const::Float(*x)),
Bool(x) => Value::Const(Const::Bool(*x)),
String(x) => Value::Const(Const::String(x.as_ref().into())),
String(x) => Value::Const(Const::String(x.as_str().into())),
Null => Value::Const(Const::Null),
Thunk(_) => Value::Thunk,
PrimOp(primop) => Value::PrimOp(primop.name),

View File

@@ -73,13 +73,17 @@ impl<'gc> PartialPrimOp<'gc> {
mc: &Mutation<'gc>,
) -> Result<Value<'gc>> {
let func = self.func;
let self_mut = self.make_mut(mc);
let Some(ret) = self.make_mut(|self_mut| {
self_mut.args.push(arg);
self_mut.arity -= 1;
if self_mut.arity > 0 {
Value::PartialPrimOp(self.clone()).ok()
if self_mut.arity == 0 {
Some(func(std::mem::take(&mut self_mut.args), vm, mc))
} else {
func(std::mem::take(&mut self_mut.args), vm, mc)
None
}
}, mc) else {
return Value::PartialPrimOp(self.clone()).ok();
};
ret
}
}

View File

@@ -1,16 +1,15 @@
// TODO: Contextful String
use ecow::EcoString;
use rpds::List;
pub struct StringContext {
context: List<()>,
context: Vec<()>,
}
impl StringContext {
pub fn new() -> StringContext {
StringContext {
context: List::new(),
context: Vec::new(),
}
}
}

View File

@@ -6,7 +6,6 @@ use std::sync::LazyLock;
use derive_more::{Constructor, IsVariant, Unwrap};
use ecow::EcoString;
use regex::Regex;
use rpds::VectorSync;
use super::common::*;
@@ -93,7 +92,7 @@ impl Display for AttrSet {
#[derive(Constructor, Clone, Debug, PartialEq)]
pub struct List {
data: VectorSync<Value>,
data: Vec<Value>,
}
impl Display for List {

View File

@@ -3,7 +3,6 @@ use std::cell::RefCell;
use gc_arena::{Arena, Collect, Gc, Mutation, Rootable};
use hashbrown::{HashMap, HashSet};
use inkwell::context::Context;
use itertools::Itertools;
use crate::builtins::vm_env;
use crate::bytecode::{BinOp, Func as F, OpCode, OpCodes, Program, UnOp};
@@ -88,49 +87,9 @@ pub fn eval<T, F: for<'gc> FnOnce(Value<'gc>, &mut GcRoot<'gc>, &Mutation<'gc>)
}
Consq::PopEnv => _ = root.envs.pop().unwrap(),
Consq::Call => {
use Param::*;
let arg = root.stack.pop();
let func = root.stack.pop().unwrap_func();
let env = func.env;
let 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),
)
})
})
.unwrap();
new.insert(formal, arg);
}
if let Some(alias) = alias {
new.insert(alias, Value::AttrSet(arg));
}
env.enter_let(Gc::new(mc, new), mc)
}
};
let env = func.env.enter_arg(arg, mc);
let count = func.count.get();
func.count.set(count + 1);
if count >= 1 {
@@ -200,13 +159,12 @@ fn single_op<'gc, const CAP: usize>(
let Some(val) = stack.tos().as_ref().unwrap_thunk().get_value() else {
return Ok(Consq::Force);
};
stack.pop();
stack.push(val)?;
*stack.tos_mut() = val;
}
OpCode::InsertValue => {
let val = stack.pop();
stack.pop().unwrap_thunk().insert_value(val.clone(), mc);
let _ = stack.push(val);
stack.tos().as_ref().unwrap_thunk().insert_value(val.clone(), mc);
*stack.tos_mut() = val;
}
OpCode::Jmp { step } => return Ok(Consq::Jmp(step)),
OpCode::JmpIfFalse { step } => {
@@ -218,7 +176,7 @@ fn single_op<'gc, const CAP: usize>(
let arg = stack.pop();
let func = stack.tos_mut();
if func.is_func() {
stack.push(arg)?;
let _ = stack.push(arg);
return Ok(Consq::Call);
}
func.call(arg, vm, mc)?;
@@ -227,6 +185,9 @@ fn single_op<'gc, const CAP: usize>(
let func = vm.get_func(idx);
stack.push(Value::Func(Gc::new(mc, Func::new(func, *env, mc))))?;
}
OpCode::Arg { level } => {
stack.push(env.lookup_arg(level))?;
}
OpCode::UnOp { op } => {
use UnOp::*;
let value = stack.tos_mut();
@@ -262,8 +223,8 @@ fn single_op<'gc, const CAP: usize>(
OpCode::Path => {
todo!()
}
OpCode::List => {
stack.push(Value::List(CoW::new(List::empty(), mc)))?;
OpCode::List { cap } => {
stack.push(Value::List(CoW::new(List::with_capacity(cap), mc)))?;
}
OpCode::PushElem => {
let elem = stack.pop();
@@ -272,22 +233,14 @@ fn single_op<'gc, const CAP: usize>(
OpCode::AttrSet { cap } => {
stack.push(Value::AttrSet(CoW::new(AttrSet::with_capacity(cap), mc)))?;
}
OpCode::FinalizeRec => {
let mut new = HashMap::new();
stack
.tos()
OpCode::FinalizeLet => {
let mut list = stack.pop().unwrap_list();
let map = list
.as_ref()
.unwrap_attr_set()
.as_inner()
.iter()
.map(|(&k, v)| (k, v.clone()))
.collect_into(&mut new);
*env = env.enter_let(Gc::new(mc, new), mc);
stack
.tos_mut()
.as_mut(mc)
.unwrap_attr_set()
.capture(*env, mc);
.clone()
.into_inner();
*env = env.enter_let(map, mc);
list.make_mut(|list| list.capture(*env, mc), mc);
}
OpCode::PushStaticAttr { name } => {
let val = stack.pop();
@@ -296,7 +249,7 @@ fn single_op<'gc, const CAP: usize>(
OpCode::PushDynamicAttr => {
let val = stack.pop();
let sym = stack.pop();
let sym = vm.new_sym(sym.unwrap_string().as_ref());
let sym = vm.new_sym::<&str>(&sym.unwrap_string());
stack.tos_mut().push_attr(sym, val, mc);
}
OpCode::Select { sym } => {
@@ -309,14 +262,14 @@ fn single_op<'gc, const CAP: usize>(
OpCode::SelectDynamic => {
let mut val = stack.pop();
val.coerce_to_string();
let sym = vm.new_sym(val.unwrap_string().as_ref());
let sym = vm.new_sym::<&str>(&val.unwrap_string());
stack.tos_mut().select(sym, vm)?;
}
OpCode::SelectDynamicOrDefault => {
let default = stack.pop();
let mut val = stack.pop();
val.coerce_to_string();
let sym = vm.new_sym(val.unwrap_string().as_ref());
let sym = vm.new_sym::<&str>(&val.unwrap_string());
stack.tos_mut().select_with_default(sym, default)?;
}
OpCode::HasAttr { sym } => {
@@ -325,25 +278,18 @@ fn single_op<'gc, const CAP: usize>(
OpCode::HasDynamicAttr => {
let mut val = stack.pop();
val.coerce_to_string();
let sym = vm.new_sym(val.unwrap_string().as_ref());
let sym = vm.new_sym::<&str>(&val.unwrap_string());
stack.tos_mut().has_attr(sym);
}
OpCode::LookUp { sym } => {
stack.push(
env.lookup_slow(&sym)
env.lookup_with(&sym)
.ok_or_else(|| Error::EvalError(format!("{} not found", vm.get_sym(sym))))?
.clone(),
)?;
}
OpCode::EnterLetEnv => {
let mut new = HashMap::new();
stack
.pop()
.unwrap_attr_set()
.iter()
.map(|(&k, v)| (k, v.clone()))
.collect_into(&mut new);
*env = env.enter_let(Gc::new(mc, new), mc);
OpCode::LookUpLet { level, idx } => {
stack.push(env.lookup_let(level, idx))?;
}
OpCode::LeaveEnv => *env = env.leave(),
OpCode::EnterWithEnv => {
@@ -401,7 +347,7 @@ impl<'gc> VM<'gc> {
self.symbols.borrow()[idx].clone().into()
}
pub fn new_sym(&self, sym: impl Into<EcoString>) -> usize {
pub fn new_sym<T: Into<EcoString>>(&self, sym: T) -> usize {
let sym = sym.into();
if let Some(&idx) = self.symmap.borrow().get(&sym) {
idx
@@ -420,7 +366,7 @@ impl<'gc> VM<'gc> {
pub fn compile_func(&'gc self, func: &'gc F) -> JITFunc<'gc> {
self.jit
.compile_seq(func.opcodes.iter().copied(), self)
.compile_seq(func.opcodes.iter().copied().rev(), self)
.unwrap()
}
}

View File

@@ -7,7 +7,6 @@ use hashbrown::HashMap;
use test::{Bencher, black_box};
use ecow::EcoString;
use rpds::vector_sync;
use crate::compile::compile;
use crate::ir::downgrade;
@@ -75,7 +74,7 @@ macro_rules! symbol {
macro_rules! list {
($($x:tt)*) => (
Value::List(List::new(vector_sync![$($x)*]))
Value::List(List::new(vec![$($x)*]))
);
}