use crate::allocator::*; use crate::codegen::SubsumedBranchHits; use crate::forms::Level; use crate::instructions::*; use crate::machine::disjuncts::VarData; use crate::parser::ast::*; use crate::targets::*; use crate::variable_records::*; use bit_set::*; use bitvec::prelude::*; use fxhash::FxBuildHasher; use indexmap::IndexMap; use std::cell::Cell; use std::collections::VecDeque; use std::ops::{Deref, DerefMut}; pub type BranchHits = IndexMap; // key: var_num, value: branch arm occurrences. #[derive(Debug, Default)] pub struct BranchOccurrences { pub hits: BranchHits, pub shallow_safety: BitSet, // unset means safe, set means unsafe (after the branch merge) pub deep_safety: BitSet, pub num_branches: usize, pub current_branch: usize, pub subsumed_hits: SubsumedBranchHits, } impl BranchOccurrences { fn new(num_branches: usize) -> Self { Self { hits: BranchHits::with_hasher(FxBuildHasher::default()), shallow_safety: BitSet::default(), deep_safety: BitSet::default(), num_branches, current_branch: 0, subsumed_hits: SubsumedBranchHits::with_hasher(FxBuildHasher::default()), } } } #[derive(Debug)] pub(crate) struct BranchStack { stack: Vec, } impl Deref for BranchStack { type Target = Vec; #[inline] fn deref(&self) -> &Self::Target { &self.stack } } impl DerefMut for BranchStack { #[inline] fn deref_mut(&mut self) -> &mut Self::Target { &mut self.stack } } impl BranchStack { fn branch_subsumes(&self, branch: &BranchDesignator, sub_branch: &BranchDesignator) -> bool { if branch.branch_stack_num < sub_branch.branch_stack_num { if branch.branch_stack_num == 0 { true } else { let idx = branch.branch_stack_num - 1; self[idx].current_branch == branch.branch_num } } else { branch == sub_branch } } fn safety_unneeded_in_branch(&self, safety: &VarSafetyStatus, branch: &BranchDesignator) -> bool { match safety { VarSafetyStatus::Needed => false, VarSafetyStatus::LocallyUnneeded(planter_branch) => self.branch_subsumes(planter_branch, branch), VarSafetyStatus::GloballyUnneeded => true, } } pub(crate) fn add_branch_occurrence(&mut self, var_num: usize) { if let Some(occurrences) = self.last_mut() { debug_assert!(occurrences.current_branch < occurrences.num_branches); let num_branches = occurrences.num_branches; let entry = occurrences.hits.entry(var_num) .or_insert_with(|| BitVec::repeat(false, num_branches)); entry.set(occurrences.current_branch, true); occurrences.subsumed_hits.insert(var_num); } } pub(crate) fn add_branch_stack(&mut self, num_branches: usize) { self.push(BranchOccurrences::new(num_branches)); } pub(crate) fn current_branch_designator(&self) -> BranchDesignator { let branch_stack_num = self.len(); let branch_num = self.last() .map(|occurrences| occurrences.current_branch) .unwrap_or(0); BranchDesignator { branch_stack_num, branch_num } } #[inline] pub(crate) fn incr_current_branch(&mut self) { let branch_occurrences = self.last_mut().unwrap(); branch_occurrences.current_branch += 1; } #[inline] pub(crate) fn drain_branches(&mut self, depth: usize) -> std::vec::Drain { let start_idx = self.len() - depth; self.drain(start_idx ..) } } #[derive(Debug)] pub(crate) struct DebrayAllocator { pub(crate) var_data: VarData, // var_data replaces bindings. pub(crate) branch_stack: BranchStack, pub(crate) in_tail_position: bool, arg_c: usize, temp_lb: usize, perm_lb: usize, arity: usize, // 0 if not at head. shallow_temp_mappings: IndexMap, in_use: BitSet, // deep and non-var allocations temp_free_list: Vec, perm_free_list: VecDeque<(usize, usize)>, // chunk_num, var_num } impl DebrayAllocator { pub(crate) fn add_branch(&mut self) { let branch_designator = self.branch_stack.current_branch_designator(); let subsumed_hits = { let branch_occurrences = self.branch_stack.last_mut().unwrap(); std::mem::replace( &mut branch_occurrences.subsumed_hits, SubsumedBranchHits::with_hasher(FxBuildHasher::default()), ) }; for var_num in subsumed_hits { match &mut self.var_data.records[var_num].allocation { VarAlloc::Perm(_, ref mut allocation) => { match allocation { PermVarAllocation::Done { shallow_safety, deep_safety, .. } => { if !self.branch_stack.safety_unneeded_in_branch(shallow_safety, &branch_designator) { let branch_occurrences = self.branch_stack.last_mut().unwrap(); branch_occurrences.shallow_safety.insert(var_num); } if !self.branch_stack.safety_unneeded_in_branch(deep_safety, &branch_designator) { let branch_occurrences = self.branch_stack.last_mut().unwrap(); branch_occurrences.deep_safety.insert(var_num); } } _ => { unreachable!(); } } *allocation = PermVarAllocation::Pending; } _ => unreachable!(), } } } pub(crate) fn pop_branch(&mut self, depth: usize, subsumed_hits: SubsumedBranchHits) { let removed_branches = self.branch_stack.drain_branches(depth); let (deep_safety, shallow_safety) = removed_branches .into_iter() .fold((BitSet::default(), BitSet::default()), |(mut deep_safety, mut shallow_safety), branch_occurrences| { deep_safety.union_with(&branch_occurrences.deep_safety); shallow_safety.union_with(&branch_occurrences.shallow_safety); (deep_safety, shallow_safety) }); let branch_designator = self.branch_stack.current_branch_designator(); let (deep_safety, shallow_safety) = match self.branch_stack.last_mut() { Some(latest_branch) => { latest_branch.deep_safety.union_with(&deep_safety); latest_branch.shallow_safety.union_with(&shallow_safety); (&latest_branch.deep_safety, &latest_branch.shallow_safety) } None => (&deep_safety, &shallow_safety) }; for var_num in subsumed_hits.iter().cloned() { let running_count = self.var_data.records[var_num].running_count; let num_occurrences = self.var_data.records[var_num].num_occurrences; match &mut self.var_data.records[var_num].allocation { VarAlloc::Perm(_, allocation) => { let shallow_safety = VarSafetyStatus::needed_if( shallow_safety.contains(var_num), branch_designator, ); let deep_safety = VarSafetyStatus::needed_if( deep_safety.contains(var_num), branch_designator, ); if running_count < num_occurrences { *allocation = PermVarAllocation::Done { shallow_safety, deep_safety }; } } _ => unreachable!() } } if self.branch_stack.len() > 0 { for var_num in subsumed_hits { self.branch_stack.add_branch_occurrence(var_num); } } } fn is_curr_arg_distinct_from(&self, var_num: usize) -> bool { match self.shallow_temp_mappings.get(&self.arg_c).cloned() { Some(t_var) => t_var != var_num, _ => false, } } fn occurs_shallowly_in_head(&self, var_num: usize, r: usize) -> bool { match &self.var_data.records[var_num].allocation { VarAlloc::Temp { temp_var_data, term_loc: GenContext::Head, .. } => { temp_var_data.use_set.contains(&(GenContext::Head, r)) } _ => false, } } #[inline] fn is_in_use(&self, r: usize) -> bool { let in_use_range = r <= self.arity && r >= self.arg_c; in_use_range || self.in_use.contains(r) } fn alloc_with_cr(&self, var_num: usize) -> usize { match &self.var_data.records[var_num].allocation { VarAlloc::Temp { temp_var_data, .. } => { for &(_, reg) in temp_var_data.use_set.iter() { if !self.is_in_use(reg) { return reg; } } let mut result = 0; for reg in self.temp_lb.. { if !self.is_in_use(reg) { if !temp_var_data.no_use_set.contains(reg) { result = reg; break; } } } result } _ => 0, } } fn alloc_with_ca(&self, var_num: usize) -> usize { match &self.var_data.records[var_num].allocation { VarAlloc::Temp { temp_var_data, .. } => { for &(_, reg) in temp_var_data.use_set.iter() { if !self.is_in_use(reg) { return reg; } } let mut result = 0; for reg in self.temp_lb.. { if !self.is_in_use(reg) { if !temp_var_data.no_use_set.contains(reg) { if !temp_var_data.conflict_set.contains(reg) { result = reg; break; } } } } result } _ => 0, } } fn alloc_in_last_goal_hint(&self, chunk_num: usize) -> Option<(usize, usize)> { // we want to allocate a register to the k^{th} parameter, par_k. // par_k may not be a temporary variable. let k = self.arg_c; match self.shallow_temp_mappings.get(&k).cloned() { Some(t_var) => { // suppose this branch fires. then t_var is a // temp. var. belonging to the current chunk. // consider its use set. T == par_k iff // (GenContext::Last(_), k) is in t_var.use_set. match &self.var_data.records[t_var].allocation { VarAlloc::Temp { temp_var_data, .. } => { if !temp_var_data.use_set.contains(&(GenContext::Last(chunk_num), k)) { return Some((t_var, self.alloc_with_ca(t_var))); } } _ => { } } None } _ => None, } } fn evacuate_arg<'a, Target: CompilationTarget<'a>>( &mut self, chunk_num: usize, code: &mut CodeDeque, ) { match self.alloc_in_last_goal_hint(chunk_num) { Some((var_num, r)) => { let k = self.arg_c; if r != k { let r = RegType::Temp(r); code.push_back(Target::move_to_register(r, k)); self.shallow_temp_mappings.swap_remove(&k); self.shallow_temp_mappings.insert(r.reg_num(), var_num); self.var_data.records[var_num].allocation.set_register(r.reg_num()); self.in_use.insert(r.reg_num()); } } _ => {} }; } fn alloc_reg_to_var<'a, Target: CompilationTarget<'a>>( &mut self, var_num: usize, lvl: Level, term_loc: GenContext, target: &mut CodeDeque, ) -> usize { match term_loc { GenContext::Head => { if let Level::Shallow = lvl { self.evacuate_arg::(0, target); self.alloc_with_cr(var_num) } else { self.alloc_with_ca(var_num) } } GenContext::Mid(_) => self.alloc_with_ca(var_num), GenContext::Last(chunk_num) => { if let Level::Shallow = lvl { self.evacuate_arg::(chunk_num, target); self.alloc_with_cr(var_num) } else { self.alloc_with_ca(var_num) } } } } fn alloc_reg_to_non_var(&mut self) -> usize { let mut final_index = 0; while let Some(r) = self.temp_free_list.pop() { if !self.is_in_use(r) { self.in_use.insert(r); return r; } } for index in self.temp_lb.. { if !self.in_use.contains(index) { final_index = index; self.in_use.insert(final_index); break; } } self.temp_lb = final_index + 1; final_index } fn in_place(&self, var_num: usize, term_loc: GenContext, r: RegType, k: usize) -> bool { match term_loc { GenContext::Head if !r.is_perm() => r.reg_num() == k, _ => { match &self.var_data.records[var_num].allocation { &VarAlloc::Temp { temp_reg, .. } if r.reg_num() == k => temp_reg == k, _ => false, } }, } } fn alloc_perm_var(&mut self, var_num: usize, chunk_num: usize) -> usize { let p = if let Some(p) = self.pop_free_perm(chunk_num) { p } else { let p = self.perm_lb; self.perm_lb += 1; p }; self.var_data.records[var_num].allocation = VarAlloc::Perm(p, PermVarAllocation::done()); p } pub(crate) fn add_reg_to_free_list(&mut self, r: RegType) { if let RegType::Temp(r) = r { self.in_use.remove(r); self.temp_free_list.push(r); } } pub fn reset_free_list(&mut self) { self.temp_free_list.clear(); } #[inline(always)] pub fn get_binding(&self, var_num: usize) -> RegType { self.var_data.records[var_num].allocation.as_reg_type() } pub fn num_perm_vars(&self) -> usize { self.perm_lb - 1 } pub fn increment_running_count(&mut self, var_num: usize) { self.var_data.records[var_num].running_count += 1; } fn add_perm_to_free_list(&mut self, chunk_num: usize, var_num: usize) { match &self.var_data.records[var_num].allocation { VarAlloc::Perm(..) => { self.perm_free_list.push_back((chunk_num, var_num)); } _ => {} } } fn pop_free_perm(&mut self, chunk_num: usize) -> Option { while let Some((perm_chunk_num, var_num)) = self.perm_free_list.front().cloned() { if chunk_num > perm_chunk_num { self.perm_free_list.pop_front(); match &mut self.var_data.records[var_num].allocation { VarAlloc::Perm(p, PermVarAllocation::Pending) if *p > 0 => { return Some(std::mem::replace(p, 0)); } _ => { } } } else { return None; } } None } pub(crate) fn free_var(&mut self, chunk_num: usize, var_num: usize) { match &mut self.var_data.records[var_num].allocation { VarAlloc::Perm(_, allocation) => { *allocation = PermVarAllocation::Pending; self.add_perm_to_free_list(chunk_num, var_num); } _ => { } } } pub(crate) fn mark_safe_var_unconditionally(&mut self, var_num: usize) { let branch_designator = self.branch_stack.current_branch_designator(); match &mut self.var_data.records[var_num].allocation { VarAlloc::Perm(_, PermVarAllocation::Done { deep_safety, shallow_safety, .. }) => { *deep_safety = VarSafetyStatus::unneeded(branch_designator); *shallow_safety = VarSafetyStatus::unneeded(branch_designator); } VarAlloc::Temp { safety, .. } => { *safety = VarSafetyStatus::unneeded(branch_designator); } _ => unreachable!(), } } fn mark_safe_var(&mut self, var_num: usize, lvl: Level, term_loc: GenContext) { let branch_designator = self.branch_stack.current_branch_designator(); match &mut self.var_data.records[var_num].allocation { VarAlloc::Perm(_, PermVarAllocation::Done { deep_safety, shallow_safety, .. }) => { // GetVariable in head chunk is considered safe. if lvl == Level::Deep { *deep_safety = VarSafetyStatus::unneeded(branch_designator); *shallow_safety = VarSafetyStatus::unneeded(branch_designator); } else if term_loc == GenContext::Head { *shallow_safety = VarSafetyStatus::GloballyUnneeded; } else { if let Some(temp_var_num) = self.shallow_temp_mappings.get(&self.arg_c).cloned() { match &mut self.var_data.records[temp_var_num].allocation { VarAlloc::Temp { ref mut to_perm_var_num, .. } => { *to_perm_var_num = Some(var_num); } _ => unreachable!() } } } } VarAlloc::Temp { ref mut safety, .. } => { *safety = VarSafetyStatus::GloballyUnneeded; } _ => { unreachable!() } } } fn argument_to_value<'a, Target: CompilationTarget<'a>>( &mut self, var_num: usize, r: RegType, arg_c: usize, ) -> Instruction { let branch_designator = self.branch_stack.current_branch_designator(); match &mut self.var_data.records[var_num].allocation { VarAlloc::Perm(_, PermVarAllocation::Done { ref mut shallow_safety, .. }) => { if !self.in_tail_position || self.branch_stack.safety_unneeded_in_branch(shallow_safety, &branch_designator) { Target::argument_to_value(r, arg_c) } else { *shallow_safety = VarSafetyStatus::unneeded(branch_designator); Target::unsafe_argument_to_value(r, arg_c) } } VarAlloc::Temp { ref mut safety, .. } => { if self.branch_stack.safety_unneeded_in_branch(safety, &branch_designator) { Target::argument_to_value(r, arg_c) } else { *safety = VarSafetyStatus::GloballyUnneeded; Target::unsafe_argument_to_value(r, arg_c) } } _ => { unreachable!() } } } fn subterm_to_value<'a, Target: CompilationTarget<'a>>( &mut self, var_num: usize, r: RegType, ) -> Instruction { let branch_designator = self.branch_stack.current_branch_designator(); match &mut self.var_data.records[var_num].allocation { VarAlloc::Perm(_, PermVarAllocation::Done { ref mut deep_safety, .. }) => { if self.branch_stack.safety_unneeded_in_branch(deep_safety, &branch_designator) { Target::subterm_to_value(r) } else { *deep_safety = VarSafetyStatus::unneeded(branch_designator); Target::unsafe_subterm_to_value(r) } } VarAlloc::Temp { ref mut safety, .. } => { if self.branch_stack.safety_unneeded_in_branch(safety, &branch_designator) { Target::subterm_to_value(r) } else { *safety = VarSafetyStatus::unneeded(branch_designator); Target::unsafe_subterm_to_value(r) } } _ => { unreachable!() } } } } impl Allocator for DebrayAllocator { fn new() -> DebrayAllocator { Self { var_data: VarData::default(), in_tail_position: false, arity: 0, arg_c: 1, temp_lb: 1, perm_lb: 1, shallow_temp_mappings: IndexMap::with_hasher(FxBuildHasher::default()), in_use: BitSet::default(), temp_free_list: vec![], perm_free_list: VecDeque::new(), branch_stack: BranchStack { stack: vec![] } } } fn mark_anon_var<'a, Target: CompilationTarget<'a>>( &mut self, lvl: Level, term_loc: GenContext, code: &mut CodeDeque, ) { let r = RegType::Temp(self.alloc_reg_to_non_var()); match lvl { Level::Deep => code.push_back(Target::subterm_to_variable(r)), Level::Root | Level::Shallow => { let k = self.arg_c; if let GenContext::Last(chunk_num) = term_loc { self.evacuate_arg::(chunk_num, code); } self.arg_c += 1; code.push_back(Target::argument_to_variable(r, k)); } }; } fn mark_non_var<'a, Target: CompilationTarget<'a>>( &mut self, lvl: Level, term_loc: GenContext, cell: &'a Cell, code: &mut CodeDeque, ) { let r = cell.get(); let r = match lvl { Level::Shallow => { let k = self.arg_c; if let GenContext::Last(chunk_num) = term_loc { self.evacuate_arg::(chunk_num, code); } self.arg_c += 1; RegType::Temp(k) } _ if r.reg_num() == 0 => RegType::Temp(self.alloc_reg_to_non_var()), _ => { self.in_use.insert(r.reg_num()); r } }; cell.set(r); } fn mark_var<'a, Target: CompilationTarget<'a>>( &mut self, var_num: usize, lvl: Level, cell: &'a Cell, term_loc: GenContext, code: &mut CodeDeque, ) { let (r, is_new_var) = match self.get_binding(var_num) { RegType::Temp(0) => { let o = self.alloc_reg_to_var::(var_num, lvl, term_loc, code); cell.set(VarReg::Norm(RegType::Temp(o))); (RegType::Temp(o), true) } RegType::Perm(0) => { let p = self.alloc_perm_var(var_num, term_loc.chunk_num()); (RegType::Perm(p), true) } r @ RegType::Perm(_) => { let is_new_var = match &mut self.var_data.records[var_num].allocation { VarAlloc::Perm(_, allocation) => if allocation.pending() { *allocation = PermVarAllocation::done(); true } else { false }, _ => unreachable!(), }; (r, is_new_var) } r => (r, false), }; self.mark_reserved_var::(var_num, lvl, cell, term_loc, code, r, is_new_var); } fn mark_reserved_var<'a, Target: CompilationTarget<'a>>( &mut self, var_num: usize, lvl: Level, cell: &'a Cell, term_loc: GenContext, code: &mut CodeDeque, r: RegType, is_new_var: bool, ) { match lvl { Level::Root | Level::Shallow => { let k = self.arg_c; if self.is_curr_arg_distinct_from(var_num) { self.evacuate_arg::(term_loc.chunk_num(), code); } cell.set(VarReg::ArgAndNorm(r, k)); if !self.in_place(var_num, term_loc, r, k) { if is_new_var { self.mark_safe_var(var_num, lvl, term_loc); code.push_back(Target::argument_to_variable(r, k)); } else { code.push_back(self.argument_to_value::(var_num, r, k)); } } self.arg_c += 1; } Level::Deep if is_new_var => { if let GenContext::Head = term_loc { if self.occurs_shallowly_in_head(var_num, r.reg_num()) { code.push_back(self.subterm_to_value::(var_num, r)); } else { self.mark_safe_var(var_num, lvl, term_loc); code.push_back(Target::subterm_to_variable(r)); } } else { self.mark_safe_var(var_num, lvl, term_loc); code.push_back(Target::subterm_to_variable(r)); } } Level::Deep => code.push_back(self.subterm_to_value::(var_num, r)), } let o = r.reg_num(); if !r.is_perm() { self.shallow_temp_mappings.insert(o, var_num); } else if r.is_perm() && is_new_var { self.branch_stack.add_branch_occurrence(var_num); } let record = &mut self.var_data.records[var_num]; record.allocation.set_register(o); if record.running_count < record.num_occurrences { record.running_count += 1; } else { self.free_var(term_loc.chunk_num(), var_num); } self.in_use.insert(o); } fn mark_cut_var(&mut self, var_num: usize, chunk_num: usize) -> RegType { match self.get_binding(var_num) { RegType::Perm(0) | RegType::Temp(0) => { RegType::Perm(self.alloc_perm_var(var_num, chunk_num)) } r => r, } } fn reset(&mut self) { self.perm_lb = 1; self.shallow_temp_mappings.clear(); self.in_use.clear(); self.temp_free_list.clear(); } fn reset_contents(&mut self) { self.in_use.clear(); self.shallow_temp_mappings.clear(); self.temp_free_list.clear(); } fn advance_arg(&mut self) { self.arg_c += 1; } fn reset_at_head(&mut self, args: &Vec) { self.reset_arg(args.len()); self.arity = args.len(); for (idx, arg) in args.iter().enumerate() { if let &Term::Var(_, ref var) = arg { let var_num = var.to_var_num().unwrap(); let r = self.get_binding(var_num); if !r.is_perm() && r.reg_num() == 0 { self.in_use.insert(idx + 1); self.shallow_temp_mappings.insert(idx + 1, var_num); self.var_data.records[var_num].allocation.set_register(idx + 1); } } } } fn reset_arg(&mut self, arity: usize) { self.arity = 0; self.arg_c = 1; self.temp_lb = arity + 1; } #[inline(always)] fn max_reg_allocated(&self) -> usize { std::cmp::max(self.temp_lb, self.arg_c) } }