chore: cleanup

This commit is contained in:
2025-06-08 00:59:31 +08:00
parent 0fd846e844
commit 3797544fc2
25 changed files with 1028 additions and 1481 deletions

59
Cargo.lock generated
View File

@@ -80,6 +80,26 @@ version = "3.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7704b5fdd17b18ae31c4c1da5a2e0305a2bf17b5249300a9ee9ed7b72114c636"
[[package]]
name = "crossbeam-utils"
version = "0.8.21"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d0a5c400df2834b80a4c3327b3aad3a4c4cd4de0629063962b03235697506a28"
[[package]]
name = "dashmap"
version = "6.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5041cc499144891f3790297212f32a74fb938e5136a14943f338ef9e0ae276cf"
dependencies = [
"cfg-if",
"crossbeam-utils",
"hashbrown 0.14.3",
"lock_api",
"once_cell",
"parking_lot_core",
]
[[package]]
name = "derive_more"
version = "2.0.1"
@@ -251,6 +271,16 @@ dependencies = [
"semver",
]
[[package]]
name = "lock_api"
version = "0.4.13"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "96936507f153605bddfcda068dd804796c84324ed2510809e5b2a624c81da765"
dependencies = [
"autocfg",
"scopeguard",
]
[[package]]
name = "log"
version = "0.4.27"
@@ -297,6 +327,7 @@ dependencies = [
name = "nixjit"
version = "0.0.0"
dependencies = [
"dashmap",
"derive_more",
"ecow",
"hashbrown 0.15.3",
@@ -315,6 +346,19 @@ version = "1.21.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d"
[[package]]
name = "parking_lot_core"
version = "0.9.11"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bc838d2a56b5b1a6c25f55575dfc605fabb63bb2365f6c2353ef9159aa69e4a5"
dependencies = [
"cfg-if",
"libc",
"redox_syscall",
"smallvec",
"windows-targets",
]
[[package]]
name = "proc-macro2"
version = "1.0.95"
@@ -343,6 +387,15 @@ dependencies = [
"nibble_vec",
]
[[package]]
name = "redox_syscall"
version = "0.5.12"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "928fca9cf2aa042393a8325b9ead81d2f0df4cb12e1e24cef072922ccd99c5af"
dependencies = [
"bitflags",
]
[[package]]
name = "regex"
version = "1.11.1"
@@ -447,6 +500,12 @@ dependencies = [
"windows-sys",
]
[[package]]
name = "scopeguard"
version = "1.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49"
[[package]]
name = "semver"
version = "1.0.26"

View File

@@ -27,6 +27,7 @@ derive_more = { version = "2.0", features = ["full"] }
ecow = "0.2"
regex = "1.11"
hashbrown = "0.15"
dashmap = "6.1"
replace_with = "0.1"
inkwell = { version = "0.6.0", features = ["llvm18-1"] }

View File

@@ -1,30 +0,0 @@
use std::process::exit;
use itertools::Itertools;
use nixjit::compile::compile;
use nixjit::error::Error;
use nixjit::error::Result;
use nixjit::ir::downgrade;
fn main() -> Result<()> {
let mut args = std::env::args();
if args.len() != 2 {
eprintln!("Usage: {} expr", args.next().unwrap());
exit(1);
}
args.next();
let expr = args.next().unwrap();
let root = rnix::Root::parse(&expr);
if !root.errors().is_empty() {
return Err(Error::ParseError(
root.errors().iter().map(|err| err.to_string()).join(";"),
));
}
let expr = root.tree().expr().unwrap();
let downgraded = downgrade(expr)?;
let prog = compile(downgraded);
println!("{prog:?}");
Ok(())
}

View File

@@ -2,11 +2,10 @@ use std::process::exit;
use itertools::Itertools;
use nixjit::compile::compile;
use nixjit::error::Error;
use nixjit::error::Result;
use nixjit::ir::downgrade;
use nixjit::vm::run;
use nixjit::eval::eval;
fn main() -> Result<()> {
let mut args = std::env::args();
@@ -23,9 +22,8 @@ fn main() -> Result<()> {
));
}
let expr = root.tree().expr().unwrap();
let downgraded = downgrade(expr)?;
let prog = compile(downgraded);
println!("{}", run(prog)?);
let expr = downgrade(expr)?;
println!("{}", eval(expr)?);
Ok(())
}

View File

@@ -1,12 +1,11 @@
use ecow::EcoString;
use hashbrown::HashMap;
use crate::ty::common::Const;
use crate::ir::{DowngradeContext, Ir};
use crate::ir::{DowngradeContext, Ir, Const};
pub fn ir_env(ctx: &mut DowngradeContext) -> HashMap<usize, Ir> {
pub fn ir_env(ctx: &mut DowngradeContext) -> HashMap<EcoString, 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.insert("true".into(), Const::from(true).ir());
map.insert("false".into(), Const::from(false).ir());
map
}

View File

@@ -1,125 +0,0 @@
use ecow::EcoString;
use hashbrown::HashMap;
use crate::ty::common::Const;
use crate::ty::internal::Param;
type Slice<T> = Box<[T]>;
pub type OpCodes = Slice<OpCode>;
#[derive(Debug, Clone, Copy)]
pub enum OpCode {
/// load a constant onto stack
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
LoadValue { idx: usize },
/// let TOS capture current environment
CaptureEnv,
/// force TOS to value
ForceValue,
/// TODO:
InsertValue,
/// [ .. func arg ] consume 2 elements, call `func` with arg
Call,
/// make a function
Func { idx: usize },
/// load a function argument
Arg { level: usize },
/// consume 1 element, assert TOS is true
Assert,
/// jump forward
Jmp { step: usize },
/// [ .. cond ] consume 1 element, if `cond` is false, then jump forward
JmpIfFalse { step: usize },
/// push an empty attribute set onto stack
AttrSet { cap: usize },
/// finalize the recursive attribute set at TOS
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 { cap: usize },
/// [ .. list elem ] consume 1 element, push `elem` into `list`
PushElem,
/// convert the string as TOS to a path
Path,
/// [ .. a b ] consume 2 elements, perform a string concatenation `a` + `b`
ConcatString,
/// [ .. a b ] consume 2 elements, perform a binary operation `a` `op` `b`
BinOp { op: BinOp },
/// [ .. a ] consume 1 element, perform a unary operation `op` `a`
UnOp { op: UnOp },
/// set TOS to the bool value of whether TOS contains `sym`
HasAttr { sym: usize },
/// [ .. set sym ] consume 2 elements, set TOS to the bool value of whether `set` contains `sym`
HasDynamicAttr,
/// [ .. set ] select `sym` from `set`
Select { sym: usize },
/// [ .. set default ] select `sym` from `set` or `default`
SelectOrDefault { sym: usize },
/// [ .. set sym ] select `sym` from `set`
SelectDynamic,
/// [ .. set sym default ] select `sym` from `set` or `default`
SelectDynamicOrDefault,
/// enter the with environment of the attribute set at TOS
EnterWithEnv,
/// exit current envrironment
LeaveEnv,
/// TODO:
PopEnv,
/// illegal operation, used as termporary placeholder
Illegal,
}
#[derive(Debug, Clone, Copy)]
pub enum BinOp {
Add,
Sub,
Mul,
Div,
And,
Or,
Eq,
Lt,
Con,
Upd,
}
#[derive(Debug, Clone, Copy)]
pub enum UnOp {
Neg,
Not,
}
#[derive(Debug)]
pub struct Func {
pub param: Param,
pub opcodes: OpCodes,
}
#[derive(Debug)]
pub struct Program {
pub top_level: OpCodes,
pub thunks: Slice<OpCodes>,
pub funcs: Slice<Func>,
pub symbols: Vec<EcoString>,
pub symmap: HashMap<EcoString, usize>,
pub consts: Box<[Const]>,
}

View File

