/*++ Copyright (c) 2006 Microsoft Corporation Module Name: imdd.cpp Abstract: Interval based Multiple-valued Decision Diagrams. Author: Leonardo de Moura (leonardo) 2010-10-13. Revision History: --*/ #include"imdd.h" #include"map.h" #define DEFAULT_SIMPLE_MAX_ENTRIES 32 inline bool is_cached(imdd2imdd_cache & cache, imdd * d, imdd * & r) { if (d->is_shared()) { if (cache.find(d, r) && (r == 0 || !r->is_dead())) return true; } return false; } inline void cache_result(imdd2imdd_cache & cache, imdd * d, imdd * r) { if (d->is_shared()) cache.insert(d, r); } inline bool is_cached(imdd_pair2imdd_cache & cache, imdd * d1, imdd * d2, imdd * & r) { if (d1->is_shared() && d2->is_shared()) { if (cache.find(d1, d2, r) && (r == 0 || !r->is_dead())) return true; } return false; } inline void cache_result(imdd_pair2imdd_cache & cache, imdd * d1, imdd * d2, imdd * r) { if (d1->is_shared() && d2->is_shared()) cache.insert(d1, d2, r); } inline bool destructive_update_at(bool destructive, imdd * d) { return destructive && !d->is_memoized() && !d->is_shared(); } void sl_imdd_manager::inc_ref_eh(imdd * v) { m_manager->inc_ref(v); } void sl_imdd_manager::dec_ref_eh(imdd * v) { m_manager->dec_ref(v); } unsigned imdd::hc_hash() const { unsigned r = 0; r = get_arity(); imdd_children::iterator it = begin_children(); imdd_children::iterator end = end_children(); for (; it != end; ++it) { unsigned b = it->begin_key(); unsigned e = it->end_key(); imdd const * child = it->val(); mix(b, e, r); if (child) { SASSERT(child->is_memoized()); unsigned v = child->get_id(); mix(v, v, r); } } return r; } bool imdd::hc_equal(imdd const * other) const { if (m_arity != other->m_arity) return false; return m_children.is_equal(other->m_children); } imdd_manager::delay_dealloc::~delay_dealloc() { SASSERT(m_manager.m_to_delete.size() >= m_to_delete_size); ptr_vector::iterator it = m_manager.m_to_delete.begin() + m_to_delete_size; ptr_vector::iterator end = m_manager.m_to_delete.end(); for (; it != end; ++it) { SASSERT((*it)->is_dead()); m_manager.deallocate_imdd(*it); } m_manager.m_to_delete.shrink(m_to_delete_size); m_manager.m_delay_dealloc = m_delay_dealloc_value; } imdd_manager::imdd_manager(): m_sl_manager(m_alloc), m_simple_max_entries(DEFAULT_SIMPLE_MAX_ENTRIES), m_delay_dealloc(false) { m_sl_manager.m_manager = this; m_swap_new_child = 0; } imdd * imdd_manager::_mk_empty(unsigned arity) { SASSERT(arity > 0); void * mem = m_alloc.allocate(sizeof(imdd)); return new (mem) imdd(m_sl_manager, m_id_gen.mk(), arity); } imdd * imdd_manager::defrag_core(imdd * d) { if (d->is_memoized()) return d; unsigned h = d->get_arity(); if (h == 1 && !is_simple_node(d)) return d; imdd * new_d = 0; if (is_cached(m_defrag_cache, d, new_d)) return new_d; if (h == 1) { SASSERT(is_simple_node(d)); imdd * new_can_d = memoize(d); cache_result(m_defrag_cache, d, new_can_d); return new_can_d; } SASSERT(h > 1); new_d = _mk_empty(h); imdd_children::push_back_proc push_back(m_sl_manager, new_d->m_children); bool has_new = false; bool children_memoized = true; imdd_children::iterator it = d->begin_children(); imdd_children::iterator end = d->end_children(); for (; it != end; ++it) { imdd * child = it->val(); imdd * new_child = defrag_core(child); if (child != new_child) has_new = true; if (!new_child->is_memoized()) children_memoized = false; push_back(it->begin_key(), it->end_key(), new_child); } if (has_new) { if (children_memoized && is_simple_node(new_d)) { imdd * new_can_d = memoize(new_d); if (new_can_d != new_d) { SASSERT(new_d->get_ref_count() == 0); delete_imdd(new_d); } new_d = new_can_d; } } else { SASSERT(!has_new); delete_imdd(new_d); new_d = d; if (children_memoized && is_simple_node(new_d)) { new_d = memoize(new_d); } } cache_result(m_defrag_cache, d, new_d); return new_d; } /** \brief Compress the given IMDD by using hash-consing. */ void imdd_manager::defrag(imdd_ref & d) { delay_dealloc delay(*this); m_defrag_cache.reset(); d = defrag_core(d); } /** \brief Memoize the given IMDD. Return an IMDD structurally equivalent to d. This method assumes the children of d are memoized. If that is not the case, the user should invoke defrag instead. */ imdd * imdd_manager::memoize(imdd * d) { if (d->is_memoized()) return d; unsigned h = d->get_arity(); m_tables.reserve(h); DEBUG_CODE({ if (h > 1) { imdd_children::iterator it = d->begin_children(); imdd_children::iterator end = d->end_children(); for (; it != end; ++it) { SASSERT(it->val()->is_memoized()); } } }); imdd * r = m_tables[h-1].insert_if_not_there(d); if (r == d) { r->mark_as_memoized(); } SASSERT(r->is_memoized()); return r; } /** \brief Remove the given IMDD from the hash-consing table */ void imdd_manager::unmemoize(imdd * d) { SASSERT(d->is_memoized()); m_tables[d->get_arity()-1].erase(d); d->mark_as_memoized(false); } /** \brief Remove the given IMDD (and its children) from the hash-consing tables. */ void imdd_manager::unmemoize_rec(imdd * d) { SASSERT(m_worklist.empty()); m_worklist.push_back(d); while (!m_worklist.empty()) { d = m_worklist.back(); if (d->is_memoized()) { unmemoize(d); SASSERT(!d->is_memoized()); if (d->get_arity() > 1) { imdd_children::iterator it = d->begin_children(); imdd_children::iterator end = d->end_children(); for (; it != end; ++it) m_worklist.push_back(it->val()); } } } } bool imdd_manager::is_simple_node(imdd * d) const { return !d->m_children.has_more_than_k_entries(m_simple_max_entries); } void imdd_manager::mark_as_dead(imdd * d) { // The references to the children were decremented by delete_imdd. SASSERT(!d->is_dead()); d->m_children.deallocate_no_decref(m_sl_manager); d->mark_as_dead(); if (m_delay_dealloc) m_to_delete.push_back(d); else deallocate_imdd(d); } void imdd_manager::deallocate_imdd(imdd * d) { SASSERT(d->is_dead()); memset(d, 0, sizeof(*d)); m_alloc.deallocate(sizeof(imdd), d); } void imdd_manager::delete_imdd(imdd * d) { SASSERT(m_worklist.empty()); m_worklist.push_back(d); while (!m_worklist.empty()) { d = m_worklist.back(); m_worklist.pop_back(); m_id_gen.recycle(d->get_id()); SASSERT(d->get_ref_count() == 0); if (d->is_memoized()) { unsigned arity = d->get_arity(); SASSERT(m_tables[arity-1].contains(d)); m_tables[arity-1].erase(d); } if (d->get_arity() > 1) { imdd_children::iterator it = d->begin_children(); imdd_children::iterator end = d->end_children(); for (; it != end; ++it) { imdd * child = it->val(); SASSERT(child); child->dec_ref(); if (child->get_ref_count() == 0) m_worklist.push_back(child); } } mark_as_dead(d); } } /** \brief Return a (non-memoized) shallow copy of d. */ imdd * imdd_manager::copy_main(imdd * d) { imdd * d_copy = _mk_empty(d->get_arity()); d_copy->m_children.copy(m_sl_manager, d->m_children); SASSERT(!d_copy->is_memoized()); SASSERT(d_copy->get_ref_count() == 0); return d_copy; } /** \brief Insert the values [b, e] into a IMDD of arity 1. If destructive == true, d is not memoized and is not shared, then a destructive update is performed, and d is returned. Otherwise, a fresh IMDD is returned. */ imdd * imdd_manager::insert_main(imdd * d, unsigned b, unsigned e, bool destructive, bool memoize_res) { SASSERT(d->get_arity() == 1); if (destructive_update_at(destructive, d)) { add_child(d, b, e, 0); return d; } else { imdd * new_d = copy_main(d); add_child(new_d, b, e, 0); return memoize_new_imdd_if(memoize_res, new_d); } } /** \brief Remove the values [b, e] from an IMDD of arity 1. If destructive == true, d is not memoized and is not shared, then a destructive update is performed, and d is returned. Otherwise, a fresh IMDD is returned. */ imdd * imdd_manager::remove_main(imdd * d, unsigned b, unsigned e, bool destructive, bool memoize_res) { SASSERT(d->get_arity() == 1); if (destructive_update_at(destructive, d)) { remove_child(d, b, e); return d; } else { imdd * new_d = copy_main(d); remove_child(new_d, b, e); return memoize_new_imdd_if(memoize_res, new_d); } } /** \brief Auxiliary functor used to implement destructive version of mk_product. */ struct imdd_manager::null2imdd_proc { imdd * m_d2; null2imdd_proc(imdd * d2):m_d2(d2) {} imdd * operator()(imdd * d) { SASSERT(d == 0); return m_d2; } }; /** \brief Auxiliary functor used to implement destructive version of mk_product. */ struct imdd_manager::mk_product_proc { imdd_manager & m_manager; imdd * m_d2; bool m_memoize; mk_product_proc(imdd_manager & m, imdd * d2, bool memoize): m_manager(m), m_d2(d2), m_memoize(memoize) { } imdd * operator()(imdd * d1) { return m_manager.mk_product_core(d1, m_d2, true, m_memoize); } }; imdd * imdd_manager::mk_product_core(imdd * d1, imdd * d2, bool destructive, bool memoize_res) { SASSERT(!d1->empty()); SASSERT(!d2->empty()); if (destructive && !d1->is_shared()) { if (d1->is_memoized()) unmemoize(d1); if (d1->get_arity() == 1) { null2imdd_proc f(d2); d1->m_children.update_values(m_sl_manager, f); d1->m_arity += d2->m_arity; } else { mk_product_proc f(*this, d2, memoize_res); d1->m_children.update_values(m_sl_manager, f); d1->m_arity += d2->m_arity; } return d1; } else { imdd * new_d1 = 0; if (is_cached(m_mk_product_cache, d1, new_d1)) return new_d1; unsigned arity1 = d1->get_arity(); unsigned arity2 = d2->get_arity(); new_d1 = _mk_empty(arity1 + arity2); imdd_children::push_back_proc push_back(m_sl_manager, new_d1->m_children); imdd_children::iterator it = d1->begin_children(); imdd_children::iterator end = d1->end_children(); bool children_memoized = true; for (; it != end; ++it) { imdd * new_child; if (arity1 == 1) new_child = d2; else new_child = mk_product_core(it->val(), d2, false, memoize_res); if (!new_child->is_memoized()) children_memoized = false; push_back(it->begin_key(), it->end_key(), new_child); } new_d1 = memoize_new_imdd_if(memoize_res && children_memoized, new_d1); cache_result(m_mk_product_cache, d1, new_d1); return new_d1; } } imdd * imdd_manager::mk_product_main(imdd * d1, imdd * d2, bool destructive, bool memoize_res) { if (d1->empty() || d2->empty()) { unsigned a1 = d1->get_arity(); unsigned a2 = d2->get_arity(); return _mk_empty(a1 + a2); } delay_dealloc delay(*this); if (d1 == d2) destructive = false; m_mk_product_cache.reset(); return mk_product_core(d1, d2, destructive, memoize_res); } void imdd_manager::init_add_facts_new_children(unsigned num, unsigned const * lowers, unsigned const * uppers, bool memoize_res) { if (m_add_facts_new_children[num] != 0) { DEBUG_CODE({ for (unsigned i = 1; i <= num; i++) { SASSERT(m_add_facts_new_children[i] != 0); }}); return; } for (unsigned i = 1; i <= num; i++) { if (m_add_facts_new_children[i] == 0) { imdd * new_child = _mk_empty(i); unsigned b = lowers[num - i]; unsigned e = uppers[num - i]; bool prev_memoized = true; if (i == 1) { add_child(new_child, b, e, 0); } else { SASSERT(m_add_facts_new_children[i-1] != 0); prev_memoized = m_add_facts_new_children[i-1]->is_memoized(); add_child(new_child, b, e, m_add_facts_new_children[i-1]); } new_child = memoize_new_imdd_if(memoize_res && prev_memoized, new_child); m_add_facts_new_children[i] = new_child; } } DEBUG_CODE({ for (unsigned i = 1; i <= num; i++) { SASSERT(m_add_facts_new_children[i] != 0); }}); } imdd * imdd_manager::add_facts_core(imdd * d, unsigned num, unsigned const * lowers, unsigned const * uppers, bool destructive, bool memoize_res) { SASSERT(d->get_arity() == num); imdd_ref new_child(*this); bool new_children_memoized = true; #define INIT_NEW_CHILD() { \ if (new_child == 0) { \ init_add_facts_new_children(num - 1, lowers + 1, uppers + 1, memoize_res); \ new_child = m_add_facts_new_children[num-1]; \ if (!new_child->is_memoized()) new_children_memoized = false; \ }} if (destructive_update_at(destructive, d)) { if (num == 1) return insert_main(d, *lowers, *uppers, destructive, memoize_res); SASSERT(num > 1); sbuffer to_insert; new_child = m_add_facts_new_children[num-1]; unsigned b = *lowers; unsigned e = *uppers; imdd_children::iterator it = d->m_children.find_geq(b); imdd_children::iterator end = d->end_children(); for (; it != end && b <= e; ++it) { imdd_children::entry const & curr_entry = *it; if (e < curr_entry.begin_key()) break; if (b < curr_entry.begin_key()) { INIT_NEW_CHILD(); SASSERT(b <= curr_entry.begin_key() - 1); to_insert.push_back(entry(b, curr_entry.begin_key() - 1, new_child)); b = curr_entry.begin_key(); } imdd * curr_child = curr_entry.val(); SASSERT(b >= curr_entry.begin_key()); bool cover = b == curr_entry.begin_key() && e >= curr_entry.end_key(); // If cover == true, then the curr_child is completely covered by the new facts, and it is not needed anymore. // So, we can perform a destructive update. imdd * new_curr_child = add_facts_core(curr_child, num - 1, lowers + 1, uppers + 1, cover, memoize_res); if (e >= curr_entry.end_key()) { SASSERT(b <= curr_entry.end_key()); to_insert.push_back(entry(b, curr_entry.end_key(), new_curr_child)); } else { SASSERT(e < curr_entry.end_key()); SASSERT(b <= e); to_insert.push_back(entry(b, e, new_curr_child)); } b = curr_entry.end_key() + 1; } // Re-insert entries in m_add_facts_to_insert into d->m_children, // and if b <= e also insert [b, e] -> new_child if (b <= e) { INIT_NEW_CHILD(); add_child(d, b, e, new_child); } svector::iterator it2 = to_insert.begin(); svector::iterator end2 = to_insert.end(); for (; it2 != end2; ++it2) { imdd_children::entry const & curr_entry = *it2; add_child(d, curr_entry.begin_key(), curr_entry.end_key(), curr_entry.val()); } return d; } else { imdd * new_d = 0; if (is_cached(m_add_facts_cache, d, new_d)) return new_d; if (num == 1) { new_d = insert_main(d, *lowers, *uppers, destructive, memoize_res); } else { new_d = copy_main(d); new_child = m_add_facts_new_children[num-1]; TRACE("add_facts_bug", tout << "after copying: " << new_d << "\n" << mk_ll_pp(new_d, *this) << "\n";); unsigned b = *lowers; unsigned e = *uppers; imdd_children::iterator it = d->m_children.find_geq(b); imdd_children::iterator end = d->end_children(); for (; it != end && b <= e; ++it) { imdd_children::entry const & curr_entry = *it; if (e < curr_entry.begin_key()) break; if (b < curr_entry.begin_key()) { INIT_NEW_CHILD(); SASSERT(b <= curr_entry.begin_key() - 1); add_child(new_d, b, curr_entry.begin_key() - 1, new_child); TRACE("add_facts_bug", tout << "after inserting new child: " << new_d << "\n" << mk_ll_pp(new_d, *this) << "\n";); b = curr_entry.begin_key(); } imdd * curr_child = curr_entry.val(); imdd * new_curr_child = add_facts_core(curr_child, num - 1, lowers + 1, uppers + 1, false, memoize_res); if (!new_curr_child->is_memoized()) new_children_memoized = false; if (e >= curr_entry.end_key()) { SASSERT(b <= curr_entry.end_key()); add_child(new_d, b, curr_entry.end_key(), new_curr_child); TRACE("add_facts_bug", tout << "1) after inserting new curr child: " << new_d << "\n" << mk_ll_pp(new_d, *this) << "\n"; tout << "new_curr_child: " << mk_ll_pp(new_curr_child, *this) << "\n";); } else { SASSERT(e < curr_entry.end_key()); SASSERT(b <= e); add_child(new_d, b, e, new_curr_child); TRACE("add_facts_bug", tout << "2) after inserting new curr child: " << new_d << "\n" << mk_ll_pp(new_d, *this) << "\n"; tout << "new_curr_child: " << mk_ll_pp(new_curr_child, *this) << "\n";); } b = curr_entry.end_key() + 1; } if (b <= e) { INIT_NEW_CHILD(); add_child(new_d, b, e, new_child); } new_d = memoize_new_imdd_if(memoize_res && d->is_memoized() && new_children_memoized, new_d); } cache_result(m_add_facts_cache, d, new_d); return new_d; } } imdd * imdd_manager::add_facts_main(imdd * d, unsigned num, unsigned const * lowers, unsigned const * uppers, bool destructive, bool memoize_res) { SASSERT(d->get_arity() == num); delay_dealloc delay(*this); m_add_facts_cache.reset(); m_add_facts_new_children.reset(); m_add_facts_new_children.resize(num, 0); return add_facts_core(d, num, lowers, uppers, destructive, memoize_res); } inline void update_memoized_flag(imdd * d, bool & memoized_flag) { if (d && !d->is_memoized()) memoized_flag = false; } void imdd_manager::push_back_entries(unsigned head, imdd_children::iterator & it, imdd_children::iterator & end, imdd_children::push_back_proc & push_back, bool & children_memoized) { if (it != end) { push_back(head, it->end_key(), it->val()); update_memoized_flag(it->val(), children_memoized); ++it; for (; it != end; ++it) { update_memoized_flag(it->val(), children_memoized); push_back(it->begin_key(), it->end_key(), it->val()); } } } /** \brief Push the entries starting at head upto the given limit (no included). That is, we are copying the entries in the interval [head, limit). */ void imdd_manager::push_back_upto(unsigned & head, imdd_children::iterator & it, imdd_children::iterator & end, unsigned limit, imdd_children::push_back_proc & push_back, bool & children_memoized) { SASSERT(it != end); SASSERT(head <= it->end_key()); SASSERT(head >= it->begin_key()); SASSERT(head < limit); while (head < limit && it != end) { if (it->end_key() < limit) { update_memoized_flag(it->val(), children_memoized); push_back(head, it->end_key(), it->val()); ++it; if (it != end) head = it->begin_key(); } else { SASSERT(it->end_key() >= limit); update_memoized_flag(it->val(), children_memoized); push_back(head, limit-1, it->val()); head = limit; } } SASSERT(head == limit || it == end || head == it->begin_key()); } void imdd_manager::move_head(unsigned & head, imdd_children::iterator & it, imdd_children::iterator & end, unsigned new_head) { SASSERT(new_head >= head); SASSERT(head >= it->begin_key()); SASSERT(head <= it->end_key()); SASSERT(new_head <= it->end_key()); if (new_head < it->end_key()) { head = new_head+1; SASSERT(head <= it->end_key()); } else { SASSERT(new_head == it->end_key()); ++it; if (it != end) head = it->begin_key(); } } /** \brief Copy the entries starting at head upto the given limit (no included). That is, we are copying the entries in the interval [head, limit). */ void imdd_manager::copy_upto(unsigned & head, imdd_children::iterator & it, imdd_children::iterator & end, unsigned limit, sbuffer & result) { SASSERT(it != end); SASSERT(head <= it->end_key()); SASSERT(head >= it->begin_key()); SASSERT(head < limit); while (head < limit && it != end) { if (it->end_key() < limit) { result.push_back(entry(head, it->end_key(), it->val())); ++it; if (it != end) head = it->begin_key(); } else { SASSERT(it->end_key() >= limit); result.push_back(entry(head, limit-1, it->val())); head = limit; } } SASSERT(head == limit || it == end || head == it->begin_key()); } imdd * imdd_manager::mk_union_core(imdd * d1, imdd * d2, bool destructive, bool memoize_res) { if (d1 == d2) return d1; if (destructive_update_at(destructive, d1)) { if (d1->get_arity() == 1) { imdd_children::iterator it = d2->begin_children(); imdd_children::iterator end = d2->end_children(); for (; it != end; ++it) add_child(d1, it->begin_key(), it->end_key(), 0); return d1; } else { imdd_children::iterator it2 = d2->begin_children(); imdd_children::iterator end2 = d2->end_children(); imdd_children::iterator it1 = d1->m_children.find_geq(it2->begin_key()); imdd_children::iterator end1 = d1->end_children(); sbuffer to_insert; unsigned head1 = it1 != end1 ? it1->begin_key() : UINT_MAX; SASSERT(it2 != end2); unsigned head2 = it2->begin_key(); while (true) { if (it1 == end1) { // copy it2 to d1 // Remark: we don't need to copy to to_insert, since we will not be using it1 anymore. // That is, we can directly insert into d1->m_children. if (it2 != end2) { add_child(d1, head2, it2->end_key(), it2->val()); ++it2; for (; it2 != end2; ++it2) add_child(d1, it2->begin_key(), it2->end_key(), it2->val()); } break; } if (it2 == end2) { break; } if (head1 < head2) { it1.move_to(head2); head1 = it1 != end1 ? (it1->begin_key() < head2?head2:it1->begin_key()): UINT_MAX; } else if (head1 > head2) { copy_upto(head2, it2, end2, head1, to_insert); } else { SASSERT(head1 == head2); unsigned tail = std::min(it1->end_key(), it2->end_key()); imdd * new_child = 0; SASSERT(d1->get_arity() > 1); bool cover = head1 == it1->begin_key() && tail == it1->end_key(); // If cover == true, then the it1->val() (curr_child) is completely covered by // the new_child, and it is not needed anymore. // So, we can perform a destructive update. new_child = mk_union_core(it1->val(), it2->val(), cover, memoize_res); if (new_child != it1->val()) to_insert.push_back(entry(head1, tail, new_child)); move_head(head1, it1, end1, tail); move_head(head2, it2, end2, tail); } } sbuffer::const_iterator it3 = to_insert.begin(); sbuffer::const_iterator end3 = to_insert.end(); for (; it3 != end3; ++it3) add_child(d1, it3->begin_key(), it3->end_key(), it3->val()); return d1; } } else { imdd * r = 0; if (is_cached(m_union_cache, d1, d2, r)) return r; unsigned arity = d1->get_arity(); r = _mk_empty(arity); imdd_children::push_back_proc push_back(m_sl_manager, r->m_children); imdd_children::iterator it1 = d1->begin_children(); imdd_children::iterator end1 = d1->end_children(); imdd_children::iterator it2 = d2->begin_children(); imdd_children::iterator end2 = d2->end_children(); SASSERT(it1 != end1); SASSERT(it2 != end2); unsigned head1 = it1->begin_key(); unsigned head2 = it2->begin_key(); bool children_memoized = true; while (true) { if (it1 == end1) { // copy it2 to result push_back_entries(head2, it2, end2, push_back, children_memoized); break; } if (it2 == end2) { // copy it1 to result push_back_entries(head1, it1, end1, push_back, children_memoized); break; } if (head1 < head2) { push_back_upto(head1, it1, end1, head2, push_back, children_memoized); } else if (head1 > head2) { push_back_upto(head2, it2, end2, head1, push_back, children_memoized); } else { SASSERT(head1 == head2); unsigned tail = std::min(it1->end_key(), it2->end_key()); imdd * new_child = 0; if (arity > 1) { new_child = mk_union_core(it1->val(), it2->val(), false, memoize_res); update_memoized_flag(new_child, children_memoized); } push_back(head1, tail, new_child); move_head(head1, it1, end1, tail); move_head(head2, it2, end2, tail); } } r = memoize_new_imdd_if(memoize_res && children_memoized, r); cache_result(m_union_cache, d1, d2, r); return r; } } void imdd_manager::reset_union_cache() { m_union_cache.reset(); } imdd * imdd_manager::mk_union_main(imdd * d1, imdd * d2, bool destructive, bool memoize_res) { SASSERT(d1->get_arity() == d2->get_arity()); if (d1 == d2) return d1; if (d1->empty()) return d2; if (d2->empty()) return d1; delay_dealloc delay(*this); reset_union_cache(); return mk_union_core(d1, d2, destructive, memoize_res); } void imdd_manager::mk_union_core_dupdt(imdd_ref & d1, imdd * d2, bool memoize_res) { SASSERT(d1->get_arity() == d2->get_arity()); if (d1 == d2 || d2->empty()) return; if (d1->empty()) { d1 = d2; return; } d1 = mk_union_core(d1, d2, true, memoize_res); } void imdd_manager::mk_union_core(imdd * d1, imdd * d2, imdd_ref & r, bool memoize_res) { SASSERT(d1->get_arity() == d2->get_arity()); if (d1 == d2 || d2->empty()) { r = d1; return; } if (d1->empty()) { r = d2; return; } TRACE("mk_union_core", tout << "d1:\n"; display_ll(tout, d1); tout << "d2:\n"; display_ll(tout, d2);); r = mk_union_core(d1, d2, false, memoize_res); } imdd * imdd_manager::mk_complement_core(imdd * d, unsigned num, unsigned const * mins, unsigned const * maxs, bool destructive, bool memoize_res) { SASSERT(d->get_arity() == num); SASSERT(!d->empty()); SASSERT(*mins <= *maxs); unsigned arity = d->get_arity(); imdd_ref new_child(*this); bool new_children_memoized = true; #undef INIT_NEW_CHILD #define INIT_NEW_CHILD() { \ if (arity > 1 && new_child == 0) { \ init_add_facts_new_children(num - 1, mins + 1, maxs + 1, memoize_res); \ new_child = m_add_facts_new_children[num-1]; \ SASSERT(new_child != 0); \ if (!new_child->is_memoized()) new_children_memoized = false; \ }} if (false && destructive_update_at(destructive, d)) { // TODO NOT_IMPLEMENTED_YET(); return 0; } else { destructive = false; imdd * r = 0; if (is_cached(m_complement_cache, d, r)) return r; r = _mk_empty(arity); imdd_children::push_back_proc push_back(m_sl_manager, r->m_children); imdd_children::iterator it = d->begin_children(); imdd_children::iterator end = d->end_children(); unsigned prev_key = *mins; for (; it != end; ++it) { SASSERT(it->begin_key() >= *mins); SASSERT(it->end_key() <= *maxs); if (prev_key < it->begin_key()) { INIT_NEW_CHILD(); push_back(prev_key, it->begin_key() - 1, new_child); } if (arity > 1) { imdd * new_curr = mk_complement_core(it->val(), num - 1, mins + 1, maxs + 1, false, memoize_res); if (new_curr != 0) { push_back(it->begin_key(), it->end_key(), new_curr); if (!new_curr->is_memoized()) new_children_memoized = false; } } prev_key = it->end_key() + 1; } if (prev_key <= *maxs) { INIT_NEW_CHILD(); push_back(prev_key, *maxs, new_child); } if (r->empty()) { delete_imdd(r); r = 0; } r = memoize_new_imdd_if(r && memoize_res && new_children_memoized, r); cache_result(m_complement_cache, d, r); return r; } } imdd * imdd_manager::mk_complement_main(imdd * d, unsigned num, unsigned const * mins, unsigned const * maxs, bool destructive, bool memoize_res) { unsigned arity = d->get_arity(); SASSERT(arity == num); // reuse m_add_facts_new_children for creating the universe-set IMDDs m_add_facts_new_children.reset(); m_add_facts_new_children.resize(num+1, 0); if (d->empty()) { // return the universe-set init_add_facts_new_children(num, mins, maxs, memoize_res); return m_add_facts_new_children[num]; } delay_dealloc delay(*this); m_complement_cache.reset(); imdd * r = mk_complement_core(d, num, mins, maxs, destructive, memoize_res); if (r == 0) return _mk_empty(arity); else return r; } /** \brief Replace the IMDD children with new_children. The function will also decrement the ref-counter of every IMDD in new_children, since it assumes the counter was incremented when the IMDD was inserted into the buffer. */ void imdd::replace_children(sl_imdd_manager & m, sbuffer & new_children) { m_children.reset(m); imdd_children::push_back_proc push_back(m, m_children); svector::iterator it = new_children.begin(); svector::iterator end = new_children.end(); for (; it != end; ++it) { SASSERT(it->val() == 0 || it->val()->get_ref_count() > 0); push_back(it->begin_key(), it->end_key(), it->val()); SASSERT(it->val() == 0 || it->val()->get_ref_count() > 1); if (it->val() != 0) it->val()->dec_ref(); } } imdd * imdd_manager::mk_filter_equal_core(imdd * d, unsigned vidx, unsigned value, bool destructive, bool memoize_res) { SASSERT(!d->empty()); SASSERT(vidx >= 0 && vidx < d->get_arity()); unsigned arity = d->get_arity(); if (destructive_update_at(destructive, d)) { if (vidx == 0) { imdd * child = 0; if (d->m_children.find(value, child)) { imdd_ref ref(*this); ref = child; // protect child, we don't want the following reset to delete it. d->m_children.reset(m_sl_manager); add_child(d, value, child); return d; } else { return 0; } } SASSERT(arity > 1); sbuffer to_insert; imdd_children::iterator it = d->begin_children(); imdd_children::iterator end = d->end_children(); for (; it != end; ++it) { imdd * curr_child = it->val(); imdd * new_child = mk_filter_equal_core(curr_child, vidx-1, value, true, memoize_res); if (new_child != 0) { new_child->inc_ref(); // protect new child, we will be resetting d->m_children later. to_insert.push_back(entry(it->begin_key(), it->end_key(), new_child)); } } if (to_insert.empty()) { return 0; } d->replace_children(m_sl_manager, to_insert); return d; } imdd * r = 0; if (is_cached(m_filter_equal_cache, d, r)) return r; bool new_children_memoized = true; if (vidx == 0) { // found filter variable imdd * child = 0; if (d->m_children.find(value, child)) { r = _mk_empty(arity); add_child(r, value, child); if (child && !child->is_memoized()) new_children_memoized = false; } else { r = 0; } } else { SASSERT(arity > 1); r = _mk_empty(arity); imdd_children::push_back_proc push_back(m_sl_manager, r->m_children); imdd_children::iterator it = d->begin_children(); imdd_children::iterator end = d->end_children(); for (; it != end; ++it) { imdd * curr_child = it->val(); imdd * new_child = mk_filter_equal_core(curr_child, vidx-1, value, false, memoize_res); if (new_child != 0) { push_back(it->begin_key(), it->end_key(), new_child); if (!new_child->is_memoized()) new_children_memoized = false; } } if (r->empty()) { delete_imdd(r); r = 0; } } r = memoize_new_imdd_if(r && memoize_res && new_children_memoized, r); cache_result(m_filter_equal_cache, d, r); return r; } imdd * imdd_manager::mk_filter_equal_main(imdd * d, unsigned vidx, unsigned value, bool destructive, bool memoize_res) { unsigned arity = d->get_arity(); SASSERT(vidx >= 0 && vidx < arity); if (d->empty()) return d; delay_dealloc delay(*this); m_filter_equal_cache.reset(); imdd * r = mk_filter_equal_core(d, vidx, value, destructive, memoize_res); if (r == 0) return _mk_empty(arity); else return r; } /** \brief create map from imdd nodes to the interval of variables that are covered by variable. */ static void mk_interval_set_intersect(sl_manager_base &m, sl_interval_set const& src, unsigned b, unsigned e, sl_interval_set& dst) { sl_interval_set::iterator it = src.find_geq(b); sl_interval_set::iterator end = src.end(); for (; it != end; ++it) { unsigned b1 = it->begin_key(); unsigned e1 = it->end_key(); if (e < b1) { break; } if (b1 < b) { b1 = b; } if (e < e1) { e1 = e; } SASSERT(b <= b1 && b1 <= e1 && e1 <= e); dst.insert(m, b1, e1); } } static void mk_interval_set_union(sl_manager_base &m, sl_interval_set& dst, sl_interval_set const& src) { sl_interval_set::iterator it = src.begin(), end = src.end(); for (; it != end; ++it) { dst.insert(m, it->begin_key(), it->end_key()); } } void imdd_manager::reset_fi_intervals(sl_imanager& m) { for (unsigned i = 0; i < m_alloc_is.size(); ++i) { m_alloc_is[i]->deallocate(m); dealloc(m_alloc_is[i]); } m_alloc_is.reset(); m_imdd2interval_set.reset(); } sl_interval_set const* imdd_manager::init_fi_intervals(sl_imanager& m, imdd* d, unsigned var, unsigned num_found) { sl_interval_set* result = 0; #define ALLOC_RESULT() if (!result) { result = alloc(sl_interval_set, m); m_alloc_is.push_back(result); } if (m_imdd2interval_set.find(d, result)) { return result; } bool _is_fi_var = is_fi_var(var); unsigned new_num_found = _is_fi_var?num_found+1:num_found; bool last_fi_var = (new_num_found == m_fi_num_vars); imdd_children::iterator it = d->begin_children(); imdd_children::iterator end = d->end_children(); for (; it != end; ++it) { imdd_children::entry const & curr_entry = *it; unsigned b = curr_entry.begin_key(); unsigned e = curr_entry.end_key(); if (last_fi_var) { ALLOC_RESULT(); result->insert(m, b, e, 1); } else { SASSERT(d->get_arity() > 1); imdd* curr_child = curr_entry.val(); sl_interval_set const* is2 = init_fi_intervals(m, curr_child, var+1, new_num_found); if (!is2) { continue; } if (_is_fi_var) { sl_interval_set is3(m); mk_interval_set_intersect(m, *is2, b, e, is3); if (!is3.empty()) { ALLOC_RESULT(); mk_interval_set_union(m, *result, is3); } is3.deallocate(m); } else { if (is2 && result != is2) { ALLOC_RESULT(); mk_interval_set_union(m, *result, *is2); } } } } m_imdd2interval_set.insert(d, result); return result; } inline mk_fi_result mk(imdd * d) { mk_fi_result r; r.m_d = d; return r; } inline mk_fi_result mk(fi_cache_entry * e) { mk_fi_result r; r.m_entry = e; return r; } fi_cache_entry * imdd_manager::mk_fi_cache_entry(imdd * d, unsigned lower, unsigned upper, unsigned num_pairs, imdd_value_pair pairs[]) { void * mem = m_fi_entries.allocate(sizeof(fi_cache_entry) + sizeof(imdd_value_pair) * num_pairs); DEBUG_CODE({ for (unsigned i = 0; i < num_pairs; i++) { SASSERT(pairs[i].first != 0); } }); return new (mem) fi_cache_entry(d, lower, upper, num_pairs, pairs); } struct imdd_value_pair_lt_value { bool operator()(imdd_value_pair const & p1, imdd_value_pair const & p2) const { return p1.second < p2.second; } }; // Review note: identical nodes should be unit intervals? // 1 -> 1 -> x mk_fi_result imdd_manager::mk_filter_identical_core(imdd * d, unsigned var, unsigned num_found, unsigned lower, unsigned upper, bool destructive, bool memoize_res) { TRACE("imdd", tout << "#" << d->get_id() << " var: " << var << " num_found: " << num_found << " lower: " << lower << " upper: " << upper << "\n";); unsigned arity = d->get_arity(); #define MK_EMPTY_ENTRY (num_found == 0 && destructive_update_at(destructive, d))?mk(static_cast(0)):mk(static_cast(0)) sl_interval_set* node_ranges = m_imdd2interval_set.find(d); if (!node_ranges) { return MK_EMPTY_ENTRY; } sl_interval_set::iterator sl_it = node_ranges->find_geq(lower); if (sl_it == node_ranges->end() || upper < sl_it->begin_key()) { return MK_EMPTY_ENTRY; } bool new_children_memoized = true; bool _is_fi_var = is_fi_var(var); if (num_found == 0) { SASSERT(arity > 1); if (destructive_update_at(destructive, d)) { sbuffer to_insert; imdd_children::iterator it = d->begin_children(); imdd_children::iterator end = d->end_children(); for (; it != end; ++it) { imdd * curr_child = it->val(); if (_is_fi_var) { fi_cache_entry * child_entry = (mk_filter_identical_core(curr_child, var+1, 1, it->begin_key(), it->end_key(), false, memoize_res)).m_entry; for (unsigned i = 0; child_entry && i < child_entry->m_num_result; i++) { imdd * new_child = child_entry->m_result[i].first; unsigned value = child_entry->m_result[i].second; to_insert.push_back(entry(value, value, new_child)); } } else { imdd * new_child = (mk_filter_identical_core(curr_child, var+1, 0, 0, UINT_MAX, false, memoize_res)).m_d; if (new_child != 0) { new_child->inc_ref(); to_insert.push_back(entry(it->begin_key(), it->end_key(), new_child)); } } } if (to_insert.empty()) { return mk(static_cast(0)); } d->replace_children(m_sl_manager, to_insert); return mk(d); } else { // num_found == 0 && variable is not part of the filter && no destructive update. imdd * r = 0; if (is_cached(m_fi_top_cache, d, r)) return mk(r); r = _mk_empty(arity); imdd_children::push_back_proc push_back(m_sl_manager, r->m_children); imdd_children::iterator it = d->begin_children(); imdd_children::iterator end = d->end_children(); for (; it != end; ++it) { imdd * curr_child = it->val(); if (_is_fi_var) { fi_cache_entry * entry = (mk_filter_identical_core(curr_child, var+1, 1, it->begin_key(), it->end_key(), false, memoize_res)).m_entry; for (unsigned i = 0; entry && i < entry->m_num_result; i++) { imdd * new_child = entry->m_result[i].first; unsigned value = entry->m_result[i].second; push_back(value, value, new_child); if (!new_child->is_memoized()) new_children_memoized = false; } } else { imdd * new_child = (mk_filter_identical_core(curr_child, var+1, 0, 0, UINT_MAX, false, memoize_res)).m_d; if (new_child != 0) { push_back(it->begin_key(), it->end_key(), new_child); if (!new_child->is_memoized()) new_children_memoized = false; } } } if (r->empty()) { delete_imdd(r); r = 0; } r = memoize_new_imdd_if(r && memoize_res && new_children_memoized, r); cache_result(m_fi_top_cache, d, r); return mk(r); } } else { SASSERT(num_found > 0); fi_cache_entry d_entry(d, lower, upper); fi_cache_entry * r_entry; if (d->is_shared() && m_fi_bottom_cache.find(&d_entry, r_entry)) return mk(r_entry); sbuffer result; if (_is_fi_var) { unsigned new_num_found = num_found + 1; bool last_fi_var = (new_num_found == m_fi_num_vars); imdd_children::iterator it = d->m_children.find_geq(lower); imdd_children::iterator end = d->end_children(); for (; it != end; ++it) { imdd * curr_child = it->val(); unsigned curr_lower = it->begin_key(); unsigned curr_upper = it->end_key(); if (curr_lower < lower) curr_lower = lower; if (curr_upper > upper) curr_upper = upper; if (curr_lower <= curr_upper) { if (last_fi_var) { for (unsigned i = curr_lower; i <= curr_upper; i++) { imdd * new_d = _mk_empty(arity); add_child(new_d, i, curr_child); new_d = memoize_new_imdd_if(memoize_res && (curr_child == 0 || curr_child->is_memoized()), new_d); result.push_back(imdd_value_pair(new_d, i)); } } else { fi_cache_entry * new_entry = (mk_filter_identical_core(curr_child, var+1, new_num_found, curr_lower, curr_upper, false, memoize_res)).m_entry; for (unsigned i = 0; new_entry && i < new_entry->m_num_result; i++) { SASSERT(new_entry->m_result[i].first != 0); imdd * curr_child = new_entry->m_result[i].first; unsigned value = new_entry->m_result[i].second; imdd * new_d = _mk_empty(arity); add_child(new_d, value, curr_child); SASSERT(curr_child != 0); new_d = memoize_new_imdd_if(memoize_res && curr_child->is_memoized(), new_d); result.push_back(imdd_value_pair(new_d, value)); } } } if (curr_upper == upper) break; } } else { SASSERT(arity > 1); u_map value2imdd; imdd_children::iterator it = d->begin_children(); imdd_children::iterator end = d->end_children(); for (; it != end; ++it) { imdd * curr_child = it->val(); SASSERT(curr_child != 0); fi_cache_entry * new_entry = (mk_filter_identical_core(curr_child, var+1, num_found, lower, upper, false, memoize_res)).m_entry; for (unsigned i = 0; new_entry && i < new_entry->m_num_result; i++) { unsigned value = new_entry->m_result[i].second; imdd * new_child = new_entry->m_result[i].first; imdd * new_d = 0; if (!value2imdd.find(value, new_d)) { new_d = _mk_empty(arity); value2imdd.insert(value, new_d); result.push_back(imdd_value_pair(new_d, value)); } SASSERT(new_d != 0); add_child(new_d, it->begin_key(), it->end_key(), new_child); } } std::sort(result.begin(), result.end(), imdd_value_pair_lt_value()); } r_entry = mk_fi_cache_entry(d, lower, upper, result.size(), result.c_ptr()); if (d->is_shared()) m_fi_bottom_cache.insert(r_entry); return mk(r_entry); } } imdd * imdd_manager::mk_filter_identical_main(imdd * d, unsigned num_vars, unsigned * vars, bool destructive, bool memoize_res) { if (d->empty() || num_vars < 2) return d; TRACE("imdd", tout << "vars: "; for (unsigned i = 0; i < num_vars; ++i) { tout << vars[i] << " "; } tout << "\n"; tout << "rows: " << get_num_rows(d) << "\n"; display_ll(tout, d); ); unsigned arity = d->get_arity(); DEBUG_CODE(for (unsigned i = 0; i < num_vars; ++i) SASSERT(vars[i] < arity);); m_fi_num_vars = num_vars; m_fi_begin_vars = vars; m_fi_end_vars = vars + num_vars; delay_dealloc delay(*this); m_fi_bottom_cache.reset(); m_fi_top_cache.reset(); m_fi_entries.reset(); sl_imanager imgr; init_fi_intervals(imgr, d, 0, 0); TRACE("imdd_verbose", ptr_addr_hashtable ht; imdd2intervals::iterator it = m_imdd2interval_set.begin(); imdd2intervals::iterator end = m_imdd2interval_set.end(); for (; it != end; ++it) { tout << it->m_key->get_id() << " "; if (it->m_value) { if (ht.contains(it->m_value)) { tout << "dup\n"; continue; } ht.insert(it->m_value); sl_interval_set::iterator sit = it->m_value->begin(); sl_interval_set::iterator send = it->m_value->end(); for (; sit != send; ++sit) { tout << "[" << sit->begin_key() << ":" << sit->end_key() << "] "; } } else { tout << "{}"; } tout << "\n"; }); mk_fi_result r = mk_filter_identical_core(d, 0, 0, 0, UINT_MAX, destructive, memoize_res); reset_fi_intervals(imgr); TRACE("imdd", if (r.m_d) display_ll(tout << "result\n", r.m_d);); if (r.m_d == 0) return _mk_empty(arity); else return r.m_d; } void imdd_manager::swap_in(imdd * d, imdd_ref& r, unsigned num_vars, unsigned * vars) { // variables are sorted. DEBUG_CODE(for (unsigned i = 0; i + 1 < num_vars; ++i) SASSERT(vars[i] < vars[i+1]);); imdd_ref tmp1(*this), tmp2(*this); tmp1 = d; SASSERT(num_vars > 1); unsigned v1 = vars[0]+1; // next position to swap to. for (unsigned i = 1; i < num_vars; ++i) { unsigned v2 = vars[i]; SASSERT(v1 <= v2); for (unsigned j = v2-1; j >= v1; --j) { mk_swap(tmp1, tmp2, j); tmp1 = tmp2; } ++v1; } TRACE("imdd", for (unsigned i = 0; i < num_vars; ++i) tout << vars[i] << " "; tout << "\n"; display_ll(tout << "in\n", d); display_ll(tout << "out\n", tmp1); tout << "\n";); r = tmp1; } void imdd_manager::swap_out(imdd * d, imdd_ref& r, unsigned num_vars, unsigned * vars) { // variables are sorted. DEBUG_CODE(for (unsigned i = 0; i + 1 < num_vars; ++i) SASSERT(vars[i] < vars[i+1]);); imdd_ref tmp1(*this), tmp2(*this); tmp1 = d; SASSERT(num_vars > 1); unsigned v1 = vars[0]+num_vars-1; // position of next variable to be swapped. for (unsigned i = num_vars; i > 1; ) { --i; unsigned v2 = vars[i]; for (unsigned j = v1; j < v2; ++j) { mk_swap(tmp1, tmp2, j); tmp1 = tmp2; } --v1; } TRACE("imdd", for (unsigned i = 0; i < num_vars; ++i) tout << vars[i] << " "; tout << "\n"; display_ll(tout << "out\n", d); display_ll(tout << "in\n", tmp1); tout << "\n";); r = tmp1; } void imdd_manager::filter_identical_core2(imdd* d, unsigned num_vars, unsigned b, unsigned e, ptr_vector& ch) { SASSERT(num_vars > 0); imdd* r = 0; imdd_children::iterator it = d->m_children.find_geq(b); imdd_children::iterator end = d->m_children.end(); for (; it != end; ++it) { unsigned b1 = it->begin_key(); unsigned e1 = it->end_key(); if (e < b1) { break; } if (b1 < b) { b1 = b; } if (e <= e1) { e1 = e; } SASSERT(b <= b1 && b1 <= e1 && e1 <= e); imdd* curr_child = it->val(); if (num_vars == 1) { for (unsigned i = b1; i <= e1; ++i) { r = _mk_empty(d->get_arity()); add_child(r, i, i, it->val()); r = memoize_new_imdd_if(!it->val() || it->val()->is_memoized(), r); ch.push_back(r); } continue; } ptr_vector ch2; filter_identical_core2(curr_child, num_vars-1, b1, e1, ch2); for (unsigned i = 0; i < ch2.size(); ++i) { r = _mk_empty(d->get_arity()); unsigned key = ch2[i]->begin_children()->begin_key(); SASSERT(ch2[i]->begin_children()->end_key() == key); add_child(r, key, key, ch2[i]); r = memoize_new_imdd_if(ch2[i]->is_memoized(), r); ch.push_back(r); } } } imdd* imdd_manager::filter_identical_core2(imdd* d, unsigned var, unsigned num_vars, bool memoize_res) { imdd* r = 0; if (m_filter_identical_cache.find(d, r)) { return r; } bool children_memoized = true; r = _mk_empty(d->get_arity()); imdd_children::iterator it = d->begin_children(); imdd_children::iterator end = d->end_children(); imdd_children::push_back_proc push_back(m_sl_manager, r->m_children); if (var > 0) { for (; it != end; ++it) { imdd* curr_child = it->val(); imdd* new_child = filter_identical_core2(curr_child, var-1, num_vars, memoize_res); if (new_child) { push_back(it->begin_key(), it->end_key(), new_child); if (!new_child->is_memoized()) { children_memoized = false; } } } } else { ptr_vector ch; for (; it != end; ++it) { imdd* curr_child = it->val(); filter_identical_core2(curr_child, num_vars-1, it->begin_key(), it->end_key(), ch); } for (unsigned i = 0; i < ch.size(); ++i) { unsigned key = ch[i]->begin_children()->begin_key(); SASSERT(ch[i]->begin_children()->end_key() == key); push_back(key, key, ch[i]); if (!ch[i]->is_memoized()) { children_memoized = false; } } } if (r->empty()) { delete_imdd(r); r = 0; } m_filter_identical_cache.insert(d, r); r = memoize_new_imdd_if(r && memoize_res && children_memoized, r); return r; } void imdd_manager::filter_identical_main2(imdd * d, imdd_ref& r, unsigned num_vars, unsigned * vars, bool destructive, bool memoize_res) { m_filter_identical_cache.reset(); imdd_ref tmp1(*this), tmp2(*this); swap_in(d, tmp1, num_vars, vars); tmp2 = filter_identical_core2(tmp1, vars[0], num_vars, memoize_res); if (!tmp2) { r = _mk_empty(d->get_arity()); } else { swap_out(tmp2, r, num_vars, vars); } m_filter_identical_cache.reset(); } void imdd_manager::filter_identical_main3(imdd * d, imdd_ref& r, unsigned num_vars, unsigned * vars, bool destructive, bool memoize_res) { for (unsigned i = 0; i+1 < num_vars; ++i) { imdd_ref tmp(*this); tmp = r; filter_identical_main3(tmp, r, vars[i], false, vars[i+1], false, memoize_res); } } void imdd_manager::filter_identical_main3(imdd * d, imdd_ref& r, unsigned v1, bool del1, unsigned v2, bool del2, bool memoize_res) { r = filter_identical_loop3(d, v1, del1, v2, del2, memoize_res); if (r == 0) { r = _mk_empty(d->get_arity()-del1-del2); } m_filter_identical_cache.reset(); } imdd* imdd_manager::filter_identical_loop3(imdd * d, unsigned v1, bool del1, unsigned v2, bool del2, bool memoize_res) { imdd* r; if (m_filter_identical_cache.find(d, r)) { return r; } if (v1 == 0) { return filter_identical_mk_nodes(d, v2, del1, del2, memoize_res); } r = _mk_empty(d->get_arity()-del1-del2); imdd_children::iterator it = d->begin_children(); imdd_children::iterator end = d->end_children(); imdd_children::push_back_proc push_back(m_sl_manager, r->m_children); bool children_memoized = true; for (; it != end; ++it) { imdd* curr_child = it->val(); imdd* new_child = filter_identical_loop3(curr_child, v1-1, del1, v2-1, del2, memoize_res); if (!new_child) { continue; } if (new_child->empty()) { delete_imdd(new_child); continue; } if (!new_child->is_memoized()) { children_memoized = false; } push_back(it->begin_key(), it->end_key(), new_child); } if (r->empty()) { delete_imdd(r); r = 0; } m_filter_identical_cache.insert(d, r); r = memoize_new_imdd_if(r && memoize_res && children_memoized, r); return r; } void imdd_manager::merge_intervals(svector& dst, svector const& src) { svector& tmp = m_i_nodes_tmp; tmp.reset(); // invariant: intervals are sorted. for (unsigned i = 0, j = 0; i < src.size() || j < dst.size();) { SASSERT(!(i + 1 < src.size()) || src[i].m_hi < src[i+1].m_lo); SASSERT(!(i + 1 < dst.size()) || dst[i].m_hi < dst[i+1].m_lo); SASSERT(!(i < src.size()) || src[i].m_lo <= src[i].m_hi); SASSERT(!(i < dst.size()) || dst[i].m_lo <= dst[i].m_hi); if (i < src.size() && j < dst.size()) { if (src[i].m_lo == dst[j].m_lo) { tmp.push_back(src[i]); ++i; ++j; } else if (src[i].m_lo < dst[j].m_lo) { tmp.push_back(src[i]); ++i; } else { tmp.push_back(dst[j]); ++j; } } else if (i < src.size()) { tmp.push_back(src[i]); ++i; } else { tmp.push_back(dst[j]); ++j; } } dst.reset(); dst.append(tmp); } /** * Propagate intervals down: what intervals can reach which nodes. */ imdd* imdd_manager::filter_identical_mk_nodes(imdd* d, unsigned v, bool del1, bool del2, bool memoize_res) { SASSERT(v > 0); TRACE("imdd", display_ll(tout << "v: " << v << "\n", d);); // // (0) // Create map d |-> [I] from nodes to ordered set of disjoint intervals that visit the node. // // For each level up to 'v' create a list of nodes visited // insert to a map the set of intervals that visit the node. // m_nodes.reset(); filter_id_map& nodes = m_nodes; imdd* d1, *d2, *d3; vector > levels; levels.push_back(ptr_vector()); imdd_children::iterator it = d->begin_children(); imdd_children::iterator end = d->end_children(); imdd* curr_child; for (; it != end; ++it) { curr_child = it->val(); svector& iv = nodes.init(curr_child); if (iv.empty()) { levels.back().push_back(curr_child); } iv.push_back(interval(it->begin_key(), it->end_key())); } for (unsigned j = 0; j+1 < v; ++j) { levels.push_back(ptr_vector()); for (unsigned i = 0; i < levels[j].size(); ++i) { d1 = levels[j][i]; svector& i_nodes = nodes.init(d1); it = d1->begin_children(); end = d1->end_children(); for(; it != end; ++it) { imdd* curr_child = it->val(); svector& i_nodes2 = nodes.init(curr_child); if (i_nodes2.empty()) { levels[j+1].push_back(curr_child); i_nodes2.append(i_nodes); } else { merge_intervals(i_nodes2, i_nodes); } } } } TRACE("imdd", for (unsigned i = 0; i < levels.size(); ++i) { tout << "Level: " << i << "\n"; for (unsigned j = 0; j < levels[i].size(); ++j) { tout << levels[i][j]->get_id() << " "; svector const& i_nodes = nodes.init(levels[i][j]); for (unsigned k = 0; k < i_nodes.size(); ++k) { tout << i_nodes[k].m_lo << ":" << i_nodes[k].m_hi << " "; } tout << "\n"; } } ); // // (1) // Intersect with children: // - d [l1:h1:ch1][l2:h2:ch2][...] // => produce_units: d |-> [uI1 |-> d'[uI1:ch1], uI2 |-> d''[uI2:ch1], ...] // unit intervals // => del2: d |-> [I |-> union of ch1, ch2, .. under intersection] // => del1 & !del2: d |-> [I |-> d'[I:ch]] // intersections of intervals. // m_nodes_dd.reset(); filter_idd_map& nodes_dd = m_nodes_dd; SASSERT(levels.size() == v); for (unsigned i = 0; i < levels[v-1].size(); ++i) { d1 = levels[v-1][i]; svector const & i_nodes = nodes.init(d1); it = d1->begin_children(); end = d1->end_children(); unsigned j = 0; svector& i_nodes_dd = nodes_dd.init(d1); while (it != end && j < i_nodes.size()) { unsigned lo1 = it->begin_key(); unsigned hi1 = it->end_key(); unsigned lo2 = i_nodes[j].m_lo; unsigned hi2 = i_nodes[j].m_hi; if (hi2 < lo1) { ++j; } else if (hi1 < lo2) { ++it; } // lo1 <= hi2 && lo2 <= hi1 else { curr_child = it->val(); unsigned lo = std::max(lo1, lo2); unsigned hi = std::min(hi1, hi2); SASSERT(lo <= hi); if (!del1 && !del2) { for (unsigned k = lo; k <= hi; ++k) { imdd* d2 = _mk_empty(d1->get_arity()); add_child(d2, k, k, curr_child); i_nodes_dd.push_back(interval_dd(k, k, d2)); } } else if (del2) { i_nodes_dd.push_back(interval_dd(lo, hi, curr_child)); // retrofill after loop. } else { imdd* d2 = _mk_empty(d1->get_arity()); add_child(d2, lo, hi, curr_child); i_nodes_dd.push_back(interval_dd(lo, hi, d2)); } if (hi2 <= hi) { ++j; } if (hi1 <= hi) { ++it; } } } // take union of accumulated children. // retrofill union inside list. if (del2) { d2 = 0; for (unsigned k = 0; k < i_nodes_dd.size(); ++k) { d3 = i_nodes_dd[k].m_dd; if (!d2) { d2 = d3; } else { d2 = mk_union_core(d2, d3, true, memoize_res); } } for (unsigned k = 0; k < i_nodes_dd.size(); ++k) { i_nodes_dd[k].m_dd = d2; } } } TRACE("imdd", print_filter_idd(tout, nodes_dd);); // // (2) // Move up: // d1 |-> [I1] // intervals visiting d1 // d1 |-> [lo:hi:child] // children of d1 // child |-> [I2 |-> child'] // current decomposition // result: // d3 = d1' |-> [lo:hi:child'] // d1 |-> [I3 |-> d3] for I3 in merge of [I1] and [I2] // // The merge is defined as the intersection of intervals that reside in I1 and // the fractions in I2. They are decomposed so that all intervals are covered. // By construction I2 are contained in I1, // but they may be overlapping among different I2. // for (unsigned i = v-1; i > 0; ) { --i; for (unsigned j = 0; j < levels[i].size(); ++j) { d1 = levels[i][j]; m_i_nodes_dd.reset(); svector i_nodes = nodes.init(d1); svector& i_nodes_dd = nodes_dd.init(d1); it = d1->begin_children(); end = d1->end_children(); unsigned num_children = 0; for( ; it != end; ++it, ++num_children); m_offsets.reset(); unsigned_vector& offsets = m_offsets; offsets.resize(num_children); it = d1->begin_children(); for( ; it != end; ++it) { curr_child = it->val(); refine_intervals(i_nodes, nodes_dd.init(curr_child)); } for (unsigned k = 0; k < i_nodes.size(); ++k) { interval const& intv = i_nodes[k]; d3 = _mk_empty(d1->get_arity()-del2); it = d1->begin_children(); for(unsigned child_id = 0; it != end; ++it, ++child_id) { curr_child = it->val(); svector const& ch_nodes_dd = nodes_dd.init(curr_child); unsigned offset = offsets[child_id]; TRACE("imdd_verbose", tout << intv.m_lo << ":" << intv.m_hi << "\n"; for (unsigned l = offset; l < ch_nodes_dd.size(); ++l) { tout << ch_nodes_dd[l].m_lo << ":" << ch_nodes_dd[l].m_hi << " " << ch_nodes_dd[l].m_dd->get_id() << " "; } tout << "\n"; ); unsigned hi, lo; d2 = 0; while (offset < ch_nodes_dd.size() && !d2) { lo = ch_nodes_dd[offset].m_lo; hi = ch_nodes_dd[offset].m_hi; if (intv.m_hi < lo) { break; } if (hi < intv.m_lo) { ++offset; continue; } SASSERT(lo <= intv.m_lo); SASSERT(intv.m_hi <= hi); d2 = ch_nodes_dd[offset].m_dd; if (intv.m_hi == hi) { ++offset; } } offsets[child_id] = offset; if (d2) { add_child(d3, it->begin_key(), it->end_key(), d2); } } if (d3->empty()) { delete_imdd(d3); } else { i_nodes_dd.push_back(interval_dd(intv.m_lo, intv.m_hi, d3)); } } TRACE("imdd", tout << d1->get_id() << ": "; print_interval_dd(tout, i_nodes_dd);); } } TRACE("imdd", print_filter_idd(tout, nodes_dd);); // // (3) // Finalize: // d |-> [I1:child] // children of d // child |-> [I2 |-> child'] // current decomposition // result: // d' = union of child' // if del1 // d' |-> [I2:child'] // if !del1 // it = d->begin_children(); end = d->end_children(); d1 = _mk_empty(d->get_arity()-del1-del2); m_i_nodes_dd.reset(); m_i_nodes_tmp.reset(); svector& i_nodes_dd = m_i_nodes_dd; svector& i_nodes_tmp = m_i_nodes_dd_tmp; for (; it != end; ++it) { curr_child = it->val(); i_nodes_tmp.reset(); svector const& i_nodes_dd1 = nodes_dd.init(curr_child); for (unsigned i = 0, j = 0; i < i_nodes_dd.size() || j < i_nodes_dd1.size(); ) { if (i < i_nodes_dd.size() && j < i_nodes_dd1.size()) { interval_dd const& iv1 = i_nodes_dd[i]; interval_dd const& iv2 = i_nodes_dd1[j]; if (iv1.m_lo == iv2.m_lo) { SASSERT(iv1.m_hi == iv2.m_hi); SASSERT(iv1.m_dd == iv2.m_dd); i_nodes_tmp.push_back(iv1); ++i; ++j; } else if (iv1.m_lo < iv2.m_lo) { SASSERT(iv1.m_hi < iv2.m_lo); i_nodes_tmp.push_back(iv1); ++i; } else { SASSERT(iv2.m_hi < iv1.m_lo); i_nodes_tmp.push_back(iv2); ++j; } } else if (i < i_nodes_dd.size()) { i_nodes_tmp.push_back(i_nodes_dd[i]); ++i; } else if (j < i_nodes_dd1.size()) { i_nodes_tmp.push_back(i_nodes_dd1[j]); ++j; } } i_nodes_dd.reset(); i_nodes_dd.append(i_nodes_tmp); } for (unsigned i = 0; i < i_nodes_dd.size(); ++i) { imdd* ch = i_nodes_dd[i].m_dd; unsigned lo = i_nodes_dd[i].m_lo; unsigned hi = i_nodes_dd[i].m_hi; if (del1) { d1 = mk_union_core(d1, ch, true, memoize_res); } else { add_child(d1, lo, hi, ch); } } TRACE("imdd", display_ll(tout, d1);); return d1; } void imdd_manager::print_interval_dd(std::ostream& out, svector const& m) { for (unsigned i = 0; i < m.size(); ++i) { out << m[i].m_lo << ":" << m[i].m_hi << " " << m[i].m_dd->get_id() << " "; } out << "\n"; } void imdd_manager::print_filter_idd(std::ostream& out, filter_idd_map const& m) { filter_idd_map::iterator it = m.begin(), end = m.end(); for (unsigned i = 0; it != end; ++it, ++i) { out << i << ": "; print_interval_dd(out, *it); } } /** \brief partition intervals of i_nodes by sub-intervals that are used in i_nodes_dd. Assumption: - All values covered in i_nodes_dd are present in i_nodes. */ void imdd_manager::refine_intervals(svector& i_nodes, svector const& i_nodes_dd) { m_i_nodes.reset(); svector& result = m_i_nodes; unsigned sz1 = i_nodes.size(); unsigned sz2 = i_nodes_dd.size(); for (unsigned i = 0, j = 0; i < sz1; ++i) { interval& iv = i_nodes[i]; result.push_back(iv); unsigned lo = iv.m_lo; unsigned hi = iv.m_hi; for (; j < sz2; ++j) { unsigned lo1 = i_nodes_dd[j].m_lo; unsigned hi1 = i_nodes_dd[j].m_hi; SASSERT(lo <= hi); SASSERT(lo1 <= hi1); if (hi < lo1) { break; } if (lo < lo1) { result.back().m_hi = lo1-1; result.push_back(interval(lo1, hi)); lo = lo1; } SASSERT(lo1 <= lo); if (lo > hi1) { continue; } if (hi1 < hi) { result.back().m_hi = hi1; result.push_back(interval(hi1+1, hi)); lo = hi1+1; } } } i_nodes.reset(); i_nodes.append(result); } void imdd_manager::mk_project_core(imdd * d, imdd_ref & r, unsigned var, unsigned num_found, bool memoize_res) { unsigned arity = d->get_arity(); bool _is_proj_var = is_proj_var(var); if (_is_proj_var && arity == (m_proj_num_vars - num_found)) { r = 0; // 0 here means the unit element (aka the 0-tuple). return; } imdd * _r = 0; if (is_cached(m_proj_cache, d, _r)) { r = _r; return; } bool new_children_memoized = true; if (_is_proj_var) { SASSERT(d != 0); SASSERT(d->get_arity() > 1); unsigned new_var = var + 1; unsigned new_num_found = num_found + 1; bool found_all = new_num_found == m_proj_num_vars; imdd_children::iterator it = d->begin_children(); imdd_children::iterator end = d->end_children(); imdd_ref tmp_r(*this); CTRACE("imdd", it == end, display_ll(tout, d);); SASSERT(it != end); for (; it != end; ++it) { imdd_ref new_child(*this); if (found_all) new_child = it->val(); else mk_project_core(it->val(), new_child, new_var, new_num_found, memoize_res); if (tmp_r == 0) tmp_r = new_child; else mk_union_core(tmp_r, new_child, tmp_r, memoize_res); SASSERT(tmp_r != 0); } SASSERT(tmp_r != 0); SASSERT(tmp_r->get_arity() == d->get_arity() - (m_proj_num_vars - num_found)); r = tmp_r; } else { SASSERT(num_found < m_proj_num_vars); unsigned new_var = var+1; _r = _mk_empty(arity - (m_proj_num_vars - num_found)); imdd_children::push_back_proc push_back(m_sl_manager, _r->m_children); imdd_children::iterator it = d->begin_children(); imdd_children::iterator end = d->end_children(); SASSERT(it != end); for (; it != end; ++it) { imdd * curr_child = it->val(); imdd_ref new_child(*this); mk_project_core(curr_child, new_child, new_var, num_found, memoize_res); push_back(it->begin_key(), it->end_key(), new_child); if (new_child != 0 && !new_child->is_memoized()) new_children_memoized = false; } SASSERT(!_r->empty()); _r = memoize_new_imdd_if(memoize_res && new_children_memoized, _r); r = _r; } cache_result(m_proj_cache, d, r); } void imdd_manager::mk_project_dupdt_core(imdd_ref & d, unsigned var, unsigned num_found, bool memoize_res) { unsigned arity = d->get_arity(); bool _is_proj_var = is_proj_var(var); if (_is_proj_var && arity == (m_proj_num_vars - num_found)) { d = 0; // 0 here means the unit element (aka the 0-tuple). return; } if (!destructive_update_at(true, d)) { mk_project_core(d, d, var, num_found, memoize_res); return; } if (_is_proj_var) { SASSERT(d != 0); SASSERT(d->get_arity() > 1); unsigned new_var = var + 1; unsigned new_num_found = num_found + 1; bool found_all = new_num_found == m_proj_num_vars; imdd_children::iterator it = d->begin_children(); imdd_children::iterator end = d->end_children(); imdd_ref r(*this); for (; it != end; ++it) { imdd_ref new_child(*this); it.take_curr_ownership(m_sl_manager, new_child); if (!found_all) { mk_project_dupdt_core(new_child, new_var, new_num_found, memoize_res); } if (r == 0) r = new_child; else mk_union_core_dupdt(r, new_child, memoize_res); } // we traverse the children of d, and decrement the reference counter of each one of them. d->m_children.reset_no_decref(m_sl_manager); d = r; SASSERT(r != 0); SASSERT(r->get_arity() == d->get_arity() - (m_proj_num_vars - num_found)); } else { SASSERT(!_is_proj_var); sbuffer to_insert; unsigned new_var = var+1; imdd_children::iterator it = d->begin_children(); imdd_children::iterator end = d->end_children(); for (; it != end; ++it) { imdd_ref new_child(*this); it.take_curr_ownership(m_sl_manager, new_child); mk_project_dupdt_core(new_child, new_var, num_found, memoize_res); if (new_child) new_child->inc_ref(); // protect new child, since we will insert it into to_insert to_insert.push_back(entry(it->begin_key(), it->end_key(), new_child)); } SASSERT(!to_insert.empty()); d->replace_children(m_sl_manager, to_insert); } } void imdd_manager::mk_project_init(unsigned num_vars, unsigned * vars) { m_proj_num_vars = num_vars; m_proj_begin_vars = vars; m_proj_end_vars = vars + num_vars; m_proj_cache.reset(); reset_union_cache(); } void imdd_manager::mk_project_dupdt(imdd_ref & d, unsigned num_vars, unsigned * vars, bool memoize_res) { if (num_vars == 0) { return; } unsigned arity = d->get_arity(); SASSERT(num_vars < arity); if (d->empty()) { d = _mk_empty(arity - num_vars); return; } delay_dealloc delay(*this); mk_project_init(num_vars, vars); mk_project_dupdt_core(d, 0, 0, memoize_res); } void imdd_manager::mk_project(imdd * d, imdd_ref & r, unsigned num_vars, unsigned * vars, bool memoize_res) { if (num_vars == 0) { r = d; return; } unsigned arity = d->get_arity(); SASSERT(num_vars < arity); if (d->empty()) { r = _mk_empty(arity - num_vars); return; } delay_dealloc delay(*this); mk_project_init(num_vars, vars); mk_project_core(d, r, 0, 0, memoize_res); STRACE("imdd_trace", tout << "mk_project(0x" << d << ", 0x" << r.get() << ", "; for (unsigned i = 0; i < num_vars; i++) tout << vars[i] << ", "; tout << memoize_res << ");\n";); } void imdd_manager::mk_join( imdd * d1, imdd * d2, imdd_ref & r, unsigned_vector const& vars1, unsigned_vector const& vars2, bool memoize_res) { SASSERT(vars1.size() == vars2.size()); imdd_ref tmp(*this); unsigned d1_arity = d1->get_arity(); mk_product(d1, d2, tmp); for (unsigned i = 0; i < vars1.size(); ++i) { unsigned vars[2] = { vars1[i], vars2[i] + d1_arity }; mk_filter_identical_dupdt(tmp, 2, vars); } r = tmp; // memoize_new_imdd_if(memoize_res, tmp); } #if 0 void imdd_manager::mk_join_project( imdd * d1, imdd * d2, imdd_ref & r, unsigned_vector const& vars1, unsigned_vector const& vars2, unsigned_vector const& proj_vars, bool memoize_res) { SASSERT(vars1.size() == vars2.size()); imdd_ref tmp(*this); unsigned d1_arity = d1->get_arity(); mk_product(d1, d2, tmp); for (unsigned i = 0; i < vars1.size(); ++i) { unsigned vars[2] = { vars1[i], vars2[i] + d1_arity }; mk_filter_identical(tmp, tmp, 2, vars); } mk_project(tmp, tmp, proj_vars.size(), proj_vars.c_ptr()); TRACE("dl", tout << "vars: "; for (unsigned i = 0; i < vars1.size(); ++i) tout << vars1[i] << ":" << vars2[i] << " "; tout << "\n"; tout << "proj: "; for (unsigned i = 0; i < proj_vars.size(); ++i) tout << proj_vars[i] << " "; tout << "\n"; tout << "arity: " << d1->get_arity() << " + " << d2->get_arity() << "\n"; display_ll(tout << "d1\n", d1); display_ll(tout << "d2\n", d2); display_ll(tout << "result\n", tmp); tout << "\n"; ); r = tmp; // memoize_new_imdd_if(memoize_res, tmp); } #endif void imdd_manager::mk_join_project( imdd * d1, imdd * d2, imdd_ref & r, unsigned_vector const& vars1, unsigned_vector const& vars2, unsigned_vector const& proj_vars, bool memoize_res) { SASSERT(vars1.size() == vars2.size()); imdd_ref tmp(*this); mk_product(d1, d2, tmp); unsigned d1_arity = d1->get_arity(); unsigned sz = tmp->get_arity(); // set up schedule for join-project. unsigned_vector remap; svector projected; unsigned_vector ref_count; for (unsigned i = 0; i < sz; ++i) { remap.push_back(i); } projected.resize(sz, false); ref_count.resize(sz, 0); for (unsigned i = 0; i < proj_vars.size(); ++i) { projected[proj_vars[i]] = true; } for (unsigned i = 0; i < vars1.size(); ++i) { ref_count[vars1[i]]++; ref_count[vars2[i]+d1_arity]++; } #define UPDATE_REMAP() \ for (unsigned i = 0, j = 0; i < sz; ++i) { \ remap[i] = j; \ if (!projected[i] || ref_count[i] > 0) { \ ++j; \ } \ } \ UPDATE_REMAP(); // project unused variables: unsigned_vector proj2; for (unsigned i = 0; i < sz; ++i) { if (projected[i] && ref_count[i] == 0) { proj2.push_back(i); } } mk_project(tmp, tmp, proj2.size(), proj2.c_ptr()); for (unsigned i = 0; i < vars1.size(); ++i) { unsigned idx1 = vars1[i]; unsigned idx2 = vars2[i]+d1_arity; ref_count[idx1]--; ref_count[idx2]--; bool del1 = ref_count[idx1] == 0 && projected[idx1]; bool del2 = ref_count[idx2] == 0 && projected[idx2]; filter_identical_main3(tmp, tmp, remap[idx1], del1, remap[idx2], del2, memoize_res); if (del1 || del2) { UPDATE_REMAP(); } } TRACE("imdd", tout << "vars: "; for (unsigned i = 0; i < vars1.size(); ++i) tout << vars1[i] << ":" << vars2[i] << " "; tout << "\n"; tout << "proj: "; for (unsigned i = 0; i < proj_vars.size(); ++i) tout << proj_vars[i] << " "; tout << "\n"; tout << "arity: " << d1->get_arity() << " + " << d2->get_arity() << "\n"; display_ll(tout << "d1\n", d1); display_ll(tout << "d2\n", d2); display_ll(tout << "result\n", tmp); tout << "\n"; ); r = tmp; // memoize_new_imdd_if(memoize_res, tmp); } imdd * imdd_manager::mk_swap_new_child(unsigned lower, unsigned upper, imdd * grandchild) { if (m_swap_new_child == 0) { m_swap_new_child = _mk_empty(grandchild == 0 ? 1 : grandchild->get_arity() + 1); add_child(m_swap_new_child, lower, upper, grandchild); if (grandchild && !grandchild->is_memoized()) m_swap_granchildren_memoized = false; inc_ref(m_swap_new_child); } SASSERT(m_swap_new_child != 0); return m_swap_new_child; } void imdd_manager::mk_swap_acc1_dupdt(imdd_ref & d, unsigned lower, unsigned upper, imdd * grandchild, bool memoize_res) { SASSERT(d->get_ref_count() == 1); sbuffer to_insert; imdd_children::ext_iterator it; imdd_children::ext_iterator end; d->m_children.move_geq(it, lower); while (it != end && lower <= upper) { imdd_children::entry const & curr_entry = *it; unsigned curr_entry_begin_key = curr_entry.begin_key(); unsigned curr_entry_end_key = curr_entry.end_key(); imdd * curr_entry_val = curr_entry.val(); bool move_head = true; if (upper < curr_entry_begin_key) break; if (lower < curr_entry_begin_key) { to_insert.push_back(entry(lower, curr_entry_begin_key - 1, grandchild)); lower = curr_entry_begin_key; } SASSERT(lower >= curr_entry_begin_key); imdd * curr_grandchild = curr_entry_val; imdd_ref new_grandchild(*this); SASSERT((curr_grandchild == 0) == (grandchild == 0)); if (curr_grandchild != 0) { bool cover = lower == curr_entry_begin_key && upper >= curr_entry_end_key; // If cover == true, then the curr_child is completely covered, and it is not needed anymore. // So, we can perform a destructive update. if (cover) { new_grandchild = curr_grandchild; // take over the ownership of curr_grandchild it.erase(m_sl_manager); move_head = false; // it.erase is effectively moving the head. mk_union_core_dupdt(new_grandchild, grandchild, memoize_res); } else { mk_union_core(curr_grandchild, grandchild, new_grandchild, memoize_res); } if (!new_grandchild->is_memoized()) m_swap_granchildren_memoized = false; // protect new_grandchild, since it will be stored in to_insert. new_grandchild->inc_ref(); } if (upper >= curr_entry_end_key) { to_insert.push_back(entry(lower, curr_entry_end_key, new_grandchild)); } else { to_insert.push_back(entry(lower, upper, new_grandchild)); } lower = curr_entry_end_key + 1; if (move_head) ++it; } svector::iterator it2 = to_insert.begin(); svector::iterator end2 = to_insert.end(); for (; it2 != end2; ++it2) { imdd_children::entry const & curr_entry = *it2; add_child(d, curr_entry.begin_key(), curr_entry.end_key(), curr_entry.val()); if (curr_entry.val()) curr_entry.val()->dec_ref(); // to_insert will be destroyed, so decrement ref. } if (lower <= upper) { add_child(d, lower, upper, grandchild); } TRACE("mk_swap_bug", tout << "after mk_swap_acc1_dupt\n" << mk_ll_pp(d, *this) << "\n";); } void imdd_manager::mk_swap_acc1(imdd * d, imdd_ref & r, unsigned lower, unsigned upper, imdd * grandchild, bool memoize_res) { copy(d, r); imdd_children::iterator it = d->m_children.find_geq(lower); imdd_children::iterator end = d->end_children(); for (; it != end && lower <= upper; ++it) { imdd_children::entry const & curr_entry = *it; if (upper < curr_entry.begin_key()) break; if (lower < curr_entry.begin_key()) { SASSERT(lower <= curr_entry.begin_key() - 1); add_child(r, lower, curr_entry.begin_key() - 1, grandchild); lower = curr_entry.begin_key(); } SASSERT(lower >= curr_entry.begin_key()); imdd * curr_grandchild = curr_entry.val(); SASSERT((curr_grandchild == 0) == (grandchild == 0)); imdd_ref new_curr_grandchild(*this); mk_union_core(curr_grandchild, grandchild, new_curr_grandchild, memoize_res); if (new_curr_grandchild && !new_curr_grandchild->is_memoized()) m_swap_granchildren_memoized = false; if (upper >= curr_entry.end_key()) { add_child(r, lower, curr_entry.end_key(), new_curr_grandchild); } else { SASSERT(upper < curr_entry.end_key()); SASSERT(lower <= upper); add_child(r, lower, upper, new_curr_grandchild); } lower = curr_entry.end_key() + 1; } if (lower <= upper) { add_child(r, lower, upper, grandchild); } TRACE("mk_swap_bug", tout << "after mk_swap_acc1\n" << mk_ll_pp(r, *this) << "\n";); } /** \brief Auxiliary function for mk_swap_core */ void imdd_manager::mk_swap_acc2(imdd_ref & r, unsigned lower1, unsigned upper1, unsigned lower2, unsigned upper2, imdd * grandchild, bool memoize_res) { SASSERT((r->get_arity() == 2 && grandchild == 0) || (r->get_arity() == grandchild->get_arity() + 2)); SASSERT(r->get_ref_count() <= 1); // we perform destructive updates on r. SASSERT(!r->is_memoized()); TRACE("mk_swap_bug", tout << mk_ll_pp(r, *this) << "\n"; tout << "adding\n[" << lower1 << ", " << upper1 << "] -> [" << lower2 << ", " << upper2 << "] ->\n"; tout << mk_ll_pp(grandchild, *this) << "\n";); sbuffer to_insert; imdd_children::ext_iterator it; imdd_children::ext_iterator end; r->m_children.move_geq(it, lower1); SASSERT(m_swap_new_child == 0); while(it != end && lower1 <= upper1) { imdd_children::entry const & curr_entry = *it; unsigned curr_entry_begin_key = curr_entry.begin_key(); unsigned curr_entry_end_key = curr_entry.end_key(); imdd * curr_entry_val = curr_entry.val(); bool move_head = true; TRACE("mk_swap_bug", tout << lower1 << " " << upper1 << " " << curr_entry_begin_key << "\n";); if (upper1 < curr_entry_begin_key) break; if (lower1 < curr_entry_begin_key) { imdd * new_child = mk_swap_new_child(lower2, upper2, grandchild); SASSERT(lower1 <= curr_entry_begin_key - 1); add_child(r, lower1, curr_entry_begin_key - 1, new_child); lower1 = curr_entry_begin_key; } SASSERT(lower1 >= curr_entry_begin_key); imdd * curr_child = curr_entry_val; SASSERT(curr_child != 0); SASSERT(!curr_child->is_memoized()); imdd_ref new_curr_child(*this); bool destructive = curr_child->get_ref_count() == 1 && lower1 == curr_entry_begin_key && upper1 >= curr_entry_end_key; if (destructive) { new_curr_child = curr_child; // take over curr_child. it.erase(m_sl_manager); move_head = false; // it.erase is effectively moving the head. mk_swap_acc1_dupdt(new_curr_child, lower2, upper2, grandchild, memoize_res); } else { mk_swap_acc1(curr_child, new_curr_child, lower2, upper2, grandchild, memoize_res); } new_curr_child->inc_ref(); // it will be stored in to_insert if (upper1 >= curr_entry_end_key) { to_insert.push_back(entry(lower1, curr_entry_end_key, new_curr_child)); } else { to_insert.push_back(entry(lower1, upper1, new_curr_child)); } lower1 = curr_entry_end_key + 1; if (move_head) ++it; } svector::iterator it2 = to_insert.begin(); svector::iterator end2 = to_insert.end(); for (; it2 != end2; ++it2) { imdd_children::entry const & curr_entry = *it2; add_child(r, curr_entry.begin_key(), curr_entry.end_key(), curr_entry.val()); SASSERT(curr_entry.val()); curr_entry.val()->dec_ref(); // to_insert will be destroyed, so decrement ref. } if (lower1 <= upper1) { imdd * new_child = mk_swap_new_child(lower2, upper2, grandchild); add_child(r, lower1, upper1, new_child); } if (m_swap_new_child != 0) { dec_ref(m_swap_new_child); m_swap_new_child = 0; } TRACE("mk_swap_bug", tout << "after mk_swap_acc2\n" << mk_ll_pp(r, *this) << "\n";); } /** \brief Memoize the given IMDD assuming all grandchildren of d are memoized. */ imdd * imdd_manager::mk_swap_memoize(imdd * d) { if (d->is_memoized()) return d; bool children_memoized = true; imdd * new_d = _mk_empty(d->get_arity()); imdd_children::push_back_proc push_back(m_sl_manager, new_d->m_children); imdd_children::iterator it = d->begin_children(); imdd_children::iterator end = d->end_children(); for (; it != end; ++it) { imdd * child = it->val(); imdd * new_child = memoize(child); if (!new_child->is_memoized()) children_memoized = false; push_back(it->begin_key(), it->end_key(), new_child); } if (children_memoized && is_simple_node(new_d)) { imdd * new_can_d = memoize(new_d); if (new_can_d != new_d) { SASSERT(new_d->get_ref_count() == 0); delete_imdd(new_d); } new_d = new_can_d; } return new_d; } /** \brief Swap the two top vars. */ void imdd_manager::mk_swap_top_vars(imdd * d, imdd_ref & r, bool memoize_res) { SASSERT(d->get_arity() >= 2); r = _mk_empty(d->get_arity()); m_swap_granchildren_memoized = true; m_swap_new_child = 0; imdd_children::iterator it = d->begin_children(); imdd_children::iterator end = d->end_children(); for (; it != end; ++it) { imdd * curr_child = it->val(); SASSERT(curr_child != 0); SASSERT(m_swap_new_child == 0); imdd_children::iterator it2 = curr_child->begin_children(); imdd_children::iterator end2 = curr_child->end_children(); for (; it2 != end2; ++it2) { imdd * grandchild = it2->val(); mk_swap_acc2(r, it2->begin_key(), it2->end_key(), it->begin_key(), it->end_key(), grandchild, memoize_res); } SASSERT(m_swap_new_child == 0); } if (memoize_res && m_swap_granchildren_memoized) { r = mk_swap_memoize(r); } TRACE("mk_swap_bug", tout << "result:\n" << mk_ll_pp(r, *this) << "\n";); } void imdd_manager::mk_swap_core(imdd * d, imdd_ref & r, unsigned vidx, bool memoize_res) { SASSERT(d); unsigned arity = d->get_arity(); SASSERT(arity > 1); imdd * _r = 0; if (is_cached(m_swap_cache, d, _r)) { r = _r; return; } if (vidx == 0) { mk_swap_top_vars(d, r, memoize_res); } else { SASSERT(vidx > 0); bool new_children_memoized = true; unsigned new_vidx = vidx - 1; _r = _mk_empty(arity); imdd_children::push_back_proc push_back(m_sl_manager, _r->m_children); imdd_children::iterator it = d->begin_children(); imdd_children::iterator end = d->end_children(); SASSERT(it != end); for (; it != end; ++it) { imdd * curr_child = it->val(); imdd_ref new_child(*this); mk_swap_core(curr_child, new_child, new_vidx, memoize_res); push_back(it->begin_key(), it->end_key(), new_child); if (new_child != 0 && !new_child->is_memoized()) new_children_memoized = false; } SASSERT(!_r->empty()); _r = memoize_new_imdd_if(memoize_res && new_children_memoized, _r); r = _r; } cache_result(m_swap_cache, d, r); } void imdd_manager::mk_swap_dupdt_core(imdd_ref & d, unsigned vidx, bool memoize_res) { SASSERT(d); unsigned arity = d->get_arity(); SASSERT(arity > 1); if (!destructive_update_at(true, d)) { mk_swap_core(d, d, vidx, memoize_res); return; } if (vidx == 0) { mk_swap_top_vars(d, d, memoize_res); return; } SASSERT(vidx > 0); sbuffer to_insert; unsigned new_vidx = vidx-1; imdd_children::iterator it = d->begin_children(); imdd_children::iterator end = d->end_children(); for (; it != end; ++it) { imdd_ref new_child(*this); it.take_curr_ownership(m_sl_manager, new_child); mk_swap_dupdt_core(new_child, new_vidx, memoize_res); new_child->inc_ref(); // protect new child, since we insert into to_insert to_insert.push_back(entry(it->begin_key(), it->end_key(), new_child)); } SASSERT(!to_insert.empty()); d->replace_children(m_sl_manager, to_insert); } void imdd_manager::mk_swap_dupdt(imdd_ref & d, unsigned vidx, bool memoize_res) { SASSERT(d); unsigned arity = d->get_arity(); SASSERT(arity > 1); SASSERT(vidx < arity - 1); if (d->empty()) { return; } m_swap_cache.reset(); reset_union_cache(); delay_dealloc delay(*this); mk_swap_dupdt_core(d, vidx, memoize_res); } void imdd_manager::mk_swap(imdd * d, imdd_ref & r, unsigned vidx, bool memoize_res) { SASSERT(d); unsigned arity = d->get_arity(); SASSERT(arity > 1); SASSERT(vidx < arity - 1); if (d->empty()) { r = d; return; } TRACE("mk_swap_bug", tout << "mk_swap: " << vidx << "\n"; display_ll(tout, d); ); TRACE("mk_swap_bug2", tout << "mk_swap: " << vidx << "\n"; display_ll(tout, d); ); m_swap_cache.reset(); reset_union_cache(); delay_dealloc delay(*this); mk_swap_core(d, r, vidx, memoize_res); TRACE("mk_swap_bug2", tout << "mk_swap result\n"; display_ll(tout, r); ); STRACE("imdd_trace", tout << "mk_swap(0x" << d << ", 0x" << r.get() << ", " << vidx << ", " << memoize_res << ");\n";); } imdd_manager::iterator::iterator(imdd_manager const & m, imdd const * d) { if (d->empty()) { m_done = true; return; } m_done = false; unsigned arity = d->get_arity(); m_iterators.resize(arity); m_element.resize(arity); begin_iterators(d, 0); } void imdd_manager::iterator::begin_iterators(imdd const * curr, unsigned start_idx) { for (unsigned i = start_idx; i < get_arity(); i++) { m_iterators[i] = curr->begin_children(); imdd_children::iterator & it = m_iterators[i]; SASSERT(!it.at_end()); m_element[i] = it->begin_key(); curr = it->val(); } } imdd_manager::iterator & imdd_manager::iterator::operator++() { unsigned i = get_arity(); while (i > 0) { --i; imdd_children::iterator & it = m_iterators[i]; if (m_element[i] < it->end_key()) { m_element[i]++; begin_iterators(it->val(), i+1); return *this; } else { ++it; if (!it.at_end()) { m_element[i] = it->begin_key(); begin_iterators(it->val(), i+1); return *this; } } } m_done = true; // at end... return *this; } bool imdd_manager::iterator::operator==(iterator const & it) const { if (m_done && it.m_done) return true; if (m_done || it.m_done) return false; if (m_element.size() != it.m_element.size()) return false; unsigned sz = m_element.size(); for (unsigned i = 0; i < sz; i++) if (m_element[i] != it.m_element[i]) return false; return true; } bool imdd_manager::is_equal_core(imdd * d1, imdd * d2) { if (d1 == d2) return true; if (d1->is_memoized() && d2->is_memoized()) return false; SASSERT(d1->get_arity() == d2->get_arity()); SASSERT(!d1->empty()); SASSERT(!d2->empty()); bool shared = d1->is_shared() && d2->is_shared(); bool r; if (shared && m_is_equal_cache.find(d1, d2, r)) return r; if (d1->get_arity() == 1) { r = d1->m_children.is_equal(d2->m_children); } else { imdd_children::iterator it1 = d1->begin_children(); imdd_children::iterator end1 = d1->end_children(); imdd_children::iterator it2 = d2->begin_children(); imdd_children::iterator end2 = d2->end_children(); SASSERT(it1 != end1); SASSERT(it2 != end2); if (it1->begin_key() != it2->begin_key()) { r = false; } else { while (true) { if (it1 == end1) { r = (it2 == end2); break; } if (it2 == end2) { r = (it1 == end1); break; } SASSERT(it1->val() != 0 && it2->val() != 0); if (!is_equal_core(it1->val(), it2->val())) { r = false; break; } if (it1->end_key() < it2->end_key()) { unsigned prev_end_key = it1->end_key(); ++it1; if (it1 == end1 || it1->begin_key() != prev_end_key + 1) { r = false; break; } } else if (it1->end_key() > it2->end_key()) { unsigned prev_end_key = it2->end_key(); ++it2; if (it2 == end2 || it2->begin_key() != prev_end_key + 1) { r = false; break; } } else { SASSERT(it1->end_key() == it2->end_key()); ++it1; ++it2; } } } } if (shared) m_is_equal_cache.insert(d1, d2, r); return r; } bool imdd_manager::is_equal(imdd * d1, imdd * d2) { if (d1 == d2) return true; if (d1->is_memoized() && d2->is_memoized()) return false; if (d1->get_arity() != d2->get_arity()) return false; if (d1->empty() && d2->empty()) return true; if (d1->empty() || d2->empty()) return false; SASSERT(!d1->empty()); SASSERT(!d2->empty()); m_is_equal_cache.reset(); return is_equal_core(d1, d2); } /** \brief Return true if the given imdd contains the tuple (values[0], ..., values[num-1]) */ bool imdd_manager::contains(imdd * d, unsigned num, unsigned const * values) const { SASSERT(d->get_arity() == num); imdd * curr = d; for (unsigned i = 0; i < num; i++) { imdd * child; if (!curr->m_children.find(values[i], child)) return false; curr = child; } return true; } inline bool overlap(unsigned b1, unsigned e1, unsigned b2, unsigned e2) { // [b1, e1] [b2, e2] if (e1 < b2) return false; // [b2, e2] [b1, e1] if (e2 < b1) return false; return true; } bool imdd_manager::subsumes_core(imdd * d1, imdd * d2) { if (d1 == d2) return true; SASSERT(d1->get_arity() == d2->get_arity()); SASSERT(!d1->empty()); SASSERT(!d2->empty()); bool shared = d1->is_shared() && d2->is_shared(); bool r; if (shared && m_subsumes_cache.find(d1, d2, r)) return r; imdd_children::iterator it1 = d1->begin_children(); imdd_children::iterator end1 = d1->end_children(); imdd_children::iterator it2 = d2->begin_children(); imdd_children::iterator end2 = d2->end_children(); SASSERT(it1 != end1); SASSERT(it2 != end2); if (it1->begin_key() > it2->begin_key()) { r = false; goto subsumes_end; } if (d1->get_arity() == 1) { // leaf case while(true) { SASSERT(it1 != end1); SASSERT(it2 != end2); it1.move_to(it2->begin_key()); if (it1 == end1 || it1->begin_key() > it2->begin_key() || // missed beginning of it2 curr entry it1->end_key() < it2->end_key() // missed end of it2 curr entry ) { r = false; goto subsumes_end; } SASSERT(it1->end_key() >= it2->end_key()); ++it2; if (it2 == end2) { r = true; goto subsumes_end; } } } else { // non-leaf case while (true) { SASSERT(it1 != end1); SASSERT(it2 != end2); SASSERT(it1->val() != 0); SASSERT(it2->val() != 0); it1.move_to(it2->begin_key()); if (it1 == end1 || it1->begin_key() > it2->begin_key() // missed beginning of it2 curr entry ) { r = false; goto subsumes_end; } // the beginning of it2 is inside it1 SASSERT(it2->begin_key() >= it1->begin_key() && it2->begin_key() <= it1->end_key()); // it1: [ ][ ][ ] // it2 [ ] while (true) { SASSERT(it1 != end1); SASSERT(it2 != end2); // there is a overlap between the current entry in it1 and the current entry in it2 SASSERT(overlap(it1->begin_key(), it1->end_key(), it2->begin_key(), it2->end_key())); if (!subsumes_core(it1->val(), it2->val())) { r = false; goto subsumes_end; } if (it1->end_key() >= it2->end_key()) { ++it2; // processed the whole entry in it2 break; } SASSERT(it1->end_key() < it2->end_key()); unsigned prev_end_key = it1->end_key(); ++it1; if (it1 == end1 || it1->begin_key() != prev_end_key + 1) { r = false; goto subsumes_end; } } if (it2 == end2) { r = true; goto subsumes_end; } } } subsumes_end: if (shared) m_subsumes_cache.insert(d1, d2, r); return r; } /** \brief Return true is d1 is a superset of d2, or equal to d2. */ bool imdd_manager::subsumes(imdd * d1, imdd * d2) { SASSERT(d1->get_arity() == d2->get_arity()); if (d1 == d2) return true; if (d2->empty()) return true; if (d1->empty()) { SASSERT(!d2->empty()); return false; } SASSERT(!d1->empty()); SASSERT(!d2->empty()); m_subsumes_cache.reset(); return subsumes_core(d1, d2); } void imdd_manager::remove_facts_dupdt(imdd_ref & d, unsigned num, unsigned const * lowers, unsigned const * uppers) { SASSERT(d->get_arity() == num); d = remove_facts_main(d, num, lowers, uppers, true, true); } void imdd_manager::remove_facts(imdd * d, imdd_ref & r, unsigned num, unsigned const * lowers, unsigned const * uppers) { SASSERT(d->get_arity() == num); r = remove_facts_main(d, num, lowers, uppers, false, true); } imdd * imdd_manager::remove_facts_main(imdd * d, unsigned num, unsigned const * lowers, unsigned const * uppers, bool destructive, bool memoize_res) { SASSERT(d->get_arity() == num); delay_dealloc delay(*this); m_remove_facts_cache.reset(); return remove_facts_core(d, num, lowers, uppers, destructive, memoize_res); } imdd * imdd_manager::remove_facts_core(imdd * d, unsigned num, unsigned const * lowers, unsigned const * uppers, bool destructive, bool memoize_res) { SASSERT(d->get_arity() == num && num > 0); imdd * new_d = 0; unsigned b = *lowers; unsigned e = *uppers; sbuffer to_insert, to_remove; imdd_children::iterator it = d->m_children.find_geq(b); imdd_children::iterator end = d->end_children(); bool is_destructive = destructive_update_at(destructive, d); if (!is_destructive && is_cached(m_remove_facts_cache, d, new_d)) { return new_d; } if (num == 1) { new_d = remove_main(d, *lowers, *uppers, destructive, memoize_res); if (!is_destructive) { cache_result(m_remove_facts_cache, d, new_d); } return new_d; } if (it == end || e < it->begin_key()) { if (!is_destructive) { cache_result(m_remove_facts_cache, d, d); } return d; } if (!is_destructive) { new_d = copy_main(d); } else { new_d = d; } for (; it != end && b <= e; ++it) { // // remove ([b:e]::rest), [b_key:e_key] * ch) = // { [b_key:e_key] * ch } if e < b_key // { [b_key:e_key] * ch' } if b <= b_key <= e_key <= e // { [b_key:e]*ch' [e+1:e_key]*ch } if b <= b_key <= e < e_key // { [b_key:b-1]*ch [b:e]*ch', [e+1:e_key]*ch } if b_key < b <= e < e_key // { [b_key:b-1]*ch [b:e_key]*ch' } if b_key < b <= e_key <= e // where // ch' = remove(rest, ch) // assumption: remove_child retains the cases where ch is in the tree. // imdd_children::entry const & curr_entry = *it; unsigned b_key = curr_entry.begin_key(); unsigned e_key = curr_entry.end_key(); imdd* curr_child = curr_entry.val(); imdd* new_curr_child = 0; if (e < b_key) { break; } new_curr_child = remove_facts_core(curr_child, num-1, lowers+1, uppers+1, destructive, memoize_res); if (new_curr_child == curr_child) { continue; } if (new_curr_child != 0) { if (b <= b_key && e_key <= e) { to_insert.push_back(entry(b_key, e_key, new_curr_child)); } if (b <= b_key && e < e_key) { to_insert.push_back(entry(b_key, e, new_curr_child)); } if (b_key < b && e < e_key) { to_insert.push_back(entry(b, e, new_curr_child)); } if (b_key < b && e_key <= e) { to_insert.push_back(entry(b, e_key, new_curr_child)); } } if (is_destructive) { to_remove.push_back(entry(b, e, 0)); } else { remove_child(new_d, b, e); } b = e_key + 1; } for (unsigned i = 0; i < to_remove.size(); ++i) { remove_child(new_d, to_remove[i].begin_key(), to_remove[i].end_key()); } for (unsigned i = 0; i < to_insert.size(); ++i) { add_child(new_d, to_insert[i].begin_key(), to_insert[i].end_key(), to_insert[i].val()); } if (!is_destructive) { cache_result(m_remove_facts_cache, d, new_d); } return new_d; } void imdd_manager::display(std::ostream & out, imdd const * d, unsigned_vector & intervals, bool & first) const { imdd_children::iterator it = d->begin_children(); imdd_children::iterator end = d->end_children(); if (d->get_arity() == 1) { for (; it != end; ++it) { SASSERT(it->val() == 0); if (first) { first = false; } else { out << ",\n "; } out << "("; unsigned sz = intervals.size(); for (unsigned i = 0; i < sz; i+=2) { out << "[" << intervals[i] << ", " << intervals[i+1] << "]"; out << ", "; } out << "[" << it->begin_key() << ", " << it->end_key() << "])"; } } else { for (; it != end; ++it) { intervals.push_back(it->begin_key()); intervals.push_back(it->end_key()); display(out, it->val(), intervals, first); intervals.pop_back(); intervals.pop_back(); } } } void imdd_manager::display(std::ostream & out, imdd const * d) const { unsigned_vector intervals; bool first = true; out << "{"; display(out, d, intervals, first); out << "}"; } struct display_ll_context { typedef map, default_eq > imdd2uint; std::ostream & m_out; unsigned m_next_id; imdd2uint m_map; display_ll_context(std::ostream & out):m_out(out), m_next_id(1) {} void display_tabs(unsigned num_tabs) { for (unsigned i = 0; i < num_tabs; i++) m_out << " "; } void display_node(unsigned num_tabs, imdd const * d) { imdd_children::iterator it = d->begin_children(); imdd_children::iterator end = d->end_children(); if (it == end) { m_out << "{}"; return; } if (d->get_arity() == 1) { // leaf m_out << "{"; for (bool first = true; it != end; ++it) { if (first) first = false; else m_out << ", "; m_out << "[" << it->begin_key() << ", " << it->end_key() << "]"; } m_out << "}"; } else { m_out << "{\n"; display_tabs(num_tabs); while (it != end) { m_out << " [" << it->begin_key() << ", " << it->end_key() << "] -> "; display(num_tabs+1, it->val()); ++it; if (it != end) { m_out << "\n"; display_tabs(num_tabs); } else { m_out << "}"; } } } m_out << (d->is_memoized() ? "*" : "") << "$" << d->memory(); } void display(unsigned num_tabs, imdd const * d) { if (d == 0) { m_out << ""; return; } unsigned id; if (m_map.find(const_cast(d), id)) { m_out << "#" << id; } else if (d->is_shared()) { id = m_next_id; m_next_id++; m_map.insert(const_cast(d), id); m_out << "#" << id << ":"; display_node(num_tabs, d); } else { display_node(num_tabs, d); } } }; void imdd_manager::display_ll(std::ostream & out, imdd const * d) const { display_ll_context ctx(out); ctx.display(0, d); out << "\n"; } struct addone_proc { unsigned m_r; addone_proc():m_r(0) {} void operator()(imdd * d) { m_r++; } }; /** \brief Return the number of nodes in an IMDD. This is *not* a constant time operation. It is linear on the size of the IMDD */ unsigned imdd_manager::get_num_nodes(imdd const * d) const { addone_proc p; for_each(const_cast(d), p); return p.m_r; } struct addmem_proc { unsigned m_r; addmem_proc():m_r(0) {} void operator()(imdd * d) { m_r += d->memory(); } }; /** \brief Return the amount of memory consumed by the given IMDD. */ unsigned imdd_manager::memory(imdd const * d) const { addmem_proc p; for_each(const_cast(d), p); return p.m_r; } /** \brief Return number of rows the given IMDD represents. */ size_t imdd_manager::get_num_rows(imdd const* root) const { obj_map counts; ptr_vector todo; todo.push_back(root); while (!todo.empty()) { imdd const* d = todo.back(); if (counts.contains(d)) { todo.pop_back(); continue; } imdd_children::iterator it = d->begin_children(); imdd_children::iterator end = d->end_children(); bool all_valid = true; size_t count = 0, c; for (; it != end; ++it) { imdd const* ch = it->val(); if (!ch) { count += (it->end_key()-it->begin_key()+1); } else if (counts.find(ch, c)) { count += c*(it->end_key()-it->begin_key()+1); } else { all_valid = false; todo.push_back(ch); } } if (all_valid) { todo.pop_back(); counts.insert(d, count); } } return counts.find(root); } imdd * imdd_manager::add_bounded_var_core(imdd * d, unsigned before_vidx, unsigned lower, unsigned upper, bool destructive, bool memoize_res) { if (d == 0) { SASSERT(before_vidx == 0); imdd * r = _mk_empty(1); add_child(r, lower, upper, 0); r = memoize_new_imdd_if(memoize_res, r); return r; } imdd * r = 0; if (is_cached(m_add_bounded_var_cache, d, r)) return r; if (before_vidx == 0) { imdd * r = _mk_empty(d->get_arity() + 1); add_child(r, lower, upper, d); r = memoize_new_imdd_if(d->is_memoized() && memoize_res, r); return r; } if (destructive_update_at(destructive, d)) { sbuffer to_insert; imdd_children::iterator it = d->begin_children(); imdd_children::iterator end = d->end_children(); for (; it != end; ++it) { imdd * curr_child = it->val(); imdd * new_child = add_bounded_var_core(curr_child, before_vidx-1, lower, upper, true, memoize_res); new_child->inc_ref(); // we will be resetting d->m_children later. to_insert.push_back(entry(it->begin_key(), it->end_key(), new_child)); } SASSERT(!to_insert.empty()); d->replace_children(m_sl_manager, to_insert); return d; } bool new_children_memoized = true; r = _mk_empty(d->get_arity() + 1); imdd_children::push_back_proc push_back(m_sl_manager, r->m_children); imdd_children::iterator it = d->begin_children(); imdd_children::iterator end = d->end_children(); SASSERT(it != end); for (; it != end; ++it) { imdd * curr_child = it->val(); imdd * new_child = add_bounded_var_core(curr_child, before_vidx-1, lower, upper, false, memoize_res); push_back(it->begin_key(), it->end_key(), new_child); if (!new_child->is_memoized()) new_children_memoized = false; } r = memoize_new_imdd_if(r && memoize_res && new_children_memoized, r); cache_result(m_add_bounded_var_cache, d, r); return r; } /** \brief Add a variable (bounded by lower and upper) before the variable before_var. That is, for each tuple (v_1, ..., v_n) in the IMDD \c d, the resultant IMDD contains the tuples (v_1, ..., v_{after_vidx}, lower, ..., v_n) (v_1, ..., v_{after_vidx}, lower+1, ..., v_n) ... (v_1, ..., v_{after_vidx}, upper, ..., v_n) This function is mainly used to implement mk_filter. \pre after_vidx < d->get_arity() */ imdd * imdd_manager::add_bounded_var_main(imdd * d, unsigned before_vidx, unsigned lower, unsigned upper, bool destructive, bool memoize_res) { SASSERT(before_vidx <= d->get_arity()); if (d->empty()) return d; m_add_bounded_var_cache.reset(); delay_dealloc delay(*this); imdd * r = add_bounded_var_core(d, before_vidx, lower, upper, destructive, memoize_res); return r; } filter_cache_entry * imdd_manager::mk_filter_cache_entry(imdd * d, unsigned ctx_sz, unsigned * ctx, imdd * r) { void * mem = m_filter_entries.allocate(filter_cache_entry::get_obj_size(ctx_sz)); return new (mem) filter_cache_entry(d, r, ctx_sz, ctx); } imdd * imdd_manager::is_mk_filter_cached(imdd * d, unsigned ctx_sz, unsigned * ctx) { if (!d->is_shared()) return 0; m_filter_entries.push_scope(); filter_cache_entry * tmp_entry = mk_filter_cache_entry(d, ctx_sz, ctx, 0); filter_cache_entry * e = 0; bool r = m_filter_cache.find(tmp_entry, e); m_filter_entries.pop_scope(); if (!r || e->m_r->is_dead()) return 0; return e->m_r; } void imdd_manager::cache_mk_filter(imdd * d, unsigned ctx_sz, unsigned * ctx, imdd * r) { if (d->is_shared()) { filter_cache_entry * new_entry = mk_filter_cache_entry(d, ctx_sz, ctx, r); m_filter_cache.insert(new_entry); } } void imdd_manager::init_mk_filter(unsigned arity, unsigned num_vars, unsigned * vars) { m_filter_cache.reset(); m_filter_entries.reset(); m_filter_context.reset(); m_filter_num_vars = num_vars; m_filter_begin_vars = vars; m_filter_end_vars = vars + num_vars; DEBUG_CODE({ for (unsigned i = 0; i < num_vars; i++) { SASSERT(vars[i] < arity); } }); } template void imdd_manager::mk_filter_dupdt_core(imdd_ref & d, unsigned vidx, unsigned num_found, FilterProc & proc, bool memoize_res) { SASSERT(!d->empty()); if (!destructive_update_at(true, d)) { mk_filter_core(d, d, vidx, num_found, proc, memoize_res); return; } bool _is_filter_var = is_filter_var(vidx); if (_is_filter_var) num_found++; unsigned new_vidx = vidx+1; imdd_ref new_r(*this); imdd_children::iterator it = d->begin_children(); imdd_children::iterator end = d->end_children(); SASSERT(it != end); for (; it != end; ++it) { imdd_ref new_child(*this); it.take_curr_ownership(m_sl_manager, new_child); // take ownership of the current child. if (!_is_filter_var) { mk_filter_dupdt_core(new_child, new_vidx, num_found, proc, memoize_res); add_bounded_var_dupdt(new_child, num_found, it->begin_key(), it->end_key(), memoize_res); if (new_r == 0) new_r = new_child; else mk_union_core_dupdt(new_r, new_child, memoize_res); } else { m_filter_context.push_back(it->begin_key()); m_filter_context.push_back(it->end_key()); if (num_found < m_filter_num_vars) { mk_filter_dupdt_core(new_child, new_vidx, num_found, proc, memoize_res); } else { proc(m_filter_context.c_ptr(), new_child, new_child, memoize_res); } m_filter_context.pop_back(); m_filter_context.pop_back(); if (new_r == 0) new_r = new_child; else mk_union_core_dupdt(new_r, new_child, memoize_res); } } d->m_children.reset(m_sl_manager); d = new_r; } template void imdd_manager::mk_filter_core(imdd * d, imdd_ref & r, unsigned vidx, unsigned num_found, FilterProc & proc, bool memoize_res) { SASSERT(!d->empty()); imdd * _r = is_mk_filter_cached(d, m_filter_context.size(), m_filter_context.c_ptr()); if (_r) { r = _r; return; } bool _is_filter_var = is_filter_var(vidx); if (_is_filter_var) num_found++; unsigned new_vidx = vidx+1; imdd_ref new_r(*this); imdd_children::iterator it = d->begin_children(); imdd_children::iterator end = d->end_children(); SASSERT(it != end); for (; it != end; ++it) { imdd * curr_child = it->val(); imdd_ref new_child(*this); if (!_is_filter_var) { mk_filter_core(curr_child, new_child, new_vidx, num_found, proc, memoize_res); imdd_ref tmp(*this); add_bounded_var(new_child, tmp, num_found, it->begin_key(), it->end_key(), memoize_res); if (new_r == 0) new_r = tmp; else mk_union_core(new_r, tmp, new_r, memoize_res); } else { m_filter_context.push_back(it->begin_key()); m_filter_context.push_back(it->end_key()); if (num_found < m_filter_num_vars) { mk_filter_core(curr_child, new_child, new_vidx, num_found, proc, memoize_res); } else { proc(m_filter_context.c_ptr(), curr_child, new_child, memoize_res); } m_filter_context.pop_back(); m_filter_context.pop_back(); if (new_r == 0) new_r = new_child; else mk_union_core(new_r, new_child, new_r, memoize_res); } } r = new_r; cache_mk_filter(d, m_filter_context.size(), m_filter_context.c_ptr(), r); } template void imdd_manager::mk_filter_dupdt(imdd_ref & d, unsigned num_vars, unsigned * vars, FilterProc & proc, bool memoize_res) { if (d->empty()) return; unsigned arity = d->get_arity(); init_mk_filter(arity, num_vars, vars); mk_filter_dupdt_core(d, 0, 0, proc, memoize_res); if (d == 0) d = _mk_empty(arity); } template void imdd_manager::mk_filter(imdd * d, imdd_ref & r, unsigned num_vars, unsigned * vars, FilterProc & proc, bool memoize_res) { if (d->empty()) { r = d; return; } unsigned arity = d->get_arity(); init_mk_filter(arity, num_vars, vars); mk_filter_core(d, r, 0, 0, proc, memoize_res); if (r == 0) r = _mk_empty(arity); } imdd * imdd_manager::mk_distinct_imdd(unsigned l1, unsigned u1, unsigned l2, unsigned u2, imdd * d, bool memoize_res) { unsigned d_arity; if (d == 0) { d_arity = 0; } else { d_arity = d->get_arity(); memoize_res = memoize_res && d->is_memoized(); } imdd * r = _mk_empty(d_arity + 2); imdd_children::push_back_proc push_back(m_sl_manager, r->m_children); TRACE("mk_distinct_imdd", tout << "STARTING: " << l1 << " " << u1 << " " << l2 << " " << u2 << "\n";); #define ADD_ENTRY(L1, U1, L2, U2) { \ TRACE("mk_distinct_imdd", tout << "[" << L1 << " " << U1 << " " << L2 << " " << U2 << "]\n";); \ imdd * new_child = _mk_empty(d_arity + 1); \ add_child(new_child, L2, U2, d); \ new_child = memoize_new_imdd_if(memoize_res, new_child); \ push_back(L1, U1, new_child);} if (u1 < l2 || u2 < l1) { ADD_ENTRY(l1, u1, l2, u2); // the intervals are disjoint } else { if (l1 < l2) { SASSERT(u1 >= l2); // [l1 ... // [l2 ... // --> // [l1, l2-1] X [l2, u2] ADD_ENTRY(l1, l2-1, l2, u2); } unsigned l = std::max(l1, l2); unsigned u = std::min(u1, u2); // [l, l] X [l2, l-1] // if l-1 >= l2 (i.e., l > l2) // [l, l] X [l+1, u2] // [l+1, l+1] X [l2, l] // [l+1, l+1] X [l+2, u2] // [l+2, l+2] X [l2, l+1] // [l+2, l+2] X [l+3, u2] // ... // [u, u] X [l2, u-1] // [u, u] X [u+1, u2] // if u+1 <= u2 (i.e., u < u2) for (unsigned i = l; i <= u; i++) { if (i > l2 && i < u2) { // ADD_ENTRY(i, i, l2, i-1); // ADD_ENTRY(i, i, i+1, u2); imdd * new_child = _mk_empty(d_arity + 1); add_child(new_child, l2, i-1, d); add_child(new_child, i+1, u2, d); new_child = memoize_new_imdd_if(memoize_res, new_child); push_back(i, i, new_child); } else if (i > l2) { SASSERT(!(i < u2)); ADD_ENTRY(i, i, l2, i-1); } else if (i < u2) { SASSERT(!(i > l2)); ADD_ENTRY(i, i, i+1, u2); } } if (u1 > u2) { // ... u1] // ... u2] // --> // [u2+1, u1] X [l2, u2] SASSERT(u2 >= l1); ADD_ENTRY(u2+1, u1, l2, u2); } } #undef ADD_ENTRY r = memoize_new_imdd_if(memoize_res, r); return r; } /** \brief Auxiliary functor used to implement mk_filter_distinct */ struct distinct_proc { imdd_manager & m_manager; distinct_proc(imdd_manager & m): m_manager(m) { } void operator()(unsigned * lowers_uppers, imdd * d, imdd_ref & r, bool memoize_res) { r = m_manager.mk_distinct_imdd(lowers_uppers[0], lowers_uppers[1], lowers_uppers[2], lowers_uppers[3], d, memoize_res); } }; void imdd_manager::mk_filter_distinct_dupdt(imdd_ref & d, unsigned v1, unsigned v2, bool memoize_res) { unsigned vars[2] = { v1, v2 }; distinct_proc proc(*this); mk_filter_dupdt(d, 2, vars, proc, memoize_res); } void imdd_manager::mk_filter_distinct(imdd * d, imdd_ref & r, unsigned v1, unsigned v2, bool memoize_res) { unsigned vars[2] = { v1, v2 }; distinct_proc proc(*this); mk_filter(d, r, 2, vars, proc, memoize_res); STRACE("imdd_trace", tout << "mk_filter_distinct(0x" << d << ", 0x" << r.get() << ", " << v1 << ", " << v2 << ", " << memoize_res << ");\n";); } imdd * imdd_manager::mk_disequal_imdd(unsigned l1, unsigned u1, unsigned value, imdd * d, bool memoize_res) { unsigned d_arity; if (d == 0) { d_arity = 0; } else { d_arity = d->get_arity(); memoize_res = memoize_res && d->is_memoized(); } imdd * r = _mk_empty(d_arity + 1); if (value < l1 || value > u1) { add_child(r, l1, u1, d); } else { SASSERT(l1 <= value && value <= u1); if (l1 < value) { add_child(r, l1, value-1, d); } if (value < u1) { add_child(r, value+1, u1, d); } } r = memoize_new_imdd_if(memoize_res, r); return r; } struct disequal_proc { imdd_manager & m_manager; unsigned m_value; disequal_proc(imdd_manager & m, unsigned v): m_manager(m), m_value(v) { } void operator()(unsigned * lowers_uppers, imdd * d, imdd_ref & r, bool memoize_res) { r = m_manager.mk_disequal_imdd(lowers_uppers[0], lowers_uppers[1], m_value, d, memoize_res); } }; void imdd_manager::mk_filter_disequal_dupdt(imdd_ref & d, unsigned var, unsigned value, bool memoize_res) { unsigned vars[1] = { var }; disequal_proc proc(*this, value); mk_filter_dupdt(d, 1, vars, proc, memoize_res); } void imdd_manager::mk_filter_disequal(imdd * d, imdd_ref & r, unsigned var, unsigned value, bool memoize_res) { unsigned vars[1] = { var }; disequal_proc proc(*this, value); mk_filter(d, r, 1, vars, proc, memoize_res); STRACE("imdd_trace", tout << "mk_filter_disequal(0x" << d << ", 0x" << r.get() << ", " << var << ", " << value << ", " << memoize_res << ");\n";); }