/*** * Bitwuzla: Satisfiability Modulo Theories (SMT) solver. * * Copyright (C) 2020 by the authors listed in the AUTHORS file at * https://github.com/bitwuzla/bitwuzla/blob/main/AUTHORS * * This file is part of Bitwuzla under the MIT license. See COPYING for more * information at https://github.com/bitwuzla/bitwuzla/blob/main/COPYING */ #ifndef BZLALS__TEST__TEST_BVNODE_H #define BZLALS__TEST__TEST_BVNODE_H #include "bv/bitvector.h" #include "bv/bounds/bitvector_bounds.h" #include "bv/domain/bitvector_domain.h" #include "ls/bv/bitvector_node.h" #include "test_lib.h" namespace bzla::ls::test { class TestBvNodeCommon : public ::bzla::test::TestCommon { protected: void SetUp() override { TestCommon::SetUp(); gen_values(TEST_BW, d_values); gen_xvalues(TEST_BW, d_xvalues); d_rng.reset(new RNG(1234)); } BitVector eval_op_binary(NodeKind op_kind, const BitVector& x_val, const BitVector& s_val, uint32_t pos_x) const; static constexpr uint64_t TEST_BW = 4; std::vector d_values; std::vector d_xvalues; std::unique_ptr d_rng; BitVector d_nullbv; BitVectorRange d_nullr; }; BitVector TestBvNodeCommon::eval_op_binary(NodeKind op_kind, const BitVector& x_val, const BitVector& s_val, uint32_t pos_x) const { BitVector res; switch (op_kind) { case NodeKind::BV_ADD: res = pos_x ? s_val.bvadd(x_val) : x_val.bvadd(s_val); break; case NodeKind::BV_AND: res = pos_x ? s_val.bvand(x_val) : x_val.bvand(s_val); break; case NodeKind::BV_ASHR: res = pos_x ? s_val.bvashr(x_val) : x_val.bvashr(s_val); break; case NodeKind::BV_CONCAT: res = pos_x ? s_val.bvconcat(x_val) : x_val.bvconcat(s_val); break; case NodeKind::EQ: res = pos_x ? s_val.bveq(x_val) : x_val.bveq(s_val); break; case NodeKind::BV_MUL: res = pos_x ? s_val.bvmul(x_val) : x_val.bvmul(s_val); break; case NodeKind::BV_SHL: res = pos_x ? s_val.bvshl(x_val) : x_val.bvshl(s_val); break; case NodeKind::BV_SHR: res = pos_x ? s_val.bvshr(x_val) : x_val.bvshr(s_val); break; case NodeKind::BV_SLT: res = pos_x ? s_val.bvslt(x_val) : x_val.bvslt(s_val); break; case NodeKind::BV_UDIV: res = pos_x ? s_val.bvudiv(x_val) : x_val.bvudiv(s_val); break; case NodeKind::BV_ULT: res = pos_x ? s_val.bvult(x_val) : x_val.bvult(s_val); break; case NodeKind::BV_UREM: res = pos_x ? s_val.bvurem(x_val) : x_val.bvurem(s_val); break; case NodeKind::BV_XOR: res = pos_x ? s_val.bvxor(x_val) : x_val.bvxor(s_val); break; default: assert(false); } return res; } /* -------------------------------------------------------------------------- */ class TestBvNode : public TestBvNodeCommon { protected: enum Kind { CONS, INV, IS_CONS, IS_ESS, IS_INV, }; enum BoundsKind { NONE, SIGNED, UNSIGNED, BOTH, }; enum OptimizationKind { DEFAULT, CONCAT, SEXT, }; bool check_sat_binary(Kind kind, NodeKind op_kind, BitVectorNode* op_x, const BitVector& t, const BitVector& s_val, uint32_t pos_x, OptimizationKind opt_kind = DEFAULT) const; bool check_sat_binary_cons(NodeKind op_kind, const BitVector& x_val, const BitVector& t, uint64_t s_size, uint32_t pos_x) const; bool check_sat_binary_is_ess(NodeKind op_kind, const BitVector& x_val, const BitVector& t, const BitVectorDomain& s, uint32_t pos_x) const; bool check_sat_ite(Kind kind, const BitVectorDomain& x, const BitVector& t, const BitVector& s0_val, const BitVector& s1_val, uint32_t pos_x) const; bool check_sat_ite_cons(const BitVector& x_val, const BitVector& t, uint64_t s0_size, uint64_t s1_size, uint32_t pos_x) const; bool check_sat_ite_is_ess(const BitVector& x_val, const BitVector& t, const BitVectorDomain& s0, const BitVector& s0_val_cur, const BitVectorDomain& s1, const BitVector& s1_val_cur, uint32_t pos_x) const; bool check_sat_not(Kind kind, const BitVectorDomain& x, const BitVector& t) const; bool check_sat_extract(Kind kind, const BitVectorDomain& x, const BitVector& t, uint64_t hi, uint64_t lo) const; bool check_sat_sext(Kind kind, const BitVectorDomain& x, const BitVector& t, uint64_t n) const; template void test_binary(Kind kind, NodeKind op_kind, uint32_t pos_x, BoundsKind bounds_kind = NONE, OptimizationKind opt_kind = DEFAULT); void test_ite(Kind kind, uint32_t pos_x); void test_not(Kind kind); void test_extract(Kind kind); void test_sext(Kind kind); void test_normalize_bounds_no_u(); void test_normalize_bounds_no_s(); void test_normalize_bounds_only_hi(); void test_normalize_bounds_only_lo(); void test_normalize_bounds_both(); protected: void test_normalize_bounds(const BitVector& min_u, bool min_u_is_excl, const BitVector& max_u, bool max_u_is_excl, const BitVector& min_s, bool min_s_is_excl, const BitVector& max_s, bool max_s_is_excl, const BitVectorRange& lo_exp, const BitVectorRange& hi_exp); }; bool TestBvNode::check_sat_binary(Kind kind, NodeKind op_kind, BitVectorNode* op_x, const BitVector& t, const BitVector& s_val, uint32_t pos_x, OptimizationKind opt_kind) const { const BitVectorRange& bounds_u = op_x->bounds_u(); const BitVectorRange& bounds_s = op_x->bounds_s(); const BitVectorDomain& x = op_x->domain(); BitVectorDomainGenerator gen(x); do { BitVector res; BitVector x_val = gen.has_next() ? gen.next() : x.lo(); if (kind == IS_CONS) { assert(bounds_u.empty() && bounds_s.empty()); BitVectorDomainGenerator gens(s_val.size()); while (gens.has_next()) { res = eval_op_binary(op_kind, x_val, gens.next(), pos_x); if (t.compare(res) == 0) return true; } } else { assert(kind == IS_INV); if (opt_kind == OptimizationKind::SEXT) { uint64_t n = static_cast(op_x)->get_n(); if (n > 0) { uint64_t bw_x = op_x->size(); uint64_t bw_xx = bw_x - n; BitVector xn = x_val.bvextract(bw_x - 1, bw_xx); BitVector xx = x_val.bvextract(bw_xx - 1, 0); if (!xn.is_zero() && !xn.is_ones()) continue; if (xn.is_zero() && !xx.bvextract(bw_xx - 1, bw_xx - 1).is_zero()) continue; if (xn.is_ones() && !xx.bvextract(bw_xx - 1, bw_xx - 1).is_ones()) continue; } } res = eval_op_binary(op_kind, x_val, s_val, pos_x); if (t.compare(res) == 0) { if (!bounds_u.empty() && !bounds_s.empty()) { if (x_val.compare(bounds_u.d_min) >= 0 && x_val.signed_compare(bounds_s.d_min) >= 0 && x_val.compare(bounds_u.d_max) <= 0 && x_val.signed_compare(bounds_s.d_max) <= 0) { return true; } } else if (!bounds_u.empty()) { if (x_val.compare(bounds_u.d_min) >= 0 && x_val.compare(bounds_u.d_max) <= 0) { return true; } } else if (!bounds_s.empty()) { if (x_val.signed_compare(bounds_s.d_min) >= 0 && x_val.signed_compare(bounds_s.d_max) <= 0) { return true; } } else { return true; } } } } while (gen.has_next()); return false; } bool TestBvNode::check_sat_binary_cons(NodeKind op_kind, const BitVector& x_val, const BitVector& t, uint64_t s_size, uint32_t pos_x) const { BitVectorDomain s(s_size); BitVectorDomainGenerator gen(s); do { BitVector res; BitVector s_val = gen.next(); res = eval_op_binary(op_kind, x_val, s_val, pos_x); if (t.compare(res) == 0) return true; } while (gen.has_next()); return false; } bool TestBvNode::check_sat_binary_is_ess(NodeKind op_kind, const BitVector& x_val, const BitVector& t, const BitVectorDomain& s, uint32_t pos_x) const { BitVectorDomainGenerator gen(s); do { BitVector s_val = gen.has_next() ? gen.next() : s.lo(); BitVector res = eval_op_binary(op_kind, x_val, s_val, pos_x); if (t.compare(res) == 0) return false; } while (gen.has_next()); return true; } bool TestBvNode::check_sat_ite(Kind kind, const BitVectorDomain& x, const BitVector& t, const BitVector& s0_val, const BitVector& s1_val, uint32_t pos_x) const { BitVectorDomainGenerator gen(x); do { BitVector x_val = gen.has_next() ? gen.next() : x.lo(); if (pos_x == 0) { if (kind == IS_CONS) { BitVectorDomainGenerator gens0(s0_val.size()); while (gens0.has_next()) { BitVector s0val = gens0.next(); BitVectorDomainGenerator gens1(s1_val.size()); while (gens1.has_next()) { BitVector res = BitVector::bvite(x_val, s0val, gens1.next()); if (t.compare(res) == 0) return true; } } } else { assert(kind == IS_INV); BitVector res = BitVector::bvite(x_val, s0_val, s1_val); if (t.compare(res) == 0) return true; } } else if (pos_x == 1) { if (kind == IS_CONS) { BitVectorDomainGenerator gens0(s0_val.size()); while (gens0.has_next()) { BitVector s0val = gens0.next(); BitVectorDomainGenerator gens1(s1_val.size()); while (gens1.has_next()) { BitVector res = BitVector::bvite(s0val, x_val, gens1.next()); if (t.compare(res) == 0) return true; } } } else { assert(kind == IS_INV); if (s0_val.is_false() && s1_val.compare(t) != 0) return false; BitVector res = BitVector::bvite(s0_val, x_val, s1_val); if (t.compare(res) == 0) return true; } } else { if (kind == IS_CONS) { BitVectorDomainGenerator gens0(s0_val.size()); while (gens0.has_next()) { BitVector s0val = gens0.next(); BitVectorDomainGenerator gens1(s1_val.size()); while (gens1.has_next()) { BitVector res = BitVector::bvite(s0val, gens1.next(), x_val); if (t.compare(res) == 0) return true; } } } else { assert(kind == IS_INV); if (s0_val.is_true() && s1_val.compare(t) != 0) return false; BitVector res = BitVector::bvite(s0_val, s1_val, x_val); if (t.compare(res) == 0) return true; } } } while (gen.has_next()); return false; } bool TestBvNode::check_sat_ite_is_ess(const BitVector& x_val, const BitVector& t, const BitVectorDomain& s0, const BitVector& s0_val_cur, const BitVectorDomain& s1, const BitVector& s1_val_cur, uint32_t pos_x) const { BitVectorDomainGenerator gens0(s0); BitVectorDomainGenerator gens1(s1); do { BitVector s0_val = gens0.has_next() ? gens0.next() : s0.lo(); BitVectorDomainGenerator gens1(s1); BitVector res; if (pos_x == 0) { res = BitVector::bvite(x_val, s0_val, s1_val_cur); } else if (pos_x == 1) { res = BitVector::bvite(s0_val, x_val, s1_val_cur); } else { res = BitVector::bvite(s0_val, s1_val_cur, x_val); } if (t.compare(res) == 0) return false; } while (gens0.has_next()); do { BitVector s1_val = gens1.has_next() ? gens1.next() : s1.lo(); BitVectorDomainGenerator gens1(s1); BitVector res; if (pos_x == 0) { res = BitVector::bvite(x_val, s0_val_cur, s1_val); } else if (pos_x == 1) { res = BitVector::bvite(s0_val_cur, x_val, s1_val); } else { res = BitVector::bvite(s0_val_cur, s1_val, x_val); } if (t.compare(res) == 0) return false; } while (gens1.has_next()); return true; } bool TestBvNode::check_sat_ite_cons(const BitVector& x_val, const BitVector& t, uint64_t s0_size, uint64_t s1_size, uint32_t pos_x) const { BitVectorDomainGenerator gens0(s0_size); while (gens0.has_next()) { BitVector s0val = gens0.next(); BitVectorDomainGenerator gens1(s1_size); while (gens1.has_next()) { BitVector res; if (pos_x == 0) { res = BitVector::bvite(x_val, s0val, gens1.next()); } else if (pos_x == 1) { res = BitVector::bvite(s0val, x_val, gens1.next()); } else { res = BitVector::bvite(s0val, gens1.next(), x_val); } if (t.compare(res) == 0) return true; } } return false; } bool TestBvNode::check_sat_not(Kind kind, const BitVectorDomain& x, const BitVector& t) const { assert(kind == IS_CONS || kind == IS_ESS || kind == IS_INV); BitVectorDomainGenerator gen(x); do { BitVector val = gen.has_next() ? gen.next() : x.lo(); BitVector res = val.bvnot(); if (t.compare(res) == 0) return kind == IS_ESS ? false : true; } while (gen.has_next()); return kind == IS_ESS ? true : false; } bool TestBvNode::check_sat_extract(Kind kind, const BitVectorDomain& x, const BitVector& t, uint64_t hi, uint64_t lo) const { assert(kind == IS_CONS || kind == IS_ESS || kind == IS_INV); BitVectorDomainGenerator gen(x); do { BitVector val = gen.has_next() ? gen.next() : x.lo(); BitVector res = val.bvextract(hi, lo); if (t.compare(res) == 0) return kind == IS_ESS ? false : true; } while (gen.has_next()); return kind == IS_ESS ? true : false; } bool TestBvNode::check_sat_sext(Kind kind, const BitVectorDomain& x, const BitVector& t, uint64_t n) const { assert(kind == IS_CONS || kind == IS_ESS || kind == IS_INV); BitVectorDomainGenerator gen(x); do { BitVector val = gen.has_next() ? gen.next() : x.lo(); BitVector res = val.bvsext(n); if (t.compare(res) == 0) return kind == IS_ESS ? false : true; } while (gen.has_next()); return kind == IS_ESS ? true : false; } template void TestBvNode::test_binary(Kind kind, NodeKind op_kind, uint32_t pos_x, TestBvNode::BoundsKind bounds_kind, OptimizationKind opt_kind) { uint64_t bw_x = TEST_BW; uint64_t bw_s = TEST_BW; uint64_t bw_t = TEST_BW; if (op_kind == NodeKind::BV_ULT || op_kind == NodeKind::BV_SLT || op_kind == NodeKind::EQ) { bw_t = 1; } else if (op_kind == NodeKind::BV_CONCAT) { bw_s = 2; /* decrease number of tests for concat */ bw_t = bw_s + bw_x; } uint64_t nval_x = 1 << bw_x; uint64_t nval_s = 1 << bw_s; uint64_t nval_t = 1 << bw_t; if (kind == IS_ESS) { std::vector svalues; if (op_kind == NodeKind::BV_CONCAT) { gen_xvalues(bw_s, svalues); } else { svalues = d_xvalues; } for (const std::string& s_value : svalues) { BitVectorDomain s(s_value); for (uint64_t i = 0; i < nval_x; i++) { BitVector x_val = BitVector::from_ui(bw_x, i); for (uint64_t j = 0; j < nval_t; j++) { /* Target value of the operation (op). */ BitVector t = BitVector::from_ui(bw_t, j); /* For this test, we don't care about the current assignment of s, * thus we initialize it with a random value that matches constant * bits in s. */ BitVector s_val = s.lo(); if (!s.is_fixed()) { BitVectorDomainGenerator gen(s, d_rng.get()); s_val = gen.random(); } /* For this test, the domain of x is irrelevant, hence we * initialize it with an unconstrained domain. */ std::unique_ptr op_x( new BitVectorNode(d_rng.get(), x_val, BitVectorDomain(bw_x))); std::unique_ptr op_s( new BitVectorNode(d_rng.get(), s_val, s)); /* For this test, we don't care about current assignment and domain of * the op, thus we initialize them with 0 and x..x, respectively. */ T op(d_rng.get(), bw_t, pos_x == 0 ? op_x.get() : op_s.get(), pos_x == 1 ? op_x.get() : op_s.get()); bool res = op.is_essential(t, pos_x); bool status = check_sat_binary_is_ess(op_kind, x_val, t, s, pos_x); if (res != status) { std::cout << "pos_x: " << pos_x << std::endl; std::cout << "t: " << t << std::endl; std::cout << "x: " << x_val << std::endl; std::cout << "s: " << s << ": " << s_val << std::endl; } ASSERT_EQ(res, status); } } } } else { for (const std::string& x_value : d_xvalues) { BitVectorDomain x(x_value); for (uint64_t i = 0; i < nval_s; i++) { /* Assignment of the other operand. */ BitVector s_val = BitVector::from_ui(bw_s, i); for (uint64_t j = 0; j < nval_t; j++) { /* Target value of the operation (op). */ BitVector t = BitVector::from_ui(bw_t, j); /* For this test, we don't care about the current assignment of x, * thus we initialize it with a random value that matches constant * bits in x. */ BitVector x_val = x.lo(); if (!x.is_fixed()) { BitVectorDomainGenerator gen(x, d_rng.get()); x_val = gen.random(); } bool use_bounds = bounds_kind != NONE; uint32_t n_tests = 0; BitVector min, max; do { std::unique_ptr op_x; std::unique_ptr child0, child1; if (opt_kind == CONCAT) { uint64_t bw_x0 = d_rng->pick(1, bw_x - 1); uint64_t bw_x1 = bw_x - bw_x0; child0.reset(new BitVectorNode(d_rng.get(), bw_x0)); child1.reset(new BitVectorNode(d_rng.get(), bw_x1)); op_x.reset(new BitVectorConcat( d_rng.get(), x, child0.get(), child1.get())); op_x->set_assignment(x_val); } else if (opt_kind == SEXT) { uint64_t n = d_rng->pick(0, bw_x - 1); uint64_t bw_xx = bw_x - n; child0.reset(new BitVectorNode(d_rng.get(), bw_xx)); op_x.reset( new BitVectorSignExtend(d_rng.get(), x, child0.get(), n)); op_x->set_assignment(x_val); } else { op_x.reset(new BitVectorNode(d_rng.get(), x_val, x)); } /* For this test, we don't care about the domain of s, thus we * initialize it with an unconstrained domain. */ BitVectorDomain s(bw_s); std::unique_ptr op_s( new BitVectorNode(d_rng.get(), s_val, s)); /* For this test, we don't care about current assignment and domain * of the op, thus we initialize them with 0 and x..x, * respectively. */ std::unique_ptr op; if (opt_kind == DEFAULT) { op.reset(new T(d_rng.get(), bw_t, pos_x == 0 ? op_x.get() : op_s.get(), pos_x == 1 ? op_x.get() : op_s.get())); } else { if (op_kind == NodeKind::BV_ULT) { op.reset( new BitVectorUlt(d_rng.get(), bw_t, pos_x == 0 ? op_x.get() : op_s.get(), pos_x == 1 ? op_x.get() : op_s.get(), opt_kind == CONCAT || opt_kind == SEXT)); } else { assert(op_kind == NodeKind::BV_SLT); op.reset( new BitVectorSlt(d_rng.get(), bw_t, pos_x == 0 ? op_x.get() : op_s.get(), pos_x == 1 ? op_x.get() : op_s.get(), opt_kind == CONCAT || opt_kind == SEXT)); } } if (bounds_kind != NONE) { bool is_signed = bounds_kind == SIGNED; min = BitVector(bw_x, *d_rng.get()); if (is_signed) { max = BitVector(bw_x, *d_rng.get(), min, is_signed ? BitVector::mk_max_signed(bw_x) : BitVector::mk_ones(bw_x), is_signed); } else { max = BitVector( bw_x, *d_rng.get(), min, BitVector::mk_ones(bw_x), false); if (bounds_kind == BOTH) { op_x->update_bounds(min, max, false, false, false); is_signed = true; min = BitVector(bw_x, *d_rng.get()); max = BitVector(bw_x, *d_rng.get(), min, BitVector::mk_max_signed(bw_x), is_signed); } } op_x->update_bounds(min, max, false, false, is_signed); } if (kind == IS_CONS || kind == IS_INV) { bool res = kind == IS_INV ? op->is_invertible(t, pos_x) : op->is_consistent(t, pos_x); bool status = check_sat_binary( kind, op_kind, op_x.get(), t, s_val, pos_x, opt_kind); if (res != status) { std::cout << "pos_x: " << pos_x << std::endl; std::cout << "t: " << t << std::endl; std::cout << "x: " << x_value << ": " << x_val << std::endl; std::cout << "s: " << s_val << std::endl; if (opt_kind != DEFAULT && op_x->kind() == NodeKind::BV_SEXT) { std::cout << "n: " << static_cast(op_x.get())->get_n() << std::endl; } std::cout << "bounds_u: " << (op_x->bounds_u().empty() ? "(nil)" : op_x->bounds_u().str()) << std::endl; std::cout << "bounds_s: " << (op_x->bounds_s().empty() ? "(nil)" : op_x->bounds_s().str()) << std::endl; } assert(res == status); ASSERT_EQ(res, status); } else if (kind == INV) { if (!op->is_invertible(t, pos_x)) continue; BitVector inv = op->inverse_value(t, pos_x); int32_t cmp = t.compare(eval_op_binary(op_kind, inv, s_val, pos_x)); if (cmp != 0) { std::cout << "pos_x: " << pos_x << std::endl; std::cout << "t: " << t << std::endl; std::cout << "x: " << x_value << ": " << x_val << std::endl; std::cout << "s: " << s_val << std::endl; std::cout << "inverse: " << inv << std::endl; } ASSERT_EQ(cmp, 0); ASSERT_TRUE(op->is_consistent(t, pos_x)); } else { assert(kind == CONS); if (!op->is_consistent(t, pos_x)) continue; BitVector cons = op->consistent_value(t, pos_x); bool status = check_sat_binary_cons(op_kind, cons, t, s_val.size(), pos_x); if (!status) { std::cout << "pos_x: " << pos_x << std::endl; std::cout << "t: " << t << std::endl; std::cout << "x: " << x_value << ": " << x_val << std::endl; std::cout << "consistent: " << cons << std::endl; } ASSERT_TRUE(status); } } while (use_bounds && ++n_tests < 10); } } } } } void TestBvNode::test_ite(Kind kind, uint32_t pos_x) { uint64_t bw_s0, bw_s1, bw_x, bw_t = TEST_BW; uint64_t n_vals, n_vals_s0, n_vals_s1; if (pos_x) { bw_x = TEST_BW; bw_s0 = 1; bw_s1 = TEST_BW; } else { bw_x = 1; bw_s0 = TEST_BW; bw_s1 = TEST_BW; } n_vals = 1 << bw_x; n_vals_s0 = 1 << bw_s0; n_vals_s1 = 1 << bw_s1; if (kind == IS_ESS) { std::vector s0_values, s1_values; if (pos_x) { s0_values.push_back("x"); s0_values.push_back("0"); s0_values.push_back("1"); } else { s0_values = d_xvalues; } s1_values = d_xvalues; for (const std::string& s0_value : s0_values) { BitVectorDomain s0(s0_value); for (const std::string& s1_value : s1_values) { BitVectorDomain s1(s1_value); for (uint64_t i = 0; i < n_vals; i++) { BitVector x_val = BitVector::from_ui(bw_x, i); for (uint64_t j = 0; j < n_vals; j++) { BitVector t = BitVector::from_ui(bw_t, j); /* For this test, the domain of x is irrelevant, hence we * initialize it with an unconstrained domain. */ std::unique_ptr op_x( new BitVectorNode(d_rng.get(), x_val, BitVectorDomain(bw_x))); /* For this test, we don't care about the current assignment of s0 * and s1, hence we use a random value. */ BitVector s0_val = s0.lo(), s1_val = s1.lo(); if (!s0.is_fixed()) { BitVectorDomainGenerator gen(s0, d_rng.get()); s0_val = gen.random(); } if (!s1.is_fixed()) { BitVectorDomainGenerator gen(s1, d_rng.get()); s1_val = gen.random(); } std::unique_ptr op_s0( new BitVectorNode(d_rng.get(), s0_val, s0)); std::unique_ptr op_s1( new BitVectorNode(d_rng.get(), s1_val, s1)); /* For this test, we don't care about current assignment and domain * of the op, thus we initialize them with 0 and x..x, * respectively. */ BitVectorIte op(d_rng.get(), bw_t, pos_x == 0 ? op_x.get() : op_s0.get(), pos_x == 1 ? op_x.get() : (pos_x == 2 ? op_s1.get() : op_s0.get()), pos_x == 2 ? op_x.get() : op_s1.get()); bool res = op.is_essential(t, pos_x); bool status = check_sat_ite_is_ess(x_val, t, s0, s0_val, s1, s1_val, pos_x); if (res != status) { std::cout << "pos_x: " << pos_x << std::endl; std::cout << "t: " << t << std::endl; std::cout << "x: " << x_val << std::endl; std::cout << "s0: " << s0 << ": " << s0_val << std::endl; std::cout << "s1: " << s1 << ": " << s1_val << std::endl; } ASSERT_EQ(res, status); } } } } } else { std::vector x_values; if (pos_x) { x_values = d_xvalues; } else { x_values.push_back("x"); x_values.push_back("0"); x_values.push_back("1"); } for (const std::string& x_value : x_values) { BitVectorDomain x(x_value); for (uint64_t i = 0; i < n_vals_s0; i++) { BitVector s0_val = BitVector::from_ui(bw_s0, i); for (uint64_t j = 0; j < n_vals_s1; j++) { BitVector s1_val = BitVector::from_ui(bw_s1, j); for (uint64_t k = 0; k < n_vals; k++) { BitVector t = BitVector::from_ui(bw_t, k); /* For this test, we don't care about the current assignment of x, * thus we initialize it with a random value that matches constant * bits in x. */ BitVector x_val = x.lo(); if (!x.is_fixed()) { BitVectorDomainGenerator gen(x, d_rng.get()); x_val = gen.random(); } std::unique_ptr op_x( new BitVectorNode(d_rng.get(), x_val, x)); /* For this test, we don't care about the domains of s0 and s1, * hence we initialize them with unconstrained domains. */ std::unique_ptr op_s0( new BitVectorNode(d_rng.get(), s0_val, BitVectorDomain(bw_s0))); std::unique_ptr op_s1( new BitVectorNode(d_rng.get(), s1_val, BitVectorDomain(bw_s1))); /* For this test, we don't care about current assignment and domain * of the op, thus we initialize them with 0 and x..x, * respectively. */ BitVectorIte op(d_rng.get(), bw_t, pos_x == 0 ? op_x.get() : op_s0.get(), pos_x == 1 ? op_x.get() : (pos_x == 2 ? op_s1.get() : op_s0.get()), pos_x == 2 ? op_x.get() : op_s1.get()); if (kind == IS_INV || kind == IS_CONS) { bool res = kind == IS_INV ? op.is_invertible(t, pos_x) : op.is_consistent(t, pos_x); bool status = check_sat_ite(kind, x, t, s0_val, s1_val, pos_x); if (res != status) { std::cout << "pos_x: " << pos_x << std::endl; std::cout << "t: " << t << std::endl; std::cout << "x: " << x_value << ": " << x_val << std::endl; std::cout << "s0: " << s0_val << std::endl; std::cout << "s1: " << s1_val << std::endl; } ASSERT_EQ(res, status); } else if (kind == INV) { if (x.is_fixed()) continue; if (!op.is_invertible(t, pos_x)) continue; BitVector inv = op.inverse_value(t, pos_x); int32_t cmp; if (pos_x == 0) { cmp = t.compare(BitVector::bvite(inv, s0_val, s1_val)); } else if (pos_x == 1) { cmp = t.compare(BitVector::bvite(s0_val, inv, s1_val)); } else { cmp = t.compare(BitVector::bvite(s0_val, s1_val, inv)); } if (cmp != 0) { std::cout << "pos_x: " << pos_x << std::endl; std::cout << "t: " << t << std::endl; std::cout << "x: " << x_value << ": " << x_val << std::endl; std::cout << "s0: " << s0_val << std::endl; std::cout << "s1: " << s1_val << std::endl; std::cout << "inverse: " << inv << std::endl; } ASSERT_EQ(cmp, 0); ASSERT_TRUE(op.is_consistent(t, pos_x)); } else { assert(kind == CONS); if (x.is_fixed()) continue; if (!op.is_consistent(t, pos_x)) continue; BitVector cons = op.consistent_value(t, pos_x); bool status = check_sat_ite_cons( cons, t, s0_val.size(), s1_val.size(), pos_x); if (!status) { std::cout << "pos_x: " << pos_x << std::endl; std::cout << "t: " << t << std::endl; std::cout << "x: " << x_value << ": " << x_val << std::endl; std::cout << "consistent: " << cons << std::endl; } ASSERT_TRUE(status); } } } } } } } void TestBvNode::test_not(Kind kind) { for (const std::string& x_value : d_xvalues) { BitVectorDomain x(x_value); uint64_t bw_t = x.size(); for (uint64_t i = 0, n = 1 << bw_t; i < n; ++i) { BitVector t = BitVector::from_ui(bw_t, i); /* For this test, we don't care about the current assignment of x, * thus we initialize it with a random value that matches constant * bits in x. */ BitVector x_val = x.lo(); if (!x.is_fixed()) { BitVectorDomainGenerator gen(x, d_rng.get()); x_val = gen.random(); } std::unique_ptr op_x( new BitVectorNode(d_rng.get(), x_val, x)); /* For this test, we don't care about current assignment and domain * of the op, thus we initialize them with 0 and 'x..x', * respectively. */ BitVectorNot op(d_rng.get(), bw_t, op_x.get()); if (kind == IS_CONS || kind == IS_ESS || kind == IS_INV) { bool res = kind == IS_ESS ? op.is_essential(t, 0) : (kind == IS_INV ? op.is_invertible(t, 0) : op.is_consistent(t, 0)); bool status = check_sat_not(kind, x, t); if (res != status) { std::cout << "t: " << t << std::endl; std::cout << "x: " << x_value << ": " << x_val << std::endl; } ASSERT_EQ(res, status); } else { assert(kind == INV || kind == CONS); if (x.is_fixed()) continue; if (kind == INV && !op.is_invertible(t, 0)) continue; if (kind == CONS && !op.is_consistent(t, 0)) continue; if (kind == INV) { BitVector inv = op.inverse_value(t, 0); int32_t cmp = t.compare(inv.bvnot()); if (cmp != 0) { std::cout << "t: " << t << std::endl; std::cout << "x: " << x_value << ": " << x_val << std::endl; std::cout << "inverse: " << inv << std::endl; } ASSERT_EQ(cmp, 0); } else { BitVector cons = op.consistent_value(t, 0); int32_t cmp = t.compare(cons.bvnot()); if (cmp != 0) { std::cout << "t: " << t << std::endl; std::cout << "x: " << x_value << ": " << x_val << std::endl; std::cout << "consistent: " << cons << std::endl; } ASSERT_EQ(cmp, 0); } ASSERT_TRUE(op.is_consistent(t, 0)); } } } } void TestBvNode::test_extract(Kind kind) { uint64_t bw_x = TEST_BW; for (const std::string& x_value : d_xvalues) { BitVectorDomain x(x_value); for (uint64_t lo = 0; lo < bw_x; ++lo) { for (uint64_t hi = lo; hi < bw_x; ++hi) { uint64_t bw_t = hi - lo + 1; for (uint64_t i = 0, n = 1 << bw_t; i < n; ++i) { BitVector t = BitVector::from_ui(bw_t, i); /* For this test, we don't care about the current assignment of x, * thus we initialize it with a random value that matches constant * bits in x. */ BitVector x_val = x.lo(); if (!x.is_fixed()) { BitVectorDomainGenerator gen(x, d_rng.get()); x_val = gen.random(); } std::unique_ptr op_x( new BitVectorNode(d_rng.get(), x_val, x)); /* For this test, we don't care about current assignment and domain * of the op, thus we initialize them with 0 and 'x..x', * respectively. */ BitVectorExtract op(d_rng.get(), bw_t, op_x.get(), hi, lo, false); if (kind == IS_CONS || kind == IS_ESS || kind == IS_INV) { bool res = kind == IS_ESS ? op.is_essential(t, 0) : (kind == IS_INV ? op.is_invertible(t, 0) : op.is_consistent(t, 0)); bool status = check_sat_extract(kind, x, t, hi, lo); if (res != status) { std::cout << "hi: " << hi << std::endl; std::cout << "lo: " << lo << std::endl; std::cout << "t: " << t << std::endl; std::cout << "x: " << x_value << ": " << x_val << std::endl; } ASSERT_EQ(res, status); } else { assert(kind == INV || kind == CONS); if (x.is_fixed()) continue; if (kind == INV && !op.is_invertible(t, 0)) continue; if (kind == CONS && !op.is_consistent(t, 0)) continue; if (kind == INV) { BitVector inv = op.inverse_value(t, 0); int32_t cmp = t.compare(inv.bvextract(hi, lo)); if (cmp != 0) { std::cout << "hi: " << hi << std::endl; std::cout << "lo: " << lo << std::endl; std::cout << "t: " << t << std::endl; std::cout << "x: " << x_value << ": " << x_val << std::endl; std::cout << "inverse: " << inv << std::endl; } ASSERT_EQ(cmp, 0); } else { BitVector cons = op.consistent_value(t, 0); int32_t cmp = t.compare(cons.bvextract(hi, lo)); if (cmp != 0) { std::cout << "hi: " << hi << std::endl; std::cout << "lo: " << lo << std::endl; std::cout << "t: " << t << std::endl; std::cout << "x: " << x_value << ": " << x_val << std::endl; std::cout << "consistent: " << cons << std::endl; } ASSERT_EQ(cmp, 0); } ASSERT_TRUE(op.is_consistent(t, 0)); } } } } } } void TestBvNode::test_sext(Kind kind) { uint64_t bw_x = TEST_BW; for (const std::string& x_value : d_xvalues) { BitVectorDomain x(x_value); for (uint64_t n = 1; n <= bw_x; ++n) { uint64_t bw_t = bw_x + n; for (uint64_t i = 0, m = 1 << bw_t; i < m; ++i) { BitVector t = BitVector::from_ui(bw_t, i); /* For this test, we don't care about the current assignment of x, * thus we initialize it with a random value that matches constant * bits in x. */ BitVector x_val = x.lo(); if (!x.is_fixed()) { BitVectorDomainGenerator gen(x, d_rng.get()); x_val = gen.random(); } std::unique_ptr op_x( new BitVectorNode(d_rng.get(), x_val, x)); /* For this test, we don't care about current assignment and domain * of the op, thus we initialize them with 0 and 'x..x', * respectively. */ BitVectorSignExtend op(d_rng.get(), bw_t, op_x.get(), n); if (kind == IS_CONS || kind == IS_ESS || kind == IS_INV) { bool res = kind == IS_ESS ? op.is_essential(t, 0) : (kind == IS_INV ? op.is_invertible(t, 0) : op.is_consistent(t, 0)); bool status = check_sat_sext(kind, x, t, n); if (res != status) { std::cout << "n: " << n << std::endl; std::cout << "t: " << t << std::endl; std::cout << "x: " << x_value << ": " << x_val << std::endl; } ASSERT_EQ(res, status); } else { assert(kind == INV || kind == CONS); if (x.is_fixed()) continue; if (kind == INV && !op.is_invertible(t, 0)) continue; if (kind == CONS && !op.is_consistent(t, 0)) continue; if (kind == INV) { BitVector inv = op.inverse_value(t, 0); int32_t cmp = t.compare(inv.bvsext(n)); if (cmp != 0) { std::cout << "n: " << n << std::endl; std::cout << "t: " << t << std::endl; std::cout << "x: " << x_value << ": " << x_val << std::endl; std::cout << "inverse: " << inv << std::endl; } ASSERT_EQ(cmp, 0); } else { BitVector cons = op.consistent_value(t, 0); int32_t cmp = t.compare(cons.bvsext(n)); if (cmp != 0) { std::cout << "n: " << n << std::endl; std::cout << "t: " << t << std::endl; std::cout << "x: " << x_value << ": " << x_val << std::endl; std::cout << "consistent: " << cons << std::endl; } ASSERT_EQ(cmp, 0); } ASSERT_TRUE(op.is_consistent(t, 0)); } } } } } } // namespace bzla::ls::test #endif