feat(ir): use petgraph

This commit is contained in:
2025-07-22 09:58:31 +08:00
parent e06bcf3f9d
commit 78e3c5a26e
19 changed files with 329 additions and 521 deletions

19
Cargo.lock generated
View File

@@ -342,6 +342,12 @@ dependencies = [
"windows-sys 0.59.0", "windows-sys 0.59.0",
] ]
[[package]]
name = "fixedbitset"
version = "0.5.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1d674e81391d1e1ab681a28d99df07927c6d4aa5b027d7da16ba32d1d21ecd99"
[[package]] [[package]]
name = "foldhash" name = "foldhash"
version = "0.1.5" version = "0.1.5"
@@ -495,6 +501,7 @@ dependencies = [
"hashbrown 0.15.3", "hashbrown 0.15.3",
"itertools", "itertools",
"lru", "lru",
"petgraph",
"priority-queue", "priority-queue",
"regex", "regex",
"replace_with", "replace_with",
@@ -503,6 +510,18 @@ dependencies = [
"thiserror", "thiserror",
] ]
[[package]]
name = "petgraph"
version = "0.8.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "54acf3a685220b533e437e264e4d932cfbdc4cc7ec0cd232ed73c08d03b8a7ca"
dependencies = [
"fixedbitset",
"hashbrown 0.15.3",
"indexmap",
"serde",
]
[[package]] [[package]]
name = "priority-queue" name = "priority-queue"
version = "2.5.0" version = "2.5.0"

View File

@@ -10,10 +10,6 @@ repl = ["dep:rustyline"]
name = "repl" name = "repl"
required-features = ["repl"] required-features = ["repl"]
[[bin]]
name = "scc"
required-features = ["repl"]
[profile.perf] [profile.perf]
debug = 2 debug = 2
strip = false strip = false
@@ -30,6 +26,7 @@ derive_more = { version = "2.0", features = ["full"] }
ecow = "0.2" ecow = "0.2"
regex = "1.11" regex = "1.11"
hashbrown = "0.15" hashbrown = "0.15"
petgraph = "0.8"
priority-queue = "2.5" priority-queue = "2.5"
lru = "0.14" lru = "0.14"
replace_with = "0.1" replace_with = "0.1"

View File

