fix: fixpoint

This commit is contained in:
2025-07-17 22:50:01 +08:00
parent b556f1ea2d
commit e06bcf3f9d
8 changed files with 111 additions and 107 deletions

View File

@@ -207,6 +207,7 @@ fn test_func() {
"(inputs@{ x, y, ... }: x + inputs.y) { x = 1; y = 2; z = 3; }", "(inputs@{ x, y, ... }: x + inputs.y) { x = 1; y = 2; z = 3; }",
int!(3), int!(3),
); );
test_expr("let fix = f: let x = f x; in x; in (fix (self: { x = 1; y = self.x + 1; })).y", int!(2));
} }
#[bench] #[bench]

View File

@@ -8,9 +8,8 @@ use crate::ty::internal::Value;
#[derive(Clone, Debug)] #[derive(Clone, Debug)]
pub struct Env { pub struct Env {
cache: Vec<HashMap<usize, Value>>, stack: Vec<Value>,
with: Vec<Rc<HashMap<String, Value>>>, with: Vec<Rc<HashMap<String, Value>>>,
args: Vec<Value>,
} }
impl Default for Env { impl Default for Env {
@@ -22,36 +21,37 @@ impl Default for Env {
impl Env { impl Env {
pub fn new() -> Self { pub fn new() -> Self {
Self { Self {
cache: Vec::from([HashMap::new()]), stack: Vec::new(),
with: Vec::new(), with: Vec::new(),
args: Vec::new(),
} }
} }
pub fn with_new_cache<T>( pub fn with_new_cache<T>(
&mut self, &mut self,
len: usize,
f: impl FnOnce(&mut Self) -> T, f: impl FnOnce(&mut Self) -> T,
) -> (T, HashMap<usize, Value>) { ) -> (T, Vec<Value>) {
self.cache.push(HashMap::new()); self.stack.reserve(len);
let ret = f(self); let ret = f(self);
(ret, self.cache.pop().unwrap()) (ret, self.stack.split_off(self.stack.len() - len))
} }
pub fn with_cache<T>( pub fn with_cache<T>(
&mut self, &mut self,
cache: HashMap<usize, Value>, cache: Vec<Value>,
len: usize,
f: impl FnOnce(&mut Self) -> T, f: impl FnOnce(&mut Self) -> T,
) -> (T, HashMap<usize, Value>) { ) -> (T, Vec<Value>) {
self.cache.push(cache); self.stack.extend(cache);
let ret = f(self); let ret = f(self);
(ret, self.cache.pop().unwrap()) (ret, self.stack.split_off(self.stack.len() - len))
} }
pub fn insert_cache(&mut self, idx: usize, val: Value) { /* pub fn insert_cache(&mut self, idx: usize, val: Value) {
self.cache.last_mut().unwrap().insert(idx, val); self.cache.last_mut().unwrap().insert(idx, val);
} } */
pub fn insert_cache_lazy( /* pub fn insert_cache_lazy(
&mut self, &mut self,
idx: usize, idx: usize,
f: impl FnOnce(&mut Self) -> Result<Value>, f: impl FnOnce(&mut Self) -> Result<Value>,
@@ -61,25 +61,20 @@ impl Env {
self.cache.last_mut().unwrap().insert(idx, val); self.cache.last_mut().unwrap().insert(idx, val);
} }
Ok(()) Ok(())
} } */
pub fn lookup_cache( pub fn insert_stack(&mut self, iter: impl IntoIterator<Item = fn(&mut Self) -> Result<Value>>) -> Result<()> {
&mut self, let iter = iter.into_iter();
idx: usize, self.stack.reserve(iter.size_hint().0);
f: impl FnOnce(&mut Env) -> Result<Value>, for f in iter {
) -> Result<Value> { let val = f(self)?;
for level in self.cache.iter().rev() { self.stack.push(val);
if let Some(ret) = level.get(&idx) {
return ret.clone().ok();
}
} }
let val = f(self)?; Ok(())
self.cache.last_mut().unwrap().insert(idx, val.clone());
val.ok()
} }
pub fn lookup_arg(&self, level: usize) -> Value { pub fn lookup_stack(&mut self, idx: usize) -> Value {
self.args[self.args.len() - level - 1].clone() self.stack.get(self.stack.len() - idx - 1).unwrap().clone()
} }
pub fn lookup_with(&self, symbol: &str) -> Option<Value> { pub fn lookup_with(&self, symbol: &str) -> Option<Value> {
@@ -91,22 +86,6 @@ impl Env {
None None
} }
pub fn enter_arg(&mut self, arg: Value) {
self.args.push(arg);
}
pub fn pop_args(&mut self, len: usize) -> Vec<Value> {
self.args.split_off(self.args.len() - len)
}
pub fn reserve_args(&mut self, len: usize) {
self.args.reserve(len);
}
pub fn enter_args(&mut self, args: Vec<Value>) {
self.args.extend(args);
}
pub fn exit_with(&mut self) { pub fn exit_with(&mut self) {
self.with.pop(); self.with.pop();
} }

View File

@@ -540,6 +540,12 @@ impl JITCompile for LetVar {
} }
} }
impl JITCompile for Cache {
fn compile(&self, ctx: &mut JITContext, engine: ir::Value, env: ir::Value) -> StackSlot {
todo!()
}
}
impl JITCompile for Thunk { impl JITCompile for Thunk {
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 {
let slot = ctx.alloca(); let slot = ctx.alloca();

View File

@@ -305,6 +305,12 @@ impl Evaluate for ir::LetVar {
} }
} }
impl Evaluate for ir::Cache {
fn eval(&self, engine: &mut Engine, env: &mut Env) -> Result<Value> {
todo!()
}
}
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() Value::Thunk(self.idx).ok()

View File

@@ -16,7 +16,6 @@ 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<Dep, usize>>,
pub func_arg_dep: Vec<bool>,
pub funcs: Vec<Func>, pub funcs: Vec<Func>,
} }
@@ -148,7 +147,6 @@ 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_dep: Vec::new(),
funcs: Vec::new(), funcs: Vec::new(),
} }
} }
@@ -190,7 +188,6 @@ 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_dep.push(false);
LoadFunc { idx } LoadFunc { idx }
} }

