#include "ast/seq/seq_eq_approx.h"
#include "ast/ast_pp.h"
#include <functional>
#include <unordered_set>
#include <vector>
namespace {
struct key_hash {
size_t operator()(std::vector<unsigned> const& k) const {
uint64_t h = 1469598103934665603ull;
for (unsigned x : k)
h = (h ^ x) * 1099511628211ull;
return static_cast<size_t>(h);
}
};
}
void seq_eq_approx::add_view(expr* t, seq::view const& v) {
seq::view_vector& views = m_views.insert_if_not_there(t, seq::view_vector());
views.push_back(v);
m_pin.push_back(t);
}
void seq_eq_approx::set_views(expr* t, seq::view_vector const& views) {
m_views.remove(t);
for (auto const& v : views) {
add_view(t, v);
}
}
void seq_eq_approx::unset_views(expr* t) {
m_views.remove(t);
}
void seq_eq_approx::reset_views() {
m_views.reset();
m_pin.reset();
m_used.reset();
}
seq::view_vector const* seq_eq_approx::get_views(expr* t) const {
return m_views.contains(t) ? &m_views[t] : nullptr;
}
lbool seq_eq_approx::nullable(expr* r) {
lbool i = re().get_info(r).nullable;
if (i != l_undef)
return i;
expr_ref nb = m_rw.is_nullable(r);
return m.is_true(nb) ? l_true : m.is_false(nb) ? l_false : l_undef;
}
expr_ref_pair_vector const& seq_eq_approx::cofactors(expr* r) {
return m_rw.get_derive().get_cached_cofactors(m_mode, r);
}
bool seq_eq_approx::out_of_budget() {
if (m_budget == 0 || !m.inc())
return true;
--m_budget;
return false;
}
void seq_eq_approx::add_segment(expr* r, segments& out) {
m_pin.push_back(r);
seq::view_vector views;
views.push_back(seq::view::membership(r, m));
out.push_back(views);
}
void seq_eq_approx::to_segments(expr* t, segments& out) {
if (u().str.is_empty(t))
return;
if (u().str.is_concat(t)) {
for (auto arg : *to_app(t))
to_segments(arg, out);
return;
}
sort* seq_sort = t->get_sort();
VERIFY(u().is_seq(seq_sort));
sort_ref re_sort(re().mk_re(seq_sort), m);
seq::view_vector views;
if (m_views.find(t, views)) {
DEBUG_CODE(all_of(views, [&](seq::view const& v) { return v.m_state && v.m_state->get_sort() == re_sort; }););
if (!m_used.contains(t))
m_used.push_back(t);
out.push_back(views);
return;
}
zstring s;
if (u().str.is_string(t, s)) {
if (s.length() > 0)
add_segment(re().mk_to_re(t), out);
return;
}
expr* elem = nullptr;
if (u().str.is_unit(t, elem)) {
add_segment(m.is_value(elem) ? (expr*)re().mk_to_re(t) : (expr*)re().mk_full_char(re_sort), out);
}
else
add_segment(re().mk_full_seq(re_sort), out); }
lbool seq_eq_approx::segment_done(seq::view_vector const& views, ptr_vector<expr> const& states) {
for (unsigned i = 0; i < views.size(); ++i) {
if (views[i].m_target) {
if (states[i] != views[i].m_target)
return l_false; }
else {
lbool nb = nullable(states[i]);
if (nb != l_true)
return nb; }
}
return l_true;
}
lbool seq_eq_approx::intersect_nonempty(segments const& lhs, segments const& rhs) {
m_budget = m_max_states;
if (lhs.empty() && rhs.empty())
return l_true;
expr* probe = nullptr;
for (auto const* side : { &lhs, &rhs }) {
for (auto const& seg : *side) {
if (seg.empty())
return l_undef;
for (auto const& v : seg) {
VERIFY(v.m_state);
if (!probe)
probe = v.m_state;
VERIFY(v.m_state->get_sort() == probe->get_sort());
}
}
}
sort* seq_sort = nullptr;
sort* elem_sort = nullptr;
if (!probe)
return l_undef;
VERIFY(u().is_re(probe, seq_sort));
VERIFY(u().is_seq(seq_sort, elem_sort));
expr_ref v0(m.mk_var(0, elem_sort), m);
struct node {
unsigned l_seg = 0, r_seg = 0;
ptr_vector<expr> l_st, r_st;
};
vector<node> work;
std::unordered_set<std::vector<unsigned>, key_hash> visited;
auto enter = [](segments const& side, unsigned seg, ptr_vector<expr>& out) {
out.reset();
if (seg < side.size())
for (auto const& v : side[seg])
out.push_back(v.m_state);
};
auto push = [&](node const& n) {
std::vector<unsigned> key;
key.push_back(n.l_seg);
key.push_back(n.r_seg);
for (expr* e : n.l_st)
key.push_back(e->get_id());
for (expr* e : n.r_st)
key.push_back(e->get_id());
if (visited.insert(key).second)
work.push_back(n);
};
node init;
enter(lhs, 0, init.l_st);
enter(rhs, 0, init.r_st);
push(init);
ptr_vector<expr> cur_l, cur_r;
svector<expr_ref_pair_vector const*> branches;
bool bail = false;
for (unsigned head = 0; head < work.size(); ++head) {
if (out_of_budget())
return l_undef;
++m_stats.m_states;
node const n = work[head]; bool const l_done = n.l_seg == lhs.size();
bool const r_done = n.r_seg == rhs.size();
if (l_done && r_done)
return l_true;
if (!l_done) {
lbool done = segment_done(lhs[n.l_seg], n.l_st);
if (done == l_undef)
return l_undef;
if (done == l_true) {
node next = n;
++next.l_seg;
enter(lhs, next.l_seg, next.l_st);
push(next);
}
}
if (!r_done) {
lbool done = segment_done(rhs[n.r_seg], n.r_st);
if (done == l_undef)
return l_undef;
if (done == l_true) {
node next = n;
++next.r_seg;
enter(rhs, next.r_seg, next.r_st);
push(next);
}
}
if (l_done || r_done)
continue;
branches.reset();
for (expr* s : n.l_st)
branches.push_back(&cofactors(s));
for (expr* s : n.r_st)
branches.push_back(&cofactors(s));
cur_l = n.l_st;
cur_r = n.r_st;
unsigned const num_l = n.l_st.size();
std::function<void(unsigned, guard_set const&)> rec =
[&](unsigned i, guard_set const& acc) {
if (bail)
return;
if (i == branches.size()) {
node next;
next.l_seg = n.l_seg;
next.r_seg = n.r_seg;
next.l_st = cur_l;
next.r_st = cur_r;
push(next);
return;
}
for (auto const& [g, t] : *branches[i]) {
if (re().is_empty(t))
continue;
guard_set joint = acc;
joint.conjoin(g);
lbool ne = joint.eval(nullptr);
if (ne == l_undef) {
bail = true;
return;
}
if (ne == l_false)
continue;
if (i < num_l)
cur_l[i] = t;
else
cur_r[i - num_l] = t;
rec(i + 1, joint);
if (bail)
return;
}
};
guard_set top(m, u(), elem_sort, v0, &m_rp_cache);
rec(0, top);
if (bail)
return l_undef;
}
return l_false;
}
lbool seq_eq_approx::check(expr* lhs, expr* rhs) {
++m_stats.m_checks;
m_last_result = l_undef;
m_used.reset();
m_rp_cache.maybe_reset(1u << 16);
segments l, r;
VERIFY(lhs->get_sort() == rhs->get_sort());
to_segments(lhs, l);
to_segments(rhs, r);
m_last_result = intersect_nonempty(l, r);
if (m_last_result == l_false)
++m_stats.m_refuted;
if (m_last_result == l_undef)
++m_stats.m_giveup;
return m_last_result;
}
lbool seq_eq_approx::check(expr* eq) {
expr* lhs = nullptr, *rhs = nullptr;
if (!m.is_eq(eq, lhs, rhs)) {
++m_stats.m_unsupported;
return l_undef;
}
return check(lhs, rhs);
}
void seq_eq_approx::collect_statistics(::statistics& st) const {
st.update("seq eq approx checks", m_stats.m_checks);
st.update("seq eq approx refuted", m_stats.m_refuted);
st.update("seq eq approx unsupported", m_stats.m_unsupported);
st.update("seq eq approx giveup", m_stats.m_giveup);
st.update("seq eq approx states", m_stats.m_states);
}
std::ostream& seq_eq_approx::display(std::ostream& out) const {
auto display_expr = [&](expr* e) {
if (e)
out << mk_pp(e, m);
else
out << "null";
};
out << "(seq-eq-approx\n"
<< " :last-result " << m_last_result << "\n"
<< " :max-states " << m_max_states << "\n"
<< " :views (";
for (auto const& [t, views] : m_views) {
out << "\n ";
display_expr(t);
for (auto const& v : views) {
out << "\n ";
display_expr(v.m_state);
if (v.is_reach()) {
out << " -> ";
display_expr(v.m_target);
}
}
}
if (!m_views.empty())
out << "\n ";
out << ")\n :used (";
for (expr* t : m_used) {
out << "\n ";
display_expr(t);
}
if (!m_used.empty())
out << "\n ";
out << ")\n :checks " << m_stats.m_checks
<< "\n :refuted " << m_stats.m_refuted
<< "\n :unsupported " << m_stats.m_unsupported
<< "\n :giveup " << m_stats.m_giveup
<< "\n :states " << m_stats.m_states
<< ")\n";
return out;
}