mirror of
https://github.com/Z3Prover/z3
synced 2025-04-22 00:26:38 +00:00
slicing: change how values are tracked
Do not eagerly merge equivalence classes with the same constant value. Reason: - when we split a value slice, the subslice might already exist in the egraph and already have subslices itself, which breaks assumptions in split/split_core. - introduces unnecessary splits Now: - wrap constants into fresh function symbol before creating the enode - check value compatibility in on_merge callback instead - track pointer to value_node for each enode, and update it in on_merge
This commit is contained in:
parent
08928d041a
commit
c95ff56d2d
3 changed files with 304 additions and 109 deletions
|
@ -28,12 +28,6 @@ Example:
|
|||
|
||||
|
||||
TODO:
|
||||
- notify solver about equalities discovered by congruence
|
||||
- variable equalities x = y will be handled on-demand by the viable component
|
||||
- but whenever we derive an equality between pvar and value we must propagate the value in the solver
|
||||
-> need a way to store and retrieve justification for this propagation (explain_equal)
|
||||
- track fixed bits along with enodes
|
||||
- implement query functions
|
||||
- About the sub-slice sharing among equivalent nodes:
|
||||
- When extracting a variable y := x[h:l], we always need to create a new slice for y.
|
||||
- Merge slices for x[h:l] with y; store as dependency 'x[h:l]' (rather than 'null_dep' as we do now).
|
||||
|
@ -47,24 +41,9 @@ TODO:
|
|||
- a question is how to track/handle the dependency on the assignment
|
||||
- check Algorithm 2 of "Solving Bitvectors with MCSAT"; what is the difference to what we are doing now?
|
||||
- track equalities such as x = -y ?
|
||||
|
||||
|
||||
|
||||
|
||||
Current issue:
|
||||
|
||||
1. mk_extract v7 := v6[63:32]
|
||||
2. solver assigns v7 := 1
|
||||
3. solver assigns v6 := 1 by decision
|
||||
|
||||
This is a conflict, because v6[63:32] = 0, v7 = v6[63:32], v7 = 1.
|
||||
|
||||
Solution:
|
||||
- when finding a viable value for v6 to make a decision,
|
||||
call slicing::collect_fixed to find already fixed bits
|
||||
- v7 is a subslice of v6, so we find v6[63:32] = 1
|
||||
- viable can already deal with fixed bits
|
||||
(needs some refactoring because of how justifications are tracked)
|
||||
- on_merge could propagate values upwards:
|
||||
if slice has value but parent has no value, then check if sub_other(parent(s)) [sibling(s)?] has a value.
|
||||
if yes, can propagate value upwards. (add a congruence term to track deps properly?)
|
||||
|
||||
*/
|
||||
|
||||
|
@ -145,13 +124,14 @@ namespace polysat {
|
|||
m_egraph.set_display_justification([&](std::ostream& out, void* dp) { display(out, dep_t::decode(dp)); });
|
||||
m_egraph.set_on_merge([&](enode* root, enode* other) { egraph_on_merge(root, other); });
|
||||
m_egraph.set_on_propagate([&](enode* lit, enode* ante) { egraph_on_propagate(lit, ante); });
|
||||
// m_egraph.set_on_make([&](enode* n) { egraph_on_make(n); });
|
||||
}
|
||||
|
||||
slicing::slice_info& slicing::info(euf::enode* n) {
|
||||
slicing::slice_info& slicing::info(enode* n) {
|
||||
return const_cast<slice_info&>(std::as_const(*this).info(n));
|
||||
}
|
||||
|
||||
slicing::slice_info const& slicing::info(euf::enode* n) const {
|
||||
slicing::slice_info const& slicing::info(enode* n) const {
|
||||
SASSERT(n);
|
||||
SASSERT(!n->is_equality());
|
||||
SASSERT(m_bv->is_bv_sort(n->get_sort()));
|
||||
|
@ -230,6 +210,9 @@ namespace polysat {
|
|||
case trail_item::mk_concat:
|
||||
num_replay_concat++;
|
||||
break;
|
||||
case trail_item::set_value_node:
|
||||
undo_set_value_node();
|
||||
break;
|
||||
default:
|
||||
UNREACHABLE();
|
||||
}
|
||||
|
@ -267,6 +250,9 @@ namespace polysat {
|
|||
replay_concat(ci.num_args, &m_concat_args[ci.args_idx], ci.v);
|
||||
break;
|
||||
}
|
||||
case trail_item::set_value_node:
|
||||
/* do nothing */
|
||||
break;
|
||||
default:
|
||||
UNREACHABLE();
|
||||
}
|
||||
|
@ -274,6 +260,7 @@ namespace polysat {
|
|||
m_concat_trail.shrink(m_concat_trail.size() - num_replay_concat);
|
||||
m_concat_args.shrink(m_concat_trail.empty() ? 0 : m_concat_trail.back().next_args_idx());
|
||||
m_trail.shrink(target_size);
|
||||
SASSERT(invariant());
|
||||
}
|
||||
|
||||
void slicing::add_var(unsigned bit_width) {
|
||||
|
@ -300,13 +287,23 @@ namespace polysat {
|
|||
return eqn;
|
||||
}
|
||||
|
||||
#if 0
|
||||
void slicing::egraph_on_make(enode* n) {
|
||||
LOG("on_make: " << e_pp(n));
|
||||
}
|
||||
#endif
|
||||
|
||||
slicing::enode* slicing::alloc_enode(expr* e, unsigned num_args, enode* const* args, pvar var) {
|
||||
SASSERT(!m_egraph.find(e));
|
||||
euf::enode* n = m_egraph.mk(e, 0, num_args, args); // NOTE: the egraph keeps a strong reference to 'e'
|
||||
m_info.reserve(n->get_id() + 1);
|
||||
slice_info& i = info(n);
|
||||
// NOTE: sometimes egraph::mk already triggers a merge due to congruence.
|
||||
// in this case we have to make sure to allocate m_info early enough.
|
||||
unsigned const id = e->get_id();
|
||||
m_info.reserve(id + 1);
|
||||
slice_info& i = m_info[id];
|
||||
i.reset();
|
||||
i.var = var;
|
||||
enode* n = m_egraph.mk(e, 0, num_args, args); // NOTE: the egraph keeps a strong reference to 'e'
|
||||
LOG("alloc_enode: " << e_pp(n));
|
||||
return n;
|
||||
}
|
||||
|
||||
|
@ -321,7 +318,7 @@ namespace polysat {
|
|||
|
||||
slicing::enode* slicing::alloc_slice(unsigned width, pvar var) {
|
||||
SASSERT(width > 0);
|
||||
app* a = m_ast.mk_fresh_const("s", m_bv->mk_sort(width), false);
|
||||
app_ref a(m_ast.mk_fresh_const("s", m_bv->mk_sort(width), false), m_ast);
|
||||
return alloc_enode(a, 0, nullptr, var);
|
||||
}
|
||||
|
||||
|
@ -391,12 +388,14 @@ namespace polysat {
|
|||
else {
|
||||
sub_hi = alloc_slice(width_hi);
|
||||
sub_lo = alloc_slice(width_lo);
|
||||
info(sub_hi).parent = s;
|
||||
info(sub_lo).parent = s;
|
||||
}
|
||||
SASSERT(!parent(sub_hi));
|
||||
SASSERT(!parent(sub_lo));
|
||||
info(sub_hi).parent = s;
|
||||
info(sub_lo).parent = s;
|
||||
info(s).set_cut(cut, sub_hi, sub_lo);
|
||||
m_trail.push_back(trail_item::split_core);
|
||||
m_split_trail.push_back(s);
|
||||
m_enode_trail.push_back(s);
|
||||
for (enode* n = s; n != nullptr; n = parent(n)) {
|
||||
pvar const v = slice2var(n);
|
||||
if (v == null_var)
|
||||
|
@ -419,8 +418,8 @@ namespace polysat {
|
|||
}
|
||||
|
||||
void slicing::undo_split_core() {
|
||||
enode* s = m_split_trail.back();
|
||||
m_split_trail.pop_back();
|
||||
enode* s = m_enode_trail.back();
|
||||
m_enode_trail.pop_back();
|
||||
info(s).set_cut(null_cut, nullptr, nullptr);
|
||||
}
|
||||
|
||||
|
@ -437,7 +436,6 @@ namespace polysat {
|
|||
enode* target = n->get_target();
|
||||
if (!target)
|
||||
continue;
|
||||
SASSERT(!is_value(n)); // values are always roots, and target always points towards the root.
|
||||
euf::justification const j = n->get_justification();
|
||||
SASSERT(j.is_external()); // cannot be a congruence since the slice wasn't split before.
|
||||
dep_t const d = dep_t::decode(j.ext<void>());
|
||||
|
@ -509,14 +507,32 @@ namespace polysat {
|
|||
|
||||
slicing::enode* slicing::mk_value_slice(rational const& val, unsigned bit_width) {
|
||||
SASSERT(0 <= val && val < rational::power_of_two(bit_width));
|
||||
app_ref a(m_bv->mk_numeral(val, bit_width), m_ast);
|
||||
enode* s = find_or_alloc_enode(a, 0, nullptr, null_var);
|
||||
s->mark_interpreted();
|
||||
SASSERT(s->interpreted());
|
||||
sort* bv_sort = m_bv->mk_sort(bit_width);
|
||||
func_decl_ref f(m_ast.mk_fresh_func_decl("val", nullptr, 1, &bv_sort, bv_sort, false), m_ast);
|
||||
app_ref a(m_ast.mk_app(f, m_bv->mk_numeral(val, bit_width)), m_ast);
|
||||
enode* s = alloc_enode(a, 0, nullptr, null_var);
|
||||
set_value_node(s, s);
|
||||
SASSERT_EQ(get_value(s), val);
|
||||
return s;
|
||||
}
|
||||
|
||||
slicing::enode* slicing::mk_interpreted_value_node(enode* s) {
|
||||
SASSERT(is_value(s));
|
||||
// NOTE: how this is used now, the node will not yet be contained in the egraph.
|
||||
enode* n = alloc_enode(s->get_app()->get_arg(0), 0, nullptr, null_var);
|
||||
info(n).value_node = s;
|
||||
n->mark_interpreted();
|
||||
SASSERT(n->interpreted());
|
||||
SASSERT_EQ(get_value_node(n), s);
|
||||
return n;
|
||||
}
|
||||
|
||||
bool slicing::is_value(enode* n) const {
|
||||
SASSERT(is_app(n->get_expr())); // we only create app nodes at the moment; if this fails just return false here.
|
||||
app* a = n->get_app();
|
||||
return a->get_num_args() == 1 && m_bv->is_numeral(a->get_arg(0));
|
||||
}
|
||||
|
||||
rational slicing::get_value(enode* s) const {
|
||||
SASSERT(is_value(s));
|
||||
rational val;
|
||||
|
@ -525,7 +541,10 @@ namespace polysat {
|
|||
}
|
||||
|
||||
bool slicing::try_get_value(enode* s, rational& val) const {
|
||||
bool const ok = m_bv->is_numeral(s->get_expr(), val);
|
||||
app* a = s->get_app();
|
||||
if (a->get_num_args() != 1)
|
||||
return false;
|
||||
bool const ok = m_bv->is_numeral(a->get_arg(0), val);
|
||||
SASSERT_EQ(ok, is_value(s));
|
||||
return ok;
|
||||
}
|
||||
|
@ -591,6 +610,7 @@ namespace polysat {
|
|||
SASSERT(is_conflict());
|
||||
m_egraph.begin_explain();
|
||||
if (m_disequality_conflict) {
|
||||
LOG("Disequality conflict: " << m_disequality_conflict);
|
||||
enode* eqn = m_disequality_conflict;
|
||||
SASSERT(eqn->is_equality());
|
||||
SASSERT_EQ(eqn->value(), l_false);
|
||||
|
@ -654,18 +674,23 @@ namespace polysat {
|
|||
m_tmp_deps.reset();
|
||||
if (x != null_var) LOG("Variable v" << x << " with slice " << slice_pp(*this, sx));
|
||||
if (y != null_var) LOG("Variable v" << y << " with slice " << slice_pp(*this, sy));
|
||||
SASSERT(x != null_var || y == null_var);
|
||||
SASSERT(y != null_var || x == null_var);
|
||||
// SASSERT(x != null_var || y == null_var);
|
||||
// SASSERT(y != null_var || x == null_var);
|
||||
|
||||
// Algorithm 1 in BitvectorsMCSAT
|
||||
signed_constraint c;
|
||||
if (x == null_var) {
|
||||
/* nothing to do */
|
||||
SASSERT_EQ(y, null_var);
|
||||
}
|
||||
else if (y == null_var) {
|
||||
SASSERT(get_value_node(sx));
|
||||
unsigned hi, lo;
|
||||
VERIFY(find_range_in_ancestor(sx, var2slice(x), hi, lo));
|
||||
LOG("Variable v" << x << " has solver-value " << m_solver.get_value(x));
|
||||
LOG("Subslice v" << x << "[" << hi << ":" << lo << "] has value " << get_value(get_value_node(sx)));
|
||||
UNREACHABLE();
|
||||
/*
|
||||
SASSERT(is_value(sx->get_root()));
|
||||
// the egraph has derived that x (or a sub-slice thereof) must have value b that differs from the currently assigned value of x.
|
||||
// the explanation is: lits ==> x[...] = b
|
||||
enode* const xn = var2slice(x)->get_root();
|
||||
|
@ -680,8 +705,8 @@ namespace polysat {
|
|||
unsigned hi, lo;
|
||||
VERIFY(find_range_in_ancestor(sx, var2slice(x), hi, lo));
|
||||
LOG("Variable v" << x << " has solver-value " << m_solver.get_value(x));
|
||||
LOG("Subslice v" << x << "[" << hi << ":" << lo << "] has value " << get_value(sx->get_root()));
|
||||
rational const b = get_value(sx->get_root());
|
||||
LOG("Subslice v" << x << "[" << hi << ":" << lo << "] has value " << get_value(get_value_node(sx)));
|
||||
rational const b = get_value(get_value_node(sx));
|
||||
// TODO: problematic case when this assertion is violated:
|
||||
// 1. v3 := v2[0:0]
|
||||
// 2. propagate value assignment v2 := 1 from some other constraints.
|
||||
|
@ -711,12 +736,16 @@ namespace polysat {
|
|||
*/
|
||||
}
|
||||
else {
|
||||
display_tree(std::cout);
|
||||
SASSERT(y != null_var);
|
||||
SASSERT(x != y);
|
||||
SASSERT(get_value_node(sx));
|
||||
SASSERT(get_value_node(sy));
|
||||
unsigned x_hi, x_lo, y_hi, y_lo;
|
||||
VERIFY(find_range_in_ancestor(sx, var2slice(x), x_hi, x_lo));
|
||||
VERIFY(find_range_in_ancestor(sy, var2slice(y), y_hi, y_lo));
|
||||
LOG("find_range_in_ancestor: v" << x << "[" << x_hi << ":" << x_lo << "] = " << slice_pp(*this, sx) << ", slice-value " << get_value(sx->get_root()));
|
||||
LOG("find_range_in_ancestor: v" << y << "[" << y_hi << ":" << y_lo << "] = " << slice_pp(*this, sy) << ", slice-value " << get_value(sy->get_root()));
|
||||
LOG("find_range_in_ancestor: v" << x << "[" << x_hi << ":" << x_lo << "] = " << slice_pp(*this, sx) << ", slice-value " << get_value(get_value_node(sx)));
|
||||
LOG("find_range_in_ancestor: v" << y << "[" << y_hi << ":" << y_lo << "] = " << slice_pp(*this, sy) << ", slice-value " << get_value(get_value_node(sy)));
|
||||
if (m_solver.is_assigned(x)) {
|
||||
rational sval = mod2k(machine_div2k(m_solver.get_value(x), x_lo), x_hi - x_lo + 1);
|
||||
LOG("Variable v" << x << " has solver-value " << m_solver.get_value(x) << ", i.e., v" << x << "[" << x_hi << ":" << x_lo << "] = " << sval);
|
||||
|
@ -730,8 +759,8 @@ namespace polysat {
|
|||
// below is TODO
|
||||
|
||||
|
||||
// LOG("Slice sx=" << slice_pp(*this, sx) << " has value " << get_value(sx->get_root()));
|
||||
// LOG("Slice sy=" << slice_pp(*this, sy) << " has value " << get_value(sy->get_root()));
|
||||
// LOG("Slice sx=" << slice_pp(*this, sx) << " has value " << get_value(get_value_node(sx)));
|
||||
// LOG("Slice sy=" << slice_pp(*this, sy) << " has value " << get_value(get_value_node(sy)));
|
||||
// the egraph has derived that x (or a subslice of x) must be equal to y (or a subslice of y),
|
||||
// but the currently assigned values differ.
|
||||
// the explanation is: lits ==> x[...] = y[...]
|
||||
|
@ -746,13 +775,13 @@ namespace polysat {
|
|||
// (it is also hard and probably not useful to handle otherwise)
|
||||
|
||||
// we handle one special case for now
|
||||
if (sx->get_root() == var2slice(x)->get_root() && m_solver.get_value(x) != get_value(var2slice(x)->get_root())) {
|
||||
LOG("Variable v" << x << " has solver-value " << m_solver.get_value(x) << " and slicing-value " << get_value(var2slice(x)->get_root()));
|
||||
if (sx->get_root() == var2slice(x)->get_root() && m_solver.get_value(x) != get_value(get_value_node(var2slice(x)))) {
|
||||
LOG("Variable v" << x << " has solver-value " << m_solver.get_value(x) << " and slicing-value " << get_value(get_value_node(var2slice(x))));
|
||||
// here, the learned clause should be y = value(y) ==> x = slicing-value(x)
|
||||
signed_constraint d = m_solver.eq(m_solver.var(y), m_solver.get_value(y));
|
||||
LOG("Premise: " << lit_pp(m_solver, d));
|
||||
cb.insert_eval(~d);
|
||||
c = m_solver.eq(m_solver.var(x), get_value(sx->get_root()));
|
||||
c = m_solver.eq(m_solver.var(x), get_value(get_value_node(sx)));
|
||||
}
|
||||
}
|
||||
else {
|
||||
|
@ -777,6 +806,7 @@ namespace polysat {
|
|||
LOG("Conclusion: <conflict>");
|
||||
}
|
||||
|
||||
std::abort();
|
||||
return cb.build();
|
||||
}
|
||||
|
||||
|
@ -784,11 +814,11 @@ namespace polysat {
|
|||
SASSERT(invariant());
|
||||
SASSERT(m_marked_lits.empty());
|
||||
|
||||
enode* r = s->get_root();
|
||||
SASSERT(is_value(r)); // by convention, value slices are always the root; and this method may only be called if s is equivalent to a value node.
|
||||
enode* n = get_value_node(s);
|
||||
SASSERT(is_value(n));
|
||||
|
||||
SASSERT(m_tmp_deps.empty());
|
||||
explain_equal(s, r, m_tmp_deps);
|
||||
explain_equal(s, n, m_tmp_deps);
|
||||
|
||||
for (void* dp : m_tmp_deps) {
|
||||
dep_t const d = dep_t::decode(dp);
|
||||
|
@ -838,22 +868,58 @@ namespace polysat {
|
|||
}
|
||||
|
||||
void slicing::egraph_on_merge(enode* root, enode* other) {
|
||||
SASSERT(!is_value(other)); // by convention, interpreted nodes are always chosen as root
|
||||
if (is_value(root)) {
|
||||
// LOG("on_merge:\nroot " << e_pp(root) << "other " << e_pp(other));
|
||||
if (root->interpreted())
|
||||
return;
|
||||
SASSERT(!other->interpreted()); // by convention, interpreted nodes are always chosen as root
|
||||
SASSERT(root != other);
|
||||
SASSERT_EQ(root, root->get_root());
|
||||
SASSERT_EQ(root, other->get_root());
|
||||
|
||||
enode* v1 = info(root).value_node; // root is the root
|
||||
enode* v2 = info(other).value_node; // 'other' was its own root before the merge
|
||||
if (v1 && v2 && get_value(v1) != get_value(v2)) {
|
||||
// we have a conflict. add interpreted enodes to make the egraph realize it.
|
||||
enode* i1 = mk_interpreted_value_node(v1);
|
||||
enode* i2 = mk_interpreted_value_node(v2);
|
||||
m_egraph.merge(i1, v1, dep_t().encode());
|
||||
m_egraph.merge(i2, v2, dep_t().encode());
|
||||
SASSERT(is_conflict());
|
||||
return;
|
||||
}
|
||||
|
||||
if (v1 || v2) {
|
||||
if (!v1) set_value_node(root, v2);
|
||||
if (!v2) set_value_node(other, v1);
|
||||
rational const val = get_value(v1 ? v1 : v2);
|
||||
for (enode* n : euf::enode_class(other)) {
|
||||
pvar const v = slice2var(n);
|
||||
if (v == null_var)
|
||||
continue;
|
||||
if (m_solver.is_assigned(v))
|
||||
continue;
|
||||
LOG("on_merge: v" << v << " := " << get_value(root));
|
||||
m_solver.assign_propagate_by_slicing(v, get_value(root));
|
||||
// TODO: congruence? what if all base slices of a variable are equivalent to values?
|
||||
// -> could track, for each variable, how many of its base slices are (not) equivalent to values. (update on split and merge)
|
||||
LOG("on_merge: v" << v << " := " << val);
|
||||
m_solver.assign_propagate_by_slicing(v, val);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void slicing::set_value_node(enode* s, enode* value_node) {
|
||||
SASSERT(!info(s).value_node);
|
||||
SASSERT(is_value(value_node));
|
||||
info(s).value_node = value_node;
|
||||
if (s != value_node) {
|
||||
m_trail.push_back(trail_item::set_value_node);
|
||||
m_enode_trail.push_back(s);
|
||||
}
|
||||
}
|
||||
|
||||
void slicing::undo_set_value_node() {
|
||||
enode* s = m_enode_trail.back();
|
||||
m_enode_trail.pop_back();
|
||||
info(s).value_node = nullptr;
|
||||
}
|
||||
|
||||
void slicing::egraph_on_propagate(enode* lit, enode* ante) {
|
||||
// ante may be set when symmetric equality is added by congruence
|
||||
if (ante)
|
||||
|
@ -882,7 +948,17 @@ namespace polysat {
|
|||
}
|
||||
|
||||
bool slicing::egraph_merge(enode* s1, enode* s2, dep_t dep) {
|
||||
LOG("egraph_merge: " << slice_pp(*this, s1) << " and " << slice_pp(*this, s2));
|
||||
SASSERT_EQ(width(s1), width(s2));
|
||||
enode* v1 = get_value_node(s1);
|
||||
enode* v2 = get_value_node(s2);
|
||||
if (v1 && v2 && get_value(v1) == get_value(v2)) {
|
||||
// optimization: if s1, s2 are already equivalent to the same value,
|
||||
// then they could have been merged already and we do not need to record 'dep'.
|
||||
// merge the value slices instead.
|
||||
m_egraph.merge(v1, v2, dep_t().encode());
|
||||
return !is_conflict();
|
||||
}
|
||||
if (dep.is_value()) {
|
||||
if (is_value(s1))
|
||||
std::swap(s1, s2);
|
||||
|
@ -896,6 +972,7 @@ namespace polysat {
|
|||
}
|
||||
|
||||
bool slicing::merge_base(enode* s1, enode* s2, dep_t dep) {
|
||||
LOG("merge_base: " << slice_pp(*this, s1) << " and " << slice_pp(*this, s2));
|
||||
SASSERT(!has_sub(s1));
|
||||
SASSERT(!has_sub(s2));
|
||||
return egraph_merge(s1, s2, dep);
|
||||
|
@ -968,6 +1045,7 @@ namespace polysat {
|
|||
}
|
||||
|
||||
bool slicing::merge(enode* x, enode* y, dep_t dep) {
|
||||
LOG("merge: " << slice_pp(*this, x) << " and " << slice_pp(*this, y));
|
||||
SASSERT_EQ(width(x), width(y));
|
||||
if (!has_sub(x) && !has_sub(y))
|
||||
return merge_base(x, y, dep);
|
||||
|
@ -990,28 +1068,28 @@ namespace polysat {
|
|||
enode_vector& ys = m_tmp3;
|
||||
SASSERT(xs.empty());
|
||||
SASSERT(ys.empty());
|
||||
on_scope_exit clear_vectors([&xs, &ys](){
|
||||
xs.clear();
|
||||
ys.clear();
|
||||
});
|
||||
// TODO: we don't always have to collect the full base if intermediate nodes are already equal
|
||||
get_root_base(x, xs);
|
||||
get_root_base(y, ys);
|
||||
SASSERT(all_of(xs, [](enode* s) { return s->is_root(); }));
|
||||
SASSERT(all_of(ys, [](enode* s) { return s->is_root(); }));
|
||||
bool result = (xs == ys);
|
||||
xs.clear();
|
||||
ys.clear();
|
||||
return result;
|
||||
get_base(x, xs);
|
||||
get_base(y, ys);
|
||||
if (xs.size() != ys.size())
|
||||
return false;
|
||||
for (unsigned i = xs.size(); i-- > 0; )
|
||||
if (xs[i]->get_root() != ys[i]->get_root())
|
||||
return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
template <bool should_get_root>
|
||||
void slicing::get_base_core(enode* src, enode_vector& out_base) const {
|
||||
void slicing::get_base(enode* src, enode_vector& out_base) const {
|
||||
enode_vector& todo = m_tmp1;
|
||||
SASSERT(todo.empty());
|
||||
todo.push_back(src);
|
||||
while (!todo.empty()) {
|
||||
enode* s = todo.back();
|
||||
todo.pop_back();
|
||||
if constexpr (should_get_root) {
|
||||
s = s->get_root();
|
||||
}
|
||||
if (!has_sub(s))
|
||||
out_base.push_back(s);
|
||||
else {
|
||||
|
@ -1022,14 +1100,6 @@ namespace polysat {
|
|||
SASSERT(todo.empty());
|
||||
}
|
||||
|
||||
void slicing::get_base(enode* src, enode_vector& out_base) const {
|
||||
get_base_core<false>(src, out_base);
|
||||
}
|
||||
|
||||
void slicing::get_root_base(enode* src, enode_vector& out_base) const {
|
||||
get_base_core<true>(src, out_base);
|
||||
}
|
||||
|
||||
pvar slicing::mk_extract(enode* src, unsigned hi, unsigned lo, pvar replay_var) {
|
||||
LOG("mk_extract: src=" << slice_pp(*this, src) << " hi=" << hi << " lo=" << lo);
|
||||
enode_vector& slices = m_tmp3;
|
||||
|
@ -1237,7 +1307,7 @@ namespace polysat {
|
|||
}
|
||||
else {
|
||||
LOG(" store disequality v" << x << " != v" << y);
|
||||
enode* n = find_or_alloc_disequality(sy, sx, lit);
|
||||
enode* n = find_or_alloc_disequality(sx, sy, lit);
|
||||
if (!m_disequality_conflict && is_equal(sx, sy)) {
|
||||
add_var_congruence_if_needed(x);
|
||||
add_var_congruence_if_needed(y);
|
||||
|
@ -1396,11 +1466,11 @@ namespace polysat {
|
|||
out << "v" << v << ":";
|
||||
base.reset();
|
||||
enode* const vs = var2slice(v);
|
||||
get_root_base(vs, base);
|
||||
get_base(vs, base);
|
||||
for (enode* s : base)
|
||||
display(out << " ", s);
|
||||
if (is_value(vs->get_root()))
|
||||
out << " [root_value: " << get_value(vs->get_root()) << "]";
|
||||
if (enode* vnode = get_value_node(vs))
|
||||
out << " [root_value: " << get_value(vnode) << "]";
|
||||
out << "\n";
|
||||
}
|
||||
return out;
|
||||
|
@ -1426,8 +1496,8 @@ namespace polysat {
|
|||
out << " parent=" << parent(s)->get_id();
|
||||
if (!s->is_root())
|
||||
out << " root=" << s->get_root_id();
|
||||
if (is_value(s->get_root()))
|
||||
out << " root_value=" << get_value(s->get_root());
|
||||
if (enode* n = get_value_node(s))
|
||||
out << " value=" << get_value(n);
|
||||
// if (is_proper_slice(s))
|
||||
// out << " <slice>";
|
||||
if (is_value(s))
|
||||
|
@ -1448,8 +1518,11 @@ namespace polysat {
|
|||
std::ostream& slicing::display(std::ostream& out, enode* s) const {
|
||||
out << "{id:" << s->get_id();
|
||||
out << ",w:" << width(s);
|
||||
out << ",root:" << s->get_root_id();
|
||||
if (is_value(s))
|
||||
out << ",<value>";
|
||||
out << ",value:" << get_value(s);
|
||||
if (s->interpreted())
|
||||
out << ",<interpreted>";
|
||||
if (is_concat(s))
|
||||
out << ",<concat>";
|
||||
if (is_equality(s))
|
||||
|
@ -1462,6 +1535,8 @@ namespace polysat {
|
|||
VERIFY(m_tmp1.empty());
|
||||
VERIFY(m_tmp2.empty());
|
||||
VERIFY(m_tmp3.empty());
|
||||
if (is_conflict()) // if we break a loop early on conflict, we can't guarantee that all properties are satisfied
|
||||
return true;
|
||||
for (enode* s : m_egraph.nodes()) {
|
||||
// we use equality enodes only to track disequalities
|
||||
if (s->is_equality())
|
||||
|
@ -1472,12 +1547,9 @@ namespace polysat {
|
|||
VERIFY_EQ(var2slice(v)->get_root(), s->get_root());
|
||||
}
|
||||
// if slice has a value, it should be propagated to its sub-slices
|
||||
if (is_value(s)) {
|
||||
VERIFY(s->is_root());
|
||||
if (has_sub(s)) {
|
||||
VERIFY(is_value(sub_hi(s)));
|
||||
VERIFY(is_value(sub_lo(s)));
|
||||
}
|
||||
if (get_value_node(s) && has_sub(s)) {
|
||||
VERIFY(get_value_node(sub_hi(s)));
|
||||
VERIFY(get_value_node(sub_lo(s)));
|
||||
}
|
||||
// a base slice is never equivalent to a congruence node
|
||||
if (is_slice(s) && !has_sub(s)) {
|
||||
|
@ -1491,14 +1563,18 @@ namespace polysat {
|
|||
VERIFY(is_slice(sv));
|
||||
VERIFY(s->num_args() >= 2);
|
||||
}
|
||||
// properties below only matter for representatives
|
||||
/////////////////////////////////////////////////////////////////
|
||||
// class properties (i.e., skip non-representatives)
|
||||
if (!s->is_root())
|
||||
continue;
|
||||
bool const sub = has_sub(s);
|
||||
enode_vector s_base;
|
||||
get_base(s, s_base);
|
||||
for (enode* n : euf::enode_class(s)) {
|
||||
// equivalence class only contains slices of equal length
|
||||
VERIFY_EQ(width(s), width(n));
|
||||
// either all nodes in the class have subslices or none do
|
||||
SASSERT_EQ(sub, has_sub(n));
|
||||
// bases of equivalent nodes are equivalent
|
||||
enode_vector n_base;
|
||||
get_base(n, n_base);
|
||||
|
|
|
@ -41,6 +41,7 @@ namespace polysat {
|
|||
|
||||
public:
|
||||
using enode = euf::enode;
|
||||
using enode_pair = euf::enode_pair;
|
||||
using enode_vector = euf::enode_vector;
|
||||
|
||||
private:
|
||||
|
@ -81,6 +82,7 @@ namespace polysat {
|
|||
// We use the following kinds of enodes:
|
||||
// - proper slices (of variables)
|
||||
// - value slices
|
||||
// - interpreted value nodes ... these are short-lived, and only created to immediately trigger a conflict inside the egraph
|
||||
// - virtual concat(...) expressions
|
||||
// - equalities between enodes (to track disequalities; currently not represented in slice_info)
|
||||
struct slice_info {
|
||||
|
@ -92,6 +94,7 @@ namespace polysat {
|
|||
enode* slice = nullptr; // if enode corresponds to a concat(...) expression, this field links to the represented slice.
|
||||
enode* sub_hi = nullptr; // upper subslice s[|s|-1:cut+1]
|
||||
enode* sub_lo = nullptr; // lower subslice s[cut:0]
|
||||
enode* value_node = nullptr; // the root of an equivalence class stores the value slice here, if any
|
||||
|
||||
void reset() { *this = slice_info(); }
|
||||
bool has_sub() const { return !!sub_hi; }
|
||||
|
@ -103,7 +106,7 @@ namespace polysat {
|
|||
bool is_slice(enode* n) const;
|
||||
|
||||
bool is_proper_slice(enode* n) const { return !is_value(n) && is_slice(n); }
|
||||
bool is_value(enode* n) const { return n->interpreted(); }
|
||||
bool is_value(enode* n) const;
|
||||
bool is_concat(enode* n) const;
|
||||
bool is_equality(enode* n) const { return n->is_equality(); }
|
||||
|
||||
|
@ -148,6 +151,9 @@ namespace polysat {
|
|||
|
||||
enode* parent(enode* s) const { return info(s).parent; }
|
||||
|
||||
enode* get_value_node(enode* s) const { return info(s->get_root()).value_node; }
|
||||
void set_value_node(enode* s, enode* value_node);
|
||||
|
||||
bool has_sub(enode* s) const { return info(s).has_sub(); }
|
||||
|
||||
/// Upper subslice (direct child, not necessarily the representative)
|
||||
|
@ -159,6 +165,9 @@ namespace polysat {
|
|||
// Retrieve (or create) a slice representing the given value.
|
||||
enode* mk_value_slice(rational const& val, unsigned bit_width);
|
||||
|
||||
// Turn value node into unwrapped BV constant node
|
||||
enode* mk_interpreted_value_node(enode* value_slice);
|
||||
|
||||
rational get_value(enode* s) const;
|
||||
bool try_get_value(enode* s, rational& val) const;
|
||||
|
||||
|
@ -166,13 +175,8 @@ namespace polysat {
|
|||
void split(enode* s, unsigned cut);
|
||||
void split_core(enode* s, unsigned cut);
|
||||
|
||||
template <bool should_get_root>
|
||||
void get_base_core(enode* src, enode_vector& out_base) const;
|
||||
|
||||
/// Retrieve base slices s_1,...,s_n such that src == s_1 ++ ... ++ s_n (actual descendant subslices)
|
||||
void get_base(enode* src, enode_vector& out_base) const;
|
||||
/// Retrieve base slices s_1,...,s_n such that src == s_1 ++ ... ++ s_n (representatives of subslices)
|
||||
void get_root_base(enode* src, enode_vector& out_base) const;
|
||||
|
||||
/// Retrieve (or create) base slices s_1,...,s_n such that src[hi:lo] == s_1 ++ ... ++ s_n.
|
||||
/// If output_full_src is true, return the new base for src, i.e., src == s_1 ++ ... ++ s_n.
|
||||
|
@ -195,6 +199,7 @@ namespace polysat {
|
|||
/** Extract reason for x == y */
|
||||
void explain_equal(pvar x, pvar y, ptr_vector<void>& out_deps);
|
||||
|
||||
void egraph_on_make(enode* n);
|
||||
void egraph_on_merge(enode* root, enode* other);
|
||||
void egraph_on_propagate(enode* lit, enode* ante);
|
||||
|
||||
|
@ -233,18 +238,19 @@ namespace polysat {
|
|||
using extract_args_hash = obj_hash<extract_args>;
|
||||
using extract_map = map<extract_args, pvar, extract_args_hash, extract_args_eq>;
|
||||
extract_map m_extract_dedup;
|
||||
|
||||
bool is_extract(pvar v) const;
|
||||
bool is_extract(pvar v, pvar& out_src, pvar& out_hi, pvar& out_lo) const;
|
||||
// svector<extract_args> m_extract_origin; // pvar -> extract_args
|
||||
// TODO: add 'm_extract_origin' (pvar -> extract_args)? 1. for dependency tracking when sharing subslice trees; 2. for easily checking if a variable is an extraction of another; 3. also makes the replay easier
|
||||
// bool is_extract(pvar v) const { return m_extract_origin[v].src != null_var; }
|
||||
|
||||
enum class trail_item : std::uint8_t {
|
||||
add_var,
|
||||
split_core,
|
||||
mk_extract,
|
||||
mk_concat,
|
||||
set_value_node,
|
||||
};
|
||||
svector<trail_item> m_trail;
|
||||
enode_vector m_split_trail;
|
||||
enode_vector m_enode_trail;
|
||||
svector<extract_args> m_extract_trail;
|
||||
unsigned_vector m_scopes;
|
||||
|
||||
|
@ -260,6 +266,7 @@ namespace polysat {
|
|||
void undo_add_var();
|
||||
void undo_split_core();
|
||||
void undo_mk_extract();
|
||||
void undo_set_value_node();
|
||||
|
||||
mutable enode_vector m_tmp1;
|
||||
mutable enode_vector m_tmp2;
|
||||
|
@ -305,6 +312,8 @@ namespace polysat {
|
|||
};
|
||||
friend std::ostream& operator<<(std::ostream& out, dep_pp const& d) { return d.display(out); }
|
||||
|
||||
euf::egraph::e_pp e_pp(enode* n) const { return euf::egraph::e_pp(m_egraph, n); }
|
||||
|
||||
public:
|
||||
slicing(solver& s);
|
||||
|
||||
|
|
|
@ -29,7 +29,9 @@ namespace polysat {
|
|||
|
||||
class scoped_solver_slicing : public solver_scope_slicing, public solver {
|
||||
public:
|
||||
scoped_solver_slicing(): solver(lim, pars) {}
|
||||
scoped_solver_slicing(): solver(lim, pars) {
|
||||
set_log_enabled(false);
|
||||
}
|
||||
slicing& sl() { return m_slicing; }
|
||||
};
|
||||
|
||||
|
@ -75,12 +77,14 @@ namespace polysat {
|
|||
VERIFY(sl.merge(y_5_0, sl.var2slice(b), sat::literal(2)));
|
||||
std::cout << sl << "\n";
|
||||
|
||||
/*
|
||||
slicing::enode_vector x_base;
|
||||
sl.get_root_base(sl.var2slice(x), x_base);
|
||||
slicing::enode_vector y_base;
|
||||
sl.get_root_base(sl.var2slice(y), y_base);
|
||||
VERIFY(sl.merge(x_base, y_base, sat::literal(3)));
|
||||
std::cout << sl << "\n";
|
||||
*/
|
||||
|
||||
sl.display_tree(std::cout);
|
||||
VERIFY(sl.invariant());
|
||||
|
@ -212,6 +216,11 @@ namespace polysat {
|
|||
// x[3:0] = z
|
||||
// y = 0b1001
|
||||
// z = 0b0111
|
||||
//
|
||||
// x: xxxxxx
|
||||
// y: 1001
|
||||
// z: 0111
|
||||
// (no conflict)
|
||||
static void test5() {
|
||||
std::cout << __func__ << "\n";
|
||||
scoped_solver_slicing s;
|
||||
|
@ -232,6 +241,43 @@ namespace polysat {
|
|||
VERIFY(sl.merge(sl.var2slice(z), seven, sat::literal(107)));
|
||||
std::cout << "v" << z << " = 7\n" << sl << "\n";
|
||||
|
||||
VERIFY(!sl.is_conflict());
|
||||
|
||||
sl.display_tree(std::cout);
|
||||
VERIFY(sl.invariant());
|
||||
}
|
||||
|
||||
// x[5:2] = y
|
||||
// x[3:0] = z
|
||||
// y = 0b1001
|
||||
// z = 0b1011
|
||||
//
|
||||
// x: xxxxxx
|
||||
// y: 1001
|
||||
// z: 1011
|
||||
// (conflict)
|
||||
static void test5b() {
|
||||
std::cout << __func__ << "\n";
|
||||
scoped_solver_slicing s;
|
||||
slicing& sl = s.sl();
|
||||
pvar x = s.add_var(6);
|
||||
std::cout << sl << "\n";
|
||||
|
||||
pvar y = sl.mk_extract(x, 5, 2);
|
||||
std::cout << "v" << y << " := v" << x << "[5:2]\n" << sl << "\n";
|
||||
pvar z = sl.mk_extract(x, 3, 0);
|
||||
std::cout << "v" << z << " := v" << x << "[3:0]\n" << sl << "\n";
|
||||
|
||||
slicing::enode* nine = sl.mk_value_slice(rational(9), 4);
|
||||
VERIFY(sl.merge(sl.var2slice(y), nine, sat::literal(109)));
|
||||
std::cout << "v" << y << " = 9\n" << sl << "\n";
|
||||
|
||||
slicing::enode* eleven = sl.mk_value_slice(rational(11), 4);
|
||||
VERIFY(!sl.merge(sl.var2slice(z), eleven, sat::literal(107)));
|
||||
std::cout << "v" << z << " = 11\n" << sl << "\n";
|
||||
|
||||
VERIFY(sl.is_conflict());
|
||||
|
||||
sl.display_tree(std::cout);
|
||||
VERIFY(sl.invariant());
|
||||
}
|
||||
|
@ -264,7 +310,6 @@ namespace polysat {
|
|||
// in various permutations
|
||||
static void test7() {
|
||||
std::cout << __func__ << "\n";
|
||||
scoped_set_log_enabled _logging(false);
|
||||
scoped_solver_slicing s;
|
||||
slicing& sl = s.sl();
|
||||
pdd x = s.var(s.add_var(8));
|
||||
|
@ -374,6 +419,68 @@ namespace polysat {
|
|||
VERIFY(sl.invariant());
|
||||
}
|
||||
|
||||
// x == 7
|
||||
// y == 7
|
||||
static void test11() {
|
||||
std::cout << __func__ << "\n";
|
||||
scoped_solver_slicing s;
|
||||
slicing& sl = s.sl();
|
||||
pvar x = s.add_var(8);
|
||||
sl.add_constraint(s.eq(s.var(x), 7));
|
||||
pvar a = sl.mk_extract(x, 7, 6);
|
||||
pvar b = sl.mk_extract(x, 3, 0);
|
||||
sl.display_tree(std::cout);
|
||||
|
||||
pvar y = s.add_var(8);
|
||||
pvar c = sl.mk_extract(y, 7, 6);
|
||||
pvar d = sl.mk_extract(y, 1, 0);
|
||||
sl.display_tree(std::cout);
|
||||
|
||||
sl.add_constraint(s.eq(s.var(y), 7));
|
||||
sl.display_tree(std::cout);
|
||||
}
|
||||
|
||||
|
||||
|
||||
// x == 0
|
||||
// x != y
|
||||
// y <= 1
|
||||
// y[0:0] = z
|
||||
// z == w
|
||||
// w == 0
|
||||
static void test12() {
|
||||
std::cout << __func__ << "\n";
|
||||
// TODO
|
||||
scoped_solver_slicing s;
|
||||
slicing& sl = s.sl();
|
||||
}
|
||||
|
||||
|
||||
// mk_extract y := x[63:32]
|
||||
// a * x = 123
|
||||
// b * y = 456
|
||||
// a = 1, propagates x = 123 (by constraint)
|
||||
// b = 1, propagates y = 456 (by constraint), propagates x = 0 (by slicing)
|
||||
// Conflict in slicing.
|
||||
static void test13() {
|
||||
std::cout << __func__ << "\n";
|
||||
scoped_solver_slicing s;
|
||||
slicing& sl = s.sl();
|
||||
pvar x = s.add_var(64);
|
||||
pvar y = sl.mk_extract(x, 63, 32);
|
||||
pvar a = s.add_var(64);
|
||||
pvar b = s.add_var(32);
|
||||
sl.add_constraint(s.eq(s.var(a)*s.var(x), 123)); // NOTE: this does nothing inside slicing atm.
|
||||
sl.add_constraint(s.eq(s.var(b)*s.var(y), 456));
|
||||
sl.add_value(a, 1);
|
||||
sl.add_value(x, 123);
|
||||
sl.add_value(b, 1);
|
||||
sl.add_value(y, 456);
|
||||
VERIFY(sl.is_conflict());
|
||||
sl.display_tree(std::cout);
|
||||
VERIFY(sl.invariant());
|
||||
}
|
||||
|
||||
}; // test_slicing
|
||||
|
||||
} // namespace polysat
|
||||
|
@ -386,10 +493,13 @@ void tst_slicing() {
|
|||
test_slicing::test3();
|
||||
test_slicing::test4();
|
||||
test_slicing::test5();
|
||||
test_slicing::test5b();
|
||||
test_slicing::test6();
|
||||
test_slicing::test7();
|
||||
test_slicing::test8();
|
||||
test_slicing::test9();
|
||||
test_slicing::test10();
|
||||
test_slicing::test11();
|
||||
test_slicing::test13();
|
||||
std::cout << "ok\n";
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue