#define INLINE_SORT #include "allocate.h" #include "colors.h" #include "collect.h" #include "compact.h" #include "inline.h" #include "print.h" #include "report.h" #include "trail.h" #include "sort.c" #include static void flush_watched_clauses_by_literal (kissat * solver, litpairs * hyper, unsigned lit, bool compact, reference start) { assert (start != INVALID_REF); const value *values = solver->values; const assigned *all_assigned = solver->assigned; const value lit_value = values[lit]; const assigned *lit_assigned = all_assigned + IDX (lit); const value lit_fixed = (lit_value && !lit_assigned->level) ? lit_value : 0; const unsigned mlit = kissat_map_literal (solver, lit, true); watches *lit_watches = &WATCHES (lit); watch *begin = BEGIN_WATCHES (*lit_watches), *q = begin; const watch *end_of_watches = END_WATCHES (*lit_watches), *p = q; while (p != end_of_watches) { watch head = *p++; if (head.type.binary) { const unsigned other = head.binary.lit; const unsigned other_idx = IDX (other); const value other_value = values[other]; const value other_fixed = (other_value && !all_assigned[other_idx].level) ? other_value : 0; const unsigned mother = kissat_map_literal (solver, other, compact); if (lit_fixed > 0 || other_fixed > 0 || mother == INVALID_LIT) { if (lit < other) kissat_delete_binary (solver, head.binary.redundant, head.binary.hyper, lit, other); } else { assert (!lit_fixed); assert (!other_fixed); if (head.binary.hyper) { assert (head.binary.redundant); assert (mlit != INVALID_LIT); assert (mother != INVALID_LIT); if (lit < other) { const litpair litpair = kissat_litpair (lit, other); PUSH_STACK (*hyper, litpair); LOGBINARY (lit, other, "saved SRC hyper"); } } else { head.binary.lit = mother; *q++ = head; #ifdef LOGGING if (lit < other) { LOGBINARY (lit, other, "SRC"); LOGBINARY (mlit, mother, "DST"); } #endif } } } else { assert (solver->watching); const watch tail = *p++; if (!lit_fixed) { const reference ref = tail.large.ref; if (ref < start) { *q++ = head; *q++ = tail; } } } } assert (!lit_fixed || q == begin); SET_END_OF_WATCHES (*lit_watches, q); LOG ("keeping %" SECTOR_FORMAT " watches[%u]", lit_watches->size, lit); if (!compact) return; if (mlit == INVALID_LIT) return; watches *mlit_watches = &WATCHES (mlit); if (lit_fixed) assert (!mlit_watches->size); else if (mlit < lit) { assert (mlit != INVALID_LIT); assert (mlit < lit); *mlit_watches = *lit_watches; LOG ("copied watches[%u] = watches[%u] " "(size %" SECTOR_FORMAT ")", mlit, lit, mlit_watches->size); memset (lit_watches, 0, sizeof *lit_watches); } else assert (mlit == lit); } static void flush_hyper_binary_watches (kissat * solver, litpairs * hyper, bool compact) { assert (!solver->probing); const litpair *end = END_STACK (*hyper); const value *values = solver->values; size_t flushed = 0; for (const litpair * p = BEGIN_STACK (*hyper); p != end; p++) { const unsigned lit = p->lits[0]; const unsigned other = p->lits[1]; assert (lit < other); const value lit_value = values[lit]; const value other_value = values[other]; assert (!lit_value || LEVEL (lit)); assert (!other_value || LEVEL (other)); if (((lit_value < 0 && other_value > 0) || ((lit_value > 0 && other_value < 0)))) { LOGBINARY (lit, other, "keeping potential reason hyper SRC"); const unsigned mlit = kissat_map_literal (solver, lit, compact); const unsigned mother = kissat_map_literal (solver, other, compact); LOGBINARY (mlit, mother, "keeping potential reason hyper DST"); kissat_watch_other (solver, true, true, mlit, mother); kissat_watch_other (solver, true, true, mother, mlit); } else { LOGBINARY (lit, other, "flushing hyper SRC"); kissat_delete_binary (solver, true, true, lit, other); flushed++; } } if (flushed) kissat_phase (solver, "collect", GET (garbage_collections), "flushed %zu unused hyper binary clauses %.0f%%", flushed, kissat_percent (flushed, SIZE_STACK (*hyper))); (void) flushed; } static void flush_all_watched_clauses (kissat * solver, bool compact, reference start) { assert (solver->watching); LOG ("starting to flush watches at clause[%zu]", start); litpairs hyper; INIT_STACK (hyper); for (all_variables (idx)) { const unsigned lit = LIT (idx); flush_watched_clauses_by_literal (solver, &hyper, lit, compact, start); const unsigned not_lit = NOT (lit); flush_watched_clauses_by_literal (solver, &hyper, not_lit, compact, start); } LOG ("saved %zu hyper binary watches", SIZE_STACK (hyper)); flush_hyper_binary_watches (solver, &hyper, compact); RELEASE_STACK (hyper); } static void update_large_reason (kissat * solver, assigned * assigned, unsigned forced, clause * dst) { assert (dst->reason); assert (forced != INVALID_LIT); reference dst_ref = kissat_reference_clause (solver, dst); const unsigned forced_idx = IDX (forced); struct assigned *a = assigned + forced_idx; assert (!a->binary); if (a->reason != dst_ref) { LOG ("reason reference %u of %s updated to %u", a->reason, LOGLIT (forced), dst_ref); a->reason = dst_ref; } dst->reason = false; } static unsigned get_forced (const value * values, clause * dst) { assert (dst->reason); unsigned forced = INVALID_LIT; for (all_literals_in_clause (lit, dst)) { const value value = values[lit]; if (value <= 0) continue; forced = lit; break; } assert (forced != INVALID_LIT); return forced; } static void get_forced_and_update_large_reason (kissat * solver, assigned * assigned, const value * values, clause * dst) { const unsigned forced = get_forced (values, dst); update_large_reason (solver, assigned, forced, dst); } static void update_first_reducible (kissat * solver, const clause * end, clause * first_reducible) { if (first_reducible >= end) { LOG ("first reducible after end of arena"); solver->first_reducible = INVALID_REF; } else if (first_reducible) { LOGCLS (first_reducible, "updating first reducible clause to"); solver->first_reducible = kissat_reference_clause (solver, first_reducible); } else { LOG ("first reducible clause becomes invalid"); solver->first_reducible = INVALID_REF; } } static void update_last_irredundant (kissat * solver, const clause * end, clause * last_irredundant) { if (!last_irredundant) { LOG ("no more large irredundant clauses left"); solver->last_irredundant = INVALID_REF; } else if (end <= last_irredundant) { LOG ("last irredundant clause after end of arena"); solver->last_irredundant = INVALID_REF; } else { LOGCLS (last_irredundant, "updating last irredundant clause to"); reference ref = kissat_reference_clause (solver, last_irredundant); solver->last_irredundant = ref; } } static void move_redundant_clauses_to_the_end (kissat * solver, reference ref) { INC (moved); assert (ref != INVALID_REF); #ifndef NDEBUG const size_t size = SIZE_STACK (solver->arena); assert ((size_t) ref <= size); #endif clause *begin = (clause *) (BEGIN_STACK (solver->arena) + ref); clause *end = (clause *) END_STACK (solver->arena); size_t bytes_redundant = (char *) end - (char *) begin; kissat_phase (solver, "move", GET (moved), "moving redundant clauses of %s to the end", FORMAT_BYTES (bytes_redundant)); kissat_mark_reason_clauses (solver, ref); clause *redundant = (clause *) kissat_malloc (solver, bytes_redundant); clause *p = begin, *q = begin, *r = redundant; const value *values = solver->values; assigned *assigned = solver->assigned; clause *last_irredundant = kissat_last_irredundant_clause (solver); while (p != end) { assert (!p->shrunken); size_t bytes = kissat_bytes_of_clause (p->size); if (p->redundant) { memcpy (r, p, bytes); r = (clause *) (bytes + (char *) r); } else { LOGCLS (p, "old DST"); memmove (q, p, bytes); LOGCLS (q, "new DST"); last_irredundant = q; if (q->reason) get_forced_and_update_large_reason (solver, assigned, values, q); q = (clause *) (bytes + (char *) q); } p = (clause *) (bytes + (char *) p); } r = redundant; clause *first_reducible = 0; while (q != end) { size_t bytes = kissat_bytes_of_clause (r->size); memcpy (q, r, bytes); LOGCLS (q, "new DST"); if (q->reason) get_forced_and_update_large_reason (solver, assigned, values, q); assert (q->redundant); if (!first_reducible && !q->keep) first_reducible = q; r = (clause *) (bytes + (char *) r); q = (clause *) (bytes + (char *) q); } assert ((char *) r <= (char *) redundant + bytes_redundant); kissat_free (solver, redundant, bytes_redundant); assert (!first_reducible || first_reducible < q); update_first_reducible (solver, q, first_reducible); update_last_irredundant (solver, q, last_irredundant); } static reference sparse_sweep_garbage_clauses (kissat * solver, bool compact, reference start) { assert (solver->watching); LOG ("sparse garbage collection starting at clause[%zu]", start); #ifdef CHECKING_OR_PROVING const bool checking_or_proving = kissat_checking_or_proving (solver); #endif assert (EMPTY_STACK (solver->added)); assert (EMPTY_STACK (solver->removed)); const value *values = solver->values; assigned *assigned = solver->assigned; size_t flushed_garbage_clauses = 0; size_t flushed_satisfied_clauses = 0; size_t flushed_literals = 0; clause *begin = (clause *) BEGIN_STACK (solver->arena); const clause *end = (clause *) END_STACK (solver->arena); clause *first, *src, *dst; if (start) first = kissat_dereference_clause (solver, start); else first = begin; src = dst = first; clause *first_redundant = 0; clause *first_reducible = 0; clause *last_irredundant; if (start) last_irredundant = kissat_last_irredundant_clause (solver); else last_irredundant = 0; size_t redundant_bytes = 0; for (clause * next; src != end; src = next) { if (src->garbage) { next = kissat_delete_clause (solver, src); flushed_garbage_clauses++; if (last_irredundant == src) { if (first == begin) last_irredundant = 0; else last_irredundant = first; } continue; } assert (src->size > 1); LOGCLS (src, "SRC"); next = kissat_next_clause (src); #if !defined(NDEBUG) || defined(CHECKING_OR_PROVING) const unsigned old_size = src->size; #endif assert (SIZE_OF_CLAUSE_HEADER == sizeof (unsigned)); *(unsigned *) dst = *(unsigned *) src; unsigned *q = dst->lits; unsigned mfirst = INVALID_LIT; unsigned msecond = INVALID_LIT; unsigned forced = INVALID_LIT; unsigned other = INVALID_LIT; unsigned non_false = 0; bool satisfied = false; for (all_literals_in_clause (lit, src)) { #ifdef CHECKING_OR_PROVING if (checking_or_proving) PUSH_STACK (solver->removed, lit); #endif if (satisfied) continue; const value tmp = values[lit]; const unsigned idx = IDX (lit); const unsigned level = tmp ? assigned[idx].level : INVALID_LEVEL; if (tmp < 0 && !level) flushed_literals++; else if (tmp > 0 && !level) { assert (!satisfied); assert (!dst->reason); LOG ("SRC satisfied by %s", LOGLIT (lit)); satisfied = true; } else { const unsigned mlit = kissat_map_literal (solver, lit, compact); if (tmp > 0) { assert (level); forced = non_false++ ? INVALID_LIT : lit; } else if (tmp < 0) other = lit; if (mfirst == INVALID_LIT) mfirst = mlit; else if (msecond == INVALID_LIT) msecond = mlit; *q++ = mlit; #ifdef CHECKING_OR_PROVING if (checking_or_proving) PUSH_STACK (solver->added, lit); #endif } } if (satisfied) { if (dst->redundant) DEC (clauses_redundant); else DEC (clauses_irredundant); if (dst->hyper) DEC (hyper_ternaries); flushed_satisfied_clauses++; #ifdef CHECKING_OR_PROVING if (checking_or_proving) { REMOVE_CHECKER_STACK (solver->removed); DELETE_STACK_FROM_PROOF (solver->removed); CLEAR_STACK (solver->added); CLEAR_STACK (solver->removed); } #endif if (last_irredundant == src) { if (first == begin) last_irredundant = 0; else last_irredundant = first; } continue; } const unsigned new_size = q - dst->lits; assert (new_size <= old_size); assert (1 < new_size); if (new_size == 2) { assert (mfirst != INVALID_LIT); assert (msecond != INVALID_LIT); const bool redundant = dst->redundant; LOGBINARY (mfirst, msecond, "DST"); kissat_watch_binary (solver, redundant, false, mfirst, msecond); if (dst->hyper) DEC (hyper_ternaries); if (dst->reason) { assert (non_false == 1); assert (other != INVALID_LIT); assert (forced != INVALID_LIT); const unsigned forced_idx = IDX (forced); struct assigned *a = assigned + forced_idx; assert (!a->binary); LOGBINARY (mfirst, msecond, "reason clause[%u] of %s updated to binary reason", a->reason, LOGLIT (forced)); a->binary = true; a->reason = other; } if (!redundant && last_irredundant == src) { if (first == begin) last_irredundant = 0; else last_irredundant = first; } } else { assert (2 < new_size); dst->size = new_size; dst->shrunken = false; dst->searched = 2; LOGCLS (dst, "DST"); if (dst->reason) update_large_reason (solver, assigned, forced, dst); clause *next_dst = kissat_next_clause (dst); if (dst->redundant) { if (!first_reducible && !dst->keep) first_reducible = dst; redundant_bytes += (char *) next_dst - (char *) dst; if (!first_redundant) first_redundant = dst; } else last_irredundant = dst; dst = next_dst; } #ifdef CHECKING_OR_PROVING if (!checking_or_proving) continue; if (new_size != old_size) { assert (1 < new_size); assert (new_size < old_size); CHECK_AND_ADD_STACK (solver->added); ADD_STACK_TO_PROOF (solver->added); REMOVE_CHECKER_STACK (solver->removed); DELETE_STACK_FROM_PROOF (solver->removed); } CLEAR_STACK (solver->added); CLEAR_STACK (solver->removed); #endif } update_first_reducible (solver, dst, first_reducible); update_last_irredundant (solver, dst, last_irredundant); if (first_redundant) LOGCLS (first_redundant, "determined first redundant clause as"); #if !defined(QUIET) || !defined(NMETRICS) size_t bytes = (char *) END_STACK (solver->arena) - (char *) dst; #endif #ifndef QUIET if (flushed_literals) kissat_phase (solver, "collect", GET (garbage_collections), "flushed %zu falsified literals in large clauses", flushed_literals); size_t flushed_clauses = flushed_satisfied_clauses + flushed_garbage_clauses; if (flushed_satisfied_clauses) kissat_phase (solver, "collect", GET (garbage_collections), "flushed %zu satisfied large clauses %.0f%%", flushed_satisfied_clauses, kissat_percent (flushed_satisfied_clauses, flushed_clauses)); if (flushed_garbage_clauses) kissat_phase (solver, "collect", GET (garbage_collections), "flushed %zu large garbage clauses %.0f%%", flushed_garbage_clauses, kissat_percent (flushed_garbage_clauses, flushed_clauses)); kissat_phase (solver, "collect", GET (garbage_collections), "collected %s in total", FORMAT_BYTES (bytes)); #endif ADD (literals_flushed, flushed_literals); #ifndef NMETRICS ADD (allocated_collected, bytes); #endif reference res = INVALID_REF; if (first_redundant && last_irredundant && first_redundant < last_irredundant) { #ifdef LOGGING size_t move_bytes = (char *) dst - (char *) first_redundant; LOG ("redundant bytes %s (%.0f%) out of %s moving bytes", FORMAT_BYTES (redundant_bytes), kissat_percent (redundant_bytes, move_bytes), FORMAT_BYTES (move_bytes)); #endif assert (first_redundant < dst); res = kissat_reference_clause (solver, first_redundant); assert (res != INVALID_REF); } SET_END_OF_STACK (solver->arena, (word *) dst); kissat_shrink_arena (solver); kissat_clear_clueue (solver, &solver->clueue); #ifndef NMETRICS if (solver->statistics.arena_garbage) kissat_very_verbose (solver, "still %s garbage left in arena", FORMAT_BYTES (solver->statistics.arena_garbage)); else kissat_very_verbose (solver, "all garbage clauses in arena collected"); #endif return res; } static void rewatch_clauses (kissat * solver, reference start) { LOG ("rewatching clause[%zu] and following clauses", start); assert (solver->watching); const value *values = solver->values; const assigned *assigned = solver->assigned; watches *watches = solver->watches; const word *arena = BEGIN_STACK (solver->arena); clause *end = (clause *) END_STACK (solver->arena); clause *c = (clause *) (BEGIN_STACK (solver->arena) + start); assert (c <= end); for (clause * next; c != end; c = next) { next = kissat_next_clause (c); unsigned *lits = c->lits; kissat_sort_literals (solver, values, assigned, c->size, lits); c->searched = 2; const reference ref = (word *) c - arena; const unsigned l0 = lits[0]; const unsigned l1 = lits[1]; kissat_push_blocking_watch (solver, watches + l0, l1, ref); kissat_push_blocking_watch (solver, watches + l1, l0, ref); } } void kissat_sparse_collect (kissat * solver, bool compact, reference start) { assert (solver->watching); START (collect); INC (garbage_collections); INC (sparse_garbage_collections); REPORT (1, 'G'); unsigned vars, mfixed; if (compact) vars = kissat_compact_literals (solver, &mfixed); else { vars = solver->vars; mfixed = INVALID_LIT; } flush_all_watched_clauses (solver, compact, start); reference move = sparse_sweep_garbage_clauses (solver, compact, start); if (compact) kissat_finalize_compacting (solver, vars, mfixed); if (move != INVALID_REF) move_redundant_clauses_to_the_end (solver, move); rewatch_clauses (solver, start); REPORT (1, 'C'); kissat_check_statistics (solver); STOP (collect); } static void dense_sweep_garbage_clauses (kissat * solver) { assert (!solver->level); assert (!solver->watching); LOG ("dense garbage collection"); size_t flushed_garbage_clauses = 0; clause *first_reducible = 0; clause *last_irredundant = 0; clause *begin = (clause *) BEGIN_STACK (solver->arena); const clause *end = (clause *) END_STACK (solver->arena); clause *src = begin; clause *dst = src; for (clause * next; src != end; src = next) { if (src->garbage) { next = kissat_delete_clause (solver, src); flushed_garbage_clauses++; continue; } assert (src->size > 1); LOGCLS (src, "SRC"); next = kissat_next_clause (src); assert (SIZE_OF_CLAUSE_HEADER == sizeof (unsigned)); *(unsigned *) dst = *(unsigned *) src; dst->searched = src->searched; dst->size = src->size; dst->shrunken = false; memmove (dst->lits, src->lits, src->size * sizeof (unsigned)); LOGCLS (dst, "DST"); if (!dst->redundant) last_irredundant = dst; else if (!first_reducible && !dst->keep) first_reducible = dst; dst = kissat_next_clause (dst); } update_first_reducible (solver, dst, first_reducible); update_last_irredundant (solver, dst, last_irredundant); #if !defined(QUIET) || !defined(NMETRICS) size_t bytes = (char *) END_STACK (solver->arena) - (char *) dst; #endif kissat_phase (solver, "collect", GET (garbage_collections), "flushed %zu large garbage clauses", flushed_garbage_clauses); kissat_phase (solver, "collect", GET (garbage_collections), "collected %s in total", FORMAT_BYTES (bytes)); #ifndef NMETRICS ADD (allocated_collected, bytes); #endif SET_END_OF_STACK (solver->arena, (word *) dst); kissat_shrink_arena (solver); kissat_clear_clueue (solver, &solver->clueue); #ifndef NMETRICS if (solver->statistics.arena_garbage) kissat_very_verbose (solver, "still %s garbage left in arena", FORMAT_BYTES (solver->statistics.arena_garbage)); else kissat_very_verbose (solver, "all garbage clauses in arena collected"); #endif } void kissat_dense_collect (kissat * solver) { assert (!solver->watching); assert (!solver->level); START (collect); INC (garbage_collections); INC (dense_garbage_collections); REPORT (1, 'G'); dense_sweep_garbage_clauses (solver); REPORT (1, 'C'); STOP (collect); }