3
0
Fork 0
mirror of https://github.com/Z3Prover/z3 synced 2026-08-14 17:55:36 +00:00
z3/src/ast/rewriter/seq_regex_live.cpp
Nikolaj Bjorner 63ad8f9447 Add lazy regex live-state traversal
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

Copilot-Session: 2bcccba7-ac7d-476e-8dfe-718a21d37554
2026-08-07 16:10:23 -07:00

279 lines
8.2 KiB
C++

/*++
Copyright (c) 2026 Microsoft Corporation
Module Name:
seq_regex_live.cpp
Abstract:
Shared lazy live-state traversal for regular-expression derivatives.
--*/
#include "ast/rewriter/seq_regex_live.h"
#include "ast/rewriter/seq_rewriter.h"
#include "util/obj_hashtable.h"
#include "util/uint_set.h"
namespace seq {
struct live_states::search {
svector<unsigned> m_to_explore;
unsigned m_explore_head = 0;
uint_set m_seen;
vector<svector<unsigned>> m_predecessors;
bool_vector m_live;
bool_vector m_closed;
svector<unsigned> m_live_frontier;
failure m_failure = failure::none;
bool m_complete = false;
};
struct live_states::imp {
seq_rewriter& m_rw;
ast_manager& m;
seq_util::rex& m_re;
transition_mode m_mode;
unsigned m_max_states;
obj_map<expr, unsigned> m_ids;
expr_ref_vector m_states;
vector<svector<unsigned>> m_successors;
svector<char> m_nullable;
bool_vector m_expanded;
obj_map<expr, search*> m_searches;
imp(seq_rewriter& rw, transition_mode mode, unsigned max_states) :
m_rw(rw),
m(rw.m()),
m_re(rw.u().re),
m_mode(mode),
m_max_states(max_states),
m_states(m) {
}
~imp() {
reset();
}
unsigned intern(expr* r) {
unsigned id = 0;
if (m_ids.find(r, id))
return id;
id = m_states.size();
m_ids.insert(r, id);
m_states.push_back(r);
m_successors.push_back(svector<unsigned>());
m_nullable.push_back(2);
m_expanded.push_back(false);
return id;
}
void resize(search& s) {
unsigned size = m_states.size();
if (s.m_predecessors.size() < size)
s.m_predecessors.resize(size);
if (s.m_live.size() < size)
s.m_live.resize(size, false);
if (s.m_closed.size() < size)
s.m_closed.resize(size, false);
}
char nullable(unsigned id) {
if (m_nullable[id] != 2)
return m_nullable[id];
expr* r = m_states.get(id);
lbool n = m_re.get_info(r).nullable;
if (n == l_undef) {
expr_ref f = m_rw.is_nullable(r);
n = m.is_true(f) ? l_true : m.is_false(f) ? l_false : l_undef;
}
m_nullable[id] = n == l_true ? 1 : n == l_false ? 0 : 3;
return m_nullable[id];
}
void mark_live(search& s, unsigned id) {
svector<unsigned> todo;
todo.push_back(id);
while (!todo.empty()) {
id = todo.back();
todo.pop_back();
if (!s.m_seen.contains(id) || s.m_live[id])
continue;
s.m_live[id] = true;
s.m_live_frontier.push_back(id);
for (unsigned predecessor : s.m_predecessors[id])
todo.push_back(predecessor);
}
}
bool add_state(search& s, unsigned id) {
if (s.m_seen.contains(id))
return true;
if (s.m_to_explore.size() >= m_max_states) {
s.m_failure = failure::state_cap;
return false;
}
resize(s);
s.m_seen.insert(id);
s.m_to_explore.push_back(id);
if (nullable(id) != 0)
mark_live(s, id);
return true;
}
bool expand_state(unsigned id, failure& f) {
if (m_expanded[id])
return true;
if (!m.inc()) {
f = failure::resource;
return false;
}
m_expanded[id] = true;
nullable(id);
auto const& cofactors = m_rw.get_derive().get_cached_cofactors(m_mode, m_states.get(id));
for (auto const& [guard, target] : cofactors) {
if (m_re.is_empty(target))
continue;
unsigned target_id = intern(target);
if (!m_successors[id].contains(target_id))
m_successors[id].push_back(target_id);
}
return true;
}
void close(search& s) {
for (unsigned id : s.m_to_explore)
if (!s.m_live[id])
s.m_closed[id] = true;
s.m_complete = true;
}
void expand(search& s) {
if (s.m_complete || s.m_failure != failure::none)
return;
if (s.m_explore_head == s.m_to_explore.size()) {
close(s);
return;
}
unsigned id = s.m_to_explore[s.m_explore_head++];
if (!expand_state(id, s.m_failure))
return;
resize(s);
for (unsigned target : m_successors[id]) {
if (!add_state(s, target))
return;
if (!s.m_predecessors[target].contains(id))
s.m_predecessors[target].push_back(id);
if (s.m_live[target])
mark_live(s, id);
}
}
bool ensure(search& s, unsigned index) {
while (index >= s.m_live_frontier.size() &&
!s.m_complete &&
s.m_failure == failure::none)
expand(s);
return index < s.m_live_frontier.size();
}
search* get_search(expr* root) {
search* s = nullptr;
if (m_searches.find(root, s))
return s;
s = alloc(search);
unsigned root_id = intern(root);
add_state(*s, root_id);
m_searches.insert(root, s);
return s;
}
void reset() {
for (auto const& [root, s] : m_searches)
dealloc(s);
m_searches.reset();
m_ids.reset();
m_states.reset();
m_successors.reset();
m_nullable.reset();
m_expanded.reset();
}
};
live_states::live_states(seq_rewriter& rw, transition_mode mode, unsigned max_states) :
m_imp(alloc(imp, rw, mode, max_states)) {
}
live_states::~live_states() {
dealloc(m_imp);
}
expr* live_states::iterator::operator*() const {
return m_owner->get_live(m_search, m_index);
}
live_states::iterator& live_states::iterator::operator++() {
++m_index;
return *this;
}
bool live_states::iterator::operator!=(iterator const& other) const {
if (other.m_end)
return m_owner->ensure(m_search, m_index);
return m_owner != other.m_owner ||
m_search != other.m_search ||
m_index != other.m_index ||
m_end != other.m_end;
}
live_states::iterator live_states::reachable::begin() const {
return iterator(m_owner, m_search, 0, false);
}
live_states::iterator live_states::reachable::end() const {
return iterator(m_owner, m_search, 0, true);
}
bool live_states::reachable::failed() const {
return m_owner->get_failure(m_search) != failure::none;
}
live_states::failure live_states::reachable::failure_reason() const {
return m_owner->get_failure(m_search);
}
bool live_states::reachable::is_dead() {
return !m_owner->ensure(m_search, 0) && !failed();
}
live_states::reachable live_states::reachable_live(expr* r) {
return reachable(this, m_imp->get_search(r));
}
bool live_states::contains(expr* r) const {
return m_imp->m_ids.contains(r);
}
unsigned live_states::state_id(expr* r) {
return m_imp->intern(r) + 1;
}
void live_states::reset() {
m_imp->reset();
}
bool live_states::ensure(search* s, unsigned index) {
return m_imp->ensure(*s, index);
}
expr* live_states::get_live(search* s, unsigned index) const {
return m_imp->m_states.get(s->m_live_frontier[index]);
}
live_states::failure live_states::get_failure(search* s) const {
return s->m_failure;
}
}