mirror of
https://github.com/Z3Prover/z3
synced 2026-07-23 07:22:33 +00:00
Issue 438 (#10085)
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
This commit is contained in:
parent
ba7b12c18c
commit
eaceded5f1
13 changed files with 269 additions and 11 deletions
|
|
@ -997,3 +997,30 @@ app* bv_util::mk_bv_rotate_right(expr* arg, unsigned n) {
|
|||
parameter p(n);
|
||||
return m_manager.mk_app(get_fid(), OP_ROTATE_RIGHT, 1, &p, 1, &arg);
|
||||
}
|
||||
|
||||
void bv_util::mk_bv_divrem_bound(expr* t, expr_ref_vector& clause) {
|
||||
clause.reset();
|
||||
if (!is_app(t) || !is_bv_divrem(t))
|
||||
return;
|
||||
expr* a = to_app(t)->get_arg(0);
|
||||
expr* b = to_app(t)->get_arg(1);
|
||||
if (is_numeral(b))
|
||||
return;
|
||||
expr* bound = nullptr;
|
||||
// Use ¬(b ≤u t) instead of (t <u b) and ¬(abs(b) ≤u abs(t)) instead of (abs(t) <u abs(b))
|
||||
// so that all bound atoms are OP_ULEQ (handled by theory_bv::internalize_atom).
|
||||
// OP_ULT is not handled by theory_bv::internalize_atom and would trigger UNREACHABLE.
|
||||
if (is_bv_urem(t) || is_bv_uremi(t))
|
||||
bound = m_manager.mk_not(mk_ule(b, t));
|
||||
else if (is_bv_srem(t) || is_bv_sremi(t) || is_bv_smod(t) || is_bv_smodi(t))
|
||||
bound = m_manager.mk_not(mk_ule(mk_abs(b), mk_abs(t)));
|
||||
else if (is_bv_udiv(t) || is_bv_udivi(t))
|
||||
bound = mk_ule(t, a);
|
||||
else if (is_bv_sdiv(t) || is_bv_sdivi(t))
|
||||
bound = mk_ule(mk_abs(t), mk_abs(a));
|
||||
if (!bound)
|
||||
return;
|
||||
// clause encodes b != 0 => bound as the disjunction (b = 0) \/ bound
|
||||
clause.push_back(m_manager.mk_eq(b, mk_zero(b->get_sort())));
|
||||
clause.push_back(bound);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -473,6 +473,7 @@ public:
|
|||
|
||||
|
||||
app * mk_ule(expr * arg1, expr * arg2) { return m_manager.mk_app(get_fid(), OP_ULEQ, arg1, arg2); }
|
||||
app * mk_ult(expr * arg1, expr * arg2) { return m_manager.mk_app(get_fid(), OP_ULT, arg1, arg2); }
|
||||
app * mk_sle(expr * arg1, expr * arg2) { return m_manager.mk_app(get_fid(), OP_SLEQ, arg1, arg2); }
|
||||
app * mk_slt(expr * arg1, expr * arg2) { return m_manager.mk_app(get_fid(), OP_SLT, arg1, arg2); }
|
||||
app * mk_extract(unsigned high, unsigned low, expr * n) {
|
||||
|
|
@ -499,6 +500,25 @@ public:
|
|||
|
||||
app * mk_bv_not(expr * arg) { return m_manager.mk_app(get_fid(), OP_BNOT, arg); }
|
||||
app * mk_bv_neg(expr * arg) { return m_manager.mk_app(get_fid(), OP_BNEG, arg); }
|
||||
// absolute value: ite(arg <s 0, -arg, arg). Note mk_abs(INT_MIN) = INT_MIN.
|
||||
// Uses ¬(0 ≤s arg) instead of (arg <s 0) so that the ITE condition is OP_NOT(OP_SLEQ)
|
||||
// (handled by theory_bv::internalize_atom) rather than OP_SLT (not handled).
|
||||
app * mk_abs(expr * arg) { return m_manager.mk_ite(mk_sle(mk_zero(arg->get_sort()), arg), arg, mk_bv_neg(arg)); }
|
||||
// Magnitude-bound clause for a division/remainder term t with a symbolic (non-numeral)
|
||||
// divisor. Fills clause with the disjuncts { divisor = 0, bound }, encoding
|
||||
// divisor != 0 => bound, where bound is expressed using only OP_ULEQ and OP_NOT(OP_ULEQ)
|
||||
// (so it can be internalized by theory_bv::internalize_atom):
|
||||
// urem: ¬(b ≤u t) srem/smod: ¬(|b| ≤u |t|)
|
||||
// udiv: t ≤u a sdiv: |t| ≤u |a|
|
||||
// Leaves clause empty if t is not a division/remainder operator or the divisor is a
|
||||
// numeral. The bound is a bit-vector theory axiom (implied for a non-zero divisor); the
|
||||
// unsigned comparison on absolute values keeps it sound at INT_MIN.
|
||||
bool is_bv_divrem(expr* t) const {
|
||||
return is_bv_urem(t) || is_bv_uremi(t) || is_bv_srem(t) || is_bv_sremi(t) ||
|
||||
is_bv_smod(t) || is_bv_smodi(t) || is_bv_udiv(t) || is_bv_udivi(t) ||
|
||||
is_bv_sdiv(t) || is_bv_sdivi(t);
|
||||
}
|
||||
void mk_bv_divrem_bound(expr* t, expr_ref_vector& clause);
|
||||
app * mk_bv_urem(expr * arg1, expr * arg2) const { return m_manager.mk_app(get_fid(), OP_BUREM, arg1, arg2); }
|
||||
app * mk_bv_srem(expr * arg1, expr * arg2) const { return m_manager.mk_app(get_fid(), OP_BSREM, arg1, arg2); }
|
||||
app * mk_bv_smod(expr * arg1, expr * arg2) const { return m_manager.mk_app(get_fid(), OP_BSMOD, arg1, arg2); }
|
||||
|
|
|
|||
|
|
@ -6,6 +6,7 @@ z3_add_component(simplifiers
|
|||
bound_propagator.cpp
|
||||
bound_simplifier.cpp
|
||||
bv_bounds_simplifier.cpp
|
||||
bv_divrem_bounds.cpp
|
||||
bv_slice.cpp
|
||||
card2bv.cpp
|
||||
demodulator_simplifier.cpp
|
||||
|
|
|
|||
43
src/ast/simplifiers/bv_divrem_bounds.cpp
Normal file
43
src/ast/simplifiers/bv_divrem_bounds.cpp
Normal file
|
|
@ -0,0 +1,43 @@
|
|||
/*++
|
||||
Copyright (c) 2026 Microsoft Corporation
|
||||
|
||||
Module Name:
|
||||
|
||||
bv_divrem_bounds.cpp
|
||||
|
||||
Abstract:
|
||||
|
||||
Simplifier that adds range/bound lemmas for bit-vector division and
|
||||
remainder terms with a non-constant divisor. See bv_divrem_bounds.h.
|
||||
|
||||
Author:
|
||||
|
||||
Nikolaj Bjorner (nbjorner)
|
||||
|
||||
--*/
|
||||
#include "ast/simplifiers/bv_divrem_bounds.h"
|
||||
#include "ast/for_each_expr.h"
|
||||
|
||||
namespace bv {
|
||||
|
||||
void divrem_bounds::reduce() {
|
||||
expr_ref_vector targets(m), fmls(m), clause(m);
|
||||
for (unsigned i : indices())
|
||||
fmls.push_back(m_fmls[i].fml());
|
||||
for (expr* e : subterms::ground(fmls))
|
||||
if (m_util.is_bv_divrem(e))
|
||||
targets.push_back(e);
|
||||
for (expr* t : targets) {
|
||||
m_util.mk_bv_divrem_bound(t, clause);
|
||||
if (clause.empty())
|
||||
continue;
|
||||
expr_ref lemma(m.mk_or(clause), m);
|
||||
// the lemma is a bit-vector theory axiom (valid with no premises)
|
||||
proof_ref pr(m);
|
||||
if (m.proofs_enabled())
|
||||
pr = m.mk_th_lemma(m_util.get_fid(), lemma, 0, nullptr);
|
||||
m_fmls.add(dependent_expr(m, lemma, pr, nullptr));
|
||||
++m_num_lemmas;
|
||||
}
|
||||
}
|
||||
}
|
||||
64
src/ast/simplifiers/bv_divrem_bounds.h
Normal file
64
src/ast/simplifiers/bv_divrem_bounds.h
Normal file
|
|
@ -0,0 +1,64 @@
|
|||
/*++
|
||||
Copyright (c) 2026 Microsoft Corporation
|
||||
|
||||
Module Name:
|
||||
|
||||
bv_divrem_bounds.h
|
||||
|
||||
Abstract:
|
||||
|
||||
Simplifier that adds range/bound lemmas for bit-vector division and
|
||||
remainder terms with a non-constant divisor.
|
||||
|
||||
Bit-blasting a division circuit with a symbolic divisor hides the algebraic
|
||||
fact that the remainder magnitude is bounded by the divisor magnitude. For a
|
||||
divisor b that is not a numeral the following facts hold and are added as
|
||||
(implied) lemmas so that a downstream bit-blasting + SAT solver can reason
|
||||
about the magnitudes without unfolding the division circuit:
|
||||
|
||||
b != 0 => bvult (bvurem a b) b (unsigned remainder)
|
||||
b != 0 => bvult |bvsrem a b| |b| (signed remainder)
|
||||
b != 0 => bvult |bvsmod a b| |b| (signed modulo)
|
||||
b != 0 => bvule (bvudiv a b) a (unsigned quotient)
|
||||
b != 0 => bvule |bvsdiv a b| |a| (signed quotient)
|
||||
|
||||
where |x| = ite(x <s 0, -x, x). These lemmas are logically implied by the
|
||||
semantics of the operators for a non-zero divisor, so the transformation
|
||||
preserves satisfiability and all models. The unsigned comparison on the
|
||||
absolute values keeps the bounds sound at INT_MIN: -INT_MIN overflows to
|
||||
INT_MIN whose unsigned value is the maximum magnitude, so the bound only
|
||||
ever loosens, never becomes unsound.
|
||||
|
||||
Author:
|
||||
|
||||
Nikolaj Bjorner (nbjorner)
|
||||
|
||||
--*/
|
||||
#pragma once
|
||||
|
||||
#include "ast/bv_decl_plugin.h"
|
||||
#include "ast/simplifiers/dependent_expr_state.h"
|
||||
|
||||
namespace bv {
|
||||
|
||||
class divrem_bounds : public dependent_expr_simplifier {
|
||||
bv_util m_util;
|
||||
unsigned m_num_lemmas = 0;
|
||||
|
||||
public:
|
||||
divrem_bounds(ast_manager& m, dependent_expr_state& fmls) :
|
||||
dependent_expr_simplifier(m, fmls), m_util(m) {}
|
||||
|
||||
char const* name() const override { return "bv-divrem-bounds"; }
|
||||
|
||||
void reduce() override;
|
||||
|
||||
bool supports_proofs() const override { return true; }
|
||||
|
||||
void collect_statistics(statistics& st) const override {
|
||||
st.update("bv-divrem-bounds", m_num_lemmas);
|
||||
}
|
||||
|
||||
void reset_statistics() override { m_num_lemmas = 0; }
|
||||
};
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue