summaryrefslogtreecommitdiffstats
path: root/vendor/varisat/src/clause/alloc.rs
diff options
context:
space:
mode:
Diffstat (limited to 'vendor/varisat/src/clause/alloc.rs')
-rw-r--r--vendor/varisat/src/clause/alloc.rs262
1 files changed, 262 insertions, 0 deletions
diff --git a/vendor/varisat/src/clause/alloc.rs b/vendor/varisat/src/clause/alloc.rs
new file mode 100644
index 000000000..d39514040
--- /dev/null
+++ b/vendor/varisat/src/clause/alloc.rs
@@ -0,0 +1,262 @@
+//! Clause allocator.
+use std::{mem::transmute, slice};
+
+use varisat_formula::{lit::LitIdx, Lit};
+
+use super::{Clause, ClauseHeader, HEADER_LEN};
+
+/// Integer type used to store offsets into [`ClauseAlloc`]'s memory.
+type ClauseOffset = u32;
+
+/// Bump allocator for clause storage.
+///
+/// Clauses are allocated from a single continuous buffer. Clauses cannot be freed individually. To
+/// reclaim space from deleted clauses, a new `ClauseAlloc` is created and the remaining clauses are
+/// copied over.
+///
+/// When the `ClauseAlloc`'s buffer is full, it is reallocated using the growing strategy of
+/// [`Vec`]. External references ([`ClauseRef`]) store an offset into the `ClauseAlloc`'s memory and
+/// remaind valid when the buffer is grown. Clauses are aligned and the offset represents a multiple
+/// of the alignment size. This allows using 32-bit offsets while still supporting up to 16GB of
+/// clauses.
+#[derive(Default)]
+pub struct ClauseAlloc {
+ buffer: Vec<LitIdx>,
+}
+
+impl ClauseAlloc {
+ /// Create an emtpy clause allocator.
+ pub fn new() -> ClauseAlloc {
+ ClauseAlloc::default()
+ }
+
+ /// Create a clause allocator with preallocated capacity.
+ pub fn with_capacity(capacity: usize) -> ClauseAlloc {
+ ClauseAlloc {
+ buffer: Vec::with_capacity(capacity),
+ }
+ }
+
+ /// Allocate space for and add a new clause.
+ ///
+ /// Clauses have a minimal size of 3, as binary and unit clauses are handled separately. This is
+ /// enforced on the ClauseAlloc level to safely avoid extra bound checks when accessing the
+ /// initial literals of a clause.
+ ///
+ /// The size of the header will be set to the size of the given slice. The returned
+ /// [`ClauseRef`] can be used to access the new clause.
+ pub fn add_clause(&mut self, mut header: ClauseHeader, lits: &[Lit]) -> ClauseRef {
+ let offset = self.buffer.len();
+
+ assert!(
+ lits.len() >= 3,
+ "ClauseAlloc can only store ternary and larger clauses"
+ );
+
+ // TODO Maybe let the caller handle this?
+ assert!(
+ offset <= (ClauseRef::max_offset() as usize),
+ "Exceeded ClauseAlloc's maximal buffer size"
+ );
+
+ header.set_len(lits.len());
+
+ self.buffer.extend_from_slice(&header.data);
+
+ let lit_idx_slice = unsafe {
+ // This is safe as Lit and LitIdx have the same representation
+ slice::from_raw_parts(lits.as_ptr() as *const LitIdx, lits.len())
+ };
+
+ self.buffer.extend_from_slice(lit_idx_slice);
+
+ ClauseRef {
+ offset: offset as ClauseOffset,
+ }
+ }
+
+ /// Access the header of a clause.
+ pub fn header(&self, cref: ClauseRef) -> &ClauseHeader {
+ let offset = cref.offset as usize;
+ assert!(
+ offset as usize + HEADER_LEN <= self.buffer.len(),
+ "ClauseRef out of bounds"
+ );
+ unsafe { self.header_unchecked(cref) }
+ }
+
+ /// Mutate the header of a clause.
+ pub fn header_mut(&mut self, cref: ClauseRef) -> &mut ClauseHeader {
+ let offset = cref.offset as usize;
+ assert!(
+ offset as usize + HEADER_LEN <= self.buffer.len(),
+ "ClauseRef out of bounds"
+ );
+ unsafe { self.header_unchecked_mut(cref) }
+ }
+
+ unsafe fn header_unchecked(&self, cref: ClauseRef) -> &ClauseHeader {
+ let offset = cref.offset as usize;
+ let header_pointer = self.buffer.as_ptr().add(offset) as *const ClauseHeader;
+ &*header_pointer
+ }
+
+ /// Mutate the header of a clause without bound checks.
+ pub unsafe fn header_unchecked_mut(&mut self, cref: ClauseRef) -> &mut ClauseHeader {
+ let offset = cref.offset as usize;
+ let header_pointer = self.buffer.as_mut_ptr().add(offset) as *mut ClauseHeader;
+ &mut *header_pointer
+ }
+
+ /// Access a clause.
+ pub fn clause(&self, cref: ClauseRef) -> &Clause {
+ let header = self.header(cref);
+ let len = header.len();
+
+ // Even on 32 bit systems these additions can't overflow as we never create clause refs with
+ // an offset larger than ClauseRef::max_offset()
+
+ let lit_offset = cref.offset as usize + HEADER_LEN;
+ let lit_end = lit_offset + len;
+ assert!(lit_end <= self.buffer.len(), "ClauseRef out of bounds");
+ unsafe { self.clause_with_len_unchecked(cref, len) }
+ }
+
+ /// Mutate a clause.
+ pub fn clause_mut(&mut self, cref: ClauseRef) -> &mut Clause {
+ let header = self.header(cref);
+ let len = header.len();
+
+ // Even on 32 bit systems these additions can't overflow as we never create clause refs with
+ // an offset larger than ClauseRef::max_offset()
+
+ let lit_offset = cref.offset as usize + HEADER_LEN;
+ let lit_end = lit_offset + len;
+ assert!(lit_end <= self.buffer.len(), "ClauseRef out of bounds");
+ unsafe { self.clause_with_len_unchecked_mut(cref, len) }
+ }
+
+ /// Mutate the literals of a clause without bound checks.
+ pub unsafe fn lits_ptr_mut_unchecked(&mut self, cref: ClauseRef) -> *mut Lit {
+ let offset = cref.offset as usize;
+ self.buffer.as_ptr().add(offset + HEADER_LEN) as *mut Lit
+ }
+
+ /// Perform a manual bound check on a ClauseRef assuming a given clause length.
+ pub fn check_bounds(&self, cref: ClauseRef, len: usize) {
+ // Even on 32 bit systems these additions can't overflow as we never create clause refs with
+ // an offset larger than ClauseRef::max_offset()
+
+ let lit_offset = cref.offset as usize + HEADER_LEN;
+ let lit_end = lit_offset + len;
+ assert!(lit_end <= self.buffer.len(), "ClauseRef out of bounds");
+ }
+
+ unsafe fn clause_with_len_unchecked(&self, cref: ClauseRef, len: usize) -> &Clause {
+ let offset = cref.offset as usize;
+ #[allow(clippy::transmute_ptr_to_ptr)]
+ transmute::<&[LitIdx], &Clause>(slice::from_raw_parts(
+ self.buffer.as_ptr().add(offset),
+ len + HEADER_LEN,
+ ))
+ }
+
+ unsafe fn clause_with_len_unchecked_mut(&mut self, cref: ClauseRef, len: usize) -> &mut Clause {
+ let offset = cref.offset as usize;
+ #[allow(clippy::transmute_ptr_to_ptr)]
+ transmute::<&mut [LitIdx], &mut Clause>(slice::from_raw_parts_mut(
+ self.buffer.as_mut_ptr().add(offset),
+ len + HEADER_LEN,
+ ))
+ }
+
+ /// Current buffer size in multiples of [`LitIdx`].
+ pub fn buffer_size(&self) -> usize {
+ self.buffer.len()
+ }
+}
+
+/// Compact reference to a clause.
+///
+/// Used with [`ClauseAlloc`] to access the clause.
+#[derive(Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Debug)]
+pub struct ClauseRef {
+ offset: ClauseOffset,
+}
+
+impl ClauseRef {
+ /// The largest offset supported by the ClauseAlloc
+ const fn max_offset() -> ClauseOffset {
+ // Make sure we can savely add a length to an offset without overflowing usize
+ ((usize::max_value() >> 1) & (ClauseOffset::max_value() as usize)) as ClauseOffset
+ }
+}
+
+#[cfg(test)]
+mod tests {
+ use super::*;
+
+ use varisat_formula::{cnf::strategy::*, CnfFormula, ExtendFormula};
+
+ use proptest::*;
+
+ proptest! {
+ #[test]
+ fn roundtrip_from_cnf_formula(input in cnf_formula(1..100usize, 0..1000, 3..30)) {
+
+ let mut clause_alloc = ClauseAlloc::new();
+ let mut clause_refs = vec![];
+
+ for clause_lits in input.iter() {
+ let header = ClauseHeader::new();
+ clause_refs.push(clause_alloc.add_clause(header, clause_lits));
+ }
+
+ let mut recovered = CnfFormula::new();
+
+ for cref in clause_refs {
+ let clause = clause_alloc.clause(cref);
+ prop_assert_eq!(clause.header().len(), clause.lits().len());
+ recovered.add_clause(clause.lits());
+ }
+
+ // Ignore difference caused by unused vars
+ recovered.set_var_count(input.var_count());
+
+ prop_assert_eq!(input, recovered);
+ }
+
+ #[test]
+ fn clause_mutation(input in cnf_formula(1..100usize, 0..1000, 3..30)) {
+
+ let mut clause_alloc = ClauseAlloc::new();
+ let mut clause_refs = vec![];
+
+ for clause_lits in input.iter() {
+ let header = ClauseHeader::new();
+ clause_refs.push(clause_alloc.add_clause(header, clause_lits));
+ }
+
+ for &cref in clause_refs.iter() {
+ let clause = clause_alloc.clause_mut(cref);
+ clause.lits_mut().reverse();
+ }
+
+ for &cref in clause_refs.iter() {
+ let clause_len = clause_alloc.clause(cref).lits().len();
+ if clause_len > 3 {
+ clause_alloc.header_mut(cref).set_len(clause_len - 1);
+ }
+ }
+
+ for (&cref, lits) in clause_refs.iter().zip(input.iter()) {
+ let expected = if lits.len() > 3 {
+ lits[1..].iter().rev()
+ } else {
+ lits.iter().rev()
+ };
+ prop_assert!(clause_alloc.clause(cref).lits().iter().eq(expected));
+ }
+ }
+ }
+}