You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
437 lines
10 KiB
437 lines
10 KiB
/*
|
|
* Copyright 2011-2016 Formal Methods and Tools, University of Twente
|
|
* Copyright 2016-2017 Tom van Dijk, Johannes Kepler University Linz
|
|
*
|
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
* you may not use this file except in compliance with the License.
|
|
* You may obtain a copy of the License at
|
|
*
|
|
* http://www.apache.org/licenses/LICENSE-2.0
|
|
*
|
|
* Unless required by applicable law or agreed to in writing, software
|
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
* See the License for the specific language governing permissions and
|
|
* limitations under the License.
|
|
*/
|
|
|
|
#include <sylvan_int.h>
|
|
|
|
#ifndef cas
|
|
#define cas(ptr, old, new) (__sync_bool_compare_and_swap((ptr),(old),(new)))
|
|
#endif
|
|
|
|
/**
|
|
* Implementation of garbage collection
|
|
*/
|
|
|
|
/**
|
|
* Whether garbage collection is enabled or not.
|
|
*/
|
|
static int gc_enabled = 1;
|
|
|
|
/**
|
|
* Enable garbage collection (both automatic and manual).
|
|
*/
|
|
void
|
|
sylvan_gc_enable()
|
|
{
|
|
gc_enabled = 1;
|
|
}
|
|
|
|
/**
|
|
* Disable garbage collection (both automatic and manual).
|
|
*/
|
|
void
|
|
sylvan_gc_disable()
|
|
{
|
|
gc_enabled = 0;
|
|
}
|
|
|
|
/**
|
|
* This variable is used for a cas flag so only one gc runs at one time
|
|
*/
|
|
static volatile int gc;
|
|
|
|
/**
|
|
* Structures for the marking mechanisms
|
|
*/
|
|
typedef struct gc_hook_entry
|
|
{
|
|
struct gc_hook_entry *next;
|
|
gc_hook_cb cb;
|
|
} * gc_hook_entry_t;
|
|
|
|
static gc_hook_entry_t mark_list;
|
|
static gc_hook_entry_t pregc_list;
|
|
static gc_hook_entry_t postgc_list;
|
|
static gc_hook_cb main_hook;
|
|
|
|
void
|
|
sylvan_gc_hook_pregc(gc_hook_cb callback)
|
|
{
|
|
gc_hook_entry_t e = (gc_hook_entry_t)malloc(sizeof(struct gc_hook_entry));
|
|
e->cb = callback;
|
|
e->next = pregc_list;
|
|
pregc_list = e;
|
|
}
|
|
|
|
void
|
|
sylvan_gc_hook_postgc(gc_hook_cb callback)
|
|
{
|
|
gc_hook_entry_t e = (gc_hook_entry_t)malloc(sizeof(struct gc_hook_entry));
|
|
e->cb = callback;
|
|
e->next = postgc_list;
|
|
postgc_list = e;
|
|
}
|
|
|
|
void
|
|
sylvan_gc_add_mark(gc_hook_cb callback)
|
|
{
|
|
gc_hook_entry_t e = (gc_hook_entry_t)malloc(sizeof(struct gc_hook_entry));
|
|
e->cb = callback;
|
|
e->next = mark_list;
|
|
mark_list = e;
|
|
}
|
|
|
|
void
|
|
sylvan_gc_hook_main(gc_hook_cb callback)
|
|
{
|
|
main_hook = callback;
|
|
}
|
|
|
|
/**
|
|
* Clear the operation cache.
|
|
*/
|
|
VOID_TASK_IMPL_0(sylvan_clear_cache)
|
|
{
|
|
cache_clear();
|
|
}
|
|
|
|
/**
|
|
* Clear the nodes table and mark all referenced nodes.
|
|
*
|
|
* This does not clear the hash data or rehash the nodes.
|
|
* After marking, the "destroy" hooks are called for all unmarked nodes,
|
|
* for example to free data of custom MTBDD leaves.
|
|
*/
|
|
VOID_TASK_IMPL_0(sylvan_clear_and_mark)
|
|
{
|
|
llmsset_clear_data(nodes);
|
|
|
|
for (gc_hook_entry_t e = mark_list; e != NULL; e = e->next) {
|
|
WRAP(e->cb);
|
|
}
|
|
|
|
llmsset_destroy_unmarked(nodes);
|
|
}
|
|
|
|
/**
|
|
* Clear the hash array of the nodes table and rehash all marked buckets.
|
|
*/
|
|
VOID_TASK_IMPL_0(sylvan_rehash_all)
|
|
{
|
|
// clear hash array
|
|
llmsset_clear_hashes(nodes);
|
|
|
|
// rehash marked nodes
|
|
if (llmsset_rehash(nodes) != 0) {
|
|
fprintf(stderr, "sylvan_gc_rehash error: not all nodes could be rehashed!\n");
|
|
exit(1);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Logic for resizing the nodes table and operation cache
|
|
*/
|
|
|
|
/**
|
|
* Helper routine to compute the next size....
|
|
*/
|
|
size_t
|
|
next_size(size_t current_size)
|
|
{
|
|
#if SYLVAN_SIZE_FIBONACCI
|
|
size_t f1=1, f2=1;
|
|
for (;;) {
|
|
f2 += f1;
|
|
if (f2 > current_size) return f2;
|
|
f1 += f2;
|
|
if (f1 > current_size) return f1;
|
|
}
|
|
#else
|
|
return current_size*2;
|
|
#endif
|
|
}
|
|
|
|
/**
|
|
* Resizing heuristic that always doubles the tables when running gc (until max).
|
|
* The nodes table and operation cache are both resized until their maximum size.
|
|
*/
|
|
VOID_TASK_IMPL_0(sylvan_gc_aggressive_resize)
|
|
{
|
|
size_t nodes_size = llmsset_get_size(nodes);
|
|
size_t nodes_max = llmsset_get_max_size(nodes);
|
|
if (nodes_size < nodes_max) {
|
|
size_t new_size = next_size(nodes_size);
|
|
if (new_size > nodes_max) new_size = nodes_max;
|
|
llmsset_set_size(nodes, new_size);
|
|
}
|
|
size_t cache_size = cache_getsize();
|
|
size_t cache_max = cache_getmaxsize();
|
|
if (cache_size < cache_max) {
|
|
size_t new_size = next_size(cache_size);
|
|
if (new_size > cache_max) new_size = cache_max;
|
|
cache_setsize(new_size);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Resizing heuristic that only resizes when more than 50% is marked.
|
|
* The operation cache is only resized if the nodes table is resized.
|
|
*/
|
|
VOID_TASK_IMPL_0(sylvan_gc_normal_resize)
|
|
{
|
|
size_t nodes_size = llmsset_get_size(nodes);
|
|
size_t nodes_max = llmsset_get_max_size(nodes);
|
|
if (nodes_size < nodes_max) {
|
|
size_t marked = llmsset_count_marked(nodes);
|
|
if (marked*2 > nodes_size) {
|
|
size_t new_size = next_size(nodes_size);
|
|
if (new_size > nodes_max) new_size = nodes_max;
|
|
llmsset_set_size(nodes, new_size);
|
|
|
|
// also increase the operation cache
|
|
size_t cache_size = cache_getsize();
|
|
size_t cache_max = cache_getmaxsize();
|
|
if (cache_size < cache_max) {
|
|
new_size = next_size(cache_size);
|
|
if (new_size > cache_max) new_size = cache_max;
|
|
cache_setsize(new_size);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Actual implementation of garbage collection
|
|
*/
|
|
VOID_TASK_0(sylvan_gc_go)
|
|
{
|
|
sylvan_stats_count(SYLVAN_GC_COUNT);
|
|
sylvan_timer_start(SYLVAN_GC);
|
|
|
|
// call pre gc hooks
|
|
for (gc_hook_entry_t e = pregc_list; e != NULL; e = e->next) {
|
|
WRAP(e->cb);
|
|
}
|
|
|
|
/*
|
|
* This simply clears the cache.
|
|
* Alternatively, we could implement for example some strategy
|
|
* where part of the cache is cleared and part is marked
|
|
*/
|
|
CALL(sylvan_clear_cache);
|
|
|
|
CALL(sylvan_clear_and_mark);
|
|
|
|
// call hooks for resizing and all that
|
|
WRAP(main_hook);
|
|
|
|
CALL(sylvan_rehash_all);
|
|
|
|
// call post gc hooks
|
|
for (gc_hook_entry_t e = postgc_list; e != NULL; e = e->next) {
|
|
WRAP(e->cb);
|
|
}
|
|
|
|
sylvan_timer_stop(SYLVAN_GC);
|
|
}
|
|
|
|
/**
|
|
* Perform garbage collection
|
|
*/
|
|
VOID_TASK_IMPL_0(sylvan_gc)
|
|
{
|
|
if (gc_enabled) {
|
|
if (cas(&gc, 0, 1)) {
|
|
NEWFRAME(sylvan_gc_go);
|
|
gc = 0;
|
|
} else {
|
|
/* wait for new frame to appear */
|
|
while (*(Task* volatile*)&(lace_newframe.t) == 0) {}
|
|
lace_yield(__lace_worker, __lace_dq_head);
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* The unique table
|
|
*/
|
|
|
|
llmsset_t nodes;
|
|
|
|
static size_t table_min = 0, table_max = 0, cache_min = 0, cache_max = 0;
|
|
|
|
static int
|
|
is_power_of_two(size_t size)
|
|
{
|
|
return (size != 0) && ((size & (size-1)) == 0);
|
|
}
|
|
|
|
void
|
|
sylvan_set_sizes(size_t min_tablesize, size_t max_tablesize, size_t min_cachesize, size_t max_cachesize)
|
|
{
|
|
/* Some sanity checks */
|
|
if (min_tablesize > max_tablesize) min_tablesize = max_tablesize;
|
|
if (min_cachesize > max_cachesize) min_cachesize = max_cachesize;
|
|
|
|
if (!is_power_of_two(min_tablesize) || !is_power_of_two(max_tablesize) ||
|
|
!is_power_of_two(min_cachesize) || !is_power_of_two(max_cachesize)) {
|
|
fprintf(stderr, "sylvan_set_sizes error: parameters not powers of 2!\n");
|
|
exit(1);
|
|
}
|
|
|
|
if (max_tablesize > 0x0000040000000000) {
|
|
fprintf(stderr, "sylvan_set_sizes error: tablesize must be <= 42 bits!\n");
|
|
exit(1);
|
|
}
|
|
|
|
table_min = min_tablesize;
|
|
table_max = max_tablesize;
|
|
cache_min = min_cachesize;
|
|
cache_max = max_cachesize;
|
|
}
|
|
|
|
void
|
|
sylvan_set_limits(size_t memorycap, int table_ratio, int initial_ratio)
|
|
{
|
|
if (table_ratio > 10 && table_ratio < 10) {
|
|
fprintf(stderr, "sylvan_set_limits: table_ratio unreasonable (between -10 and 10)\n");
|
|
exit(1);
|
|
}
|
|
|
|
size_t max_t = 1;
|
|
size_t max_c = 1;
|
|
if (table_ratio > 0) {
|
|
max_t <<= table_ratio;
|
|
} else {
|
|
max_c <<= -table_ratio;
|
|
}
|
|
|
|
size_t cur = max_t * 24 + max_c * 36;
|
|
if (cur > memorycap) {
|
|
fprintf(stderr, "sylvan_set_limits: memory cap incompatible with requested table ratio\n");
|
|
}
|
|
|
|
while (2*cur < memorycap && max_t < 0x0000040000000000) {
|
|
max_t *= 2;
|
|
max_c *= 2;
|
|
cur *= 2;
|
|
}
|
|
|
|
if (initial_ratio < 0) {
|
|
fprintf(stderr, "sylvan_set_limits: initial_ratio unreasonable (may not be negative)\n");
|
|
exit(1);
|
|
}
|
|
|
|
size_t min_t = max_t, min_c = max_c;
|
|
while (initial_ratio > 0 && min_t > 0x1000 && min_c > 0x1000) {
|
|
min_t >>= 1;
|
|
min_c >>= 1;
|
|
initial_ratio--;
|
|
}
|
|
|
|
table_min = min_t;
|
|
table_max = max_t;
|
|
cache_min = min_c;
|
|
cache_max = max_c;
|
|
}
|
|
|
|
/**
|
|
* Initializes Sylvan.
|
|
*/
|
|
void
|
|
sylvan_init_package(void)
|
|
{
|
|
if (table_max == 0) {
|
|
fprintf(stderr, "sylvan_init_package error: table sizes not set (sylvan_set_sizes or sylvan_set_limits)!");
|
|
exit(1);
|
|
}
|
|
|
|
/* Create tables */
|
|
nodes = llmsset_create(table_min, table_max);
|
|
cache_create(cache_min, cache_max);
|
|
|
|
/* Initialize garbage collection */
|
|
gc = 0;
|
|
#if SYLVAN_AGGRESSIVE_RESIZE
|
|
main_hook = TASK(sylvan_gc_aggressive_resize);
|
|
#else
|
|
main_hook = TASK(sylvan_gc_normal_resize);
|
|
#endif
|
|
|
|
LACE_ME;
|
|
sylvan_stats_init();
|
|
}
|
|
|
|
struct reg_quit_entry
|
|
{
|
|
struct reg_quit_entry *next;
|
|
quit_cb cb;
|
|
};
|
|
|
|
static struct reg_quit_entry *quit_register = NULL;
|
|
|
|
void
|
|
sylvan_register_quit(quit_cb cb)
|
|
{
|
|
struct reg_quit_entry *e = (struct reg_quit_entry*)malloc(sizeof(struct reg_quit_entry));
|
|
e->next = quit_register;
|
|
e->cb = cb;
|
|
quit_register = e;
|
|
}
|
|
|
|
void
|
|
sylvan_quit()
|
|
{
|
|
while (quit_register != NULL) {
|
|
struct reg_quit_entry *e = quit_register;
|
|
quit_register = e->next;
|
|
e->cb();
|
|
free(e);
|
|
}
|
|
|
|
while (pregc_list != NULL) {
|
|
gc_hook_entry_t e = pregc_list;
|
|
pregc_list = e->next;
|
|
free(e);
|
|
}
|
|
|
|
while (postgc_list != NULL) {
|
|
gc_hook_entry_t e = postgc_list;
|
|
postgc_list = e->next;
|
|
free(e);
|
|
}
|
|
|
|
while (mark_list != NULL) {
|
|
gc_hook_entry_t e = mark_list;
|
|
mark_list = e->next;
|
|
free(e);
|
|
}
|
|
|
|
cache_free();
|
|
llmsset_free(nodes);
|
|
}
|
|
|
|
/**
|
|
* Calculate table usage (in parallel)
|
|
*/
|
|
VOID_TASK_IMPL_2(sylvan_table_usage, size_t*, filled, size_t*, total)
|
|
{
|
|
size_t tot = llmsset_get_size(nodes);
|
|
if (filled != NULL) *filled = llmsset_count_marked(nodes);
|
|
if (total != NULL) *total = tot;
|
|
}
|
|
|
|
|