use std::collections::{HashMap, HashSet};
use crate::common::{BoxError, Config};
use crate::optimizer::Optimizer;
use crate::parser::Program;
use crate::planner::StratumPlanner;
use crate::profiler::Profiler;
use crate::stratifier::Stratifier;
#[derive(Debug)]
pub struct ProgramPlanner {
strata: Vec<StratumPlanner>,
}
impl ProgramPlanner {
pub fn from_program(
config: &Config,
program: &Program,
profiler: &mut Option<Profiler>,
) -> Result<Self, BoxError> {
let stratifier = Stratifier::from_program(program, config.is_extended())?;
let mut optimizer = Optimizer::new();
let mut strata: Vec<StratumPlanner> = stratifier
.stratum()
.iter()
.enumerate()
.map(|(idx, rule_refs)| {
let rules: Vec<_> = rule_refs.iter().copied().cloned().collect();
StratumPlanner::from_rules(
config,
&rules,
&mut optimizer,
profiler,
&stratifier,
idx,
)
.map_err(BoxError::from)
})
.collect::<Result<_, _>>()?;
prune_cross_stratum_duplicates(&mut strata);
Ok(Self { strata })
}
pub fn strata(&self) -> &[StratumPlanner] {
&self.strata
}
}
fn prune_cross_stratum_duplicates(strata: &mut [StratumPlanner]) {
let mut idb_writes: HashMap<u64, Vec<usize>> = HashMap::new();
for (idx, stratum) in strata.iter().enumerate() {
for fp in stratum.idb_to_heads_map().keys() {
idb_writes.entry(*fp).or_default().push(idx);
}
}
let mut idb_deps: HashMap<u64, HashSet<u64>> = idb_writes
.keys()
.map(|&fp| (fp, HashSet::from([fp])))
.collect();
let mut emitted_at: HashMap<u64, usize> = HashMap::new();
for (idx, stratum) in strata.iter_mut().enumerate() {
stratum.retain_non_recursive_transformations(|t| {
let fp = t.output().fingerprint();
let t_deps: HashSet<u64> = t
.input_fingerprints()
.into_iter()
.filter_map(|f| idb_deps.get(&f))
.flatten()
.copied()
.collect();
idb_deps.entry(fp).or_default().extend(&t_deps);
let keep = match emitted_at.get(&fp) {
None => true,
Some(&prev) => t_deps
.iter()
.any(|idb| idb_writes[idb].iter().any(|&k| k > prev && k <= idx)),
};
if keep {
emitted_at.insert(fp, idx);
}
keep
});
}
}
#[cfg(test)]
mod tests {
use super::*;
use std::io::Write;
use crate::common::SourceMap;
fn analyze(src: &str) -> ProgramPlanner {
let mut tmp = tempfile::NamedTempFile::new().expect("tempfile");
tmp.write_all(src.as_bytes()).expect("write");
let mut sm = SourceMap::new();
let mut program =
Program::parse(&tmp.path().to_string_lossy(), false, &mut sm).expect("parse");
crate::typechecker::check_program(&mut program, &Config::default()).expect("typecheck");
ProgramPlanner::from_program(&Config::default(), &program, &mut None).expect("plan")
}
const DYCK_SRC: &str = "\
.decl Arc(x: int32, y: int32, l: int32)\n\
.input Arc(IO=\"file\", filename=\"Arc.csv\", delimiter=\",\")\n\
.decl Zero(x: int32, y: int32)\n\
.printsize Zero\n\
.decl One(x: int32, y: int32)\n\
.printsize One\n\
.decl Dyck(x: int32, y: int32)\n\
.printsize Dyck\n\
Zero(x, y) :- Arc(x, y, 0).\n\
One(x, y) :- Arc(x, y, 1).\n\
Dyck(x, y) :- Zero(x, z), Zero(z, y).\n\
Dyck(x, y) :- One(x, z), One(z, y).\n\
Dyck(x, y) :- Zero(x, z), Dyck(z, w), Zero(w, y).\n\
Dyck(x, y) :- One(x, z), Dyck(z, w), One(w, y).\n\
Dyck(x, y) :- Dyck(x, z), Dyck(z, y).\n";
#[test]
fn dyck_prune_collapses_cross_stratum_duplicates() {
let pp = analyze(DYCK_SRC);
assert_eq!(pp.strata().len(), 3, "dyck should stratify into 3 strata");
let mut owner: HashMap<u64, usize> = HashMap::new();
for (idx, stratum) in pp.strata().iter().enumerate() {
for t in stratum.non_recursive_transformations() {
let fp = t.output().fingerprint();
if let Some(prev) = owner.insert(fp, idx) {
panic!("fp 0x{fp:016x} survives in both stratum {prev} and stratum {idx}");
}
}
}
assert_eq!(
owner.len(),
8,
"expected 8 non-recursive transformations after prune"
);
}
}