From 16dc10ee968ef478eacc4ce1b4b9c22622daba17 Mon Sep 17 00:00:00 2001 From: Mark Thom Date: Sun, 7 Dec 2025 15:15:18 -0800 Subject: [PATCH 01/12] assert rational(3) as true in tests/builtins.pl --- src/codegen.rs | 16 +++++++++++----- src/tests/builtins.pl | 2 +- 2 files changed, 12 insertions(+), 6 deletions(-) diff --git a/src/codegen.rs b/src/codegen.rs index c5a0c857..67ba2dbf 100644 --- a/src/codegen.rs +++ b/src/codegen.rs @@ -639,7 +639,10 @@ impl CodeGenerator { } }, InlinedClauseType::IsRational(..) => match terms[0] { - Term::Literal(_, Literal::Rational(_)) => { + Term::Literal( + _, + Literal::Rational(_) | Literal::Fixnum(_) | Literal::Integer(_), + ) => { instr!("$succeed") } Term::Var(ref vr, ref name) => { @@ -679,10 +682,13 @@ impl CodeGenerator { } }, InlinedClauseType::IsNumber(..) => match terms[0] { - Term::Literal(_, Literal::F64(..)) - | Term::Literal(_, Literal::Rational(_)) - | Term::Literal(_, Literal::Integer(_)) - | Term::Literal(_, Literal::Fixnum(_)) => { + Term::Literal( + _, + Literal::F64(..) + | Literal::Rational(_) + | Literal::Integer(_) + | Literal::Fixnum(_), + ) => { instr!("$succeed") } Term::Var(ref vr, ref name) => { diff --git a/src/tests/builtins.pl b/src/tests/builtins.pl index a87fc22a..1a437413 100644 --- a/src/tests/builtins.pl +++ b/src/tests/builtins.pl @@ -46,7 +46,7 @@ test_queries_on_builtins :- \+ float([1,2,_]), \+ (X is 3 rdiv 4, float(X)), \+ \+ (X is 3 rdiv 4, rational(X)), - \+ rational(3), + rational(3), \+ rational(f(_)), \+ rational("sdfa"), \+ rational(atom), From 9089f9ddb49820671a1e65ad2a886aec56b2a928 Mon Sep 17 00:00:00 2001 From: Mark Thom Date: Tue, 9 Dec 2025 23:14:24 -0800 Subject: [PATCH 02/12] use branch numbers to detect branch subsumption --- src/codegen.rs | 17 +++-- src/debray_allocator.rs | 64 ++++++++---------- src/forms.rs | 95 +++++++++++++++++++++++--- src/iterators.rs | 58 +++++++++++----- src/machine/disjuncts.rs | 140 +++++++++++---------------------------- src/variable_records.rs | 25 +++---- 6 files changed, 218 insertions(+), 181 deletions(-) diff --git a/src/codegen.rs b/src/codegen.rs index 67ba2dbf..19d056d6 100644 --- a/src/codegen.rs +++ b/src/codegen.rs @@ -994,20 +994,27 @@ impl CodeGenerator { self.marker.in_tail_position = false; self.marker.reset_contents(); } - ClauseItem::FirstBranch(num_branches) => { + ClauseItem::FirstBranch { + branch_num, + num_branches, + } => { branch_code_stack.add_new_branch_stack(); branch_code_stack.add_new_branch(); - self.marker.branch_stack.add_branch_stack(num_branches); + self.marker + .branch_stack + .add_branch_stack(branch_num.clone(), num_branches); self.marker.add_branch(); } - ClauseItem::NextBranch => { + ClauseItem::NextBranch { branch_num } => { branch_code_stack.add_new_branch(); self.marker.add_branch(); - self.marker.branch_stack.incr_current_branch(); + self.marker + .branch_stack + .incr_current_branch(branch_num.clone()); } - ClauseItem::BranchEnd(depth) => { + ClauseItem::BranchEnd { depth } => { if !clause_iter.in_tail_position() { let subsumed_hits = branch_code_stack.push_missing_vars(depth, &mut self.marker); diff --git a/src/debray_allocator.rs b/src/debray_allocator.rs index 7fe37259..d214b8c7 100644 --- a/src/debray_allocator.rs +++ b/src/debray_allocator.rs @@ -1,6 +1,6 @@ use crate::allocator::*; use crate::codegen::SubsumedBranchHits; -use crate::forms::{GenContext, Level}; +use crate::forms::{BranchNumber, GenContext, Level}; use crate::instructions::*; use crate::machine::disjuncts::VarData; use crate::parser::ast::*; @@ -24,24 +24,26 @@ pub struct BranchOccurrences { 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 current_branch_idx: usize, + pub current_branch_num: BranchNumber, pub subsumed_hits: SubsumedBranchHits, } impl BranchOccurrences { - fn new(num_branches: usize) -> Self { + fn new(current_branch_num: BranchNumber, num_branches: usize) -> Self { Self { hits: BranchHits::with_hasher(FxBuildHasher::default()), shallow_safety: BitSet::default(), deep_safety: BitSet::default(), num_branches, - current_branch: 0, + current_branch_idx: 0, + current_branch_num, subsumed_hits: SubsumedBranchHits::with_hasher(FxBuildHasher::default()), } } pub(crate) fn add_branch_occurrence(&mut self, var_num: usize) { - debug_assert!(self.current_branch < self.num_branches); + debug_assert!(self.current_branch_idx < self.num_branches); let num_branches = self.num_branches; let entry = self @@ -49,7 +51,7 @@ impl BranchOccurrences { .entry(var_num) .or_insert_with(|| BitVec::repeat(false, num_branches)); - entry.set(self.current_branch, true); + entry.set(self.current_branch_idx, true); self.subsumed_hits.insert(var_num); } } @@ -76,19 +78,6 @@ impl DerefMut for BranchStack { } 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, @@ -97,7 +86,7 @@ impl BranchStack { match safety { VarSafetyStatus::Needed => false, VarSafetyStatus::LocallyUnneeded(planter_branch) => { - self.branch_subsumes(planter_branch, branch) + planter_branch.branch_num.has_as_subbranch(&branch.branch_num) } VarSafetyStatus::GloballyUnneeded => true, } @@ -109,27 +98,26 @@ impl BranchStack { } } - pub(crate) fn add_branch_stack(&mut self, num_branches: usize) { - self.push(BranchOccurrences::new(num_branches)); + pub(crate) fn add_branch_stack(&mut self, branch_num: BranchNumber, num_branches: usize) { + self.push(BranchOccurrences::new(branch_num, 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); + .map(|occurrences| occurrences.current_branch_num.clone()) + .unwrap_or_else(|| BranchNumber::default()); BranchDesignator { - branch_stack_num, branch_num, } } #[inline] - pub(crate) fn incr_current_branch(&mut self) { + pub(crate) fn incr_current_branch(&mut self, branch_num: BranchNumber) { let branch_occurrences = self.last_mut().unwrap(); - branch_occurrences.current_branch += 1; + branch_occurrences.current_branch_idx += 1; + branch_occurrences.current_branch_num = branch_num; } #[inline] @@ -235,12 +223,12 @@ impl DebrayAllocator { VarAlloc::Perm(_, allocation) => { let shallow_safety = VarSafetyStatus::needed_if( shallow_safety.contains(var_num), - branch_designator, + &branch_designator, ); let deep_safety = VarSafetyStatus::needed_if( deep_safety.contains(var_num), - branch_designator, + &branch_designator, ); if running_count < num_occurrences { @@ -531,11 +519,11 @@ impl DebrayAllocator { .. }, ) => { - *deep_safety = VarSafetyStatus::unneeded(branch_designator); - *shallow_safety = VarSafetyStatus::unneeded(branch_designator); + *deep_safety = VarSafetyStatus::unneeded(&branch_designator); + *shallow_safety = VarSafetyStatus::unneeded(&branch_designator); } VarAlloc::Temp { safety, .. } => { - *safety = VarSafetyStatus::unneeded(branch_designator); + *safety = VarSafetyStatus::unneeded(&branch_designator); } _ => { unreachable!() @@ -557,8 +545,8 @@ impl DebrayAllocator { ) => { // GetVariable in head chunk is considered safe. if lvl == Level::Deep { - *deep_safety = VarSafetyStatus::unneeded(branch_designator); - *shallow_safety = VarSafetyStatus::unneeded(branch_designator); + *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) { @@ -605,7 +593,7 @@ impl DebrayAllocator { { Target::argument_to_value(r, arg_c) } else { - *shallow_safety = VarSafetyStatus::unneeded(branch_designator); + *shallow_safety = VarSafetyStatus::unneeded(&branch_designator); Target::unsafe_argument_to_value(r, arg_c) } } @@ -640,7 +628,7 @@ impl DebrayAllocator { { Target::subterm_to_value(r) } else { - *deep_safety = VarSafetyStatus::unneeded(branch_designator); + *deep_safety = VarSafetyStatus::unneeded(&branch_designator); Target::unsafe_subterm_to_value(r) } } @@ -651,7 +639,7 @@ impl DebrayAllocator { { Target::subterm_to_value(r) } else { - *safety = VarSafetyStatus::unneeded(branch_designator); + *safety = VarSafetyStatus::unneeded(&branch_designator); Target::unsafe_subterm_to_value(r) } } diff --git a/src/forms.rs b/src/forms.rs index ae9b829d..447328f7 100644 --- a/src/forms.rs +++ b/src/forms.rs @@ -18,9 +18,11 @@ use indexmap::{IndexMap, IndexSet}; use ordered_float::OrderedFloat; use std::cell::Cell; +use std::cmp::Ordering; use std::collections::VecDeque; use std::convert::TryFrom; use std::fmt; +use std::hash::{Hash, Hasher}; use std::ops::{AddAssign, Deref, DerefMut}; use std::path::PathBuf; @@ -128,10 +130,86 @@ impl ChunkType { } } +#[derive(Debug, Clone)] //, PartialOrd, PartialEq, Eq, Hash)] +pub(crate) struct BranchNumber { + pub(crate) branch_num: Rational, + pub(crate) delta: Rational, +} + +impl Default for BranchNumber { + fn default() -> Self { + Self { + branch_num: Rational::from(0), + delta: Rational::from(1u64 << 31), + } + } +} + +impl PartialEq for BranchNumber { + #[inline] + fn eq(&self, rhs: &BranchNumber) -> bool { + self.branch_num == rhs.branch_num + } +} + +impl Eq for BranchNumber {} + +impl Hash for BranchNumber { + #[inline(always)] + fn hash(&self, hasher: &mut H) { + self.branch_num.hash(hasher) + } +} + +impl PartialOrd for BranchNumber { + #[inline] + fn partial_cmp(&self, rhs: &BranchNumber) -> Option { + self.branch_num.partial_cmp(&rhs.branch_num) + } +} + +impl BranchNumber { + pub(crate) fn has_as_subbranch(&self, other: &Self) -> bool { + let delta_ratio = &self.delta / &other.delta; + + if !delta_ratio.denominator().is_one() { + return false; + } + + other.branch_num >= self.branch_num && other.branch_num < &self.branch_num + &self.delta + } + + pub(crate) fn split(&self) -> BranchNumber { + BranchNumber { + branch_num: self.branch_num.clone() + &self.delta / Rational::from(2), + delta: &self.delta / Rational::from(4), + } + } + + pub(crate) fn incr_by_delta(&self) -> BranchNumber { + BranchNumber { + branch_num: self.branch_num.clone() + &self.delta, + delta: self.delta.clone(), + } + } + + pub(crate) fn halve_delta(&self) -> BranchNumber { + BranchNumber { + branch_num: self.branch_num.clone(), + delta: &self.delta / Rational::from(2), + } + } +} + #[derive(Debug)] pub enum ChunkedTerms { - Branch(Vec>), - Chunk { terms: VecDeque }, + Branch { + branch_nums: Vec, + arms: Vec>, + }, + Chunk { + terms: VecDeque, + }, } #[derive(Debug)] @@ -165,21 +243,22 @@ impl ChunkedTermVec { } pub fn reserve_branch(&mut self, capacity: usize) { - self.chunk_vec - .push_back(ChunkedTerms::Branch(Vec::with_capacity(capacity))); + self.chunk_vec.push_back(ChunkedTerms::Branch { + branch_nums: Vec::with_capacity(capacity), + arms: Vec::with_capacity(capacity), + }); } #[inline] pub fn add_chunk(&mut self) { - let chunk = ChunkedTerms::Chunk { + self.chunk_vec.push_back(ChunkedTerms::Chunk { terms: VecDeque::from(vec![]), - }; - self.chunk_vec.push_back(chunk); + }); } pub fn push_chunk_term(&mut self, term: QueryTerm) { match self.chunk_vec.back_mut() { - Some(ChunkedTerms::Branch(_)) => { + Some(ChunkedTerms::Branch { .. }) => { let chunk = ChunkedTerms::Chunk { terms: VecDeque::from(vec![term]), }; diff --git a/src/iterators.rs b/src/iterators.rs index e3a2ccbd..0aa7bd38 100644 --- a/src/iterators.rs +++ b/src/iterators.rs @@ -319,15 +319,28 @@ pub(crate) fn breadth_first_iter( #[derive(Debug, Copy, Clone)] enum ClauseIteratorState<'a> { RemainingChunks(&'a VecDeque, usize), - RemainingBranches(&'a Vec>, usize), + RemainingBranches( + &'a Vec, + &'a Vec>, + usize, + ), } #[derive(Debug, Clone)] pub(crate) enum ClauseItem<'a> { - FirstBranch(usize), - NextBranch, - BranchEnd(usize), - Chunk { terms: &'a VecDeque }, + FirstBranch { + branch_num: &'a BranchNumber, + num_branches: usize, + }, + NextBranch { + branch_num: &'a BranchNumber, + }, + BranchEnd { + depth: usize, + }, + Chunk { + terms: &'a VecDeque, + }, } #[derive(Debug)] @@ -338,8 +351,8 @@ pub(crate) struct ClauseIterator<'a> { fn state_from_chunked_terms(chunk_vec: &VecDeque) -> ClauseIteratorState<'_> { if chunk_vec.len() == 1 { - if let Some(ChunkedTerms::Branch(ref branches)) = chunk_vec.front() { - return ClauseIteratorState::RemainingBranches(branches, 0); + if let Some(ChunkedTerms::Branch { branch_nums, arms }) = chunk_vec.front() { + return ClauseIteratorState::RemainingBranches(branch_nums, arms, 0); } } @@ -370,7 +383,9 @@ impl<'a> ClauseIterator<'a> { while let Some(state) = self.state_stack.pop() { match state { - ClauseIteratorState::RemainingBranches(terms, focus) if terms.len() == focus => { + ClauseIteratorState::RemainingBranches(_branch_nums, terms, focus) + if terms.len() == focus => + { depth += 1; } _ => { @@ -399,9 +414,9 @@ impl<'a> Iterator for ClauseIterator<'a> { } match &chunks[focus] { - ChunkedTerms::Branch(branches) => { + ChunkedTerms::Branch { branch_nums, arms } => { self.state_stack - .push(ClauseIteratorState::RemainingBranches(branches, 0)); + .push(ClauseIteratorState::RemainingBranches(branch_nums, arms, 0)); } ChunkedTerms::Chunk { ref terms } => { return Some(ClauseItem::Chunk { terms }); @@ -411,11 +426,15 @@ impl<'a> Iterator for ClauseIterator<'a> { ClauseIteratorState::RemainingChunks(chunks, focus) => { debug_assert_eq!(chunks.len(), focus); } - ClauseIteratorState::RemainingBranches(branches, focus) + ClauseIteratorState::RemainingBranches(branch_nums, branches, focus) if focus < branches.len() => { self.state_stack - .push(ClauseIteratorState::RemainingBranches(branches, focus + 1)); + .push(ClauseIteratorState::RemainingBranches( + branch_nums, + branches, + focus + 1, + )); let state = state_from_chunked_terms(&branches[focus]); if let ClauseIteratorState::RemainingChunks(..) = &state { @@ -425,14 +444,21 @@ impl<'a> Iterator for ClauseIterator<'a> { self.state_stack.push(state); return if focus == 0 { - Some(ClauseItem::FirstBranch(branches.len())) + Some(ClauseItem::FirstBranch { + branch_num: &branch_nums[0], + num_branches: branches.len(), + }) } else { - Some(ClauseItem::NextBranch) + Some(ClauseItem::NextBranch { + branch_num: &branch_nums[focus], + }) }; } - ClauseIteratorState::RemainingBranches(branches, focus) => { + ClauseIteratorState::RemainingBranches(_branch_nums, branches, focus) => { debug_assert_eq!(branches.len(), focus); - return Some(ClauseItem::BranchEnd(self.branch_end_depth())); + return Some(ClauseItem::BranchEnd { + depth: self.branch_end_depth(), + }); } } } diff --git a/src/machine/disjuncts.rs b/src/machine/disjuncts.rs index 4ecac7bc..95f4ae5f 100644 --- a/src/machine/disjuncts.rs +++ b/src/machine/disjuncts.rs @@ -6,79 +6,16 @@ use crate::machine::loader::*; use crate::machine::machine_errors::CompilationError; use crate::machine::preprocessor::*; use crate::parser::ast::*; -use crate::parser::dashu::Rational; use crate::variable_records::*; use dashu::Integer; use indexmap::{IndexMap, IndexSet}; use std::cell::Cell; -use std::cmp::Ordering; use std::collections::VecDeque; -use std::hash::{Hash, Hasher}; +use std::hash::Hash; use std::ops::{Deref, DerefMut}; -#[derive(Debug, Clone)] //, PartialOrd, PartialEq, Eq, Hash)] -pub struct BranchNumber { - branch_num: Rational, - delta: Rational, -} - -impl Default for BranchNumber { - fn default() -> Self { - Self { - branch_num: Rational::from(1u64 << 63), - delta: Rational::from(1), - } - } -} - -impl PartialEq for BranchNumber { - #[inline] - fn eq(&self, rhs: &BranchNumber) -> bool { - self.branch_num == rhs.branch_num - } -} - -impl Eq for BranchNumber {} - -impl Hash for BranchNumber { - #[inline(always)] - fn hash(&self, hasher: &mut H) { - self.branch_num.hash(hasher) - } -} - -impl PartialOrd for BranchNumber { - #[inline] - fn partial_cmp(&self, rhs: &BranchNumber) -> Option { - self.branch_num.partial_cmp(&rhs.branch_num) - } -} - -impl BranchNumber { - fn split(&self) -> BranchNumber { - BranchNumber { - branch_num: self.branch_num.clone() + &self.delta / Rational::from(2), - delta: &self.delta / Rational::from(4), - } - } - - fn incr_by_delta(&self) -> BranchNumber { - BranchNumber { - branch_num: self.branch_num.clone() + &self.delta, - delta: self.delta.clone(), - } - } - - fn halve_delta(&self) -> BranchNumber { - BranchNumber { - branch_num: self.branch_num.clone(), - delta: &self.delta / Rational::from(2), - } - } -} - #[derive(Debug, Clone, PartialEq, Eq, Hash)] pub struct VarInfo { var_ptr: VarPtr, @@ -140,12 +77,13 @@ pub struct ClassifyInfo { } enum TraversalState { - // construct a QueryTerm::Branch with number of disjuncts, reset - // the chunk type to that of the chunk preceding the disjunct and the chunk_num. + // pop the latest branch number from the root set and use it to construct a QueryTerm::Branch + // with number of disjuncts, reset the chunk type to that of the chunk preceding the disjunct + // and the chunk_num. BuildDisjunct(usize), + BuildFinalDisjunct(usize), // add the last disjunct to a QueryTerm::Branch, continuing from // where it leaves off. - BuildFinalDisjunct(usize), Fail, GetCutPoint { var_num: usize, prev_b: bool }, Cut { var_num: usize, is_global: bool }, @@ -154,7 +92,6 @@ enum TraversalState { Term(Term), OverrideGlobalCutVar(usize), ResetGlobalCutVarOverride(Option), - RemoveBranchNum, // pop the current_branch_num and from the root set. AddBranchNum(BranchNumber), // set current_branch_num, add it to the root set RepBranchNum(BranchNumber), // replace current_branch_num and the latest in the root set } @@ -199,7 +136,7 @@ impl VarData { VarAlloc::Perm(0, PermVarAllocation::Pending); match build_stack.front_mut() { - Some(ChunkedTerms::Branch(_)) => { + Some(ChunkedTerms::Branch { .. }) => { build_stack.push_front(ChunkedTerms::Chunk { terms: VecDeque::from(vec![term]), }); @@ -232,11 +169,16 @@ fn merge_branch_seq(branches: impl Iterator) -> BranchInfo { branch_info } -fn flatten_into_disjunct(build_stack: &mut ChunkedTermVec, preceding_len: usize) { +fn flatten_into_disjunct( + build_stack: &mut ChunkedTermVec, + branch_num: BranchNumber, + preceding_len: usize, +) { let branch_vec = build_stack.drain(preceding_len + 1..).collect(); - if let ChunkedTerms::Branch(ref mut disjuncts) = &mut build_stack[preceding_len] { - disjuncts.push(branch_vec); + if let ChunkedTerms::Branch { branch_nums, arms } = &mut build_stack[preceding_len] { + branch_nums.push(branch_num); + arms.push(branch_vec); } else { unreachable!(); } @@ -477,9 +419,6 @@ impl VariableClassifier { self.root_set.insert(branch_num.clone()); self.current_branch_num = branch_num; } - TraversalState::RemoveBranchNum => { - self.root_set.pop(); - } TraversalState::RepBranchNum(branch_num) => { self.root_set.pop(); self.root_set.insert(branch_num.clone()); @@ -488,14 +427,9 @@ impl VariableClassifier { TraversalState::ResetCallPolicy(call_policy) => { self.call_policy = call_policy; } - TraversalState::BuildDisjunct(preceding_len) => { - flatten_into_disjunct(&mut build_stack, preceding_len); - - self.current_chunk_type = ChunkType::Mid; - self.current_chunk_num += 1; - } - TraversalState::BuildFinalDisjunct(preceding_len) => { - flatten_into_disjunct(&mut build_stack, preceding_len); + TraversalState::BuildDisjunct(preceding_len) | TraversalState::BuildFinalDisjunct(preceding_len) => { + let branch_num = self.root_set.pop().unwrap(); + flatten_into_disjunct(&mut build_stack, branch_num, preceding_len); self.current_chunk_type = ChunkType::Mid; self.current_chunk_num += 1; @@ -632,11 +566,10 @@ impl VariableClassifier { )); let iter = branches.into_iter().zip(branch_numbers.into_iter()); - let final_disjunct_loc = state_stack.len(); + let final_disjunct_loc = state_stack.len(); for (term, branch_num) in iter.rev() { state_stack.push(TraversalState::BuildDisjunct(build_stack_len)); - state_stack.push(TraversalState::RemoveBranchNum); state_stack.push(TraversalState::Term(term)); state_stack.push(TraversalState::AddBranchNum(branch_num)); } @@ -655,23 +588,18 @@ impl VariableClassifier { let then_term = terms.pop().unwrap(); let if_term = terms.pop().unwrap(); - let prev_b = if matches!( - state_stack.last(), - Some(TraversalState::RemoveBranchNum) - ) { - // check if the second-to-last element - // is a regular BuildDisjunct, as we - // don't want to add GetPrevLevel in - // case of a TrustMe. - match state_stack.iter().rev().nth(1) { - Some(&TraversalState::BuildDisjunct(preceding_len)) => { - preceding_len + 1 == build_stack.len() - } - _ => false, - } - } else { - false - }; + let prev_b = + if let Some(&TraversalState::BuildDisjunct(preceding_len)) = + state_stack.last() + { + // check if the second-to-last element + // is a regular BuildDisjunct, as we + // don't want to add GetPrevLevel in + // case of a TrustMe. + preceding_len + 1 == build_stack.len() + } else { + false + }; state_stack.push(TraversalState::Term(then_term)); state_stack.push(TraversalState::Cut { @@ -690,14 +618,21 @@ impl VariableClassifier { let not_term = terms.pop().unwrap(); let build_stack_len = build_stack.len(); + let first_branch_num = self.current_branch_num.split(); + let second_branch_num = first_branch_num.incr_by_delta(); + build_stack.reserve_branch(2); + state_stack.push(TraversalState::RepBranchNum( + self.current_branch_num.halve_delta(), + )); state_stack.push(TraversalState::BuildFinalDisjunct(build_stack_len)); state_stack.push(TraversalState::Term(Term::Clause( Cell::default(), atom!("$succeed"), vec![], ))); + state_stack.push(TraversalState::AddBranchNum(second_branch_num)); state_stack.push(TraversalState::BuildDisjunct(build_stack_len)); state_stack.push(TraversalState::Fail); state_stack.push(TraversalState::CutPrev(self.var_num)); @@ -710,6 +645,7 @@ impl VariableClassifier { var_num: self.var_num, prev_b: false, }); + state_stack.push(TraversalState::AddBranchNum(first_branch_num)); self.current_chunk_type = ChunkType::Mid; self.current_chunk_num += 1; diff --git a/src/variable_records.rs b/src/variable_records.rs index c918067e..e8a867df 100644 --- a/src/variable_records.rs +++ b/src/variable_records.rs @@ -1,9 +1,11 @@ -use crate::forms::GenContext; +use crate::forms::{BranchNumber, GenContext}; use crate::parser::ast::*; use bit_set::*; use fxhash::FxBuildHasher; use indexmap::{IndexMap, IndexSet}; +use num_order::NumOrd; + use std::ops::{Deref, DerefMut}; #[derive(Debug, Clone)] @@ -13,20 +15,19 @@ pub struct TempVarData { pub(crate) conflict_set: BitSet, } -#[derive(Debug, Clone, Copy, PartialEq, Eq)] +#[derive(Debug, Clone, PartialEq, Eq)] pub struct BranchDesignator { - pub branch_stack_num: usize, - pub branch_num: usize, + pub branch_num: BranchNumber, } impl BranchDesignator { #[inline] pub fn is_sub_branch(&self) -> bool { - self.branch_stack_num > 0 + self.branch_num.branch_num.num_gt(&0) } } -#[derive(Debug, Clone, Copy)] +#[derive(Debug, Clone)] pub enum VarSafetyStatus { Needed, // which branch planted the last unsafe guarded instruction? It may still be needed. @@ -35,27 +36,27 @@ pub enum VarSafetyStatus { } impl VarSafetyStatus { - pub(crate) fn unneeded(current_branch: BranchDesignator) -> Self { + pub(crate) fn unneeded(current_branch: &BranchDesignator) -> Self { if current_branch.is_sub_branch() { - VarSafetyStatus::LocallyUnneeded(current_branch) + VarSafetyStatus::LocallyUnneeded(current_branch.clone()) } else { VarSafetyStatus::GloballyUnneeded } } #[inline] - pub(crate) fn needed_if(needed: bool, branch_designator: BranchDesignator) -> Self { + pub(crate) fn needed_if(needed: bool, branch_designator: &BranchDesignator) -> Self { if needed { VarSafetyStatus::Needed - } else if branch_designator.branch_stack_num == 0 { + } else if branch_designator.branch_num.branch_num.num_eq(&0) { VarSafetyStatus::GloballyUnneeded } else { - VarSafetyStatus::LocallyUnneeded(branch_designator) + VarSafetyStatus::LocallyUnneeded(branch_designator.clone()) } } } -#[derive(Debug, Clone, Copy)] +#[derive(Debug, Clone)] pub enum PermVarAllocation { Done { shallow_safety: VarSafetyStatus, From 29cd80510ba8b81905c634348995d1c1ed665d3a Mon Sep 17 00:00:00 2001 From: Mark Thom Date: Thu, 11 Dec 2025 22:08:16 -0800 Subject: [PATCH 03/12] replace compare_term_test with parallel iterator, add is_not_variant --- build/instructions_template.rs | 7 +- src/forms.rs | 10 +- src/heap_iter.rs | 357 +++++++++++++++++++++++++++ src/instructions.rs | 2 - src/machine/attributed_variables.rs | 5 +- src/machine/dispatch.rs | 103 +++----- src/machine/machine_state_impl.rs | 364 ++-------------------------- src/machine/mock_wam.rs | 51 ++-- src/machine/system_calls.rs | 49 +++- src/macros.rs | 15 -- 10 files changed, 488 insertions(+), 475 deletions(-) diff --git a/build/instructions_template.rs b/build/instructions_template.rs index 16f37bcb..8971f505 100644 --- a/build/instructions_template.rs +++ b/build/instructions_template.rs @@ -643,15 +643,12 @@ enum SystemClauseType { UnattributedVar, #[strum_discriminants(strum(props(Arity = "4", Name = "$get_db_refs")))] GetDBRefs, - #[strum_discriminants(strum(props( - Arity = "2", - Name = "$keysort_with_constant_var_ordering" - )))] - KeySortWithConstantVarOrdering, #[strum_discriminants(strum(props(Arity = "0", Name = "$inference_limit_exceeded")))] InferenceLimitExceeded, #[strum_discriminants(strum(props(Arity = "1", Name = "$argv")))] Argv, + #[strum_discriminants(strum(props(Arity = "2", Name = "$variant")))] + IsVariant, Repl(ReplCodePtr), } diff --git a/src/forms.rs b/src/forms.rs index 447328f7..d6cc39d4 100644 --- a/src/forms.rs +++ b/src/forms.rs @@ -170,13 +170,9 @@ impl PartialOrd for BranchNumber { impl BranchNumber { pub(crate) fn has_as_subbranch(&self, other: &Self) -> bool { - let delta_ratio = &self.delta / &other.delta; - - if !delta_ratio.denominator().is_one() { - return false; - } - - other.branch_num >= self.branch_num && other.branch_num < &self.branch_num + &self.delta + other.delta <= self.delta && + other.branch_num >= self.branch_num && + other.branch_num < &self.branch_num + &self.delta } pub(crate) fn split(&self) -> BranchNumber { diff --git a/src/heap_iter.rs b/src/heap_iter.rs index 70314082..05b91293 100644 --- a/src/heap_iter.rs +++ b/src/heap_iter.rs @@ -1,21 +1,378 @@ #![allow(clippy::new_without_default)] // annotating structs annotated with #[bitfield] doesn't work #![allow(unused_parens)] // see mthom/scryer-prolog#3092 and rust-lang/rust#147126 +use crate::arena::Arena; +use crate::forms::Number; #[cfg(test)] pub(crate) use crate::machine::gc::StacklessPreOrderHeapIter; use crate::atom_table::*; use crate::machine::cycle_detection::*; use crate::machine::heap::*; +use crate::machine::machine_indices::TermOrderCategory; use crate::machine::stack::*; +use crate::parser::lexer::MachineState; use crate::types::*; use core::marker::PhantomData; +use fxhash::FxBuildHasher; +use indexmap::IndexSet; use scryer_modular_bitfield::prelude::*; +use std::cmp::Ordering; use std::ops::Deref; use std::vec::Vec; +// iterate through the subterms of a pair of terms +// for as long as they structurally agree, reporting +// the earliest (pre-order) difference before returning +// None's and pairs of variables as the Iterator Item. + +pub struct ParallelHeapIter<'a> { + stack: Vec, + heap: &'a Heap, + arena: &'a Arena, + tabu_list: IndexSet<(usize, usize), FxBuildHasher>, +} + +impl<'a> ParallelHeapIter<'a> { + pub fn from(machine_st: &'a MachineState, h1: HeapCellValue, h2: HeapCellValue) -> Self { + Self { + stack: vec![h2, h1], + heap: &machine_st.heap, + arena: &machine_st.arena, + tabu_list: IndexSet::with_hasher(FxBuildHasher::new()), + } + } +} + +#[derive(Debug)] +pub enum TermPair { + Vars(usize, usize), + Less(HeapCellValue, HeapCellValue), + Greater(HeapCellValue, HeapCellValue), + Unordered(HeapCellValue, HeapCellValue), +} + +impl ParallelHeapIter<'_> { + #[inline] + fn parallel_cmp( + &mut self, + v1: Cmp, + v2: Cmp, + h1: HeapCellValue, + h2: HeapCellValue, + ) -> Option { + match v1.cmp(&v2) { + Ordering::Greater => { + self.stack.clear(); + Some(TermPair::Greater(h1, h2)) + } + Ordering::Less => { + self.stack.clear(); + Some(TermPair::Less(h1, h2)) + } + Ordering::Equal => None, + } + } +} + +macro_rules! some_or_return { + ($e:expr) => { + if let Some(x) = $e { + return Some(x); + } + }; +} + +impl Iterator for ParallelHeapIter<'_> { + type Item = TermPair; + + fn next(&mut self) -> Option { + use crate::offset_table::F64Offset; + + while let Some(s1) = self.stack.pop() { + let s1 = heap_bound_deref(self.heap, s1); + + let s2 = self.stack.pop().unwrap(); + let s2 = heap_bound_deref(self.heap, s2); + + if s1 == s2 { + continue; + } + + let v1 = heap_bound_store(self.heap, s1); + let v2 = heap_bound_store(self.heap, s2); + + let order_cat_v1 = v1.order_category(self.heap); + let order_cat_v2 = v2.order_category(self.heap); + + some_or_return!(self.parallel_cmp(order_cat_v1, order_cat_v2, v1, v2)); + + match order_cat_v1 { + Some(TermOrderCategory::Variable) => { + let v1 = v1.get_value() as usize; + let v2 = v2.get_value() as usize; + + return Some(TermPair::Vars(v1, v2)); + } + Some(TermOrderCategory::FloatingPoint) => { + let v1_offset = cell_as_f64_offset!(v1); + let v2_offset = cell_as_f64_offset!(v2); + + let v1_f64 = self.arena.f64_tbl.get_entry(v1_offset); + let v2_f64 = self.arena.f64_tbl.get_entry(v2_offset); + + some_or_return!(self.parallel_cmp(v1_f64, v2_f64, v1, v2)); + } + Some(TermOrderCategory::Integer) => { + let v1_int = Number::try_from((v1, &self.arena.f64_tbl)).unwrap(); + let v2_int = Number::try_from((v2, &self.arena.f64_tbl)).unwrap(); + + some_or_return!(self.parallel_cmp(v1_int, v2_int, v1, v2)); + } + Some(TermOrderCategory::Atom) => { + read_heap_cell!(v1, + (HeapCellValueTag::Atom, (n1, _a1)) => { + read_heap_cell!(v2, + (HeapCellValueTag::Atom, (n2, _a2)) => { + some_or_return!(self.parallel_cmp(n1, n2, v1, v2)); + } + (HeapCellValueTag::Str, s) => { + let n2 = cell_as_atom_cell!(self.heap[s]) + .get_name(); + + some_or_return!(self.parallel_cmp(n1, n2, v1, v2)); + } + _ => { + unreachable!(); + } + ) + } + (HeapCellValueTag::Str, s) => { + let n1 = cell_as_atom_cell!(self.heap[s]) + .get_name(); + + read_heap_cell!(v2, + (HeapCellValueTag::Atom, (n2, _a2)) => { + some_or_return!(self.parallel_cmp(n1, n2, v1, v2)); + } + (HeapCellValueTag::Str, s) => { + let n2 = cell_as_atom_cell!(self.heap[s]) + .get_name(); + + some_or_return!(self.parallel_cmp(n1, n2, v1, v2)); + } + _ => { + unreachable!(); + } + ) + } + _ => { + unreachable!() + } + ) + } + Some(TermOrderCategory::Compound) => { + read_heap_cell!(v1, + (HeapCellValueTag::Lis, l1) => { + read_heap_cell!(v2, + (HeapCellValueTag::PStrLoc, l2) => { + if self.tabu_list.contains(&(l1, l2)) { + continue; + } + + self.tabu_list.insert((l1, l2)); + + // like the action of partial_string_to_stack here but the + // ordering of stack pushes is (crucially for comparison + // correctness) different. + let (c, succ_cell) = self.heap.last_str_char_and_tail(l2); + + self.stack.push(succ_cell); + self.stack.push(heap_loc_as_cell!(l1 + 1)); + + self.stack.push(char_as_cell!(c)); + self.stack.push(heap_loc_as_cell!(l1)); + } + (HeapCellValueTag::Lis, l2) => { + if self.tabu_list.contains(&(l1, l2)) { + continue; + } + + self.tabu_list.insert((l1, l2)); + + self.stack.push(self.heap[l2 + 1]); + self.stack.push(self.heap[l1 + 1]); + + self.stack.push(self.heap[l2]); + self.stack.push(self.heap[l1]); + } + (HeapCellValueTag::Str, s2) => { + if self.tabu_list.contains(&(l1, s2)) { + continue; + } + + let (n2, a2) = cell_as_atom_cell!(self.heap[s2]) + .get_name_and_arity(); + + some_or_return!(self.parallel_cmp((2, atom!(".")), (a2, n2), v1, v2)); + + self.tabu_list.insert((l1, s2)); + + self.stack.push(self.heap[s2 + 2]); + self.stack.push(self.heap[l1 + 1]); + + self.stack.push(self.heap[s2 + 1]); + self.stack.push(self.heap[l1]); + } + _ => { + unreachable!(); + } + ) + } + (HeapCellValueTag::PStrLoc, l1) => { + read_heap_cell!(v2, + (HeapCellValueTag::PStrLoc, l2) => { + if self.tabu_list.contains(&(l1, l2)) { + continue; + } + + match self.heap.compare_pstr_segments(l1, l2) { + PStrSegmentCmpResult::Continue(v1, v2) => { + self.tabu_list.insert((l1, l2)); + + self.stack.push(v1.offset_by(l1)); + self.stack.push(v2.offset_by(l2)); + } + PStrSegmentCmpResult::Less => { + self.stack.clear(); + return Some(TermPair::Less(v1, v2)); + } + PStrSegmentCmpResult::Greater => { + self.stack.clear(); + return Some(TermPair::Greater(v1, v2)); + } + } + } + (HeapCellValueTag::Lis, l2) => { + if self.tabu_list.contains(&(l1, l2)) { + continue; + } + + self.tabu_list.insert((l1, l2)); + + let (c, succ_cell) = self.heap.last_str_char_and_tail(l1); + + self.stack.push(succ_cell); + self.stack.push(heap_loc_as_cell!(l2 + 1)); + + self.stack.push(char_as_cell!(c)); + self.stack.push(heap_loc_as_cell!(l2)); + } + (HeapCellValueTag::Str, s2) => { + if self.tabu_list.contains(&(l1, s2)) { + continue; + } + + self.tabu_list.insert((l1, s2)); + + let (n2, a2) = cell_as_atom_cell!(self.heap[s2]) + .get_name_and_arity(); + + some_or_return!(self.parallel_cmp((2, atom!(".")), (a2, n2), v1, v2)); + + let (c, succ_cell) = self.heap.last_str_char_and_tail(l1); + + self.stack.push(heap_loc_as_cell!(s2+2)); + self.stack.push(succ_cell); + + self.stack.push(heap_loc_as_cell!(s2+1)); + self.stack.push(char_as_cell!(c)); + } + _ => { + unreachable!() + } + ); + } + (HeapCellValueTag::Str, s1) => { + read_heap_cell!(v2, + (HeapCellValueTag::Str, s2) => { + if self.tabu_list.contains(&(s1, s2)) { + continue; + } + + let (n1, a1) = cell_as_atom_cell!(self.heap[s1]) + .get_name_and_arity(); + + let (n2, a2) = cell_as_atom_cell!(self.heap[s2]) + .get_name_and_arity(); + + some_or_return!(self.parallel_cmp((a1, n1), (a2, n2), v1, v2)); + + self.tabu_list.insert((s1, s2)); + + for idx in (1 .. a1+1).rev() { + self.stack.push(self.heap[s2+idx]); + self.stack.push(self.heap[s1+idx]); + } + } + (HeapCellValueTag::Lis, l2) => { + if self.tabu_list.contains(&(s1, l2)) { + continue; + } + + let (n1, a1) = cell_as_atom_cell!(self.heap[s1]) + .get_name_and_arity(); + + some_or_return!(self.parallel_cmp((a1, n1), (2, atom!(".")), v1, v2)); + + self.stack.push(self.heap[l2]); + self.stack.push(self.heap[s1+1]); + + self.stack.push(self.heap[l2+1]); + self.stack.push(self.heap[s1+2]); + } + (HeapCellValueTag::PStrLoc, l2) => { + if self.tabu_list.contains(&(s1, l2)) { + continue; + } + + let (n1, a1) = cell_as_atom_cell!(self.heap[s1]) + .get_name_and_arity(); + + some_or_return!(self.parallel_cmp((a1, n1), (2, atom!(".")), v1, v2)); + + self.tabu_list.insert((s1, l2)); + + let (c, succ_cell) = self.heap.last_str_char_and_tail(l2); + + self.stack.push(succ_cell); + self.stack.push(heap_loc_as_cell!(s1+2)); + + self.stack.push(char_as_cell!(c)); + self.stack.push(heap_loc_as_cell!(s1+1)); + } + _ => { + unreachable!() + } + ) + } + _ => { + unreachable!() + } + ); + } + None => { + return Some(TermPair::Unordered(v1, v2)); + } + } + } + + None + } +} + #[inline(always)] pub fn eager_stackful_preorder_iter( heap: &mut Heap, diff --git a/src/instructions.rs b/src/instructions.rs index 076c2672..f029e463 100644 --- a/src/instructions.rs +++ b/src/instructions.rs @@ -726,7 +726,6 @@ impl Instruction { | &Instruction::CallDeleteAllAttributesFromVar | &Instruction::CallUnattributedVar | &Instruction::CallGetDBRefs - | &Instruction::CallKeySortWithConstantVarOrdering | &Instruction::CallInferenceLimitExceeded | &Instruction::CallFetchGlobalVar | &Instruction::CallFirstStream @@ -986,7 +985,6 @@ impl Instruction { | &Instruction::ExecuteDeleteAllAttributesFromVar | &Instruction::ExecuteUnattributedVar | &Instruction::ExecuteGetDBRefs - | &Instruction::ExecuteKeySortWithConstantVarOrdering | &Instruction::ExecuteInferenceLimitExceeded | &Instruction::ExecuteFetchGlobalVar | &Instruction::ExecuteFirstStream diff --git a/src/machine/attributed_variables.rs b/src/machine/attributed_variables.rs index fdedabcc..6b822b95 100644 --- a/src/machine/attributed_variables.rs +++ b/src/machine/attributed_variables.rs @@ -103,9 +103,8 @@ impl MachineState { .collect() }; - attr_vars.sort_unstable_by(|a1, a2| { - compare_term_test!(self, *a1, *a2).unwrap_or(Ordering::Less) - }); + attr_vars + .sort_unstable_by(|a1, a2| self.compare_term_test(*a1, *a2).unwrap_or(Ordering::Less)); attr_vars.dedup(); attr_vars diff --git a/src/machine/dispatch.rs b/src/machine/dispatch.rs index f67b0df2..d68f8efe 100644 --- a/src/machine/dispatch.rs +++ b/src/machine/dispatch.rs @@ -118,7 +118,7 @@ impl MachineState { } ); - let atom = match compare_term_test!(self, a2, a3) { + let atom = match self.compare_term_test(a2, a3) { Some(Ordering::Greater) => { atom!(">") } @@ -151,11 +151,8 @@ impl MachineState { let stub_gen = || functor_stub(atom!("sort"), 2); let mut list = self.try_from_list(self.registers[1], stub_gen)?; - list.sort_unstable_by(|v1, v2| { - compare_term_test!(self, *v1, *v2).unwrap_or(Ordering::Less) - }); - - list.dedup_by(|v1, v2| compare_term_test!(self, *v1, *v2) == Some(Ordering::Equal)); + list.sort_unstable_by(|v1, v2| self.compare_term_test(*v1, *v2).unwrap_or(Ordering::Less)); + list.dedup_by(|v1, v2| self.compare_term_test(*v1, *v2) == Some(Ordering::Equal)); let heap_addr = resource_error_call_result!( self, @@ -167,7 +164,7 @@ impl MachineState { Ok(()) } - fn keysort(&mut self, var_comparison: VarComparison) -> CallResult { + fn keysort(&mut self) -> CallResult { self.check_keysort_errors()?; let stub_gen = || functor_stub(atom!("keysort"), 2); @@ -180,9 +177,7 @@ impl MachineState { key_pairs.push((key, val)); } - key_pairs.sort_by(|a1, a2| { - compare_term_test!(self, a1.0, a2.0, var_comparison).unwrap_or(Ordering::Less) - }); + key_pairs.sort_by(|a1, a2| self.compare_term_test(a1.0, a2.0).unwrap_or(Ordering::Less)); let heap_addr = resource_error_call_result!( self, @@ -2034,8 +2029,7 @@ impl Machine { let a1 = self.machine_st.registers[1]; let a2 = self.machine_st.registers[2]; - if let Some(Ordering::Greater) = compare_term_test!(self.machine_st, a1, a2) - { + if let Some(Ordering::Greater) = self.machine_st.compare_term_test(a1, a2) { self.machine_st.p += 1; } else { self.machine_st.backtrack(); @@ -2045,8 +2039,7 @@ impl Machine { let a1 = self.machine_st.registers[1]; let a2 = self.machine_st.registers[2]; - if let Some(Ordering::Greater) = compare_term_test!(self.machine_st, a1, a2) - { + if let Some(Ordering::Greater) = self.machine_st.compare_term_test(a1, a2) { self.machine_st.p = self.machine_st.cp; } else { self.machine_st.backtrack(); @@ -2056,7 +2049,7 @@ impl Machine { let a1 = self.machine_st.registers[1]; let a2 = self.machine_st.registers[2]; - if let Some(Ordering::Less) = compare_term_test!(self.machine_st, a1, a2) { + if let Some(Ordering::Less) = self.machine_st.compare_term_test(a1, a2) { self.machine_st.p += 1; } else { self.machine_st.backtrack(); @@ -2066,7 +2059,7 @@ impl Machine { let a1 = self.machine_st.registers[1]; let a2 = self.machine_st.registers[2]; - if let Some(Ordering::Less) = compare_term_test!(self.machine_st, a1, a2) { + if let Some(Ordering::Less) = self.machine_st.compare_term_test(a1, a2) { self.machine_st.p = self.machine_st.cp; } else { self.machine_st.backtrack(); @@ -2076,7 +2069,7 @@ impl Machine { let a1 = self.machine_st.registers[1]; let a2 = self.machine_st.registers[2]; - match compare_term_test!(self.machine_st, a1, a2) { + match self.machine_st.compare_term_test(a1, a2) { Some(Ordering::Greater | Ordering::Equal) => { self.machine_st.p += 1; } @@ -2089,7 +2082,7 @@ impl Machine { let a1 = self.machine_st.registers[1]; let a2 = self.machine_st.registers[2]; - match compare_term_test!(self.machine_st, a1, a2) { + match self.machine_st.compare_term_test(a1, a2) { Some(Ordering::Greater | Ordering::Equal) => { self.machine_st.p = self.machine_st.cp; } @@ -2102,7 +2095,7 @@ impl Machine { let a1 = self.machine_st.registers[1]; let a2 = self.machine_st.registers[2]; - match compare_term_test!(self.machine_st, a1, a2) { + match self.machine_st.compare_term_test(a1, a2) { Some(Ordering::Less | Ordering::Equal) => { self.machine_st.p += 1; } @@ -2115,7 +2108,7 @@ impl Machine { let a1 = self.machine_st.registers[1]; let a2 = self.machine_st.registers[2]; - match compare_term_test!(self.machine_st, a1, a2) { + match self.machine_st.compare_term_test(a1, a2) { Some(Ordering::Less | Ordering::Equal) => { self.machine_st.p = self.machine_st.cp; } @@ -2188,7 +2181,7 @@ impl Machine { let a1 = self.machine_st.registers[1]; let a2 = self.machine_st.registers[2]; - if let Some(Ordering::Equal) = compare_term_test!(self.machine_st, a1, a2) { + if let Some(Ordering::Equal) = self.machine_st.compare_term_test(a1, a2) { self.machine_st.backtrack(); } else { self.machine_st.p += 1; @@ -2198,7 +2191,7 @@ impl Machine { let a1 = self.machine_st.registers[1]; let a2 = self.machine_st.registers[2]; - if let Some(Ordering::Equal) = compare_term_test!(self.machine_st, a1, a2) { + if let Some(Ordering::Equal) = self.machine_st.compare_term_test(a1, a2) { self.machine_st.backtrack(); } else { self.machine_st.p = self.machine_st.cp; @@ -2213,19 +2206,11 @@ impl Machine { step_or_fail!(self.machine_st, self.machine_st.p = self.machine_st.cp); } &Instruction::DefaultCallKeySort => { - try_or_throw!( - self.machine_st, - self.machine_st.keysort(VarComparison::Distinct), - continue - ); + try_or_throw!(self.machine_st, self.machine_st.keysort(), continue); step_or_fail!(self.machine_st, self.machine_st.p += 1); } &Instruction::DefaultExecuteKeySort => { - try_or_throw!( - self.machine_st, - self.machine_st.keysort(VarComparison::Distinct), - continue - ); + try_or_throw!(self.machine_st, self.machine_st.keysort(), continue); if self.machine_st.fail { self.machine_st.backtrack(); @@ -2323,8 +2308,7 @@ impl Machine { let a1 = self.machine_st.registers[1]; let a2 = self.machine_st.registers[2]; - if let Some(Ordering::Greater) = compare_term_test!(self.machine_st, a1, a2) - { + if let Some(Ordering::Greater) = self.machine_st.compare_term_test(a1, a2) { increment_call_count!(self.machine_st); self.machine_st.p += 1; } else { @@ -2335,8 +2319,7 @@ impl Machine { let a1 = self.machine_st.registers[1]; let a2 = self.machine_st.registers[2]; - if let Some(Ordering::Greater) = compare_term_test!(self.machine_st, a1, a2) - { + if let Some(Ordering::Greater) = self.machine_st.compare_term_test(a1, a2) { increment_call_count!(self.machine_st); self.machine_st.p = self.machine_st.cp; } else { @@ -2347,7 +2330,7 @@ impl Machine { let a1 = self.machine_st.registers[1]; let a2 = self.machine_st.registers[2]; - if let Some(Ordering::Less) = compare_term_test!(self.machine_st, a1, a2) { + if let Some(Ordering::Less) = self.machine_st.compare_term_test(a1, a2) { increment_call_count!(self.machine_st); self.machine_st.p += 1; } else { @@ -2358,7 +2341,7 @@ impl Machine { let a1 = self.machine_st.registers[1]; let a2 = self.machine_st.registers[2]; - if let Some(Ordering::Less) = compare_term_test!(self.machine_st, a1, a2) { + if let Some(Ordering::Less) = self.machine_st.compare_term_test(a1, a2) { increment_call_count!(self.machine_st); self.machine_st.p = self.machine_st.cp; } else { @@ -2369,7 +2352,7 @@ impl Machine { let a1 = self.machine_st.registers[1]; let a2 = self.machine_st.registers[2]; - match compare_term_test!(self.machine_st, a1, a2) { + match self.machine_st.compare_term_test(a1, a2) { Some(Ordering::Greater | Ordering::Equal) => { increment_call_count!(self.machine_st); self.machine_st.p += 1; @@ -2383,7 +2366,7 @@ impl Machine { let a1 = self.machine_st.registers[1]; let a2 = self.machine_st.registers[2]; - match compare_term_test!(self.machine_st, a1, a2) { + match self.machine_st.compare_term_test(a1, a2) { Some(Ordering::Greater | Ordering::Equal) => { increment_call_count!(self.machine_st); self.machine_st.p = self.machine_st.cp; @@ -2397,7 +2380,7 @@ impl Machine { let a1 = self.machine_st.registers[1]; let a2 = self.machine_st.registers[2]; - match compare_term_test!(self.machine_st, a1, a2) { + match self.machine_st.compare_term_test(a1, a2) { Some(Ordering::Less | Ordering::Equal) => { increment_call_count!(self.machine_st); self.machine_st.p += 1; @@ -2411,7 +2394,7 @@ impl Machine { let a1 = self.machine_st.registers[1]; let a2 = self.machine_st.registers[2]; - match compare_term_test!(self.machine_st, a1, a2) { + match self.machine_st.compare_term_test(a1, a2) { Some(Ordering::Less | Ordering::Equal) => { increment_call_count!(self.machine_st); self.machine_st.p = self.machine_st.cp; @@ -2503,7 +2486,7 @@ impl Machine { let a1 = self.machine_st.registers[1]; let a2 = self.machine_st.registers[2]; - if let Some(Ordering::Equal) = compare_term_test!(self.machine_st, a1, a2) { + if let Some(Ordering::Equal) = self.machine_st.compare_term_test(a1, a2) { self.machine_st.backtrack(); } else { increment_call_count!(self.machine_st); @@ -2514,7 +2497,7 @@ impl Machine { let a1 = self.machine_st.registers[1]; let a2 = self.machine_st.registers[2]; - if let Some(Ordering::Equal) = compare_term_test!(self.machine_st, a1, a2) { + if let Some(Ordering::Equal) = self.machine_st.compare_term_test(a1, a2) { self.machine_st.backtrack(); } else { increment_call_count!(self.machine_st); @@ -2542,11 +2525,7 @@ impl Machine { } } &Instruction::CallKeySort => { - try_or_throw!( - self.machine_st, - self.machine_st.keysort(VarComparison::Distinct), - continue - ); + try_or_throw!(self.machine_st, self.machine_st.keysort(), continue); if self.machine_st.fail { self.machine_st.backtrack(); @@ -2556,11 +2535,7 @@ impl Machine { } } &Instruction::ExecuteKeySort => { - try_or_throw!( - self.machine_st, - self.machine_st.keysort(VarComparison::Distinct), - continue - ); + try_or_throw!(self.machine_st, self.machine_st.keysort(), continue); if self.machine_st.fail { self.machine_st.backtrack(); @@ -2570,11 +2545,7 @@ impl Machine { } } &Instruction::CallKeySortWithConstantVarOrdering => { - try_or_throw!( - self.machine_st, - self.machine_st.keysort(VarComparison::Indistinct), - continue - ); + try_or_throw!(self.machine_st, self.machine_st.keysort(), continue); if self.machine_st.fail { self.machine_st.backtrack(); @@ -2584,11 +2555,7 @@ impl Machine { } } &Instruction::ExecuteKeySortWithConstantVarOrdering => { - try_or_throw!( - self.machine_st, - self.machine_st.keysort(VarComparison::Indistinct), - continue - ); + try_or_throw!(self.machine_st, self.machine_st.keysort(), continue); if self.machine_st.fail { self.machine_st.backtrack(); @@ -4813,6 +4780,14 @@ impl Machine { try_or_throw!(self.machine_st, self.argv(), continue); step_or_fail!(self.machine_st, self.machine_st.p = self.machine_st.cp); } + &Instruction::CallIsVariant => { + self.machine_st.fail = self.machine_st.is_not_variant(); + step_or_fail!(self.machine_st, self.machine_st.p += 1); + } + &Instruction::ExecuteIsVariant => { + self.machine_st.fail = self.machine_st.is_not_variant(); + step_or_fail!(self.machine_st, self.machine_st.p = self.machine_st.cp); + } &Instruction::CallCurrentTime => { self.current_time(); step_or_fail!(self.machine_st, self.machine_st.p += 1); diff --git a/src/machine/machine_state_impl.rs b/src/machine/machine_state_impl.rs index 7b777ae6..668a5bc0 100644 --- a/src/machine/machine_state_impl.rs +++ b/src/machine/machine_state_impl.rs @@ -7,7 +7,6 @@ use crate::machine::copier::*; use crate::machine::heap::AllocError; use crate::machine::heap::*; use crate::machine::machine_errors::*; -use crate::machine::machine_indices::*; use crate::machine::machine_state::*; use crate::machine::partial_string::*; use crate::machine::stack::*; @@ -17,8 +16,6 @@ use crate::parser::ast::*; use crate::parser::dashu::{Integer, Rational}; use crate::types::*; -use indexmap::IndexSet; - use std::cmp::Ordering; use std::convert::TryFrom; @@ -380,347 +377,6 @@ impl MachineState { } } - pub fn compare_term_test(&mut self, var_comparison: VarComparison) -> Option { - let mut tabu_list = IndexSet::new(); - - while let Some(s1) = self.pdl.pop() { - let s1 = self.deref(s1); - - let s2 = self.pdl.pop().unwrap(); - let s2 = self.deref(s2); - - if s1 == s2 { - continue; - } - - let v1 = self.store(s1); - let v2 = self.store(s2); - - let order_cat_v1 = v1.order_category(&self.heap); - let order_cat_v2 = v2.order_category(&self.heap); - - if order_cat_v1 != order_cat_v2 { - self.pdl.clear(); - return Some(order_cat_v1.cmp(&order_cat_v2)); - } - - match order_cat_v1 { - Some(TermOrderCategory::Variable) => { - if let VarComparison::Distinct = var_comparison { - let v1 = v1.as_var().unwrap(); - let v2 = v2.as_var().unwrap(); - - if v1 != v2 { - self.pdl.clear(); - return Some(v1.cmp(&v2)); - } - } - } - Some(TermOrderCategory::FloatingPoint) => { - let v1 = cell_as_f64_offset!(v1); - let v2 = cell_as_f64_offset!(v2); - - let v1 = self.arena.f64_tbl.get_entry(v1); - let v2 = self.arena.f64_tbl.get_entry(v2); - - if v1 != v2 { - self.pdl.clear(); - return Some(v1.cmp(&v2)); - } - } - Some(TermOrderCategory::Integer) => { - let v1 = Number::try_from((v1, &self.arena.f64_tbl)).unwrap(); - let v2 = Number::try_from((v2, &self.arena.f64_tbl)).unwrap(); - - if v1 != v2 { - self.pdl.clear(); - return Some(v1.cmp(&v2)); - } - } - Some(TermOrderCategory::Atom) => { - read_heap_cell!(v1, - (HeapCellValueTag::Atom, (n1, _a1)) => { - read_heap_cell!(v2, - (HeapCellValueTag::Atom, (n2, _a2)) => { - if n1 != n2 { - self.pdl.clear(); - return Some(n1.cmp(&n2)); - } - } - (HeapCellValueTag::Str, s) => { - let n2 = cell_as_atom_cell!(self.heap[s]) - .get_name(); - - if n1 != n2 { - self.pdl.clear(); - return Some(n1.cmp(&n2)); - } - } - _ => { - unreachable!(); - } - ) - } - (HeapCellValueTag::Str, s) => { - let n1 = cell_as_atom_cell!(self.heap[s]) - .get_name(); - - read_heap_cell!(v2, - (HeapCellValueTag::Atom, (n2, _a2)) => { - if n1 != n2 { - self.pdl.clear(); - return Some(n1.cmp(&n2)); - } - } - (HeapCellValueTag::Str, s) => { - let n2 = cell_as_atom_cell!(self.heap[s]) - .get_name(); - - if n1 != n2 { - self.pdl.clear(); - return Some(n1.cmp(&n2)); - } - } - _ => { - unreachable!(); - } - ) - } - _ => { - unreachable!() - } - ) - } - Some(TermOrderCategory::Compound) => { - read_heap_cell!(v1, - (HeapCellValueTag::Lis, l1) => { - read_heap_cell!(v2, - (HeapCellValueTag::PStrLoc, l2) => { - if tabu_list.contains(&(l1, l2)) { - continue; - } - - tabu_list.insert((l1, l2)); - - // like the action of - // partial_string_to_pdl here but - // the ordering of PDL pushes is - // (crucially for comparison - // correctness) different. - let (c, succ_cell) = self.heap.last_str_char_and_tail(l2); - - self.pdl.push(succ_cell); - self.pdl.push(heap_loc_as_cell!(l1 + 1)); - - self.pdl.push(char_as_cell!(c)); - self.pdl.push(heap_loc_as_cell!(l1)); - } - (HeapCellValueTag::Lis, l2) => { - if tabu_list.contains(&(l1, l2)) { - continue; - } - - tabu_list.insert((l1, l2)); - - self.pdl.push(self.heap[l2 + 1]); - self.pdl.push(self.heap[l1 + 1]); - - self.pdl.push(self.heap[l2]); - self.pdl.push(self.heap[l1]); - } - (HeapCellValueTag::Str, s2) => { - if tabu_list.contains(&(l1, s2)) { - continue; - } - - let (name, arity) = cell_as_atom_cell!(self.heap[s2]) - .get_name_and_arity(); - - match (2, atom!(".")).cmp(&(arity, name)) { - Ordering::Equal => { - tabu_list.insert((l1, s2)); - - self.pdl.push(self.heap[s2 + 2]); - self.pdl.push(self.heap[l1 + 1]); - - self.pdl.push(self.heap[s2 + 1]); - self.pdl.push(self.heap[l1]); - } - ordering => { - self.pdl.clear(); - return Some(ordering); - } - } - } - _ => { - unreachable!(); - } - ) - } - (HeapCellValueTag::PStrLoc, l1) => { - read_heap_cell!(v2, - (HeapCellValueTag::PStrLoc, l2) => { - if tabu_list.contains(&(l1, l2)) { - continue; - } - - tabu_list.insert((l1, l2)); - - match self.heap.compare_pstr_segments(l1, l2) { - PStrSegmentCmpResult::Continue(v1, v2) => { - self.pdl.push(v1.offset_by(l1)); - self.pdl.push(v2.offset_by(l2)); - } - PStrSegmentCmpResult::Less => { - self.pdl.clear(); - return Some(Ordering::Less); - } - PStrSegmentCmpResult::Greater => { - self.pdl.clear(); - return Some(Ordering::Greater); - } - } - } - (HeapCellValueTag::Lis, l2) => { - if tabu_list.contains(&(l1, l2)) { - continue; - } - - tabu_list.insert((l1, l2)); - - let (c, succ_cell) = self.heap.last_str_char_and_tail(l1); - - self.pdl.push(succ_cell); - self.pdl.push(heap_loc_as_cell!(l2 + 1)); - - self.pdl.push(char_as_cell!(c)); - self.pdl.push(heap_loc_as_cell!(l2)); - } - (HeapCellValueTag::Str, s2) => { - if tabu_list.contains(&(l1, s2)) { - continue; - } - - tabu_list.insert((l1, s2)); - - let (n2, a2) = cell_as_atom_cell!(self.heap[s2]) - .get_name_and_arity(); - - match (2, atom!(".")).cmp(&(a2,n2)) { - Ordering::Equal => { - let (c, succ_cell) = self.heap.last_str_char_and_tail(l1); - - self.pdl.push(heap_loc_as_cell!(s2+2)); - self.pdl.push(succ_cell); - - self.pdl.push(heap_loc_as_cell!(s2+1)); - self.pdl.push(char_as_cell!(c)); - } - ordering => { - self.pdl.clear(); - return Some(ordering); - } - } - } - _ => { - unreachable!() - } - ); - } - (HeapCellValueTag::Str, s1) => { - read_heap_cell!(v2, - (HeapCellValueTag::Str, s2) => { - if tabu_list.contains(&(s1, s2)) { - continue; - } - - let (n1, a1) = cell_as_atom_cell!(self.heap[s1]) - .get_name_and_arity(); - - let (n2, a2) = cell_as_atom_cell!(self.heap[s2]) - .get_name_and_arity(); - - match (a1,n1).cmp(&(a2, n2)) { - Ordering::Equal => { - tabu_list.insert((s1, s2)); - - for idx in (1 .. a1+1).rev() { - self.pdl.push(self.heap[s2+idx]); - self.pdl.push(self.heap[s1+idx]); - } - } - ordering => { - self.pdl.clear(); - return Some(ordering); - } - } - } - (HeapCellValueTag::Lis, l2) => { - if tabu_list.contains(&(s1, l2)) { - continue; - } - - tabu_list.insert((s1, l2)); - - let (n1, a1) = cell_as_atom_cell!(self.heap[s1]) - .get_name_and_arity(); - - match (a1,n1).cmp(&(2, atom!("."))) { - Ordering::Equal => { - self.pdl.push(self.heap[l2]); - self.pdl.push(self.heap[s1+1]); - - self.pdl.push(self.heap[l2+1]); - self.pdl.push(self.heap[s1+2]); - } - ordering => { - self.pdl.clear(); - return Some(ordering); - } - } - } - (HeapCellValueTag::PStrLoc, l2) => { - let (n1, a1) = cell_as_atom_cell!(self.heap[s1]) - .get_name_and_arity(); - - match (a1,n1).cmp(&(2, atom!("."))) { - Ordering::Equal => { - let (c, succ_cell) = self.heap.last_str_char_and_tail(l2); - - self.pdl.push(succ_cell); - self.pdl.push(heap_loc_as_cell!(s1+2)); - - self.pdl.push(char_as_cell!(c)); - self.pdl.push(heap_loc_as_cell!(s1+1)); - } - ordering => { - self.pdl.clear(); - return Some(ordering); - } - } - } - _ => { - unreachable!() - } - ) - } - _ => { - unreachable!() - } - ); - } - None => { - if v1 != v2 { - self.pdl.clear(); - return None; - } - } - } - } - - Some(Ordering::Equal) - } - pub(crate) fn setup_call_n_init_goal_info( &mut self, goal: HeapCellValue, @@ -891,16 +547,32 @@ impl MachineState { } // returns true on failure, false on success. - pub fn eq_test(&mut self, h1: HeapCellValue, h2: HeapCellValue) -> bool { + pub fn eq_test(&self, h1: HeapCellValue, h2: HeapCellValue) -> bool { if h1 == h2 { return false; } - compare_term_test!(self, h1, h2) + self.compare_term_test(h1, h2) .map(|o| o != Ordering::Equal) .unwrap_or(true) } + pub fn compare_term_test(&self, h1: HeapCellValue, h2: HeapCellValue) -> Option { + for term_pair in ParallelHeapIter::from(self, h1, h2) { + match term_pair { + TermPair::Vars(v1_offset, v2_offset) if v1_offset != v2_offset => { + return Some(v1_offset.cmp(&v2_offset)); + } + TermPair::Less(..) => return Some(Ordering::Less), + TermPair::Greater(..) => return Some(Ordering::Greater), + TermPair::Unordered(cell_1, cell_2) if cell_1 != cell_2 => return None, + _ => {} + } + } + + Some(Ordering::Equal) + } + #[inline(always)] fn try_functor_compound_case(&mut self, name: Atom, arity: usize) { self.try_functor_unify_components(atom_as_cell!(name), arity); diff --git a/src/machine/mock_wam.rs b/src/machine/mock_wam.rs index 7fcc5613..082ed62c 100644 --- a/src/machine/mock_wam.rs +++ b/src/machine/mock_wam.rs @@ -581,56 +581,44 @@ mod tests { }); assert_eq!( - compare_term_test!(wam, wam.heap[0], wam.heap[1]), + wam.compare_term_test(wam.heap[0], wam.heap[1]), Some(Ordering::Less) ); assert_eq!( - compare_term_test!(wam, wam.heap[1], wam.heap[0]), + wam.compare_term_test(wam.heap[1], wam.heap[0]), Some(Ordering::Greater) ); assert_eq!( - compare_term_test!(wam, wam.heap[0], wam.heap[0]), + wam.compare_term_test(wam.heap[0], wam.heap[0]), Some(Ordering::Equal) ); assert_eq!( - compare_term_test!(wam, wam.heap[1], wam.heap[1]), + wam.compare_term_test(wam.heap[1], wam.heap[1]), Some(Ordering::Equal) ); let cstr_cell = wam.heap.allocate_cstr("string").unwrap(); assert_eq!( - compare_term_test!(wam, atom_as_cell!(atom!("atom")), cstr_cell), + wam.compare_term_test(atom_as_cell!(atom!("atom")), cstr_cell), Some(Ordering::Less) ); assert_eq!( - compare_term_test!( - wam, - atom_as_cell!(atom!("atom")), - atom_as_cell!(atom!("atom")) - ), + wam.compare_term_test(atom_as_cell!(atom!("atom")), atom_as_cell!(atom!("atom"))), Some(Ordering::Equal) ); assert_eq!( - compare_term_test!( - wam, - atom_as_cell!(atom!("atom")), - atom_as_cell!(atom!("aaa")) - ), + wam.compare_term_test(atom_as_cell!(atom!("atom")), atom_as_cell!(atom!("aaa"))), Some(Ordering::Greater) ); assert_eq!( - compare_term_test!( - wam, - fixnum_as_cell!(Fixnum::build_with(6)), - heap_loc_as_cell!(1) - ), + wam.compare_term_test(fixnum_as_cell!(Fixnum::build_with(6)), heap_loc_as_cell!(1)), Some(Ordering::Greater) ); @@ -644,12 +632,12 @@ mod tests { }); assert_eq!( - compare_term_test!(wam, heap_loc_as_cell!(0), heap_loc_as_cell!(0)), + wam.compare_term_test(heap_loc_as_cell!(0), heap_loc_as_cell!(0)), Some(Ordering::Equal) ); assert_eq!( - compare_term_test!(wam, heap_loc_as_cell!(0), atom_as_cell!(atom!("a"))), + wam.compare_term_test(heap_loc_as_cell!(0), atom_as_cell!(atom!("a"))), Some(Ordering::Greater) ); @@ -676,23 +664,22 @@ mod tests { }); assert_eq!( - compare_term_test!(wam, heap_loc_as_cell!(7), heap_loc_as_cell!(7)), + wam.compare_term_test(heap_loc_as_cell!(7), heap_loc_as_cell!(7)), Some(Ordering::Equal) ); assert_eq!( - compare_term_test!(wam, heap_loc_as_cell!(0), heap_loc_as_cell!(7)), + wam.compare_term_test(heap_loc_as_cell!(0), heap_loc_as_cell!(7)), Some(Ordering::Greater) ); assert_eq!( - compare_term_test!(wam, empty_list_as_cell!(), heap_loc_as_cell!(7)), + wam.compare_term_test(empty_list_as_cell!(), heap_loc_as_cell!(7)), Some(Ordering::Less) ); assert_eq!( - compare_term_test!( - wam, + wam.compare_term_test( empty_list_as_cell!(), fixnum_as_cell!(Fixnum::build_with(1)) ), @@ -702,29 +689,29 @@ mod tests { let cstr_cell = wam.heap.allocate_cstr("string").unwrap(); assert_eq!( - compare_term_test!(wam, empty_list_as_cell!(), cstr_cell), + wam.compare_term_test(empty_list_as_cell!(), cstr_cell), Some(Ordering::Less) ); assert_eq!( - compare_term_test!(wam, empty_list_as_cell!(), atom_as_cell!(atom!("atom"))), + wam.compare_term_test(empty_list_as_cell!(), atom_as_cell!(atom!("atom"))), Some(Ordering::Less) ); assert_eq!( - compare_term_test!(wam, atom_as_cell!(atom!("atom")), empty_list_as_cell!()), + wam.compare_term_test(atom_as_cell!(atom!("atom")), empty_list_as_cell!()), Some(Ordering::Greater) ); let one_p_one = HeapCellValue::from(float_alloc!(1.1, &mut wam.arena)); assert_eq!( - compare_term_test!(wam, one_p_one, fixnum_as_cell!(Fixnum::build_with(1))), + wam.compare_term_test(one_p_one, fixnum_as_cell!(Fixnum::build_with(1))), Some(Ordering::Less) ); assert_eq!( - compare_term_test!(wam, fixnum_as_cell!(Fixnum::build_with(1)), one_p_one), + wam.compare_term_test(fixnum_as_cell!(Fixnum::build_with(1)), one_p_one), Some(Ordering::Greater) ); } diff --git a/src/machine/system_calls.rs b/src/machine/system_calls.rs index d2db935e..aff084db 100644 --- a/src/machine/system_calls.rs +++ b/src/machine/system_calls.rs @@ -38,7 +38,7 @@ use rand::{Rng, SeedableRng}; use ordered_float::OrderedFloat; use fxhash::{FxBuildHasher, FxHasher}; -use indexmap::IndexSet; +use indexmap::*; use std::cell::Cell; use std::cmp::Ordering; @@ -574,6 +574,53 @@ pub(crate) struct FindallCopyInfo { } impl MachineState { + // determine whether two terms are variants, i.e. if there exists + // a bijection between their variable sets such that applying it + // to h1 produces h2 (ISO Prolog standard section 7.1.6.1). + // return true on failure and false on success. + #[inline(always)] + pub fn is_not_variant(&self) -> bool { + let h1 = self.registers[1]; + let h2 = self.registers[2]; + + let mut a_to_b = IndexMap::with_hasher(FxBuildHasher::default()); + let mut b_to_a = IndexMap::with_hasher(FxBuildHasher::default()); + + for term_pair in ParallelHeapIter::from(self, h1, h2) { + match term_pair { + TermPair::Vars(v1_offset, v2_offset) => { + match a_to_b.entry(v1_offset) { + indexmap::map::Entry::Occupied(stored_v2_offset) => { + if v2_offset != *stored_v2_offset.get() { + return true; + } + } + indexmap::map::Entry::Vacant(entry) => { + entry.insert_entry(v2_offset); + } + } + + match b_to_a.entry(v2_offset) { + indexmap::map::Entry::Occupied(stored_v1_offset) => { + if v1_offset != *stored_v1_offset.get() { + return true; + } + } + indexmap::map::Entry::Vacant(entry) => { + entry.insert_entry(v1_offset); + } + } + } + TermPair::Less(..) => return true, + TermPair::Greater(..) => return true, + TermPair::Unordered(cell_1, cell_2) if cell_1 != cell_2 => return true, + _ => {} + } + } + + false + } + fn copy_lifted_heap_from_offset(&mut self, offset: usize, lh_offset: usize) { let reserve_size = self.lifted_heap.cell_len() - lh_offset; let mut writer = step_or_resource_error!(self, self.heap.reserve(reserve_size)); diff --git a/src/macros.rs b/src/macros.rs index 7e7853dc..8d60e959 100644 --- a/src/macros.rs +++ b/src/macros.rs @@ -426,21 +426,6 @@ macro_rules! unify_with_occurs_check { }}; } -macro_rules! compare_term_test { - ($machine_st:expr, $e1:expr, $e2:expr) => {{ - $machine_st.pdl.push($e2); - $machine_st.pdl.push($e1); - - $machine_st.compare_term_test(VarComparison::Distinct) - }}; - ($machine_st:expr, $e1:expr, $e2:expr, $var_comparison:expr) => {{ - $machine_st.pdl.push($e2); - $machine_st.pdl.push($e1); - - $machine_st.compare_term_test($var_comparison) - }}; -} - macro_rules! step_or_resource_error { ($machine_st:expr, $val:expr) => {{ match $val { From 6284aa3a3f2ffe99985f6c66601052fffc323a66 Mon Sep 17 00:00:00 2001 From: Mark Thom Date: Fri, 12 Dec 2025 18:05:47 -0800 Subject: [PATCH 04/12] add variant_hash and is_non_variant to fix setof/3, bagof/3 --- Cargo.lock | 26 +++- Cargo.toml | 1 + build/instructions_template.rs | 2 + src/forms.rs | 6 - src/heap_iter.rs | 7 +- src/lib/builtins.pl | 59 +--------- src/machine/dispatch.rs | 42 +++---- src/machine/machine_state_impl.rs | 6 +- src/machine/mock_wam.rs | 4 +- src/machine/mod.rs | 1 + src/machine/system_calls.rs | 47 -------- src/machine/variant_hashing.rs | 190 ++++++++++++++++++++++++++++++ 12 files changed, 248 insertions(+), 143 deletions(-) create mode 100644 src/machine/variant_hashing.rs diff --git a/Cargo.lock b/Cargo.lock index 4fa61925..a605ccf3 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -45,6 +45,12 @@ version = "0.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "250f629c0161ad8107cf89319e990051fae62832fd343083bea452d93e2205fd" +[[package]] +name = "allocator-api2" +version = "0.2.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "683d7910e743518b0e34f1186f92494becacb047c7b6bf616c96772180fef923" + [[package]] name = "android-tzdata" version = "0.1.1" @@ -872,6 +878,12 @@ version = "1.0.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" +[[package]] +name = "foldhash" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77ce24cb58228fbb8aa041425bb1050850ac19177686ea6e0f41a70416f56fdb" + [[package]] name = "foreign-types" version = "0.3.2" @@ -1112,6 +1124,17 @@ version = "0.15.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "84b26c544d002229e640969970a2e74021aadf6e2f96372b9c58eff97de08eb3" +[[package]] +name = "hashbrown" +version = "0.16.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "841d1cc9bed7f9236f321df977030373f4a4163ae1a7dbfe1a51a2c1a51d9100" +dependencies = [ + "allocator-api2", + "equivalent", + "foldhash", +] + [[package]] name = "headers" version = "0.3.9" @@ -1456,7 +1479,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cea70ddb795996207ad57735b50c5982d8844f38ba9ee5f1aedcfb708a2aa11e" dependencies = [ "equivalent", - "hashbrown", + "hashbrown 0.15.3", ] [[package]] @@ -2734,6 +2757,7 @@ dependencies = [ "fxhash", "getrandom 0.2.16", "git-version", + "hashbrown 0.16.1", "hostname", "iai-callgrind", "indexmap", diff --git a/Cargo.toml b/Cargo.toml index 5971a229..88682b69 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -88,6 +88,7 @@ ego-tree = "0.10.0" serde_json = "1.0.122" serde = "1.0.204" parking_lot = "0.12.4" +hashbrown = "0.16.1" [target.'cfg(not(target_arch = "wasm32"))'.dependencies] crossterm = { version = "0.28.1", optional = true } diff --git a/build/instructions_template.rs b/build/instructions_template.rs index 8971f505..eb67213a 100644 --- a/build/instructions_template.rs +++ b/build/instructions_template.rs @@ -649,6 +649,8 @@ enum SystemClauseType { Argv, #[strum_discriminants(strum(props(Arity = "2", Name = "$variant")))] IsVariant, + #[strum_discriminants(strum(props(Arity = "2", Name = "$group_by_variant")))] + GroupByVariant, Repl(ReplCodePtr), } diff --git a/src/forms.rs b/src/forms.rs index d6cc39d4..65939d58 100644 --- a/src/forms.rs +++ b/src/forms.rs @@ -44,12 +44,6 @@ impl AppendOrPrepend { } } -#[derive(Debug, Clone, Copy)] -pub enum VarComparison { - Indistinct, - Distinct, -} - #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] pub enum Level { Deep, diff --git a/src/heap_iter.rs b/src/heap_iter.rs index 05b91293..903fcbc2 100644 --- a/src/heap_iter.rs +++ b/src/heap_iter.rs @@ -47,6 +47,7 @@ impl<'a> ParallelHeapIter<'a> { } #[derive(Debug)] +#[allow(dead_code)] pub enum TermPair { Vars(usize, usize), Less(HeapCellValue, HeapCellValue), @@ -97,10 +98,6 @@ impl Iterator for ParallelHeapIter<'_> { let s2 = self.stack.pop().unwrap(); let s2 = heap_bound_deref(self.heap, s2); - if s1 == s2 { - continue; - } - let v1 = heap_bound_store(self.heap, s1); let v2 = heap_bound_store(self.heap, s2); @@ -392,7 +389,7 @@ pub struct EagerStackfulPreOrderHeapIter<'a> { start_value: HeapCellValue, iter_stack: Vec, mark_phase: bool, - heap: &'a mut Heap, + pub heap: &'a mut Heap, } impl<'a> Drop for EagerStackfulPreOrderHeapIter<'a> { diff --git a/src/lib/builtins.pl b/src/lib/builtins.pl index 76a53420..7dea6d3e 100644 --- a/src/lib/builtins.pl +++ b/src/lib/builtins.pl @@ -956,54 +956,6 @@ set_difference([], _, []) :- !. set_difference(Xs, [], Xs). -% variant/2 checks whether X is a variant of Y per the definition in -% 7.1.6.1 of the ISO standard. - -:- non_counted_backtracking variant/4. - -variant(X,Y,VPs,VPs0) :- - ( var(X) -> - var(Y), - VPs = [X-Y|VPs0] - ; var(Y) -> - false - ; X =.. [FX | XArgs], - Y =.. [FX | YArgs], - lists:foldl('$call'(builtins:variant), XArgs, YArgs, VPs, VPs0) - ). - -:- non_counted_backtracking variant/2. - -singleton([_]). - -variant(X, Y) :- - variant(X,Y, VPs, []), - keysort(VPs, SVPs), - pairs:group_pairs_by_key(SVPs, SVPKs), - pairs:pairs_values(SVPKs, Vals), - lists:maplist('$call'(builtins:term_variables), Vals, Vs), - lists:maplist('$call'(builtins:singleton), Vs), - term_variables(Vs, YVars), - lists:length(SVPKs, N), - lists:length(YVars, N). - - -:- non_counted_backtracking group_by_variant/4. - -group_by_variant([V2-S2 | Pairs], V1-S1, [S2 | Solutions], Pairs0) :- - variant(V1, V2), - !, - V1 = V2, - group_by_variant(Pairs, V2-S2, Solutions, Pairs0). -group_by_variant(Pairs, _, [], Pairs). - -:- non_counted_backtracking group_by_variants/2. - -group_by_variants([V-S|Pairs], [V-Solution|Solutions]) :- - group_by_variant([V-S|Pairs], V-S, Solution, Pairs0), - group_by_variants(Pairs0, Solutions). -group_by_variants([], []). - :- non_counted_backtracking iterate_variants/3. iterate_variants([V-Solution|GroupSolutions], V, Solution) :- @@ -1074,9 +1026,8 @@ bagof(Template, Goal, Solution) :- term_variables(Goal, GoalVars), term_variables(TemplateVars+GoalVars, TGVs), lists:append(TemplateVars, Witnesses0, TGVs), - findall_with_existential(Template, Goal, PairedSolutions0, Witnesses0, Witnesses), - keysort(PairedSolutions0, PairedSolutions), - group_by_variants(PairedSolutions, GroupedSolutions), + findall_with_existential(Template, Goal, PairedSolutions, Witnesses0, Witnesses), + '$group_by_variant'(PairedSolutions, GroupedSolutions), iterate_variants(GroupedSolutions, Witnesses, Solution). :- non_counted_backtracking iterate_variants_and_sort/3. @@ -1090,7 +1041,6 @@ iterate_variants_and_sort([V-Solution0|GroupSolutions], V, Solution) :- iterate_variants_and_sort([_|GroupSolutions], Ws, Solution) :- iterate_variants_and_sort(GroupSolutions, Ws, Solution). - :- meta_predicate(setof(?, 0, ?)). :- non_counted_backtracking setof/3. @@ -1112,9 +1062,8 @@ setof(Template, Goal, Solution) :- term_variables(Goal, GoalVars), term_variables(TemplateVars+GoalVars, TGVs), lists:append(TemplateVars, Witnesses0, TGVs), - findall_with_existential(Template, Goal, PairedSolutions0, Witnesses0, Witnesses), - '$keysort_with_constant_var_ordering'(PairedSolutions0, PairedSolutions), % see 7.2.1 - group_by_variants(PairedSolutions, GroupedSolutions), + findall_with_existential(Template, Goal, PairedSolutions, Witnesses0, Witnesses), + '$group_by_variant'(PairedSolutions, GroupedSolutions), iterate_variants_and_sort(GroupedSolutions, Witnesses, Solution). % Clause retrieval and information. diff --git a/src/machine/dispatch.rs b/src/machine/dispatch.rs index d68f8efe..900f55dc 100644 --- a/src/machine/dispatch.rs +++ b/src/machine/dispatch.rs @@ -156,7 +156,7 @@ impl MachineState { let heap_addr = resource_error_call_result!( self, - sized_iter_to_heap_list(&mut self.heap, list.len(), list.into_iter(),) + sized_iter_to_heap_list(&mut self.heap, list.len(), list.into_iter()) ); let target_addr = self.registers[2]; @@ -173,7 +173,7 @@ impl MachineState { let mut key_pairs = Vec::with_capacity(list.len()); for val in list { - let key = self.project_onto_key(val)?; + let (key, _) = self.key_val_pair(val)?; key_pairs.push((key, val)); } @@ -2544,26 +2544,6 @@ impl Machine { self.machine_st.p = self.machine_st.cp; } } - &Instruction::CallKeySortWithConstantVarOrdering => { - try_or_throw!(self.machine_st, self.machine_st.keysort(), continue); - - if self.machine_st.fail { - self.machine_st.backtrack(); - } else { - increment_call_count!(self.machine_st); - self.machine_st.p += 1; - } - } - &Instruction::ExecuteKeySortWithConstantVarOrdering => { - try_or_throw!(self.machine_st, self.machine_st.keysort(), continue); - - if self.machine_st.fail { - self.machine_st.backtrack(); - } else { - increment_call_count!(self.machine_st); - self.machine_st.p = self.machine_st.cp; - } - } &Instruction::CallIs(r, at) => { try_or_throw!(self.machine_st, self.machine_st.is(r, at), continue); @@ -4781,11 +4761,25 @@ impl Machine { step_or_fail!(self.machine_st, self.machine_st.p = self.machine_st.cp); } &Instruction::CallIsVariant => { - self.machine_st.fail = self.machine_st.is_not_variant(); + self.machine_st.fail = self.machine_st.is_non_variant( + self.machine_st.registers[1], + self.machine_st.registers[2], + ); step_or_fail!(self.machine_st, self.machine_st.p += 1); } &Instruction::ExecuteIsVariant => { - self.machine_st.fail = self.machine_st.is_not_variant(); + self.machine_st.fail = self.machine_st.is_non_variant( + self.machine_st.registers[1], + self.machine_st.registers[2], + ); + step_or_fail!(self.machine_st, self.machine_st.p = self.machine_st.cp); + } + &Instruction::CallGroupByVariant => { + try_or_throw!(self.machine_st, self.machine_st.group_by_variant(), continue); + step_or_fail!(self.machine_st, self.machine_st.p += 1); + } + &Instruction::ExecuteGroupByVariant => { + try_or_throw!(self.machine_st, self.machine_st.group_by_variant(), continue); step_or_fail!(self.machine_st, self.machine_st.p = self.machine_st.cp); } &Instruction::CallCurrentTime => { diff --git a/src/machine/machine_state_impl.rs b/src/machine/machine_state_impl.rs index 668a5bc0..393b646f 100644 --- a/src/machine/machine_state_impl.rs +++ b/src/machine/machine_state_impl.rs @@ -553,7 +553,7 @@ impl MachineState { } self.compare_term_test(h1, h2) - .map(|o| o != Ordering::Equal) + .map(|o| !o.is_eq()) .unwrap_or(true) } @@ -934,7 +934,7 @@ impl MachineState { } // see 8.4.4.3 of Draft Technical Corrigendum 2 for an error guide. - pub fn project_onto_key(&mut self, value: HeapCellValue) -> Result { + pub fn key_val_pair(&mut self, value: HeapCellValue) -> Result<(HeapCellValue, HeapCellValue), MachineStub> { let stub_gen = || functor_stub(atom!("keysort"), 2); let store_v = self.store(self.deref(value)); @@ -948,7 +948,7 @@ impl MachineState { let (name, arity) = cell_as_atom_cell!(self.heap[s]).get_name_and_arity(); if name == atom!("-") && arity == 2 { - Ok(heap_loc_as_cell!(s + 1)) + Ok((heap_loc_as_cell!(s+1), heap_loc_as_cell!(s+2))) } else { let err = self.type_error(ValidType::Pair, self.heap[s]); Err(self.error_form(err, stub_gen())) diff --git a/src/machine/mock_wam.rs b/src/machine/mock_wam.rs index 082ed62c..b871e0fc 100644 --- a/src/machine/mock_wam.rs +++ b/src/machine/mock_wam.rs @@ -632,12 +632,12 @@ mod tests { }); assert_eq!( - wam.compare_term_test(heap_loc_as_cell!(0), heap_loc_as_cell!(0)), + wam.compare_term_test(str_loc_as_cell!(0), str_loc_as_cell!(0)), Some(Ordering::Equal) ); assert_eq!( - wam.compare_term_test(heap_loc_as_cell!(0), atom_as_cell!(atom!("a"))), + wam.compare_term_test(str_loc_as_cell!(0), atom_as_cell!(atom!("a"))), Some(Ordering::Greater) ); diff --git a/src/machine/mod.rs b/src/machine/mod.rs index 859290a4..42999e07 100644 --- a/src/machine/mod.rs +++ b/src/machine/mod.rs @@ -27,6 +27,7 @@ pub mod streams; pub mod system_calls; pub mod term_stream; pub mod unify; +pub mod variant_hashing; use crate::arena::*; use crate::arithmetic::*; diff --git a/src/machine/system_calls.rs b/src/machine/system_calls.rs index aff084db..3a6c321b 100644 --- a/src/machine/system_calls.rs +++ b/src/machine/system_calls.rs @@ -574,53 +574,6 @@ pub(crate) struct FindallCopyInfo { } impl MachineState { - // determine whether two terms are variants, i.e. if there exists - // a bijection between their variable sets such that applying it - // to h1 produces h2 (ISO Prolog standard section 7.1.6.1). - // return true on failure and false on success. - #[inline(always)] - pub fn is_not_variant(&self) -> bool { - let h1 = self.registers[1]; - let h2 = self.registers[2]; - - let mut a_to_b = IndexMap::with_hasher(FxBuildHasher::default()); - let mut b_to_a = IndexMap::with_hasher(FxBuildHasher::default()); - - for term_pair in ParallelHeapIter::from(self, h1, h2) { - match term_pair { - TermPair::Vars(v1_offset, v2_offset) => { - match a_to_b.entry(v1_offset) { - indexmap::map::Entry::Occupied(stored_v2_offset) => { - if v2_offset != *stored_v2_offset.get() { - return true; - } - } - indexmap::map::Entry::Vacant(entry) => { - entry.insert_entry(v2_offset); - } - } - - match b_to_a.entry(v2_offset) { - indexmap::map::Entry::Occupied(stored_v1_offset) => { - if v1_offset != *stored_v1_offset.get() { - return true; - } - } - indexmap::map::Entry::Vacant(entry) => { - entry.insert_entry(v1_offset); - } - } - } - TermPair::Less(..) => return true, - TermPair::Greater(..) => return true, - TermPair::Unordered(cell_1, cell_2) if cell_1 != cell_2 => return true, - _ => {} - } - } - - false - } - fn copy_lifted_heap_from_offset(&mut self, offset: usize, lh_offset: usize) { let reserve_size = self.lifted_heap.cell_len() - lh_offset; let mut writer = step_or_resource_error!(self, self.heap.reserve(reserve_size)); diff --git a/src/machine/variant_hashing.rs b/src/machine/variant_hashing.rs new file mode 100644 index 00000000..42a7e18a --- /dev/null +++ b/src/machine/variant_hashing.rs @@ -0,0 +1,190 @@ +use crate::forms::*; +use crate::heap_iter::*; +use crate::types::*; +use crate::machine::*; +use crate::machine::heap::*; + +use fxhash::{FxHasher, FxBuildHasher}; +use hashbrown::{HashTable}; + +use std::hash::{Hash, Hasher}; + +impl MachineState { + // determine whether two terms are variants, i.e. if there exists + // a bijection between their variable sets such that applying it + // to h1 produces h2 (ISO Prolog standard section 7.1.6.1). + // return false on success and true on failure like eq_test. + #[inline(always)] + pub fn is_non_variant(&self, h1: HeapCellValue, h2: HeapCellValue) -> bool { + let mut a_to_b = IndexMap::with_hasher(FxBuildHasher::default()); + let mut b_to_a = IndexMap::with_hasher(FxBuildHasher::default()); + + for term_pair in ParallelHeapIter::from(self, h1, h2) { + match term_pair { + TermPair::Vars(v1_offset, v2_offset) => { + match a_to_b.entry(v1_offset) { + indexmap::map::Entry::Occupied(stored_v2_offset) => { + if v2_offset != *stored_v2_offset.get() { + return true; + } + } + indexmap::map::Entry::Vacant(entry) => { + entry.insert_entry(v2_offset); + } + } + + match b_to_a.entry(v2_offset) { + indexmap::map::Entry::Occupied(stored_v1_offset) => { + if v1_offset != *stored_v1_offset.get() { + return true; + } + } + indexmap::map::Entry::Vacant(entry) => { + entry.insert_entry(v1_offset); + } + } + } + TermPair::Less(..) => return true, + TermPair::Greater(..) => return true, + TermPair::Unordered(cell_1, cell_2) if cell_1 != cell_2 => return true, + _ => {} + } + } + + false + } + + fn variant_hash(&mut self, cell: HeapCellValue) -> u64 { + let mut var_ids = IndexMap::with_hasher(FxBuildHasher::default()); + let mut hasher = FxHasher::default(); + let mut iter = eager_stackful_preorder_iter(&mut self.heap, cell); + let mut next_var_id = 0; + + while let Some(term) = iter.next() { + read_heap_cell!(term, + (HeapCellValueTag::Str, s) => { + let (name, arity) = cell_as_atom_cell!(iter.heap[s]).get_name_and_arity(); + (name.index, arity).hash(&mut hasher); + } + (HeapCellValueTag::Lis) => { + (atom!(".").index, 2).hash(&mut hasher); + } + (HeapCellValueTag::PStrLoc, l) => { + let string = iter.heap.scan_slice_to_str(l).string; + + for c in string.chars() { + (atom!(".").index, 2).hash(&mut hasher); + hasher.write_u64(AtomCell::new_char_inlined(c).get_name().index); + } + } + (HeapCellValueTag::Atom, (name, arity)) => { + debug_assert_eq!(arity, 0); + (name.index, arity).hash(&mut hasher); + } + (HeapCellValueTag::AttrVar | HeapCellValueTag::Var, h) => { + let canonical_id = var_ids.entry(h).or_insert_with(|| { + let id = next_var_id; + next_var_id += 1; + id + }); + + hasher.write_u64(*canonical_id); + } + _ => { + if let Some(n) = Number::try_from((term, &self.arena.f64_tbl)).ok() { + match n { + Number::Float(f) => f.hash(&mut hasher), + Number::Integer(n) => n.hash(&mut hasher), + Number::Rational(r) => r.hash(&mut hasher), + Number::Fixnum(f) => f.hash(&mut hasher), + } + } else { + term.hash(&mut hasher); + } + } + ); + } + + hasher.finish() + } + + pub fn group_by_variant(&mut self) -> CallResult { + let stub_gen = || functor_stub(atom!("$group_by_variant"), 2); + let list = self.try_from_list(self.registers[1], stub_gen)?; + + let mut key_pairs = Vec::with_capacity(list.len()); + + for val in list { + key_pairs.push(self.key_val_pair(val)?); + } + + // the first parameter is the hash. Rust forces us to store it + // because of non-lexical lifetime hell between + // HashTable::find_mut and HashTable::insert_unique. also + // avoid computing the same hash repeatedly + let mut table: HashTable<(u64, Vec, Vec)> = HashTable::new(); + + for (key, val) in key_pairs { + let hash = self.variant_hash(key); + + match table.find_mut(hash, |(_, keys, _)| !self.is_non_variant(key, keys[0])) { + Some((_, keys, vals)) => { + keys.push(key); + vals.push(val); + } + None => { + table.insert_unique(hash, (hash, vec![key], vec![val]), |(h, _, _)| *h); + } + } + } + + let mut list_of_lists = Vec::with_capacity(table.len()); + + for (_, keys, variants) in table { + if let None = keys.windows(2).try_for_each(|cells| { + unify_fn!(*self, cells[0], cells[1]); + if self.fail { None } else { Some(()) } + }) { + return Ok(()); + } + + let variant_list_cell = resource_error_call_result!( + self, + sized_iter_to_heap_list( + &mut self.heap, + variants.len(), + variants.into_iter(), + ) + ); + + let mut writer = resource_error_call_result!(self, self.heap.reserve(3)); + + let key_val_cell = writer.write_with(|section| { + let key_val_cell = str_loc_as_cell!(section.cell_len()); + + section.push_cell(atom_as_cell!(atom!("-"), 2)); + section.push_cell(keys[0]); + section.push_cell(variant_list_cell); + + key_val_cell + }).result; + + list_of_lists.push(key_val_cell); + } + + let variant_grouped_list = resource_error_call_result!( + self, + sized_iter_to_heap_list( + &mut self.heap, + list_of_lists.len(), + list_of_lists.into_iter(), + ) + ); + + let target_addr = self.registers[2]; + unify_fn!(*self, target_addr, variant_grouped_list); + Ok(()) + } +} + + From e2bdf59c8158d995686b120dcdaaab9bc7ba2ff8 Mon Sep 17 00:00:00 2001 From: Mark Thom Date: Sat, 13 Dec 2025 18:22:15 -0800 Subject: [PATCH 05/12] fix cargo fmt --- src/machine/variant_hashing.rs | 264 +++++++++++++++++---------------- 1 file changed, 134 insertions(+), 130 deletions(-) diff --git a/src/machine/variant_hashing.rs b/src/machine/variant_hashing.rs index 42a7e18a..13f0d217 100644 --- a/src/machine/variant_hashing.rs +++ b/src/machine/variant_hashing.rs @@ -1,11 +1,11 @@ use crate::forms::*; use crate::heap_iter::*; -use crate::types::*; -use crate::machine::*; use crate::machine::heap::*; +use crate::machine::*; +use crate::types::*; -use fxhash::{FxHasher, FxBuildHasher}; -use hashbrown::{HashTable}; +use fxhash::{FxBuildHasher, FxHasher}; +use hashbrown::HashTable; use std::hash::{Hash, Hasher}; @@ -16,33 +16,33 @@ impl MachineState { // return false on success and true on failure like eq_test. #[inline(always)] pub fn is_non_variant(&self, h1: HeapCellValue, h2: HeapCellValue) -> bool { - let mut a_to_b = IndexMap::with_hasher(FxBuildHasher::default()); - let mut b_to_a = IndexMap::with_hasher(FxBuildHasher::default()); + let mut a_to_b = IndexMap::with_hasher(FxBuildHasher::default()); + let mut b_to_a = IndexMap::with_hasher(FxBuildHasher::default()); for term_pair in ParallelHeapIter::from(self, h1, h2) { match term_pair { TermPair::Vars(v1_offset, v2_offset) => { - match a_to_b.entry(v1_offset) { - indexmap::map::Entry::Occupied(stored_v2_offset) => { - if v2_offset != *stored_v2_offset.get() { - return true; - } - } - indexmap::map::Entry::Vacant(entry) => { - entry.insert_entry(v2_offset); - } - } + match a_to_b.entry(v1_offset) { + indexmap::map::Entry::Occupied(stored_v2_offset) => { + if v2_offset != *stored_v2_offset.get() { + return true; + } + } + indexmap::map::Entry::Vacant(entry) => { + entry.insert_entry(v2_offset); + } + } - match b_to_a.entry(v2_offset) { - indexmap::map::Entry::Occupied(stored_v1_offset) => { - if v1_offset != *stored_v1_offset.get() { - return true; - } - } - indexmap::map::Entry::Vacant(entry) => { - entry.insert_entry(v1_offset); - } - } + match b_to_a.entry(v2_offset) { + indexmap::map::Entry::Occupied(stored_v1_offset) => { + if v1_offset != *stored_v1_offset.get() { + return true; + } + } + indexmap::map::Entry::Vacant(entry) => { + entry.insert_entry(v1_offset); + } + } } TermPair::Less(..) => return true, TermPair::Greater(..) => return true, @@ -51,140 +51,144 @@ impl MachineState { } } - false + false } fn variant_hash(&mut self, cell: HeapCellValue) -> u64 { - let mut var_ids = IndexMap::with_hasher(FxBuildHasher::default()); - let mut hasher = FxHasher::default(); - let mut iter = eager_stackful_preorder_iter(&mut self.heap, cell); - let mut next_var_id = 0; + let mut var_ids = IndexMap::with_hasher(FxBuildHasher::default()); + let mut hasher = FxHasher::default(); + let mut iter = eager_stackful_preorder_iter(&mut self.heap, cell); + let mut next_var_id = 0; - while let Some(term) = iter.next() { - read_heap_cell!(term, - (HeapCellValueTag::Str, s) => { - let (name, arity) = cell_as_atom_cell!(iter.heap[s]).get_name_and_arity(); - (name.index, arity).hash(&mut hasher); - } - (HeapCellValueTag::Lis) => { - (atom!(".").index, 2).hash(&mut hasher); - } - (HeapCellValueTag::PStrLoc, l) => { - let string = iter.heap.scan_slice_to_str(l).string; + while let Some(term) = iter.next() { + read_heap_cell!(term, + (HeapCellValueTag::Str, s) => { + let (name, arity) = cell_as_atom_cell!(iter.heap[s]).get_name_and_arity(); + (name.index, arity).hash(&mut hasher); + } + (HeapCellValueTag::Lis) => { + (atom!(".").index, 2).hash(&mut hasher); + } + (HeapCellValueTag::PStrLoc, l) => { + let string = iter.heap.scan_slice_to_str(l).string; - for c in string.chars() { - (atom!(".").index, 2).hash(&mut hasher); - hasher.write_u64(AtomCell::new_char_inlined(c).get_name().index); - } - } - (HeapCellValueTag::Atom, (name, arity)) => { - debug_assert_eq!(arity, 0); - (name.index, arity).hash(&mut hasher); - } - (HeapCellValueTag::AttrVar | HeapCellValueTag::Var, h) => { - let canonical_id = var_ids.entry(h).or_insert_with(|| { - let id = next_var_id; - next_var_id += 1; - id - }); + for c in string.chars() { + (atom!(".").index, 2).hash(&mut hasher); + hasher.write_u64(AtomCell::new_char_inlined(c).get_name().index); + } + } + (HeapCellValueTag::Atom, (name, arity)) => { + debug_assert_eq!(arity, 0); + (name.index, arity).hash(&mut hasher); + } + (HeapCellValueTag::AttrVar | HeapCellValueTag::Var, h) => { + let canonical_id = var_ids.entry(h).or_insert_with(|| { + let id = next_var_id; + next_var_id += 1; + id + }); - hasher.write_u64(*canonical_id); - } - _ => { - if let Some(n) = Number::try_from((term, &self.arena.f64_tbl)).ok() { - match n { - Number::Float(f) => f.hash(&mut hasher), - Number::Integer(n) => n.hash(&mut hasher), - Number::Rational(r) => r.hash(&mut hasher), - Number::Fixnum(f) => f.hash(&mut hasher), - } - } else { - term.hash(&mut hasher); - } - } - ); - } + hasher.write_u64(*canonical_id); + } + _ => { + if let Ok(n) = Number::try_from((term, &self.arena.f64_tbl)) { + match n { + Number::Float(f) => f.hash(&mut hasher), + Number::Integer(n) => n.hash(&mut hasher), + Number::Rational(r) => r.hash(&mut hasher), + Number::Fixnum(f) => f.hash(&mut hasher), + } + } else { + term.hash(&mut hasher); + } + } + ); + } - hasher.finish() + hasher.finish() } pub fn group_by_variant(&mut self) -> CallResult { - let stub_gen = || functor_stub(atom!("$group_by_variant"), 2); + let stub_gen = || functor_stub(atom!("$group_by_variant"), 2); let list = self.try_from_list(self.registers[1], stub_gen)?; let mut key_pairs = Vec::with_capacity(list.len()); for val in list { - key_pairs.push(self.key_val_pair(val)?); + key_pairs.push(self.key_val_pair(val)?); } - // the first parameter is the hash. Rust forces us to store it - // because of non-lexical lifetime hell between - // HashTable::find_mut and HashTable::insert_unique. also - // avoid computing the same hash repeatedly - let mut table: HashTable<(u64, Vec, Vec)> = HashTable::new(); + // the first parameter is the hash. Rust forces us to store it + // because of non-lexical lifetime hell between + // HashTable::find_mut and HashTable::insert_unique. also + // avoid computing the same hash repeatedly + let mut table: HashTable<(u64, Vec, Vec)> = HashTable::new(); - for (key, val) in key_pairs { - let hash = self.variant_hash(key); + for (key, val) in key_pairs { + let hash = self.variant_hash(key); - match table.find_mut(hash, |(_, keys, _)| !self.is_non_variant(key, keys[0])) { - Some((_, keys, vals)) => { - keys.push(key); - vals.push(val); - } - None => { - table.insert_unique(hash, (hash, vec![key], vec![val]), |(h, _, _)| *h); - } - } - } + match table.find_mut(hash, |(_, keys, _)| !self.is_non_variant(key, keys[0])) { + Some((_, keys, vals)) => { + keys.push(key); + vals.push(val); + } + None => { + table.insert_unique(hash, (hash, vec![key], vec![val]), |(h, _, _)| *h); + } + } + } - let mut list_of_lists = Vec::with_capacity(table.len()); + let mut list_of_lists = Vec::with_capacity(table.len()); - for (_, keys, variants) in table { - if let None = keys.windows(2).try_for_each(|cells| { - unify_fn!(*self, cells[0], cells[1]); - if self.fail { None } else { Some(()) } - }) { - return Ok(()); - } + for (_, keys, variants) in table { + if keys + .windows(2) + .try_for_each(|cells| { + unify_fn!(*self, cells[0], cells[1]); + if self.fail { + None + } else { + Some(()) + } + }) + .is_none() + { + return Ok(()); + } - let variant_list_cell = resource_error_call_result!( - self, - sized_iter_to_heap_list( - &mut self.heap, - variants.len(), - variants.into_iter(), - ) - ); + let variant_list_cell = resource_error_call_result!( + self, + sized_iter_to_heap_list(&mut self.heap, variants.len(), variants.into_iter(),) + ); - let mut writer = resource_error_call_result!(self, self.heap.reserve(3)); + let mut writer = resource_error_call_result!(self, self.heap.reserve(3)); - let key_val_cell = writer.write_with(|section| { - let key_val_cell = str_loc_as_cell!(section.cell_len()); + let key_val_cell = writer + .write_with(|section| { + let key_val_cell = str_loc_as_cell!(section.cell_len()); - section.push_cell(atom_as_cell!(atom!("-"), 2)); - section.push_cell(keys[0]); - section.push_cell(variant_list_cell); + section.push_cell(atom_as_cell!(atom!("-"), 2)); + section.push_cell(keys[0]); + section.push_cell(variant_list_cell); - key_val_cell - }).result; + key_val_cell + }) + .result; - list_of_lists.push(key_val_cell); - } + list_of_lists.push(key_val_cell); + } - let variant_grouped_list = resource_error_call_result!( - self, - sized_iter_to_heap_list( - &mut self.heap, - list_of_lists.len(), - list_of_lists.into_iter(), - ) - ); + let variant_grouped_list = resource_error_call_result!( + self, + sized_iter_to_heap_list( + &mut self.heap, + list_of_lists.len(), + list_of_lists.into_iter(), + ) + ); - let target_addr = self.registers[2]; + let target_addr = self.registers[2]; unify_fn!(*self, target_addr, variant_grouped_list); Ok(()) } } - - From 69a367d1c9297bdaa7cc6674a8acf6538be8a205 Mon Sep 17 00:00:00 2001 From: Mark Thom Date: Tue, 30 Dec 2025 22:16:25 -0800 Subject: [PATCH 06/12] do not retain attributes in solutions of findall (#3020) --- src/machine/system_calls.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/machine/system_calls.rs b/src/machine/system_calls.rs index 3a6c321b..edd08cb5 100644 --- a/src/machine/system_calls.rs +++ b/src/machine/system_calls.rs @@ -925,7 +925,7 @@ impl MachineState { &mut self.lifted_heap, ); - let pstr_boundary = copy_term(copy_ball_term, copy_target, AttrVarPolicy::DeepCopy)?; + let pstr_boundary = copy_term(copy_ball_term, copy_target, AttrVarPolicy::StripAttributes)?; Ok(FindallCopyInfo { offset: threshold + lh_offset + 2, From 70220687f4d1004aa01957c1ec57e36499e4f05c Mon Sep 17 00:00:00 2001 From: Mark Thom Date: Thu, 8 Jan 2026 00:23:52 -0800 Subject: [PATCH 07/12] find variant terms using just sort/2 and (==)/2 --- src/lib/builtins.pl | 62 ++++++++++++++++++++++++++++++++++----------- 1 file changed, 47 insertions(+), 15 deletions(-) diff --git a/src/lib/builtins.pl b/src/lib/builtins.pl index 7dea6d3e..ae3d2742 100644 --- a/src/lib/builtins.pl +++ b/src/lib/builtins.pl @@ -997,6 +997,47 @@ findall_with_existential(Template, Goal, PairedSolutions, Witnesses0, Witnesses) ). +:- non_counted_backtracking split_by_variant/4. + +:- non_counted_backtracking split_by_variant/3. + +:- non_counted_backtracking unify_variant_variables/2. + +:- non_counted_backtracking sort_without_dedup/2. + +:- non_counted_backtracking tag_pairs/2. + +split_by_variant([V2-S2 | Pairs], V1-S1, Solutions, Rest) :- + ( V1 == V2 -> + Solutions = [S2 | Solutions1], + split_by_variant(Pairs, V1-S1, Solutions1, Rest) + ; Solutions = [], + Rest = [V2-S2 | Pairs] + ). +split_by_variant([], _, [], []). + +split_by_variant([V-S|Pairs], Ws, Solutions) :- + split_by_variant(Pairs, V-S, Solutions0, Rest), + ( Rest == [] -> V = Ws, Solutions = [S|Solutions0] + ; V = Ws, Solutions = [S|Solutions0] + ; split_by_variant(Rest, Ws, Solutions) + ). + +unify_variant_variables([], _Dict). +unify_variant_variables([V-_S|Pairs], Dict) :- + term_variables(V, VVars), + lists:append(VVars, _, Dict), + unify_variant_variables(Pairs, Dict). + +tag_pairs([], []) :- !. +tag_pairs([V-S|UntaggedPairs], [V-S-_I|TaggedPairs]) :- + tag_pairs(UntaggedPairs, TaggedPairs). + +sort_without_dedup(UnsortedPairs, SortedPairs) :- + tag_pairs(UnsortedPairs, TaggedUnsortedPairs), + sort(TaggedUnsortedPairs, TaggedSortedPairs), + tag_pairs(SortedPairs, TaggedSortedPairs). + :- meta_predicate(bagof(?, 0, ?)). :- non_counted_backtracking bagof/3. @@ -1027,19 +1068,9 @@ bagof(Template, Goal, Solution) :- term_variables(TemplateVars+GoalVars, TGVs), lists:append(TemplateVars, Witnesses0, TGVs), findall_with_existential(Template, Goal, PairedSolutions, Witnesses0, Witnesses), - '$group_by_variant'(PairedSolutions, GroupedSolutions), - iterate_variants(GroupedSolutions, Witnesses, Solution). - -:- non_counted_backtracking iterate_variants_and_sort/3. - -iterate_variants_and_sort([V-Solution0|GroupSolutions], V, Solution) :- - sort(Solution0, Solution1), - Solution1 = Solution, - ( GroupSolutions == [] -> ! - ; true - ). -iterate_variants_and_sort([_|GroupSolutions], Ws, Solution) :- - iterate_variants_and_sort(GroupSolutions, Ws, Solution). + unify_variant_variables(PairedSolutions, _Dict), + sort_without_dedup(PairedSolutions, PairedSolutions1), + split_by_variant(PairedSolutions1, Witnesses, Solution). :- meta_predicate(setof(?, 0, ?)). @@ -1063,8 +1094,9 @@ setof(Template, Goal, Solution) :- term_variables(TemplateVars+GoalVars, TGVs), lists:append(TemplateVars, Witnesses0, TGVs), findall_with_existential(Template, Goal, PairedSolutions, Witnesses0, Witnesses), - '$group_by_variant'(PairedSolutions, GroupedSolutions), - iterate_variants_and_sort(GroupedSolutions, Witnesses, Solution). + unify_variant_variables(PairedSolutions, _Dict), + sort(PairedSolutions, PairedSolutions1), + split_by_variant(PairedSolutions1, Witnesses, Solution). % Clause retrieval and information. From c2e1ded8521e0caab21931bf10e1b943d29d2a79 Mon Sep 17 00:00:00 2001 From: Mark Thom Date: Thu, 8 Jan 2026 00:27:02 -0800 Subject: [PATCH 08/12] remove variant_hashing.rs and related instructions --- build/instructions_template.rs | 4 - src/machine/dispatch.rs | 22 ---- src/machine/mod.rs | 1 - src/machine/variant_hashing.rs | 194 --------------------------------- 4 files changed, 221 deletions(-) delete mode 100644 src/machine/variant_hashing.rs diff --git a/build/instructions_template.rs b/build/instructions_template.rs index eb67213a..7b8be1f8 100644 --- a/build/instructions_template.rs +++ b/build/instructions_template.rs @@ -647,10 +647,6 @@ enum SystemClauseType { InferenceLimitExceeded, #[strum_discriminants(strum(props(Arity = "1", Name = "$argv")))] Argv, - #[strum_discriminants(strum(props(Arity = "2", Name = "$variant")))] - IsVariant, - #[strum_discriminants(strum(props(Arity = "2", Name = "$group_by_variant")))] - GroupByVariant, Repl(ReplCodePtr), } diff --git a/src/machine/dispatch.rs b/src/machine/dispatch.rs index 900f55dc..98a47309 100644 --- a/src/machine/dispatch.rs +++ b/src/machine/dispatch.rs @@ -4760,28 +4760,6 @@ impl Machine { try_or_throw!(self.machine_st, self.argv(), continue); step_or_fail!(self.machine_st, self.machine_st.p = self.machine_st.cp); } - &Instruction::CallIsVariant => { - self.machine_st.fail = self.machine_st.is_non_variant( - self.machine_st.registers[1], - self.machine_st.registers[2], - ); - step_or_fail!(self.machine_st, self.machine_st.p += 1); - } - &Instruction::ExecuteIsVariant => { - self.machine_st.fail = self.machine_st.is_non_variant( - self.machine_st.registers[1], - self.machine_st.registers[2], - ); - step_or_fail!(self.machine_st, self.machine_st.p = self.machine_st.cp); - } - &Instruction::CallGroupByVariant => { - try_or_throw!(self.machine_st, self.machine_st.group_by_variant(), continue); - step_or_fail!(self.machine_st, self.machine_st.p += 1); - } - &Instruction::ExecuteGroupByVariant => { - try_or_throw!(self.machine_st, self.machine_st.group_by_variant(), continue); - step_or_fail!(self.machine_st, self.machine_st.p = self.machine_st.cp); - } &Instruction::CallCurrentTime => { self.current_time(); step_or_fail!(self.machine_st, self.machine_st.p += 1); diff --git a/src/machine/mod.rs b/src/machine/mod.rs index 42999e07..859290a4 100644 --- a/src/machine/mod.rs +++ b/src/machine/mod.rs @@ -27,7 +27,6 @@ pub mod streams; pub mod system_calls; pub mod term_stream; pub mod unify; -pub mod variant_hashing; use crate::arena::*; use crate::arithmetic::*; diff --git a/src/machine/variant_hashing.rs b/src/machine/variant_hashing.rs deleted file mode 100644 index 13f0d217..00000000 --- a/src/machine/variant_hashing.rs +++ /dev/null @@ -1,194 +0,0 @@ -use crate::forms::*; -use crate::heap_iter::*; -use crate::machine::heap::*; -use crate::machine::*; -use crate::types::*; - -use fxhash::{FxBuildHasher, FxHasher}; -use hashbrown::HashTable; - -use std::hash::{Hash, Hasher}; - -impl MachineState { - // determine whether two terms are variants, i.e. if there exists - // a bijection between their variable sets such that applying it - // to h1 produces h2 (ISO Prolog standard section 7.1.6.1). - // return false on success and true on failure like eq_test. - #[inline(always)] - pub fn is_non_variant(&self, h1: HeapCellValue, h2: HeapCellValue) -> bool { - let mut a_to_b = IndexMap::with_hasher(FxBuildHasher::default()); - let mut b_to_a = IndexMap::with_hasher(FxBuildHasher::default()); - - for term_pair in ParallelHeapIter::from(self, h1, h2) { - match term_pair { - TermPair::Vars(v1_offset, v2_offset) => { - match a_to_b.entry(v1_offset) { - indexmap::map::Entry::Occupied(stored_v2_offset) => { - if v2_offset != *stored_v2_offset.get() { - return true; - } - } - indexmap::map::Entry::Vacant(entry) => { - entry.insert_entry(v2_offset); - } - } - - match b_to_a.entry(v2_offset) { - indexmap::map::Entry::Occupied(stored_v1_offset) => { - if v1_offset != *stored_v1_offset.get() { - return true; - } - } - indexmap::map::Entry::Vacant(entry) => { - entry.insert_entry(v1_offset); - } - } - } - TermPair::Less(..) => return true, - TermPair::Greater(..) => return true, - TermPair::Unordered(cell_1, cell_2) if cell_1 != cell_2 => return true, - _ => {} - } - } - - false - } - - fn variant_hash(&mut self, cell: HeapCellValue) -> u64 { - let mut var_ids = IndexMap::with_hasher(FxBuildHasher::default()); - let mut hasher = FxHasher::default(); - let mut iter = eager_stackful_preorder_iter(&mut self.heap, cell); - let mut next_var_id = 0; - - while let Some(term) = iter.next() { - read_heap_cell!(term, - (HeapCellValueTag::Str, s) => { - let (name, arity) = cell_as_atom_cell!(iter.heap[s]).get_name_and_arity(); - (name.index, arity).hash(&mut hasher); - } - (HeapCellValueTag::Lis) => { - (atom!(".").index, 2).hash(&mut hasher); - } - (HeapCellValueTag::PStrLoc, l) => { - let string = iter.heap.scan_slice_to_str(l).string; - - for c in string.chars() { - (atom!(".").index, 2).hash(&mut hasher); - hasher.write_u64(AtomCell::new_char_inlined(c).get_name().index); - } - } - (HeapCellValueTag::Atom, (name, arity)) => { - debug_assert_eq!(arity, 0); - (name.index, arity).hash(&mut hasher); - } - (HeapCellValueTag::AttrVar | HeapCellValueTag::Var, h) => { - let canonical_id = var_ids.entry(h).or_insert_with(|| { - let id = next_var_id; - next_var_id += 1; - id - }); - - hasher.write_u64(*canonical_id); - } - _ => { - if let Ok(n) = Number::try_from((term, &self.arena.f64_tbl)) { - match n { - Number::Float(f) => f.hash(&mut hasher), - Number::Integer(n) => n.hash(&mut hasher), - Number::Rational(r) => r.hash(&mut hasher), - Number::Fixnum(f) => f.hash(&mut hasher), - } - } else { - term.hash(&mut hasher); - } - } - ); - } - - hasher.finish() - } - - pub fn group_by_variant(&mut self) -> CallResult { - let stub_gen = || functor_stub(atom!("$group_by_variant"), 2); - let list = self.try_from_list(self.registers[1], stub_gen)?; - - let mut key_pairs = Vec::with_capacity(list.len()); - - for val in list { - key_pairs.push(self.key_val_pair(val)?); - } - - // the first parameter is the hash. Rust forces us to store it - // because of non-lexical lifetime hell between - // HashTable::find_mut and HashTable::insert_unique. also - // avoid computing the same hash repeatedly - let mut table: HashTable<(u64, Vec, Vec)> = HashTable::new(); - - for (key, val) in key_pairs { - let hash = self.variant_hash(key); - - match table.find_mut(hash, |(_, keys, _)| !self.is_non_variant(key, keys[0])) { - Some((_, keys, vals)) => { - keys.push(key); - vals.push(val); - } - None => { - table.insert_unique(hash, (hash, vec![key], vec![val]), |(h, _, _)| *h); - } - } - } - - let mut list_of_lists = Vec::with_capacity(table.len()); - - for (_, keys, variants) in table { - if keys - .windows(2) - .try_for_each(|cells| { - unify_fn!(*self, cells[0], cells[1]); - if self.fail { - None - } else { - Some(()) - } - }) - .is_none() - { - return Ok(()); - } - - let variant_list_cell = resource_error_call_result!( - self, - sized_iter_to_heap_list(&mut self.heap, variants.len(), variants.into_iter(),) - ); - - let mut writer = resource_error_call_result!(self, self.heap.reserve(3)); - - let key_val_cell = writer - .write_with(|section| { - let key_val_cell = str_loc_as_cell!(section.cell_len()); - - section.push_cell(atom_as_cell!(atom!("-"), 2)); - section.push_cell(keys[0]); - section.push_cell(variant_list_cell); - - key_val_cell - }) - .result; - - list_of_lists.push(key_val_cell); - } - - let variant_grouped_list = resource_error_call_result!( - self, - sized_iter_to_heap_list( - &mut self.heap, - list_of_lists.len(), - list_of_lists.into_iter(), - ) - ); - - let target_addr = self.registers[2]; - unify_fn!(*self, target_addr, variant_grouped_list); - Ok(()) - } -} From 1a8c4f9b03dff3301723bc3d8e8877ea5af915b8 Mon Sep 17 00:00:00 2001 From: Mark Thom Date: Thu, 8 Jan 2026 20:09:12 -0800 Subject: [PATCH 09/12] cargo fmt fixes --- src/debray_allocator.rs | 10 ++++------ src/forms.rs | 6 +++--- src/machine/disjuncts.rs | 7 ++++--- src/machine/machine_state_impl.rs | 5 ++++- 4 files changed, 15 insertions(+), 13 deletions(-) diff --git a/src/debray_allocator.rs b/src/debray_allocator.rs index d214b8c7..30fae204 100644 --- a/src/debray_allocator.rs +++ b/src/debray_allocator.rs @@ -85,9 +85,9 @@ impl BranchStack { ) -> bool { match safety { VarSafetyStatus::Needed => false, - VarSafetyStatus::LocallyUnneeded(planter_branch) => { - planter_branch.branch_num.has_as_subbranch(&branch.branch_num) - } + VarSafetyStatus::LocallyUnneeded(planter_branch) => planter_branch + .branch_num + .has_as_subbranch(&branch.branch_num), VarSafetyStatus::GloballyUnneeded => true, } } @@ -108,9 +108,7 @@ impl BranchStack { .map(|occurrences| occurrences.current_branch_num.clone()) .unwrap_or_else(|| BranchNumber::default()); - BranchDesignator { - branch_num, - } + BranchDesignator { branch_num } } #[inline] diff --git a/src/forms.rs b/src/forms.rs index 65939d58..310228e4 100644 --- a/src/forms.rs +++ b/src/forms.rs @@ -164,9 +164,9 @@ impl PartialOrd for BranchNumber { impl BranchNumber { pub(crate) fn has_as_subbranch(&self, other: &Self) -> bool { - other.delta <= self.delta && - other.branch_num >= self.branch_num && - other.branch_num < &self.branch_num + &self.delta + other.delta <= self.delta + && other.branch_num >= self.branch_num + && other.branch_num < &self.branch_num + &self.delta } pub(crate) fn split(&self) -> BranchNumber { diff --git a/src/machine/disjuncts.rs b/src/machine/disjuncts.rs index 95f4ae5f..09eec4a1 100644 --- a/src/machine/disjuncts.rs +++ b/src/machine/disjuncts.rs @@ -427,7 +427,8 @@ impl VariableClassifier { TraversalState::ResetCallPolicy(call_policy) => { self.call_policy = call_policy; } - TraversalState::BuildDisjunct(preceding_len) | TraversalState::BuildFinalDisjunct(preceding_len) => { + TraversalState::BuildDisjunct(preceding_len) + | TraversalState::BuildFinalDisjunct(preceding_len) => { let branch_num = self.root_set.pop().unwrap(); flatten_into_disjunct(&mut build_stack, branch_num, preceding_len); @@ -566,7 +567,7 @@ impl VariableClassifier { )); let iter = branches.into_iter().zip(branch_numbers.into_iter()); - let final_disjunct_loc = state_stack.len(); + let final_disjunct_loc = state_stack.len(); for (term, branch_num) in iter.rev() { state_stack.push(TraversalState::BuildDisjunct(build_stack_len)); @@ -623,7 +624,7 @@ impl VariableClassifier { build_stack.reserve_branch(2); - state_stack.push(TraversalState::RepBranchNum( + state_stack.push(TraversalState::RepBranchNum( self.current_branch_num.halve_delta(), )); state_stack.push(TraversalState::BuildFinalDisjunct(build_stack_len)); diff --git a/src/machine/machine_state_impl.rs b/src/machine/machine_state_impl.rs index 393b646f..36a4772c 100644 --- a/src/machine/machine_state_impl.rs +++ b/src/machine/machine_state_impl.rs @@ -934,7 +934,10 @@ impl MachineState { } // see 8.4.4.3 of Draft Technical Corrigendum 2 for an error guide. - pub fn key_val_pair(&mut self, value: HeapCellValue) -> Result<(HeapCellValue, HeapCellValue), MachineStub> { + pub fn key_val_pair( + &mut self, + value: HeapCellValue, + ) -> Result<(HeapCellValue, HeapCellValue), MachineStub> { let stub_gen = || functor_stub(atom!("keysort"), 2); let store_v = self.store(self.deref(value)); From a83f4122515b27cbb6050fac6d27d1249de64c59 Mon Sep 17 00:00:00 2001 From: Mark Thom Date: Thu, 8 Jan 2026 20:29:26 -0800 Subject: [PATCH 10/12] replace sort_without_dedup/2 with keysort/2 --- src/lib/builtins.pl | 15 +-------------- 1 file changed, 1 insertion(+), 14 deletions(-) diff --git a/src/lib/builtins.pl b/src/lib/builtins.pl index ae3d2742..9253aabf 100644 --- a/src/lib/builtins.pl +++ b/src/lib/builtins.pl @@ -1003,10 +1003,6 @@ findall_with_existential(Template, Goal, PairedSolutions, Witnesses0, Witnesses) :- non_counted_backtracking unify_variant_variables/2. -:- non_counted_backtracking sort_without_dedup/2. - -:- non_counted_backtracking tag_pairs/2. - split_by_variant([V2-S2 | Pairs], V1-S1, Solutions, Rest) :- ( V1 == V2 -> Solutions = [S2 | Solutions1], @@ -1029,15 +1025,6 @@ unify_variant_variables([V-_S|Pairs], Dict) :- lists:append(VVars, _, Dict), unify_variant_variables(Pairs, Dict). -tag_pairs([], []) :- !. -tag_pairs([V-S|UntaggedPairs], [V-S-_I|TaggedPairs]) :- - tag_pairs(UntaggedPairs, TaggedPairs). - -sort_without_dedup(UnsortedPairs, SortedPairs) :- - tag_pairs(UnsortedPairs, TaggedUnsortedPairs), - sort(TaggedUnsortedPairs, TaggedSortedPairs), - tag_pairs(SortedPairs, TaggedSortedPairs). - :- meta_predicate(bagof(?, 0, ?)). :- non_counted_backtracking bagof/3. @@ -1069,7 +1056,7 @@ bagof(Template, Goal, Solution) :- lists:append(TemplateVars, Witnesses0, TGVs), findall_with_existential(Template, Goal, PairedSolutions, Witnesses0, Witnesses), unify_variant_variables(PairedSolutions, _Dict), - sort_without_dedup(PairedSolutions, PairedSolutions1), + keysort(PairedSolutions, PairedSolutions1), split_by_variant(PairedSolutions1, Witnesses, Solution). :- meta_predicate(setof(?, 0, ?)). From e446b133762f27e27b945409098eb302016c9e51 Mon Sep 17 00:00:00 2001 From: Mark Thom Date: Mon, 12 Jan 2026 22:13:02 -0800 Subject: [PATCH 11/12] remove hashbrown crate --- Cargo.lock | 26 +------------------------- Cargo.toml | 1 - 2 files changed, 1 insertion(+), 26 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index a605ccf3..4fa61925 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -45,12 +45,6 @@ version = "0.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "250f629c0161ad8107cf89319e990051fae62832fd343083bea452d93e2205fd" -[[package]] -name = "allocator-api2" -version = "0.2.21" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "683d7910e743518b0e34f1186f92494becacb047c7b6bf616c96772180fef923" - [[package]] name = "android-tzdata" version = "0.1.1" @@ -878,12 +872,6 @@ version = "1.0.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" -[[package]] -name = "foldhash" -version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "77ce24cb58228fbb8aa041425bb1050850ac19177686ea6e0f41a70416f56fdb" - [[package]] name = "foreign-types" version = "0.3.2" @@ -1124,17 +1112,6 @@ version = "0.15.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "84b26c544d002229e640969970a2e74021aadf6e2f96372b9c58eff97de08eb3" -[[package]] -name = "hashbrown" -version = "0.16.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "841d1cc9bed7f9236f321df977030373f4a4163ae1a7dbfe1a51a2c1a51d9100" -dependencies = [ - "allocator-api2", - "equivalent", - "foldhash", -] - [[package]] name = "headers" version = "0.3.9" @@ -1479,7 +1456,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cea70ddb795996207ad57735b50c5982d8844f38ba9ee5f1aedcfb708a2aa11e" dependencies = [ "equivalent", - "hashbrown 0.15.3", + "hashbrown", ] [[package]] @@ -2757,7 +2734,6 @@ dependencies = [ "fxhash", "getrandom 0.2.16", "git-version", - "hashbrown 0.16.1", "hostname", "iai-callgrind", "indexmap", diff --git a/Cargo.toml b/Cargo.toml index 88682b69..5971a229 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -88,7 +88,6 @@ ego-tree = "0.10.0" serde_json = "1.0.122" serde = "1.0.204" parking_lot = "0.12.4" -hashbrown = "0.16.1" [target.'cfg(not(target_arch = "wasm32"))'.dependencies] crossterm = { version = "0.28.1", optional = true } From c79fd7433154cbaf7fa548d361fdddf3dcc3e001 Mon Sep 17 00:00:00 2001 From: Mark Thom Date: Mon, 12 Jan 2026 22:47:39 -0800 Subject: [PATCH 12/12] remove unnecessary extra work in findall_with_existential/5 --- src/lib/builtins.pl | 15 +-------------- 1 file changed, 1 insertion(+), 14 deletions(-) diff --git a/src/lib/builtins.pl b/src/lib/builtins.pl index 9253aabf..65c829a5 100644 --- a/src/lib/builtins.pl +++ b/src/lib/builtins.pl @@ -944,17 +944,6 @@ findall(Template, Goal, Solutions0, Solutions1) :- builtins:findall_cleanup(LhLength, Error) ). -:- non_counted_backtracking set_difference/3. - -set_difference([X|Xs], [Y|Ys], Zs) :- - X == Y, !, set_difference(Xs, [Y|Ys], Zs). -set_difference([X|Xs], [Y|Ys], [X|Zs]) :- - X @< Y, !, set_difference(Xs, [Y|Ys], Zs). -set_difference([X|Xs], [Y|Ys], Zs) :- - X @> Y, !, set_difference([X|Xs], Ys, Zs). -set_difference([], _, []) :- !. -set_difference(Xs, [], Xs). - :- non_counted_backtracking iterate_variants/3. @@ -987,9 +976,7 @@ findall_with_existential(Template, Goal, PairedSolutions, Witnesses0, Witnesses) ( Goal1 = _ ^ _ ) -> rightmost_power(Goal1, Goal2, ExistentialVars0), term_variables(ExistentialVars0, ExistentialVars), - sort(Witnesses0, Witnesses1), - sort(ExistentialVars, ExistentialVars1), - set_difference(Witnesses1, ExistentialVars1, Witnesses), + lists:append(Witnesses0, Witnesses, ExistentialVars), expand_goal(M:Goal2, M, Goal3), findall(Witnesses-Template, Goal3, PairedSolutions) ; Witnesses = Witnesses0,