@@ -1,447 +0,0 @@
use crate::bytecode::*;
use crate::ir;
pub struct Compiler {
opcodes: Vec<OpCode>,
}
pub fn compile(downgraded: ir::Downgraded) -> Program {
Program {
top_level: Compiler::new().compile(downgraded.top_level),
thunks: downgraded
.thunks
.into_iter()
.map(|thunk| Compiler::new().compile(thunk))
.collect(),
funcs: downgraded
.funcs
.into_iter()
.map(|func| Func {
param: func.param.into(),
opcodes: Compiler::new().compile(*func.body),
})
.collect(),
symbols: downgraded.symbols,
symmap: downgraded.symmap,
consts: downgraded.consts,
}
}
impl Compiler {
fn new() -> Self {
Self {
opcodes: Vec::new(),
}
}
fn compile(mut self, ir: ir::Ir) -> OpCodes {
ir.compile_force(&mut self);
self.opcodes()
}
fn push(&mut self, code: OpCode) {
self.opcodes.push(code);
}
fn idx(&self) -> usize {
self.opcodes.len()
}
fn modify(&mut self, idx: usize, code: OpCode) {
self.opcodes[idx] = code;
}
fn pop(&mut self) -> Option<OpCode> {
self.opcodes.pop()
}
fn opcodes(self) -> OpCodes {
self.opcodes.into()
}
}
pub trait Compile: Sized {
fn compile(self, comp: &mut Compiler);
fn compile_force(self, comp: &mut Compiler) {
self.compile(comp);
comp.push(OpCode::ForceValue);
}
}
pub trait CompileWithLength {
fn compile_with_length(self, comp: &mut Compiler) -> usize;
}
impl<T: Compile> CompileWithLength for T {
fn compile_with_length(self, comp: &mut Compiler) -> usize {
let start = comp.idx();
self.compile(comp);
let end = comp.idx();
end - start
}
}
impl Compile for ir::Const {
fn compile(self, comp: &mut Compiler) {
comp.push(OpCode::Const { idx: self.idx });
}
fn compile_force(self, comp: &mut Compiler) {
self.compile(comp);
}
}
impl Compile for ir::Var {
fn compile(self, comp: &mut Compiler) {
comp.push(OpCode::LookUp { sym: self.sym });
}
}
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 });
}
fn compile_force(self, comp: &mut Compiler) {
comp.push(OpCode::LoadValue { idx: self.idx });
}
}
impl Compile for ir::Attrs {
fn compile(self, comp: &mut Compiler) {
comp.push(OpCode::AttrSet {
cap: self.stcs.len() + self.dyns.len(),
});
for stc in self.stcs {
let thunk = stc.1.is_thunk();
stc.1.compile(comp);
if thunk {
comp.push(OpCode::CaptureEnv);
}
comp.push(OpCode::PushStaticAttr { name: stc.0 });
}
for dynamic in self.dyns {
let thunk = dynamic.1.is_thunk();
dynamic.0.compile_force(comp);
dynamic.1.compile(comp);
if thunk {
comp.push(OpCode::CaptureEnv);
}
comp.push(OpCode::PushDynamicAttr)
}
}
}
impl Compile for ir::List {
fn compile(self, comp: &mut Compiler) {
comp.push(OpCode::List {
cap: self.items.len(),
});
for item in self.items {
item.compile(comp);
comp.push(OpCode::PushElem);
}
}
}
impl Compile for ir::UnOp {
fn compile(self, comp: &mut Compiler) {
use ir::UnOpKind::*;
match self.kind {
Neg => {
self.rhs.compile_force(comp);
comp.push(OpCode::UnOp { op: UnOp::Neg });
}
Not => {
self.rhs.compile_force(comp);
comp.push(OpCode::UnOp { op: UnOp::Not });
}
}
}
}
impl Compile for ir::BinOp {
fn compile(self, comp: &mut Compiler) {
use ir::BinOpKind::*;
match self.kind {
Add => {
self.lhs.compile_force(comp);
self.rhs.compile_force(comp);
comp.push(OpCode::BinOp { op: BinOp::Add });
}
Mul => {
self.lhs.compile_force(comp);
self.rhs.compile_force(comp);
comp.push(OpCode::BinOp { op: BinOp::Mul });
}
Div => {
self.lhs.compile_force(comp);
self.rhs.compile_force(comp);
comp.push(OpCode::BinOp { op: BinOp::Div });
}
And => {
self.lhs.compile_force(comp);
self.rhs.compile_force(comp);
comp.push(OpCode::BinOp { op: BinOp::And });
}
Or => {
self.lhs.compile_force(comp);
self.rhs.compile_force(comp);
comp.push(OpCode::BinOp { op: BinOp::Or });
}
Eq => {
self.lhs.compile_force(comp);
self.rhs.compile_force(comp);
comp.push(OpCode::BinOp { op: BinOp::Eq });
}
Lt => {
self.lhs.compile_force(comp);
self.rhs.compile_force(comp);
comp.push(OpCode::BinOp { op: BinOp::Lt });
}
Con => {
self.lhs.compile_force(comp);
self.rhs.compile_force(comp);
comp.push(OpCode::BinOp { op: BinOp::Con });
}
Upd => {
self.lhs.compile_force(comp);
self.rhs.compile_force(comp);
comp.push(OpCode::BinOp { op: BinOp::Upd });
}
Sub => {
self.lhs.compile_force(comp);
self.rhs.compile_force(comp);
comp.push(OpCode::BinOp { op: BinOp::Sub });
}
Impl => {
self.lhs.compile_force(comp);
comp.push(OpCode::UnOp { op: UnOp::Not });
self.rhs.compile_force(comp);
comp.push(OpCode::BinOp { op: BinOp::Or });
}
Neq => {
self.lhs.compile_force(comp);
self.rhs.compile_force(comp);
comp.push(OpCode::BinOp { op: BinOp::Eq });
comp.push(OpCode::UnOp { op: UnOp::Not });
}
Gt => {
self.rhs.compile_force(comp);
self.lhs.compile_force(comp);
comp.push(OpCode::BinOp { op: BinOp::Lt });
}
Leq => {
self.rhs.compile_force(comp);
self.lhs.compile_force(comp);
comp.push(OpCode::BinOp { op: BinOp::Lt });
comp.push(OpCode::UnOp { op: UnOp::Not });
}
Geq => {
self.lhs.compile_force(comp);
self.rhs.compile_force(comp);
comp.push(OpCode::BinOp { op: BinOp::Lt });
comp.push(OpCode::UnOp { op: UnOp::Not });
}
PipeL => {
self.lhs.compile_force(comp);
self.rhs.compile_force(comp);
comp.push(OpCode::Call);
}
PipeR => {
self.rhs.compile_force(comp);
self.lhs.compile_force(comp);
comp.push(OpCode::Call);
}
}
}
}
impl Compile for ir::HasAttr {
fn compile(self, comp: &mut Compiler) {
self.lhs.compile(comp);
for attr in self.rhs {
comp.push(OpCode::ForceValue);
match attr {
ir::Attr::Str(sym) => {
comp.push(OpCode::AttrSet { cap: 0 });
comp.push(OpCode::SelectOrDefault { sym })
}
ir::Attr::Dynamic(dynamic) => {
dynamic.compile_force(comp);
comp.push(OpCode::AttrSet { cap: 0 });
comp.push(OpCode::SelectDynamicOrDefault);
}
ir::Attr::Strs(string) => {
string.compile(comp);
comp.push(OpCode::AttrSet { cap: 0 });
comp.push(OpCode::SelectDynamicOrDefault);
}
}
}
let last = comp.pop().unwrap();
let _ = comp.pop();
match last {
OpCode::SelectOrDefault { sym } => comp.push(OpCode::HasAttr { sym }),
OpCode::SelectDynamicOrDefault => comp.push(OpCode::HasDynamicAttr),
_ => unreachable!(),
}
}
}
impl Compile for ir::Select {
fn compile(self, comp: &mut Compiler) {
self.expr.compile(comp);
for attr in self.attrpath {
comp.push(OpCode::ForceValue);
match attr {
ir::Attr::Str(sym) => {
comp.push(OpCode::AttrSet { cap: 0 });
comp.push(OpCode::SelectOrDefault { sym })
}
ir::Attr::Dynamic(dynamic) => {
dynamic.compile(comp);
comp.push(OpCode::AttrSet { cap: 0 });
comp.push(OpCode::SelectDynamicOrDefault);
}
ir::Attr::Strs(string) => {
string.compile(comp);
comp.push(OpCode::AttrSet { cap: 0 });
comp.push(OpCode::SelectDynamicOrDefault);
}
}
}
match self.default {
Some(default) => {
let last = comp.pop().unwrap();
let _ = comp.pop();
default.compile(comp);
comp.push(last);
}
None => {
let last = comp.pop().unwrap();
let _ = comp.pop();
match last {
OpCode::SelectOrDefault { sym } => comp.push(OpCode::Select { sym }),
OpCode::SelectDynamicOrDefault => comp.push(OpCode::SelectDynamic),
_ => unreachable!(),
}
}
}
}
fn compile_force(self, comp: &mut Compiler) {
self.compile(comp);
comp.push(OpCode::ForceValue);
}
}
impl Compile for ir::ConcatStrings {
fn compile(self, comp: &mut Compiler) {
let mut iter = self.parts.into_iter();
iter.next().unwrap().compile_force(comp);
for item in iter {
item.compile_force(comp);
comp.push(OpCode::ConcatString);
}
}
}
impl Compile for ir::If {
fn compile(self, comp: &mut Compiler) {
self.cond.compile(comp);
let idx_jmp_if_false = comp.idx();
// place holder
comp.push(OpCode::Illegal);
let consq_length = self.consq.compile_with_length(comp);
let idx_jmp = comp.idx();
// place holder
comp.push(OpCode::Illegal);
let alter_length = self.alter.compile_with_length(comp);
comp.modify(
idx_jmp_if_false,
OpCode::JmpIfFalse {
step: consq_length + 1,
},
);
comp.modify(idx_jmp, OpCode::Jmp { step: alter_length });
}
}
impl Compile for ir::Let {
fn compile(self, comp: &mut Compiler) {
comp.push(OpCode::List {
cap: self.bindings.len(),
});
for (_, val) in self.bindings {
val.compile(comp);
comp.push(OpCode::PushElem);
}
comp.push(OpCode::FinalizeLet);
let thunk = self.expr.is_thunk();
self.expr.compile(comp);
if thunk {
comp.push(OpCode::CaptureEnv);
}
comp.push(OpCode::LeaveEnv);
}
}
impl Compile for ir::With {
fn compile(self, comp: &mut Compiler) {
self.namespace.compile(comp);
comp.push(OpCode::EnterWithEnv);
self.expr.compile(comp);
comp.push(OpCode::LeaveEnv);
}
}
impl Compile for ir::Assert {
fn compile(self, comp: &mut Compiler) {
self.assertion.compile(comp);
comp.push(OpCode::Assert);
self.expr.compile(comp);
}
}
impl Compile for ir::LoadFunc {
fn compile(self, comp: &mut Compiler) {
comp.push(OpCode::Func { idx: self.idx });
}
}
impl Compile for ir::Call {
fn compile(self, comp: &mut Compiler) {
self.func.compile_force(comp);
self.args.into_iter().for_each(|arg| {
arg.compile(comp);
comp.push(OpCode::Call);
});
}
}
impl Compile for ir::Path {
fn compile(self, comp: &mut Compiler) {
self.expr.compile(comp);
comp.push(OpCode::Path);
}
}

29
src/engine/mod.rs Normal file
View File

@@ -0,0 +1,29 @@
use hashbrown::HashSet;
use crate::eval::Evaluate;
use crate::ir::{Downgraded, Ir};
use crate::ty::public::Value;
use crate::error::Result;
#[cfg(test)]
mod test;
pub struct Engine {
}
pub fn eval(downgraded: Downgraded) -> Result<Value> {
let mut engine = Engine::new();
engine.eval(downgraded.top_level)
}
impl Engine {
pub fn new() -> Self {
Self {}
}
pub fn eval(&mut self, expr: Ir) -> Result<Value> {
expr.eval(self).map(|val| val.to_public(self, &mut HashSet::new()))
}
}

229
src/engine/test.rs Normal file
View File