View File

@@ -147,6 +147,8 @@ ir! {
#[derive(Copy)] #[derive(Copy)]
LetVar => { level: usize, idx: usize }, LetVar => { level: usize, idx: usize },
#[derive(Copy)] #[derive(Copy)]
Cache => { idx: usize },
#[derive(Copy)]
Thunk => { idx: usize }, Thunk => { idx: usize },
Path => { expr: Box<Ir> }, Path => { expr: Box<Ir> },
} }
@@ -362,9 +364,20 @@ impl Thunk {
ctx: &mut DowngradeContext, ctx: &mut DowngradeContext,
env: &Env<'a, 'env>, env: &Env<'a, 'env>,
) -> Result<Ir> { ) -> Result<Ir> {
ctx.new_dep(self_idx, Dep::Thunk(self.idx)); let idx = ctx.new_dep(self_idx, Dep::Thunk(self.idx));
ctx.resolve_thunk(self.idx, env)?; ctx.resolve_thunk(self.idx, env)?;
self.ir().ok() Cache { idx }.ir().ok()
}
}
impl Cache {
fn resolve<'a, 'env>(
self,
self_idx: Index,
ctx: &mut DowngradeContext,
env: &Env<'a, 'env>,
) -> Result<Ir> {
unreachable!()
} }
} }
@@ -468,27 +481,28 @@ where
impl Downgrade for Expr { impl Downgrade for Expr {
fn downgrade(self, ctx: &mut DowngradeContext) -> Result<Ir> { fn downgrade(self, ctx: &mut DowngradeContext) -> Result<Ir> {
use Expr::*;
match self { match self {
Expr::Apply(apply) => apply.downgrade(ctx), Apply(apply) => apply.downgrade(ctx),
Expr::Assert(assert) => assert.downgrade(ctx), Assert(assert) => assert.downgrade(ctx),
Expr::Error(error) => Err(Error::DowngradeError(error.to_string())), Error(error) => Err(self::Error::DowngradeError(error.to_string())),
Expr::IfElse(ifelse) => ifelse.downgrade(ctx), IfElse(ifelse) => ifelse.downgrade(ctx),
Expr::Select(select) => select.downgrade(ctx), Select(select) => select.downgrade(ctx),
Expr::Str(str) => str.downgrade(ctx), Str(str) => str.downgrade(ctx),
Expr::Path(path) => path.downgrade(ctx), Path(path) => path.downgrade(ctx),
Expr::Literal(lit) => lit.downgrade(ctx), Literal(lit) => lit.downgrade(ctx),
Expr::Lambda(lambda) => lambda.downgrade(ctx), Lambda(lambda) => lambda.downgrade(ctx),
Expr::LegacyLet(let_) => let_.downgrade(ctx), LegacyLet(let_) => let_.downgrade(ctx),
Expr::LetIn(letin) => letin.downgrade(ctx), LetIn(letin) => letin.downgrade(ctx),
Expr::List(list) => list.downgrade(ctx), List(list) => list.downgrade(ctx),
Expr::BinOp(op) => op.downgrade(ctx), BinOp(op) => op.downgrade(ctx),
Expr::Paren(paren) => paren.expr().unwrap().downgrade(ctx), Paren(paren) => paren.expr().unwrap().downgrade(ctx),
Expr::Root(root) => root.expr().unwrap().downgrade(ctx), Root(root) => root.expr().unwrap().downgrade(ctx),
Expr::AttrSet(attrs) => attrs.downgrade(ctx), AttrSet(attrs) => attrs.downgrade(ctx),
Expr::UnaryOp(op) => op.downgrade(ctx), UnaryOp(op) => op.downgrade(ctx),
Expr::Ident(ident) => ident.downgrade(ctx), Ident(ident) => ident.downgrade(ctx),
Expr::With(with) => with.downgrade(ctx), With(with) => with.downgrade(ctx),
Expr::HasAttr(has) => has.downgrade(ctx), HasAttr(has) => has.downgrade(ctx),
} }
} }
} }
@@ -935,14 +949,13 @@ impl Select {
.collect::<Result<Vec<_>>>()?; .collect::<Result<Vec<_>>>()?;
let res = match &expr { let res = match &expr {
Ir::Attrs(attrs) => attrs.select(&attrpath), Ir::Attrs(attrs) => attrs.select(&attrpath),
&Ir::Thunk(Thunk { idx }) => ctx.thunks[idx] &Ir::Thunk(Thunk { idx }) => {
.0 match ctx.thunks[idx].0.as_ref() {
.as_ref() IrRef::Attrs(attrs) => attrs.select(&attrpath),
.try_unwrap_attrs() _ => Ok(None)
.map_err(|_| Error::DowngradeError("can not select from <type>".into()))? }
.select(&attrpath), }
Ir::Arg(_) => Ok(None), _ => Ok(None),
_ => return Err(Error::DowngradeError("can not select from <type>".into())),
}; };
let res = match res { let res = match res {
Err(err) => { Err(err) => {
@@ -1061,11 +1074,10 @@ impl Let {
) -> Result<Ir> { ) -> Result<Ir> {
let map = self.bindings.clone(); let map = self.bindings.clone();
let env = env.enter_let(&map); let env = env.enter_let(&map);
self.bindings self.bindings.into_iter().try_for_each(|(_, ir)| {
.into_iter().try_for_each(|(_, ir)| { ir.resolve(self_idx, ctx, &env)?;
ir.resolve(self_idx, ctx, &env)?; Ok(())
Ok(()) })?;
})?;
self.expr.resolve(self_idx, ctx, &env)?.ok() self.expr.resolve(self_idx, ctx, &env)?.ok()
} }
} }

View File

@@ -1,3 +1,5 @@
use std::collections::VecDeque;
use hashbrown::{HashMap, HashSet}; use hashbrown::{HashMap, HashSet};
use super::*; use super::*;
@@ -22,7 +24,7 @@ pub struct SccGraph {
} }
impl SccGraph { impl SccGraph {
fn from(ctx: &DowngradeContext, sccs: Vec<Vec<usize>>) -> Self { fn new(ctx: &DowngradeContext, sccs: Vec<Vec<usize>>) -> Self {
let mut graph = SccGraph::default(); let mut graph = SccGraph::default();
let mut thunk_to_scc = HashMap::new(); let mut thunk_to_scc = HashMap::new();
@@ -92,11 +94,11 @@ impl SccGraph {
} }
} }
let mut queue: std::collections::VecDeque<usize> = in_degrees let mut queue = in_degrees
.iter() .iter()
.filter(|(_, deg)| **deg == 0) .filter(|&(_, &deg)| deg == 0)
.map(|(&id, _)| id) .map(|(&id, _)| id)
.collect(); .collect::<VecDeque<_>>();
queue.make_contiguous().sort(); queue.make_contiguous().sort();
@@ -119,7 +121,7 @@ impl SccGraph {
} }
if sorted_order.len() != reachable.len() { if sorted_order.len() != reachable.len() {
panic!("Cycle detected in the reachable part of SCC graph!"); unreachable!("Cycle detected in the reachable part of SCC graph!");
} }
sorted_order sorted_order
@@ -155,7 +157,7 @@ impl<'ctx> SccAnalyzer<'ctx> {
self.strong_connect(idx); self.strong_connect(idx);
} }
} }
SccGraph::from(self.ctx, self.sccs).sorted() SccGraph::new(self.ctx, self.sccs).sorted()
} }
fn strong_connect(&mut self, v_id: usize) { fn strong_connect(&mut self, v_id: usize) {
@@ -165,21 +167,22 @@ impl<'ctx> SccAnalyzer<'ctx> {
self.stack.push(v_id); self.stack.push(v_id);
self.on_stack.insert(v_id); self.on_stack.insert(v_id);
if let Some(deps) = self.ctx.thunk_deps.get(v_id) { let Some(deps) = self.ctx.thunk_deps.get(v_id) else {
for (&w_id, _) in deps { unreachable!()
if !self.indices.contains_key(&w_id) { };
self.strong_connect(w_id); for (&w_id, _) in deps {
let v_low_link = *self.low_links.get(&v_id).unwrap(); if !self.indices.contains_key(&w_id) {
let w_low_link = *self.low_links.get(&w_id).unwrap(); self.strong_connect(w_id);
if w_low_link < v_low_link { let v_low_link = *self.low_links.get(&v_id).unwrap();
self.low_links.insert(v_id, w_low_link); let w_low_link = *self.low_links.get(&w_id).unwrap();
} if w_low_link < v_low_link {
} else if self.on_stack.contains(&w_id) { self.low_links.insert(v_id, w_low_link);
let v_low_link = *self.low_links.get(&v_id).unwrap(); }
let w_index = *self.indices.get(&w_id).unwrap(); } else if self.on_stack.contains(&w_id) {
if w_index < v_low_link { let v_low_link = *self.low_links.get(&v_id).unwrap();
self.low_links.insert(v_id, w_index); let w_index = *self.indices.get(&w_id).unwrap();
} if w_index < v_low_link {
self.low_links.insert(v_id, w_index);
} }
} }
} }

View File

@@ -462,7 +462,7 @@ impl Value {
AttrSet(attrs) => attrs.select(path), AttrSet(attrs) => attrs.select(path),
Catchable(_) => return Ok(self), Catchable(_) => return Ok(self),
_ => Err(Error::EvalError(format!( _ => Err(Error::EvalError(format!(
"cannot select from {:?}", "can not select from {:?}",
self.typename() self.typename()
))), ))),
}?; }?;
@@ -481,7 +481,7 @@ impl Value {
Catchable(_) => return Ok(self), Catchable(_) => return Ok(self),
_ => { _ => {
return Err(Error::EvalError(format!( return Err(Error::EvalError(format!(
"cannot select from {:?}", "can not select from {:?}",
self.typename() self.typename()
))); )));
} }
@@ -517,7 +517,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_cache(idx, |_| unreachable!())? *self = env.lookup_stack(idx, |_| unreachable!())?
} }
Ok(self) Ok(self)
} }