/*++ Copyright (c) 2006 Microsoft Corporation Module Name: theory_bv.cpp Abstract: Author: Leonardo de Moura (leonardo) 2008-06-06. Revision History: --*/ #include"smt_context.h" #include"theory_bv.h" #include"ast_ll_pp.h" #include"ast_pp.h" #include"smt_model_generator.h" #include"stats.h" namespace smt { void theory_bv::init(context * ctx) { theory::init(ctx); m_simplifier = &(ctx->get_simplifier()); } theory_var theory_bv::mk_var(enode * n) { theory_var r = theory::mk_var(n); m_find.mk_var(); m_bits.push_back(literal_vector()); m_wpos.push_back(0); m_zero_one_bits.push_back(zero_one_bits()); get_context().attach_th_var(n, this, r); return r; } app * theory_bv::mk_bit2bool(app * bv, unsigned idx) { parameter p(idx); expr * args[1] = {bv}; return get_manager().mk_app(get_id(), OP_BIT2BOOL, 1, &p, 1, args); } void theory_bv::mk_bits(theory_var v) { enode * n = get_enode(v); app * owner = n->get_owner(); unsigned bv_size = get_bv_size(n); context & ctx = get_context(); literal_vector & bits = m_bits[v]; for (unsigned i = 0; i < bv_size; i++) { app * bit = mk_bit2bool(owner, i); ctx.internalize(bit, true); bool_var b = ctx.get_bool_var(bit); bits.push_back(literal(b)); } } class mk_atom_trail : public trail { bool_var m_var; public: mk_atom_trail(bool_var v):m_var(v) {} virtual void undo(theory_bv & th) { theory_bv::atom * a = th.get_bv2a(m_var); a->~atom(); th.erase_bv2a(m_var); } }; void theory_bv::mk_bit2bool(app * n) { context & ctx = get_context(); SASSERT(!ctx.b_internalized(n)); if (!ctx.e_internalized(n->get_arg(0))) { // This may happen if bit2bool(x) is in a conflict // clause that is being reinitialized, and x was not reinitialized // yet. // So, we internalize x (i.e., n->get_arg(0)) expr * first_arg = n->get_arg(0); ctx.internalize(first_arg, false); SASSERT(ctx.e_internalized(first_arg)); // In most cases, when x is internalized, its bits are created. // They are created because x is a bit-vector operation or apply_sort_cnstr is invoked. // However, there is an exception. The method apply_sort_cnstr is not invoked for ite-terms. // So, I execute get_var on the enode attached to first_arg. // This will force a theory variable to be created if it does not already exist. // This will also force the creation of all bits for x. enode * first_arg_enode = ctx.get_enode(first_arg); get_var(first_arg_enode); SASSERT(ctx.b_internalized(n)); } else { enode * arg = ctx.get_enode(n->get_arg(0)); // The argument was already internalized, but it may not have a theory variable associated with it. // For example, for ite-terms the method apply_sort_cnstr is not invoked. // See comment in the then-branch. theory_var v_arg = arg->get_th_var(get_id()); if (v_arg == null_theory_var) { // The method get_var will create a theory variable for arg. // As a side-effect the bits for arg will also be created. get_var(arg); SASSERT(ctx.b_internalized(n)); } else { SASSERT(v_arg != null_theory_var); bool_var bv = ctx.mk_bool_var(n); ctx.set_var_theory(bv, get_id()); bit_atom * a = new (get_region()) bit_atom(); insert_bv2a(bv, a); m_trail_stack.push(mk_atom_trail(bv)); unsigned idx = n->get_decl()->get_parameter(0).get_int(); SASSERT(a->m_occs == 0); a->m_occs = new (get_region()) var_pos_occ(v_arg, idx); } } } void theory_bv::process_args(app * n) { context & ctx = get_context(); unsigned num_args = n->get_num_args(); for (unsigned i = 0; i < num_args; i++) ctx.internalize(n->get_arg(i), false); } enode * theory_bv::mk_enode(app * n) { context & ctx = get_context(); enode * e; if (ctx.e_internalized(n)) { e = ctx.get_enode(n); } else { e = ctx.mk_enode(n, !m_params.m_bv_reflect, false, m_params.m_bv_cc); mk_var(e); } SASSERT(e->get_th_var(get_id()) != null_theory_var); return e; } theory_var theory_bv::get_var(enode * n) { theory_var v = n->get_th_var(get_id()); if (v == null_theory_var) { v = mk_var(n); mk_bits(v); } return v; } enode * theory_bv::get_arg(enode * n, unsigned idx) { if (m_params.m_bv_reflect) { return n->get_arg(idx); } else { context & ctx = get_context(); app * arg = to_app(n->get_owner()->get_arg(idx)); SASSERT(ctx.e_internalized(arg)); return ctx.get_enode(arg); } } inline theory_var theory_bv::get_arg_var(enode * n, unsigned idx) { return get_var(get_arg(n, idx)); } void theory_bv::get_bits(theory_var v, expr_ref_vector & r) { context & ctx = get_context(); literal_vector & bits = m_bits[v]; literal_vector::const_iterator it = bits.begin(); literal_vector::const_iterator end = bits.end(); for (; it != end; ++it) { expr_ref l(get_manager()); ctx.literal2expr(*it, l); r.push_back(l); } } inline void theory_bv::get_bits(enode * n, expr_ref_vector & r) { get_bits(get_var(n), r); } inline void theory_bv::get_arg_bits(enode * n, unsigned idx, expr_ref_vector & r) { get_bits(get_arg_var(n, idx), r); } inline void theory_bv::get_arg_bits(app * n, unsigned idx, expr_ref_vector & r) { context & ctx = get_context(); app * arg = to_app(n->get_arg(idx)); SASSERT(ctx.e_internalized(arg)); get_bits(ctx.get_enode(arg), r); } class add_var_pos_trail : public trail { theory_bv::bit_atom * m_atom; public: add_var_pos_trail(theory_bv::bit_atom * a):m_atom(a) {} virtual void undo(theory_bv & th) { SASSERT(m_atom->m_occs); m_atom->m_occs = m_atom->m_occs->m_next; } }; /** \brief v1[idx] = ~v2[idx], then v1 /= v2 is a theory axiom. */ void theory_bv::mk_new_diseq_axiom(theory_var v1, theory_var v2, unsigned idx) { SASSERT(m_bits[v1][idx] == ~m_bits[v2][idx]); TRACE("bv_diseq_axiom", tout << "found new diseq axiom\n"; display_var(tout, v1); display_var(tout, v2);); // found new disequality m_stats.m_num_diseq_static++; enode * e1 = get_enode(v1); enode * e2 = get_enode(v2); literal l = ~(mk_eq(e1->get_owner(), e2->get_owner(), true)); context & ctx = get_context(); ctx.mk_th_axiom(get_id(), 1, &l); if (ctx.relevancy()) { expr * eq = ctx.bool_var2expr(l.var()); relevancy_eh * eh = ctx.mk_relevancy_eh(pair_relevancy_eh(e1->get_owner(), e2->get_owner(), eq)); ctx.add_relevancy_eh(e1->get_owner(), eh); ctx.add_relevancy_eh(e2->get_owner(), eh); } } void theory_bv::register_true_false_bit(theory_var v, unsigned idx) { SASSERT(m_bits[v][idx] == true_literal || m_bits[v][idx] == false_literal); bool is_true = (m_bits[v][idx] == true_literal); zero_one_bits & bits = m_zero_one_bits[v]; bits.push_back(zero_one_bit(v, idx, is_true)); } /** \brief v[idx] = ~v'[idx], then v /= v' is a theory axiom. */ void theory_bv::find_new_diseq_axioms(var_pos_occ * occs, theory_var v, unsigned idx) { literal l = m_bits[v][idx]; l.neg(); while (occs) { theory_var v2 = occs->m_var; unsigned idx2 = occs->m_idx; if (idx == idx2 && m_bits[v2][idx2] == l && get_bv_size(v2) == get_bv_size(v)) mk_new_diseq_axiom(v, v2, idx); occs = occs->m_next; } } /** \brief Add bit l to the given variable. */ void theory_bv::add_bit(theory_var v, literal l) { context & ctx = get_context(); literal_vector & bits = m_bits[v]; unsigned idx = bits.size(); bits.push_back(l); if (l.var() == true_bool_var) { register_true_false_bit(v, idx); } else { theory_id th_id = ctx.get_var_theory(l.var()); if (th_id == get_id()) { atom * a = get_bv2a(l.var()); SASSERT(a && a->is_bit()); bit_atom * b = static_cast(a); find_new_diseq_axioms(b->m_occs, v, idx); m_trail_stack.push(add_var_pos_trail(b)); b->m_occs = new (get_region()) var_pos_occ(v, idx, b->m_occs); } else { SASSERT(th_id == null_theory_id); ctx.set_var_theory(l.var(), get_id()); SASSERT(ctx.get_var_theory(l.var()) == get_id()); bit_atom * b = new (get_region()) bit_atom(); insert_bv2a(l.var(), b); m_trail_stack.push(mk_atom_trail(l.var())); SASSERT(b->m_occs == 0); b->m_occs = new (get_region()) var_pos_occ(v, idx); } } } void theory_bv::simplify_bit(expr * s, expr_ref & r) { // proof_ref p(get_manager()); // if (get_context().at_base_level()) // m_simplifier->operator()(s, r, p); // else r = s; } void theory_bv::init_bits(enode * n, expr_ref_vector const & bits) { context & ctx = get_context(); ast_manager & m = get_manager(); theory_var v = n->get_th_var(get_id()); SASSERT(v != null_theory_var); unsigned sz = bits.size(); SASSERT(get_bv_size(n) == sz); for (unsigned i = 0; i < sz; i++) { expr * bit = bits.get(i); expr_ref s_bit(m); simplify_bit(bit, s_bit); ctx.internalize(s_bit, true); literal l = ctx.get_literal(s_bit.get()); TRACE("init_bits", tout << "bit " << i << " of #" << n->get_owner_id() << "\n" << mk_ll_pp(s_bit, m) << "\n";); add_bit(v, l); } find_wpos(v); } /** \brief Find an unassigned bit for m_wpos[v], if such bit cannot be found invoke fixed_var_eh */ void theory_bv::find_wpos(theory_var v) { context & ctx = get_context(); literal_vector const & bits = m_bits[v]; unsigned sz = bits.size(); unsigned & wpos = m_wpos[v]; unsigned init = wpos; for (; wpos < sz; wpos++) { TRACE("find_wpos", tout << "curr bit: " << bits[wpos] << "\n";); if (ctx.get_assignment(bits[wpos]) == l_undef) { TRACE("find_wpos", tout << "moved wpos of v" << v << " to " << wpos << "\n";); return; } } wpos = 0; for (; wpos < init; wpos++) { if (ctx.get_assignment(bits[wpos]) == l_undef) { TRACE("find_wpos", tout << "moved wpos of v" << v << " to " << wpos << "\n";); return; } } TRACE("find_wpos", tout << "v" << v << " is a fixed variable.\n";); fixed_var_eh(v); } class fixed_eq_justification : public justification { theory_bv & m_th; theory_var m_var1; theory_var m_var2; void mark_bits(conflict_resolution & cr, literal_vector const & bits) { context & ctx = cr.get_context(); literal_vector::const_iterator it = bits.begin(); literal_vector::const_iterator end = bits.end(); for (; it != end; ++it) { if (it->var() != true_bool_var) { if (ctx.get_assignment(*it) == l_true) cr.mark_literal(*it); else cr.mark_literal(~(*it)); } } } void get_proof(conflict_resolution & cr, literal l, ptr_buffer & prs, bool & visited) { if (l.var() == true_bool_var) return; proof * pr = 0; if (cr.get_context().get_assignment(l) == l_true) pr = cr.get_proof(l); else pr = cr.get_proof(~l); if (pr) prs.push_back(pr); else visited = false; } public: fixed_eq_justification(theory_bv & th, theory_var v1, theory_var v2): m_th(th), m_var1(v1), m_var2(v2) { } virtual void get_antecedents(conflict_resolution & cr) { mark_bits(cr, m_th.m_bits[m_var1]); mark_bits(cr, m_th.m_bits[m_var2]); } virtual proof * mk_proof(conflict_resolution & cr) { ptr_buffer prs; context & ctx = cr.get_context(); bool visited = true; literal_vector const & bits1 = m_th.m_bits[m_var1]; literal_vector const & bits2 = m_th.m_bits[m_var2]; literal_vector::const_iterator it1 = bits1.begin(); literal_vector::const_iterator it2 = bits2.begin(); literal_vector::const_iterator end1 = bits1.end(); for (; it1 != end1; ++it1, ++it2) { get_proof(cr, *it1, prs, visited); get_proof(cr, *it2, prs, visited); } if (!visited) return 0; expr * fact = ctx.mk_eq_atom(m_th.get_enode(m_var1)->get_owner(), m_th.get_enode(m_var2)->get_owner()); ast_manager & m = ctx.get_manager(); return m.mk_th_lemma(get_from_theory(), fact, prs.size(), prs.c_ptr()); } virtual theory_id get_from_theory() const { return m_th.get_id(); } virtual char const * get_name() const { return "bv-fixed-eq"; } }; void theory_bv::fixed_var_eh(theory_var v) { numeral val; bool r = get_fixed_value(v, val); SASSERT(r); unsigned sz = get_bv_size(v); value_sort_pair key(val, sz); theory_var v2; if (m_fixed_var_table.find(key, v2)) { numeral val2; if (v2 < static_cast(get_num_vars()) && is_bv(v2) && get_bv_size(v2) == sz && get_fixed_value(v2, val2) && val == val2) { if (get_enode(v)->get_root() != get_enode(v2)->get_root()) { SASSERT(get_bv_size(v) == get_bv_size(v2)); context & ctx = get_context(); justification * js = ctx.mk_justification(fixed_eq_justification(*this, v, v2)); TRACE("fixed_var_eh", tout << "detected equality: v" << v << " = v" << v2 << "\n"; display_var(tout, v); display_var(tout, v2);); m_stats.m_num_th2core_eq++; ctx.assign_eq(get_enode(v), get_enode(v2), eq_justification(js)); } } else { // the original fixed variable v2 was deleted or it is not fixed anymore. m_fixed_var_table.erase(key); m_fixed_var_table.insert(key, v); } } else { m_fixed_var_table.insert(key, v); } } bool theory_bv::get_fixed_value(theory_var v, numeral & result) const { context & ctx = get_context(); result.reset(); literal_vector const & bits = m_bits[v]; literal_vector::const_iterator it = bits.begin(); literal_vector::const_iterator end = bits.end(); for (unsigned i = 0; it != end; ++it, ++i) { switch (ctx.get_assignment(*it)) { case l_false: break; case l_undef: return false; case l_true: result += m_bb.power(i); break; } } return true; } bool theory_bv::get_fixed_value(app* x, numeral & result) const { context& ctx = get_context(); if (!ctx.e_internalized(x)) return false; enode * e = ctx.get_enode(x); theory_var v = e->get_th_var(get_id()); return get_fixed_value(v, result); } void theory_bv::internalize_num(app * n) { SASSERT(!get_context().e_internalized(n)); ast_manager & m = get_manager(); numeral val; unsigned sz; m_util.is_numeral(n, val, sz); enode * e = mk_enode(n); // internalizer is marking enodes as interpreted whenever the associated ast is a value and a constant. // e->mark_as_interpreted(); theory_var v = e->get_th_var(get_id()); expr_ref_vector bits(m); m_bb.num2bits(val, sz, bits); SASSERT(bits.size() == sz); literal_vector & c_bits = m_bits[v]; for (unsigned i = 0; i < sz; i++) { expr * l = bits.get(i); if (m.is_true(l)) { c_bits.push_back(true_literal); } else { SASSERT(m.is_false(l)); c_bits.push_back(false_literal); } register_true_false_bit(v, i); } fixed_var_eh(v); } void theory_bv::internalize_mkbv(app* n) { ast_manager& m = get_manager(); expr_ref_vector bits(m); process_args(n); enode * e = mk_enode(n); bits.append(n->get_num_args(), n->get_args()); init_bits(e, bits); } void theory_bv::internalize_bv2int(app* n) { SASSERT(!get_context().e_internalized(n)); ast_manager & m = get_manager(); context& ctx = get_context(); TRACE("bv", tout << mk_bounded_pp(n, m) << "\n";); process_args(n); mk_enode(n); if (!ctx.relevancy()) { assert_bv2int_axiom(n); } } void theory_bv::assert_bv2int_axiom(app * n) { // // create the axiom: // n = bv2int(k) = ite(bit2bool(k[sz-1],2^{sz-1},0) + ... + ite(bit2bool(k[0],1,0)) // SASSERT(get_context().e_internalized(n)); SASSERT(m_util.is_bv2int(n)); ast_manager & m = get_manager(); TRACE("bv2int_bug", tout << "bv2int:\n" << mk_pp(n, m) << "\n";); context & ctx = get_context(); sort * int_sort = m.get_sort(n); app * k = to_app(n->get_arg(0)); SASSERT(m_util.is_bv_sort(m.get_sort(k))); expr_ref_vector k_bits(m); enode * k_enode = mk_enode(k); get_bits(k_enode, k_bits); unsigned sz = m_util.get_bv_size(k); expr_ref_vector args(m); expr_ref zero(m_autil.mk_numeral(numeral(0), int_sort), m); numeral num(1); for (unsigned i = 0; i < sz; ++i) { // Remark: A previous version of this method was using // // expr* b = mk_bit2bool(k,i); // // This is not correct. The predicate bit2bool is an // internal construct, and it was not meant for building // axioms directly. It is used to represent the bits of a // constant, and in some cases the bits of a complicated // bit-vector expression. In most cases, the bits of a // composite bit-vector expression T are just boolean // combinations of bit2bool atoms of the bit-vector // constants contained in T. So, instead of using // mk_bit2bool to access a particular bit of T, we should // use the method get_bits. // expr * b = k_bits.get(i); expr_ref n(m_autil.mk_numeral(num, int_sort), m); args.push_back(m.mk_ite(b, n, zero)); num *= numeral(2); } expr_ref sum(m); arith_simp().mk_add(sz, args.c_ptr(), sum); TRACE("bv", tout << mk_pp(n, m) << "\n"; tout << mk_pp(sum, m) << "\n"; ); literal l(mk_eq(n, sum, false)); ctx.mark_as_relevant(l); ctx.mk_th_axiom(get_id(), 1, &l); } void theory_bv::internalize_int2bv(app* n) { SASSERT(!get_context().e_internalized(n)); SASSERT(n->get_num_args() == 1); context& ctx = get_context(); process_args(n); mk_enode(n); mk_bits(ctx.get_enode(n)->get_th_var(get_id())); if (!ctx.relevancy()) { assert_int2bv_axiom(n); } } void theory_bv::assert_int2bv_axiom(app* n) { // // create the axiom: // bv2int(n) = e mod 2^bit_width // // where n = int2bv(e) // SASSERT(get_context().e_internalized(n)); SASSERT(m_util.is_int2bv(n)); ast_manager & m = get_manager(); context& ctx = get_context(); parameter param(m_autil.mk_int()); expr* n_expr = n; expr* lhs = m.mk_app(get_id(), OP_BV2INT, 1, ¶m, 1, &n_expr); unsigned sz = m_util.get_bv_size(n); numeral mod = power(numeral(2), sz); expr* rhs = m_autil.mk_mod(n->get_arg(0), m_autil.mk_numeral(mod, true)); literal l(mk_eq(lhs, rhs, false)); ctx.mark_as_relevant(l); ctx.mk_th_axiom(get_id(), 1, &l); TRACE("bv", tout << mk_pp(lhs, m) << " == \n"; tout << mk_pp(rhs, m) << "\n"; ); } #define MK_UNARY(NAME, BLAST_OP) \ void theory_bv::NAME(app * n) { \ SASSERT(!get_context().e_internalized(n)); \ SASSERT(n->get_num_args() == 1); \ process_args(n); \ ast_manager & m = get_manager(); \ enode * e = mk_enode(n); \ expr_ref_vector arg1_bits(m), bits(m); \ get_arg_bits(e, 0, arg1_bits); \ m_bb.BLAST_OP(arg1_bits.size(), arg1_bits.c_ptr(), bits); \ init_bits(e, bits); \ } #define MK_BINARY(NAME, BLAST_OP) \ void theory_bv::NAME(app * n) { \ SASSERT(!get_context().e_internalized(n)); \ SASSERT(n->get_num_args() == 2); \ process_args(n); \ ast_manager & m = get_manager(); \ enode * e = mk_enode(n); \ expr_ref_vector arg1_bits(m), arg2_bits(m), bits(m); \ get_arg_bits(e, 0, arg1_bits); \ get_arg_bits(e, 1, arg2_bits); \ SASSERT(arg1_bits.size() == arg2_bits.size()); \ m_bb.BLAST_OP(arg1_bits.size(), arg1_bits.c_ptr(), arg2_bits.c_ptr(), bits); \ init_bits(e, bits); \ } #define MK_AC_BINARY(NAME, BLAST_OP) \ void theory_bv::NAME(app * n) { \ SASSERT(!get_context().e_internalized(n)); \ SASSERT(n->get_num_args() >= 2); \ process_args(n); \ ast_manager & m = get_manager(); \ enode * e = mk_enode(n); \ expr_ref_vector arg_bits(m); \ expr_ref_vector bits(m); \ expr_ref_vector new_bits(m); \ unsigned i = n->get_num_args(); \ --i; \ get_arg_bits(e, i, bits); \ while (i > 0) { \ --i; \ arg_bits.reset(); \ get_arg_bits(e, i, arg_bits); \ SASSERT(arg_bits.size() == bits.size()); \ new_bits.reset(); \ m_bb.BLAST_OP(arg_bits.size(), arg_bits.c_ptr(), bits.c_ptr(), new_bits); \ bits.swap(new_bits); \ } \ init_bits(e, bits); \ } #define MK_BINARY_COND(NAME, BLAST_OP) \ void theory_bv::NAME(app * n) { \ SASSERT(!get_context().e_internalized(n)); \ SASSERT(n->get_num_args() == 2); \ process_args(n); \ ast_manager & m = get_manager(); \ context& ctx = get_context(); \ enode * e = mk_enode(n); \ expr_ref_vector arg1_bits(m), arg2_bits(m), bits(m); \ expr_ref cond(m), s_cond(m); \ get_arg_bits(e, 0, arg1_bits); \ get_arg_bits(e, 1, arg2_bits); \ SASSERT(arg1_bits.size() == arg2_bits.size()); \ m_bb.BLAST_OP(arg1_bits.size(), arg1_bits.c_ptr(), arg2_bits.c_ptr(), bits, cond); \ init_bits(e, bits); \ simplify_bit(cond, s_cond); \ ctx.internalize(s_cond, true); \ literal l(ctx.get_literal(s_cond)); \ ctx.mark_as_relevant(l); \ ctx.mk_th_axiom(get_id(), 1, &l); \ TRACE("bv", tout << mk_pp(cond, get_manager()) << "\n"; tout << l << "\n";); \ } MK_UNARY(internalize_not, mk_not); MK_UNARY(internalize_redand, mk_redand); MK_UNARY(internalize_redor, mk_redor); MK_AC_BINARY(internalize_add, mk_adder); MK_AC_BINARY(internalize_mul, mk_multiplier); MK_BINARY(internalize_udiv, mk_udiv); MK_BINARY(internalize_sdiv, mk_sdiv); MK_BINARY(internalize_urem, mk_urem); MK_BINARY(internalize_srem, mk_srem); MK_BINARY(internalize_smod, mk_smod); MK_BINARY(internalize_shl, mk_shl); MK_BINARY(internalize_lshr, mk_lshr); MK_BINARY(internalize_ashr, mk_ashr); MK_BINARY(internalize_ext_rotate_left, mk_ext_rotate_left); MK_BINARY(internalize_ext_rotate_right, mk_ext_rotate_right); MK_AC_BINARY(internalize_and, mk_and); MK_AC_BINARY(internalize_or, mk_or); MK_AC_BINARY(internalize_xor, mk_xor); MK_AC_BINARY(internalize_nand, mk_nand); MK_AC_BINARY(internalize_nor, mk_nor); MK_AC_BINARY(internalize_xnor, mk_xnor); MK_BINARY(internalize_comp, mk_comp); #define MK_PARAMETRIC_UNARY(NAME, BLAST_OP) \ void theory_bv::NAME(app * n) { \ SASSERT(!get_context().e_internalized(n)); \ SASSERT(n->get_num_args() == 1); \ process_args(n); \ ast_manager & m = get_manager(); \ enode * e = mk_enode(n); \ expr_ref_vector arg1_bits(m), bits(m); \ get_arg_bits(e, 0, arg1_bits); \ unsigned param = n->get_decl()->get_parameter(0).get_int(); \ m_bb.BLAST_OP(arg1_bits.size(), arg1_bits.c_ptr(), param, bits); \ init_bits(e, bits); \ } MK_PARAMETRIC_UNARY(internalize_sign_extend, mk_sign_extend); MK_PARAMETRIC_UNARY(internalize_zero_extend, mk_zero_extend); MK_PARAMETRIC_UNARY(internalize_rotate_left, mk_rotate_left); MK_PARAMETRIC_UNARY(internalize_rotate_right, mk_rotate_right); void theory_bv::internalize_concat(app * n) { process_args(n); enode * e = mk_enode(n); theory_var v = e->get_th_var(get_id()); unsigned num_args = n->get_num_args(); unsigned i = num_args; while (i > 0) { i--; theory_var arg = get_arg_var(e, i); literal_vector::const_iterator it = m_bits[arg].begin(); literal_vector::const_iterator end = m_bits[arg].end(); for (; it != end; ++it) add_bit(v, *it); } find_wpos(v); } void theory_bv::internalize_extract(app * n) { SASSERT(n->get_num_args() == 1); process_args(n); enode * e = mk_enode(n); theory_var v = e->get_th_var(get_id()); theory_var arg = get_arg_var(e, 0); unsigned start = n->get_decl()->get_parameter(1).get_int(); unsigned end = n->get_decl()->get_parameter(0).get_int(); SASSERT(start <= end); literal_vector & arg_bits = m_bits[arg]; for (unsigned i = start; i <= end; ++i) add_bit(v, arg_bits[i]); find_wpos(v); } bool theory_bv::internalize_term(app * term) { SASSERT(term->get_family_id() == get_family_id()); TRACE("bv", tout << "internalizing term: " << mk_bounded_pp(term, get_manager()) << "\n";); if (approximate_term(term)) { return false; } switch (term->get_decl_kind()) { case OP_BV_NUM: internalize_num(term); return true; case OP_BADD: internalize_add(term); return true; case OP_BMUL: internalize_mul(term); return true; case OP_BSDIV_I: internalize_sdiv(term); return true; case OP_BUDIV_I: internalize_udiv(term); return true; case OP_BSREM_I: internalize_srem(term); return true; case OP_BUREM_I: internalize_urem(term); return true; case OP_BSMOD_I: internalize_smod(term); return true; case OP_BAND: internalize_and(term); return true; case OP_BOR: internalize_or(term); return true; case OP_BNOT: internalize_not(term); return true; case OP_BXOR: internalize_xor(term); return true; case OP_BNAND: internalize_nand(term); return true; case OP_BNOR: internalize_nor(term); return true; case OP_BXNOR: internalize_xnor(term); return true; case OP_CONCAT: internalize_concat(term); return true; case OP_SIGN_EXT: internalize_sign_extend(term); return true; case OP_ZERO_EXT: internalize_zero_extend(term); return true; case OP_EXTRACT: internalize_extract(term); return true; case OP_BREDOR: internalize_redor(term); return true; case OP_BREDAND: internalize_redand(term); return true; case OP_BCOMP: internalize_comp(term); return true; case OP_BSHL: internalize_shl(term); return true; case OP_BLSHR: internalize_lshr(term); return true; case OP_BASHR: internalize_ashr(term); return true; case OP_ROTATE_LEFT: internalize_rotate_left(term); return true; case OP_ROTATE_RIGHT: internalize_rotate_right(term); return true; case OP_EXT_ROTATE_LEFT: internalize_ext_rotate_left(term); return true; case OP_EXT_ROTATE_RIGHT: internalize_ext_rotate_right(term); return true; case OP_BSDIV0: return false; case OP_BUDIV0: return false; case OP_BSREM0: return false; case OP_BUREM0: return false; case OP_BSMOD0: return false; case OP_MKBV: internalize_mkbv(term); return true; case OP_INT2BV: if (m_params.m_bv_enable_int2bv2int) { internalize_int2bv(term); } return m_params.m_bv_enable_int2bv2int; case OP_BV2INT: if (m_params.m_bv_enable_int2bv2int) { internalize_bv2int(term); } return m_params.m_bv_enable_int2bv2int; default: TRACE("bv_op", tout << "unsupported operator: " << mk_ll_pp(term, get_manager()) << "\n";); UNREACHABLE(); return false; } } #define MK_NO_OVFL(NAME, OP) \ void theory_bv::NAME(app *n) { \ SASSERT(n->get_num_args() == 2); \ process_args(n); \ ast_manager & m = get_manager(); \ context & ctx = get_context(); \ expr_ref_vector arg1_bits(m), arg2_bits(m); \ get_arg_bits(n, 0, arg1_bits); \ get_arg_bits(n, 1, arg2_bits); \ expr_ref out(m); \ m_bb.OP(arg1_bits.size(), arg1_bits.c_ptr(), arg2_bits.c_ptr(), out); \ expr_ref s_out(m); \ simplify_bit(out, s_out); \ ctx.internalize(s_out, true); \ literal def = ctx.get_literal(s_out); \ literal l(ctx.mk_bool_var(n)); \ ctx.set_var_theory(l.var(), get_id()); \ le_atom * a = new (get_region()) le_atom(l, def); /* abuse le_atom */ \ insert_bv2a(l.var(), a); \ m_trail_stack.push(mk_atom_trail(l.var())); \ /* smul_no_overflow and umul_no_overflow are using the le_atom (THIS IS A BIG HACK)... */ \ /* the connection between the l and def was never realized when */ \ /* relevancy() is true and m_bv_lazy_le is false (the default configuration). */ \ /* So, we need to check also the m_bv_lazy_le flag here. */ \ /* Maybe, we should rename the le_atom to bridge_atom, and m_bv_lazy_le option to m_bv_lazy_bridge. */ \ if (!ctx.relevancy() || !m_params.m_bv_lazy_le) { \ ctx.mk_th_axiom(get_id(), l, ~def); \ ctx.mk_th_axiom(get_id(), ~l, def); \ } \ } MK_NO_OVFL(internalize_umul_no_overflow, mk_umul_no_overflow); MK_NO_OVFL(internalize_smul_no_overflow, mk_smul_no_overflow); MK_NO_OVFL(internalize_smul_no_underflow, mk_smul_no_underflow); template void theory_bv::internalize_le(app * n) { SASSERT(n->get_num_args() == 2); process_args(n); ast_manager & m = get_manager(); context & ctx = get_context(); expr_ref_vector arg1_bits(m), arg2_bits(m); get_arg_bits(n, 0, arg1_bits); get_arg_bits(n, 1, arg2_bits); expr_ref le(m); if (Signed) m_bb.mk_sle(arg1_bits.size(), arg1_bits.c_ptr(), arg2_bits.c_ptr(), le); else m_bb.mk_ule(arg1_bits.size(), arg1_bits.c_ptr(), arg2_bits.c_ptr(), le); expr_ref s_le(m); simplify_bit(le, s_le); ctx.internalize(s_le, true); literal def = ctx.get_literal(s_le); literal l(ctx.mk_bool_var(n)); ctx.set_var_theory(l.var(), get_id()); le_atom * a = new (get_region()) le_atom(l, def); insert_bv2a(l.var(), a); m_trail_stack.push(mk_atom_trail(l.var())); if (!ctx.relevancy() || !m_params.m_bv_lazy_le) { ctx.mk_th_axiom(get_id(), l, ~def); ctx.mk_th_axiom(get_id(), ~l, def); } } bool theory_bv::internalize_carry(app * n, bool gate_ctx) { context & ctx = get_context(); ctx.internalize(n->get_arg(0), true); ctx.internalize(n->get_arg(1), true); ctx.internalize(n->get_arg(2), true); bool is_new_var = false; bool_var v; if (!ctx.b_internalized(n)) { is_new_var = true; v = ctx.mk_bool_var(n); literal r(v); literal l1 = ctx.get_literal(n->get_arg(0)); literal l2 = ctx.get_literal(n->get_arg(1)); literal l3 = ctx.get_literal(n->get_arg(2)); ctx.mk_gate_clause(~r, l1, l2); ctx.mk_gate_clause(~r, l1, l3); ctx.mk_gate_clause(~r, l2, l3); ctx.mk_gate_clause( r, ~l1, ~l2); ctx.mk_gate_clause( r, ~l1, ~l3); ctx.mk_gate_clause( r, ~l2, ~l3); } else { v = ctx.get_bool_var(n); } if (!ctx.e_internalized(n) && !gate_ctx) { bool suppress_args = true; bool merge_tf = !gate_ctx; ctx.mk_enode(n, suppress_args, merge_tf, true); ctx.set_enode_flag(v, is_new_var); } return true; } bool theory_bv::internalize_xor3(app * n, bool gate_ctx) { context & ctx = get_context(); ctx.internalize(n->get_arg(0), true); ctx.internalize(n->get_arg(1), true); ctx.internalize(n->get_arg(2), true); bool is_new_var = false; bool_var v; if (!ctx.b_internalized(n)) { is_new_var = true; v = ctx.mk_bool_var(n); literal r(v); literal l1 = ctx.get_literal(n->get_arg(0)); literal l2 = ctx.get_literal(n->get_arg(1)); literal l3 = ctx.get_literal(n->get_arg(2)); ctx.mk_gate_clause(~r, l1, l2, l3); ctx.mk_gate_clause(~r, ~l1, ~l2, l3); ctx.mk_gate_clause(~r, ~l1, l2, ~l3); ctx.mk_gate_clause(~r, l1, ~l2, ~l3); ctx.mk_gate_clause( r, ~l1, l2, l3); ctx.mk_gate_clause( r, l1, ~l2, l3); ctx.mk_gate_clause( r, l1, l2, ~l3); ctx.mk_gate_clause( r, ~l1, ~l2, ~l3); } else { v = ctx.get_bool_var(n); } if (!ctx.e_internalized(n) && !gate_ctx) { bool suppress_args = true; bool merge_tf = !gate_ctx; ctx.mk_enode(n, suppress_args, merge_tf, true); ctx.set_enode_flag(v, is_new_var); } return true; } bool theory_bv::internalize_atom(app * atom, bool gate_ctx) { TRACE("bv", tout << "internalizing atom: " << mk_bounded_pp(atom, get_manager()) << "\n";); SASSERT(atom->get_family_id() == get_family_id()); if (approximate_term(atom)) { return false; } switch (atom->get_decl_kind()) { case OP_BIT2BOOL: mk_bit2bool(atom); return true; case OP_ULEQ: internalize_le(atom); return true; case OP_SLEQ: internalize_le(atom); return true; case OP_XOR3: return internalize_xor3(atom, gate_ctx); case OP_CARRY: return internalize_carry(atom, gate_ctx); case OP_BUMUL_NO_OVFL: internalize_umul_no_overflow(atom); return true; case OP_BSMUL_NO_OVFL: internalize_smul_no_overflow(atom); return true; case OP_BSMUL_NO_UDFL: internalize_smul_no_underflow(atom); return true; default: UNREACHABLE(); return false; } } // // Determine whether bit-vector expression should be approximated // based on the number of bits used by the arguments. // bool theory_bv::approximate_term(app* n) { if (m_params.m_bv_blast_max_size == INT_MAX) { return false; } unsigned num_args = n->get_num_args(); for (unsigned i = 0; i <= num_args; i++) { expr* arg = (i == num_args)?n:n->get_arg(i); sort* s = get_manager().get_sort(arg); s = get_manager().get_sort(arg); if (m_util.is_bv_sort(s) && m_util.get_bv_size(arg) > m_params.m_bv_blast_max_size) { if (!m_approximates_large_bvs) { TRACE("bv", tout << "found large size bit-vector:\n" << mk_pp(n, get_manager()) << "\n";); get_context().push_trail(value_trail(m_approximates_large_bvs)); m_approximates_large_bvs = true; } return true; } } return false; } void theory_bv::apply_sort_cnstr(enode * n, sort * s) { if (!is_attached_to_var(n) && !approximate_term(n->get_owner())) { theory_var v = mk_var(n); mk_bits(v); } } void theory_bv::new_eq_eh(theory_var v1, theory_var v2) { TRACE("bv_eq", tout << "new_eq: " << mk_pp(get_enode(v1)->get_owner(), get_manager()) << " = " << mk_pp(get_enode(v2)->get_owner(), get_manager()) << "\n";); TRACE("bv", tout << "new_eq_eh v" << v1 << " = v" << v2 << " relevant1: " << get_context().is_relevant(get_enode(v1)) << " relevant2: " << get_context().is_relevant(get_enode(v2)) << "\n";); m_find.merge(v1, v2); } void theory_bv::new_diseq_eh(theory_var v1, theory_var v2) { if (is_bv(v1)) { expand_diseq(v1, v2); } } void theory_bv::expand_diseq(theory_var v1, theory_var v2) { SASSERT(get_bv_size(v1) == get_bv_size(v2)); context & ctx = get_context(); ast_manager & m = get_manager(); #ifdef _TRACE unsigned num_bool_vars = ctx.get_num_bool_vars(); #endif literal_vector & lits = m_tmp_literals; lits.reset(); lits.push_back(mk_eq(get_enode(v1)->get_owner(), get_enode(v2)->get_owner(), true)); literal_vector const & bits1 = m_bits[v1]; literal_vector::const_iterator it1 = bits1.begin(); literal_vector::const_iterator end1 = bits1.end(); literal_vector const & bits2 = m_bits[v2]; literal_vector::const_iterator it2 = bits2.begin(); for (; it1 != end1; ++it1, ++it2) { if (*it1 == ~(*it2)) return; // static diseq } it1 = bits1.begin(); it2 = bits2.begin(); for (; it1 != end1; ++it1, ++it2) { expr_ref l1(m), l2(m), diff(m); ctx.literal2expr(*it1, l1); ctx.literal2expr(*it2, l2); m_bb.mk_xor(l1, l2, diff); ctx.internalize(diff, true); literal arg = ctx.get_literal(diff); lits.push_back(arg); } m_stats.m_num_diseq_dynamic++; ctx.mk_th_axiom(get_id(), lits.size(), lits.c_ptr()); TRACE_CODE({ static unsigned num = 0; static unsigned new_bool_vars = 0; new_bool_vars += (ctx.get_num_bool_vars() - num_bool_vars); if (num % 1000 == 0) TRACE("expand_diseq", tout << "num: " << num << " " << new_bool_vars << "\n";); num++; }); } void theory_bv::assign_eh(bool_var v, bool is_true) { context & ctx = get_context(); atom * a = get_bv2a(v); TRACE("bv", tout << "assert: v" << v << " #" << ctx.bool_var2expr(v)->get_id() << " is_true: " << is_true << "\n";); if (a->is_bit()) { // The following optimization is not correct. // Boolean variables created for performing bit-blasting are reused. // See regression\trevor6.smt for example. // // if (ctx.has_th_justification(v, get_id())) { // TRACE("bv", tout << "has th_justification\n";); // return; // } m_prop_queue.reset(); bit_atom * b = static_cast(a); var_pos_occ * curr = b->m_occs; while (curr) { m_prop_queue.push_back(var_pos(curr->m_var, curr->m_idx)); curr = curr->m_next; } TRACE("bv", tout << m_prop_queue.size() << "\n";); propagate_bits(); } } void theory_bv::propagate_bits() { context & ctx = get_context(); for (unsigned i = 0; i < m_prop_queue.size(); i++) { var_pos const & entry = m_prop_queue[i]; theory_var v = entry.first; unsigned idx = entry.second; if (m_wpos[v] == idx) find_wpos(v); literal_vector & bits = m_bits[v]; literal bit = bits[idx]; lbool val = ctx.get_assignment(bit); theory_var v2 = next(v); TRACE("bv_bit_prop", tout << "propagating #" << get_enode(v)->get_owner_id() << "[" << idx << "] = " << val << "\n";); while (v2 != v) { literal_vector & bits2 = m_bits[v2]; literal bit2 = bits2[idx]; SASSERT(bit != ~bit2); lbool val2 = ctx.get_assignment(bit2); TRACE("bv_bit_prop", tout << "propagating #" << get_enode(v2)->get_owner_id() << "[" << idx << "] = " << val2 << "\n";); if (val != val2) { literal antecedent = bit; literal consequent = bit2; if (val == l_false) { antecedent.neg(); consequent.neg(); } SASSERT(ctx.get_assignment(antecedent) == l_true); assign_bit(consequent, v, v2, idx, antecedent, false); if (ctx.inconsistent()) { TRACE("bv_bit_prop", tout << "inconsistent " << bit << " " << bit2 << "\n";); return; } } v2 = next(v2); } } m_prop_queue.reset(); TRACE("bv_bit_prop", tout << "done propagating\n";); } void theory_bv::assign_bit(literal consequent, theory_var v1, theory_var v2, unsigned idx, literal antecedent, bool propagate_eqc) { m_stats.m_num_bit2core++; context & ctx = get_context(); SASSERT(ctx.get_assignment(antecedent) == l_true); SASSERT(m_bits[v2][idx].var() == consequent.var()); SASSERT(consequent.var() != antecedent.var()); TRACE("bv_bit_prop", tout << "assigning: "; ctx.display_literal(tout, consequent); tout << " using "; ctx.display_literal(tout, antecedent); tout << " #" << get_enode(v1)->get_owner_id() << " #" << get_enode(v2)->get_owner_id() << " idx: " << idx << "\n"; tout << "propagate_eqc: " << propagate_eqc << "\n";); if (consequent == false_literal) { m_stats.m_num_conflicts++; ctx.set_conflict(mk_bit_eq_justification(v1, v2, consequent, antecedent)); } else { ctx.assign(consequent, mk_bit_eq_justification(v1, v2, consequent, antecedent)); if (m_wpos[v2] == idx) find_wpos(v2); // REMARK: bit_eq_justification is marked as a theory_bv justification. // Thus, the assignment to consequent will not be notified back to the theory. // So, we need to propagate the assignment to other bits. bool_var bv = consequent.var(); atom * a = get_bv2a(bv); SASSERT(a->is_bit()); bit_atom * b = static_cast(a); var_pos_occ * curr = b->m_occs; while (curr) { TRACE("assign_bit_bug", tout << "curr->m_var: v" << curr->m_var << ", curr->m_idx: " << curr->m_idx << ", v2: v" << v2 << ", idx: " << idx << "\n"; tout << "find(curr->m_var): v" << find(curr->m_var) << ", find(v2): v" << find(v2) << "\n"; tout << "is bit of #" << get_enode(curr->m_var)->get_owner_id() << "\n"; ); // If find(curr->m_var) == find(v2) && curr->m_idx == idx and propagate_eqc == false, then // this bit will be propagated to the equivalence class of v2 by assign_bit caller. if (propagate_eqc || find(curr->m_var) != find(v2) || curr->m_idx != idx) m_prop_queue.push_back(var_pos(curr->m_var, curr->m_idx)); curr = curr->m_next; } } } void theory_bv::relevant_eh(app * n) { ast_manager & m = get_manager(); context & ctx = get_context(); if (m.is_bool(n)) { bool_var v = ctx.get_bool_var(n); atom * a = get_bv2a(v); if (a && !a->is_bit()) { le_atom * le = static_cast(a); ctx.mark_as_relevant(le->m_def); if (m_params.m_bv_lazy_le) { ctx.mk_th_axiom(get_id(), le->m_var, ~le->m_def); ctx.mk_th_axiom(get_id(), ~le->m_var, le->m_def); } } } else if (m_params.m_bv_enable_int2bv2int && m_util.is_bv2int(n)) { ctx.mark_as_relevant(n->get_arg(0)); assert_bv2int_axiom(n); } else if (m_params.m_bv_enable_int2bv2int && m_util.is_int2bv(n)) { ctx.mark_as_relevant(n->get_arg(0)); assert_int2bv_axiom(n); } else if (ctx.e_internalized(n)) { enode * e = ctx.get_enode(n); theory_var v = e->get_th_var(get_id()); if (v != null_theory_var) { literal_vector & bits = m_bits[v]; literal_vector::iterator it = bits.begin(); literal_vector::iterator end = bits.end(); for (; it != end; ++it) ctx.mark_as_relevant(*it); } } } void theory_bv::push_scope_eh() { theory::push_scope_eh(); m_trail_stack.push_scope(); } void theory_bv::pop_scope_eh(unsigned num_scopes) { TRACE("bv",tout << num_scopes << "\n";); m_trail_stack.pop_scope(num_scopes); unsigned num_old_vars = get_old_num_vars(num_scopes); m_bits.shrink(num_old_vars); m_wpos.shrink(num_old_vars); m_zero_one_bits.shrink(num_old_vars); theory::pop_scope_eh(num_scopes); } final_check_status theory_bv::final_check_eh() { SASSERT(check_invariant()); if (m_approximates_large_bvs) { return FC_GIVEUP; } return FC_DONE; } void theory_bv::reset_eh() { pop_scope_eh(m_trail_stack.get_num_scopes()); m_bool_var2atom.reset(); m_fixed_var_table.reset(); theory::reset_eh(); } theory_bv::theory_bv(ast_manager & m, theory_bv_params const & params, bit_blaster_params const & bb_params): theory(m.get_family_id("bv")), m_params(params), m_util(m), m_autil(m), m_simplifier(0), m_bb(m, bb_params), m_trail_stack(*this), m_find(*this), m_approximates_large_bvs(false) { } theory_bv::~theory_bv() { } void theory_bv::merge_eh(theory_var r1, theory_var r2, theory_var v1, theory_var v2) { TRACE("bv", tout << "merging: #" << get_enode(v1)->get_owner_id() << " #" << get_enode(v2)->get_owner_id() << "\n";); TRACE("bv_bit_prop", tout << "merging: #" << get_enode(v1)->get_owner_id() << " #" << get_enode(v2)->get_owner_id() << "\n";); if (!merge_zero_one_bits(r1, r2)) { TRACE("bv", tout << "conflict detected\n";); return; // conflict was detected } m_prop_queue.reset(); context & ctx = get_context(); literal_vector & bits1 = m_bits[v1]; literal_vector & bits2 = m_bits[v2]; SASSERT(bits1.size() == bits2.size()); unsigned sz = bits1.size(); bool changed; TRACE("bv", tout << "bits size: " << sz << "\n";); do { // This outerloop is necessary to avoid missing propagation steps. // For example, let's assume that bits1 and bits2 contains the following // sequence of bits: // b4 b3 b2 b1 // b5 b4 b3 b2 // Let's also assume that b1 is assigned, and b2, b3, b4, and b5 are not. // Only the propagation from b1 to b2 is performed by the first iteration of this // loop. // // In the worst case, we need to execute this loop bits1.size() times. // // Remark: the assignment to b2 is marked as a bv theory propagation, // then it is not notified to the bv theory. changed = false; for (unsigned idx = 0; idx < sz; idx++) { literal bit1 = bits1[idx]; literal bit2 = bits2[idx]; CTRACE("bv_bug", bit1 == ~bit2, display_var(tout, v1); display_var(tout, v2); tout << "idx: " << idx << "\n";); SASSERT(bit1 != ~bit2); lbool val1 = ctx.get_assignment(bit1); lbool val2 = ctx.get_assignment(bit2); if (val1 == val2) continue; changed = true; if (val1 != l_undef && val2 != l_undef) { TRACE("bv", tout << "inconsistent "; display_var(tout, v1); display_var(tout, v2); tout << "idx: " << idx << "\n";); } if (val1 != l_undef) { literal antecedent = bit1; literal consequent = bit2; if (val1 == l_false) { consequent.neg(); antecedent.neg(); } assign_bit(consequent, v1, v2, idx, antecedent, true); } else if (val2 != l_undef) { literal antecedent = bit2; literal consequent = bit1; if (val2 == l_false) { consequent.neg(); antecedent.neg(); } assign_bit(consequent, v2, v1, idx, antecedent, true); } if (ctx.inconsistent()) return; if (val1 != l_undef && val2 != l_undef && val1 != val2) { UNREACHABLE(); } } } while(changed); propagate_bits(); } bool theory_bv::merge_zero_one_bits(theory_var r1, theory_var r2) { zero_one_bits & bits2 = m_zero_one_bits[r2]; if (bits2.empty()) return true; zero_one_bits & bits1 = m_zero_one_bits[r1]; unsigned bv_size = get_bv_size(r1); SASSERT(bv_size == get_bv_size(r2)); m_merge_aux[0].reserve(bv_size+1, null_theory_var); m_merge_aux[1].reserve(bv_size+1, null_theory_var); #define RESET_MERGET_AUX() { \ zero_one_bits::iterator it = bits1.begin(); \ zero_one_bits::iterator end = bits1.end(); \ for (; it != end; ++it) \ m_merge_aux[it->m_is_true][it->m_idx] = null_theory_var; \ } DEBUG_CODE(for (unsigned i = 0; i < bv_size; i++) { SASSERT(m_merge_aux[0][i] == null_theory_var || m_merge_aux[1][i] == null_theory_var); }); // save info about bits1 zero_one_bits::iterator it = bits1.begin(); zero_one_bits::iterator end = bits1.end(); for (; it != end; ++it) m_merge_aux[it->m_is_true][it->m_idx] = it->m_owner; // check if bits2 is consistent with bits1, and copy new bits to bits1 it = bits2.begin(); end = bits2.end(); for (; it != end; ++it) { theory_var v2 = it->m_owner; theory_var v1 = m_merge_aux[!it->m_is_true][it->m_idx]; if (v1 != null_theory_var) { // conflict was detected ... v1 and v2 have complementary bits SASSERT(m_bits[v1][it->m_idx] == ~(m_bits[v2][it->m_idx])); mk_new_diseq_axiom(v1, v2, it->m_idx); RESET_MERGET_AUX(); return false; } if (m_merge_aux[it->m_is_true][it->m_idx] == null_theory_var) { // copy missing variable to bits1 bits1.push_back(*it); } } // reset m_merge_aux vector RESET_MERGET_AUX(); DEBUG_CODE(for (unsigned i = 0; i < bv_size; i++) { SASSERT(m_merge_aux[0][i] == null_theory_var || m_merge_aux[1][i] == null_theory_var); }); return true; } class bit_eq_justification : public justification { enode * m_v1; enode * m_v2; theory_id m_th_id; // TODO: steal 4 bits from each one of the following literas and use them to represent the th_id. literal m_consequent; literal m_antecedent; public: bit_eq_justification(theory_id th_id, enode * v1, enode * v2, literal c, literal a): m_v1(v1), m_v2(v2), m_th_id(th_id), m_consequent(c), m_antecedent(a) {} virtual void get_antecedents(conflict_resolution & cr) { cr.mark_eq(m_v1, m_v2); if (m_antecedent.var() != true_bool_var) cr.mark_literal(m_antecedent); } virtual proof * mk_proof(conflict_resolution & cr) { bool visited = true; ptr_buffer prs; proof * pr = cr.get_proof(m_v1, m_v2); if (pr) prs.push_back(pr); else visited = false; if (m_antecedent.var() != true_bool_var) { proof * pr = cr.get_proof(m_antecedent); if (pr) prs.push_back(pr); else visited = false; } if (!visited) return 0; context & ctx = cr.get_context(); ast_manager & m = cr.get_manager(); expr_ref fact(m); ctx.literal2expr(m_consequent, fact); return m.mk_th_lemma(get_from_theory(), fact, prs.size(), prs.c_ptr()); } virtual theory_id get_from_theory() const { return m_th_id; } virtual char const * get_name() const { return "bv-bit-eq"; } }; inline justification * theory_bv::mk_bit_eq_justification(theory_var v1, theory_var v2, literal consequent, literal antecedent) { return get_context().mk_justification(bit_eq_justification(get_id(), get_enode(v1), get_enode(v2), consequent, antecedent)); } void theory_bv::unmerge_eh(theory_var v1, theory_var v2) { // v1 was the root of the equivalence class // I must remove the zero_one_bits that are from v2. // REMARK: it is unsafe to invoke check_zero_one_bits, since // the enode associated with v1 and v2 may have already been // deleted. // // The logical context trail_stack is popped before // the theories pop_scope_eh is invoked. zero_one_bits & bits = m_zero_one_bits[v1]; if (bits.empty()) { // SASSERT(check_zero_one_bits(v1)); // SASSERT(check_zero_one_bits(v2)); return; } unsigned j = bits.size(); while (j > 0) { --j; zero_one_bit & bit = bits[j]; if (find(bit.m_owner) == v1) { bits.shrink(j+1); // SASSERT(check_zero_one_bits(v1)); // SASSERT(check_zero_one_bits(v2)); return; } } bits.shrink(0); // SASSERT(check_zero_one_bits(v1)); // SASSERT(check_zero_one_bits(v2)); } void theory_bv::init_model(model_generator & m) { m_factory = alloc(bv_factory, get_manager()); m.register_factory(m_factory); } model_value_proc * theory_bv::mk_value(enode * n, model_generator & mg) { numeral val; theory_var v = n->get_th_var(get_id()); SASSERT(v != null_theory_var); #ifdef Z3DEBUG bool r = #endif get_fixed_value(v, val); SASSERT(r); return alloc(expr_wrapper_proc, m_factory->mk_value(val, get_bv_size(v))); } void theory_bv::display_var(std::ostream & out, theory_var v) const { out << "v"; out.width(4); out << std::left << v; out << " #"; out.width(4); out << get_enode(v)->get_owner_id() << " -> #"; out.width(4); out << get_enode(find(v))->get_owner_id(); out << std::right << ", bits:"; context & ctx = get_context(); literal_vector const & bits = m_bits[v]; literal_vector::const_iterator it = bits.begin(); literal_vector::const_iterator end = bits.end(); for (; it != end; ++it) { out << " "; ctx.display_literal(out, *it); } numeral val; if (get_fixed_value(v, val)) out << ", value: " << val; out << "\n"; } void theory_bv::display_bit_atom(std::ostream & out, bool_var v, bit_atom const * a) const { context & ctx = get_context(); out << "#" << ctx.bool_var2expr(v)->get_id() << " ->"; var_pos_occ * curr = a->m_occs; while (curr) { out << " #" << get_enode(curr->m_var)->get_owner_id() << "[" << curr->m_idx << "]"; curr = curr->m_next; } out << "\n"; } void theory_bv::display_atoms(std::ostream & out) const { out << "atoms:\n"; context & ctx = get_context(); unsigned num = ctx.get_num_bool_vars(); for (unsigned v = 0; v < num; v++) { atom * a = get_bv2a(v); if (a && a->is_bit()) display_bit_atom(out, v, static_cast(a)); } } void theory_bv::display(std::ostream & out) const { out << "Theory bv:\n"; unsigned num_vars = get_num_vars(); for (unsigned v = 0; v < num_vars; v++) { display_var(out, v); } display_atoms(out); } void theory_bv::collect_statistics(::statistics & st) const { st.update("bv conflicts", m_stats.m_num_conflicts); st.update("bv diseqs", m_stats.m_num_diseq_static); st.update("bv dynamic diseqs", m_stats.m_num_diseq_dynamic); st.update("bv bit2core", m_stats.m_num_bit2core); st.update("bv->core eq", m_stats.m_num_th2core_eq); } #ifdef Z3DEBUG bool theory_bv::check_assignment(theory_var v) const { context & ctx = get_context(); if (!is_root(v)) return true; if (!ctx.is_relevant(get_enode(v))) { return true; } theory_var v2 = v; literal_vector const & bits2 = m_bits[v2]; theory_var v1 = v2; do { literal_vector const & bits1 = m_bits[v1]; SASSERT(bits1.size() == bits2.size()); unsigned sz = bits1.size(); for (unsigned i = 0; i < sz; i++) { literal bit1 = bits1[i]; literal bit2 = bits2[i]; lbool val1 = ctx.get_assignment(bit1); lbool val2 = ctx.get_assignment(bit2); CTRACE("bv_bug", val1 != val2, tout << "equivalence class is inconsistent, i: " << i << "\n"; display_var(tout, v1); display_var(tout, v2); tout << "val1: " << val1 << " lvl: " << ctx.get_assign_level(bit1.var()) << " bit " << bit1 << "\n"; tout << "val2: " << val2 << " lvl: " << ctx.get_assign_level(bit2.var()) << " bit " << bit2 << "\n";); SASSERT(val1 == val2); } SASSERT(ctx.is_relevant(get_enode(v1))); v1 = next(v1); } while (v1 != v); return true; } /** \brief Check whether m_zero_one_bits is an accurate summary of the bits in the equivalence class rooted by v. \remark The method does nothing if v is not the root of the equivalence class. */ bool theory_bv::check_zero_one_bits(theory_var v) const { if (get_context().inconsistent()) return true; // property is only valid if the context is not in a conflict. if (is_root(v) && is_bv(v)) { svector bits[2]; unsigned num_bits = 0; unsigned bv_sz = get_bv_size(v); bits[0].resize(bv_sz, false); bits[1].resize(bv_sz, false); theory_var curr = v; do { literal_vector const & lits = m_bits[curr]; for (unsigned i = 0; i < lits.size(); i++) { literal l = lits[i]; if (l.var() == true_bool_var) { unsigned is_true = (l == true_literal); SASSERT(!bits[!is_true][i]); // no complementary bits if (!bits[is_true][i]) { bits[is_true][i] = true; num_bits++; } } } curr = next(curr); } while (curr != v); zero_one_bits const & _bits = m_zero_one_bits[v]; SASSERT(_bits.size() == num_bits); svector already_found; already_found.resize(bv_sz, false); zero_one_bits::const_iterator it = _bits.begin(); zero_one_bits::const_iterator end = _bits.end(); for (; it != end; ++it) { SASSERT(find(it->m_owner) == v); SASSERT(bits[it->m_is_true][it->m_idx]); SASSERT(!already_found[it->m_idx]); already_found[it->m_idx] = true; } } return true; } bool theory_bv::check_invariant() const { unsigned num = get_num_vars(); for (unsigned v = 0; v < num; v++) { check_assignment(v); check_zero_one_bits(v); } return true; } #endif };