@@ -0,0 +1,229 @@
#![allow(unused_macros)]
extern crate test;
use hashbrown::HashMap;
use test::{Bencher, black_box};
use ecow::EcoString;
use crate::ir::downgrade;
use crate::ty::common::Const;
use crate::ty::public::*;
use super::eval;
#[inline]
fn test_expr(expr: &str, expected: Value) {
let downgraded = downgrade(rnix::Root::parse(expr).tree().expr().unwrap()).unwrap();
assert_eq!(eval(downgraded).unwrap(), expected);
}
macro_rules! map {
($($k:expr => $v:expr),*) => {
{
#[allow(unused_mut)]
let mut m = HashMap::new();
$(
m.insert($k, $v);
)*
m
}
};
}
macro_rules! thunk {
() => {
Value::Thunk
};
}
macro_rules! int {
($e:expr) => {
Value::Const(Const::Int($e))
};
}
macro_rules! float {
($e:expr) => {
Value::Const(Const::Float($e as f64))
};
}
macro_rules! boolean {
($e:expr) => {
Value::Const(Const::Bool($e))
};
}
macro_rules! string {
($e:expr) => {
Value::String(EcoString::from($e))
};
}
macro_rules! symbol {
($e:expr) => {
Symbol::from($e.to_string())
};
}
macro_rules! list {
($($x:tt)*) => (
Value::List(List::new(vec![$($x)*]))
);
}
macro_rules! attrs {
($($x:tt)*) => (
Value::AttrSet(AttrSet::new(map!{$($x)*}))
)
}
#[test]
fn test_arith() {
test_expr("1", int!(1));
test_expr("1.", float!(1));
test_expr("-1", int!(-1));
test_expr("-1.", float!(-1));
test_expr("1 + 1", int!(2));
test_expr("1 + 1.", float!(2));
test_expr("1. + 1", float!(2));
test_expr("1. + 1.", float!(2));
test_expr("1 - 1", int!(0));
test_expr("1 - 1.", float!(0));
test_expr("1. - 1", float!(0));
test_expr("1. - 1.", float!(0));
test_expr("1 * 1", int!(1));
test_expr("1 * 1.", float!(1));
test_expr("1. * 1", float!(1));
test_expr("1. * 1.", float!(1));
test_expr("1 / 1", int!(1));
test_expr("1 / 1.", float!(1));
test_expr("1. / 1", float!(1));
test_expr("1. / 1.", float!(1));
}
#[test]
fn test_cmp() {
test_expr("1 < 2", boolean!(true));
test_expr("1 < 1", boolean!(false));
test_expr("1 > 0", boolean!(true));
test_expr("1 > 1", boolean!(false));
test_expr("1 <= 1", boolean!(true));
test_expr("1 <= 0", boolean!(false));
test_expr("1 >= 1", boolean!(true));
test_expr("1 >= 2", boolean!(false));
}
#[test]
fn test_string() {
test_expr(r#""test""#, string!("test"));
test_expr(r#""hello" + " world""#, string!("hello world"));
}
#[test]
fn test_bool() {
test_expr("true", boolean!(true));
test_expr("false", boolean!(false));
test_expr("!false", boolean!(true));
test_expr("true && false", boolean!(false));
test_expr("true || false", boolean!(true));
test_expr("true -> false", boolean!(false));
}
#[test]
fn test_list() {
test_expr(
"[ 1 2 3 true ]",
list![int!(1), int!(2), int!(3), boolean!(true)],
);
test_expr(
"[ 1 2 ] ++ [ 3 4 ]",
list![int!(1), int!(2), int!(3), int!(4)],
);
}
#[test]
fn test_attrs() {
test_expr(
"{ a = 1; }",
attrs! {
symbol!("a") => int!(1)
},
);
test_expr("{ a = 1; }.a", int!(1));
test_expr("{ a = 1; }.b or 1", int!(1));
test_expr(
"{ a = { a = 1; }; }.a",
attrs! {
symbol!("a") => int!(1)
},
);
test_expr("{ a.b = 1; }.a.b", int!(1));
test_expr(
"{ a.b = 1; a.c = 2; }",
attrs! { symbol!("a") => attrs!{ symbol!("b") => int!(1), symbol!("c") => int!(2) } },
);
test_expr("{ a.b = 1; } ? a.b", boolean!(true));
test_expr(
"{ a.b = 1; } // { a.c = 2; }",
attrs! { symbol!("a") => attrs!{ symbol!("c") => int!(2) } },
);
}
#[test]
fn test_if() {
test_expr("if true || false then 1 else 2", int!(1));
}
#[test]
fn test_with() {
test_expr(r#"with { a = 1; }; a"#, int!(1));
}
#[test]
fn test_let() {
test_expr(r#"let a = 1; in a"#, int!(1));
test_expr(r#"let a = 1; b = a; in b"#, int!(1));
test_expr(r#"let a = { a = 1; }; b = "a"; in a.${b}"#, int!(1));
test_expr(
r#"let b = "c"; in { a.b = 1; } // { a."a${b}" = 2; }"#,
attrs! { symbol!("a") => attrs!{ symbol!("ac") => int!(2) } },
);
}
#[test]
fn test_func() {
test_expr("(x: x) 1", int!(1));
test_expr("(x: x) (x: x) 1", int!(1));
test_expr("(x: y: x + y) 1 1", int!(2));
test_expr("({ x, y }: x + y) { x = 1; y = 2; }", int!(3));
test_expr("({ x, y, ... }: x + y) { x = 1; y = 2; z = 3; }", int!(3));
test_expr(
"(inputs@{ x, y, ... }: x + inputs.y) { x = 1; y = 2; z = 3; }",
int!(3),
);
}
#[test]
#[ignore]
fn test_fib() {
test_expr(
"let fib = n: if n == 1 || n == 2 then 1 else (fib (n - 1)) + (fib (n - 2)); in fib 30",
int!(832040),
)
}
#[bench]
fn bench_fib(b: &mut Bencher) {
b.iter(|| {
test_expr(
"let fib = n: if n == 1 || n == 2 then 1 else (fib (n - 1)) + (fib (n - 2)); in fib 30",
int!(832040),
);
black_box(())
})
}

129
src/eval/jit/compile.rs Normal file
View File

@@ -0,0 +1,129 @@
#![allow(unused_variables)]
use crate::ir::*;
use super::JITContext;
pub trait JITCompile {
fn compile<'gc>(self, ctx: &JITContext<'gc>);
}
impl JITCompile for Attrs {
fn compile<'gc>(self, ctx: &JITContext<'gc>) {
todo!()
}
}
impl JITCompile for List {
fn compile<'gc>(self, ctx: &JITContext<'gc>) {
todo!()
}
}
impl JITCompile for HasAttr {
fn compile<'gc>(self, ctx: &JITContext<'gc>) {
todo!()
}
}
impl JITCompile for BinOp {
fn compile<'gc>(self, ctx: &JITContext<'gc>) {
todo!()
}
}
impl JITCompile for UnOp {
fn compile<'gc>(self, ctx: &JITContext<'gc>) {
todo!()
}
}
impl JITCompile for Select {
fn compile<'gc>(self, ctx: &JITContext<'gc>) {
todo!()
}
}
impl JITCompile for If {
fn compile<'gc>(self, ctx: &JITContext<'gc>) {
todo!()
}
}
impl JITCompile for LoadFunc {
fn compile<'gc>(self, ctx: &JITContext<'gc>) {
todo!()
}
}
impl JITCompile for Call {
fn compile<'gc>(self, ctx: &JITContext<'gc>) {
todo!()
}
}
impl JITCompile for Let {
fn compile<'gc>(self, ctx: &JITContext<'gc>) {
todo!()
}
}
impl JITCompile for With {
fn compile<'gc>(self, ctx: &JITContext<'gc>) {
todo!()
}
}
impl JITCompile for Assert {
fn compile<'gc>(self, ctx: &JITContext<'gc>) {
todo!()
}
}
impl JITCompile for ConcatStrings {
fn compile<'gc>(self, ctx: &JITContext<'gc>) {
todo!()
}
}
impl JITCompile for Const {
fn compile<'gc>(self, ctx: &JITContext<'gc>) {
todo!()
}
}
impl JITCompile for String {
fn compile<'gc>(self, ctx: &JITContext<'gc>) {
todo!()
}
}
impl JITCompile for Var {
fn compile<'gc>(self, ctx: &JITContext<'gc>) {
todo!()
}
}
impl JITCompile for Arg {
fn compile<'gc>(self, ctx: &JITContext<'gc>) {
todo!()
}
}
impl JITCompile for LetVar {
fn compile<'gc>(self, ctx: &JITContext<'gc>) {
todo!()
}
}
impl JITCompile for Thunk {
fn compile<'gc>(self, ctx: &JITContext<'gc>) {
todo!()
}
}
impl JITCompile for Path {
fn compile<'gc>(self, ctx: &JITContext<'gc>) {
todo!()
}
}

View File

@@ -8,13 +8,11 @@ use inkwell::module::Module;
use inkwell::types::{FloatType, FunctionType, IntType, PointerType, StructType};
use inkwell::values::{BasicValueEnum, FunctionValue};
use crate::bytecode::OpCodes;
use crate::env::VmEnv;
use crate::jit::JITValueData;
use crate::eval::Engine;
use crate::ty::internal::{Thunk, Value};
use crate::vm::VM;
use super::{JITContext, JITValue, ValueTag};
use super::{JITContext, JITValue, ValueTag, JITValueData};
pub struct Helpers<'ctx> {
pub int_type: IntType<'ctx>,
@@ -337,16 +335,8 @@ extern "C" fn helper_or(lhs: JITValue, rhs: JITValue) -> JITValue {
}
}
extern "C" fn helper_call<'gc>(func: JITValue, arg: JITValue, vm: *const VM<'gc>) -> JITValue {
let vm = unsafe { vm.as_ref() }.unwrap();
let arg = Value::from(arg);
match func.tag {
ValueTag::Function => {
let func = Value::from(func).unwrap_func();
func.call_compile(arg, vm).unwrap().clone().into()
}
_ => todo!(),
}
extern "C" fn helper_call<'gc>(func: JITValue, arg: JITValue, engine: *mut Engine) -> JITValue {
todo!()
}
extern "C" fn helper_arg(idx: usize, env: *const VmEnv) -> JITValue {
@@ -369,28 +359,15 @@ extern "C" fn helper_lookup(sym: usize, env: *const VmEnv) -> JITValue {
extern "C" fn helper_force<'gc>(
thunk: JITValue,
vm: NonNull<VM<'gc>>,
vm: NonNull<Engine>,
jit: *const JITContext<'gc>,
) -> JITValue {
if !matches!(thunk.tag, ValueTag::Thunk) {
return thunk;
}
let vm = unsafe { vm.as_ref() };
let thunk = Value::from(thunk).unwrap_thunk();
if let Some(val) = thunk.get_value() {
return val.clone().into();
}
let (opcodes, env) = thunk.suspend().unwrap();
let func = unsafe { jit.as_ref() }
.unwrap()
.compile_seq(opcodes.iter().copied().rev(), vm)
.unwrap();
let val = unsafe { func(env.as_ref() as *const _) };
thunk.insert_value(val.into());
val
todo!()
}
extern "C" fn helper_new_thunk(opcodes: *const OpCodes) -> JITValue {
Value::Thunk(Thunk::new(unsafe { opcodes.as_ref() }.unwrap())).into()
extern "C" fn helper_new_thunk(opcodes: *const ()) -> JITValue {
todo!()
}

140
src/eval/jit/mod.rs Normal file
View File

@@ -0,0 +1,140 @@
use std::ops::Deref;
use std::rc::Rc;
use std::marker::PhantomData;
use inkwell::OptimizationLevel;
use inkwell::builder::Builder;
use inkwell::context::Context;
use inkwell::execution_engine::ExecutionEngine;
use inkwell::module::Module;
use crate::env::VmEnv;
use crate::ty::internal::{Value, Thunk};
mod helpers;
mod compile;
pub use compile::JITCompile;
use helpers::Helpers;
#[cfg(test)]
mod test;
#[repr(u64)]
#[derive(Debug, Clone, Copy)]
pub enum ValueTag {
Null,
Int,
Float,
String,
Bool,
AttrSet,
List,
Function,
Thunk,
Path,
}
#[repr(C)]
#[derive(Clone, Copy)]
pub struct JITValue {
tag: ValueTag,
data: JITValueData,
}
#[repr(C)]
#[derive(Clone, Copy)]
pub union JITValueData {
int: i64,
float: f64,
bool: bool,
ptr: *const (),
}
impl<'gc> From<JITValue> for Value<'gc> {
fn from(value: JITValue) -> Self {
use ValueTag::*;
match value.tag {
Int => Value::Int(unsafe { value.data.int }),
Null => Value::Null,
Function => Value::Func(unsafe { Rc::from_raw(value.data.ptr as *const _) }),
Thunk => Value::Thunk(self::Thunk {
thunk: unsafe { Rc::from_raw(value.data.ptr as *const _) },
}),
_ => todo!("not implemented for {:?}", value.tag),
}
}
}
impl From<Value<'_>> for JITValue {
fn from(value: Value) -> Self {
match value {
Value::Int(int) => JITValue {
tag: ValueTag::Int,
data: JITValueData { int },
},
Value::Func(func) => JITValue {
tag: ValueTag::Function,
data: JITValueData {
ptr: Rc::into_raw(func) as *const _,
},
},
Value::Thunk(thunk) => JITValue {
tag: ValueTag::Thunk,
data: JITValueData {
ptr: Rc::into_raw(thunk.thunk) as *const _,
},
},
_ => todo!(),
}
}
}
pub struct JITFunc<'gc>(F<'gc>, PhantomData<&'gc mut ()>);
type F<'gc> = unsafe extern "C" fn(*const VmEnv<'gc>) -> JITValue;
impl<'gc> From<F<'gc>> for JITFunc<'gc> {
fn from(value: F<'gc>) -> Self {
Self(value, PhantomData)
}
}
impl<'gc> Deref for JITFunc<'gc> {
type Target = F<'gc>;
fn deref(&self) -> &Self::Target {
&self.0
}
}
pub struct JITContext<'ctx> {
context: &'ctx Context,
module: Module<'ctx>,
builder: Builder<'ctx>,
execution_engine: ExecutionEngine<'ctx>,
helpers: Helpers<'ctx>,
}
impl<'ctx> JITContext<'ctx> {
pub fn new(context: &'ctx Context) -> Self {
// force linker to link JIT engine
unsafe {
inkwell::llvm_sys::execution_engine::LLVMLinkInMCJIT();
}
let module = context.create_module("nixjit");
let execution_engine = module
.create_jit_execution_engine(OptimizationLevel::Aggressive)
.unwrap();
let helpers = Helpers::new(context, &module, &execution_engine);
JITContext {
execution_engine,
builder: context.create_builder(),
context,
module,
helpers,
}
}
}