@@ -14,7 +14,7 @@
{ {
default = pkgs.mkShell { default = pkgs.mkShell {
packages = with pkgs; [ packages = with pkgs; [
(fenix.packages.${system}.complete.withComponents [ /* (fenix.packages.${system}.complete.withComponents [
"cargo" "cargo"
"clippy" "clippy"
"rust-src" "rust-src"
@@ -22,7 +22,7 @@
"rustfmt" "rustfmt"
"rust-analyzer" "rust-analyzer"
"miri" "miri"
]) ]) */
gdb gdb
valgrind valgrind
gemini-cli gemini-cli

View File

@@ -40,6 +40,7 @@ fn main() -> Result<()> {
} }
let expr = root.tree().expr().unwrap(); let expr = root.tree().expr().unwrap();
let downgraded = unwrap!(downgrade(expr)); let downgraded = unwrap!(downgrade(expr));
println!("{downgraded:?}");
println!("{}", unwrap!(eval(downgraded))); println!("{}", unwrap!(eval(downgraded)));
} }
Err(ReadlineError::Interrupted) => { Err(ReadlineError::Interrupted) => {

View File

@@ -1,59 +0,0 @@
use itertools::Itertools;
use rustyline::error::ReadlineError;
use rustyline::{DefaultEditor, Result};
use nixjit::error::Error;
use nixjit::ir::downgrade;
macro_rules! unwrap {
($e:expr) => {
match $e {
Ok(ok) => ok,
Err(err) => {
println!("{err}");
continue;
}
}
};
}
fn main() -> Result<()> {
let mut rl = DefaultEditor::new()?;
loop {
let readline = rl.readline("nixjit-scc-analyzer> ");
match readline {
Ok(expr) => {
if expr.trim().is_empty() {
continue;
}
rl.add_history_entry(expr.as_str())?;
let root = rnix::Root::parse(&expr);
if !root.errors().is_empty() {
println!(
"{}",
Error::ParseError(
root.errors().iter().map(|err| err.to_string()).join(";")
)
);
continue;
}
let expr = root.tree().expr().unwrap();
let downgraded = unwrap!(downgrade(expr));
println!("{:?}", downgraded);
}
Err(ReadlineError::Interrupted) => {
println!("CTRL-C");
break;
}
Err(ReadlineError::Eof) => {
println!("CTRL-D");
break;
}
Err(err) => {
println!("Error: {err:?}");
break;
}
}
}
Ok(())
}

View File

@@ -3,13 +3,14 @@ use std::cell::OnceCell;
use std::rc::Rc; use std::rc::Rc;
use hashbrown::{HashMap, HashSet}; use hashbrown::{HashMap, HashSet};
use itertools::Itertools;
use priority_queue::PriorityQueue; use priority_queue::PriorityQueue;
use crate::env::Env; use crate::env::Env;
use crate::error::Result; use crate::error::Result;
use crate::eval::Evaluate; use crate::eval::Evaluate;
use crate::eval::jit::{JITCompiler, JITFunc}; use crate::eval::jit::{JITCompiler, JITFunc};
use crate::ir::{Dep, Downgraded, Ir, SccNode}; use crate::ir::{Downgraded, Ir};
use crate::ty::internal as i; use crate::ty::internal as i;
use crate::ty::public::Value; use crate::ty::public::Value;
@@ -22,7 +23,8 @@ type EnvIdx = usize;
pub struct Engine { pub struct Engine {
pub thunks: Box<[Ir]>, pub thunks: Box<[Ir]>,
pub funcs: Box<[Ir]>, pub funcs: Box<[Ir]>,
pub func_deps: Vec<HashMap<Dep, usize>>, pub func_deps: Vec<HashMap<usize, usize>>,
pub func_arg_deps: Vec<Vec<usize>>,
jit: JITCompiler, jit: JITCompiler,
compiled: Box<[OnceCell<JITFunc>]>, compiled: Box<[OnceCell<JITFunc>]>,
tasks: PriorityQueue<CompileTask, usize>, tasks: PriorityQueue<CompileTask, usize>,
@@ -33,6 +35,7 @@ pub fn eval(downgraded: Downgraded) -> Result<Value> {
downgraded.thunks, downgraded.thunks,
downgraded.funcs, downgraded.funcs,
downgraded.func_deps, downgraded.func_deps,
downgraded.func_arg_deps,
JITCompiler::new(), JITCompiler::new(),
); );
engine.eval(downgraded.graph) engine.eval(downgraded.graph)
@@ -42,7 +45,8 @@ impl Engine {
pub fn new( pub fn new(
thunks: Box<[Ir]>, thunks: Box<[Ir]>,
funcs: Box<[Ir]>, funcs: Box<[Ir]>,
func_deps: Vec<HashMap<Dep, usize>>, func_deps: Vec<HashMap<usize, usize>>,
func_arg_deps: Vec<Vec<usize>>,
jit: JITCompiler, jit: JITCompiler,
) -> Self { ) -> Self {
Self { Self {
@@ -53,34 +57,29 @@ impl Engine {
thunks, thunks,
funcs, funcs,
func_deps, func_deps,
func_arg_deps,
jit, jit,
} }
} }
pub fn eval(&mut self, graph: Vec<SccNode>) -> Result<Value> { pub fn eval(&mut self, graph: Vec<Vec<usize>>) -> Result<Value> {
let mut env = Env::new(); let mut env = Env::new();
let last = graph.last().unwrap().members[0];
for SccNode { members, .. } in graph.into_iter() {
if members.len() == 1 {
for member in members.into_iter() {
let engine = unsafe { &mut *(self as *mut Self) }; let engine = unsafe { &mut *(self as *mut Self) };
let mut val = self.thunks[member].eval(engine, &mut env)?; env.new_frame(0);
val.force(engine, &mut env)?; env.new_frame(0);
env.insert_cache(member, val); for members in graph.into_iter() {
} if members.len() == 1 {
let val = self.thunks[members[0]].eval(engine, &mut env)?;
env.push_stack(val);
} else { } else {
todo!(); todo!();
for member in members.into_iter() { for member in members.into_iter() {
let val = self.thunks[member].clone().eval(self, &mut env)?; let val = self.thunks[member].eval(engine, &mut env)?;
env.insert_cache(member, val); env.push_stack(val);
} }
} }
} }
env.lookup_cache(last, |_| unreachable!()).map(|mut val| { Ok(env.pop_frame().last_mut().unwrap().force(self, &mut env)?.to_public(self, &mut HashSet::new()))
Ok(val
.force(self, &mut env)?
.to_public(self, &mut HashSet::new()))
})?
} }
pub fn eval_thunk(&mut self, idx: usize, env: &mut Env) -> Result<i::Value> { pub fn eval_thunk(&mut self, idx: usize, env: &mut Env) -> Result<i::Value> {
@@ -105,27 +104,15 @@ impl Engine {
} }
pub fn eval_func_deps(&mut self, idx: usize, env: &mut Env) -> Result<()> { pub fn eval_func_deps(&mut self, idx: usize, env: &mut Env) -> Result<()> {
for (&dep, _) in unsafe { &*(&self.func_deps[idx] as *const HashMap<Dep, usize>) }.iter() {
match dep {
Dep::Arg(idx) => {
if let i::Value::Thunk(idx) = env.lookup_arg(idx) {
env.insert_cache_lazy(idx, |env| {
let engine = unsafe { &mut *(self as *mut Self) }; let engine = unsafe { &mut *(self as *mut Self) };
let mut val = self.thunks[idx].eval(engine, env)?; let engine_mut = unsafe { &mut *(self as *mut Self) };
val.force(engine, env)?; env.new_frame(self.func_deps[idx].len());
val.ok() for (&dep, _) in engine.func_deps[idx].iter().sorted_by_key(|&(_, &k)| k) {
})?; let val = self.thunks[dep].eval(engine_mut, env)?;
} env.push_stack(val);
}
Dep::Thunk(idx) => {
env.insert_cache_lazy(idx, |env| {
let engine = unsafe { &mut *(self as *mut Self) };
let mut val = self.thunks[idx].eval(engine, env)?;
val.force(engine, env)?;
val.ok()
})?;
}
} }
for &arg in engine.func_arg_deps[idx].iter() {
env.force_arg(arg);
} }
Ok(()) Ok(())
} }

View File

@@ -1,11 +1,8 @@
#![allow(unused_macros)] #![allow(unused_macros)]
extern crate test;
use std::collections::BTreeMap; use std::collections::BTreeMap;
use test::{Bencher, black_box};
use crate::ir::downgrade; use crate::ir::downgrade;
use crate::ty::common::Const; use crate::ty::common::Const;
use crate::ty::public::*; use crate::ty::public::*;
@@ -210,14 +207,11 @@ fn test_func() {
test_expr("let fix = f: let x = f x; in x; in (fix (self: { x = 1; y = self.x + 1; })).y", int!(2)); test_expr("let fix = f: let x = f x; in x; in (fix (self: { x = 1; y = self.x + 1; })).y", int!(2));
} }
#[bench] #[test]
#[ignore] #[ignore]
fn bench_fib(b: &mut Bencher) { fn test_fib() {
b.iter(|| {
test_expr( test_expr(
"let fib = n: if n == 1 || n == 2 then 1 else (fib (n - 1)) + (fib (n - 2)); in fib 30", "let fib = n: if n == 1 || n == 2 then 1 else (fib (n - 1)) + (fib (n - 2)); in fib 30",
int!(832040), int!(832040),
); );
black_box(())
})
} }

View File

@@ -3,78 +3,77 @@ use std::rc::Rc;
use hashbrown::HashMap; use hashbrown::HashMap;
use crate::error::Result;
use crate::ty::internal::Value; use crate::ty::internal::Value;
#[derive(Clone, Debug)] #[derive(Clone, Debug, Default)]
pub struct Env { pub struct Env {
stack: Vec<Value>, stack: Vec<Value>,
args: Vec<Value>,
frame_offsets: Vec<usize>,
with: Vec<Rc<HashMap<String, Value>>>, with: Vec<Rc<HashMap<String, Value>>>,
} }
impl Default for Env {
fn default() -> Self {
Self::new()
}
}
impl Env { impl Env {
pub fn new() -> Self { pub fn new() -> Self {
Self { Self::default()
stack: Vec::new(), }
with: Vec::new(),
pub fn reserve_args(&mut self, size: usize) {
self.args.reserve(size);
}
pub fn push_arg(&mut self, value: Value) {
self.args.push(value);
}
pub fn push_args(&mut self, values: impl IntoIterator<Item = Value>) {
self.args.extend(values);
}
pub fn lookup_arg(&self, offset: usize) -> &Value {
&self.args[self.args.len() - offset - 1]
}
pub fn pop_args(&mut self, len: usize) -> Vec<Value> {
self.args.split_off(self.args.len() - len)
}
pub fn drop_args(&mut self, len: usize) {
self.args.truncate(self.args.len() - len);
}
pub fn force_arg(&mut self, offset: usize) {
let env = unsafe { &mut *(self as *mut Env) };
if let thunk @ Value::Thunk(_) = &mut self.args[offset] {
let &mut Value::Thunk(offset) = thunk else { unreachable!() };
*thunk = env.lookup_stack(offset).clone()
} }
} }
pub fn with_new_cache<T>( pub fn new_frame(&mut self, size: usize) {
&mut self, self.stack.reserve(size);
len: usize, self.frame_offsets.push(self.stack.len());
f: impl FnOnce(&mut Self) -> T,
) -> (T, Vec<Value>) {
self.stack.reserve(len);
let ret = f(self);
(ret, self.stack.split_off(self.stack.len() - len))
} }
pub fn with_cache<T>( pub fn push_stack(&mut self, value: Value) {
&mut self, self.stack.push(value);
cache: Vec<Value>,
len: usize,
f: impl FnOnce(&mut Self) -> T,
) -> (T, Vec<Value>) {
self.stack.extend(cache);
let ret = f(self);
(ret, self.stack.split_off(self.stack.len() - len))
} }
/* pub fn insert_cache(&mut self, idx: usize, val: Value) { pub fn resume_stack(&mut self, iter: impl IntoIterator<Item = Value>) {
self.cache.last_mut().unwrap().insert(idx, val); self.stack.extend(iter);
} */
/* pub fn insert_cache_lazy(
&mut self,
idx: usize,
f: impl FnOnce(&mut Self) -> Result<Value>,
) -> Result<()> {
if self.cache.last().unwrap().get(&idx).is_none() {
let val = f(self)?;
self.cache.last_mut().unwrap().insert(idx, val);
}
Ok(())
} */
pub fn insert_stack(&mut self, iter: impl IntoIterator<Item = fn(&mut Self) -> Result<Value>>) -> Result<()> {
let iter = iter.into_iter();
self.stack.reserve(iter.size_hint().0);
for f in iter {
let val = f(self)?;
self.stack.push(val);
}
Ok(())
} }
pub fn lookup_stack(&mut self, idx: usize) -> Value { pub fn lookup_stack(&self, offset: usize) -> &Value {
self.stack.get(self.stack.len() - idx - 1).unwrap().clone() dbg!(self.frame_offsets[self.frame_offsets.len() - 2], offset);
&self.stack[self.frame_offsets[self.frame_offsets.len() - 2] + offset]
}
pub fn pop_frame(&mut self) -> Vec<Value> {
self.stack.split_off(self.frame_offsets.pop().unwrap())
}
pub fn drop_frame(&mut self) {
self.stack.truncate(self.frame_offsets.pop().unwrap());
} }
pub fn lookup_with(&self, symbol: &str) -> Option<Value> { pub fn lookup_with(&self, symbol: &str) -> Option<Value> {
@@ -86,11 +85,11 @@ impl Env {
None None
} }
pub fn exit_with(&mut self) {
self.with.pop();
}
pub fn enter_with(&mut self, map: Rc<HashMap<String, Value>>) { pub fn enter_with(&mut self, map: Rc<HashMap<String, Value>>) {
self.with.push(map) self.with.push(map)
} }
pub fn exit_with(&mut self) {
self.with.pop();
}
} }

View File

@@ -530,19 +530,13 @@ impl JITCompile for Var {
impl JITCompile for Arg { impl JITCompile for Arg {
fn compile(&self, ctx: &mut JITContext, engine: ir::Value, env: ir::Value) -> StackSlot { fn compile(&self, ctx: &mut JITContext, engine: ir::Value, env: ir::Value) -> StackSlot {
ctx.lookup_arg(env, self.level) ctx.lookup_arg(env, self.offset)
} }
} }
impl JITCompile for LetVar { impl JITCompile for StackVar {
fn compile(&self, ctx: &mut JITContext, engine: ir::Value, env: ir::Value) -> StackSlot { fn compile(&self, ctx: &mut JITContext, engine: ir::Value, env: ir::Value) -> StackSlot {
unreachable!() ctx.lookup_stack(env, self.offset)
}
}
impl JITCompile for Cache {
fn compile(&self, ctx: &mut JITContext, engine: ir::Value, env: ir::Value) -> StackSlot {
todo!()
} }
} }

View File

@@ -25,8 +25,12 @@ pub extern "C" fn helper_call(
.unwrap(); .unwrap();
} }
pub extern "C" fn helper_lookup_arg(env: &Env, level: usize, ret: &mut MaybeUninit<Value>) { pub extern "C" fn helper_lookup_stack(env: &Env, offset: usize, ret: &mut MaybeUninit<Value>) {
ret.write(env.lookup_arg(level)); ret.write(env.lookup_stack(offset).clone());
}
pub extern "C" fn helper_lookup_arg(env: &Env, offset: usize, ret: &mut MaybeUninit<Value>) {
ret.write(env.lookup_arg(offset).clone());
} }
pub extern "C" fn helper_lookup( pub extern "C" fn helper_lookup(

View File

@@ -250,6 +250,24 @@ impl<'comp, 'ctx> JITContext<'comp, 'ctx> {
slot slot
} }
fn lookup_stack(&mut self, env: ir::Value, idx: usize) -> StackSlot {
let slot = self.alloca();
let lookup_stack = self
.compiler
.module
.declare_func_in_func(self.compiler.lookup_stack, self.builder.func);
let idx = self
.builder
.ins()
.iconst(self.compiler.ptr_type, idx as i64);
let ptr = self
.builder
.ins()
.stack_addr(self.compiler.ptr_type, slot, 0);
self.builder.ins().call(lookup_stack, &[env, idx, ptr]);
slot
}
fn lookup_arg(&mut self, env: ir::Value, idx: usize) -> StackSlot { fn lookup_arg(&mut self, env: ir::Value, idx: usize) -> StackSlot {
let slot = self.alloca(); let slot = self.alloca();
let lookup_arg = self let lookup_arg = self
@@ -374,6 +392,7 @@ pub struct JITCompiler {
func_sig: Signature, func_sig: Signature,
call: FuncId, call: FuncId,
lookup_stack: FuncId,
lookup_arg: FuncId, lookup_arg: FuncId,
lookup: FuncId, lookup: FuncId,
select: FuncId, select: FuncId,
@@ -413,6 +432,7 @@ impl JITCompiler {
let mut builder = JITBuilder::with_isa(isa, cranelift_module::default_libcall_names()); let mut builder = JITBuilder::with_isa(isa, cranelift_module::default_libcall_names());
builder.symbol("helper_call", helper_call as _); builder.symbol("helper_call", helper_call as _);
builder.symbol("helper_lookup_stack", helper_lookup_stack as _);
builder.symbol("helper_lookup_arg", helper_lookup_arg as _); builder.symbol("helper_lookup_arg", helper_lookup_arg as _);
builder.symbol("helper_lookup", helper_lookup as _); builder.symbol("helper_lookup", helper_lookup as _);
builder.symbol("helper_select", helper_select as _); builder.symbol("helper_select", helper_select as _);
@@ -466,6 +486,18 @@ impl JITCompiler {
.declare_function("helper_call", Linkage::Import, &call_sig) .declare_function("helper_call", Linkage::Import, &call_sig)
.unwrap(); .unwrap();
let mut lookup_stack_sig = module.make_signature();
lookup_stack_sig.params.extend(
[AbiParam {
value_type: ptr_type,
purpose: ArgumentPurpose::Normal,
extension: ArgumentExtension::None,
}; 3],
);
let lookup_stack = module
.declare_function("helper_lookup_stack", Linkage::Import, &lookup_stack_sig)
.unwrap();
// fn(env: &Env, level: usize, ret: &mut MaybeUninit<Value>) // fn(env: &Env, level: usize, ret: &mut MaybeUninit<Value>)
let mut lookup_arg_sig = module.make_signature(); let mut lookup_arg_sig = module.make_signature();
lookup_arg_sig.params.extend( lookup_arg_sig.params.extend(
@@ -669,6 +701,7 @@ impl JITCompiler {
func_sig, func_sig,
call, call,
lookup_stack,
lookup_arg, lookup_arg,
lookup, lookup,
select, select,

View File

@@ -294,26 +294,20 @@ impl Evaluate for ir::Var {
} }
impl Evaluate for ir::Arg { impl Evaluate for ir::Arg {
fn eval(&self, _: &mut Engine, env: &mut Env) -> Result<Value> {
env.lookup_arg(self.level).ok()
}
}
impl Evaluate for ir::LetVar {
fn eval(&self, _: &mut Engine, env: &mut Env) -> Result<Value> {
unreachable!()
}
}
impl Evaluate for ir::Cache {
fn eval(&self, engine: &mut Engine, env: &mut Env) -> Result<Value> { fn eval(&self, engine: &mut Engine, env: &mut Env) -> Result<Value> {
todo!() env.lookup_arg(self.offset).clone().ok()
}
}
impl Evaluate for ir::StackVar {
fn eval(&self, engine: &mut Engine, env: &mut Env) -> Result<Value> {
env.lookup_stack(self.offset).clone().ok()
} }
} }
impl Evaluate for ir::Thunk { impl Evaluate for ir::Thunk {
fn eval(&self, engine: &mut Engine, env: &mut Env) -> Result<Value> { fn eval(&self, engine: &mut Engine, env: &mut Env) -> Result<Value> {
Value::Thunk(self.idx).ok() unreachable!()
} }
} }

View File

@@ -1,10 +1,10 @@
use derive_more::Unwrap; use derive_more::Unwrap;
use hashbrown::HashMap; use hashbrown::HashMap;
use crate::error::Result; use crate::{error::Result, ir::sort_dependencies};
use crate::ty::common::Const; use crate::ty::common::Const;
use super::{Dep, Func, Ir, LoadFunc, MaybeThunk, SccAnalyzer, SccNode, Thunk}; use super::{Func, Ir, LoadFunc, MaybeThunk, Thunk};
#[derive(Clone, Copy, Unwrap, Debug)] #[derive(Clone, Copy, Unwrap, Debug)]
pub enum Index { pub enum Index {
@@ -15,7 +15,8 @@ pub enum Index {
pub struct DowngradeContext { pub struct DowngradeContext {
pub thunks: Vec<(Ir, bool)>, pub thunks: Vec<(Ir, bool)>,
pub thunk_deps: Vec<HashMap<usize, usize>>, pub thunk_deps: Vec<HashMap<usize, usize>>,
pub func_deps: Vec<HashMap<Dep, usize>>, pub func_deps: Vec<HashMap<usize, usize>>,
pub func_arg_deps: Vec<Vec<usize>>,
pub funcs: Vec<Func>, pub funcs: Vec<Func>,
} }
@@ -35,7 +36,6 @@ enum EnvNode<'a> {
pub enum LookupResult { pub enum LookupResult {
Builtin(Ir), Builtin(Ir),
MaybeThunk(MaybeThunk), MaybeThunk(MaybeThunk),
Let { level: usize, idx: usize },
SingleArg { idx: usize }, SingleArg { idx: usize },
MultiArg { idx: usize, default: Option<Ir> }, MultiArg { idx: usize, default: Option<Ir> },
With, With,
@@ -147,6 +147,7 @@ impl DowngradeContext {
thunks: Vec::new(), thunks: Vec::new(),
thunk_deps: Vec::new(), thunk_deps: Vec::new(),
func_deps: Vec::new(), func_deps: Vec::new(),
func_arg_deps: Vec::new(),
funcs: Vec::new(), funcs: Vec::new(),
} }
} }
@@ -169,17 +170,29 @@ impl DowngradeContext {
} }
} }
pub fn new_dep(&mut self, this: Index, dep: Dep) -> usize { pub fn new_thunk_dep(&mut self, this: Index, thunk: usize) -> usize {
match this { match this {
Index::Thunk(idx) => { Index::Thunk(idx) => {
let len = self.thunk_deps.len(); let len = self.thunk_deps[idx].len();
*self.thunk_deps[idx] *self.thunk_deps[idx]
.entry(dep.unwrap_thunk()) .entry(thunk)
.or_insert(len) .or_insert(len)
} }
Index::Func(idx) => { Index::Func(idx) => {
let len = self.thunk_deps.len(); let len = self.func_deps[idx].len();
*self.func_deps[idx].entry(dep).or_insert(len) *self.func_deps[idx].entry(thunk).or_insert(len)
}
}
}
pub fn new_arg_dep(&mut self, this: Index, arg: usize) {
match this {
Index::Func(idx) => {
self.func_arg_deps[idx].push(arg);
}
Index::Thunk(idx) => {
return;
panic!("unexpected dependency from Thunk({idx}) to Arg({arg})")
} }
} }
} }
@@ -188,6 +201,7 @@ impl DowngradeContext {
let idx = self.funcs.len(); let idx = self.funcs.len();
self.funcs.push(func); self.funcs.push(func);
self.func_deps.push(HashMap::new()); self.func_deps.push(HashMap::new());
self.func_arg_deps.push(Vec::new());
LoadFunc { idx } LoadFunc { idx }
} }
@@ -246,15 +260,17 @@ impl DowngradeContext {
pub struct Downgraded { pub struct Downgraded {
pub thunks: Box<[Ir]>, pub thunks: Box<[Ir]>,
pub funcs: Box<[Ir]>, pub funcs: Box<[Ir]>,
pub func_deps: Vec<HashMap<Dep, usize>>, pub func_deps: Vec<HashMap<usize, usize>>,
pub graph: Vec<SccNode>, pub func_arg_deps: Vec<Vec<usize>>,
pub graph: Vec<Vec<usize>>,
} }
impl Downgraded { impl Downgraded {
pub fn new(ctx: DowngradeContext) -> Self { pub fn new(ctx: DowngradeContext) -> Self {
Self { Self {
graph: SccAnalyzer::new(&ctx).analyze(), graph: sort_dependencies(&ctx),
func_deps: ctx.func_deps, func_deps: ctx.func_deps,
func_arg_deps: ctx.func_arg_deps,
thunks: ctx.thunks.into_iter().map(|(ir, _)| ir).collect(), thunks: ctx.thunks.into_iter().map(|(ir, _)| ir).collect(),
funcs: ctx funcs: ctx
.funcs .funcs

37
src/ir/graph.rs Normal file
View File

@@ -0,0 +1,37 @@
use hashbrown::HashMap;
use petgraph::graph::{DiGraph, NodeIndex};
use petgraph::algo::{condensation, toposort};
use super::*;
struct GraphBuilder<'ctx> {
ctx: &'ctx DowngradeContext,
graph: DiGraph<usize, ()>,
nodes: HashMap<usize, NodeIndex>
}
impl GraphBuilder<'_> {
fn connect(&mut self, idx: usize) {
let node = self.graph.add_node(idx);
self.nodes.insert(idx, node);
let deps = self.ctx.thunk_deps.get(idx).unwrap();
for (&refee, _) in deps {
if !self.nodes.contains_key(&refee) {
self.connect(refee);
}
self.graph.add_edge(node, *self.nodes.get(&refee).unwrap(), ());
}
}
}
pub fn sort_dependencies(ctx: &DowngradeContext) -> Vec<Vec<usize>> {
let mut builder = GraphBuilder {
ctx,
graph: DiGraph::new(),
nodes: HashMap::new(),
};
builder.connect(ctx.thunks.len() - 1);
let mut graph = condensation(builder.graph, false);
let sorted = toposort(&graph, None).unwrap();
sorted.into_iter().map(|idx| graph.remove_node(idx).unwrap()).collect_vec()
}

View File

@@ -143,11 +143,9 @@ ir! {
Str => { val: String }, Str => { val: String },
Var => { sym: String }, Var => { sym: String },
#[derive(Copy)] #[derive(Copy)]
Arg => { level: usize }, Arg => { offset: usize },
#[derive(Copy)] #[derive(Copy)]
LetVar => { level: usize, idx: usize }, StackVar => { offset: usize },
#[derive(Copy)]
Cache => { idx: usize },
#[derive(Copy)] #[derive(Copy)]
Thunk => { idx: usize }, Thunk => { idx: usize },
Path => { expr: Box<Ir> }, Path => { expr: Box<Ir> },
@@ -364,13 +362,24 @@ impl Thunk {
ctx: &mut DowngradeContext, ctx: &mut DowngradeContext,
env: &Env<'a, 'env>, env: &Env<'a, 'env>,
) -> Result<Ir> { ) -> Result<Ir> {
let idx = ctx.new_dep(self_idx, Dep::Thunk(self.idx)); let idx = ctx.new_thunk_dep(self_idx, self.idx);
ctx.resolve_thunk(self.idx, env)?; ctx.resolve_thunk(self.idx, env)?;
Cache { idx }.ir().ok() StackVar { offset: idx }.ir().ok()
} }
} }
impl Cache { impl Arg {
fn resolve<'a, 'env>(
self,
self_idx: Index,
ctx: &mut DowngradeContext,
env: &Env<'a, 'env>,
) -> Result<Ir> {
unreachable!()
}
}
impl StackVar {
fn resolve<'a, 'env>( fn resolve<'a, 'env>(
self, self,
self_idx: Index, self_idx: Index,
@@ -705,17 +714,22 @@ impl Var {
}; };
match res { match res {
Builtin(ir) => ir, Builtin(ir) => ir,
Let { level, idx } => LetVar { level, idx }.ir(), SingleArg { idx } => {
SingleArg { idx: level } => Arg { level }.ir(), ctx.new_arg_dep(self_idx, idx);
Arg { offset: idx }.ir()
},
MultiArg { MultiArg {
idx: level, idx,
default, default,
} => Select { } => {
expr: Arg { level }.ir().boxed(), ctx.new_thunk_dep(self_idx, idx);
Select {
expr: Arg { offset: idx }.ir().boxed(),
attrpath: vec![Attr::Str(self.sym)], attrpath: vec![Attr::Str(self.sym)],
default: default.map(Box::new), default: default.map(Box::new),
} }
.ir(), .ir()
}
MaybeThunk(thunk) => thunk.resolve(self_idx, ctx, env)?, MaybeThunk(thunk) => thunk.resolve(self_idx, ctx, env)?,
With => self.ir(), With => self.ir(),
} }
@@ -723,28 +737,6 @@ impl Var {
} }
} }
impl Arg {
fn resolve<'a, 'env>(
self,
_: Index,
_: &mut DowngradeContext,
_: &Env<'a, 'env>,
) -> Result<Ir> {
self.ir().ok()
}
}
impl LetVar {
fn resolve<'a, 'env>(
self,
_: Index,
_: &mut DowngradeContext,
_: &Env<'a, 'env>,
) -> Result<Ir> {
self.ir().ok()
}
}
impl Downgrade for ast::AttrSet { impl Downgrade for ast::AttrSet {
fn downgrade(self, ctx: &mut DowngradeContext) -> Result<Ir> { fn downgrade(self, ctx: &mut DowngradeContext) -> Result<Ir> {
let rec = self.rec_token().is_some(); let rec = self.rec_token().is_some();

View File

@@ -1,203 +0,0 @@
use std::collections::VecDeque;
use hashbrown::{HashMap, HashSet};
use super::*;
#[derive(Debug, Clone)]
pub struct SccNode {
pub id: usize,
pub members: Vec<usize>,
pub deps: HashSet<usize>,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Unwrap)]
pub enum Dep {
Thunk(usize),
Arg(usize),
}
#[derive(Default, Debug)]
pub struct SccGraph {
nodes: HashMap<usize, SccNode>,
root: usize,
}
impl SccGraph {
fn new(ctx: &DowngradeContext, sccs: Vec<Vec<usize>>) -> Self {
let mut graph = SccGraph::default();
let mut thunk_to_scc = HashMap::new();
for (id, members) in sccs.iter().enumerate() {
for &thunk_id in members {
thunk_to_scc.insert(thunk_id, id);
}
graph.nodes.insert(
id,
SccNode {
id,
members: members.clone(),
deps: HashSet::new(),
},
);
}
graph.root = thunk_to_scc[&(ctx.thunks.len() - 1)];
for (from_node_id, from_deps) in ctx.thunk_deps.iter().enumerate() {
let from_scc_id = thunk_to_scc[&from_node_id];
for (&to_node_id, _) in from_deps {
let to_scc_id = thunk_to_scc[&to_node_id];
if from_scc_id != to_scc_id {
graph
.nodes
.get_mut(&from_scc_id)
.unwrap()
.deps
.insert(to_scc_id);
}
}
}
graph
}
fn sorted(self) -> Vec<SccNode> {
let mut reachable = HashSet::new();
let mut stack = vec![self.root];
reachable.insert(self.root);
while let Some(id) = stack.pop() {
if let Some(node) = self.nodes.get(&id) {
for &dep_id in &node.deps {
if reachable.insert(dep_id) {
stack.push(dep_id);
}
}
}
}
let mut in_degrees: HashMap<usize, usize> = HashMap::new();
let mut reverse_adj: HashMap<usize, Vec<usize>> = HashMap::new();
for &id in &reachable {
in_degrees.insert(id, 0);
}
for &id in &reachable {
if let Some(node) = self.nodes.get(&id) {
for &dep_id in &node.deps {
if reachable.contains(&dep_id) {
reverse_adj.entry(dep_id).or_default().push(id);
*in_degrees.get_mut(&id).unwrap() += 1;
}
}
}
}
let mut queue = in_degrees
.iter()
.filter(|&(_, &deg)| deg == 0)
.map(|(&id, _)| id)
.collect::<VecDeque<_>>();
queue.make_contiguous().sort();
let mut sorted_order = Vec::new();
while let Some(u) = queue.pop_front() {
if let Some(node) = self.nodes.get(&u) {
sorted_order.push(node.clone());
}
if let Some(dependents) = reverse_adj.get(&u) {
for &v in dependents {
if let Some(degree) = in_degrees.get_mut(&v) {
*degree -= 1;
if *degree == 0 {
queue.push_back(v);
}
}
}
}
}
if sorted_order.len() != reachable.len() {
unreachable!("Cycle detected in the reachable part of SCC graph!");
}
sorted_order
}
}
pub struct SccAnalyzer<'ctx> {
ctx: &'ctx DowngradeContext,
index: usize,
stack: Vec<usize>,
on_stack: HashSet<usize>,
indices: HashMap<usize, usize>,
low_links: HashMap<usize, usize>,
sccs: Vec<Vec<usize>>,
}
impl<'ctx> SccAnalyzer<'ctx> {
pub fn new(ctx: &'ctx DowngradeContext) -> Self {
Self {
ctx,
index: 0,
stack: Vec::new(),
on_stack: HashSet::new(),
indices: HashMap::new(),
low_links: HashMap::new(),
sccs: Vec::new(),
}
}
pub fn analyze(mut self) -> Vec<SccNode> {
for idx in 0..self.ctx.thunks.len() {
if !self.indices.contains_key(&idx) {
self.strong_connect(idx);
}
}
SccGraph::new(self.ctx, self.sccs).sorted()
}
fn strong_connect(&mut self, v_id: usize) {
self.indices.insert(v_id, self.index);
self.low_links.insert(v_id, self.index);
self.index += 1;
self.stack.push(v_id);
self.on_stack.insert(v_id);
let Some(deps) = self.ctx.thunk_deps.get(v_id) else {
unreachable!()
};
for (&w_id, _) in deps {
if !self.indices.contains_key(&w_id) {
self.strong_connect(w_id);
let v_low_link = *self.low_links.get(&v_id).unwrap();
let w_low_link = *self.low_links.get(&w_id).unwrap();
if w_low_link < v_low_link {
self.low_links.insert(v_id, w_low_link);
}
} else if self.on_stack.contains(&w_id) {
let v_low_link = *self.low_links.get(&v_id).unwrap();
let w_index = *self.indices.get(&w_id).unwrap();
if w_index < v_low_link {
self.low_links.insert(v_id, w_index);
}
}
}
if self.low_links[&v_id] == self.indices[&v_id] {
let mut scc = Vec::new();
loop {
let w_id = self.stack.pop().unwrap();
self.on_stack.remove(&w_id);
scc.push(w_id);
if w_id == v_id {
break;
}
}
self.sccs.push(scc);
}
}
}

View File

@@ -1,4 +1,3 @@
#![cfg_attr(test, feature(test))]
#![allow(dead_code)] #![allow(dead_code)]
mod builtins; mod builtins;

View File

@@ -7,5 +7,5 @@ use super::Value;
pub struct PartialFunc { pub struct PartialFunc {
pub idx: usize, pub idx: usize,
pub args: Vec<Value>, pub args: Vec<Value>,
pub cache: HashMap<usize, Value>, pub frame: Vec<Value>,
} }

View File

@@ -183,21 +183,21 @@ impl Value {
let self::PartialFunc { let self::PartialFunc {
idx, idx,
args: old_args, args: old_args,
cache, frame,
} = Rc::make_mut(func); } = Rc::make_mut(func);
let idx = *idx; let idx = *idx;
let len = args.len() + old_args.len(); let len = args.len() + old_args.len();
env.reserve_args(len); env.reserve_args(len);
env.enter_args(std::mem::take(old_args)); env.push_args(core::mem::take(old_args));
let mut args = args.into_iter().peekable(); let mut args = args.into_iter().peekable();
env.enter_arg(args.next().unwrap()); env.push_arg(args.next().unwrap());
let (mut ret, cache) = env.with_cache(std::mem::take(cache), |env| { env.resume_stack(core::mem::take(frame));
engine.eval_func_deps(idx, env)?; engine.eval_func_deps(idx, env)?;
let mut ret = engine.call_func(idx, env)?; let mut ret = engine.call_func(idx, env)?;
while args.peek().is_some() { while args.peek().is_some() {
match ret { match ret {
Value::Func(func) => { Value::Func(func) => {
env.enter_arg(args.next().unwrap()); env.push_arg(args.next().unwrap());
engine.eval_func_deps(idx, env)?; engine.eval_func_deps(idx, env)?;
ret = engine.call_func(func, env)?; ret = engine.call_func(func, env)?;
} }
@@ -215,31 +215,32 @@ impl Value {
_ => todo!(), _ => todo!(),
} }
} }
ret.ok() let frame = env.pop_frame();
}); if let Value::Func(idx) = ret {
let args = env.pop_args(len); let args = env.pop_args(len);
if let Ok(Value::Func(idx)) = ret { ret = PartialFunc(self::PartialFunc::new(idx, args, frame).into());
ret = PartialFunc(self::PartialFunc::new(idx, args, cache).into()).ok(); } else if let Value::PartialFunc(func) = &mut ret {
} else if let Ok(Value::PartialFunc(func)) = &mut ret { let args = env.pop_args(len);
Rc::make_mut(func).args.extend(args); let func = Rc::make_mut(func);
func.args.extend(args);
} else {
env.drop_args(len);
} }
ret ret.ok()
} }
&mut Func(idx) => { &mut Func(idx) => {
let len = args.len(); let len = args.len();
let mut args = args.into_iter().peekable();
env.reserve_args(len); env.reserve_args(len);
env.enter_arg(args.next().unwrap()); let mut args = args.into_iter().peekable();
let (mut ret, cache) = env.with_new_cache(|env| { env.push_arg(args.next().unwrap());
engine.eval_func_deps(idx, env)?; engine.eval_func_deps(idx, env)?;
let mut ret = engine.call_func(idx, env)?; let mut ret = engine.call_func(idx, env)?;
ret.force(engine, env)?;
while args.peek().is_some() { while args.peek().is_some() {
match ret { match ret {
Value::Func(idx) => { Value::Func(func) => {
env.enter_arg(args.next().unwrap()); env.push_arg(args.next().unwrap());
engine.eval_func_deps(idx, env)?; engine.eval_func_deps(idx, env)?;
ret = engine.call_func(idx, env)?; ret = engine.call_func(func, env)?;
} }
Value::PartialFunc(_) => { Value::PartialFunc(_) => {
todo!() todo!()
@@ -255,15 +256,18 @@ impl Value {
_ => todo!(), _ => todo!(),
} }
} }
ret.ok() let frame = env.pop_frame();
}); if let Value::Func(idx) = ret {
let args = env.pop_args(len); let args = env.pop_args(len);
if let Ok(Value::Func(idx)) = ret { ret = PartialFunc(self::PartialFunc::new(idx, args, frame).into());
ret = Value::PartialFunc(self::PartialFunc::new(idx, args, cache).into()).ok() } else if let Value::PartialFunc(func) = &mut ret {
} else if let Ok(Value::PartialFunc(func)) = &mut ret { let args = env.pop_args(len);
Rc::make_mut(func).args.extend(args); let func = Rc::make_mut(func);
func.args.extend(args);
} else {
env.drop_args(len);
} }
ret ret.ok()
} }
Catchable(_) => return Ok(()), Catchable(_) => return Ok(()),
other => todo!("{}", other.typename()), other => todo!("{}", other.typename()),
@@ -517,7 +521,7 @@ impl Value {
pub fn force(&mut self, engine: &mut Engine, env: &mut Env) -> Result<&mut Self> { pub fn force(&mut self, engine: &mut Engine, env: &mut Env) -> Result<&mut Self> {
if let &mut Value::Thunk(idx) = self { if let &mut Value::Thunk(idx) = self {
*self = env.lookup_stack(idx, |_| unreachable!())? *self = env.lookup_stack(idx).clone();
} }
Ok(self) Ok(self)
} }