add variant_hash and is_non_variant to fix setof/3, bagof/3

This commit is contained in:
Mark Thom
2025-12-12 18:05:47 -08:00
parent 29cd80510b
commit 6284aa3a3f
12 changed files with 248 additions and 143 deletions

View File

@@ -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 => {

View File

@@ -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<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));
@@ -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()))

View File

@@ -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)
);

View File

@@ -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::*;

View File

@@ -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));

View File

@@ -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<HeapCellValue>, Vec<HeapCellValue>)> = 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(())
}
}