View File

@@ -8,19 +8,14 @@ use inkwell::context::Context;
use ecow::EcoString;
use crate::compile::compile;
use crate::ir::downgrade;
use crate::jit::JITContext;
use super::JITContext;
use crate::ty::common::Const;
use crate::ty::public::*;
use crate::vm::run;
#[inline]
fn test_expr(expr: &str, expected: Value) {
let downgraded = downgrade(rnix::Root::parse(expr).tree().expr().unwrap()).unwrap();
let prog = compile(downgraded);
dbg!(&prog);
assert_eq!(run(prog).unwrap(), expected);
todo!()
}
macro_rules! map {

141
src/eval/mod.rs Normal file
View File

@@ -0,0 +1,141 @@
use crate::ty::common::Const;
use crate::ty::internal::Value;
use crate::ir::{self, Downgraded};
use crate::ty::public as p;
use crate::error::Result;
use crate::engine::Engine;
pub mod jit;
pub trait Evaluate {
fn eval<'a>(self, engine: &'a Engine) -> Result<Value<'a>>;
}
impl Evaluate for ir::Attrs {
fn eval<'a>(self, engine: &'a Engine) -> Result<Value<'a>> {
todo!()
}
}
impl Evaluate for ir::List {
fn eval<'a>(self, engine: &'a Engine) -> Result<Value<'a>> {
todo!()
}
}
impl Evaluate for ir::HasAttr {
fn eval<'a>(self, engine: &'a Engine) -> Result<Value<'a>> {
todo!()
}
}
impl Evaluate for ir::BinOp {
fn eval<'a>(self, engine: &'a Engine) -> Result<Value<'a>> {
todo!()
}
}
impl Evaluate for ir::UnOp {
fn eval<'a>(self, engine: &'a Engine) -> Result<Value<'a>> {
todo!()
}
}
impl Evaluate for ir::Select {
fn eval<'a>(self, engine: &'a Engine) -> Result<Value<'a>> {
todo!()
}
}
impl Evaluate for ir::If {
fn eval<'a>(self, engine: &'a Engine) -> Result<Value<'a>> {
todo!()
}
}
impl Evaluate for ir::LoadFunc {
fn eval<'a>(self, engine: &'a Engine) -> Result<Value<'a>> {
todo!()
}
}
impl Evaluate for ir::Call {
fn eval<'a>(self, engine: &'a Engine) -> Result<Value<'a>> {
todo!()
}
}
impl Evaluate for ir::Let {
fn eval<'a>(self, engine: &'a Engine) -> Result<Value<'a>> {
todo!()
}
}
impl Evaluate for ir::With {
fn eval<'a>(self, engine: &'a Engine) -> Result<Value<'a>> {
todo!()
}
}
impl Evaluate for ir::Assert {
fn eval<'a>(self, engine: &'a Engine) -> Result<Value<'a>> {
todo!()
}
}
impl Evaluate for ir::ConcatStrings {
fn eval<'a>(self, engine: &'a Engine) -> Result<Value<'a>> {
todo!()
}
}
impl Evaluate for ir::String {
fn eval<'a>(self, engine: &'a Engine) -> Result<Value<'a>> {
todo!()
}
}
impl Evaluate for ir::Const {
fn eval<'a>(self, _: &'a Engine) -> Result<Value<'a>> {
match self.val {
Const::Null => Value::Null,
Const::Int(x) => Value::Int(x),
Const::Float(x) => Value::Float(x),
Const::Bool(x) => Value::Bool(x),
}.ok()
}
}
impl Evaluate for ir::Var {
fn eval<'a>(self, engine: &'a Engine) -> Result<Value<'a>> {
todo!()
}
}
impl Evaluate for ir::Arg {
fn eval<'a>(self, engine: &'a Engine) -> Result<Value<'a>> {
todo!()
}
}
impl Evaluate for ir::LetVar {
fn eval<'a>(self, engine: &'a Engine) -> Result<Value<'a>> {
todo!()
}
}
impl Evaluate for ir::Thunk {
fn eval<'a>(self, engine: &'a Engine) -> Result<Value<'a>> {
todo!()
}
}
impl Evaluate for ir::Path {
fn eval<'a>(self, engine: &'a Engine) -> Result<Value<'a>> {
todo!()
}
}
pub fn eval(expr: Downgraded) -> Result<p::Value> {
todo!()
}

View File

