3
0
Fork 0
mirror of https://github.com/Z3Prover/z3 synced 2026-07-14 11:05:41 +00:00

Minor corrections

This commit is contained in:
CEisenhofer 2026-06-29 18:25:09 +02:00
parent 6f1eafaa5c
commit 93801ccc8e
4 changed files with 54 additions and 46 deletions

View file

@ -390,13 +390,15 @@ namespace euf {
zstring s; zstring s;
if (m_seq.str.is_string(e, s) && !s.empty()) { if (m_seq.str.is_string(e, s) && !s.empty()) {
snode const* result = mk_char(s[s.length() - 1]); snode const* result = mk_char(s[s.length() - 1]);
for (unsigned i = s.length() - 1; i-- > 0; ) for (unsigned i = s.length() - 1; i-- > 0; ) {
result = mk_concat(mk_char(s[i]), result); result = mk_concat(mk_char(s[i]), result);
}
// register the original string expression as an alias // register the original string expression as an alias
unsigned eid = e->get_id(); unsigned eid = e->get_id();
m_expr2snode.reserve(eid + 1, nullptr); m_expr2snode.reserve(eid + 1, nullptr);
m_expr2snode[eid] = result; m_expr2snode[eid] = result;
m_alias_trail.push_back(eid); m_alias_trail.push_back(eid);
m_pin.push_back(e);
mk_enode(e); mk_enode(e);
return result; return result;
} }

View file

@ -97,8 +97,7 @@ namespace euf {
// m_expr. snodes are allocated in m_region — which is never freed — // m_expr. snodes are allocated in m_region — which is never freed —
// but their m_expr field is owned by the egraph trail. Without this // but their m_expr field is owned by the egraph trail. Without this
// pin the egraph would release expressions on pop while clients still // pin the egraph would release expressions on pop while clients still
// hold the matching snode* (e.g. inside nielsen_node str_mems, edge // hold the matching snode*, turning every later
// substitutions, or the partial-DFA cache), turning every later
// get_expr() into a use-after-free. The pin grows monotonically; it // get_expr() into a use-after-free. The pin grows monotonically; it
// is dropped only when sgraph itself is destroyed. // is dropped only when sgraph itself is destroyed.
expr_ref_vector m_pin; expr_ref_vector m_pin;

View file

@ -342,6 +342,25 @@ namespace seq {
nielsen_node::nielsen_node(nielsen_graph& graph, const unsigned id): nielsen_node::nielsen_node(nielsen_graph& graph, const unsigned id):
m_id(id), m_graph(graph), m_is_progress(true) { } m_id(id), m_graph(graph), m_is_progress(true) { }
void nielsen_node::set_conflict(const backtrack_reason r, const dep_tracker confl) {
if (m_conflict_internal != nullptr && m_conflict_external_literal == sat::null_literal)
return;
// We prefer internal conflicts (we need it as a justification for general conflicts)
TRACE(seq, tout << "internal conflict " << (unsigned)r << "\n");
m_reason = r;
m_conflict_internal = confl;
m_conflict_external_literal = sat::null_literal;
}
void nielsen_node::set_external_conflict(const sat::literal lit, dep_tracker confl) {
if (m_reason != backtrack_reason::unevaluated)
return;
TRACE(seq, tout << "external conflict " << lit << "\n");
m_reason = backtrack_reason::external;
m_conflict_external_literal = ~lit;
m_conflict_internal = confl;
}
void nielsen_node::clone_from(nielsen_node const& parent) { void nielsen_node::clone_from(nielsen_node const& parent) {
m_str_eq.reset(); m_str_eq.reset();
m_str_deq.reset(); m_str_deq.reset();
@ -550,9 +569,15 @@ namespace seq {
if (str_mems()[i] != n->str_mems()[i]) if (str_mems()[i] != n->str_mems()[i])
return false; return false;
} }
for (unsigned i = 0; i < char_ranges().size(); i++) { // char_ranges is a u_map keyed by the symbolic-char snode id (NOT a vector),
// TODO: Check once more // so it must be compared by key lookup, not by position. Sizes are equal
if (char_ranges()[i] != n->char_ranges()[i]) // (checked above), so a one-directional key+value match is a full match.
// Compare only the char_set (the constraint); deps differ between siblings,
// exactly as canonize_and_compute_node_hash hashes ranges+uid and ignores dep.
for (auto const& [uid, cr] : char_ranges()) {
if (!n->char_ranges().contains(uid))
return false;
if (cr.first != n->char_ranges().find(uid).first)
return false; return false;
} }
return true; return true;
@ -2165,11 +2190,6 @@ namespace seq {
case backtrack_reason::regex_widening: case backtrack_reason::regex_widening:
case backtrack_reason::symbol_clash: case backtrack_reason::symbol_clash:
case backtrack_reason::character_range: case backtrack_reason::character_range:
// a sibling (subsumption / loop-cut) conflict depends only on the node's
// string signature: a cut equates two nodes by their string constraints,
// and a sibling closure is reached only when every leaf below is itself a
// string-only conflict or a cut. (Whether the closure is *cacheable* is a
// separate, stronger question handled via the lowlink in search_dfs.)
case backtrack_reason::sibling: case backtrack_reason::sibling:
return true; return true;
default: default:
@ -2263,7 +2283,7 @@ namespace seq {
// the conflict from this node's own constraint deps (a sound over-approx). // the conflict from this node's own constraint deps (a sound over-approx).
{ {
if (m_unsat_node_cache.contains(node)) { if (m_unsat_node_cache.contains(node)) {
node->set_conflict(backtrack_reason::sibling, node_all_deps(node)); node->set_conflict(backtrack_reason::sibling, nullptr /*we use the one of the sibling*/);
node->set_general_conflict(); node->set_general_conflict();
node->m_unsat_cacheable = true; node->m_unsat_cacheable = true;
++m_stats.m_num_simplify_conflict; ++m_stats.m_num_simplify_conflict;
@ -2487,10 +2507,11 @@ namespace seq {
return search_result::unknown; return search_result::unknown;
if (all_string_only) { if (all_string_only) {
// Subsumption rule: this node is a string-only conflict. Internal // Subsumption rule: this node is a string-only conflict. It is a
// (has children) closures carry no own deps — collect_conflict_deps // STOP point for collect_conflict_deps, so it must carry its own
// recurses through them to the genuine conflict / cut leaves below. // justification: node_all_deps over-approximates the input
node->set_conflict(backtrack_reason::sibling, nullptr); // constraints behind its unsatisfiable string signature.
node->set_conflict(backtrack_reason::sibling, node_all_deps(node));
++m_stats.m_num_sibling_closure; ++m_stats.m_num_sibling_closure;
if (self_contained) { if (self_contained) {
// No cut escapes this subtree: the unsat is a property of the // No cut escapes this subtree: the unsat is a property of the
@ -4834,17 +4855,14 @@ namespace seq {
while (!to_visit.empty()) { while (!to_visit.empty()) {
nielsen_node const* n = to_visit.back(); nielsen_node const* n = to_visit.back();
to_visit.pop_back(); to_visit.pop_back();
// Recurse through internal closures: children_failed nodes, and sibling // Recurse only through children_failed nodes. A sibling (subsumption)
// (subsumption) closures that have children. The latter carry no own // node — whether a loop-cut leaf, a transposition-cache hit, or an
// deps (m_conflict_internal == null) — their justification is the union // internal string-only closure — is a STOP point: it carries its own
// of the genuine conflict / cut leaves below them, gathered by recursing // node_all_deps justification in m_conflict_internal (a sound
// (sound: collecting from all leaves never under-approximates). A sibling // over-approximation of the input constraints that produced its
// LEAF (a loop cut, or a transposition-cache hit; no children) instead // unsatisfiable string signature), so we do NOT descend into its
// contributes its own node_all_deps recorded in m_conflict_internal. // subtree (which may contain cut leaves whose targets lie outside it).
const bool recurse = if (n->reason() == backtrack_reason::children_failed) {
n->reason() == backtrack_reason::children_failed ||
(n->reason() == backtrack_reason::sibling && !n->outgoing().empty());
if (recurse) {
for (unsigned i = n->outgoing().size(); i > 0; i--) { for (unsigned i = n->outgoing().size(); i > 0; i--) {
nielsen_edge const* e = n->outgoing()[i - 1]; nielsen_edge const* e = n->outgoing()[i - 1];
to_visit.push_back(e->tgt()); to_visit.push_back(e->tgt());
@ -4854,12 +4872,16 @@ namespace seq {
// not true anymore since we might have done a hot-restart where we previously created the child: // not true anymore since we might have done a hot-restart where we previously created the child:
//SASSERT(n->outgoing().empty()); //SASSERT(n->outgoing().empty());
SASSERT(n->is_currently_conflict()); SASSERT(n->is_currently_conflict());
if (n->m_conflict_external_literal != sat::null_literal) if (n->m_conflict_external_literal != sat::null_literal) {
std::cout << "Node " << n->id() << std::endl;
// We know from the outer solver that this literal is assigned true and contradicts node constraint // 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)); deps = m_dep_mgr.mk_join(deps, m_dep_mgr.mk_leaf(n->m_conflict_external_literal));
}
if (n->m_conflict_internal) if (n->m_conflict_internal) {
std::cout << "Node " << n->id() << std::endl;
deps = m_dep_mgr.mk_join(deps, n->m_conflict_internal); deps = m_dep_mgr.mk_join(deps, n->m_conflict_internal);
}
} }
return deps; return deps;
} }

View file

@ -687,24 +687,9 @@ namespace seq {
m_reason = backtrack_reason::children_failed; m_reason = backtrack_reason::children_failed;
} }
void set_conflict(const backtrack_reason r, const dep_tracker confl) { void set_conflict(backtrack_reason r, dep_tracker confl);
if (m_conflict_internal != nullptr && m_conflict_external_literal == sat::null_literal)
return;
// We prefer internal conflicts (we need it as a justification for general conflicts)
TRACE(seq, tout << "internal conflict " << (unsigned)r << "\n");
m_reason = r;
m_conflict_internal = confl;
m_conflict_external_literal = sat::null_literal;
}
void set_external_conflict(const sat::literal lit, dep_tracker confl) { void set_external_conflict(sat::literal lit, dep_tracker confl);
if (m_reason != backtrack_reason::unevaluated)
return;
TRACE(seq, tout << "external conflict " << lit << "\n");
m_reason = backtrack_reason::external;
m_conflict_external_literal = ~lit;
m_conflict_internal = confl;
}
bool is_progress() const { return m_is_progress; } bool is_progress() const { return m_is_progress; }