From b2d2490327c737660fb2f4eaa7d1c1f8f70b3cc5 Mon Sep 17 00:00:00 2001 From: imxyy_soope_ Date: Sun, 15 Jun 2025 17:23:32 +0800 Subject: [PATCH] feat: SCC analysis (WIP) --- Cargo.toml | 4 + src/bin/eval.rs | 4 +- src/bin/scc.rs | 59 ++++ src/engine/mod.rs | 8 +- src/engine/test.rs | 2 +- src/eval/jit/compile.rs | 46 +-- src/eval/jit/helpers.rs | 12 +- src/eval/jit/mod.rs | 4 +- src/eval/mod.rs | 16 +- src/ir/ctx.rs | 234 +++++++++++++++ src/ir/mod.rs | 629 +++++++++++++++++++++------------------- src/ir/scc.rs | 169 +++++++++++ src/ty/common.rs | 7 +- 13 files changed, 842 insertions(+), 352 deletions(-) create mode 100644 src/bin/scc.rs create mode 100644 src/ir/ctx.rs create mode 100644 src/ir/scc.rs diff --git a/Cargo.toml b/Cargo.toml index dab0a9d..988e521 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -10,6 +10,10 @@ repl = ["dep:rustyline"] name = "repl" required-features = ["repl"] +[[bin]] +name = "scc" +required-features = ["repl"] + [profile.perf] debug = 2 strip = false diff --git a/src/bin/eval.rs b/src/bin/eval.rs index 80c5fd2..4bae80b 100644 --- a/src/bin/eval.rs +++ b/src/bin/eval.rs @@ -22,8 +22,8 @@ fn main() -> Result<()> { )); } let expr = root.tree().expr().unwrap(); - let expr = downgrade(expr)?; - println!("{}", eval(expr)?); + let (downgraded, _) = downgrade(expr)?; + println!("{}", eval(downgraded)?); Ok(()) } diff --git a/src/bin/scc.rs b/src/bin/scc.rs new file mode 100644 index 0000000..1f219bb --- /dev/null +++ b/src/bin/scc.rs @@ -0,0 +1,59 @@ +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 (_, graph) = unwrap!(downgrade(expr)); + println!("{:?}", graph); + } + Err(ReadlineError::Interrupted) => { + println!("CTRL-C"); + break; + } + Err(ReadlineError::Eof) => { + println!("CTRL-D"); + break; + } + Err(err) => { + println!("Error: {err:?}"); + break; + } + } + } + Ok(()) +} diff --git a/src/engine/mod.rs b/src/engine/mod.rs index 005aaf9..60748ff 100644 --- a/src/engine/mod.rs +++ b/src/engine/mod.rs @@ -27,7 +27,7 @@ pub struct Engine { pub fn eval(downgraded: Downgraded) -> Result { let mut engine = Engine::new(downgraded.thunks, downgraded.func_offset); engine - .eval(downgraded.top_level, &mut VmEnv::new()) + .eval() .map(|mut val| { Ok(val .force(&mut engine)? @@ -38,15 +38,15 @@ pub fn eval(downgraded: Downgraded) -> Result { impl Engine { pub fn new(thunks: Box<[Ir]>, func_offset: usize) -> Self { Self { - lru: LruCache::new(thunks.len().clamp(1, usize::MAX).try_into().unwrap()), + lru: LruCache::new(thunks.len().try_into().unwrap()), thunks, func_offset, tasks: PriorityQueue::new(), } } - pub fn eval(&mut self, expr: Ir, env: &mut VmEnv) -> Result { - expr.eval(self, env) + pub fn eval(&mut self) -> Result { + self.thunks.last().unwrap().clone().eval(self, &mut VmEnv::new()) } pub fn eval_thunk(&mut self, idx: usize, env: &mut VmEnv) -> Result { diff --git a/src/engine/test.rs b/src/engine/test.rs index ce79b44..24e06f8 100644 --- a/src/engine/test.rs +++ b/src/engine/test.rs @@ -16,7 +16,7 @@ use super::eval; #[inline] fn test_expr(expr: &str, expected: Value) { - let downgraded = downgrade(rnix::Root::parse(expr).tree().expr().unwrap()).unwrap(); + let (downgraded, _) = downgrade(rnix::Root::parse(expr).tree().expr().unwrap()).unwrap(); assert_eq!(eval(downgraded).unwrap(), expected); } diff --git a/src/eval/jit/compile.rs b/src/eval/jit/compile.rs index c2607de..3c766bf 100644 --- a/src/eval/jit/compile.rs +++ b/src/eval/jit/compile.rs @@ -1,4 +1,4 @@ -use inkwell::values::{BasicValueEnum, FunctionValue}; +use inkwell::values::{StructValue, FunctionValue}; use crate::ir::*; use crate::ty::common as c; @@ -7,35 +7,35 @@ use crate::ty::internal::Value; use super::JITContext; pub trait JITCompile { - fn compile<'gc>(self, ctx: &JITContext<'gc>, func: FunctionValue<'gc>) -> BasicValueEnum<'gc>; + fn compile<'gc>(self, ctx: &JITContext<'gc>, func: FunctionValue<'gc>) -> StructValue<'gc>; } impl JITCompile for Attrs { - fn compile<'gc>(self, ctx: &JITContext<'gc>, func: FunctionValue<'gc>) -> BasicValueEnum<'gc> { + fn compile<'gc>(self, ctx: &JITContext<'gc>, func: FunctionValue<'gc>) -> StructValue<'gc> { todo!() } } impl JITCompile for List { - fn compile<'gc>(self, ctx: &JITContext<'gc>, func: FunctionValue<'gc>) -> BasicValueEnum<'gc> { + fn compile<'gc>(self, ctx: &JITContext<'gc>, func: FunctionValue<'gc>) -> StructValue<'gc> { todo!() } } impl JITCompile for HasAttr { - fn compile<'gc>(self, ctx: &JITContext<'gc>, func: FunctionValue<'gc>) -> BasicValueEnum<'gc> { + fn compile<'gc>(self, ctx: &JITContext<'gc>, func: FunctionValue<'gc>) -> StructValue<'gc> { todo!() } } impl JITCompile for BinOp { - fn compile<'gc>(self, ctx: &JITContext<'gc>, func: FunctionValue<'gc>) -> BasicValueEnum<'gc> { + fn compile<'gc>(self, ctx: &JITContext<'gc>, func: FunctionValue<'gc>) -> StructValue<'gc> { todo!() } } impl JITCompile for UnOp { - fn compile<'gc>(self, ctx: &JITContext<'gc>, func: FunctionValue<'gc>) -> BasicValueEnum<'gc> { + fn compile<'gc>(self, ctx: &JITContext<'gc>, func: FunctionValue<'gc>) -> StructValue<'gc> { todo!(); let rhs = self.rhs.compile(ctx, func); let tag = ctx.get_tag(rhs); @@ -45,60 +45,60 @@ impl JITCompile for UnOp { ctx.builder.build_switch(tag, fallback, &[]).unwrap(); ctx.builder.position_at_end(fallback); ctx.builder.position_at_end(ret); - ctx.builder.build_load(ctx.helpers.value_type, res, "load_res").unwrap() + ctx.builder.build_load(ctx.helpers.value_type, res, "load_res").unwrap().try_into().unwrap() } } impl JITCompile for Select { - fn compile<'gc>(self, ctx: &JITContext<'gc>, func: FunctionValue<'gc>) -> BasicValueEnum<'gc> { + fn compile<'gc>(self, ctx: &JITContext<'gc>, func: FunctionValue<'gc>) -> StructValue<'gc> { todo!() } } impl JITCompile for If { - fn compile<'gc>(self, ctx: &JITContext<'gc>, func: FunctionValue<'gc>) -> BasicValueEnum<'gc> { + fn compile<'gc>(self, ctx: &JITContext<'gc>, func: FunctionValue<'gc>) -> StructValue<'gc> { todo!() } } impl JITCompile for LoadFunc { - fn compile<'gc>(self, ctx: &JITContext<'gc>, func: FunctionValue<'gc>) -> BasicValueEnum<'gc> { + fn compile<'gc>(self, ctx: &JITContext<'gc>, func: FunctionValue<'gc>) -> StructValue<'gc> { todo!() } } impl JITCompile for Call { - fn compile<'gc>(self, ctx: &JITContext<'gc>, func: FunctionValue<'gc>) -> BasicValueEnum<'gc> { + fn compile<'gc>(self, ctx: &JITContext<'gc>, func: FunctionValue<'gc>) -> StructValue<'gc> { todo!() } } impl JITCompile for Let { - fn compile<'gc>(self, ctx: &JITContext<'gc>, func: FunctionValue<'gc>) -> BasicValueEnum<'gc> { + fn compile<'gc>(self, ctx: &JITContext<'gc>, func: FunctionValue<'gc>) -> StructValue<'gc> { todo!() } } impl JITCompile for With { - fn compile<'gc>(self, ctx: &JITContext<'gc>, func: FunctionValue<'gc>) -> BasicValueEnum<'gc> { + fn compile<'gc>(self, ctx: &JITContext<'gc>, func: FunctionValue<'gc>) -> StructValue<'gc> { todo!() } } impl JITCompile for Assert { - fn compile<'gc>(self, ctx: &JITContext<'gc>, func: FunctionValue<'gc>) -> BasicValueEnum<'gc> { + fn compile<'gc>(self, ctx: &JITContext<'gc>, func: FunctionValue<'gc>) -> StructValue<'gc> { todo!() } } impl JITCompile for ConcatStrings { - fn compile<'gc>(self, ctx: &JITContext<'gc>, func: FunctionValue<'gc>) -> BasicValueEnum<'gc> { + fn compile<'gc>(self, ctx: &JITContext<'gc>, func: FunctionValue<'gc>) -> StructValue<'gc> { todo!() } } impl JITCompile for Const { - fn compile<'gc>(self, ctx: &JITContext<'gc>, func: FunctionValue<'gc>) -> BasicValueEnum<'gc> { + fn compile<'gc>(self, ctx: &JITContext<'gc>, func: FunctionValue<'gc>) -> StructValue<'gc> { use c::Const::*; match self.val { Bool(x) => ctx.helpers.new_bool(x), @@ -110,37 +110,37 @@ impl JITCompile for Const { } impl JITCompile for String { - fn compile<'gc>(self, ctx: &JITContext<'gc>, func: FunctionValue<'gc>) -> BasicValueEnum<'gc> { + fn compile<'gc>(self, ctx: &JITContext<'gc>, func: FunctionValue<'gc>) -> StructValue<'gc> { todo!() } } impl JITCompile for Var { - fn compile<'gc>(self, ctx: &JITContext<'gc>, func: FunctionValue<'gc>) -> BasicValueEnum<'gc> { + fn compile<'gc>(self, ctx: &JITContext<'gc>, func: FunctionValue<'gc>) -> StructValue<'gc> { todo!() } } impl JITCompile for Arg { - fn compile<'gc>(self, ctx: &JITContext<'gc>, func: FunctionValue<'gc>) -> BasicValueEnum<'gc> { + fn compile<'gc>(self, ctx: &JITContext<'gc>, func: FunctionValue<'gc>) -> StructValue<'gc> { todo!() } } impl JITCompile for LetVar { - fn compile<'gc>(self, ctx: &JITContext<'gc>, func: FunctionValue<'gc>) -> BasicValueEnum<'gc> { + fn compile<'gc>(self, ctx: &JITContext<'gc>, func: FunctionValue<'gc>) -> StructValue<'gc> { todo!() } } impl JITCompile for Thunk { - fn compile<'gc>(self, ctx: &JITContext<'gc>, func: FunctionValue<'gc>) -> BasicValueEnum<'gc> { + fn compile<'gc>(self, ctx: &JITContext<'gc>, func: FunctionValue<'gc>) -> StructValue<'gc> { todo!() } } impl JITCompile for Path { - fn compile<'gc>(self, ctx: &JITContext<'gc>, func: FunctionValue<'gc>) -> BasicValueEnum<'gc> { + fn compile<'gc>(self, ctx: &JITContext<'gc>, func: FunctionValue<'gc>) -> StructValue<'gc> { todo!() } } diff --git a/src/eval/jit/helpers.rs b/src/eval/jit/helpers.rs index 2d60299..a4130a0 100644 --- a/src/eval/jit/helpers.rs +++ b/src/eval/jit/helpers.rs @@ -5,7 +5,7 @@ use inkwell::context::Context; use inkwell::execution_engine::ExecutionEngine; use inkwell::module::Module; use inkwell::types::{FloatType, FunctionType, IntType, PointerType, StructType}; -use inkwell::values::{BasicValueEnum, FloatValue, FunctionValue, IntValue}; +use inkwell::values::{BasicValueEnum, FloatValue, FunctionValue, IntValue, StructValue}; use crate::env::VmEnv; use crate::eval::Engine; @@ -180,7 +180,7 @@ impl<'ctx> Helpers<'ctx> { self.int_type.const_int(int as _, false) } - pub fn new_int(&self, int: i64) -> BasicValueEnum<'ctx> { + pub fn new_int(&self, int: i64) -> StructValue<'ctx> { self.value_type .const_named_struct(&[ self.int_type.const_int(ValueTag::Int as _, false).into(), @@ -193,7 +193,7 @@ impl<'ctx> Helpers<'ctx> { self.float_type.const_float(float) } - pub fn new_float(&self, float: f64) -> BasicValueEnum<'ctx> { + pub fn new_float(&self, float: f64) -> StructValue<'ctx> { self.value_type .const_named_struct(&[ self.int_type.const_int(ValueTag::Float as _, false).into(), @@ -202,7 +202,7 @@ impl<'ctx> Helpers<'ctx> { .into() } - pub fn new_bool(&self, bool: bool) -> BasicValueEnum<'ctx> { + pub fn new_bool(&self, bool: bool) -> StructValue<'ctx> { self.value_type .const_named_struct(&[ self.int_type.const_int(ValueTag::Bool as _, false).into(), @@ -211,7 +211,7 @@ impl<'ctx> Helpers<'ctx> { .into() } - pub fn new_null(&self) -> BasicValueEnum<'ctx> { + pub fn new_null(&self) -> StructValue<'ctx> { self.value_type .const_named_struct(&[ self.int_type.const_int(ValueTag::Null as _, false).into(), @@ -220,7 +220,7 @@ impl<'ctx> Helpers<'ctx> { .into() } - pub fn const_string(&self, string: *const u8) -> BasicValueEnum<'ctx> { + pub fn const_string(&self, string: *const u8) -> StructValue<'ctx> { self.value_type .const_named_struct(&[ self.int_type.const_int(ValueTag::String as _, false).into(), diff --git a/src/eval/jit/mod.rs b/src/eval/jit/mod.rs index 73028aa..35d5364 100644 --- a/src/eval/jit/mod.rs +++ b/src/eval/jit/mod.rs @@ -139,10 +139,10 @@ impl<'ctx> JITContext<'ctx> { } pub fn compile(&self, ir: Ir) -> JITFunc { - + todo!() } - pub fn get_tag(&self, val: BasicValueEnum<'ctx>) -> IntValue<'ctx> { + pub fn get_tag(&self, val: StructValue<'ctx>) -> IntValue<'ctx> { let alloca = self .builder .build_alloca(self.helpers.value_type, "get_tag_alloca").unwrap(); diff --git a/src/eval/mod.rs b/src/eval/mod.rs index 508c363..4479ba2 100644 --- a/src/eval/mod.rs +++ b/src/eval/mod.rs @@ -1,12 +1,9 @@ -use std::rc::Rc; - use ecow::EcoVec; use crate::engine::Engine; use crate::env::VmEnv; use crate::error::{Error, Result}; -use crate::ir::{self, DynamicAttrPair}; -use crate::ty::common::Const; +use crate::ir::{self, DynAttr}; use crate::ty::common::Const; use crate::ty::internal::{AttrSet, EnvRef, List, ThunkRef, Value}; use crate::ty::public::Symbol; @@ -32,7 +29,7 @@ impl Evaluate for ir::Attrs { }) .collect::>()?, ); - for DynamicAttrPair(k, v) in self.dyns { + for DynAttr(k, v) in self.dyns { let mut k = k.eval(engine, env)?; k.force(engine)?.coerce_to_string(); attrs.push_attr(k.unwrap_string(), v.eval(engine, env)?); @@ -315,6 +312,15 @@ impl Evaluate for ir::Thunk { } } +impl Evaluate for ir::MaybeThunk { + fn eval(self, engine: &mut Engine, env: &mut VmEnv) -> Result { + match self { + ir::MaybeThunk::Const(cnst) => cnst.eval(engine, env), + ir::MaybeThunk::Thunk(thunk) => thunk.eval(engine, env) + } + } +} + impl Evaluate for ir::Path { fn eval(self, engine: &mut Engine, env: &mut VmEnv) -> Result { todo!() diff --git a/src/ir/ctx.rs b/src/ir/ctx.rs new file mode 100644 index 0000000..f9e5b2c --- /dev/null +++ b/src/ir/ctx.rs @@ -0,0 +1,234 @@ +use ecow::EcoString; +use hashbrown::{HashMap, HashSet}; + +use crate::error::Result; + +use super::{Func, Ir, LoadFunc, MaybeThunk, Thunk}; + +pub struct DowngradeContext { + pub thunks: Vec, + pub deps: Vec>, + pub funcs: Vec, +} + +pub struct Env<'a, 'env> { + env: EnvNode<'a>, + prev: Option<&'env Env<'a, 'env>>, + arg_level: usize, + let_level: usize, +} + +enum EnvNode<'a> { + Builtins(&'a HashMap), + Let(&'a Vec<(EcoString, MaybeThunk)>), + SingleArg(EcoString), + MultiArg(HashMap>, Option), + With, +} + +pub enum LookupResult { + Builtin(Ir), + MaybeThunk(MaybeThunk), + Let { level: usize, idx: usize }, + SingleArg { level: usize }, + MultiArg { level: usize, default: Option }, + With, +} + +impl<'a, 'env> Env<'a, 'env> { + pub fn new(base: &'a HashMap) -> Self { + Self { + env: EnvNode::Builtins(base), + prev: None, + arg_level: 0, + let_level: 0, + } + } + + pub fn enter_let(&'env self, map: &'a Vec<(EcoString, MaybeThunk)>) -> Self { + Self { + env: EnvNode::Let(map), + prev: Some(self), + arg_level: self.arg_level, + let_level: self.let_level + 1, + } + } + + pub fn enter_single_arg(&'env self, ident: EcoString) -> Self { + Self { + env: EnvNode::SingleArg(ident), + prev: Some(self), + arg_level: self.arg_level + 1, + let_level: self.let_level, + } + } + + pub fn enter_multi_arg( + &'env self, + map: HashMap>, + alias: Option, + ) -> Self { + Self { + env: EnvNode::MultiArg(map, alias), + prev: Some(self), + arg_level: self.arg_level + 1, + let_level: 0, + } + } + + pub fn enter_with(&'env self) -> Self { + Self { + env: EnvNode::With, + prev: Some(self), + arg_level: self.arg_level, + let_level: self.let_level, + } + } + + fn _lookup( + &self, + ident: &EcoString, + mut arg_level: usize, + mut let_level: usize, + has_with: bool, + ) -> core::result::Result { + use EnvNode::*; + let mut has_with = has_with; + match &self.env { + Builtins(map) => { + return if let Some(ir) = map.get(ident) { + Ok(LookupResult::Builtin(ir.clone())) + } else if has_with { + Ok(LookupResult::With) + } else { + Err(()) + }; + } + Let(map) => { + if let Ok(idx) = map.binary_search_by(|(k, _)| k.cmp(ident)) { + return Ok(LookupResult::MaybeThunk(map[idx].1)) + /* return Ok(LookupResult::Let { + level: let_level - 1, + idx, + }); */ + } else { + let_level -= 1; + } + } + SingleArg(arg) => { + if arg == ident { + return Ok(LookupResult::SingleArg { + level: arg_level - 1, + }); + } else { + arg_level -= 1; + } + } + MultiArg(set, alias) => { + if let Some(default) = set.get(ident) { + return Ok(LookupResult::MultiArg { + level: arg_level - 1, + default: default.clone(), + }); + } else if alias.as_ref() == Some(ident) { + return Ok(LookupResult::SingleArg { + level: arg_level - 1, + }); + } else { + arg_level -= 1; + } + } + With => has_with = true, + } + self.prev + .map(|prev| prev._lookup(ident, arg_level, let_level, has_with)) + .map_or_else(|| unreachable!(), |x| x) + } + + pub fn lookup(&self, ident: &EcoString) -> core::result::Result { + self._lookup(ident, self.arg_level, self.let_level, false) + } +} + +impl DowngradeContext { + pub fn new() -> Self { + DowngradeContext { + thunks: Vec::new(), + deps: Vec::new(), + funcs: Vec::new(), + } + } +} + +impl DowngradeContext { + pub fn new_thunk(&mut self, thunk: Ir) -> Thunk { + let idx = self.thunks.len(); + self.thunks.push(thunk); + self.deps.push(HashSet::new()); + Thunk { idx } + } + + pub fn maybe_thunk(&mut self, ir: Ir) -> MaybeThunk { + match ir { + Ir::Const(cnst) => MaybeThunk::Const(cnst), + Ir::Thunk(thunk) => MaybeThunk::Thunk(thunk), + ir => MaybeThunk::Thunk(self.new_thunk(ir)) + } + } + + pub fn new_dep(&mut self, this: usize, dep: usize) { + self.deps[this].insert(dep); + } + + pub fn new_func(&mut self, func: Func) -> LoadFunc { + let idx = self.funcs.len(); + self.funcs.push(func); + LoadFunc { idx } + } + + pub fn resolve_func(&mut self, thunk_idx: usize, func_idx: usize, env: &Env) -> Result<()> { + let self_ptr = self as *mut Self; + self.funcs.get_mut(func_idx).map_or_else( + || unreachable!(), + |func| { + unsafe { + let old = std::ptr::read(func); + std::ptr::write(func, old.resolve(thunk_idx, Some(func_idx), self_ptr.as_mut().unwrap(), env)?); + } + Ok(()) + }, + ) + } + + pub fn resolve_thunk(&mut self, thunk_idx: usize, func_idx: Option, env: &Env) -> Result<()> { + let self_ptr = self as *mut Self; + self.thunks.get_mut(thunk_idx).map_or_else( + || unreachable!(), + |thunk| { + unsafe { + let old = std::ptr::read(thunk); + std::ptr::write(thunk, old.resolve(thunk_idx, func_idx, self_ptr.as_mut().unwrap(), env)?); + } + Ok(()) + }, + ) + } +} + +pub struct Downgraded { + pub thunks: Box<[Ir]>, + pub func_offset: usize, +} + +impl Downgraded { + pub fn new(ctx: DowngradeContext) -> Self { + Self { + func_offset: ctx.thunks.len(), + thunks: ctx + .thunks + .into_iter() + .chain(ctx.funcs.into_iter().map(|Func { body, .. }| *body)) + .collect(), + } + } +} diff --git a/src/ir/mod.rs b/src/ir/mod.rs index 851b77a..4385414 100644 --- a/src/ir/mod.rs +++ b/src/ir/mod.rs @@ -1,7 +1,7 @@ use derive_more::{IsVariant, TryUnwrap, Unwrap}; use ecow::EcoString; -use hashbrown::HashMap; -use inkwell::values::{StructValue, FunctionValue}; +use hashbrown::{HashMap, HashSet}; +use inkwell::values::{FunctionValue, StructValue}; use itertools::Itertools; use rnix::ast::HasEntry; use rnix::ast::{self, Expr}; @@ -16,24 +16,24 @@ use crate::ty::common as c; use crate::ty::internal::Value; use crate::ty::public::Symbol; +mod ctx; +mod scc; mod utils; +use ctx::*; use utils::*; -pub fn downgrade(expr: Expr) -> Result { +pub use ctx::{DowngradeContext, Downgraded}; +pub use scc::*; + +pub fn downgrade(expr: Expr) -> Result<(Downgraded, Vec)> { let mut ctx = DowngradeContext::new(); let builtins = ir_env(&mut ctx); let env = Env::new(&builtins); - let ir = expr.downgrade(&mut ctx)?; - let ir = ir.resolve(&mut ctx, &env)?; - Ok(Downgraded { - top_level: ir, - func_offset: ctx.thunks.len(), - thunks: ctx - .thunks - .into_iter() - .chain(ctx.funcs.into_iter().map(|Func { body, .. }| *body)) - .collect(), - }) + let top_level = expr.downgrade(&mut ctx)?; + let Thunk { idx } = ctx.new_thunk(top_level); + ctx.resolve_thunk(idx, None, &env)?; + let scc = SccAnalyzer::new(&ctx).analyze(); + Ok((Downgraded::new(ctx), scc)) } macro_rules! ir { @@ -83,9 +83,9 @@ macro_rules! ir { } #[inline] - fn resolve<'a, 'env>(self, ctx: &mut DowngradeContext, env: &Env<'a, 'env>) -> Result { + fn resolve<'a, 'env>(self, self_idx: usize, func_idx: Option, ctx: &mut DowngradeContext, env: &Env<'a, 'env>) -> Result { match self { - $(Ir::$ty(ir) => ir.resolve(ctx, env),)* + $(Ir::$ty(ir) => ir.resolve(self_idx, func_idx, ctx, env),)* } } } @@ -127,7 +127,7 @@ macro_rules! ir { } ir! { - Attrs => { stcs: HashMap, dyns: Vec }, + Attrs => { stcs: HashMap, dyns: Vec }, List => { items: Vec }, HasAttr => { lhs: Box, rhs: Vec }, BinOp => { lhs: Box, rhs: Box, kind: BinOpKind }, @@ -137,10 +137,11 @@ ir! { LoadFunc => { idx: usize }, Call => { func: Box, args: Vec }, - Let => { bindings: Vec<(EcoString, Ir)>, expr: Box }, + Let => { bindings: Vec<(EcoString, MaybeThunk)>, expr: Box }, With => { namespace: Box, expr: Box }, Assert => { assertion: Box, expr: Box }, ConcatStrings => { parts: Vec }, + #[derive(Copy)] Const => { val: c::Const }, String => { val: EcoString }, Var => { sym: EcoString }, @@ -165,6 +166,27 @@ impl Ir { } } +#[derive(Debug, Clone, Copy)] +pub enum MaybeThunk { + Const(Const), + Thunk(Thunk) +} + +impl MaybeThunk { + fn resolve<'a, 'env>( + self, + self_idx: usize, + func_idx: Option, + ctx: &mut DowngradeContext, + env: &Env<'a, 'env>, + ) -> Result { + match self { + MaybeThunk::Thunk(thunk) => thunk.resolve(self_idx, func_idx, ctx, env), + MaybeThunk::Const(cnst) => cnst.ir().ok() + } + } +} + impl> From for Const { fn from(value: T) -> Self { Const { val: value.into() } @@ -172,206 +194,7 @@ impl> From for Const { } #[derive(Clone, Debug)] -pub struct DynamicAttrPair(pub Ir, pub Ir); - -pub struct DowngradeContext { - thunks: Vec, - funcs: Vec, -} - -struct Env<'a, 'env> { - env: EnvNode<'a>, - prev: Option<&'env Env<'a, 'env>>, - arg_level: usize, - let_level: usize, -} - -enum EnvNode<'a> { - Builtins(&'a HashMap), - Let(&'a Vec), - SingleArg(EcoString), - MultiArg(HashMap>, Option), - With, -} - -enum LookupResult { - Builtin(Ir), - Let { level: usize, idx: usize }, - SingleArg { level: usize }, - MultiArg { level: usize, default: Option }, - With, -} - -impl<'a, 'env> Env<'a, 'env> { - fn new(base: &'a HashMap) -> Self { - Self { - env: EnvNode::Builtins(base), - prev: None, - arg_level: 0, - let_level: 0, - } - } - - fn enter_let(&'env self, map: &'a Vec) -> Self { - Self { - env: EnvNode::Let(map), - prev: Some(self), - arg_level: self.arg_level, - let_level: self.let_level + 1, - } - } - - fn enter_single_arg(&'env self, ident: EcoString) -> Self { - Self { - env: EnvNode::SingleArg(ident), - prev: Some(self), - arg_level: self.arg_level + 1, - let_level: self.let_level, - } - } - - fn enter_multi_arg( - &'env self, - map: HashMap>, - alias: Option, - ) -> Self { - Self { - env: EnvNode::MultiArg(map, alias), - prev: Some(self), - arg_level: self.arg_level + 1, - let_level: 0, - } - } - - fn enter_with(&'env self) -> Self { - Self { - env: EnvNode::With, - prev: Some(self), - arg_level: self.arg_level, - let_level: self.let_level, - } - } - - fn _lookup( - &self, - ident: &EcoString, - mut arg_level: usize, - mut let_level: usize, - has_with: bool, - ) -> core::result::Result { - use EnvNode::*; - let mut has_with = has_with; - match &self.env { - Builtins(map) => { - return if let Some(ir) = map.get(ident) { - Ok(LookupResult::Builtin(ir.clone())) - } else if has_with { - Ok(LookupResult::With) - } else { - Err(()) - }; - } - Let(map) => { - if let Ok(idx) = map.binary_search(ident) { - return Ok(LookupResult::Let { - level: let_level - 1, - idx, - }); - } else { - let_level -= 1; - } - } - SingleArg(arg) => { - if arg == ident { - return Ok(LookupResult::SingleArg { - level: arg_level - 1, - }); - } else { - arg_level -= 1; - } - } - MultiArg(set, alias) => { - if let Some(default) = set.get(ident) { - return Ok(LookupResult::MultiArg { - level: arg_level - 1, - default: default.clone(), - }); - } else if alias.as_ref() == Some(ident) { - return Ok(LookupResult::SingleArg { - level: arg_level - 1, - }); - } else { - arg_level -= 1; - } - } - With => has_with = true, - } - self.prev - .map(|prev| prev._lookup(ident, arg_level, let_level, has_with)) - .map_or_else(|| unreachable!(), |x| x) - } - - fn lookup(&self, ident: &EcoString) -> core::result::Result { - self._lookup(ident, self.arg_level, self.let_level, false) - } -} - -impl DowngradeContext { - fn new() -> Self { - DowngradeContext { - thunks: Vec::new(), - funcs: Vec::new(), - } - } -} - -pub struct Downgraded { - pub top_level: Ir, - pub thunks: Box<[Ir]>, - pub func_offset: usize, -} - -impl DowngradeContext { - fn new_thunk(&mut self, thunk: Ir) -> Thunk { - let idx = self.thunks.len(); - self.thunks.push(thunk); - Thunk { idx } - } - - fn new_func(&mut self, func: Func) -> LoadFunc { - let idx = self.funcs.len(); - self.funcs.push(func); - LoadFunc { 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( - || unreachable!(), - |func| { - unsafe { - let old = std::ptr::read(func); - std::ptr::write(func, old.resolve(self_ptr.as_mut().unwrap(), env)?); - } - Ok(()) - }, - ) - } - - fn resolve_thunk(&mut self, idx: usize, env: &Env) -> Result<()> { - let self_ptr = self as *mut Self; - self.thunks.get_mut(idx).map_or_else( - || unreachable!(), - |thunk| { - unsafe { - let old = std::ptr::read(thunk); - std::ptr::write(thunk, old.resolve(self_ptr.as_mut().unwrap(), env)?); - } - Ok(()) - }, - ) - } -} +pub struct DynAttr(pub Ir, pub Ir); impl Attrs { fn _insert( @@ -408,7 +231,7 @@ impl Attrs { dyns: Vec::new(), }; attrs._insert(path, name, value, ctx)?; - self.dyns.push(DynamicAttrPair(string.ir(), attrs.ir())); + self.dyns.push(DynAttr(string.ir(), attrs.ir())); Ok(()) } Attr::Dynamic(dynamic) => { @@ -417,7 +240,7 @@ impl Attrs { dyns: Vec::new(), }; attrs._insert(path, name, value, ctx)?; - self.dyns.push(DynamicAttrPair(dynamic, attrs.ir())); + self.dyns.push(DynAttr(dynamic, attrs.ir())); Ok(()) } } @@ -432,10 +255,10 @@ impl Attrs { } } Attr::Strs(string) => { - self.dyns.push(DynamicAttrPair(string.ir(), value)); + self.dyns.push(DynAttr(string.ir(), value)); } Attr::Dynamic(dynamic) => { - self.dyns.push(DynamicAttrPair(dynamic, value)); + self.dyns.push(DynAttr(dynamic, value)); } } Ok(()) @@ -468,6 +291,46 @@ impl Attrs { let name = path.next_back().unwrap().clone(); self._has_attr(path, name) } + + fn _select(&self, mut path: std::slice::Iter, name: Attr) -> Result> { + match path.next() { + Some(Attr::Str(ident)) => self + .stcs + .get(ident) + .and_then(|attrs| attrs.as_ref().try_unwrap_attrs().ok()) + .ok_or_else(|| { + Error::DowngradeError(format!("{} not found", Symbol::from(ident.clone()))) + })? + ._select(path, name), + None => match name { + Attr::Str(ident) => self + .stcs + .get(&ident) + .map(|res| Some(res.clone())) + .map_or_else( + || { + if self.dyns.len() > 0 { + Ok(None) + } else { + Err(Error::DowngradeError(format!( + "{} not found", + Symbol::from(ident.clone()) + ))) + } + }, + Ok, + ), + _ => Ok(None), + }, + _ => Ok(None), + } + } + + pub fn select(&self, path: &[Attr]) -> Result> { + let mut path = path.iter(); + let name = path.next_back().unwrap().clone(); + self._select(path, name) + } } #[derive(Clone, Debug, TryUnwrap)] @@ -478,18 +341,31 @@ pub enum Attr { } impl Attr { - fn resolve<'a, 'env>(self, ctx: &mut DowngradeContext, env: &Env<'a, 'env>) -> Result { + fn resolve<'a, 'env>( + self, + self_idx: usize, + func_idx: Option, + ctx: &mut DowngradeContext, + env: &Env<'a, 'env>, + ) -> Result { use Attr::*; Ok(match self { - Dynamic(ir) => Dynamic(ir.resolve(ctx, env)?), + Dynamic(ir) => Dynamic(ir.resolve(self_idx, func_idx, ctx, env)?), other => other, }) } } impl Thunk { - fn resolve<'a, 'env>(self, ctx: &mut DowngradeContext, env: &Env<'a, 'env>) -> Result { - ctx.resolve_thunk(self.idx, env)?; + fn resolve<'a, 'env>( + self, + self_idx: usize, + func_idx: Option, + ctx: &mut DowngradeContext, + env: &Env<'a, 'env>, + ) -> Result { + ctx.new_dep(self_idx, self.idx); + ctx.resolve_thunk(self.idx, func_idx, env)?; self.ir().ok() } } @@ -574,8 +450,14 @@ pub enum Param { } impl LoadFunc { - fn resolve<'a, 'env>(self, ctx: &mut DowngradeContext, env: &Env<'a, 'env>) -> Result { - ctx.resolve_func(self.idx, env)?; + fn resolve<'a, 'env>( + self, + self_idx: usize, + _: Option, + ctx: &mut DowngradeContext, + env: &Env<'a, 'env>, + ) -> Result { + ctx.resolve_func(self_idx, self.idx, env)?; self.ir().ok() } } @@ -626,10 +508,19 @@ impl Downgrade for ast::Assert { } impl Assert { - fn resolve<'a, 'env>(self, ctx: &mut DowngradeContext, env: &Env<'a, 'env>) -> Result { + fn resolve<'a, 'env>( + self, + self_idx: usize, + func_idx: Option, + ctx: &mut DowngradeContext, + env: &Env<'a, 'env>, + ) -> Result { Self { - assertion: self.assertion.resolve(ctx, env)?.boxed(), - expr: self.expr.resolve(ctx, env)?.boxed(), + assertion: self + .assertion + .resolve(self_idx, func_idx, ctx, env)? + .boxed(), + expr: self.expr.resolve(self_idx, func_idx, ctx, env)?.boxed(), } .ir() .ok() @@ -649,11 +540,17 @@ impl Downgrade for ast::IfElse { } impl If { - fn resolve<'a, 'env>(self, ctx: &mut DowngradeContext, env: &Env<'a, 'env>) -> Result { + fn resolve<'a, 'env>( + self, + self_idx: usize, + func_idx: Option, + ctx: &mut DowngradeContext, + env: &Env<'a, 'env>, + ) -> Result { If { - cond: self.cond.resolve(ctx, env)?.boxed(), - consq: self.consq.resolve(ctx, env)?.boxed(), - alter: self.alter.resolve(ctx, env)?.boxed(), + cond: self.cond.resolve(self_idx, func_idx, ctx, env)?.boxed(), + consq: self.consq.resolve(self_idx, func_idx, ctx, env)?.boxed(), + alter: self.alter.resolve(self_idx, func_idx, ctx, env)?.boxed(), } .ir() .ok() @@ -690,9 +587,15 @@ impl Downgrade for ast::Path { } impl Path { - fn resolve<'a, 'env>(self, ctx: &mut DowngradeContext, env: &Env<'a, 'env>) -> Result { + fn resolve<'a, 'env>( + self, + self_idx: usize, + func_idx: Option, + ctx: &mut DowngradeContext, + env: &Env<'a, 'env>, + ) -> Result { Self { - expr: self.expr.resolve(ctx, env)?.boxed(), + expr: self.expr.resolve(self_idx, func_idx, ctx, env)?.boxed(), } .ir() .ok() @@ -720,12 +623,18 @@ impl Downgrade for ast::Str { } impl ConcatStrings { - fn resolve<'a, 'env>(self, ctx: &mut DowngradeContext, env: &Env<'a, 'env>) -> Result { + fn resolve<'a, 'env>( + self, + self_idx: usize, + func_idx: Option, + ctx: &mut DowngradeContext, + env: &Env<'a, 'env>, + ) -> Result { Self { parts: self .parts .into_iter() - .map(|ir| ir.resolve(ctx, env)) + .map(|ir| ir.resolve(self_idx, func_idx, ctx, env)) .collect::>>()?, } .ir() @@ -748,13 +657,25 @@ impl Downgrade for ast::Literal { } impl Const { - fn resolve<'a, 'env>(self, _: &mut DowngradeContext, _: &Env<'a, 'env>) -> Result { + fn resolve<'a, 'env>( + self, + _: usize, + _: Option, + _: &mut DowngradeContext, + _: &Env<'a, 'env>, + ) -> Result { self.ir().ok() } } impl String { - fn resolve<'a, 'env>(self, _: &mut DowngradeContext, _: &Env<'a, 'env>) -> Result { + fn resolve<'a, 'env>( + self, + _: usize, + _: Option, + _: &mut DowngradeContext, + _: &Env<'a, 'env>, + ) -> Result { self.ir().ok() } } @@ -767,7 +688,13 @@ impl Downgrade for ast::Ident { } impl Var { - fn resolve<'a, 'env>(self, _: &mut DowngradeContext, env: &Env<'a, 'env>) -> Result { + fn resolve<'a, 'env>( + self, + self_idx: usize, + func_idx: Option, + ctx: &mut DowngradeContext, + env: &Env<'a, 'env>, + ) -> Result { use LookupResult::*; let Ok(res) = env.lookup(&self.sym) else { return Err(Error::DowngradeError(format!( @@ -785,6 +712,7 @@ impl Var { default: default.map(Box::new), } .ir(), + MaybeThunk(thunk) => thunk.resolve(self_idx, func_idx, ctx, env)?, With => self.ir(), } .ok() @@ -792,14 +720,26 @@ impl Var { } impl Arg { - fn resolve<'a, 'env>(self, _: &mut DowngradeContext, _: &Env<'a, 'env>) -> Result { - unreachable!() + fn resolve<'a, 'env>( + self, + _: usize, + _: Option, + _: &mut DowngradeContext, + _: &Env<'a, 'env>, + ) -> Result { + self.ir().ok() } } impl LetVar { - fn resolve<'a, 'env>(self, _: &mut DowngradeContext, _: &Env<'a, 'env>) -> Result { - unreachable!() + fn resolve<'a, 'env>( + self, + _: usize, + _: Option, + _: &mut DowngradeContext, + _: &Env<'a, 'env>, + ) -> Result { + self.ir().ok() } } @@ -812,6 +752,7 @@ impl Downgrade for ast::AttrSet { .stcs .into_iter() .sorted_by(|(a, _), (b, _)| a.cmp(b)) + .map(|(k, v)| (k, ctx.maybe_thunk(v))) .collect::>(); let stcs = bindings .iter() @@ -830,18 +771,27 @@ impl Downgrade for ast::AttrSet { } impl Attrs { - fn resolve<'a, 'env>(self, ctx: &mut DowngradeContext, env: &Env<'a, 'env>) -> Result { + fn resolve<'a, 'env>( + self, + self_idx: usize, + func_idx: Option, + ctx: &mut DowngradeContext, + env: &Env<'a, 'env>, + ) -> Result { Self { stcs: self .stcs .into_iter() - .map(|(k, v)| Ok((k, v.resolve(ctx, env)?))) + .map(|(k, v)| Ok((k, v.resolve(self_idx, func_idx, ctx, env)?))) .collect::>()?, dyns: self .dyns .into_iter() - .map(|DynamicAttrPair(k, v)| { - Ok(DynamicAttrPair(k.resolve(ctx, env)?, v.resolve(ctx, env)?)) + .map(|DynAttr(k, v)| { + Ok(DynAttr( + k.resolve(self_idx, func_idx, ctx, env)?, + v.resolve(self_idx, func_idx, ctx, env)?, + )) }) .collect::>()?, } @@ -861,12 +811,18 @@ impl Downgrade for ast::List { } impl List { - fn resolve<'a, 'env>(self, ctx: &mut DowngradeContext, env: &Env<'a, 'env>) -> Result { + fn resolve<'a, 'env>( + self, + self_idx: usize, + func_idx: Option, + ctx: &mut DowngradeContext, + env: &Env<'a, 'env>, + ) -> Result { Self { items: self .items .into_iter() - .map(|item| item.resolve(ctx, env)) + .map(|item| item.resolve(self_idx, func_idx, ctx, env)) .collect::>()?, } .ir() @@ -887,10 +843,16 @@ impl Downgrade for ast::BinOp { } impl BinOp { - fn resolve<'a, 'env>(self, ctx: &mut DowngradeContext, env: &Env<'a, 'env>) -> Result { + fn resolve<'a, 'env>( + self, + self_idx: usize, + func_idx: Option, + ctx: &mut DowngradeContext, + env: &Env<'a, 'env>, + ) -> Result { Self { - lhs: self.lhs.resolve(ctx, env)?.boxed(), - rhs: self.rhs.resolve(ctx, env)?.boxed(), + lhs: self.lhs.resolve(self_idx, func_idx, ctx, env)?.boxed(), + rhs: self.rhs.resolve(self_idx, func_idx, ctx, env)?.boxed(), ..self } .ir() @@ -912,13 +874,19 @@ impl Downgrade for ast::HasAttr { } impl HasAttr { - fn resolve<'a, 'env>(self, ctx: &mut DowngradeContext, env: &Env<'a, 'env>) -> Result { + fn resolve<'a, 'env>( + self, + self_idx: usize, + func_idx: Option, + ctx: &mut DowngradeContext, + env: &Env<'a, 'env>, + ) -> Result { Self { - lhs: self.lhs.resolve(ctx, env)?.boxed(), + lhs: self.lhs.resolve(self_idx, func_idx, ctx, env)?.boxed(), rhs: self .rhs .into_iter() - .map(|attr| attr.resolve(ctx, env)) + .map(|attr| attr.resolve(self_idx, func_idx, ctx, env)) .collect::>()?, } .ir() @@ -938,9 +906,15 @@ impl Downgrade for ast::UnaryOp { } impl UnOp { - fn resolve<'a, 'env>(self, ctx: &mut DowngradeContext, env: &Env<'a, 'env>) -> Result { + fn resolve<'a, 'env>( + self, + self_idx: usize, + func_idx: Option, + ctx: &mut DowngradeContext, + env: &Env<'a, 'env>, + ) -> Result { Self { - rhs: self.rhs.resolve(ctx, env)?.boxed(), + rhs: self.rhs.resolve(self_idx, func_idx, ctx, env)?.boxed(), ..self } .ir() @@ -964,23 +938,57 @@ impl Downgrade for ast::Select { } impl Select { - fn resolve<'a, 'env>(self, ctx: &mut DowngradeContext, env: &Env<'a, 'env>) -> Result { - let default = if let Some(default) = self.default { - Some(default.resolve(ctx, env)?.boxed()) - } else { - None + fn resolve<'a, 'env>( + self, + self_idx: usize, + func_idx: Option, + ctx: &mut DowngradeContext, + env: &Env<'a, 'env>, + ) -> Result { + let expr = self.expr.resolve(self_idx, func_idx, ctx, env)?; + let attrpath = self + .attrpath + .into_iter() + .map(|attr| attr.resolve(self_idx, func_idx, ctx, env)) + .collect::>>()?; + let res = match &expr { + Ir::Attrs(attrs) => attrs.select(&attrpath)?, + &Ir::Thunk(Thunk { idx }) => { + let res = ctx.thunks[idx] + .as_ref() + .try_unwrap_attrs() + .map_err(|_| { + Error::DowngradeError("can only select from a attribute set".into()) + })? + .select(&attrpath); + match res { + Err(err) => { + if let Some(default) = self.default.clone() { + Ok(Some(default.resolve(self_idx, func_idx, ctx, env)?)) + } else { + Err(err) + } + } + ok => ok, + }? + } + _ => return Err(Error::DowngradeError("can not select from ".into())), }; - Self { - expr: self.expr.resolve(ctx, env)?.boxed(), - attrpath: self - .attrpath - .into_iter() - .map(|attr| attr.resolve(ctx, env)) - .collect::>()?, - default, + if let Some(res) = res { + res.ok() + } else { + Select { + expr: expr.boxed(), + attrpath, + default: if let Some(default) = self.default { + Some(default.resolve(self_idx, func_idx, ctx, env)?.boxed()) + } else { + None + }, + } + .ir() + .ok() } - .ir() - .ok() } } @@ -1058,35 +1066,26 @@ impl Downgrade for ast::LetIn { let bindings = bindings .into_iter() .sorted_by(|(a, _), (b, _)| a.cmp(b)) + .map(|(k, v)| (k, ctx.maybe_thunk(v))) .collect(); Let { bindings, expr }.ir().ok() } } impl Let { - fn resolve<'a, 'env>(self, ctx: &mut DowngradeContext, env: &Env<'a, 'env>) -> Result { + fn resolve<'a, 'env>( + self, + self_idx: usize, + func_idx: Option, + ctx: &mut DowngradeContext, + env: &Env<'a, 'env>, + ) -> Result { let map = self .bindings - .iter() - .map(|(sym, _)| sym.clone()) - .sorted() - .collect(); + .clone(); let env = env.enter_let(&map); - let bindings = self - .bindings - .into_iter() - .map(|(k, v)| { - Ok(( - k, - match v.resolve(ctx, &env)? { - ir @ Ir::Const(_) => ir, - ir => ctx.new_thunk(ir).ir(), - }, - )) - }) - .collect::>()?; - let expr = self.expr.resolve(ctx, &env)?.boxed(); - Self { bindings, expr }.ir().ok() + let expr = self.expr.resolve(self_idx, func_idx, ctx, &env)?.boxed(); + Self { expr, ..self }.ir().ok() } } @@ -1099,9 +1098,21 @@ impl Downgrade for ast::With { } impl With { - fn resolve<'a, 'env>(self, ctx: &mut DowngradeContext, env: &Env<'a, 'env>) -> Result { - let namespace = self.namespace.resolve(ctx, env)?.boxed(); - let expr = self.expr.resolve(ctx, &env.enter_with())?.boxed(); + fn resolve<'a, 'env>( + self, + self_idx: usize, + func_idx: Option, + ctx: &mut DowngradeContext, + env: &Env<'a, 'env>, + ) -> Result { + let namespace = self + .namespace + .resolve(self_idx, func_idx, ctx, env)? + .boxed(); + let expr = self + .expr + .resolve(self_idx, func_idx, ctx, &env.enter_with())? + .boxed(); Self { namespace, expr }.ir().ok() } } @@ -1116,7 +1127,13 @@ impl Downgrade for ast::Lambda { } impl Func { - fn resolve<'a, 'env>(self, ctx: &mut DowngradeContext, env: &Env<'a, 'env>) -> Result { + fn resolve<'a, 'env>( + self, + self_idx: usize, + func_idx: Option, + ctx: &mut DowngradeContext, + env: &Env<'a, 'env>, + ) -> Result { let env = match self.param.clone() { Param::Ident(ident) => env.enter_single_arg(ident), Param::Formals { formals, alias, .. } => env.enter_multi_arg( @@ -1128,7 +1145,7 @@ impl Func { alias, ), }; - let body = self.body.resolve(ctx, &env)?.boxed(); + let body = self.body.resolve(self_idx, func_idx, ctx, &env)?.boxed(); Ok(Self { body, ..self }) } } @@ -1148,13 +1165,19 @@ impl Downgrade for ast::Apply { } impl Call { - fn resolve<'a, 'env>(self, ctx: &mut DowngradeContext, env: &Env<'a, 'env>) -> Result { + fn resolve<'a, 'env>( + self, + self_idx: usize, + func_idx: Option, + ctx: &mut DowngradeContext, + env: &Env<'a, 'env>, + ) -> Result { Self { - func: self.func.resolve(ctx, env)?.boxed(), + func: self.func.resolve(self_idx, func_idx, ctx, env)?.boxed(), args: self .args .into_iter() - .map(|arg| arg.resolve(ctx, env)) + .map(|arg| arg.resolve(self_idx, func_idx, ctx, env)) .collect::>()?, } .ir() diff --git a/src/ir/scc.rs b/src/ir/scc.rs new file mode 100644 index 0000000..4df5cd3 --- /dev/null +++ b/src/ir/scc.rs @@ -0,0 +1,169 @@ +use hashbrown::{HashMap, HashSet}; + +use super::*; + +#[derive(Debug, Clone)] +pub struct SccNode { + id: usize, + members: Vec, + deps: HashSet, +} + +#[derive(Default, Debug)] +pub struct SccGraph { + nodes: HashMap, +} + +impl SccGraph { + fn from(ctx: &DowngradeContext, sccs: Vec>) -> 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(), + }, + ); + } + + for (from_node_id, from_deps) in ctx.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 { + let mut in_degrees: HashMap = self.nodes.keys().map(|&id| (id, 0)).collect(); + for node in self.nodes.values() { + in_degrees.insert(node.id, node.deps.len()); + } + + let mut reverse_adj: HashMap> = HashMap::new(); + for (node_id, node) in &self.nodes { + for &dep_id in &node.deps { + reverse_adj.entry(dep_id).or_default().push(*node_id); + } + } + + let mut queue: std::collections::VecDeque = in_degrees + .iter() + .filter_map(|(&id, °)| if deg == 0 { Some(id) } else { None }) + .collect(); + + queue.make_contiguous().sort(); + + let mut sorted_order = Vec::new(); + while let Some(u) = queue.pop_front() { + sorted_order.push(self.nodes[&u].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() != self.nodes.len() { + panic!("Cycle detected in SCC graph, which is impossible!"); + } + + sorted_order + } +} + +pub struct SccAnalyzer<'ctx> { + ctx: &'ctx DowngradeContext, + index: usize, + stack: Vec, + on_stack: HashSet, + indices: HashMap, + low_links: HashMap, + sccs: Vec>, +} + +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 { + for idx in 0..self.ctx.thunks.len() { + if !self.indices.contains_key(&idx) { + self.strong_connect(idx); + } + } + SccGraph::from(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); + + if let Some(deps) = self.ctx.deps.get(v_id) { + 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); + } + } +} diff --git a/src/ty/common.rs b/src/ty/common.rs index 524a4ad..b42ada3 100644 --- a/src/ty/common.rs +++ b/src/ty/common.rs @@ -21,7 +21,7 @@ impl Display for Catchable { } } -#[derive(Debug, Clone, IsVariant, Unwrap)] +#[derive(Debug, Clone, Copy, IsVariant, Unwrap)] pub enum Const { Bool(bool), Int(i64), @@ -87,8 +87,3 @@ impl PartialEq for Const { } impl Eq for Const {} - -pub enum MaybeThunk { - Thunk(usize), - Const(Const), -}