@@ -7,9 +7,16 @@ use rnix::ast::HasEntry;
use rnix::ast::{self, Expr};
use crate::builtins::ir_env;
use crate::compile::*;
use crate::error::*;
use crate::ty::public::Symbol;
use crate::ty::common as c;
use crate::ty::internal::Value;
use crate::eval::jit::{JITContext, JITCompile};
use crate::eval::Evaluate;
use crate::engine::Engine;
mod utils;
use utils::*;
pub fn downgrade(expr: Expr) -> Result<Downgraded> {
let mut ctx = DowngradeContext::new();
@@ -19,9 +26,6 @@ pub fn downgrade(expr: Expr) -> Result<Downgraded> {
let ir = ir.resolve(&mut ctx, &env)?;
Ok(Downgraded {
top_level: ir,
consts: ctx.consts.into(),
symbols: ctx.symbols,
symmap: ctx.symmap,
thunks: ctx.thunks.into(),
funcs: ctx.funcs.into(),
})
@@ -90,14 +94,23 @@ macro_rules! ir {
}
}
}
impl Compile for Ir {
fn compile(self, ctx: &mut Compiler) {
impl JITCompile for Ir {
fn compile(self, ctx: &JITContext) {
match self {
$(Ir::$ty(ir) => ir.compile(ctx),)*
}
}
}
impl Evaluate for Ir {
fn eval<'a>(self, engine: &'a Engine) -> Result<Value<'a>> {
match self {
$(Ir::$ty(ir) => ir.eval(engine),)*
}
}
}
$(
$(
#[$($x)*]
@@ -119,7 +132,7 @@ macro_rules! ir {
}
ir! {
Attrs => { stcs: HashMap<usize, Ir>, dyns: Vec<DynamicAttrPair> },
Attrs => { stcs: HashMap<EcoString, Ir>, dyns: Vec<DynamicAttrPair> },
List => { items: Vec<Ir> },
HasAttr => { lhs: Box<Ir>, rhs: Vec<Attr> },
BinOp => { lhs: Box<Ir>, rhs: Box<Ir>, kind: BinOpKind },
@@ -129,14 +142,13 @@ ir! {
LoadFunc => { idx: usize },
Call => { func: Box<Ir>, args: Vec<Ir> },
Let => { bindings: Vec<(usize, Ir)>, expr: Box<Ir> },
Let => { bindings: Vec<(EcoString, Ir)>, expr: Box<Ir> },
With => { namespace: Box<Ir>, expr: Box<Ir> },
Assert => { assertion: Box<Ir>, expr: Box<Ir> },
ConcatStrings => { parts: Vec<Ir> },
#[derive(Copy)]
Const => { idx: usize },
#[derive(Copy)]
Var => { sym: usize },
Const => { val: c::Const },
String => { val: EcoString },
Var => { sym: EcoString },
#[derive(Copy)]
Arg => { level: usize },
#[derive(Copy)]
@@ -146,16 +158,18 @@ ir! {
Path => { expr: Box<Ir> },
}
impl<T: Into<c::Const>> From<T> for Const {
fn from(value: T) -> Self {
Const { val: value.into() }
}
}
#[derive(Clone, Debug)]
pub struct DynamicAttrPair(pub Ir, pub Ir);
pub struct DowngradeContext {
thunks: Vec<Ir>,
funcs: Vec<Func>,
consts: Vec<c::Const>,
constmap: HashMap<c::Const, usize>,
symbols: Vec<EcoString>,
symmap: HashMap<EcoString, usize>,
}
struct Env<'a, 'env> {
@@ -164,10 +178,10 @@ struct Env<'a, 'env> {
}
enum EnvNode<'a> {
Builtins(&'a HashMap<usize, Ir>),
Let(&'a Vec<usize>),
SingleArg(usize),
MultiArg(HashMap<usize, Option<Ir>>, Option<usize>),
Builtins(&'a HashMap<EcoString, Ir>),
Let(&'a Vec<EcoString>),
SingleArg(EcoString),
MultiArg(HashMap<EcoString, Option<Ir>>, Option<EcoString>),
With,
}
@@ -180,28 +194,28 @@ enum LookupResult {
}
impl<'a, 'env> Env<'a, 'env> {
fn new(base: &'a HashMap<usize, Ir>) -> Self {
fn new(base: &'a HashMap<EcoString, Ir>) -> Self {
Self {
env: EnvNode::Builtins(base),
prev: None,
}
}
fn enter_let(&'env self, map: &'a Vec<usize>) -> Self {
fn enter_let(&'env self, map: &'a Vec<EcoString>) -> Self {
Self {
env: EnvNode::Let(map),
prev: Some(self),
}
}
fn enter_single_arg(&'env self, ident: usize) -> Self {
fn enter_single_arg(&'env self, ident: EcoString) -> Self {
Self {
env: EnvNode::SingleArg(ident),
prev: Some(self),
}
}
fn enter_multi_arg(&'env self, map: HashMap<usize, Option<Ir>>, alias: Option<usize>) -> Self {
fn enter_multi_arg(&'env self, map: HashMap<EcoString, Option<Ir>>, alias: Option<EcoString>) -> Self {
Self {
env: EnvNode::MultiArg(map, alias),
prev: Some(self),
@@ -217,7 +231,7 @@ impl<'a, 'env> Env<'a, 'env> {
fn _lookup(
&self,
ident: usize,
ident: &EcoString,
mut arg_level: usize,
mut let_level: usize,
has_with: bool,
@@ -226,7 +240,7 @@ impl<'a, 'env> Env<'a, 'env> {
let mut has_with = has_with;
match &self.env {
Builtins(map) => {
return if let Some(ir) = map.get(&ident) {
return if let Some(ir) = map.get(ident) {
Ok(LookupResult::Builtin(ir.clone()))
} else if has_with {
Ok(LookupResult::With)
@@ -235,7 +249,7 @@ impl<'a, 'env> Env<'a, 'env> {
};
}
Let(map) => {
if let Ok(idx) = map.binary_search(&ident) {
if let Ok(idx) = map.binary_search(ident) {
return Ok(LookupResult::Let {
level: let_level,
idx,
@@ -245,19 +259,19 @@ impl<'a, 'env> Env<'a, 'env> {
}
}
SingleArg(arg) => {
if *arg == ident {
if arg == ident {
return Ok(LookupResult::SingleArg { level: arg_level });
} else {
arg_level += 1;
}
}
MultiArg(set, alias) => {
if let Some(default) = set.get(&ident) {
if let Some(default) = set.get(ident) {
return Ok(LookupResult::MultiArg {
level: arg_level,
default: default.clone(),
});
} else if *alias == Some(ident) {
} else if alias.as_ref() == Some(ident) {
return Ok(LookupResult::SingleArg { level: arg_level });
} else {
arg_level += 1;
@@ -270,7 +284,7 @@ impl<'a, 'env> Env<'a, 'env> {
.map_or_else(|| unreachable!(), |x| x)
}
fn lookup(&self, ident: usize) -> core::result::Result<LookupResult, ()> {
fn lookup(&self, ident: &EcoString) -> core::result::Result<LookupResult, ()> {
self._lookup(ident, 0, 0, false)
}
}
@@ -280,19 +294,12 @@ impl DowngradeContext {
DowngradeContext {
thunks: Vec::new(),
funcs: Vec::new(),
consts: Vec::new(),
constmap: HashMap::new(),
symbols: Vec::new(),
symmap: HashMap::new(),
}
}
}
pub struct Downgraded {
pub top_level: Ir,
pub consts: Box<[c::Const]>,
pub symbols: Vec<EcoString>,
pub symmap: HashMap<EcoString, usize>,
pub thunks: Box<[Ir]>,
pub funcs: Box<[Func]>,
}
@@ -310,33 +317,6 @@ impl DowngradeContext {
LoadFunc { idx }
}
pub fn new_const(&mut self, cnst: c::Const) -> Const {
if let Some(&idx) = self.constmap.get(&cnst) {
Const { idx }
} else {
self.constmap.insert(cnst.clone(), self.consts.len());
self.consts.push(cnst);
Const {
idx: self.consts.len() - 1,
}
}
}
pub fn new_sym(&mut self, sym: impl Into<EcoString>) -> usize {
let sym = sym.into();
if let Some(&idx) = self.symmap.get(&sym) {
idx
} else {
self.symmap.insert(sym.clone(), self.symbols.len());
self.symbols.push(sym);
self.symbols.len() - 1
}
}
fn get_sym(&self, idx: usize) -> &EcoString {
&self.symbols[idx]
}
fn resolve_func(&mut self, idx: usize, env: &Env) -> Result<()> {
let self_ptr = self as *mut Self;
self.funcs.get_mut(idx).map_or_else(
@@ -384,7 +364,7 @@ impl Attrs {
.as_mut()
.try_unwrap_attrs().map_err(|_| Error::DowngradeError(format!(
r#"attribute '{}' already defined"#,
ctx.get_sym(ident)
Symbol::from(ident)
)))
.and_then(|attrs: &mut Attrs| attrs._insert(path, name, value, ctx))
} else {
@@ -422,7 +402,7 @@ impl Attrs {
if self.stcs.get(&ident).is_some() {
return Err(Error::DowngradeError(format!(
r#"attribute '{}' already defined"#,
ctx.get_sym(ident)
Symbol::from(ident)
)));
}
self.stcs.insert(ident, value);
@@ -470,7 +450,7 @@ impl Attrs {
pub enum Attr {
Dynamic(Ir),
Strs(ConcatStrings),
Str(usize),
Str(EcoString),
}
impl Attr {
@@ -561,12 +541,11 @@ pub struct Func {
#[derive(Clone, Debug)]
pub enum Param {
Ident(usize),
Ident(EcoString),
Formals {
// formals: Vec<(usize, Option<Ir>)>,
formals: Vec<(usize, Option<Thunk>)>,
formals: Vec<(EcoString, Option<Thunk>)>,
ellipsis: bool,
alias: Option<usize>,
alias: Option<EcoString>,
},
}
@@ -662,7 +641,7 @@ impl Downgrade for ast::Path {
let parts = self
.parts()
.map(|part| match part {
ast::InterpolPart::Literal(lit) => ctx.new_const(lit.to_string().into()).ir().ok(),
ast::InterpolPart::Literal(lit) => String { val: lit.to_string().into() }.ir().ok(),
ast::InterpolPart::Interpolation(interpol) => {
interpol.expr().unwrap().downgrade(ctx)
}
@@ -698,7 +677,7 @@ impl Downgrade for ast::Str {
.normalized_parts()
.into_iter()
.map(|part| match part {
ast::InterpolPart::Literal(lit) => ctx.new_const(lit.into()).ir().ok(),
ast::InterpolPart::Literal(lit) => String { val: lit.into() }.ir().ok(),
ast::InterpolPart::Interpolation(interpol) => {
interpol.expr().unwrap().downgrade(ctx)
}
@@ -729,11 +708,10 @@ impl ConcatStrings {
impl Downgrade for ast::Literal {
fn downgrade(self, ctx: &mut DowngradeContext) -> Result<Ir> {
match self.kind() {
ast::LiteralKind::Integer(int) => ctx.new_const(int.value().unwrap().into()),
ast::LiteralKind::Float(float) => ctx.new_const(float.value().unwrap().into()),
ast::LiteralKind::Uri(uri) => ctx.new_const(uri.to_string().into()),
ast::LiteralKind::Integer(int) => Const::from(int.value().unwrap()).ir(),
ast::LiteralKind::Float(float) => Const::from(float.value().unwrap()).ir(),
ast::LiteralKind::Uri(uri) => String { val: uri.to_string().into() }.ir()
}
.ir()
.ok()
}
}
@@ -744,9 +722,15 @@ impl Const {
}
}
impl String {
fn resolve<'a, 'env>(self, _: &mut DowngradeContext, _: &Env<'a, 'env>) -> Result<Ir> {
self.ir().ok()
}
}
impl Downgrade for ast::Ident {
fn downgrade(self, ctx: &mut DowngradeContext) -> Result<Ir> {
let sym = ctx.new_sym(self.ident_token().unwrap().text());
let sym = self.ident_token().unwrap().to_string().into();
Var { sym }.ir().ok()
}
}
@@ -754,10 +738,10 @@ impl Downgrade for ast::Ident {
impl Var {
fn resolve<'a, 'env>(self, ctx: &mut DowngradeContext, env: &Env<'a, 'env>) -> Result<Ir> {
use LookupResult::*;
match env
.lookup(self.sym)
.map_err(|_| Error::DowngradeError(format!("{} not found", ctx.get_sym(self.sym))))?
{
let Ok(res) = env.lookup(&self.sym) else {
return Err(Error::DowngradeError(format!("{} not found", Symbol::from(self.sym))))
};
match res {
Builtin(ir) => ir,
Let { level, idx } => LetVar { level, idx }.ir(),
SingleArg { level } => Arg { level }.ir(),
@@ -793,11 +777,11 @@ impl Downgrade for ast::AttrSet {
let bindings = attrs
.stcs
.into_iter()
.sorted_by_key(|(k, _)| *k)
.sorted_by(|(a, _), (b, _)| a.cmp(b))
.collect::<Vec<_>>();
let stcs = bindings
.iter()
.map(|&(sym, _)| (sym, Var { sym }.ir()))
.map(|(sym, _)| (sym.clone(), Var { sym: sym.clone() }.ir()))
.collect();
Let {
bindings,
@@ -971,7 +955,7 @@ impl Downgrade for ast::LegacyLet {
let attrs = downgrade_attrs(self, ctx)?;
Select {
expr: attrs.ir().boxed(),
attrpath: vec![Attr::Str(ctx.new_sym("body"))],
attrpath: vec![Attr::Str("body".into())],
default: None,
}
.ir()
@@ -997,7 +981,7 @@ impl Downgrade for ast::LetIn {
let ident = unwrap_ident(path.next().unwrap())?;
if path.len() > 1 {
let mut attrs = bindings
.entry(ident)
.entry(ident.clone())
.or_insert_with(|| {
Attrs {
stcs: HashMap::new(),
@@ -1010,7 +994,7 @@ impl Downgrade for ast::LetIn {
.map_err(|_| {
Error::DowngradeError(format!(
r#"attribute '{}' already defined"#,
ctx.get_sym(ident)
Symbol::from(ident)
))
})?;
while path.len() > 1 {
@@ -1037,14 +1021,14 @@ impl Downgrade for ast::LetIn {
}
}
}
let bindings = bindings.into_iter().sorted_by_key(|(k, _)| *k).collect();
let bindings = bindings.into_iter().sorted_by(|(a, _), (b, _)| a.cmp(b)).collect();
Let { bindings, expr }.ir().ok()
}
}
impl Let {
fn resolve<'a, 'env>(self, ctx: &mut DowngradeContext, env: &Env<'a, 'env>) -> Result<Ir> {
let map = self.bindings.iter().map(|(sym, _)| *sym).sorted().collect();
let map = self.bindings.iter().map(|(sym, _)| sym.clone()).sorted().collect();
let env = env.enter_let(&map);
let bindings = self
.bindings
@@ -1091,15 +1075,15 @@ impl Downgrade for ast::Lambda {
impl Func {
fn resolve<'a, 'env>(self, ctx: &mut DowngradeContext, env: &Env<'a, 'env>) -> Result<Func> {
let env = match &self.param {
Param::Ident(ident) => env.enter_single_arg(*ident),
let env = match self.param.clone() {
Param::Ident(ident) => env.enter_single_arg(ident),
Param::Formals { formals, alias, .. } => env.enter_multi_arg(
formals
.iter()
.cloned()
.map(|(ident, default)| (ident, default.map(Ir::Thunk)))
.collect(),
*alias,
alias,
),
};
let body = self.body.resolve(ctx, &env)?.boxed();
@@ -1135,143 +1119,3 @@ impl Call {
.ok()
}
}
fn downgrade_param(param: ast::Param, ctx: &mut DowngradeContext) -> Result<Param> {
match param {
ast::Param::IdentParam(ident) => Ok(Param::Ident(ctx.new_sym(ident.to_string()))),
ast::Param::Pattern(pattern) => downgrade_pattern(pattern, ctx),
}
}
fn downgrade_pattern(pattern: ast::Pattern, ctx: &mut DowngradeContext) -> Result<Param> {
let formals = pattern
.pat_entries()
.map(|entry| {
let ident = ctx.new_sym(entry.ident().unwrap().to_string());
if entry.default().is_none() {
Ok((ident, None))
} else {
entry
.default()
.unwrap()
.downgrade(ctx)
.map(|ok| (ident, Some(ctx.new_thunk(ok))))
// .map(|ok| (ident, Some(ok)))
}
})
.collect::<Result<Vec<_>>>()?;
let ellipsis = pattern.ellipsis_token().is_some();
let alias = pattern
.pat_bind()
.map(|alias| ctx.new_sym(alias.ident().unwrap().to_string()));
Ok(Param::Formals {
formals,
ellipsis,
alias,
})
}
fn downgrade_attrs(has_entry: impl ast::HasEntry, ctx: &mut DowngradeContext) -> Result<Attrs> {
let entires = has_entry.entries();
let mut attrs = Attrs {
stcs: HashMap::new(),
dyns: Vec::new(),
};
for entry in entires {
match entry {
ast::Entry::Inherit(inherit) => downgrade_inherit(inherit, &mut attrs.stcs, ctx)?,
ast::Entry::AttrpathValue(value) => downgrade_attrpathvalue(value, &mut attrs, ctx)?,
}
}
Ok(attrs)
}
fn downgrade_inherit(
inherit: ast::Inherit,
stcs: &mut HashMap<usize, Ir>,
ctx: &mut DowngradeContext,
) -> Result<()> {
let from = if let Some(from) = inherit.from() {
let from = from.expr().unwrap().downgrade(ctx)?;
Some(ctx.new_thunk(from))
} else {
None
};
for attr in inherit.attrs() {
let ident = match downgrade_attr(attr, ctx)? {
Attr::Str(ident) => ident,
_ => {
return Err(Error::DowngradeError(
"dynamic attributes not allowed in inherit".to_string(),
));
}
};
let expr = from.map_or_else(
|| Var { sym: ident }.ir().ok(),
|from| {
Ok(Select {
expr: from.ir().boxed(),
attrpath: vec![Attr::Str(ident)],
default: None,
}
.ir())
},
)?;
assert!(stcs.insert(ident, expr).is_none());
}
Ok(())
}
fn downgrade_attr(attr: ast::Attr, ctx: &mut DowngradeContext) -> Result<Attr> {
use ast::Attr::*;
use ast::InterpolPart::*;
match attr {
Ident(ident) => Ok(Attr::Str(ctx.new_sym(ident.to_string()))),
Str(string) => {
let parts = string.normalized_parts();
if parts.is_empty() {
Ok(Attr::Str(ctx.new_sym("")))
} else if parts.len() == 1 {
match parts.into_iter().next().unwrap() {
Literal(ident) => Ok(Attr::Str(ctx.new_sym(ident))),
Interpolation(interpol) => {
Ok(Attr::Dynamic(interpol.expr().unwrap().downgrade(ctx)?))
}
}
} else {
let parts = parts
.into_iter()
.map(|part| match part {
Literal(lit) => ctx.new_const(lit.into()).ir().ok(),
Interpolation(interpol) => interpol.expr().unwrap().downgrade(ctx),
})
.collect::<Result<Vec<_>>>()?;
Ok(Attr::Strs(ConcatStrings { parts }))
}
}
Dynamic(dynamic) => Ok(Attr::Dynamic(dynamic.expr().unwrap().downgrade(ctx)?)),
}
}
fn downgrade_attrpath(attrpath: ast::Attrpath, ctx: &mut DowngradeContext) -> Result<Vec<Attr>> {
attrpath
.attrs()
.map(|attr| downgrade_attr(attr, ctx))
.collect::<Result<Vec<_>>>()
}
fn downgrade_attrpathvalue(
value: ast::AttrpathValue,
attrs: &mut Attrs,
ctx: &mut DowngradeContext,
) -> Result<()> {
let path = downgrade_attrpath(value.attrpath().unwrap(), ctx)?;
let value = value.value().unwrap().downgrade(ctx)?;
let value = match value {
x @ Ir::Const(_) => x,
x => ctx.new_thunk(x).ir(),
};
attrs.insert(path, value, ctx)
}

144
src/ir/utils.rs Normal file
View File

@@ -0,0 +1,144 @@
use rnix::ast;
use super::*;
pub fn downgrade_param(param: ast::Param, ctx: &mut DowngradeContext) -> Result<Param> {
match param {
ast::Param::IdentParam(ident) => Ok(Param::Ident(ident.to_string().into())),
ast::Param::Pattern(pattern) => downgrade_pattern(pattern, ctx),
}
}
pub fn downgrade_pattern(pattern: ast::Pattern, ctx: &mut DowngradeContext) -> Result<Param> {
let formals = pattern
.pat_entries()
.map(|entry| {
let ident = entry.ident().unwrap().to_string().into();
if entry.default().is_none() {
Ok((ident, None))
} else {
entry
.default()
.unwrap()
.downgrade(ctx)
.map(|ok| (ident, Some(ctx.new_thunk(ok))))
}
})
.collect::<Result<Vec<_>>>()?;
let ellipsis = pattern.ellipsis_token().is_some();
let alias = pattern
.pat_bind()
.map(|alias| alias.ident().unwrap().to_string().into());
Ok(Param::Formals {
formals,
ellipsis,
alias,
})
}
pub fn downgrade_attrs(has_entry: impl ast::HasEntry, ctx: &mut DowngradeContext) -> Result<Attrs> {
let entires = has_entry.entries();
let mut attrs = Attrs {
stcs: HashMap::new(),
dyns: Vec::new(),
};
for entry in entires {
match entry {
ast::Entry::Inherit(inherit) => downgrade_inherit(inherit, &mut attrs.stcs, ctx)?,
ast::Entry::AttrpathValue(value) => downgrade_attrpathvalue(value, &mut attrs, ctx)?,
}
}
Ok(attrs)
}
pub fn downgrade_inherit(
inherit: ast::Inherit,
stcs: &mut HashMap<EcoString, Ir>,
ctx: &mut DowngradeContext,
) -> Result<()> {
let from = if let Some(from) = inherit.from() {
let from = from.expr().unwrap().downgrade(ctx)?;
Some(ctx.new_thunk(from))
} else {
None
};
for attr in inherit.attrs() {
let ident = match downgrade_attr(attr, ctx)? {
Attr::Str(ident) => ident,
_ => {
return Err(Error::DowngradeError(
"dynamic attributes not allowed in inherit".to_string(),
));
}
};
let expr = from.map_or_else(
|| Var { sym: ident.clone() }.ir().ok(),
|from| {
Ok(Select {
expr: from.ir().boxed(),
attrpath: vec![Attr::Str(ident.clone())],
default: None,
}
.ir())
},
)?;
// TODO: Error Handling
assert!(stcs.insert(ident, expr).is_none());
}
Ok(())
}
pub fn downgrade_attr(attr: ast::Attr, ctx: &mut DowngradeContext) -> Result<Attr> {
use ast::Attr::*;
use ast::InterpolPart::*;
match attr {
Ident(ident) => Ok(Attr::Str(ident.to_string().into())),
Str(string) => {
let parts = string.normalized_parts();
if parts.is_empty() {
Ok(Attr::Str("".into()))
} else if parts.len() == 1 {
match parts.into_iter().next().unwrap() {
Literal(ident) => Ok(Attr::Str(ident.into())),
Interpolation(interpol) => {
Ok(Attr::Dynamic(interpol.expr().unwrap().downgrade(ctx)?))
}
}
} else {
let parts = parts
.into_iter()
.map(|part| match part {
Literal(lit) => String { val: lit.into() }.ir().ok(),
Interpolation(interpol) => interpol.expr().unwrap().downgrade(ctx),
})
.collect::<Result<Vec<_>>>()?;
Ok(Attr::Strs(ConcatStrings { parts }))
}
}
Dynamic(dynamic) => Ok(Attr::Dynamic(dynamic.expr().unwrap().downgrade(ctx)?)),
}
}
pub fn downgrade_attrpath(attrpath: ast::Attrpath, ctx: &mut DowngradeContext) -> Result<Vec<Attr>> {
attrpath
.attrs()
.map(|attr| downgrade_attr(attr, ctx))
.collect::<Result<Vec<_>>>()
}
pub fn downgrade_attrpathvalue(
value: ast::AttrpathValue,
attrs: &mut Attrs,
ctx: &mut DowngradeContext,
) -> Result<()> {
let path = downgrade_attrpath(value.attrpath().unwrap(), ctx)?;
let value = value.value().unwrap().downgrade(ctx)?;
let value = match value {
x @ Ir::Const(_) => x,
x => ctx.new_thunk(x).ir(),
};
attrs.insert(path, value, ctx)
}

View File

@@ -1,468 +0,0 @@
use std::marker::PhantomData;
use std::ops::Deref;
use std::rc::Rc;
use inkwell::OptimizationLevel;
use inkwell::builder::Builder;
use inkwell::context::Context;
use inkwell::execution_engine::ExecutionEngine;
use inkwell::module::Module;
use inkwell::values::{BasicValueEnum, FunctionValue, PointerValue};
use crate::bytecode::{OpCode, UnOp};
use crate::env::VmEnv;
use crate::error::*;
use crate::stack::Stack;
use crate::ty::common::Const;
use crate::ty::internal::{Thunk, Value};
use crate::vm::VM;
mod helpers;
use helpers::Helpers;
#[cfg(test)]
mod test;
const STACK_SIZE: usize = 8 * 1024 / size_of::<Value>();
#[repr(u64)]
#[derive(Debug, Clone, Copy)]
pub enum ValueTag {
Null,
Int,
Float,
String,
Bool,
AttrSet,
List,
Function,
Thunk,
Path,
}
#[repr(C)]
#[derive(Clone, Copy)]
pub struct JITValue {
tag: ValueTag,
data: JITValueData,
}
#[repr(C)]
#[derive(Clone, Copy)]
pub union JITValueData {
int: i64,
float: f64,
bool: bool,
ptr: *const (),
}
impl<'gc> From<JITValue> for Value<'gc> {
fn from(value: JITValue) -> Self {
use ValueTag::*;
match value.tag {
Int => Value::Int(unsafe { value.data.int }),
Null => Value::Null,
Function => Value::Func(unsafe { Rc::from_raw(value.data.ptr as *const _) }),
Thunk => Value::Thunk(self::Thunk {
thunk: unsafe { Rc::from_raw(value.data.ptr as *const _) },
}),
_ => todo!("not implemented for {:?}", value.tag),
}
}
}
impl From<Value<'_>> for JITValue {
fn from(value: Value) -> Self {
match value {
Value::Int(int) => JITValue {
tag: ValueTag::Int,
data: JITValueData { int },
},
Value::Func(func) => JITValue {
tag: ValueTag::Function,
data: JITValueData {
ptr: Rc::into_raw(func) as *const _,
},
},
Value::Thunk(thunk) => JITValue {
tag: ValueTag::Thunk,
data: JITValueData {
ptr: Rc::into_raw(thunk.thunk) as *const _,
},
},
_ => todo!(),
}
}
}
pub struct JITFunc<'gc>(F<'gc>, PhantomData<&'gc mut ()>);
type F<'gc> = unsafe extern "C" fn(*const VmEnv<'gc>) -> JITValue;
impl<'gc> From<F<'gc>> for JITFunc<'gc> {
fn from(value: F<'gc>) -> Self {
Self(value, PhantomData)
}
}
impl<'gc> Deref for JITFunc<'gc> {
type Target = F<'gc>;
fn deref(&self) -> &Self::Target {
&self.0
}
}
pub struct JITContext<'gc> {
context: &'gc Context,
module: Module<'gc>,
builder: Builder<'gc>,
execution_engine: ExecutionEngine<'gc>,
helpers: Helpers<'gc>,
}
impl<'gc> JITContext<'gc> {
pub fn new(context: &'gc Context) -> Self {
// force linker to link JIT engine
unsafe {
inkwell::llvm_sys::execution_engine::LLVMLinkInMCJIT();
}
let module = context.create_module("nixjit");
let execution_engine = module
.create_jit_execution_engine(OptimizationLevel::Aggressive)
.unwrap();
let helpers = Helpers::new(context, &module, &execution_engine);
JITContext {
execution_engine,
builder: context.create_builder(),
context,
module,
helpers,
}
}
pub fn new_ptr<T>(&self, ptr: *const T) -> PointerValue<'gc> {
self.builder
.build_int_to_ptr(
self.helpers.int_type.const_int(ptr as _, false),
self.helpers.ptr_type,
"ptrconv",
)
.unwrap()
}
pub fn compile_seq(
&self,
mut opcodes: impl ExactSizeIterator<Item = OpCode>,
vm: &VM<'gc>,
) -> Result<JITFunc<'gc>> {
let mut stack = Stack::<_, STACK_SIZE>::new();
let func_ = self
.module
.add_function("nixjit_function", self.helpers.func_type, None);
let env = func_.get_nth_param(0).unwrap().into_pointer_value();
let entry = self.context.append_basic_block(func_, "entry");
self.builder.position_at_end(entry);
let len = opcodes.len();
self.build_expr(&mut opcodes, vm, env, &mut stack, func_, len)?;
assert_eq!(stack.len(), 1);
let value = stack.pop();
let exit = self.context.append_basic_block(func_, "exit");
self.builder.build_unconditional_branch(exit)?;
self.builder.position_at_end(exit);
self.builder.build_return(Some(&value))?;
if func_.verify(true) {
unsafe {
let name = func_.get_name().to_str().unwrap();
let func = self.execution_engine.get_function(name).unwrap();
Ok(JITFunc(func.as_raw(), PhantomData))
}
} else {
todo!()
}
}
fn build_expr<const CAP: usize>(
&self,
iter: &mut impl Iterator<Item = OpCode>,
vm: &VM<'gc>,
env: PointerValue<'gc>,
stack: &mut Stack<BasicValueEnum<'gc>, CAP>,
func: FunctionValue<'gc>,
mut length: usize,
) -> Result<usize> {
while length > 1 {
let opcode = iter.next().unwrap();
let br = self.single_op(opcode, vm, env, stack)?;
length -= 1;
if br > 0 {
let consq = self.context.append_basic_block(func, "consq");
let alter = self.context.append_basic_block(func, "alter");
let cont = self.context.append_basic_block(func, "cont");
let cond = self
.builder
.build_alloca(self.helpers.value_type, "cond_alloca")?;
let result = self
.builder
.build_alloca(self.helpers.value_type, "result_alloca")?;
self.builder.build_store(cond, stack.pop())?;
self.builder.build_conditional_branch(
self.builder
.build_load(
self.context.bool_type(),
self.builder.build_struct_gep(
self.helpers.value_type,
cond,
1,
"gep_cond",
)?,
"load_cond",
)?
.into_int_value(),
consq,
alter,
)?;
length -= br;
self.builder.position_at_end(consq);
let br = self.build_expr(iter, vm, env, stack, func, br)?;
self.builder.build_store(result, stack.pop())?;
self.builder.build_unconditional_branch(cont)?;
length -= br;
self.builder.position_at_end(alter);
self.build_expr(iter, vm, env, stack, func, br)?;
self.builder.build_store(result, stack.pop())?;
self.builder.build_unconditional_branch(cont)?;
self.builder.position_at_end(cont);
stack.push(self.builder.build_load(
self.helpers.value_type,
result,
"load_result",
)?)?;
}
}
if length > 0 {
self.single_op(iter.next().unwrap(), vm, env, stack)
} else {
Ok(0)
}
}
#[inline(always)]
fn single_op<const CAP: usize>(
&self,
opcode: OpCode,
vm: &VM<'_>,
env: PointerValue<'gc>,
stack: &mut Stack<BasicValueEnum<'gc>, CAP>,
) -> Result<usize> {
match opcode {
OpCode::Const { idx } => {
use Const::*;
match vm.get_const(idx) {
Int(int) => stack.push(self.helpers.new_int(int))?,
Float(float) => stack.push(self.helpers.new_float(float))?,
Bool(bool) => stack.push(self.helpers.new_bool(bool))?,
String(string) => stack.push(self.helpers.const_string(string.as_ptr()))?,
Null => stack.push(self.helpers.new_null())?,
}
}
OpCode::LoadThunk { idx } => stack.push(
self.builder
.build_direct_call(
self.helpers.new_thunk,
&[self.new_ptr(vm.get_thunk(idx) as *const _).into()],
"call_capture_env",
)?
.try_as_basic_value()
.unwrap_left(),
)?,
OpCode::ForceValue => {
let thunk = stack.pop();
let _ = stack.push(
self.builder
.build_direct_call(
self.helpers.force,
&[
thunk.into(),
self.new_ptr(vm as *const _).into(),
self.new_ptr(self as *const _).into(),
],
"call_force",
)?
.try_as_basic_value()
.left()
.unwrap(),
);
}
OpCode::CaptureEnv => {
let thunk = *stack.tos();
self.builder.build_direct_call(
self.helpers.capture_env,
&[thunk.into(), env.into()],
"call_capture_env",
)?;
}
OpCode::UnOp { op } => {
use UnOp::*;
let rhs = stack.pop();
stack.push(match op {
Neg => self
.builder
.build_direct_call(self.helpers.neg, &[rhs.into(), env.into()], "call_neg")?
.try_as_basic_value()
.left()
.unwrap(),
Not => self
.builder
.build_direct_call(self.helpers.not, &[rhs.into(), env.into()], "call_neg")?
.try_as_basic_value()
.left()
.unwrap(),
})?
}
OpCode::BinOp { op } => {
use crate::bytecode::BinOp;
let rhs = stack.pop();
let lhs = stack.pop();
match op {
BinOp::Add => {
let result = self
.builder
.build_direct_call(
self.helpers.add,
&[lhs.into(), rhs.into()],
"call_add",
)?
.try_as_basic_value()
.left()
.unwrap();
stack.push(result)?;
}
BinOp::Sub => {
let result = self
.builder
.build_direct_call(
self.helpers.sub,
&[lhs.into(), rhs.into()],
"call_add",
)?
.try_as_basic_value()
.left()
.unwrap();
stack.push(result)?;
}
BinOp::Eq => {
let result = self
.builder
.build_direct_call(
self.helpers.eq,
&[lhs.into(), rhs.into()],
"call_eq",
)?
.try_as_basic_value()
.left()
.unwrap();
stack.push(result)?;
}
BinOp::Or => {
let result = self
.builder
.build_direct_call(
self.helpers.or,
&[lhs.into(), rhs.into()],
"call_or",
)?
.try_as_basic_value()
.left()
.unwrap();
stack.push(result)?;
}
_ => 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(
self.helpers.lookup,
&[
self.helpers
.ptr_int_type
.const_int(sym as u64, false)
.into(),
env.into(),
],
"call_lookup",
)
.unwrap()
.try_as_basic_value()
.left()
.unwrap(),
)?,
OpCode::Call => {
let arg = stack.pop();
let func = stack.pop();
let ret = self
.builder
.build_direct_call(
self.helpers.call,
&[func.into(), arg.into(), self.new_ptr(vm).into()],
"call",
)?
.try_as_basic_value()
.left()
.unwrap();
stack.push(ret)?;
}
OpCode::JmpIfFalse { step } => return Ok(step),
OpCode::Jmp { step } => return Ok(step),
_ => todo!("{opcode:?} not implemented"),
}
Ok(0)
}
}

View File

@@ -1,18 +1,13 @@
#![cfg_attr(test, feature(test))]
#![allow(dead_code)]
#![feature(iter_collect_into)]
#![feature(arbitrary_self_types)]
mod builtins;
mod bytecode;
mod env;
mod stack;
mod ty;
pub mod compile;
pub mod error;
pub mod ir;
pub mod jit;
pub mod vm;
pub mod eval;
pub mod engine;
pub use ty::public::Value;

View File

@@ -6,12 +6,12 @@ use ecow::EcoString;
#[derive(Clone, Debug, PartialEq, Constructor, Hash)]
pub struct Catchable {
msg: String,
msg: EcoString,
}
impl From<String> for Catchable {
fn from(value: String) -> Self {
Catchable { msg: value }
impl<T: Into<EcoString>> From<T> for Catchable {
fn from(value: T) -> Self {
Catchable { msg: value.into() }
}
}
@@ -26,7 +26,6 @@ pub enum Const {
Bool(bool),
Int(i64),
Float(f64),
String(EcoString),
Null,
}
@@ -38,7 +37,6 @@ impl Hash for Const {
Int(x) => x.hash(state),
Float(x) => x.to_bits().hash(state),
Bool(x) => x.hash(state),
String(x) => x.hash(state),
Null => (),
}
}
@@ -51,7 +49,6 @@ impl Display for Const {
Int(x) => write!(f, "{x}"),
Float(x) => write!(f, "{x}"),
Bool(x) => write!(f, "{x}"),
String(x) => write!(f, "{x:?}"),
Null => write!(f, "null"),
}
}
@@ -75,24 +72,6 @@ impl From<f64> for Const {
}
}
impl From<EcoString> for Const {
fn from(value: EcoString) -> Self {
Const::String(value)
}
}
impl From<String> for Const {
fn from(value: String) -> Self {
Const::String(value.into())
}
}
impl From<&str> for Const {
fn from(value: &str) -> Self {
Const::String(value.into())
}
}
impl PartialEq for Const {
fn eq(&self, other: &Self) -> bool {
use Const::*;
@@ -102,7 +81,6 @@ impl PartialEq for Const {
(Float(a), Float(b)) => a == b,
(Int(a), Float(b)) => *a as f64 == *b,
(Float(a), Int(b)) => *b as f64 == *a,
(String(a), String(b)) => a == b,
_ => false,
}
}

View File

@@ -1,11 +1,12 @@
use std::ops::Deref;
use std::rc::Rc;
use ecow::EcoString;
use derive_more::Constructor;
use hashbrown::{HashMap, HashSet};
use itertools::Itertools;
use crate::vm::VM;
use crate::engine::Engine;
use super::super::public as p;
use super::Value;
@@ -13,17 +14,17 @@ use super::Value;
#[repr(transparent)]
#[derive(Constructor, Clone, PartialEq)]
pub struct AttrSet<'gc> {
data: HashMap<usize, Value<'gc>>,
data: HashMap<EcoString, Value<'gc>>,
}
impl<'gc> From<HashMap<usize, Value<'gc>>> for AttrSet<'gc> {
fn from(data: HashMap<usize, Value<'gc>>) -> Self {
impl<'gc> From<HashMap<EcoString, Value<'gc>>> for AttrSet<'gc> {
fn from(data: HashMap<EcoString, Value<'gc>>) -> Self {
Self { data }
}
}
impl<'gc> Deref for AttrSet<'gc> {
type Target = HashMap<usize, Value<'gc>>;
type Target = HashMap<EcoString, Value<'gc>>;
fn deref(&self) -> &Self::Target {
&self.data
}
@@ -36,57 +37,57 @@ impl<'gc> AttrSet<'gc> {
}
}
pub fn push_attr_force(&mut self, sym: usize, val: Value<'gc>) {
pub fn push_attr_force(&mut self, sym: EcoString, val: Value<'gc>) {
self.data.insert(sym, val);
}
pub fn push_attr(&mut self, sym: usize, val: Value<'gc>) {
pub fn push_attr(&mut self, sym: EcoString, val: Value<'gc>) {
if self.data.get(&sym).is_some() {
todo!()
}
self.data.insert(sym, val);
}
pub fn select(&self, sym: usize) -> Option<Value<'gc>> {
self.data.get(&sym).cloned()
pub fn select(&self, sym: &EcoString) -> Option<Value<'gc>> {
self.data.get(sym).cloned()
}
pub fn has_attr(&self, sym: usize) -> bool {
self.data.get(&sym).is_some()
pub fn has_attr(&self, sym: &EcoString) -> bool {
self.data.get(sym).is_some()
}
pub fn update(&mut self, other: &AttrSet<'gc>) {
for (k, v) in other.data.iter() {
self.push_attr_force(*k, v.clone())
self.push_attr_force(k.clone(), v.clone())
}
}
pub fn as_inner(&self) -> &HashMap<usize, Value<'gc>> {
pub fn as_inner(&self) -> &HashMap<EcoString, Value<'gc>> {
&self.data
}
pub fn into_inner(self: Rc<Self>) -> Rc<HashMap<usize, Value<'gc>>> {
pub fn into_inner(self: Rc<Self>) -> Rc<HashMap<EcoString, Value<'gc>>> {
unsafe { std::mem::transmute(self) }
}
pub fn from_inner(data: HashMap<usize, Value<'gc>>) -> Self {
pub fn from_inner(data: HashMap<EcoString, Value<'gc>>) -> Self {
Self { data }
}
pub fn eq_impl(&self, other: &AttrSet<'gc>) -> bool {
self.data.iter().len() == other.data.iter().len()
&& std::iter::zip(
self.data.iter().sorted_by_key(|(k, _)| **k),
self.data.iter().sorted_by_key(|(k, _)| **k),
self.data.iter().sorted_by(|(a, _), (b, _)| a.cmp(b)),
other.data.iter().sorted_by(|(a, _), (b, _)| a.cmp(b)),
)
.all(|((_, v1), (_, v2))| v1.eq_impl(v2))
}
pub fn to_public(&self, vm: &VM<'gc>, seen: &mut HashSet<Value<'gc>>) -> p::Value {
pub fn to_public(&self, engine: &'gc Engine, seen: &mut HashSet<Value<'gc>>) -> p::Value {
p::Value::AttrSet(p::AttrSet::new(
self.data
.iter()
.map(|(&sym, value)| (vm.get_sym(sym), value.to_public(vm, seen)))
.map(|(sym, value)| (sym.as_str().into(), value.to_public(engine, seen)))
.collect(),
))
}

View File

@@ -1,67 +1,20 @@
use std::cell::{Cell, OnceCell};
use std::rc::Rc;
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::Value;
use crate::vm::VM;
#[derive(Debug, Clone)]
pub enum Param {
Ident(usize),
Formals {
formals: Vec<(usize, Option<usize>)>,
ellipsis: bool,
alias: Option<usize>,
},
}
impl From<ir::Param> for Param {
fn from(value: ir::Param) -> Self {
match value {
ir::Param::Ident(ident) => Param::Ident(ident),
ir::Param::Formals {
formals,
ellipsis,
alias,
} => Param::Formals {
formals: formals
.into_iter()
.map(|(sym, default)| (sym, default.map(|default| default.idx)))
.collect(),
ellipsis,
alias,
},
}
}
}
pub struct Func<'gc> {
pub func: &'gc BFunc,
pub func: &'gc ir::Func,
pub env: Rc<VmEnv<'gc>>,
pub compiled: OnceCell<JITFunc<'gc>>,
pub count: Cell<usize>,
}
impl<'gc> Func<'gc> {
pub fn new(func: &'gc BFunc, env: Rc<VmEnv<'gc>>) -> Self {
pub fn new(func: &'gc ir::Func, env: Rc<VmEnv<'gc>>) -> Self {
Self {
func,
env,
compiled: OnceCell::new(),
count: Cell::new(0),
}
}
pub fn call_compile(&self, arg: Value<'gc>, vm: &'gc VM<'gc>) -> Result<Value<'gc>> {
let env = self.env.clone().enter_arg(arg);
let compiled = self.compiled.get_or_init(|| vm.compile_func(self.func));
let ret = unsafe { compiled(env.as_ref() as *const VmEnv) };
Ok(ret.into())
}
}
impl PartialEq for Func<'_> {

View File

@@ -4,7 +4,7 @@ use hashbrown::HashSet;
use crate::env::VmEnv;
use crate::ty::public as p;
use crate::vm::VM;
use crate::engine::Engine;
use super::Value;
@@ -52,11 +52,11 @@ impl<'gc> List<'gc> {
self.data
}
pub fn to_public(&self, vm: &VM<'gc>, seen: &mut HashSet<Value<'gc>>) -> p::Value {
pub fn to_public(&self, engine: &'gc Engine, seen: &mut HashSet<Value<'gc>>) -> p::Value {
p::Value::List(p::List::new(
self.data
.iter()
.map(|value| value.clone().to_public(vm, seen))
.map(|value| value.clone().to_public(engine, seen))
.collect(),
))
}

View File

@@ -3,16 +3,16 @@ use std::hash::Hash;
use std::rc::{Rc, Weak};
use derive_more::{IsVariant, Unwrap};
use ecow::EcoString;
use hashbrown::HashSet;
use replace_with::replace_with_or_abort;
use super::common::*;
use super::public as p;
use super::public::{self as p, Symbol};
use crate::bytecode::OpCodes;
use crate::env::VmEnv;
use crate::error::*;
use crate::vm::VM;
use crate::engine::Engine;
mod attrset;
mod func;
@@ -30,12 +30,12 @@ pub enum Value<'gc> {
Int(i64),
Float(f64),
Bool(bool),
String(Rc<String>),
String(EcoString),
Null,
Thunk(Thunk<'gc>),
AttrSet(Rc<AttrSet<'gc>>),
List(Rc<List<'gc>>),
Catchable(Rc<String>),
Catchable(EcoString),
PrimOp(Rc<PrimOp>),
PartialPrimOp(Rc<PartialPrimOp<'gc>>),
Func(Rc<Func<'gc>>),
@@ -151,7 +151,7 @@ impl<'gc> Value<'gc> {
}
}
pub fn call(&mut self, arg: Self, vm: &VM) -> Result<()> {
pub fn call(&mut self, arg: Self, vm: &Engine) -> Result<()> {
use Value::*;
if matches!(arg, Value::Catchable(_)) {
*self = arg;
@@ -239,7 +239,7 @@ impl<'gc> Value<'gc> {
(Float(a), Int(b)) => Float(a + b as f64),
(Float(a), Float(b)) => Float(a + b),
(String(mut a), String(b)) => {
Rc::make_mut(&mut a).push_str(&b);
a.push_str(&b);
String(a)
}
(a @ Value::Catchable(_), _) => a,
@@ -282,7 +282,7 @@ impl<'gc> Value<'gc> {
pub fn concat_string(&mut self, mut other: Self) -> &mut Self {
match (self.coerce_to_string(), other.coerce_to_string()) {
(Value::String(a), Value::String(b)) => {
Rc::make_mut(a).push_str(b.as_str());
a.push_str(b.as_str());
}
(_, Value::Catchable(_)) => *self = other,
(Value::Catchable(_), _) => (),
@@ -317,7 +317,7 @@ impl<'gc> Value<'gc> {
}
}
pub fn push_attr(&mut self, sym: usize, val: Self) -> &mut Self {
pub fn push_attr(&mut self, sym: EcoString, val: Self) -> &mut Self {
if let Value::AttrSet(attrs) = self {
Rc::make_mut(attrs).push_attr(sym, val);
} else if let Value::Catchable(_) = self {
@@ -343,11 +343,11 @@ impl<'gc> Value<'gc> {
}
}
pub fn select(&mut self, sym: usize, vm: &VM<'gc>) -> Result<&mut Self> {
pub fn select(&mut self, sym: &EcoString) -> Result<&mut Self> {
let val = match self {
Value::AttrSet(attrs) => attrs
.select(sym)
.ok_or_else(|| Error::EvalError(format!("{} not found", vm.get_sym(sym)))),
.ok_or_else(|| Error::EvalError(format!("{} not found", Symbol::from(sym.as_str())))),
Value::Catchable(_) => return Ok(self),
_ => Err(Error::EvalError(format!(
"cannot select from {:?}",
@@ -358,7 +358,7 @@ impl<'gc> Value<'gc> {
Ok(self)
}
pub fn select_with_default(&mut self, sym: usize, default: Self) -> Result<&mut Self> {
pub fn select_with_default(&mut self, sym: &EcoString, default: Self) -> Result<&mut Self> {
let val = match self {
Value::AttrSet(attrs) => attrs.select(sym).unwrap_or(default),
Value::Catchable(_) => return Ok(self),
@@ -373,7 +373,7 @@ impl<'gc> Value<'gc> {
Ok(self)
}
pub fn has_attr(&mut self, sym: usize) -> &mut Self {
pub fn has_attr(&mut self, sym: &EcoString) -> &mut Self {
if let Value::AttrSet(attrs) = self {
let val = Value::Bool(attrs.has_attr(sym));
*self = val;
@@ -393,7 +393,7 @@ impl<'gc> Value<'gc> {
self
}
pub fn to_public(&self, vm: &VM<'gc>, seen: &mut HashSet<Value<'gc>>) -> p::Value {
pub fn to_public(&self, vm: &'gc Engine, seen: &mut HashSet<Value<'gc>>) -> p::Value {
use self::Value::*;
use p::Value;
if seen.contains(self) {
@@ -408,11 +408,11 @@ impl<'gc> Value<'gc> {
seen.insert(self.clone());
list.to_public(vm, seen)
}
Catchable(catchable) => Value::Catchable(catchable.as_ref().clone().into()),
Catchable(catchable) => Value::Catchable(catchable.clone().into()),
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_str().into())),
String(x) => Value::String(x.clone()),
Null => Value::Const(Const::Null),
Thunk(_) => Value::Thunk,
PrimOp(primop) => Value::PrimOp(primop.name),
@@ -428,6 +428,9 @@ pub struct Thunk<'gc> {
pub thunk: Rc<RefCell<_Thunk<'gc>>>,
}
// TODO: impl
type OpCodes = ();
#[derive(IsVariant, Unwrap)]
pub enum _Thunk<'gc> {
Code(&'gc OpCodes, Option<Env<'gc>>),

View File

@@ -3,7 +3,7 @@ use std::rc::Rc;
use derive_more::Constructor;
use crate::error::Result;
use crate::vm::VM;
use crate::engine::Engine;
use super::Value;
@@ -11,7 +11,7 @@ use super::Value;
pub struct PrimOp {
pub name: &'static str,
arity: usize,
func: for<'gc> fn(Vec<Value<'gc>>, &VM) -> Result<Value<'gc>>,
func: for<'gc> fn(Vec<Value<'gc>>, &Engine) -> Result<Value<'gc>>,
}
impl PartialEq for PrimOp {
@@ -21,7 +21,7 @@ impl PartialEq for PrimOp {
}
impl PrimOp {
pub fn call<'gc>(&self, arg: Value<'gc>, vm: &VM) -> Result<Value<'gc>> {
pub fn call<'gc>(&self, arg: Value<'gc>, ctx: &Engine) -> Result<Value<'gc>> {
let mut args = Vec::with_capacity(self.arity);
args.push(arg);
if self.arity > 1 {
@@ -33,7 +33,7 @@ impl PrimOp {
}))
.ok()
} else {
(self.func)(args, vm)
(self.func)(args, ctx)
}
}
}
@@ -43,7 +43,7 @@ pub struct PartialPrimOp<'gc> {
pub name: &'static str,
arity: usize,
args: Vec<Value<'gc>>,
func: fn(Vec<Value<'gc>>, &VM) -> Result<Value<'gc>>,
func: fn(Vec<Value<'gc>>, &Engine) -> Result<Value<'gc>>,
}
impl PartialEq for PartialPrimOp<'_> {
@@ -53,14 +53,14 @@ impl PartialEq for PartialPrimOp<'_> {
}
impl<'gc> PartialPrimOp<'gc> {
pub fn call(self: &mut Rc<Self>, arg: Value<'gc>, vm: &VM) -> Result<Value<'gc>> {
pub fn call(self: &mut Rc<Self>, arg: Value<'gc>, ctx: &Engine) -> Result<Value<'gc>> {
let func = self.func;
let Some(ret) = ({
let self_mut = Rc::make_mut(self);
self_mut.args.push(arg);
self_mut.arity -= 1;
if self_mut.arity == 0 {
Some(func(std::mem::take(&mut self_mut.args), vm))
Some(func(std::mem::take(&mut self_mut.args), ctx))
} else {
None
}

View File

@@ -108,6 +108,7 @@ impl Display for List {
#[derive(IsVariant, Unwrap, Clone, Debug, PartialEq)]
pub enum Value {
Const(Const),
String(EcoString),
AttrSet(AttrSet),
List(List),
Catchable(Catchable),
@@ -123,6 +124,7 @@ impl Display for Value {
use Value::*;
match self {
Const(x) => write!(f, "{x}"),
String(x) => write!(f, "{x}"),
AttrSet(x) => write!(f, "{x}"),
List(x) => write!(f, "{x}"),
Catchable(x) => write!(f, "{x}"),