3
0
Fork 0
mirror of https://github.com/Z3Prover/z3 synced 2026-06-07 09:30:54 +00:00
Signed-off-by: Nikolaj Bjorner <nbjorner@microsoft.com>
This commit is contained in:
Nikolaj Bjorner 2026-04-14 08:35:48 -07:00
parent 0c4e4ad702
commit 725b13680e
6 changed files with 80 additions and 104 deletions

View file

@ -332,27 +332,26 @@ namespace euf {
} }
} }
snode* sgraph::mk_snode(expr* e, snode_kind k, unsigned num_args, snode* const* args) { snode *sgraph::mk_snode(expr *e, snode_kind k, unsigned num_args, snode *const *args) {
SASSERT(e);
unsigned id = m_nodes.size(); unsigned id = m_nodes.size();
snode* n = snode::mk(m_region, e, k, id, num_args, args); snode *n = snode::mk(m_region, e, k, id, num_args, args);
compute_metadata(n); compute_metadata(n);
compute_hash_matrix(n); compute_hash_matrix(n);
m_nodes.push_back(n); m_nodes.push_back(n);
SASSERT(!n->is_char_or_unit() || m_seq.str.is_unit(n->get_expr())); SASSERT(!n->is_char_or_unit() || m_seq.str.is_unit(n->get_expr()));
unsigned eid = e->get_id();
if (e) { m_expr2snode.reserve(eid + 1, nullptr);
unsigned eid = e->get_id(); m_expr2snode[eid] = n;
m_expr2snode.reserve(eid + 1, nullptr); // pin expression via egraph (the egraph has an expr trail)
m_expr2snode[eid] = n; mk_enode(e);
// pin expression via egraph (the egraph has an expr trail)
mk_enode(e);
}
++m_stats.m_num_nodes; ++m_stats.m_num_nodes;
return n; return n;
} }
snode* sgraph::mk(expr* e) { snode* sgraph::mk(expr* e) {
SASSERT(e); SASSERT(e);
expr_ref _e(e, m); // pin locally to not clash with character creation, never needed if we use mk_enode early.
snode* n = find(e); snode* n = find(e);
if (n) if (n)
return n; return n;

View file

@ -93,7 +93,7 @@ namespace euf {
// maps expression id to snode // maps expression id to snode
ptr_vector<snode> m_expr2snode; ptr_vector<snode> m_expr2snode;
// trail of alias entries (string constant → decomposed snode) for pop // trail of alias entries (string constant → decomposed snode) for pop
unsigned_vector m_alias_trail; // expression ids unsigned_vector m_alias_trail; // expression ids
unsigned_vector m_alias_trail_lim; // scope boundaries unsigned_vector m_alias_trail_lim; // scope boundaries

View file

@ -1275,8 +1275,7 @@ namespace seq {
: "UNKNOWN") : "UNKNOWN")
<< "\n";); << "\n";);
if (r == search_result::sat) { if (r == search_result::sat) {
IF_VERBOSE( IF_VERBOSE(1,
1,
verbose_stream() << "side constraints: \n"; verbose_stream() << "side constraints: \n";
for (auto const &c : cur_path.back()->side_constraints()) { for (auto const &c : cur_path.back()->side_constraints()) {
verbose_stream() << " side constraint: " << c.fml << "\n"; verbose_stream() << " side constraint: " << c.fml << "\n";
@ -1289,6 +1288,7 @@ namespace seq {
++m_stats.m_num_unsat; ++m_stats.m_num_unsat;
auto deps = collect_conflict_deps(); auto deps = collect_conflict_deps();
m_dep_mgr.linearize(deps, m_conflict_sources); m_dep_mgr.linearize(deps, m_conflict_sources);
TRACE(seq, display(tout, m_root));
return r; return r;
} }
// depth limit hit double the bound and retry // depth limit hit double the bound and retry
@ -1420,7 +1420,7 @@ namespace seq {
// for each variable with multiple regex constraints, verify // for each variable with multiple regex constraints, verify
// that the intersection of all its regexes is non-empty. // that the intersection of all its regexes is non-empty.
// Mirrors ZIPT NielsenNode.CheckRegex. // Mirrors ZIPT NielsenNode.CheckRegex.
dep_tracker dep; dep_tracker dep = nullptr;
if (!check_leaf_regex(*node, dep)) { if (!check_leaf_regex(*node, dep)) {
node->set_general_conflict(); node->set_general_conflict();
node->set_conflict(backtrack_reason::regex, dep); node->set_conflict(backtrack_reason::regex, dep);
@ -3655,18 +3655,14 @@ namespace seq {
SASSERT(n->m_conflict_external_literal == sat::null_literal); SASSERT(n->m_conflict_external_literal == sat::null_literal);
continue; continue;
} }
SASSERT(n->is_currently_conflict());
if (n->m_conflict_external_literal != sat::null_literal) {
// We know from the outer solver that this literal is assigned false
deps = m_dep_mgr.mk_join(deps, m_dep_mgr.mk_leaf(n->m_conflict_external_literal));
if (n->m_conflict_internal)
deps = m_dep_mgr.mk_join(deps, n->m_conflict_internal);
continue;
}
SASSERT(n->outgoing().empty()); SASSERT(n->outgoing().empty());
SASSERT(n->m_conflict_internal); SASSERT(n->is_currently_conflict());
deps = m_dep_mgr.mk_join(deps, n->m_conflict_internal); if (n->m_conflict_external_literal != sat::null_literal)
// We know from the outer solver that this literal is assigned true and contradicts node constraint
deps = m_dep_mgr.mk_join(deps, m_dep_mgr.mk_leaf(n->m_conflict_external_literal));
if (n->m_conflict_internal)
deps = m_dep_mgr.mk_join(deps, n->m_conflict_internal);
} }
return deps; return deps;
} }

View file

@ -118,15 +118,11 @@ namespace seq {
// Multiple stabilizers: build re.union chain. // Multiple stabilizers: build re.union chain.
// union(s1, union(s2, ... union(sN-1, sN)...)) // union(s1, union(s2, ... union(sN-1, sN)...))
seq_util& seq = m_sg.get_seq_util();
euf::snode* result = stabs[stabs.size() - 1]; euf::snode* result = stabs[stabs.size() - 1];
for (unsigned i = stabs.size() - 1; i-- > 0; ) { for (unsigned i = stabs.size() - 1; i-- > 0; ) {
expr* lhs = stabs[i]->get_expr(); expr* lhs = stabs[i]->get_expr();
expr* rhs = result->get_expr(); expr* rhs = result->get_expr();
if (!lhs || !rhs) result = m_sg.mk(seq.re.mk_union(lhs, rhs));
return nullptr;
expr_ref un(seq.re.mk_union(lhs, rhs), m_sg.get_manager());
result = m_sg.mk(un);
} }
return result; return result;
} }
@ -374,10 +370,7 @@ namespace seq {
if (re->is_loop() && re->is_nullable()) if (re->is_loop() && re->is_nullable())
return false; return false;
seq_util& seq = m_sg.get_seq_util();
expr* e = re->get_expr(); expr* e = re->get_expr();
if (!e)
return false;
expr* r1, * r2; expr* r1, * r2;
// union is empty iff both children are empty // union is empty iff both children are empty
@ -413,7 +406,6 @@ namespace seq {
void seq_regex::collect_char_boundaries(euf::snode* re, unsigned_vector& bounds) const { void seq_regex::collect_char_boundaries(euf::snode* re, unsigned_vector& bounds) const {
SASSERT(re && re->get_expr()); SASSERT(re && re->get_expr());
seq_util& seq = m_sg.get_seq_util();
expr* e = re->get_expr(); expr* e = re->get_expr();
// Range predicate re.range(lo, hi): boundary at lo and hi+1 // Range predicate re.range(lo, hi): boundary at lo and hi+1
@ -463,7 +455,6 @@ namespace seq {
if (!re || !re->get_expr()) if (!re || !re->get_expr())
return false; return false;
seq_util& seq = m_sg.get_seq_util();
unsigned max_c = seq.max_char(); unsigned max_c = seq.max_char();
// Partition the alphabet using boundary points induced by regex // Partition the alphabet using boundary points induced by regex
@ -536,7 +527,7 @@ namespace seq {
unsigned states_explored = 0; unsigned states_explored = 0;
while (!worklist.empty()) { while (!worklist.empty()) {
if (!m_sg.get_manager().inc()) if (!m.inc())
return l_undef; return l_undef;
if (states_explored >= max_states) if (states_explored >= max_states)
return l_undef; return l_undef;
@ -558,7 +549,7 @@ namespace seq {
continue; continue;
for (euf::snode* ch : reps) { for (euf::snode* ch : reps) {
if (!m_sg.get_manager().inc()) if (!m.inc())
return l_undef; return l_undef;
// std::cout << "Deriving by " << snode_label_html(ch, sg().get_manager()) << std::endl; // std::cout << "Deriving by " << snode_label_html(ch, sg().get_manager()) << std::endl;
euf::snode* deriv = m_sg.brzozowski_deriv(current, ch); euf::snode* deriv = m_sg.brzozowski_deriv(current, ch);
@ -594,9 +585,6 @@ namespace seq {
if (regexes.size() == 1) if (regexes.size() == 1)
return is_empty_bfs(regexes[0], max_states); return is_empty_bfs(regexes[0], max_states);
seq_util& seq = m_sg.get_seq_util();
ast_manager& mgr = m_sg.get_manager();
euf::snode* result = regexes[0]; euf::snode* result = regexes[0];
for (unsigned i = 1; i < regexes.size(); ++i) { for (unsigned i = 1; i < regexes.size(); ++i) {
expr* r1 = result->get_expr(); expr* r1 = result->get_expr();
@ -628,26 +616,14 @@ namespace seq {
return l_true; // L ⊆ L return l_true; // L ⊆ L
// Build complement(superset) // Build complement(superset)
seq_util& seq = m_sg.get_seq_util();
ast_manager& mgr = m_sg.get_manager();
expr* sup_expr = superset_re->get_expr(); expr* sup_expr = superset_re->get_expr();
if (!sup_expr) euf::snode *comp_sn = m_sg.mk(seq.re.mk_complement(sup_expr));
return l_undef;
expr_ref comp(seq.re.mk_complement(sup_expr), mgr);
euf::snode* comp_sn = m_sg.mk(comp);
if (!comp_sn)
return l_undef;
// Build intersection and check emptiness // Build intersection and check emptiness
// subset ∩ complement(superset) should be empty for subset relation // subset ∩ complement(superset) should be empty for subset relation
expr* sub_expr = subset_re->get_expr(); expr* sub_expr = subset_re->get_expr();
if (!sub_expr) auto inter = seq.re.mk_inter(sub_expr, comp_sn->get_expr());
return l_undef;
expr_ref inter(seq.re.mk_inter(sub_expr, comp.get()), mgr);
euf::snode* inter_sn = m_sg.mk(inter); euf::snode* inter_sn = m_sg.mk(inter);
if (!inter_sn)
return l_undef;
return is_empty_bfs(inter_sn); return is_empty_bfs(inter_sn);
} }
@ -659,8 +635,6 @@ namespace seq {
euf::snode* var, nielsen_node const& node, dep_manager& dep_mgr, dep_tracker& dep) const { euf::snode* var, nielsen_node const& node, dep_manager& dep_mgr, dep_tracker& dep) const {
SASSERT(var); SASSERT(var);
seq_util& seq = m_sg.get_seq_util();
ast_manager& m = m_sg.get_manager();
euf::snode* result = nullptr; euf::snode* result = nullptr;
for (auto const& mem : node.str_mems()) { for (auto const& mem : node.str_mems()) {
@ -672,7 +646,7 @@ namespace seq {
SASSERT(first); SASSERT(first);
if (first != var) if (first != var)
continue; continue;
TRACE(seq, tout << mk_pp(first->get_expr(), m) << " " << mem_pp(m, mem) << "\n"); TRACE(seq, tout << spp(first, m) << " " << mem_pp(m, mem) << "\n");
if (!result) { if (!result) {
result = mem.m_regex; result = mem.m_regex;
@ -682,7 +656,7 @@ namespace seq {
expr* r1 = result->get_expr(); expr* r1 = result->get_expr();
expr* r2 = mem.m_regex->get_expr(); expr* r2 = mem.m_regex->get_expr();
if (r1 && r2) { if (r1 && r2) {
expr_ref inter(seq.re.mk_inter(r1, r2), m); auto inter = seq.re.mk_inter(r1, r2);
result = m_sg.mk(inter); result = m_sg.mk(inter);
dep = dep_mgr.mk_join(dep, mem.m_dep); dep = dep_mgr.mk_join(dep, mem.m_dep);
} }
@ -742,8 +716,6 @@ namespace seq {
// For now, only handle the case where the entire string is ground: // For now, only handle the case where the entire string is ground:
// consume all characters from the front (which covers trailing chars // consume all characters from the front (which covers trailing chars
// when the string is fully ground). // when the string is fully ground).
if (!mem.m_str || !mem.m_regex)
return simplify_status::ok;
if (!mem.m_str->is_ground()) if (!mem.m_str->is_ground())
return simplify_status::ok; return simplify_status::ok;
@ -756,8 +728,7 @@ namespace seq {
// ----------------------------------------------------------------------- // -----------------------------------------------------------------------
int seq_regex::check_trivial(seq::str_mem const& mem) const { int seq_regex::check_trivial(seq::str_mem const& mem) const {
if (!mem.m_str || !mem.m_regex) SASSERT(mem.m_str && mem.m_regex);
return 0;
// regex is ∅ => always conflict // regex is ∅ => always conflict
if (is_empty_regex(mem.m_regex)) if (is_empty_regex(mem.m_regex))
return -1; return -1;
@ -802,8 +773,7 @@ namespace seq {
bool seq_regex::process_str_mem(seq::str_mem const& mem, bool seq_regex::process_str_mem(seq::str_mem const& mem,
vector<seq::str_mem>& out_mems) { vector<seq::str_mem>& out_mems) {
if (!mem.m_str || !mem.m_regex) SASSERT(mem.m_str && mem.m_regex);
return true;
// empty string: check nullable // empty string: check nullable
if (mem.m_str->is_empty()) if (mem.m_str->is_empty())
return mem.m_regex->is_nullable(); return mem.m_regex->is_nullable();
@ -840,6 +810,7 @@ namespace seq {
// ----------------------------------------------------------------------- // -----------------------------------------------------------------------
seq::str_mem seq_regex::record_history(seq::str_mem const& mem, euf::snode* history_re) { seq::str_mem seq_regex::record_history(seq::str_mem const& mem, euf::snode* history_re) {
return str_mem(mem.m_str, mem.m_regex, mem.m_dep); return str_mem(mem.m_str, mem.m_regex, mem.m_dep);
} }
@ -848,6 +819,38 @@ namespace seq {
// ----------------------------------------------------------------------- // -----------------------------------------------------------------------
euf::snode* seq_regex::extract_cycle(seq::str_mem const& mem) const { euf::snode* seq_regex::extract_cycle(seq::str_mem const& mem) const {
#if 0
// Walk the history chain looking for a repeated regex.
// A cycle exists when the current regex matches a regex in the history.
if (!mem.m_regex || !mem.m_history)
return nullptr;
euf::snode* current = mem.m_regex;
euf::snode* hist = mem.m_history;
// Walk the history chain up to a bounded depth.
// The history is structured as a chain of regex snapshots connected
// via the sgraph's regex-concat: each level's arg(0) is a snapshot
// and arg(1) is the tail. A leaf (non-concat) is a terminal entry.
unsigned bound = 1000;
while (hist && bound-- > 0) {
euf::snode* entry = hist;
euf::snode* tail = nullptr;
// If the history node is a regex concat, decompose it:
// arg(0) is the regex snapshot, arg(1) is the rest of the chain
if (hist->is_concat() && seq.re.is_concat(hist->get_expr())) {
entry = hist->arg(0);
tail = hist->arg(1);
}
// Check pointer equality (fast, covers normalized regexes)
if (entry == current)
return entry;
hist = tail;
}
#endif
return nullptr; return nullptr;
} }
@ -861,11 +864,7 @@ namespace seq {
return nullptr; return nullptr;
expr* re_expr = cycle_regex->get_expr(); expr* re_expr = cycle_regex->get_expr();
if (!re_expr) auto star_expr = seq.re.mk_star(re_expr);
return nullptr;
seq_util& seq = m_sg.get_seq_util();
expr_ref star_expr(seq.re.mk_star(re_expr), m_sg.get_manager());
return m_sg.mk(star_expr); return m_sg.mk(star_expr);
} }
@ -894,9 +893,6 @@ namespace seq {
ptr_vector<euf::snode> const* stabs = get_stabilizers(re); ptr_vector<euf::snode> const* stabs = get_stabilizers(re);
if (!stabs || stabs->empty()) if (!stabs || stabs->empty())
return nullptr; return nullptr;
seq_util& seq = m_sg.get_seq_util();
ast_manager& m = m_sg.get_manager();
euf::snode* filtered_union = nullptr; euf::snode* filtered_union = nullptr;
for (euf::snode* s : *stabs) { for (euf::snode* s : *stabs) {
@ -907,11 +903,12 @@ namespace seq {
if (d && d->is_fail()) { if (d && d->is_fail()) {
if (!filtered_union) { if (!filtered_union) {
filtered_union = s; filtered_union = s;
} else { }
else {
expr* e1 = filtered_union->get_expr(); expr* e1 = filtered_union->get_expr();
expr* e2 = s->get_expr(); expr* e2 = s->get_expr();
if (e1 && e2) { if (e1 && e2) {
expr_ref u(seq.re.mk_union(e1, e2), m); auto u = seq.re.mk_union(e1, e2);
filtered_union = m_sg.mk(u); filtered_union = m_sg.mk(u);
} }
} }
@ -924,8 +921,7 @@ namespace seq {
expr* fe = filtered_union->get_expr(); expr* fe = filtered_union->get_expr();
if (!fe) if (!fe)
return nullptr; return nullptr;
expr_ref star_expr(seq.re.mk_star(fe), m); return m_sg.mk(seq.re.mk_star(fe));
return m_sg.mk(star_expr);
} }
// ----------------------------------------------------------------------- // -----------------------------------------------------------------------
@ -945,9 +941,6 @@ namespace seq {
if (tokens.empty()) if (tokens.empty())
return nullptr; return nullptr;
seq_util& seq = m_sg.get_seq_util();
ast_manager& m = m_sg.get_manager();
// Replay tokens on the cycle regex, detecting sub-cycles. // Replay tokens on the cycle regex, detecting sub-cycles.
// A sub-cycle is detected when the derivative returns to cycle_regex. // A sub-cycle is detected when the derivative returns to cycle_regex.
svector<std::pair<unsigned, unsigned>> sub_cycles; svector<std::pair<unsigned, unsigned>> sub_cycles;
@ -1006,14 +999,11 @@ namespace seq {
if (filtered) { if (filtered) {
expr* fe = filtered->get_expr(); expr* fe = filtered->get_expr();
if (fe) { if (fe) {
if (!body) { if (!body)
body = filtered; body = filtered;
} else { else {
expr* be = body->get_expr(); expr* be = body->get_expr();
if (be) { body = m_sg.mk(seq.re.mk_concat(be, fe));
expr_ref cat(seq.re.mk_concat(be, fe), m);
body = m_sg.mk(cat);
}
} }
} }
} }
@ -1021,8 +1011,6 @@ namespace seq {
// Convert char token to regex: to_re(unit(tok)) // Convert char token to regex: to_re(unit(tok))
expr* tok_expr = tok->get_expr(); expr* tok_expr = tok->get_expr();
if (!tok_expr)
break;
expr_ref unit_str(seq.str.mk_unit(tok_expr), m); expr_ref unit_str(seq.str.mk_unit(tok_expr), m);
expr_ref tok_re(seq.re.mk_to_re(unit_str), m); expr_ref tok_re(seq.re.mk_to_re(unit_str), m);
@ -1087,16 +1075,10 @@ namespace seq {
if (!stab_union) if (!stab_union)
return false; return false;
seq_util& seq = m_sg.get_seq_util();
ast_manager& mgr = m_sg.get_manager();
expr* su_expr = stab_union->get_expr(); expr* su_expr = stab_union->get_expr();
if (!su_expr) expr_ref stab_star(seq.re.mk_star(su_expr), m);
return false;
expr_ref stab_star(seq.re.mk_star(su_expr), mgr);
euf::snode* stab_star_sn = m_sg.mk(stab_star); euf::snode* stab_star_sn = m_sg.mk(stab_star);
if (!stab_star_sn)
return false;
// 4. Collect all primitive regex constraints on variable `first` // 4. Collect all primitive regex constraints on variable `first`
euf::snode* x_range = collect_primitive_regex_intersection(first, node, dep); euf::snode* x_range = collect_primitive_regex_intersection(first, node, dep);
if (!x_range) if (!x_range)
@ -1111,7 +1093,6 @@ namespace seq {
} }
char_set seq_regex::minterm_to_char_set(expr* re_expr) { char_set seq_regex::minterm_to_char_set(expr* re_expr) {
seq_util& seq = m_sg.get_seq_util();
unsigned max_c = seq.max_char(); unsigned max_c = seq.max_char();
if (!re_expr) if (!re_expr)
@ -1189,6 +1170,4 @@ namespace seq {
return char_set::full(max_c); return char_set::full(max_c);
} }
} }

View file

@ -38,6 +38,8 @@ Author:
namespace seq { namespace seq {
class seq_regex { class seq_regex {
ast_manager &m;
seq_util &seq;
euf::sgraph& m_sg; euf::sgraph& m_sg;
// ----------------------------------------------------------------- // -----------------------------------------------------------------
@ -91,7 +93,7 @@ namespace seq {
// ----------------------------------------------------------------------- // -----------------------------------------------------------------------
char_set minterm_to_char_set(expr* minterm_re); char_set minterm_to_char_set(expr* minterm_re);
seq_regex(euf::sgraph& sg) : m_sg(sg) {} seq_regex(euf::sgraph& sg) : m(sg.get_manager()), seq(sg.get_seq_util()), m_sg(sg) {}
euf::sgraph& sg() { return m_sg; } euf::sgraph& sg() { return m_sg; }

View file

@ -85,7 +85,6 @@ namespace smt {
std::function<sat::literal(expr*)> literal_if_false = [&](expr* e) { std::function<sat::literal(expr*)> literal_if_false = [&](expr* e) {
bool is_not = m.is_not(e, e); bool is_not = m.is_not(e, e);
TRACE(seq, tout << "literal_if_false: " << mk_pp(e, m) << " internalized - " << ctx.b_internalized(e) << "\n");
if (!ctx.b_internalized(e)) if (!ctx.b_internalized(e))
// it can happen that the element is not internalized, but as soon as we do it, it becomes false. // it can happen that the element is not internalized, but as soon as we do it, it becomes false.
// In case we just skip one of those uninternalized expressions, // In case we just skip one of those uninternalized expressions,
@ -93,14 +92,15 @@ namespace smt {
// Alternatively, we could just retry Nielsen saturation in case // Alternatively, we could just retry Nielsen saturation in case
// adding the Nielsen assumption yields the assumption being false after internalizing // adding the Nielsen assumption yields the assumption being false after internalizing
ctx.internalize(to_app(e), false); ctx.internalize(to_app(e), false);
literal lit = ctx.get_literal(e); literal lit = ctx.get_literal(e);
if (is_not) if (is_not)
lit.neg(); lit.neg();
if (ctx.get_assignment(lit) == l_false) { if (ctx.get_assignment(lit) == l_false) {
TRACE(seq, tout << "literal_if_false: " << lit << " " << mk_pp(e, m) << " is assigned false\n"); // TRACE(seq, tout << "literal_if_false: " << lit << " " << mk_pp(e, m) << " is assigned false\n");
return lit; return lit;
} }
TRACE(seq, tout << "literal_if_false: " << mk_pp(e, m) << " is assigned " << ctx.get_assignment(lit) << "\n"); // TRACE(seq, tout << "literal_if_false: " << mk_pp(e, m) << " is assigned " << ctx.get_assignment(lit) << "\n");
return sat::null_literal; return sat::null_literal;
}; };