#pragma once
#include "util/memory_manager.h"
#include "util/debug.h"
#include "util/trace.h"
#include "util/tptr.h"
#include "util/util.h"
#ifdef Z3DEBUG
#include "util/hashtable.h"
#endif
#define CH_STATISTICS
#ifdef CH_STATISTICS
#define CHS_CODE(CODE) { CODE }
#else
#define CHS_CODE(CODE)
#endif
template<typename T, typename HashProc, typename EqProc>
class chashtable : private HashProc, private EqProc {
public:
static const unsigned default_init_slots = 8;
static const unsigned default_init_cellar = 2;
protected:
struct cell {
cell * m_next = (cell*)1;
T m_data;
bool is_free() const { return GET_TAG(m_next) == 1; }
void mark_free() { m_next = TAG(cell*, m_next, 1); }
void unmark_free() { m_next = UNTAG(cell*, m_next); }
};
cell * m_table; unsigned m_capacity; unsigned m_init_slots;
unsigned m_init_cellar;
unsigned m_slots; unsigned m_used_slots; unsigned m_size; #ifdef CH_STATISTICS
unsigned m_collisions;
#endif
cell * m_next_cell;
cell * m_free_cell;
cell * m_tofree_cell;
unsigned get_hash(T const & d) const { return HashProc::operator()(d); }
bool equals(T const & e1, T const & e2) const { return EqProc::operator()(e1, e2); }
static cell * alloc_table(unsigned sz) {
return alloc_vect<cell>(sz);
}
void delete_table() {
dealloc_vect(m_table, m_capacity);
}
cell * copy_table(cell * source, unsigned source_slots, unsigned source_capacity,
cell * target, unsigned target_slots, unsigned target_capacity,
unsigned & used_slots) {
TRACE(chashtable, tout << "copy_table...\n";);
SASSERT(target_slots >= source_slots);
SASSERT(target_capacity >= source_capacity);
unsigned target_mask = target_slots - 1;
used_slots = 0;
cell * source_end = source + source_slots;
cell * target_cellar = target + target_slots;
cell * target_end = target + target_capacity;
for (cell * source_it = source; source_it != source_end; ++source_it) {
if (!source_it->is_free()) {
cell * list_it = source_it;
do {
unsigned h = get_hash(list_it->m_data);
unsigned idx = h & target_mask;
cell * target_it = target + idx;
SASSERT(target_it >= target);
SASSERT(target_it < target + target_slots);
if (target_it->is_free()) {
target_it->m_data = list_it->m_data;
target_it->m_next = nullptr;
used_slots++;
}
else {
SASSERT((get_hash(target_it->m_data) & target_mask) == idx);
if (target_cellar == target_end)
return nullptr; SASSERT(target_cellar >= target + target_slots);
SASSERT(target_cellar < target_end);
*target_cellar = *target_it;
target_it->m_data = list_it->m_data;
target_it->m_next = target_cellar;
target_cellar++;
}
SASSERT(!target_it->is_free());
list_it = list_it->m_next;
}
while (list_it != nullptr);
}
}
#if 0#endif
return target_cellar;
}
void expand_table() {
unsigned curr_cellar = (m_capacity - m_slots);
unsigned new_slots = m_slots * 2;
unsigned new_cellar = curr_cellar * 2;
if (new_slots < m_slots || new_cellar < curr_cellar)
throw default_exception("table overflow");
while (true) {
unsigned new_capacity = new_slots + new_cellar;
if (new_capacity < new_slots)
throw default_exception("table overflow");
cell * new_table = alloc_table(new_capacity);
cell * next_cell = copy_table(m_table, m_slots, m_capacity,
new_table, new_slots, new_capacity,
m_used_slots);
if (next_cell != nullptr) {
delete_table();
m_table = new_table;
m_capacity = new_capacity;
m_slots = new_slots;
m_next_cell = next_cell;
m_free_cell = nullptr;
m_tofree_cell = nullptr;
CASSERT("chashtable", check_invariant());
return;
}
dealloc_vect(new_table, new_capacity);
if (2*new_cellar < new_cellar)
throw default_exception("table overflow");
new_cellar *= 2;
}
}
bool has_free_cells() const {
return m_free_cell != nullptr || m_next_cell < m_table + m_capacity;
}
cell * get_free_cell() {
if (m_free_cell != nullptr) {
cell * c = m_free_cell;
m_free_cell = c->m_next;
return c;
}
else {
cell * c = m_next_cell;
m_next_cell++;
return c;
}
}
void recycle_cell(cell * c) {
SASSERT(c >= m_table + m_slots);
SASSERT(c < m_table + m_capacity);
c->m_next = m_free_cell;
m_free_cell = c;
}
void push_recycle_cell(cell* c) {
SASSERT(c < m_table + m_capacity);
c->m_next = m_tofree_cell;
m_tofree_cell = c;
}
void init(unsigned slots, unsigned cellar) {
m_capacity = slots + cellar;
m_table = alloc_table(m_capacity);
m_slots = slots;
m_used_slots = 0;
m_size = 0;
m_next_cell = m_table + slots;
m_free_cell = nullptr;
m_tofree_cell = nullptr;
}
public:
chashtable(HashProc const & h = HashProc(),
EqProc const & e = EqProc(),
unsigned init_slots = default_init_slots,
unsigned init_cellar = default_init_cellar):
HashProc(h),
EqProc(e) {
SASSERT(is_power_of_two(init_slots));
SASSERT(init_cellar > 0);
m_init_slots = init_slots;
m_init_cellar = init_cellar;
init(m_init_slots, m_init_cellar);
CHS_CODE(m_collisions = 0;);
}
~chashtable() {
#if 0#endif
delete_table();
}
void reset() {
if (m_size == 0)
return;
finalize();
}
void finalize() {
delete_table();
init(m_init_slots, m_init_cellar);
}
bool empty() const {
return m_size == 0;
}
unsigned size() const {
return m_size;
}
unsigned capacity() const {
return m_capacity;
}
unsigned used_slots() const {
return m_used_slots;
}
void insert_fresh(T const& d) {
if (!has_free_cells()) {
expand_table();
}
unsigned mask = m_slots - 1;
unsigned h = get_hash(d);
unsigned idx = h & mask;
cell * c = m_table + idx;
cell * new_c = nullptr;
m_size++;
if (c->is_free()) {
m_used_slots++;
}
else {
new_c = get_free_cell();
*new_c = *c;
}
c->m_next = new_c;
c->m_data = d;
CASSERT("chashtable_bug", check_invariant());
}
void insert(T const & d) {
if (!has_free_cells())
expand_table();
unsigned mask = m_slots - 1;
unsigned h = get_hash(d);
unsigned idx = h & mask;
cell * c = m_table + idx;
if (c->is_free()) {
m_size++;
m_used_slots++;
c->m_data = d;
c->m_next = nullptr;
CASSERT("chashtable_bug", check_invariant());
return;
}
else {
cell * it = c;
do {
if (equals(it->m_data, d)) {
it->m_data = d;
CASSERT("chashtable_bug", check_invariant());
return;
}
CHS_CODE(m_collisions++;);
it = it->m_next;
}
while (it != nullptr);
m_size++;
cell * new_c = get_free_cell();
*new_c = *c;
c->m_data = d;
c->m_next = new_c;
CASSERT("chashtable_bug", check_invariant());
return;
}
}
T & insert_if_not_there(T const & d) {
if (!has_free_cells())
expand_table();
unsigned mask = m_slots - 1;
unsigned h = get_hash(d);
unsigned idx = h & mask;
cell * c = m_table + idx;
if (c->is_free()) {
m_size++;
m_used_slots++;
c->m_data = d;
c->m_next = nullptr;
CASSERT("chashtable_bug", check_invariant());
return c->m_data;
}
else {
cell * it = c;
do {
if (equals(it->m_data, d)) {
CASSERT("chashtable_bug", check_invariant());
return it->m_data;
}
CHS_CODE(m_collisions++;);
it = it->m_next;
}
while (it != nullptr);
m_size++;
cell * new_c = get_free_cell();
*new_c = *c;
c->m_data = d;
c->m_next = new_c;
CASSERT("chashtable_bug", check_invariant());
return c->m_data;
}
}
bool insert_if_not_there2(T const & d) {
if (!has_free_cells())
expand_table();
unsigned mask = m_slots - 1;
unsigned h = get_hash(d);
unsigned idx = h & mask;
cell * c = m_table + idx;
if (c->is_free()) {
m_size++;
m_used_slots++;
c->m_data = d;
c->m_next = nullptr;
CASSERT("chashtable_bug", check_invariant());
return true;
}
else {
cell * it = c;
do {
if (equals(it->m_data, d)) {
CASSERT("chashtable_bug", check_invariant());
return false;
}
CHS_CODE(m_collisions++;);
it = it->m_next;
}
while (it != nullptr);
m_size++;
cell * new_c = get_free_cell();
*new_c = *c;
c->m_data = d;
c->m_next = new_c;
CASSERT("chashtable_bug", check_invariant());
return true;
}
}
bool contains(T const & d) const {
unsigned mask = m_slots - 1;
unsigned h = get_hash(d);
unsigned idx = h & mask;
cell * c = m_table + idx;
if (c->is_free())
return false;
do {
if (equals(c->m_data, d)) {
return true;
}
CHS_CODE(const_cast<chashtable*>(this)->m_collisions++;);
c = c->m_next;
}
while (c != nullptr);
return false;
}
T * find_core(T const & d) const {
unsigned mask = m_slots - 1;
unsigned h = get_hash(d);
unsigned idx = h & mask;
cell * c = m_table + idx;
if (c->is_free())
return nullptr;
do {
if (equals(c->m_data, d)) {
return &(c->m_data);
}
CHS_CODE(const_cast<chashtable*>(this)->m_collisions++;);
c = c->m_next;
}
while (c != nullptr);
return nullptr;
}
bool find(T const & d, T & r) {
unsigned mask = m_slots - 1;
unsigned h = get_hash(d);
unsigned idx = h & mask;
cell * c = m_table + idx;
if (c->is_free())
return false;
do {
if (equals(c->m_data, d)) {
r = c->m_data;
return true;
}
CHS_CODE(const_cast<chashtable*>(this)->m_collisions++;);
c = c->m_next;
}
while (c != nullptr);
return false;
}
void erase(T const & d) {
unsigned mask = m_slots - 1;
unsigned h = get_hash(d);
unsigned idx = h & mask;
cell * c = m_table + idx;
if (c->is_free())
return;
cell * prev = nullptr;
do {
if (equals(c->m_data, d)) {
m_size--;
if (prev == nullptr) {
cell * next = c->m_next;
if (next == nullptr) {
m_used_slots--;
c->mark_free();
SASSERT(c->is_free());
}
else {
*c = *next;
recycle_cell(next);
}
}
else {
prev->m_next = c->m_next;
recycle_cell(c);
}
CASSERT("chashtable_bug", check_invariant());
return;
}
CHS_CODE(m_collisions++;);
prev = c;
c = c->m_next;
}
while (c != nullptr);
}
class iterator {
cell * m_it;
cell * m_end;
cell * m_list_it;
void move_to_used() {
while (m_it != m_end) {
if (!m_it->is_free()) {
m_list_it = m_it;
return;
}
m_it++;
}
m_list_it = nullptr;
}
public:
iterator(cell * start, cell * end): m_it(start), m_end(end) { move_to_used(); }
iterator():m_it(nullptr), m_end(nullptr), m_list_it(nullptr) {}
T & operator*() {
return m_list_it->m_data;
}
T const & operator*() const {
return m_list_it->m_data;
}
T const * operator->() const { return &(operator*()); }
T * operator->() { return &(operator*()); }
iterator & operator++() {
m_list_it = m_list_it->m_next;
if (m_list_it == nullptr) {
m_it++;
move_to_used();
}
return *this;
}
iterator operator++(int) { iterator tmp = *this; ++*this; return tmp; }
bool operator!=(iterator const & it) const { return m_list_it != it.m_list_it; }
};
iterator begin() const { return iterator(m_table, m_table + m_slots); }
iterator end() const { return iterator(); }
void swap(chashtable & other) noexcept {
std::swap(m_table, other.m_table);
std::swap(m_capacity, other.m_capacity);
std::swap(m_init_slots, other.m_init_slots);
std::swap(m_init_cellar, other.m_init_cellar);
std::swap(m_slots, other.m_slots);
std::swap(m_used_slots, other.m_used_slots);
std::swap(m_size, other.m_size);
#ifdef CH_STATISTICS
std::swap(m_collisions, other.m_collisions);
#endif
std::swap(m_next_cell, other.m_next_cell);
std::swap(m_free_cell, other.m_free_cell);
}
unsigned collisions() const {
#ifdef CH_STATISTICS
return m_collisions;
#else
return 0;
#endif
}
#ifdef Z3DEBUG
bool check_invariant() const {
ptr_addr_hashtable<cell> visited;
unsigned sz = 0;
cell * _end = m_table + m_slots;
for (cell * it = m_table; it != _end; ++it) {
if (!it->is_free()) {
cell * list_it = it;
while (list_it != 0) {
sz++;
SASSERT(!visited.contains(list_it));
visited.insert(list_it);
list_it = list_it->m_next;
}
}
}
SASSERT(m_size == sz);
return true;
}
#endif
};
template<typename Key, typename Value, typename HashProc, typename EqProc>
class cmap {
public:
struct key_value {
Key m_key;
Value m_value;
key_value() = default;
key_value(Key const & k):m_key(k) {}
key_value(Key const & k, Value const & v):m_key(k), m_value(v) {}
};
protected:
struct key_value_hash_proc : private HashProc {
key_value_hash_proc(HashProc const & p):HashProc(p) {}
unsigned operator()(key_value const & d) const { return HashProc::operator()(d.m_key); }
};
struct key_value_eq_proc : private EqProc {
key_value_eq_proc(EqProc const & p):EqProc(p) {}
bool operator()(key_value const & d1, key_value const & d2) const { return EqProc::operator()(d1.m_key, d2.m_key); }
};
typedef chashtable<key_value, key_value_hash_proc, key_value_eq_proc> table;
table m_table;
public:
cmap(HashProc const & h = HashProc(),
EqProc const & e = EqProc(),
unsigned init_slots = table::default_init_slots,
unsigned init_cellar = table::default_init_cellar):
m_table(key_value_hash_proc(h),
key_value_eq_proc(e),
init_slots,
init_cellar) {
}
typedef typename table::iterator iterator;
void reset() {
m_table.reset();
}
void finalize() {
m_table.finalize();
}
bool empty() const {
return m_table.empty();
}
unsigned size() const {
return m_table.size();
}
unsigned capacity() const {
return m_table.capacity();
}
unsigned used_slots() const {
return m_table.used_slots();
}
unsigned collisions() const {
return m_table.collisions();
}
iterator begin() const {
return m_table.begin();
}
iterator end() const {
return m_table.end();
}
void insert(Key const & k, Value const & v) {
return m_table.insert(key_value(k, v));
}
key_value & insert_if_not_there(Key const & k, Value const & v) {
return m_table.insert_if_not_there(key_value(k, v));
}
bool contains(Key const & k) const {
return m_table.contains(key_value(k));
}
key_value * find_core(Key const & k) const {
return m_table.find_core(key_value(k));
}
bool find(Key const & k, Value & v) const {
key_value * e = m_table.find_core(key_value(k));
if (e == nullptr)
return false;
v = e->m_value;
return true;
}
void erase(Key const & k) {
m_table.erase(key_value(k));
}
};