mirror of
https://github.com/Z3Prover/z3
synced 2025-04-24 01:25:31 +00:00
euf
Signed-off-by: Nikolaj Bjorner <nbjorner@microsoft.com>
This commit is contained in:
parent
96587bf708
commit
65e6d942ac
23 changed files with 1338 additions and 39 deletions
9
src/ast/euf/CMakeLists.txt
Normal file
9
src/ast/euf/CMakeLists.txt
Normal file
|
@ -0,0 +1,9 @@
|
|||
z3_add_component(euf
|
||||
SOURCES
|
||||
euf_enode.cpp
|
||||
euf_etable.cpp
|
||||
euf_egraph.cpp
|
||||
COMPONENT_DEPENDENCIES
|
||||
ast
|
||||
util
|
||||
)
|
302
src/ast/euf/euf_egraph.cpp
Normal file
302
src/ast/euf/euf_egraph.cpp
Normal file
|
@ -0,0 +1,302 @@
|
|||
/*++
|
||||
Copyright (c) 2020 Microsoft Corporation
|
||||
|
||||
Module Name:
|
||||
|
||||
euf_egraph.cpp
|
||||
|
||||
Abstract:
|
||||
|
||||
E-graph layer
|
||||
|
||||
Author:
|
||||
|
||||
Nikolaj Bjorner (nbjorner) 2020-08-23
|
||||
|
||||
--*/
|
||||
|
||||
#include "ast/euf/euf_egraph.h"
|
||||
|
||||
namespace euf {
|
||||
|
||||
void egraph::undo_eq(enode* r1, enode* n1, unsigned r2_num_parents) {
|
||||
enode* r2 = r1->get_root();
|
||||
r2->dec_class_size(r1->class_size());
|
||||
std::swap(r1->m_next, r2->m_next);
|
||||
auto begin = r2->begin_parents() + r2_num_parents, end = r2->end_parents();
|
||||
for (auto it = begin; it != end; ++it)
|
||||
m_table.erase(*it);
|
||||
for (enode* c : enode_class(r1))
|
||||
c->m_root = r1;
|
||||
for (auto it = begin; it != end; ++it)
|
||||
m_table.insert(*it);
|
||||
r2->m_parents.shrink(r2_num_parents);
|
||||
unmerge_justification(n1);
|
||||
}
|
||||
|
||||
enode* egraph::mk_enode(expr* f, enode * const* args) {
|
||||
unsigned num_args = is_app(f) ? to_app(f)->get_num_args() : 0;
|
||||
enode* n = enode::mk(m_region, f, num_args, args);
|
||||
m_nodes.push_back(n);
|
||||
m_exprs.push_back(f);
|
||||
return n;
|
||||
}
|
||||
|
||||
void egraph::reinsert(enode* n) {
|
||||
unsigned num_parents = n->m_parents.size();
|
||||
for (unsigned i = 0; i < num_parents; ++i) {
|
||||
enode* p = n->m_parents[i];
|
||||
if (is_equality(p)) {
|
||||
reinsert_equality(p);
|
||||
}
|
||||
else {
|
||||
auto rc = m_table.insert(p);
|
||||
merge(rc.first, p, justification::congruence(rc.second));
|
||||
if (inconsistent())
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void egraph::reinsert_equality(enode* p) {
|
||||
if (p->get_arg(0)->get_root() == p->get_arg(1)->get_root())
|
||||
m_new_eqs.push_back(p);
|
||||
}
|
||||
|
||||
bool egraph::is_equality(enode* p) const {
|
||||
return m.is_eq(p->get_owner());
|
||||
}
|
||||
|
||||
void egraph::dedup_equalities() {
|
||||
unsigned j = 0;
|
||||
for (enode* p : m_new_eqs) {
|
||||
if (!p->is_marked1())
|
||||
m_new_eqs[j++] = p;
|
||||
p->mark1();
|
||||
}
|
||||
for (enode* p : m_new_eqs)
|
||||
p->unmark1();
|
||||
m_new_eqs.shrink(j);
|
||||
}
|
||||
|
||||
void egraph::force_push() {
|
||||
for (; m_num_scopes > 0; --m_num_scopes) {
|
||||
scope s;
|
||||
s.m_inconsistent = m_inconsistent;
|
||||
s.m_num_eqs = m_eqs.size();
|
||||
s.m_num_nodes = m_nodes.size();
|
||||
m_scopes.push_back(s);
|
||||
m_region.push_scope();
|
||||
}
|
||||
}
|
||||
|
||||
void egraph::update_children(enode* n) {
|
||||
for (enode* child : enode_args(n))
|
||||
child->get_root()->add_parent(n);
|
||||
n->set_update_children();
|
||||
}
|
||||
|
||||
enode* egraph::mk(expr* f, enode *const* args) {
|
||||
SASSERT(!find(f));
|
||||
force_push();
|
||||
enode *n = mk_enode(f, args);
|
||||
SASSERT(n->class_size() == 1);
|
||||
m_expr2enode.setx(f->get_id(), n, nullptr);
|
||||
if (n->num_args() == 0 && m.is_unique_value(f))
|
||||
n->mark_interpreted();
|
||||
if (n->num_args() == 0)
|
||||
return n;
|
||||
if (is_equality(n)) {
|
||||
update_children(n);
|
||||
return n;
|
||||
}
|
||||
enode_bool_pair p = m_table.insert(n);
|
||||
enode* r = p.first;
|
||||
if (r == n) {
|
||||
update_children(n);
|
||||
}
|
||||
else {
|
||||
SASSERT(r->get_owner() != n->get_owner());
|
||||
merge_justification(n, r, justification::congruence(p.second));
|
||||
std::swap(n->m_next, r->m_next);
|
||||
n->m_root = r;
|
||||
r->inc_class_size(n->class_size());
|
||||
push_eq(n, n, r->num_parents());
|
||||
}
|
||||
return n;
|
||||
}
|
||||
|
||||
void egraph::pop(unsigned num_scopes) {
|
||||
if (num_scopes <= m_num_scopes) {
|
||||
m_num_scopes -= num_scopes;
|
||||
return;
|
||||
}
|
||||
num_scopes -= m_num_scopes;
|
||||
unsigned old_lim = m_scopes.size() - num_scopes;
|
||||
scope s = m_scopes[old_lim];
|
||||
for (unsigned i = m_eqs.size(); i-- > s.m_num_eqs; ) {
|
||||
auto const& p = m_eqs[i];
|
||||
undo_eq(p.r1, p.n1, p.r2_num_parents);
|
||||
}
|
||||
for (unsigned i = m_nodes.size(); i-- > s.m_num_nodes; ) {
|
||||
enode* n = m_nodes[i];
|
||||
if (n->num_args() > 1)
|
||||
m_table.erase(n);
|
||||
m_expr2enode[n->get_owner_id()] = nullptr;
|
||||
n->~enode();
|
||||
}
|
||||
m_inconsistent = s.m_inconsistent;
|
||||
m_eqs.shrink(s.m_num_eqs);
|
||||
m_nodes.shrink(s.m_num_nodes);
|
||||
m_exprs.shrink(s.m_num_nodes);
|
||||
m_scopes.shrink(old_lim);
|
||||
m_region.pop_scope(num_scopes);
|
||||
}
|
||||
|
||||
void egraph::merge(enode* n1, enode* n2, justification j) {
|
||||
SASSERT(m.get_sort(n1->get_owner()) == m.get_sort(n2->get_owner()));
|
||||
TRACE("euf", tout << n1->get_owner_id() << " == " << n2->get_owner_id() << "\n";);
|
||||
force_push();
|
||||
enode* r1 = n1->get_root();
|
||||
enode* r2 = n2->get_root();
|
||||
if (r1 == r2)
|
||||
return;
|
||||
if (r1->interpreted() && r2->interpreted()) {
|
||||
set_conflict(r1, r2, j);
|
||||
return;
|
||||
}
|
||||
if ((r1->class_size() > r2->class_size() && !r1->interpreted()) || r2->interpreted()) {
|
||||
std::swap(r1, r2);
|
||||
std::swap(n1, n2);
|
||||
}
|
||||
for (enode* p : enode_parents(n1))
|
||||
m_table.erase(p);
|
||||
for (enode* p : enode_parents(n2))
|
||||
m_table.erase(p);
|
||||
push_eq(r1, n1, r2->num_parents());
|
||||
merge_justification(n1, n2, j);
|
||||
for (enode* c : enode_class(n1))
|
||||
c->m_root = r2;
|
||||
std::swap(r1->m_next, r2->m_next);
|
||||
r2->inc_class_size(r1->class_size());
|
||||
r2->m_parents.append(r1->m_parents);
|
||||
m_worklist.push_back(r2);
|
||||
}
|
||||
|
||||
void egraph::propagate() {
|
||||
m_new_eqs.reset();
|
||||
SASSERT(m_num_scopes == 0 || m_worklist.empty());
|
||||
unsigned head = 0, tail = m_worklist.size();
|
||||
while (head < tail && m.limit().inc() && !inconsistent()) {
|
||||
TRACE("euf", tout << "iterate: " << head << " " << tail << "\n";);
|
||||
for (unsigned i = head; i < tail && !inconsistent(); ++i) {
|
||||
enode* n = m_worklist[i]->get_root();
|
||||
if (!n->is_marked1()) {
|
||||
n->mark1();
|
||||
m_worklist[i] = n;
|
||||
reinsert(n);
|
||||
}
|
||||
}
|
||||
for (unsigned i = head; i < tail; ++i)
|
||||
m_worklist[i]->unmark1();
|
||||
head = tail;
|
||||
tail = m_worklist.size();
|
||||
}
|
||||
m_worklist.reset();
|
||||
dedup_equalities();
|
||||
}
|
||||
|
||||
void egraph::set_conflict(enode* n1, enode* n2, justification j) {
|
||||
if (m_inconsistent)
|
||||
return;
|
||||
m_inconsistent = true;
|
||||
m_n1 = n1;
|
||||
m_n2 = n2;
|
||||
m_justification = j;
|
||||
}
|
||||
|
||||
void egraph::merge_justification(enode* n1, enode* n2, justification j) {
|
||||
SASSERT(n1->reaches(n1->get_root()));
|
||||
n1->reverse_justification();
|
||||
n1->m_target = n2;
|
||||
n1->m_justification = j;
|
||||
SASSERT(n1->get_root()->reaches(n1));
|
||||
}
|
||||
|
||||
void egraph::unmerge_justification(enode* n1) {
|
||||
// r1 -> .. -> n1 -> n2 -> ... -> r2
|
||||
// where n2 = n1->m_target
|
||||
SASSERT(n1->get_root()->reaches(n1));
|
||||
n1->m_target = nullptr;
|
||||
n1->m_justification = justification::axiom();
|
||||
n1->get_root()->reverse_justification();
|
||||
// ---------------
|
||||
// n1 -> ... -> r1
|
||||
// n2 -> ... -> r2
|
||||
SASSERT(n1->reaches(n1->get_root()));
|
||||
SASSERT(n1->get_root()->m_target == nullptr);
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
void egraph::explain(ptr_vector<T>& justifications) {
|
||||
SASSERT(m_inconsistent);
|
||||
SASSERT(m_todo.empty());
|
||||
m_todo.push_back(m_n1);
|
||||
m_todo.push_back(m_n2);
|
||||
auto push_congruence = [&](enode* p, enode* q) {
|
||||
SASSERT(p->get_decl() == q->get_decl());
|
||||
for (enode* arg : enode_args(p))
|
||||
m_todo.push_back(arg);
|
||||
for (enode* arg : enode_args(q))
|
||||
m_todo.push_back(arg);
|
||||
};
|
||||
auto explain_node = [&](enode* n) {
|
||||
if (!n->m_target)
|
||||
return;
|
||||
if (n->is_marked1())
|
||||
return;
|
||||
n->mark1();
|
||||
if (n->m_justification.is_external())
|
||||
justifications.push_back(n->m_justification.ext<T>());
|
||||
else if (n->m_justification.is_congruence())
|
||||
push_congruence(n, n->m_target);
|
||||
n = n->m_target;
|
||||
if (!n->is_marked1())
|
||||
m_todo.push_back(n);
|
||||
};
|
||||
if (m_justification.is_external())
|
||||
justifications.push_back(m_justification.ext<T>());
|
||||
for (unsigned i = 0; i < m_todo.size(); ++i)
|
||||
explain_node(m_todo[i]);
|
||||
for (enode* n : m_todo)
|
||||
n->unmark1();
|
||||
}
|
||||
|
||||
void egraph::invariant() {
|
||||
for (enode* n : m_nodes)
|
||||
n->invariant();
|
||||
}
|
||||
|
||||
std::ostream& egraph::display(std::ostream& out) const {
|
||||
m_table.display(out);
|
||||
for (enode* n : m_nodes) {
|
||||
out << std::setw(5)
|
||||
<< n->get_owner_id() << " := ";
|
||||
out << n->get_root()->get_owner_id() << " ";
|
||||
expr* f = n->get_owner();
|
||||
if (is_app(f))
|
||||
out << to_app(f)->get_decl()->get_name() << " ";
|
||||
else if (is_quantifier(f))
|
||||
out << "q ";
|
||||
else
|
||||
out << "v ";
|
||||
for (enode* arg : enode_args(n))
|
||||
out << arg->get_owner_id() << " ";
|
||||
out << std::setw(20) << " parents: ";
|
||||
for (enode* p : enode_parents(n))
|
||||
out << p->get_owner_id() << " ";
|
||||
out << "\n";
|
||||
}
|
||||
return out;
|
||||
}
|
||||
}
|
109
src/ast/euf/euf_egraph.h
Normal file
109
src/ast/euf/euf_egraph.h
Normal file
|
@ -0,0 +1,109 @@
|
|||
/*++
|
||||
Copyright (c) 2020 Microsoft Corporation
|
||||
|
||||
Module Name:
|
||||
|
||||
euf_egraph.h
|
||||
|
||||
Abstract:
|
||||
|
||||
E-graph layer
|
||||
|
||||
Author:
|
||||
|
||||
Nikolaj Bjorner (nbjorner) 2020-08-23
|
||||
|
||||
Notes:
|
||||
|
||||
It relies on
|
||||
- data structures form the (legacy) SMT solver.
|
||||
- it still uses eager path compression.
|
||||
- delayed congruence table reconstruction from egg.
|
||||
- it does not deduplicate parents.
|
||||
|
||||
--*/
|
||||
|
||||
#pragma once
|
||||
#include "ast/euf/euf_enode.h"
|
||||
#include "ast/euf/euf_etable.h"
|
||||
|
||||
namespace euf {
|
||||
|
||||
struct add_eq_record {
|
||||
enode* r1;
|
||||
enode* n1;
|
||||
unsigned r2_num_parents;
|
||||
add_eq_record(enode* r1, enode* n1, unsigned r2_num_parents):
|
||||
r1(r1), n1(n1), r2_num_parents(r2_num_parents) {}
|
||||
};
|
||||
|
||||
class egraph {
|
||||
struct scope {
|
||||
bool m_inconsistent;
|
||||
unsigned m_num_eqs;
|
||||
unsigned m_num_nodes;
|
||||
};
|
||||
ast_manager& m;
|
||||
region m_region;
|
||||
enode_vector m_worklist;
|
||||
etable m_table;
|
||||
svector<add_eq_record> m_eqs;
|
||||
svector<scope> m_scopes;
|
||||
enode_vector m_expr2enode;
|
||||
enode_vector m_nodes;
|
||||
expr_ref_vector m_exprs;
|
||||
unsigned m_num_scopes { 0 };
|
||||
bool m_inconsistent { false };
|
||||
enode *m_n1 { nullptr };
|
||||
enode *m_n2 { nullptr };
|
||||
justification m_justification;
|
||||
enode_vector m_new_eqs;
|
||||
enode_vector m_todo;
|
||||
|
||||
void push_eq(enode* r1, enode* n1, unsigned r2_num_parents) {
|
||||
m_eqs.push_back(add_eq_record(r1, n1, r2_num_parents));
|
||||
}
|
||||
void undo_eq(enode* r1, enode* n1, unsigned r2_num_parents);
|
||||
enode* mk_enode(expr* f, enode * const* args);
|
||||
void reinsert(enode* n);
|
||||
void force_push();
|
||||
void set_conflict(enode* n1, enode* n2, justification j);
|
||||
void merge(enode* n1, enode* n2, justification j);
|
||||
void merge_justification(enode* n1, enode* n2, justification j);
|
||||
void unmerge_justification(enode* n1);
|
||||
void dedup_equalities();
|
||||
bool is_equality(enode* n) const;
|
||||
void reinsert_equality(enode* p);
|
||||
void update_children(enode* n);
|
||||
public:
|
||||
egraph(ast_manager& m): m(m), m_table(m), m_exprs(m) {}
|
||||
enode* find(expr* f) { return m_expr2enode.get(f->get_id(), nullptr); }
|
||||
enode* mk(expr* f, enode *const* args);
|
||||
void push() { ++m_num_scopes; }
|
||||
void pop(unsigned num_scopes);
|
||||
/**
|
||||
\brief merge nodes, all effects are deferred to the propagation step.
|
||||
*/
|
||||
void merge(enode* n1, enode* n2, void* reason) { merge(n1, n2, justification::external(reason)); }
|
||||
|
||||
/**
|
||||
\brief propagate set of merges.
|
||||
This call may detect an inconsistency. Then inconsistent() is true.
|
||||
Use then explain() to extract an explanation for the conflict.
|
||||
|
||||
It may also infer new implied equalities, when the roots of the
|
||||
equated nodes are merged. Use then new_eqs() to extract the vector
|
||||
of new equalities.
|
||||
*/
|
||||
void propagate();
|
||||
bool inconsistent() const { return m_inconsistent; }
|
||||
enode_vector const& new_eqs() const { return m_new_eqs; }
|
||||
template <typename T>
|
||||
void explain(ptr_vector<T>& justifications);
|
||||
|
||||
void invariant();
|
||||
std::ostream& display(std::ostream& out) const;
|
||||
};
|
||||
|
||||
inline std::ostream& operator<<(std::ostream& out, egraph const& g) { return g.display(out); }
|
||||
}
|
101
src/ast/euf/euf_enode.cpp
Normal file
101
src/ast/euf/euf_enode.cpp
Normal file
|
@ -0,0 +1,101 @@
|
|||
/*++
|
||||
Copyright (c) 2020 Microsoft Corporation
|
||||
|
||||
Module Name:
|
||||
|
||||
euf_enode.cpp
|
||||
|
||||
Abstract:
|
||||
|
||||
enode layer
|
||||
|
||||
Author:
|
||||
|
||||
Nikolaj Bjorner (nbjorner) 2020-08-23
|
||||
|
||||
--*/
|
||||
|
||||
#include "ast/euf/euf_enode.h"
|
||||
|
||||
#pragma once
|
||||
|
||||
namespace euf {
|
||||
|
||||
void enode::invariant() {
|
||||
unsigned class_size = 0;
|
||||
bool found_root = false;
|
||||
bool found_this = false;
|
||||
for (enode* c : enode_class(this)) {
|
||||
VERIFY(c->m_root == m_root);
|
||||
found_root |= c == m_root;
|
||||
found_this |= c == this;
|
||||
}
|
||||
VERIFY(found_root);
|
||||
VERIFY(found_this);
|
||||
VERIFY(this != m_root || class_size == m_class_size);
|
||||
if (this == m_root) {
|
||||
for (enode* p : enode_parents(this)) {
|
||||
bool found = false;
|
||||
for (enode* arg : enode_args(p)) {
|
||||
found |= arg->get_root() == this;
|
||||
}
|
||||
VERIFY(found);
|
||||
}
|
||||
for (enode* c : enode_class(this)) {
|
||||
if (c == this)
|
||||
continue;
|
||||
for (enode* p : enode_parents(c)) {
|
||||
bool found = false;
|
||||
for (enode* q : enode_parents(this)) {
|
||||
found |= p->congruent(q);
|
||||
}
|
||||
VERIFY(found);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool enode::congruent(enode* n) const {
|
||||
if (get_decl() != n->get_decl())
|
||||
return false;
|
||||
if (num_args() != n->num_args())
|
||||
return false;
|
||||
SASSERT(!m_commutative || num_args() == 2);
|
||||
if (m_commutative &&
|
||||
get_arg(0)->get_root() == n->get_arg(1)->get_root() &&
|
||||
get_arg(1)->get_root() == n->get_arg(0)->get_root())
|
||||
return true;
|
||||
for (unsigned i = num_args(); i-- > 0; )
|
||||
if (get_arg(i)->get_root() != n->get_arg(i)->get_root())
|
||||
return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool enode::reaches(enode* n) const {
|
||||
enode const* r = this;
|
||||
while (r) {
|
||||
if (r == n)
|
||||
return true;
|
||||
r = r->m_target;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
void enode::reverse_justification() {
|
||||
enode* curr = m_target;
|
||||
enode* prev = this;
|
||||
justification js = m_justification;
|
||||
prev->m_target = nullptr;
|
||||
prev->m_justification = justification::axiom();
|
||||
while (curr != nullptr) {
|
||||
|
||||
enode* new_curr = curr->m_target;
|
||||
justification new_js = curr->m_justification;
|
||||
curr->m_target = prev;
|
||||
curr->m_justification = js;
|
||||
prev = curr;
|
||||
js = new_js;
|
||||
curr = new_curr;
|
||||
}
|
||||
}
|
||||
}
|
154
src/ast/euf/euf_enode.h
Normal file
154
src/ast/euf/euf_enode.h
Normal file
|
@ -0,0 +1,154 @@
|
|||
/*++
|
||||
Copyright (c) 2020 Microsoft Corporation
|
||||
|
||||
Module Name:
|
||||
|
||||
euf_enode.h
|
||||
|
||||
Abstract:
|
||||
|
||||
enode layer
|
||||
|
||||
Author:
|
||||
|
||||
Nikolaj Bjorner (nbjorner) 2020-08-23
|
||||
|
||||
--*/
|
||||
|
||||
#include "util/vector.h"
|
||||
#include "ast/ast.h"
|
||||
#include "ast/euf/euf_justification.h"
|
||||
|
||||
#pragma once
|
||||
|
||||
namespace euf {
|
||||
|
||||
class enode;
|
||||
|
||||
typedef ptr_vector<enode> enode_vector;
|
||||
typedef std::pair<enode*,enode*> enode_pair;
|
||||
typedef svector<enode_pair> enode_pair_vector;
|
||||
|
||||
class enode {
|
||||
expr* m_owner;
|
||||
bool m_mark1 { false };
|
||||
bool m_mark2 { false };
|
||||
bool m_commutative { false };
|
||||
bool m_update_children { false };
|
||||
bool m_interpreted { false };
|
||||
unsigned m_class_size { 1 };
|
||||
unsigned m_table_id { UINT_MAX };
|
||||
enode_vector m_parents;
|
||||
enode* m_next;
|
||||
enode* m_root;
|
||||
enode* m_target { nullptr };
|
||||
justification m_justification;
|
||||
enode* m_args[0];
|
||||
|
||||
friend class enode_args;
|
||||
friend class enode_parents;
|
||||
friend class enode_class;
|
||||
friend class etable;
|
||||
friend class egraph;
|
||||
|
||||
static unsigned get_enode_size(unsigned num_args) {
|
||||
return sizeof(enode) + num_args * sizeof(enode*);
|
||||
}
|
||||
|
||||
static enode* mk(region& r, expr* f, unsigned num_args, enode* const* args) {
|
||||
void* mem = r.allocate(get_enode_size(num_args));
|
||||
enode* n = new (mem) enode();
|
||||
n->m_owner = f;
|
||||
n->m_next = n;
|
||||
n->m_root = n;
|
||||
n->m_commutative = num_args == 2 && is_app(f) && to_app(f)->get_decl()->is_commutative();
|
||||
for (unsigned i = 0; i < num_args; ++i) {
|
||||
n->m_args[i] = args[i];
|
||||
}
|
||||
return n;
|
||||
}
|
||||
|
||||
void set_update_children() { m_update_children = true; }
|
||||
public:
|
||||
~enode() {
|
||||
SASSERT(m_root == this);
|
||||
SASSERT(class_size() == 1);
|
||||
if (m_update_children) {
|
||||
for (unsigned i = 0; i < num_args(); ++i) {
|
||||
SASSERT(m_args[i]->get_root()->m_parents.back() == this);
|
||||
m_args[i]->get_root()->m_parents.pop_back();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
enode* const* args() const { return m_args; }
|
||||
unsigned num_args() const { return is_app(m_owner) ? to_app(m_owner)->get_num_args() : 0; }
|
||||
unsigned num_parents() const { return m_parents.size(); }
|
||||
bool interpreted() const { return m_interpreted; }
|
||||
void mark_interpreted() { SASSERT(num_args() == 0); m_interpreted = true; }
|
||||
|
||||
enode* get_arg(unsigned i) const { SASSERT(i < num_args()); return m_args[i]; }
|
||||
unsigned hash() const { return m_owner->hash(); }
|
||||
func_decl* get_decl() const { return is_app(m_owner) ? to_app(m_owner)->get_decl() : nullptr; }
|
||||
unsigned get_table_id() const { return m_table_id; }
|
||||
void set_table_id(unsigned t) { m_table_id = t; }
|
||||
|
||||
void mark1() { m_mark1 = true; }
|
||||
void unmark1() { m_mark1 = false; }
|
||||
bool is_marked1() { return m_mark1; }
|
||||
void add_parent(enode* p) { m_parents.push_back(p); }
|
||||
unsigned class_size() const { return m_class_size; }
|
||||
enode* get_root() const { return m_root; }
|
||||
expr* get_owner() const { return m_owner; }
|
||||
unsigned get_owner_id() const { return m_owner->get_id(); }
|
||||
void inc_class_size(unsigned n) { m_class_size += n; }
|
||||
void dec_class_size(unsigned n) { m_class_size -= n; }
|
||||
|
||||
void reverse_justification();
|
||||
bool reaches(enode* n) const;
|
||||
|
||||
enode* const* begin_parents() const { return m_parents.begin(); }
|
||||
enode* const* end_parents() const { return m_parents.end(); }
|
||||
|
||||
void invariant();
|
||||
bool congruent(enode* n) const;
|
||||
};
|
||||
|
||||
class enode_args {
|
||||
enode& n;
|
||||
public:
|
||||
enode_args(enode& _n):n(_n) {}
|
||||
enode_args(enode* _n):n(*_n) {}
|
||||
enode* const* begin() const { return n.m_args; }
|
||||
enode* const* end() const { return n.m_args + n.num_args(); }
|
||||
};
|
||||
|
||||
class enode_parents {
|
||||
enode const& n;
|
||||
public:
|
||||
enode_parents(enode const& _n):n(_n) {}
|
||||
enode_parents(enode const* _n):n(*_n) {}
|
||||
enode* const* begin() const { return n.m_parents.begin(); }
|
||||
enode* const* end() const { return n.m_parents.end(); }
|
||||
};
|
||||
|
||||
class enode_class {
|
||||
enode & n;
|
||||
public:
|
||||
class iterator {
|
||||
enode* m_first;
|
||||
enode* m_last;
|
||||
public:
|
||||
iterator(enode* n, enode* m): m_first(n), m_last(m) {}
|
||||
enode* operator*() { return m_first; }
|
||||
iterator& operator++() { if (!m_last) m_last = m_first; m_first = m_first->m_next; return *this; }
|
||||
iterator operator++(int) { iterator tmp = *this; ++*this; return tmp; }
|
||||
bool operator==(iterator const& other) const { return m_last == other.m_last && m_first == other.m_first; }
|
||||
bool operator!=(iterator const& other) const { return !(*this == other); }
|
||||
};
|
||||
enode_class(enode & _n):n(_n) {}
|
||||
enode_class(enode * _n):n(*_n) {}
|
||||
iterator begin() const { return iterator(&n, nullptr); }
|
||||
iterator end() const { return iterator(&n, &n); }
|
||||
};
|
||||
}
|
252
src/ast/euf/euf_etable.cpp
Normal file
252
src/ast/euf/euf_etable.cpp
Normal file
|
@ -0,0 +1,252 @@
|
|||
/*++
|
||||
Copyright (c) 2006 Microsoft Corporation
|
||||
|
||||
Module Name:
|
||||
|
||||
euf_etable.cpp
|
||||
|
||||
Author:
|
||||
|
||||
Leonardo de Moura (leonardo) 2008-02-19.
|
||||
|
||||
Revision History:
|
||||
|
||||
Ported from smt_cg_table.cpp
|
||||
|
||||
--*/
|
||||
#include "ast/euf/euf_etable.h"
|
||||
#include "ast/ast_pp.h"
|
||||
#include "ast/ast_ll_pp.h"
|
||||
|
||||
namespace euf {
|
||||
|
||||
// one table per func_decl implementation
|
||||
unsigned etable::cg_hash::operator()(enode * n) const {
|
||||
SASSERT(n->get_decl()->is_flat_associative() || n->num_args() >= 3);
|
||||
unsigned a, b, c;
|
||||
a = b = 0x9e3779b9;
|
||||
c = 11;
|
||||
|
||||
unsigned i = n->num_args();
|
||||
while (i >= 3) {
|
||||
i--;
|
||||
a += n->get_arg(i)->get_root()->hash();
|
||||
i--;
|
||||
b += n->get_arg(i)->get_root()->hash();
|
||||
i--;
|
||||
c += n->get_arg(i)->get_root()->hash();
|
||||
mix(a, b, c);
|
||||
}
|
||||
|
||||
switch (i) {
|
||||
case 2:
|
||||
b += n->get_arg(1)->get_root()->hash();
|
||||
Z3_fallthrough;
|
||||
case 1:
|
||||
c += n->get_arg(0)->get_root()->hash();
|
||||
}
|
||||
mix(a, b, c);
|
||||
return c;
|
||||
}
|
||||
|
||||
bool etable::cg_eq::operator()(enode * n1, enode * n2) const {
|
||||
SASSERT(n1->get_decl() == n2->get_decl());
|
||||
unsigned num = n1->num_args();
|
||||
if (num != n2->num_args()) {
|
||||
return false;
|
||||
}
|
||||
for (unsigned i = 0; i < num; i++)
|
||||
if (n1->get_arg(i)->get_root() != n2->get_arg(i)->get_root())
|
||||
return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
etable::etable(ast_manager & m):
|
||||
m_manager(m) {
|
||||
}
|
||||
|
||||
etable::~etable() {
|
||||
reset();
|
||||
}
|
||||
|
||||
void * etable::mk_table_for(func_decl * d) {
|
||||
void * r;
|
||||
SASSERT(d->get_arity() >= 1);
|
||||
switch (d->get_arity()) {
|
||||
case 1:
|
||||
r = TAG(void*, alloc(unary_table), UNARY);
|
||||
SASSERT(GET_TAG(r) == UNARY);
|
||||
return r;
|
||||
case 2:
|
||||
if (d->is_flat_associative()) {
|
||||
// applications of declarations that are flat-assoc (e.g., +) may have many arguments.
|
||||
r = TAG(void*, alloc(table), NARY);
|
||||
SASSERT(GET_TAG(r) == NARY);
|
||||
return r;
|
||||
}
|
||||
else if (d->is_commutative()) {
|
||||
r = TAG(void*, alloc(comm_table, cg_comm_hash(), cg_comm_eq(m_commutativity)), BINARY_COMM);
|
||||
SASSERT(GET_TAG(r) == BINARY_COMM);
|
||||
return r;
|
||||
}
|
||||
else {
|
||||
r = TAG(void*, alloc(binary_table), BINARY);
|
||||
SASSERT(GET_TAG(r) == BINARY);
|
||||
return r;
|
||||
}
|
||||
default:
|
||||
r = TAG(void*, alloc(table), NARY);
|
||||
SASSERT(GET_TAG(r) == NARY);
|
||||
return r;
|
||||
}
|
||||
}
|
||||
|
||||
unsigned etable::set_table_id(enode * n) {
|
||||
func_decl * f = n->get_decl();
|
||||
unsigned tid;
|
||||
if (!m_func_decl2id.find(f, tid)) {
|
||||
tid = m_tables.size();
|
||||
m_func_decl2id.insert(f, tid);
|
||||
m_manager.inc_ref(f);
|
||||
SASSERT(tid <= m_tables.size());
|
||||
m_tables.push_back(mk_table_for(f));
|
||||
}
|
||||
SASSERT(tid < m_tables.size());
|
||||
n->set_table_id(tid);
|
||||
DEBUG_CODE({
|
||||
unsigned tid_prime;
|
||||
SASSERT(m_func_decl2id.find(n->get_decl(), tid_prime) && tid == tid_prime);
|
||||
});
|
||||
return tid;
|
||||
}
|
||||
|
||||
void etable::reset() {
|
||||
for (void* t : m_tables) {
|
||||
switch (GET_TAG(t)) {
|
||||
case UNARY:
|
||||
dealloc(UNTAG(unary_table*, t));
|
||||
break;
|
||||
case BINARY:
|
||||
dealloc(UNTAG(binary_table*, t));
|
||||
break;
|
||||
case BINARY_COMM:
|
||||
dealloc(UNTAG(comm_table*, t));
|
||||
break;
|
||||
case NARY:
|
||||
dealloc(UNTAG(table*, t));
|
||||
break;
|
||||
}
|
||||
}
|
||||
m_tables.reset();
|
||||
for (auto const& kv : m_func_decl2id) {
|
||||
m_manager.dec_ref(kv.m_key);
|
||||
}
|
||||
m_func_decl2id.reset();
|
||||
}
|
||||
|
||||
void etable::display(std::ostream & out) const {
|
||||
for (auto const& kv : m_func_decl2id) {
|
||||
void * t = m_tables[kv.m_value];
|
||||
out << mk_pp(kv.m_key, m_manager) << ": ";
|
||||
switch (GET_TAG(t)) {
|
||||
case UNARY:
|
||||
display_unary(out, t);
|
||||
break;
|
||||
case BINARY:
|
||||
display_binary(out, t);
|
||||
break;
|
||||
case BINARY_COMM:
|
||||
display_binary_comm(out, t);
|
||||
break;
|
||||
case NARY:
|
||||
display_nary(out, t);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void etable::display_binary(std::ostream& out, void* t) const {
|
||||
binary_table* tb = UNTAG(binary_table*, t);
|
||||
out << "b ";
|
||||
for (enode* n : *tb) {
|
||||
out << n->get_owner_id() << " " << cg_binary_hash()(n) << " ";
|
||||
}
|
||||
out << "\n";
|
||||
}
|
||||
|
||||
void etable::display_binary_comm(std::ostream& out, void* t) const {
|
||||
comm_table* tb = UNTAG(comm_table*, t);
|
||||
out << "bc ";
|
||||
for (enode* n : *tb) {
|
||||
out << n->get_owner_id() << " ";
|
||||
}
|
||||
out << "\n";
|
||||
}
|
||||
|
||||
void etable::display_unary(std::ostream& out, void* t) const {
|
||||
unary_table* tb = UNTAG(unary_table*, t);
|
||||
out << "un ";
|
||||
for (enode* n : *tb) {
|
||||
out << n->get_owner_id() << " ";
|
||||
}
|
||||
out << "\n";
|
||||
}
|
||||
|
||||
void etable::display_nary(std::ostream& out, void* t) const {
|
||||
table* tb = UNTAG(table*, t);
|
||||
out << "nary ";
|
||||
for (enode* n : *tb) {
|
||||
out << n->get_owner_id() << " ";
|
||||
}
|
||||
out << "\n";
|
||||
}
|
||||
|
||||
|
||||
enode_bool_pair etable::insert(enode * n) {
|
||||
// it doesn't make sense to insert a constant.
|
||||
SASSERT(n->num_args() > 0);
|
||||
SASSERT(!m_manager.is_and(n->get_owner()));
|
||||
SASSERT(!m_manager.is_or(n->get_owner()));
|
||||
enode * n_prime;
|
||||
void * t = get_table(n);
|
||||
switch (static_cast<table_kind>(GET_TAG(t))) {
|
||||
case UNARY:
|
||||
n_prime = UNTAG(unary_table*, t)->insert_if_not_there(n);
|
||||
return enode_bool_pair(n_prime, false);
|
||||
case BINARY:
|
||||
n_prime = UNTAG(binary_table*, t)->insert_if_not_there(n);
|
||||
TRACE("euf", tout << "insert: " << n->get_owner_id() << " " << cg_binary_hash()(n) << " inserted: " << (n == n_prime) << " " << n_prime->get_owner_id() << "\n";
|
||||
display_binary(tout, t); tout << "contains_ptr: " << contains_ptr(n) << "\n";);
|
||||
return enode_bool_pair(n_prime, false);
|
||||
case BINARY_COMM:
|
||||
m_commutativity = false;
|
||||
n_prime = UNTAG(comm_table*, t)->insert_if_not_there(n);
|
||||
return enode_bool_pair(n_prime, m_commutativity);
|
||||
default:
|
||||
n_prime = UNTAG(table*, t)->insert_if_not_there(n);
|
||||
return enode_bool_pair(n_prime, false);
|
||||
}
|
||||
}
|
||||
|
||||
void etable::erase(enode * n) {
|
||||
SASSERT(n->num_args() > 0);
|
||||
void * t = get_table(n);
|
||||
switch (static_cast<table_kind>(GET_TAG(t))) {
|
||||
case UNARY:
|
||||
UNTAG(unary_table*, t)->erase(n);
|
||||
break;
|
||||
case BINARY:
|
||||
TRACE("euf", tout << "erase: " << n->get_owner_id() << " " << cg_binary_hash()(n) << " contains: " << contains_ptr(n) << "\n";);
|
||||
UNTAG(binary_table*, t)->erase(n);
|
||||
break;
|
||||
case BINARY_COMM:
|
||||
UNTAG(comm_table*, t)->erase(n);
|
||||
break;
|
||||
default:
|
||||
UNTAG(table*, t)->erase(n);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
};
|
||||
|
217
src/ast/euf/euf_etable.h
Normal file
217
src/ast/euf/euf_etable.h
Normal file
|
@ -0,0 +1,217 @@
|
|||
/*++
|
||||
Copyright (c) 2006 Microsoft Corporation
|
||||
|
||||
Module Name:
|
||||
|
||||
euf_etable.h
|
||||
|
||||
Author:
|
||||
|
||||
Leonardo de Moura (leonardo) 2008-02-19.
|
||||
|
||||
Revision History:
|
||||
|
||||
copied from smt_cg_table
|
||||
|
||||
--*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "ast/euf/euf_enode.h"
|
||||
#include "util/hashtable.h"
|
||||
#include "util/chashtable.h"
|
||||
|
||||
namespace euf {
|
||||
|
||||
typedef std::pair<enode *, bool> enode_bool_pair;
|
||||
|
||||
// one table per function symbol
|
||||
|
||||
/**
|
||||
\brief Congruence table.
|
||||
*/
|
||||
class etable {
|
||||
struct cg_unary_hash {
|
||||
unsigned operator()(enode * n) const {
|
||||
SASSERT(n->num_args() == 1);
|
||||
return n->get_arg(0)->get_root()->hash();
|
||||
}
|
||||
};
|
||||
|
||||
struct cg_unary_eq {
|
||||
bool operator()(enode * n1, enode * n2) const {
|
||||
SASSERT(n1->num_args() == 1);
|
||||
SASSERT(n2->num_args() == 1);
|
||||
SASSERT(n1->get_decl() == n2->get_decl());
|
||||
return n1->get_arg(0)->get_root() == n2->get_arg(0)->get_root();
|
||||
}
|
||||
};
|
||||
|
||||
typedef chashtable<enode *, cg_unary_hash, cg_unary_eq> unary_table;
|
||||
|
||||
struct cg_binary_hash {
|
||||
unsigned operator()(enode * n) const {
|
||||
SASSERT(n->num_args() == 2);
|
||||
return combine_hash(n->get_arg(0)->get_root()->hash(), n->get_arg(1)->get_root()->hash());
|
||||
}
|
||||
};
|
||||
|
||||
struct cg_binary_eq {
|
||||
bool operator()(enode * n1, enode * n2) const {
|
||||
SASSERT(n1->num_args() == 2);
|
||||
SASSERT(n2->num_args() == 2);
|
||||
SASSERT(n1->get_decl() == n2->get_decl());
|
||||
return
|
||||
n1->get_arg(0)->get_root() == n2->get_arg(0)->get_root() &&
|
||||
n1->get_arg(1)->get_root() == n2->get_arg(1)->get_root();
|
||||
}
|
||||
};
|
||||
|
||||
typedef chashtable<enode*, cg_binary_hash, cg_binary_eq> binary_table;
|
||||
|
||||
struct cg_comm_hash {
|
||||
unsigned operator()(enode * n) const {
|
||||
SASSERT(n->num_args() == 2);
|
||||
unsigned h1 = n->get_arg(0)->get_root()->hash();
|
||||
unsigned h2 = n->get_arg(1)->get_root()->hash();
|
||||
if (h1 > h2)
|
||||
std::swap(h1, h2);
|
||||
return hash_u((h1 << 16) | (h2 & 0xFFFF));
|
||||
}
|
||||
};
|
||||
|
||||
struct cg_comm_eq {
|
||||
bool & m_commutativity;
|
||||
cg_comm_eq(bool & c):m_commutativity(c) {}
|
||||
bool operator()(enode * n1, enode * n2) const {
|
||||
SASSERT(n1->num_args() == 2);
|
||||
SASSERT(n2->num_args() == 2);
|
||||
SASSERT(n1->get_decl() == n2->get_decl());
|
||||
enode * c1_1 = n1->get_arg(0)->get_root();
|
||||
enode * c1_2 = n1->get_arg(1)->get_root();
|
||||
enode * c2_1 = n2->get_arg(0)->get_root();
|
||||
enode * c2_2 = n2->get_arg(1)->get_root();
|
||||
if (c1_1 == c2_1 && c1_2 == c2_2) {
|
||||
return true;
|
||||
}
|
||||
if (c1_1 == c2_2 && c1_2 == c2_1) {
|
||||
m_commutativity = true;
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
};
|
||||
|
||||
typedef chashtable<enode*, cg_comm_hash, cg_comm_eq> comm_table;
|
||||
|
||||
struct cg_hash {
|
||||
unsigned operator()(enode * n) const;
|
||||
};
|
||||
|
||||
struct cg_eq {
|
||||
bool operator()(enode * n1, enode * n2) const;
|
||||
};
|
||||
|
||||
typedef chashtable<enode*, cg_hash, cg_eq> table;
|
||||
|
||||
ast_manager & m_manager;
|
||||
bool m_commutativity; //!< true if the last found congruence used commutativity
|
||||
ptr_vector<void> m_tables;
|
||||
obj_map<func_decl, unsigned> m_func_decl2id;
|
||||
|
||||
enum table_kind {
|
||||
UNARY,
|
||||
BINARY,
|
||||
BINARY_COMM,
|
||||
NARY
|
||||
};
|
||||
|
||||
void * mk_table_for(func_decl * d);
|
||||
unsigned set_table_id(enode * n);
|
||||
|
||||
void * get_table(enode * n) {
|
||||
unsigned tid = n->get_table_id();
|
||||
if (tid == UINT_MAX)
|
||||
tid = set_table_id(n);
|
||||
SASSERT(tid < m_tables.size());
|
||||
return m_tables[tid];
|
||||
}
|
||||
|
||||
void display_binary(std::ostream& out, void* t) const;
|
||||
void display_binary_comm(std::ostream& out, void* t) const;
|
||||
void display_unary(std::ostream& out, void* t) const;
|
||||
void display_nary(std::ostream& out, void* t) const;
|
||||
|
||||
public:
|
||||
etable(ast_manager & m);
|
||||
|
||||
~etable();
|
||||
|
||||
/**
|
||||
\brief Try to insert n into the table. If the table already
|
||||
contains an element n' congruent to n, then do nothing and
|
||||
return n' and a boolean indicating whether n and n' are congruence
|
||||
modulo commutativity, otherwise insert n and return (n,false).
|
||||
*/
|
||||
enode_bool_pair insert(enode * n);
|
||||
|
||||
void erase(enode * n);
|
||||
|
||||
bool contains(enode * n) const {
|
||||
SASSERT(n->num_args() > 0);
|
||||
void * t = const_cast<etable*>(this)->get_table(n);
|
||||
switch (static_cast<table_kind>(GET_TAG(t))) {
|
||||
case UNARY:
|
||||
return UNTAG(unary_table*, t)->contains(n);
|
||||
case BINARY:
|
||||
return UNTAG(binary_table*, t)->contains(n);
|
||||
case BINARY_COMM:
|
||||
return UNTAG(comm_table*, t)->contains(n);
|
||||
default:
|
||||
return UNTAG(table*, t)->contains(n);
|
||||
}
|
||||
}
|
||||
|
||||
enode * find(enode * n) const {
|
||||
SASSERT(n->num_args() > 0);
|
||||
enode * r = nullptr;
|
||||
void * t = const_cast<etable*>(this)->get_table(n);
|
||||
switch (static_cast<table_kind>(GET_TAG(t))) {
|
||||
case UNARY:
|
||||
return UNTAG(unary_table*, t)->find(n, r) ? r : nullptr;
|
||||
case BINARY:
|
||||
return UNTAG(binary_table*, t)->find(n, r) ? r : nullptr;
|
||||
case BINARY_COMM:
|
||||
return UNTAG(comm_table*, t)->find(n, r) ? r : nullptr;
|
||||
default:
|
||||
return UNTAG(table*, t)->find(n, r) ? r : nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
bool contains_ptr(enode * n) const {
|
||||
enode * r;
|
||||
SASSERT(n->num_args() > 0);
|
||||
void * t = const_cast<etable*>(this)->get_table(n);
|
||||
switch (static_cast<table_kind>(GET_TAG(t))) {
|
||||
case UNARY:
|
||||
return UNTAG(unary_table*, t)->find(n, r) && n == r;
|
||||
case BINARY:
|
||||
return UNTAG(binary_table*, t)->find(n, r) && n == r;
|
||||
case BINARY_COMM:
|
||||
return UNTAG(comm_table*, t)->find(n, r) && n == r;
|
||||
default:
|
||||
return UNTAG(table*, t)->find(n, r) && n == r;
|
||||
}
|
||||
}
|
||||
|
||||
void reset();
|
||||
|
||||
void display(std::ostream & out) const;
|
||||
|
||||
};
|
||||
|
||||
};
|
||||
|
||||
|
||||
|
||||
|
59
src/ast/euf/euf_justification.h
Normal file
59
src/ast/euf/euf_justification.h
Normal file
|
@ -0,0 +1,59 @@
|
|||
/*++
|
||||
Copyright (c) 2020 Microsoft Corporation
|
||||
|
||||
Module Name:
|
||||
|
||||
euf_justification.h
|
||||
|
||||
Abstract:
|
||||
|
||||
justification structure for euf
|
||||
|
||||
Author:
|
||||
|
||||
Nikolaj Bjorner (nbjorner) 2020-08-23
|
||||
|
||||
--*/
|
||||
|
||||
#pragma once
|
||||
|
||||
namespace euf {
|
||||
|
||||
class justification {
|
||||
enum kind_t {
|
||||
axiom_t,
|
||||
congruence_t,
|
||||
external_t
|
||||
};
|
||||
kind_t m_kind;
|
||||
bool m_comm;
|
||||
void* m_external;
|
||||
justification(bool comm):
|
||||
m_kind(congruence_t),
|
||||
m_comm(comm),
|
||||
m_external(nullptr)
|
||||
{}
|
||||
|
||||
justification(void* ext):
|
||||
m_kind(external_t),
|
||||
m_comm(false),
|
||||
m_external(ext)
|
||||
{}
|
||||
|
||||
public:
|
||||
justification():
|
||||
m_kind(axiom_t),
|
||||
m_comm(false),
|
||||
m_external(nullptr)
|
||||
{}
|
||||
|
||||
static justification axiom() { return justification(); }
|
||||
static justification congruence(bool c) { return justification(c); }
|
||||
static justification external(void* ext) { return justification(ext); }
|
||||
|
||||
bool is_external() const { return m_kind == external_t; }
|
||||
bool is_congruence() const { return m_kind == congruence_t; }
|
||||
template <typename T>
|
||||
T* ext() const { SASSERT(is_external()); return static_cast<T*>(m_external); }
|
||||
};
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue