feat: SCC analysis (WIP)

This commit is contained in:
2025-06-15 17:23:32 +08:00
parent 7b6db44207
commit b2d2490327
13 changed files with 842 additions and 352 deletions

234
src/ir/ctx.rs Normal file
View File

@@ -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<Ir>,
pub deps: Vec<HashSet<usize>>,
pub funcs: Vec<Func>,
}
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<EcoString, Ir>),
Let(&'a Vec<(EcoString, MaybeThunk)>),
SingleArg(EcoString),
MultiArg(HashMap<EcoString, Option<Ir>>, Option<EcoString>),
With,
}
pub enum LookupResult {
Builtin(Ir),
MaybeThunk(MaybeThunk),
Let { level: usize, idx: usize },
SingleArg { level: usize },
MultiArg { level: usize, default: Option<Ir> },
With,
}
impl<'a, 'env> Env<'a, 'env> {
pub fn new(base: &'a HashMap<EcoString, Ir>) -> 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<EcoString, Option<Ir>>,
alias: Option<EcoString>,
) -> 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<LookupResult, ()> {
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<LookupResult, ()> {
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<usize>, 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(),
}
}
}

View File

@@ -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<Downgraded> {
pub use ctx::{DowngradeContext, Downgraded};
pub use scc::*;
pub fn downgrade(expr: Expr) -> Result<(Downgraded, Vec<SccNode>)> {
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<Ir> {
fn resolve<'a, 'env>(self, self_idx: usize, func_idx: Option<usize>, ctx: &mut DowngradeContext, env: &Env<'a, 'env>) -> Result<Ir> {
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<EcoString, Ir>, dyns: Vec<DynamicAttrPair> },
Attrs => { stcs: HashMap<EcoString, Ir>, dyns: Vec<DynAttr> },
List => { items: Vec<Ir> },
HasAttr => { lhs: Box<Ir>, rhs: Vec<Attr> },
BinOp => { lhs: Box<Ir>, rhs: Box<Ir>, kind: BinOpKind },
@@ -137,10 +137,11 @@ ir! {
LoadFunc => { idx: usize },
Call => { func: Box<Ir>, args: Vec<Ir> },
Let => { bindings: Vec<(EcoString, Ir)>, expr: Box<Ir> },
Let => { bindings: Vec<(EcoString, MaybeThunk)>, expr: Box<Ir> },
With => { namespace: Box<Ir>, expr: Box<Ir> },
Assert => { assertion: Box<Ir>, expr: Box<Ir> },
ConcatStrings => { parts: Vec<Ir> },
#[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<usize>,
ctx: &mut DowngradeContext,
env: &Env<'a, 'env>,
) -> Result<Ir> {
match self {
MaybeThunk::Thunk(thunk) => thunk.resolve(self_idx, func_idx, ctx, env),
MaybeThunk::Const(cnst) => cnst.ir().ok()
}
}
}
impl<T: Into<c::Const>> From<T> for Const {
fn from(value: T) -> Self {
Const { val: value.into() }
@@ -172,206 +194,7 @@ impl<T: Into<c::Const>> From<T> for Const {
}
#[derive(Clone, Debug)]
pub struct DynamicAttrPair(pub Ir, pub Ir);
pub struct DowngradeContext {
thunks: Vec<Ir>,
funcs: Vec<Func>,
}
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<EcoString, Ir>),
Let(&'a Vec<EcoString>),
SingleArg(EcoString),
MultiArg(HashMap<EcoString, Option<Ir>>, Option<EcoString>),
With,
}
enum LookupResult {
Builtin(Ir),
Let { level: usize, idx: usize },
SingleArg { level: usize },
MultiArg { level: usize, default: Option<Ir> },
With,
}
impl<'a, 'env> Env<'a, 'env> {
fn new(base: &'a HashMap<EcoString, Ir>) -> Self {
Self {
env: EnvNode::Builtins(base),
prev: None,
arg_level: 0,
let_level: 0,
}
}
fn enter_let(&'env self, map: &'a Vec<EcoString>) -> 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<EcoString, Option<Ir>>,
alias: Option<EcoString>,
) -> 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<LookupResult, ()> {
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<LookupResult, ()> {
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<Attr>, name: Attr) -> Result<Option<Ir>> {
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<Option<Ir>> {
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<Attr> {
fn resolve<'a, 'env>(
self,
self_idx: usize,
func_idx: Option<usize>,
ctx: &mut DowngradeContext,
env: &Env<'a, 'env>,
) -> Result<Attr> {
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<Ir> {
ctx.resolve_thunk(self.idx, env)?;
fn resolve<'a, 'env>(
self,
self_idx: usize,
func_idx: Option<usize>,
ctx: &mut DowngradeContext,
env: &Env<'a, 'env>,
) -> Result<Ir> {
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<Ir> {
ctx.resolve_func(self.idx, env)?;
fn resolve<'a, 'env>(
self,
self_idx: usize,
_: Option<usize>,
ctx: &mut DowngradeContext,
env: &Env<'a, 'env>,
) -> Result<Ir> {
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<Ir> {
fn resolve<'a, 'env>(
self,
self_idx: usize,
func_idx: Option<usize>,
ctx: &mut DowngradeContext,
env: &Env<'a, 'env>,
) -> Result<Ir> {
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<Ir> {
fn resolve<'a, 'env>(
self,
self_idx: usize,
func_idx: Option<usize>,
ctx: &mut DowngradeContext,
env: &Env<'a, 'env>,
) -> Result<Ir> {
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<Ir> {
fn resolve<'a, 'env>(
self,
self_idx: usize,
func_idx: Option<usize>,
ctx: &mut DowngradeContext,
env: &Env<'a, 'env>,
) -> Result<Ir> {
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<Ir> {
fn resolve<'a, 'env>(
self,
self_idx: usize,
func_idx: Option<usize>,
ctx: &mut DowngradeContext,
env: &Env<'a, 'env>,
) -> Result<Ir> {
Self {
parts: self
.parts
.into_iter()
.map(|ir| ir.resolve(ctx, env))
.map(|ir| ir.resolve(self_idx, func_idx, ctx, env))
.collect::<Result<Vec<_>>>()?,
}
.ir()
@@ -748,13 +657,25 @@ impl Downgrade for ast::Literal {
}
impl Const {
fn resolve<'a, 'env>(self, _: &mut DowngradeContext, _: &Env<'a, 'env>) -> Result<Ir> {
fn resolve<'a, 'env>(
self,
_: usize,
_: Option<usize>,
_: &mut DowngradeContext,
_: &Env<'a, 'env>,
) -> Result<Ir> {
self.ir().ok()
}
}
impl String {
fn resolve<'a, 'env>(self, _: &mut DowngradeContext, _: &Env<'a, 'env>) -> Result<Ir> {
fn resolve<'a, 'env>(
self,
_: usize,
_: Option<usize>,
_: &mut DowngradeContext,
_: &Env<'a, 'env>,
) -> Result<Ir> {
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<Ir> {
fn resolve<'a, 'env>(
self,
self_idx: usize,
func_idx: Option<usize>,
ctx: &mut DowngradeContext,
env: &Env<'a, 'env>,
) -> Result<Ir> {
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<Ir> {
unreachable!()
fn resolve<'a, 'env>(
self,
_: usize,
_: Option<usize>,
_: &mut DowngradeContext,
_: &Env<'a, 'env>,
) -> Result<Ir> {
self.ir().ok()
}
}
impl LetVar {
fn resolve<'a, 'env>(self, _: &mut DowngradeContext, _: &Env<'a, 'env>) -> Result<Ir> {
unreachable!()
fn resolve<'a, 'env>(
self,
_: usize,
_: Option<usize>,
_: &mut DowngradeContext,
_: &Env<'a, 'env>,
) -> Result<Ir> {
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::<Vec<_>>();
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<Ir> {
fn resolve<'a, 'env>(
self,
self_idx: usize,
func_idx: Option<usize>,
ctx: &mut DowngradeContext,
env: &Env<'a, 'env>,
) -> Result<Ir> {
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::<Result<_>>()?,
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::<Result<_>>()?,
}
@@ -861,12 +811,18 @@ impl Downgrade for ast::List {
}
impl List {
fn resolve<'a, 'env>(self, ctx: &mut DowngradeContext, env: &Env<'a, 'env>) -> Result<Ir> {
fn resolve<'a, 'env>(
self,
self_idx: usize,
func_idx: Option<usize>,
ctx: &mut DowngradeContext,
env: &Env<'a, 'env>,
) -> Result<Ir> {
Self {
items: self
.items
.into_iter()
.map(|item| item.resolve(ctx, env))
.map(|item| item.resolve(self_idx, func_idx, ctx, env))
.collect::<Result<_>>()?,
}
.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<Ir> {
fn resolve<'a, 'env>(
self,
self_idx: usize,
func_idx: Option<usize>,
ctx: &mut DowngradeContext,
env: &Env<'a, 'env>,
) -> Result<Ir> {
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<Ir> {
fn resolve<'a, 'env>(
self,
self_idx: usize,
func_idx: Option<usize>,
ctx: &mut DowngradeContext,
env: &Env<'a, 'env>,
) -> Result<Ir> {
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::<Result<_>>()?,
}
.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<Ir> {
fn resolve<'a, 'env>(
self,
self_idx: usize,
func_idx: Option<usize>,
ctx: &mut DowngradeContext,
env: &Env<'a, 'env>,
) -> Result<Ir> {
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<Ir> {
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<usize>,
ctx: &mut DowngradeContext,
env: &Env<'a, 'env>,
) -> Result<Ir> {
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::<Result<Vec<_>>>()?;
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 <type>".into())),
};
Self {
expr: self.expr.resolve(ctx, env)?.boxed(),
attrpath: self
.attrpath
.into_iter()
.map(|attr| attr.resolve(ctx, env))
.collect::<Result<_>>()?,
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<Ir> {
fn resolve<'a, 'env>(
self,
self_idx: usize,
func_idx: Option<usize>,
ctx: &mut DowngradeContext,
env: &Env<'a, 'env>,
) -> Result<Ir> {
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::<Result<_>>()?;
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<Ir> {
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<usize>,
ctx: &mut DowngradeContext,
env: &Env<'a, 'env>,
) -> Result<Ir> {
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<Func> {
fn resolve<'a, 'env>(
self,
self_idx: usize,
func_idx: Option<usize>,
ctx: &mut DowngradeContext,
env: &Env<'a, 'env>,
) -> Result<Func> {
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<Ir> {
fn resolve<'a, 'env>(
self,
self_idx: usize,
func_idx: Option<usize>,
ctx: &mut DowngradeContext,
env: &Env<'a, 'env>,
) -> Result<Ir> {
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::<Result<_>>()?,
}
.ir()

169
src/ir/scc.rs Normal file
View File

@@ -0,0 +1,169 @@
use hashbrown::{HashMap, HashSet};
use super::*;
#[derive(Debug, Clone)]
pub struct SccNode {
id: usize,
members: Vec<usize>,
deps: HashSet<usize>,
}
#[derive(Default, Debug)]
pub struct SccGraph {
nodes: HashMap<usize, SccNode>,
}
impl SccGraph {
fn from(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(),
},
);
}
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<SccNode> {
let mut in_degrees: HashMap<usize, usize> = 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<usize, Vec<usize>> = 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<usize> = in_degrees
.iter()
.filter_map(|(&id, &deg)| 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<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::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);
}
}
}