z3-z3-4.13.0.src.ast.act_cache.cpp Maven / Gradle / Ivy
The newest version!
/*++
Copyright (c) 2011 Microsoft Corporation
Module Name:
act_cache.cpp
Abstract:
expr -> expr activity cache
It maintains at most N unused entries
Author:
Leonardo (leonardo) 2011-04-12
Notes:
--*/
#include "ast/act_cache.h"
#define MIN_MAX_UNUSED 1024
#define INITIAL_CAPACITY 128
/*
This cache is a mapping from expr -> tagged expressions
A tagged expression is essentially a pair (expr, flag)
Thus, an entry
t -> (s, 0)
maps the key t to value s, and says that key t was never accessed.
That is, client code never executed find(t)
Similarly, an entry
t -> (s, 1)
also maps the key t to value s, but signs that key t was already accessed
by client code.
When a new key/value pair is inserted the flag is 0.
The flag is set to 1 after the key is accessed.
The number of unused entries (m_unused) is equal to the number of entries
of the form
t -> (s, 0)
That is, it is the number of keys that were never accessed by client code.
The cache maintains at most m_max_unused entries.
When the maximum number of unused entries exceeds m_max_unused, then
the cache will delete the oldest unused entry.
*/
/**
m_queue stores the recently added keys.
The queue is implemented as pair: m_queue (vector), m_qhead (unsigned).
The "active" part of m_queue is the range [m_qhead, m_queue.size())
The "inactive" part [0, m_qhead) contains keys that were already used by client code.
This procedure, deletes the inactive part, and makes m_qhead == 0.
*/
void act_cache::compress_queue() {
SASSERT(m_qhead > 0);
unsigned sz = m_queue.size();
unsigned j = 0;
for (unsigned i = m_qhead; i < sz; i++, j++) {
m_queue[j] = m_queue[i];
}
m_queue.shrink(j);
m_qhead = 0;
}
void act_cache::init() {
if (m_max_unused < MIN_MAX_UNUSED)
m_max_unused = MIN_MAX_UNUSED;
m_unused = 0;
m_qhead = 0;
}
void act_cache::dec_refs() {
for (auto & kv : m_table) {
m_manager.dec_ref(kv.m_key.first);
m_manager.dec_ref(UNTAG(expr*, kv.m_value));
}
}
act_cache::act_cache(ast_manager & m):
m_manager(m),
m_max_unused(m.get_num_asts()) {
init();
}
act_cache::act_cache(ast_manager & m, unsigned max_unused):
m_manager(m),
m_max_unused(max_unused) {
init();
}
act_cache::~act_cache() {
dec_refs();
}
/**
\brief Search m_queue from [m_qhead, m_queue.size()) until it finds
an unused key. That is a key associated with an entry
key -> (value, 0)
*/
void act_cache::del_unused() {
unsigned sz = m_queue.size();
while (m_qhead < sz) {
entry_t const& e = m_queue[m_qhead];
m_qhead++;
SASSERT(m_table.contains(e));
map::key_value * entry = m_table.find_core(e);
SASSERT(entry);
if (GET_TAG(entry->m_value) == 0) {
// Key k was never accessed by client code.
// That is, find(k) was never executed by client code.
m_unused--;
expr * v = entry->m_value;
m_table.erase(e);
m_manager.dec_ref(e.first);
m_manager.dec_ref(v);
break;
}
}
if (m_qhead == sz) {
// The "active" part of the queue is empty.
// So, we perform a "cheap" compress.
m_queue.reset();
m_qhead = 0;
}
else if (m_qhead > m_max_unused) {
compress_queue();
}
}
/**
\brief Insert a new entry k -> v into the cache.
*/
void act_cache::insert(expr * k, unsigned offset, expr * v) {
SASSERT(k);
entry_t e(k, offset);
if (m_unused >= m_max_unused)
del_unused();
expr * dummy = reinterpret_cast(1);
map::key_value & entry = m_table.insert_if_not_there(e, dummy);
#if 0
unsigned static counter = 0;
counter++;
if (counter % 100000 == 0)
verbose_stream() << "[act-cache] counter: " << counter << " used_slots: " << m_table.used_slots() << " capacity: " << m_table.capacity() << " size: " << m_table.size() << " collisions: " << m_table.collisions() << "\n";
#endif
#ifdef Z3DEBUG
unsigned expected_tag;
#endif
if (entry.m_value == dummy) {
// new entry;
m_manager.inc_ref(k);
m_manager.inc_ref(v);
entry.m_value = v;
m_queue.push_back(e);
m_unused++;
DEBUG_CODE(expected_tag = 0;); // new entry
}
else if (UNTAG(expr*, entry.m_value) == v) {
// already there
DEBUG_CODE(expected_tag = GET_TAG(entry.m_value););
}
else {
// replacing old entry
m_manager.inc_ref(v);
m_manager.dec_ref(UNTAG(expr*, entry.m_value));
entry.m_value = v;
SASSERT(GET_TAG(entry.m_value) == 0);
// replaced old entry, and reset the tag.
DEBUG_CODE(expected_tag = 0;);
}
DEBUG_CODE({
expr * v2;
SASSERT(m_table.find(e, v2));
SASSERT(v == UNTAG(expr*, v2));
SASSERT(expected_tag == GET_TAG(v2));
});
}
/**
\brief Search for key k in the cache.
If entry k -> (v, tag) is found, we set tag to 1.
*/
expr * act_cache::find(expr * k, unsigned offset) {
entry_t e(k, offset);
map::key_value * entry = m_table.find_core(e);
if (entry == nullptr)
return nullptr;
if (GET_TAG(entry->m_value) == 0) {
entry->m_value = TAG(expr*, entry->m_value, 1);
SASSERT(GET_TAG(entry->m_value) == 1);
SASSERT(m_unused > 0);
m_unused--;
DEBUG_CODE({
expr * v;
SASSERT(m_table.find(e, v));
SASSERT(GET_TAG(v) == 1);
});
}
return UNTAG(expr*, entry->m_value);
}
void act_cache::reset() {
dec_refs();
m_table.reset();
m_queue.reset();
m_unused = 0;
m_qhead = 0;
}
void act_cache::cleanup() {
dec_refs();
m_table.finalize();
m_queue.finalize();
m_unused = 0;
m_qhead = 0;
}
bool act_cache::check_invariant() const {
return true;
}