diff --git a/Cargo.toml b/Cargo.toml index 4791ec06..d1b01ab8 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -20,12 +20,12 @@ parking_lot = "0.12.0" colorful = "0.2.1" rustc-hash = "1.1.0" # lambdas = "0.2.0" -lambdas = { git = "https://github.com/mlb2251/lambdas", rev = "2c9bfd0"} +# lambdas = { git = "https://github.com/mlb2251/lambdas", rev = "2c9bfd0"} test-case = "3.3.1" # [patch.crates-io] -# lambdas = { path = "../lambdas"} +lambdas = { path = "../lambdas"} # enable for flamegraphs diff --git a/src/compression.rs b/src/compression.rs index 26285ea4..906580ff 100644 --- a/src/compression.rs +++ b/src/compression.rs @@ -564,7 +564,7 @@ impl Pattern { /// the index of the empty zipper `[]` in the list of zippers -const EMPTY_ZID: ZId = 0; +pub const EMPTY_ZID: ZId = 0; /// an argument to an abstraction. #[derive(Debug, Clone, PartialEq, Eq, Hash)] @@ -966,13 +966,13 @@ fn stitch_search( if should_prune_single_task(&shared, &locs) { if !shared.cfg.no_stats { shared.stats.lock().deref_mut().single_task_fired += 1; } - if tracked && !shared.cfg.quiet { println!("{} single task pruned when expanding {} to {}", "[TRACK]".red().bold(), original_pattern.to_expr(&shared), zipper_replace(original_pattern.to_expr(&shared), &shared.zippers.zip_of_zid[hole_zid], Node::Prim(format!("<{expands_to}>").into()))) } + if tracked && !shared.cfg.quiet { println!("{} single task pruned when expanding {} to {}", "[TRACK]".red().bold(), original_pattern.to_expr(&shared), zipper_replace(original_pattern.to_expr(&shared), &shared.zippers.zip_of_zid[hole_zid], Node::Prim(format!("<{expands_to}>").into()), &shared.zippers)) } continue 'expansion; } // Pruning (FREE VARS): if an invention has free variables in the body then it's not a real function and we can discard it // Here we just check if our expansion just yielded a variable, and if that is bound based on how many lambdas there are above it. - if expands_to.free_variable(shared.zippers.zip_of_zid[hole_zid].depth_root_to_arg()) { + if expands_to.free_variable(shared.zippers.zip_of_zid[hole_zid].depth_root_to_arg(&shared.zippers)) { if !shared.cfg.no_stats { shared.stats.lock().deref_mut().free_vars_fired += 1; }; if tracked && !shared.cfg.quiet { println!("{} pruned by free var in body when expanding {} to {}", "[TRACK]".red().bold(), original_pattern.to_expr(&shared), original_pattern.show_track_expansion(hole_zid, &shared)) } continue 'expansion; // free var @@ -1539,7 +1539,7 @@ pub fn inverse_delta(cost_once: Cost, usages: Cost, arg_uses: usize, cost_fn: &E // (not used in popl code - experimental; always exists at the first return statement unless --inv-arg-cap is turned on) #[allow(clippy::too_many_arguments)] -pub fn inverse_argument_capture(finished: &mut FinishedPattern, cfg: &CompressionStepConfig, zippers: &Zippers, extensions_of_zid: &[ZIdExtension], set: &ExprSet, analyzed_ivars: &AnalyzedExpr, cost_fn: &ExprCost) { +pub fn inverse_argument_capture(finished: &mut FinishedPattern, cfg: &CompressionStepConfig, zippers: &mut Zippers, extensions_of_zid: &[ZIdExtension], set: &ExprSet, analyzed_ivars: &AnalyzedExpr, cost_fn: &ExprCost) { if !cfg.inv_arg_cap || cfg.no_other_util { return } @@ -1591,7 +1591,7 @@ fn possible_to_uninline(counts: FxHashMap)>, finished_usa } /// not used in popl code - experimental -fn use_counts(pattern: &Pattern, zippers: &Zippers, extensions_of_zid: &[ZIdExtension], set: &ExprSet, analyzed_ivars: &AnalyzedExpr) -> FxHashMap)> { +fn use_counts(pattern: &Pattern, zippers: &mut Zippers, extensions_of_zid: &[ZIdExtension], set: &ExprSet, analyzed_ivars: &AnalyzedExpr) -> FxHashMap)> { let mut curr_zip: Zipper = Zipper::default(); let curr_zid: ZId = EMPTY_ZID; let zids = &pattern.pattern_args.iterate_arguments().cloned().collect::>(); @@ -1603,14 +1603,14 @@ fn use_counts(pattern: &Pattern, zippers: &Zippers, extensions_of_zid: &[ZIdExte let mut counts: FxHashMap)> = Default::default(); #[allow(clippy::too_many_arguments)] - fn helper(curr_node: Idx, match_loc: Idx, curr_zip: &mut Zipper, curr_zid: ZId, zips: &[Zipper], zids: &[LabelledZId], arg_of_zid_node: &[FxHashMap], extensions_of_zid: &[ZIdExtension], set: &ExprSet, counts: &mut FxHashMap)>, analyzed_ivars: &AnalyzedExpr) { + fn helper(curr_node: Idx, match_loc: Idx, curr_zip: &mut Zipper, curr_zid: ZId, zips: &[Zipper], zids: &[LabelledZId], extensions_of_zid: &[ZIdExtension], set: &ExprSet, counts: &mut FxHashMap)>, analyzed_ivars: &AnalyzedExpr, zippers: &mut Zippers) { if zids.iter().any(|labelled| labelled.zid == curr_zid){ return // current zip matches an arg } // if curr_zip is not a prefix of any arg zipper, then increment its count - if zips.iter().all(|zip| !zip.starts_with(curr_zip)) { + if zips.iter().all(|zip| !zip.starts_with(curr_zip, zippers)) { // also make sure its valid ie doesnt have any free ivars as ew do during normal checks - let arg = arg_of_zid_node[curr_zid].get(&match_loc).unwrap(); + let arg = zippers.arg_of_zid_node[curr_zid].get(&match_loc).unwrap(); if analyzed_ivars[arg.shifted_id].is_empty() { counts.entry(arg.shifted_id) .or_insert_with(||(arg.cost as Cost, vec![])) @@ -1621,26 +1621,26 @@ fn use_counts(pattern: &Pattern, zippers: &Zippers, extensions_of_zid: &[ZIdExte Node::Prim(_) => {}, Node::Var(_, _) => {}, Node::Lam(b, _) => { - curr_zip.add_to_end(ZNode::Body); + curr_zip.add_to_end(ZNode::Body, zippers); let new_zid = extensions_of_zid[curr_zid].body.unwrap(); - helper(*b, match_loc, curr_zip, new_zid, zips, zids, arg_of_zid_node, extensions_of_zid, set, counts, analyzed_ivars); - curr_zip.remove_from_end(); + helper(*b, match_loc, curr_zip, new_zid, zips, zids, extensions_of_zid, set, counts, analyzed_ivars, zippers); + curr_zip.remove_from_end(zippers); } Node::App(f,x) => { - curr_zip.add_to_end(ZNode::Func); + curr_zip.add_to_end(ZNode::Func, zippers); let new_zid = extensions_of_zid[curr_zid].func.unwrap(); - helper(*f, match_loc, curr_zip, new_zid, zips, zids, arg_of_zid_node, extensions_of_zid, set, counts, analyzed_ivars); - curr_zip.remove_from_end(); - curr_zip.add_to_end(ZNode::Arg); + helper(*f, match_loc, curr_zip, new_zid, zips, zids, extensions_of_zid, set, counts, analyzed_ivars, zippers); + curr_zip.remove_from_end(zippers); + curr_zip.add_to_end(ZNode::Arg, zippers); let new_zid = extensions_of_zid[curr_zid].arg.unwrap(); - helper(*x, match_loc, curr_zip, new_zid, zips, zids, arg_of_zid_node, extensions_of_zid, set, counts, analyzed_ivars); - curr_zip.remove_from_end(); + helper(*x, match_loc, curr_zip, new_zid, zips, zids, extensions_of_zid, set, counts, analyzed_ivars, zippers); + curr_zip.remove_from_end(zippers); } _ => unreachable!(), } } // we can pick any match location - helper(pattern.match_locations[0], pattern.match_locations[0], &mut curr_zip, curr_zid, &zips, zids, &zippers.arg_of_zid_node, extensions_of_zid, set, &mut counts, analyzed_ivars); + helper(pattern.match_locations[0], pattern.match_locations[0], &mut curr_zip, curr_zid, &zips, zids, extensions_of_zid, set, &mut counts, analyzed_ivars, zippers); counts } @@ -1816,7 +1816,7 @@ pub fn construct_shared( if !cfg.quiet { println!("cost_of_node structs: {:?}ms", tstart.elapsed().as_millis()) } tstart = std::time::Instant::now(); - let (zippers, + let (mut zippers, zids_of_node, extensions_of_zid) = get_zippers(&corpus_span, &analyzed_cost, &mut set, &mut analyzed_free_vars); @@ -1941,7 +1941,7 @@ pub fn construct_shared( }; // This handle the case covered by Appendix B in the paper - inverse_argument_capture(&mut finished_pattern, cfg, &zippers, &extensions_of_zid, &set, &analyzed_ivars, cost_fn); + inverse_argument_capture(&mut finished_pattern, cfg, &mut zippers, &extensions_of_zid, &set, &analyzed_ivars, cost_fn); if !cfg.no_stats { stats.azero_calc_unargcap += 1; }; // Pruning (UPPER BOUND): This is the full upper bound pruning diff --git a/src/expansion.rs b/src/expansion.rs index 6a825d08..52c01daf 100644 --- a/src/expansion.rs +++ b/src/expansion.rs @@ -134,7 +134,7 @@ impl std::fmt::Display for ExpandsTo { pub fn tracked_expands_to(pattern: &Pattern, hole_zid: ZId, shared: &SharedData) -> ExpandsTo { // apply the hole zipper to the original expr being tracked to get the subtree // this will expand into, then get the ExpandsTo of that - let idx = shared.zippers.zip_of_zid[hole_zid].zip(&shared.tracking.as_ref().unwrap().expr); + let idx = shared.zippers.zip_of_zid[hole_zid].zip(&shared.tracking.as_ref().unwrap().expr, &shared.zippers); match expands_to_of_node(&shared.tracking.as_ref().unwrap().expr.set[idx]) { ExpandsTo(ExpandsToInner::IVar(i, VariableType::Metavar)) => { ExpandsTo(ExpandsToInner::IVar(pattern.pattern_args.find_variable(shared, i as usize) as i32, VariableType::Metavar)) @@ -176,7 +176,7 @@ pub fn get_ivars_expansions(original_pattern: &Pattern, arg_of_loc: &FxHashMap (V } -pub fn zipper_replace(mut expr: ExprOwned, zipper: &Zipper, new: Node) -> ExprOwned { - let idx = zipper.zip(&expr); +pub fn zipper_replace(mut expr: ExprOwned, zipper: &Zipper, new: Node, zippers: &Zippers) -> ExprOwned { + let idx = zipper.zip(&expr, zippers); *expr.as_mut().get_node_mut(idx) = new; expr } \ No newline at end of file diff --git a/src/zipper.rs b/src/zipper.rs index 18f775b1..dd3073fc 100644 --- a/src/zipper.rs +++ b/src/zipper.rs @@ -1,57 +1,125 @@ -use lambdas::{AnalyzedExpr, ExprOwned, ExprSet, FreeVarAnalysis, Idx}; +use lambdas::{AnalyzedExpr, Expr, ExprOwned, ExprSet, FreeVarAnalysis, Idx}; pub use lambdas::{ZNode, ZId, LabelledZId}; use rustc_hash::FxHashMap; -use crate::{insert_arg_ivars, Arg, Cost, ExpandsTo, ZIdExtension}; +use crate::{insert_arg_ivars, Arg, Cost, ExpandsTo, ZIdExtension, EMPTY_ZID}; -#[derive(Clone, Debug, PartialEq, Eq, Hash, Default)] -pub struct Zipper(Vec); +#[derive(Clone, Debug, PartialEq, Eq, Hash)] +pub enum Zipper { + EmptyZipper, + ConsZipper { + head: ZNode, + tail: ZId, + }, +} impl Zipper { #[inline(always)] - pub fn ends_with_func(&self) -> bool { - matches!(self.0.last(), Some(ZNode::Func)) + pub fn ends_with_func(&self, zippers: &Zippers) -> bool { + return self.function_arity(zippers) > 0 } #[inline(always)] - pub fn function_arity(&self) -> usize { - self.0.iter().rev().take_while(|znode| **znode == ZNode::Func).count() + pub fn function_arity(&self, zippers: &Zippers) -> usize { + let mut ptr = self; + let mut count = 0; + // looking at the last run of Funcs in the zipper + while let Zipper::ConsZipper { head, tail } = ptr { + match head { + ZNode::Func => count += 1, + _ => count = 0 + } + ptr = &zippers.zip_of_zid[*tail]; + } + count } #[inline(always)] - pub fn depth_root_to_arg(&self) -> usize { - self.0.iter().filter(|x| **x == ZNode::Body).count() + pub fn depth_root_to_arg(&self, zippers: &Zippers) -> usize { + // looking at the first run of Body in the zipper + let mut ptr = self; + let mut count = 0; + while let Zipper::ConsZipper { head: ZNode::Body, tail } = ptr { + count += 1; + ptr = &zippers.zip_of_zid[*tail]; + } + count } #[inline(always)] - pub fn starts_with(&self, other: &Zipper) -> bool { - self.0.starts_with(&other.0) + pub fn starts_with(&self, other: &Zipper, zippers: &Zippers) -> bool { + // self.0.starts_with(&other.0) + let mut self_ptr = self; + let mut other_ptr = other; + while let (Zipper::ConsZipper { head: self_head, tail: self_tail }, Zipper::ConsZipper { head: other_head, tail: other_tail }) = (self_ptr, other_ptr) { + if self_head != other_head { + return false; + } + self_ptr = &zippers.zip_of_zid[*self_tail]; + other_ptr = &zippers.zip_of_zid[*other_tail]; + } + // at this point, at least one of the pointers is EmptyZipper, it should be the other one for self to start with the other + other_ptr == &Zipper::EmptyZipper } #[inline(always)] - pub fn add_to_front(&mut self, node: ZNode) { - self.0.insert(0, node); + pub fn add_to_end(&mut self, node: ZNode, zippers: &mut Zippers) -> ZId { + match self { + Zipper::EmptyZipper => { + *self = Zipper::ConsZipper { head: node, tail: EMPTY_ZID }; + }, + Zipper::ConsZipper { head, tail } => { + let mut tail_zip = zippers.zip_of_zid[*tail].clone(); + *self = Zipper::ConsZipper { head: head.clone(), tail: tail_zip.add_to_end(node, zippers) }; + } + } + zippers.add_zip(self.clone()) } #[inline(always)] - pub fn add_to_end(&mut self, node: ZNode) { - self.0.push(node); + pub fn remove_from_end(&mut self, zippers: &mut Zippers) -> ZId { + match self { + Zipper::EmptyZipper => {}, + Zipper::ConsZipper { head, tail } => { + if *tail == EMPTY_ZID { + *self = Zipper::EmptyZipper; + } else { + let mut tail_zip = zippers.zip_of_zid[*tail].clone(); + *self = Zipper::ConsZipper { head: head.clone(), tail: tail_zip.remove_from_end(zippers) }; + } + } + } + zippers.add_zip(self.clone()) } #[inline(always)] - pub fn remove_from_end(&mut self) { - self.0.pop(); + pub fn zip(&self, expr: &ExprOwned, zippers: &Zippers) -> ZId { + + self._zip(expr.immut(), zippers).idx } - #[inline(always)] - pub fn zip(&self, expr: &ExprOwned) -> ZId { - expr.immut().zip(&self.0).idx + pub fn _zip<'a>(&self, expr: Expr<'a>, zippers: &Zippers) -> Expr<'a> { + match self { + Zipper::EmptyZipper => expr, + Zipper::ConsZipper { head, tail } => { + // let head_vec = vec![head.clone()]; + let expr = expr.zip_once(head.clone()); + let expr = zippers.zip_of_zid[*tail]._zip(expr, zippers); + expr + } + } } } +impl Default for Zipper { + fn default() -> Self { + Zipper::EmptyZipper + } +} + #[derive(Clone, Debug, Default)] pub struct Zippers { pub zid_of_zip: FxHashMap, @@ -82,8 +150,20 @@ impl Zippers { #[inline(always)] pub fn extend_zipper(&mut self, unextended_zid: ZId, extended_node: Idx, unextended_node: Idx, znode: ZNode) -> usize { - let mut zip = self.zip_of_zid[unextended_zid].clone(); - zip.add_to_front(znode); + let zip = Zipper::ConsZipper { + head: znode, + tail: unextended_zid, + }; + let zid = self.add_zip(zip); + // add new zid to this node + // give it the same arg + let arg = self.arg_of_zid_node[unextended_zid][&unextended_node].clone(); + self.arg_of_zid_node[zid].insert(extended_node, arg); + zid + } + + #[inline(always)] + fn add_zip(&mut self, zip: Zipper) -> usize { let zip_of_zid = &mut self.zip_of_zid; let arg_of_zid_node = &mut self.arg_of_zid_node; let zid = self.zid_of_zip.entry(zip.clone()).or_insert_with(|| { @@ -92,13 +172,9 @@ impl Zippers { arg_of_zid_node.push(FxHashMap::default()); zid }); - // add new zid to this node - // give it the same arg - let arg = self.arg_of_zid_node[unextended_zid][&unextended_node].clone(); - self.arg_of_zid_node[*zid].insert(extended_node, arg); *zid } - + #[inline(always)] pub fn handle_shift(&mut self, extended_zid: ZId, unextended_zid: ZId, extended_node: Idx, unextended_node: Idx, analyzed_free_vars: &mut AnalyzedExpr, set: &mut ExprSet) { let zip = &self.zip_of_zid[extended_zid]; @@ -111,7 +187,7 @@ impl Zippers { // by inserting an IVar to indicate this // how many lambdas are along this zipper? (including most recent one) - let depth_root_to_arg = zip.depth_root_to_arg() as i32; + let depth_root_to_arg = zip.depth_root_to_arg(self) as i32; // find all pointers to $0 (this is the `init_depth` parameter) and replace then with #(num_lams - 1) that is // point past all lambdas except the newly added one. For example if there were no lambdas other than the @@ -125,20 +201,22 @@ impl Zippers { } #[inline(always)] - pub fn compute_extensions(&self) -> Vec { - self.zip_of_zid.iter().map(|zip| { + pub fn compute_extensions(&mut self) -> Vec { + let mut extensions = vec![]; + for zip in self.zip_of_zid.clone().into_iter() { let mut zip_body = zip.clone(); - zip_body.add_to_end(ZNode::Body); + zip_body.add_to_end(ZNode::Body, self); let mut zip_arg = zip.clone(); - zip_arg.add_to_end(ZNode::Arg); + zip_arg.add_to_end(ZNode::Arg, self); let mut zip_func = zip.clone(); - zip_func.add_to_end(ZNode::Func); - ZIdExtension { + zip_func.add_to_end(ZNode::Func, self); + extensions.push(ZIdExtension { body: self.zid_of_zip.get(&zip_body).copied(), arg: self.zid_of_zip.get(&zip_arg).copied(), func: self.zid_of_zip.get(&zip_func).copied(), - } - }).collect() + }); + } + extensions } pub fn print_stats(&self) {