mirror of
https://github.com/Z3Prover/z3
synced 2026-08-14 17:55:36 +00:00
Potential improvements on split_set
This commit is contained in:
parent
9454d7d24d
commit
0423723054
4 changed files with 61 additions and 9 deletions
|
|
@ -706,6 +706,13 @@ namespace euf {
|
|||
expr* elem_expr = elem->get_expr();
|
||||
SASSERT(re_expr);
|
||||
SASSERT(elem_expr);
|
||||
|
||||
// Pure function of (re, elem) -- see m_deriv_cache.
|
||||
snode const* cached = nullptr;
|
||||
if (m_deriv_cache.find(re_expr, elem_expr, cached))
|
||||
return cached;
|
||||
expr* const key_re = re_expr;
|
||||
expr* const key_elem = elem_expr;
|
||||
// std::cout << "Derivative of " << seq::snode_label_html(re, m) << "\nwith respect to " << seq::snode_label_html(elem, m) << std::endl;
|
||||
// if (allowed_range)
|
||||
// std::cout << "using " << seq::snode_label_html(allowed_range, m) << std::endl;
|
||||
|
|
@ -758,9 +765,10 @@ namespace euf {
|
|||
// — notably intersections like (A∩B) vs (B∩A) or (a|∅)·R vs a·R — get
|
||||
// distinct ids, breaking partial-DFA Q-membership and view/guard lap
|
||||
// detection (the multi-cycle / intersection divergence).
|
||||
th_rewriter trw(m);
|
||||
trw(result);
|
||||
return mk(result);
|
||||
m_th_rewriter(result);
|
||||
snode const* res = mk(result);
|
||||
m_deriv_cache.insert(key_re, key_elem, res);
|
||||
return res;
|
||||
}
|
||||
|
||||
bool sgraph::are_unit_distinct(snode const* a, snode const* b) const {
|
||||
|
|
|
|||
|
|
@ -40,6 +40,7 @@ Author:
|
|||
#include "util/region.h"
|
||||
#include "util/statistics.h"
|
||||
#include "util/lbool.h"
|
||||
#include "util/obj_pair_hashtable.h"
|
||||
#include "ast/ast.h"
|
||||
#include "ast/seq_decl_plugin.h"
|
||||
#include "ast/rewriter/seq_rewriter.h"
|
||||
|
|
@ -85,6 +86,13 @@ namespace euf {
|
|||
|
||||
// maps expression id to snode
|
||||
ptr_vector<snode const> m_expr2snode;
|
||||
|
||||
// Memo for brzozowski_deriv, keyed on the (regex, element) snode
|
||||
// expressions. The derivative is a pure function of that pair, and both
|
||||
// keys as well as the resulting snode are stable for the lifetime of the
|
||||
// sgraph (snodes live in m_region, which is never freed, and their
|
||||
// expressions are pinned in m_pin)
|
||||
obj_pair_map<expr, expr, snode const*> m_deriv_cache;
|
||||
|
||||
// trail of alias entries (string constant → decomposed snode) for pop
|
||||
unsigned_vector m_alias_trail; // expression ids
|
||||
|
|
|
|||
|
|
@ -253,6 +253,12 @@ bool seq_split::complement(sort* seq_sort, split_set const& sp, split_set& resul
|
|||
acc = std::move(tmp);
|
||||
if (acc.empty()) // intersection empty => ~S is empty
|
||||
break;
|
||||
// Do not simplify(acc) here. It does bound |acc| (each round
|
||||
// intersects acc with a 2-element set, so the fold is 2^|sp| without it),
|
||||
// but merge_by collapses pairs by unioning their other component, so
|
||||
// re-merging every round nests unions ~rounds*|acc| deep and the
|
||||
// recursive is_subset / mk_regex_union_normalize then overflow the stack.
|
||||
// |acc| is bounded by the caller's cap instead; see BOOL_CLOSURE_CAP.
|
||||
if (acc.size() > threshold) {
|
||||
++m_stats.m_threshold_overruns;
|
||||
return false;
|
||||
|
|
@ -663,10 +669,21 @@ expr_ref seq_split::head_normalize(expr* t, split_mode mode, unsigned threshold,
|
|||
ok = false;
|
||||
return expr_ref(m);
|
||||
}
|
||||
// The operand split-sets are materialized eagerly, so they are capped
|
||||
// rather than by the (deliberately huge) cap that bounds
|
||||
// how many splits the lazy enumeration may emit
|
||||
const unsigned cap = std::min(threshold, BOOL_CLOSURE_CAP);
|
||||
split_set sa, sb, tmp;
|
||||
if (!materialize(a, mode, threshold, oracle, sa) ||
|
||||
!materialize(b, mode, threshold, oracle, sb) ||
|
||||
!intersect(sa, sb, tmp, threshold, oracle)) {
|
||||
if (!materialize(a, mode, cap, oracle, sa) ||
|
||||
!materialize(b, mode, cap, oracle, sb)) {
|
||||
ok = false;
|
||||
return expr_ref(m);
|
||||
}
|
||||
// simplify(sa)/simplify(sb) before the cross product would cut the
|
||||
// |sa|*|sb| pairs considerably, but merge_by builds unions of unbounded
|
||||
// depth and the recursive seq_subset::is_subset then overflows the stack.
|
||||
// Needs a depth-guarded is_subset first.
|
||||
if (!intersect(sa, sb, tmp, cap, oracle)) {
|
||||
ok = false;
|
||||
return expr_ref(m);
|
||||
}
|
||||
|
|
@ -680,9 +697,15 @@ expr_ref seq_split::head_normalize(expr* t, split_mode mode, unsigned threshold,
|
|||
// The body is materialized WITHOUT the oracle (its pairs are inverted, so
|
||||
// their N is unrelated to the output N); the oracle is re-applied in
|
||||
// complement().
|
||||
const unsigned cap = std::min(threshold, BOOL_CLOSURE_CAP);
|
||||
split_set sa, res;
|
||||
if (!materialize(a, mode, threshold, split_oracle{}, sa) ||
|
||||
!complement(m_seq_sort, sa, res, threshold, oracle)) {
|
||||
if (!materialize(a, mode, cap, split_oracle{}, sa)) {
|
||||
ok = false;
|
||||
return expr_ref(m);
|
||||
}
|
||||
// As in the inter case, simplify(sa) here would cut the number of fold
|
||||
// rounds but currently risks a stack overflow; see the note above.
|
||||
if (!complement(m_seq_sort, sa, res, cap, oracle)) {
|
||||
ok = false;
|
||||
return expr_ref(m);
|
||||
}
|
||||
|
|
@ -904,7 +927,7 @@ std::pair<expr_ref, expr_ref> seq_split::split_membership(expr* str, expr* regex
|
|||
tokens.push_back(expr_ref(cur, m));
|
||||
}
|
||||
|
||||
expr* ch;
|
||||
expr* ch = nullptr;
|
||||
unsigned i = 0;
|
||||
|
||||
while (i < tokens.size() && (seq().str.is_string(tokens.get(i)) || (seq().str.is_unit(tokens.get(i), ch) && seq().is_const_char(ch)))) {
|
||||
|
|
|
|||
|
|
@ -194,6 +194,19 @@ class seq_split {
|
|||
// left (resp. right) component and unions the other component.
|
||||
void merge_by(split_set& pairs, bool by_left) const;
|
||||
|
||||
// Cap on the split-sets materialized for the *Boolean-closure* cases
|
||||
// (intersection / complement), which cannot be produced lazily and are
|
||||
// therefore drained in full inside head_normalize. This is a different
|
||||
// quantity from the caller's `threshold`, which bounds how many splits the
|
||||
// lazy enumeration may EMIT and is deliberately huge (nseq passes 2^20, so
|
||||
// that the binary child-B chain may walk arbitrarily many splits). Reusing
|
||||
// that value here let the De Morgan fold -- whose `acc` is intersected with a
|
||||
// 2-element set per element, hence doubles -- run to 2^20 pairs before
|
||||
// aborting, which is several seconds of pure waste. Overrunning this cap is
|
||||
// a give-up, so the caller falls through to its other rules; that is sound
|
||||
// and strictly better than the hang.
|
||||
static const unsigned BOOL_CLOSURE_CAP = 256;
|
||||
|
||||
public:
|
||||
explicit seq_split(seq_rewriter& rw);
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue