/*++ Copyright (c) 2020 Microsoft Corporation Module Name: bv_solver.h Abstract: Theory plugin for bit-vectors Author: Nikolaj Bjorner (nbjorner) 2020-08-30 --*/ #pragma once #include "sat/smt/sat_th.h" #include "sat/smt/bv_ackerman.h" #include "ast/rewriter/bit_blaster/bit_blaster.h" namespace euf { class solver; } namespace bv { struct lazy_mul { expr_ref_vector m_out; unsigned m_bits; lazy_mul(app* a, expr_ref_vector& out): m_out(out), m_bits(0) { } }; class solver : public euf::th_euf_solver { typedef rational numeral; typedef euf::theory_var theory_var; typedef euf::theory_id theory_id; typedef sat::literal literal; typedef sat::bool_var bool_var; typedef sat::literal_vector literal_vector; typedef svector vars; typedef std::pair value_sort_pair; typedef pair_hash, unsigned_hash> value_sort_pair_hash; typedef map > value2var; typedef union_find bv_union_find; typedef std::pair var_pos; friend class ackerman; struct stats { unsigned m_num_diseq_static, m_num_diseq_dynamic, m_num_conflicts; unsigned m_num_bit2eq, m_num_bit2ne, m_num_eq2bit, m_num_ne2bit; unsigned m_ackerman; void reset() { memset(this, 0, sizeof(stats)); } stats() { reset(); } }; struct bv_justification { enum kind_t { eq2bit, ne2bit, bit2eq, bit2ne, bv2int }; kind_t m_kind; unsigned m_idx = UINT_MAX; theory_var m_v1 = euf::null_theory_var; theory_var m_v2 = euf::null_theory_var; sat::literal m_consequent; sat::literal m_antecedent; euf::enode* a, *b, *c; bv_justification(theory_var v1, theory_var v2, sat::literal c, sat::literal a) : m_kind(bv_justification::kind_t::eq2bit), m_v1(v1), m_v2(v2), m_consequent(c), m_antecedent(a) {} bv_justification(theory_var v1, theory_var v2): m_kind(bv_justification::kind_t::bit2eq), m_v1(v1), m_v2(v2) {} bv_justification(unsigned idx, sat::literal c) : m_kind(bv_justification::kind_t::bit2ne), m_idx(idx), m_consequent(c) {} bv_justification(unsigned idx, theory_var v1, theory_var v2, sat::literal c, sat::literal a) : m_kind(bv_justification::kind_t::ne2bit), m_idx(idx), m_v1(v1), m_v2(v2), m_consequent(c), m_antecedent(a) {} bv_justification(theory_var v1, theory_var v2, euf::enode* a, euf::enode* b, euf::enode* c): m_kind(bv_justification::kind_t::bv2int), m_v1(v1), m_v2(v2), a(a), b(b), c(c) {} sat::ext_constraint_idx to_index() const { return sat::constraint_base::mem2base(this); } static bv_justification& from_index(size_t idx) { return *reinterpret_cast(sat::constraint_base::from_index(idx)->mem()); } static size_t get_obj_size() { return sat::constraint_base::obj_size(sizeof(bv_justification)); } }; sat::justification mk_eq2bit_justification(theory_var v1, theory_var v2, sat::literal c, sat::literal a); sat::ext_justification_idx mk_bit2eq_justification(theory_var v1, theory_var v2); sat::justification mk_bit2ne_justification(unsigned idx, sat::literal c); sat::justification mk_ne2bit_justification(unsigned idx, theory_var v1, theory_var v2, sat::literal c, sat::literal a); sat::ext_constraint_idx mk_bv2int_justification(theory_var v1, theory_var v2, euf::enode* a, euf::enode* b, euf::enode* c); void log_drat(bv_justification const& c); class proof_hint : public euf::th_proof_hint { bv_justification::kind_t m_kind; sat::literal_vector& m_proof_literals; unsigned m_lit_head, m_lit_tail; expr* a1 = nullptr, * a2 = nullptr, * b1 = nullptr, * b2 = nullptr; public: proof_hint(bv_justification::kind_t k, sat::literal_vector& pl, unsigned lh, unsigned lt, expr* a1 = nullptr, expr* a2 = nullptr, expr* b1 = nullptr, expr* b2 = nullptr) : m_kind(k), m_proof_literals(pl), m_lit_head(lh), m_lit_tail(lt), a1(a1), a2(a2), b1(b1), b2(b2) {} expr* get_hint(euf::solver& s) const override; }; sat::literal_vector m_proof_literals; unsigned m_lit_head = 0, m_lit_tail = 0; /** \brief Structure used to store the position of a bitvector variable that contains the true_literal/false_literal. Remark: the implementation assumes that bitvector variables containing complementary bits are never merged. I assert a disequality (not (= x y)) whenever x and y contain complementary bits. However, this is too expensive when the bit is the true_literal or false_literal. The number of disequalities is too big. To avoid this problem, each equivalence class has a set of its true_literal and false_literal bits in the form of svector. Before merging two classes we just check if the merge is valid by traversing these vectors. */ struct zero_one_bit { theory_var m_owner; //!< variable that owns the bit: useful for backtracking unsigned m_idx:31; unsigned m_is_true:1; zero_one_bit(theory_var v = euf::null_theory_var, unsigned idx = UINT_MAX, bool is_true = false): m_owner(v), m_idx(idx), m_is_true(is_true) {} std::ostream& display(std::ostream& out) const { return out << "v" << m_owner << " @ " << m_idx << " " << (m_is_true?"T":"F"); } }; typedef svector zero_one_bits; struct eq_occurs { sat::bool_var m_bv1; sat::bool_var m_bv2; unsigned m_idx; theory_var m_v1; theory_var m_v2; sat::literal m_literal; euf::enode* m_node; eq_occurs* m_next; eq_occurs* m_prev; eq_occurs(sat::bool_var b1, sat::bool_var b2, unsigned idx, theory_var v1, theory_var v2, sat::literal lit, euf::enode* n, eq_occurs* next = nullptr) : m_bv1(b1), m_bv2(b2), m_idx(idx), m_v1(v1), m_v2(v2), m_literal(lit), m_node(n), m_next(next), m_prev(nullptr) {} }; class eq_occurs_it { eq_occurs* m_first; public: eq_occurs_it(eq_occurs* c) : m_first(c) {} eq_occurs const& operator*() { return *m_first; } eq_occurs_it& operator++() { m_first = m_first->m_next; return *this; } eq_occurs_it operator++(int) { eq_occurs_it tmp = *this; ++*this; return tmp; } bool operator==(eq_occurs_it const& other) const { return m_first == other.m_first; } bool operator!=(eq_occurs_it const& other) const { return !(*this == other); } }; class eqs_iterator { eq_occurs* o; public: eqs_iterator(eq_occurs* o) :o(o) {} eq_occurs_it begin() const { return eq_occurs_it(o); } eq_occurs_it end() const { return eq_occurs_it(nullptr); } }; struct var_pos_occ { var_pos m_vp; var_pos_occ* m_next; var_pos_occ(theory_var v = euf::null_theory_var, unsigned idx = 0, var_pos_occ* next = nullptr) :m_vp(v, idx), m_next(next) {} }; class var_pos_it { var_pos_occ* m_first; public: var_pos_it(var_pos_occ* c) : m_first(c) {} var_pos operator*() { return m_first->m_vp; } var_pos_it& operator++() { m_first = m_first->m_next; return *this; } var_pos_it operator++(int) { var_pos_it tmp = *this; ++* this; return tmp; } bool operator==(var_pos_it const& other) const { return m_first == other.m_first; } bool operator!=(var_pos_it const& other) const { return !(*this == other); } }; struct atom { bool_var m_bv; eq_occurs* m_eqs = nullptr; var_pos_occ * m_occs = nullptr; svector> m_bit2occ; literal m_var = sat::null_literal; literal m_def = sat::null_literal; atom(bool_var b) :m_bv(b), m_eqs(nullptr), m_occs(nullptr) {} ~atom() { m_bit2occ.clear(); } var_pos_it begin() const { return var_pos_it(m_occs); } var_pos_it end() const { return var_pos_it(nullptr); } eqs_iterator eqs() const { return eqs_iterator(m_eqs); } }; struct propagation_item { var_pos m_vp = var_pos(0, 0) ; atom* m_atom = nullptr; explicit propagation_item(atom* a) : m_atom(a) {} explicit propagation_item(var_pos const& vp) : m_vp(vp) {} bool operator==(propagation_item const& other) const { if (m_atom) return m_atom == other.m_atom; return false; } bool is_atom() const { return m_atom != nullptr; } }; class bit_trail; class add_var_pos_trail; class add_eq_occurs_trail; class del_eq_occurs_trail; class mk_atom_trail; class bit_occs_trail; typedef ptr_vector bool_var2atom; typedef vector bits_vector; bv_util bv; arith_util m_autil; stats m_stats; ackerman m_ackerman; bit_blaster m_bb; bv_union_find m_find; bits_vector m_bits; // per var, the bits of a given variable. unsigned_vector m_wpos; // per var, watch position for fixed variable detection. vector m_zero_one_bits; // per var, see comment in the struct zero_one_bit bool_var2atom m_bool_var2atom; value2var m_fixed_var_table; mutable vector m_power2; literal_vector m_tmp_literals; svector m_prop_queue; unsigned_vector m_prop_queue_lim; unsigned m_prop_queue_head = 0; sat::literal m_true = sat::null_literal; euf::enode_vector m_bv2ints; obj_map m_lazymul; // internalize void insert_bv2a(bool_var bv, atom * a) { m_bool_var2atom.setx(bv, a, 0); } void erase_bv2a(bool_var bv) { m_bool_var2atom[bv] = 0; } atom * get_bv2a(bool_var bv) const { return m_bool_var2atom.get(bv, 0); } bool visit(expr* e) override; bool visited(expr* e) override; bool post_visit(expr* e, bool sign, bool root) override; unsigned get_bv_size(euf::enode* n); unsigned get_bv_size(theory_var v); theory_var get_var(euf::enode* n); euf::enode* get_arg(euf::enode* n, unsigned idx); inline theory_var get_arg_var(euf::enode* n, unsigned idx); void get_bits(theory_var v, expr_ref_vector& r); void get_bits(euf::enode* n, expr_ref_vector& r); void get_arg_bits(app* n, unsigned idx, expr_ref_vector& r); void fixed_var_eh(theory_var v); bool is_fixed(euf::theory_var v, expr_ref& val, sat::literal_vector& lits) override; bool is_bv(theory_var v) const { return bv.is_bv(var2expr(v)); } void register_true_false_bit(theory_var v, unsigned i); void add_bit(theory_var v, sat::literal lit); atom* mk_atom(sat::bool_var b); void eq_internalized(sat::bool_var b1, sat::bool_var b2, unsigned idx, theory_var v1, theory_var v2, sat::literal eq, euf::enode* n); void del_eq_occurs(atom* a, eq_occurs* occ); void set_bit_eh(theory_var v, literal l, unsigned idx); void init_bits(expr* e, expr_ref_vector const & bits); void mk_bits(theory_var v); void add_def(sat::literal def, sat::literal l); bool internalize_circuit(app* a); void internalize_unary(app* n, std::function& fn); void internalize_binary(app* n, std::function& fn); void internalize_par_unary(app* n, std::function& fn); void internalize_novfl(app* n, std::function& fn); void internalize_interp(app* n, std::function& ibin, std::function& un); void internalize_num(app * n); void internalize_concat(app * n); void internalize_bv2int(app* n); void internalize_int2bv(app* n); void internalize_mkbv(app* n); void internalize_xor3(app* n); void internalize_carry(app* n); void internalize_sub(app* n); void internalize_extract(app* n); void internalize_repeat(app* n); void internalize_bit2bool(app* n); void internalize_udiv_i(app* n); template void internalize_le(app* n); void assert_bv2int_axiom(app * n); void assert_int2bv_axiom(app* n); void assert_ackerman(theory_var v1, theory_var v2); bool reflect() const { return get_config().m_bv_reflect; } // delay internalize enum class internalize_mode { delay_i, no_delay_i, init_bits_only_i }; obj_map m_delay_internalize; bool m_cheap_axioms{ true }; bool should_bit_blast(app * n); bool check_delay_internalized(expr* e); bool check_lazy_mul(app* e, expr* mul_value, expr* arg_value); bool check_mul(app* e); bool check_mul_invertibility(app* n, expr_ref_vector const& arg_values, expr* value); bool check_mul_zero(app* n, expr_ref_vector const& arg_values, expr* value1, expr* value2); bool check_mul_one(app* n, expr_ref_vector const& arg_values, expr* value1, expr* value2); bool check_umul_no_overflow(app* n, expr_ref_vector const& arg_values, expr* value); bool check_bv_eval(euf::enode* n); bool check_bool_eval(euf::enode* n); void encode_msb_tail(expr* x, expr_ref_vector& xs); void encode_lsb_tail(expr* x, expr_ref_vector& xs); internalize_mode get_internalize_mode(expr* e); void set_delay_internalize(expr* e, internalize_mode mode); expr_ref eval_args(euf::enode* n, expr_ref_vector& eargs); expr_ref eval_bv(euf::enode* n); // solving theory_var find(theory_var v) const { return m_find.find(v); } bool find_wpos(theory_var v); void find_new_diseq_axioms(atom& a, theory_var v, unsigned idx); void mk_new_diseq_axiom(theory_var v1, theory_var v2, unsigned idx); bool get_fixed_value(theory_var v, numeral& result) const; void add_fixed_eq(theory_var v1, theory_var v2); svector m_merge_aux[2]; //!< auxiliary vector used in merge_zero_one_bits bool merge_zero_one_bits(theory_var r1, theory_var r2); bool assign_bit(literal consequent, theory_var v1, theory_var v2, unsigned idx, literal antecedent, bool propagate_eqc); bool propagate_bits(var_pos entry); bool propagate_eq_occurs(eq_occurs const& occ); numeral const& power2(unsigned i) const; sat::literal mk_true(); // invariants bool check_zero_one_bits(theory_var v); void check_missing_propagation() const; void validate_atoms() const; std::ostream& display(std::ostream& out, atom const& a) const; public: solver(euf::solver& ctx, theory_id id); void set_lookahead(sat::lookahead* s) override { } void init_search() override {} double get_reward(literal l, sat::ext_constraint_idx idx, sat::literal_occs_fun& occs) const override; bool is_extended_binary(sat::ext_justification_idx idx, literal_vector& r) override; bool is_external(bool_var v) override; void get_antecedents(literal l, sat::ext_justification_idx idx, literal_vector & r, bool probing) override; void asserted(literal l) override; sat::check_result check() override; void push_core() override; void pop_core(unsigned n) override; void simplify() override; bool set_root(literal l, literal r) override; void flush_roots() override; void clauses_modifed() override; lbool get_phase(bool_var v) override; std::ostream& display(std::ostream& out) const override; std::ostream& display_justification(std::ostream& out, sat::ext_justification_idx idx) const override; std::ostream& display_constraint(std::ostream& out, sat::ext_constraint_idx idx) const override; void collect_statistics(statistics& st) const override; euf::th_solver* clone(euf::solver& ctx) override; extension* copy(sat::solver* s) override; void find_mutexes(literal_vector& lits, vector & mutexes) override {} void gc() override {} void pop_reinit() override; bool validate() override; void init_use_list(sat::ext_use_list& ul) override; bool is_blocked(literal l, sat::ext_constraint_idx) override; bool check_model(sat::model const& m) const override; void finalize_model(model& mdl) override; void new_eq_eh(euf::th_eq const& eq) override; void new_diseq_eh(euf::th_eq const& ne) override; bool use_diseqs() const override { return true; } bool unit_propagate() override; void add_value(euf::enode* n, model& mdl, expr_ref_vector& values) override; bool extract_pb(std::function& card, std::function& pb) override { return false; } bool to_formulas(std::function& l2e, expr_ref_vector& fmls) override { return false; } sat::literal internalize(expr* e, bool sign, bool root) override; void internalize(expr* e) override; void eq_internalized(euf::enode* n) override; euf::theory_var mk_var(euf::enode* n) override; void apply_sort_cnstr(euf::enode * n, sort * s) override; bool_var get_bit(unsigned bit, euf::enode* n) const; void merge_eh(theory_var, theory_var, theory_var v1, theory_var v2); void after_merge_eh(theory_var r1, theory_var r2, theory_var v1, theory_var v2) { SASSERT(check_zero_one_bits(r1)); } void unmerge_eh(theory_var v1, theory_var v2); trail_stack& get_trail_stack(); // diagnostics std::ostream& display(std::ostream& out, theory_var v) const; typedef std::pair pp_var; pp_var pp(theory_var v) const { return pp_var(this, v); } friend std::ostream& operator<<(std::ostream& out, solver::zero_one_bit const& zo) { return zo.display(out); } }; inline std::ostream& operator<<(std::ostream& out, solver::pp_var const& p) { return p.first->display(out, p.second); } }