diff --git a/CMakeLists.txt b/CMakeLists.txt
index 2be241f252bebfba2ebd0923018c332ccb655741..1b8f7bb25e20e8f5c0cff9dd035099f934ed51b9 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -11,14 +11,8 @@ add_library(sylvan
     avl.h
     lace.h
     lace.c
-    llmsset.c
-    llmsset.h
-    refs.h
-    refs.c
     sha2.h
     sha2.c
-    stats.h
-    stats.c
     sylvan.h
     sylvan_bdd.h
     sylvan_bdd.c
@@ -29,19 +23,31 @@ add_library(sylvan
     sylvan_common.c
     sylvan_gmp.h
     sylvan_gmp.c
+    sylvan_int.h
     sylvan_ldd.h
     sylvan_ldd.c
+    sylvan_mt.h
+    sylvan_mt.c
     sylvan_mtbdd.h
     sylvan_mtbdd.c
     sylvan_mtbdd_int.h
     sylvan_obj.hpp
     sylvan_obj.cpp
+    sylvan_refs.h
+    sylvan_refs.c
     sylvan_seq.h
     sylvan_seq.c
+    sylvan_sl.h
+    sylvan_sl.c
+    sylvan_table.h
+    sylvan_table.c
+    sylvan_stats.h
+    sylvan_stats.c
     tls.h
 )
 
-target_link_libraries(sylvan m pthread)
+target_link_libraries(sylvan -lpthread)
+target_link_libraries(sylvan -lgmp)
 
 if(UNIX AND NOT APPLE)
     target_link_libraries(sylvan rt)
diff --git a/Makefile.am b/Makefile.am
old mode 100644
new mode 100755
diff --git a/atomics.h b/atomics.h
old mode 100644
new mode 100755
diff --git a/avl.h b/avl.h
old mode 100644
new mode 100755
index 68b4ea22009dd296fb0632933da4b44e0eec4d5a..858859d6b48d3daf4ee7e949221c65772d228269
--- a/avl.h
+++ b/avl.h
@@ -1,5 +1,6 @@
 /*
- * Copyright 2011-2014 Formal Methods and Tools, University of Twente
+ * 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.
@@ -282,7 +283,7 @@ NAME##_put(avl_node_t **root, TYPE *data, int *inserted)
 static __attribute__((unused)) int                                                          \
 NAME##_insert(avl_node_t **root, TYPE *data)                                                \
 {                                                                                           \
-    int inserted;                                                                           \
+    int inserted = 0;                                                                       \
     NAME##_put(root, data, &inserted);                                                      \
     return inserted;                                                                        \
 }                                                                                           \
diff --git a/lace.c b/lace.c
old mode 100644
new mode 100755
index 43135ac016182e59b2b3566231ae70042feb2f61..08e9f8843b2b26216a4ec8515ca08f99350f408c
--- a/lace.c
+++ b/lace.c
@@ -1,5 +1,6 @@
 /*
- * Copyright 2013-2015 Formal Methods and Tools, University of Twente
+ * Copyright 2013-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.
@@ -14,6 +15,7 @@
  * limitations under the License.
  */
 
+#define _GNU_SOURCE
 #include <errno.h> // for errno
 #include <sched.h> // for sched_getaffinity
 #include <stdio.h>  // for fprintf
@@ -27,46 +29,97 @@
 
 #include <lace.h>
 
-#ifndef USE_HWLOC
-#define USE_HWLOC 0
-#endif
-
-#if USE_HWLOC
+#if LACE_USE_HWLOC
 #include <hwloc.h>
-#endif
 
-// public Worker data
-static Worker **workers;
-static size_t default_stacksize = 0; // set by lace_init
-static size_t default_dqsize = 100000;
-
-#if USE_HWLOC
+/**
+ * HWLOC information
+ */
 static hwloc_topology_t topo;
 static unsigned int n_nodes, n_cores, n_pus;
 #endif
 
+/**
+ * (public) Worker data
+ */
+static Worker **workers = NULL;
+
+/**
+ * Default sizes for program stack and task deque
+ */
+static size_t default_stacksize = 0; // 0 means "set by lace_init"
+static size_t default_dqsize = 100000;
+
+/**
+ * Verbosity flag, set with lace_set_verbosity
+ */
 static int verbosity = 0;
 
-static int n_workers = 0;
-static int enabled_workers = 0;
+/**
+ * Number of workers and number of enabled/active workers
+ */
+static unsigned int n_workers = 0;
+static unsigned int enabled_workers = 0;
+
+/**
+ * Datastructure of the task deque etc for each worker.
+ * - first public cachelines (accessible via global "workers" variable)
+ * - then private cachelines
+ * - then the deque array
+ */
+typedef struct {
+    Worker worker_public;
+    char pad1[PAD(sizeof(Worker), LINE_SIZE)];
+    WorkerP worker_private;
+    char pad2[PAD(sizeof(WorkerP), LINE_SIZE)];
+    Task deque[];
+} worker_data;
+
+/**
+ * (Secret) holds pointers to the memory block allocated for each worker
+ */
+static worker_data **workers_memory = NULL;
+
+/**
+ * Number of bytes allocated for each worker's worker data.
+ */
+static size_t workers_memory_size = 0;
 
-// private Worker data (just for stats at end )
+/**
+ * (Secret) holds pointer to private Worker data, just for stats collection at end
+ */
 static WorkerP **workers_p;
 
-// set to 0 when quitting
+/**
+ * Flag to signal all workers to quit.
+ */
 static int lace_quits = 0;
 
-// for storing private Worker data
+/**
+ * Thread-specific mechanism to access current worker data
+ */
 #ifdef __linux__ // use gcc thread-local storage (i.e. __thread variables)
 static __thread WorkerP *current_worker;
 #else
 static pthread_key_t worker_key;
 #endif
+
+/**
+ * worker_attr used for creating threads
+ * - initialized by lace_init
+ * - used by lace_spawn_worker
+ */
 static pthread_attr_t worker_attr;
 
+/**
+ * The condition/mutex pair for when the root thread sleeps until the end of the program
+ */
 static pthread_cond_t wait_until_done = PTHREAD_COND_INITIALIZER;
 static pthread_mutex_t wait_until_done_mutex = PTHREAD_MUTEX_INITIALIZER;
 
+/**
+ * Data structure that contains the stack and stack size for each worker.
+ */
 struct lace_worker_init
 {
     void* stack;
@@ -75,8 +128,14 @@ struct lace_worker_init
 
 static struct lace_worker_init *workers_init;
 
+/**
+ * Global newframe variable used for the implementation of NEWFRAME and TOGETHER
+ */
 lace_newframe_t lace_newframe;
 
+/**
+ * Get the private Worker data of the current thread
+ */
 WorkerP*
 lace_get_worker()
 {
@@ -87,14 +146,20 @@ lace_get_worker()
 #endif
 }
 
+/**
+ * Find the head of the task deque, using the given private Worker data
+ */
 Task*
 lace_get_head(WorkerP *self)
 {
     Task *dq = self->dq;
+
+    /* First check the first tasks linearly */
     if (dq[0].thief == 0) return dq;
     if (dq[1].thief == 0) return dq+1;
     if (dq[2].thief == 0) return dq+2;
 
+    /* Then fast search for a low/high bound using powers of 2: 4, 8, 16... */
     size_t low = 2;
     size_t high = self->end - self->dq;
 
@@ -109,6 +174,7 @@ lace_get_head(WorkerP *self)
         }
     }
 
+    /* Finally zoom in using binary search */
     while (low < high) {
         size_t mid = low + (high-low)/2;
         if (dq[mid].thief == 0) high = mid;
@@ -118,22 +184,27 @@ lace_get_head(WorkerP *self)
     return dq+low;
 }
 
-size_t
+/**
+ * Get the number of workers
+ */
+unsigned int
 lace_workers()
 {
     return n_workers;
 }
 
+/**
+ * Get the default stack size (or 0 for automatically determine)
+ */
 size_t
 lace_default_stacksize()
 {
     return default_stacksize;
 }
 
-#ifndef cas
-#define cas(ptr, old, new) (__sync_bool_compare_and_swap((ptr),(old),(new)))
-#endif
-
+/**
+ * If we are collecting PIE times, then we need some helper functions.
+ */
 #if LACE_PIE_TIMES
 static uint64_t count_at_start, count_at_end;
 static long long unsigned us_elapsed_timer;
@@ -160,109 +231,185 @@ us_elapsed(void)
 }
 #endif
 
-#if USE_HWLOC
-// Lock used only during parallel lace_init_worker...
-static volatile int __attribute__((aligned(64))) lock = 0;
-static inline void
-lock_acquire()
-{
-    while (1) {
-        while (lock) {}
-        if (cas(&lock, 0, 1)) return;
-    }
-}
-static inline void
-lock_release()
-{
-    lock=0;
-}
-#endif
-
-/* Barrier */
-#define BARRIER_MAX_THREADS 128
-
-typedef union __attribute__((__packed__))
-{
-    volatile size_t val;
-    char            pad[LINE_SIZE];
-} asize_t;
-
+/**
+ * Lace barrier implementation, that synchronizes on all currently enabled workers.
+ */
 typedef struct {
     volatile int __attribute__((aligned(LINE_SIZE))) count;
+    volatile int __attribute__((aligned(LINE_SIZE))) leaving;
     volatile int __attribute__((aligned(LINE_SIZE))) wait;
-    /* the following is needed only for destroy: */
-    asize_t             entered[BARRIER_MAX_THREADS];
 } barrier_t;
 
 barrier_t lace_bar;
 
+/**
+ * Enter the Lace barrier and wait until all workers have entered the Lace barrier.
+ */
 void
 lace_barrier()
 {
-    int id = lace_get_worker()->worker;
-
-    lace_bar.entered[id].val = 1; // signal entry
-
     int wait = lace_bar.wait;
-    if (enabled_workers == __sync_add_and_fetch(&lace_bar.count, 1)) {
-        lace_bar.count = 0; // reset counter
+    if ((int)enabled_workers == __sync_add_and_fetch(&lace_bar.count, 1)) {
+        lace_bar.count = 0;
+        lace_bar.leaving = enabled_workers;
         lace_bar.wait = 1 - wait; // flip wait
-        lace_bar.entered[id].val = 0; // signal exit
     } else {
         while (wait == lace_bar.wait) {} // wait
-        lace_bar.entered[id].val = 0; // signal exit
     }
+
+    __sync_add_and_fetch(&lace_bar.leaving, -1);
 }
 
+/**
+ * Initialize the Lace barrier
+ */
 static void
 lace_barrier_init()
 {
-    assert(n_workers <= BARRIER_MAX_THREADS);
     memset(&lace_bar, 0, sizeof(barrier_t));
 }
 
+/**
+ * Destroy the Lace barrier (just wait until all are exited)
+ */
 static void
 lace_barrier_destroy()
 {
     // wait for all to exit
-    int i;
-    for ( i=0; i<n_workers; i++) {
-        while (1 == lace_bar.entered[i].val) {}
+    while (lace_bar.leaving != 0) continue;
+}
+
+/**
+ * For debugging purposes, check if memory is allocated on the correct memory nodes.
+ */
+static void __attribute__((unused))
+lace_check_memory(void)
+{
+#if LACE_USE_HWLOC
+    // get our current worker
+    WorkerP *w = lace_get_worker();
+    void* mem = workers_memory[w->worker];
+
+    // get pinned PUs
+    hwloc_cpuset_t cpuset = hwloc_bitmap_alloc();
+    hwloc_get_cpubind(topo, cpuset, HWLOC_CPUBIND_THREAD);
+
+    // get nodes of pinned PUs
+    hwloc_nodeset_t cpunodes = hwloc_bitmap_alloc();
+    hwloc_cpuset_to_nodeset(topo, cpuset, cpunodes);
+
+    // get location of memory
+    hwloc_nodeset_t memlocation = hwloc_bitmap_alloc();
+#ifdef hwloc_get_area_memlocation
+    hwloc_get_area_memlocation(topo, mem, sizeof(worker_data), memlocation, HWLOC_MEMBIND_BYNODESET);
+#else
+    hwloc_membind_policy_t policy;
+    int res = hwloc_get_area_membind_nodeset(topo, mem, sizeof(worker_data), memlocation, &policy, HWLOC_MEMBIND_STRICT);
+    if (res == -1) {
+        fprintf(stderr, "Lace warning: hwloc_get_area_membind_nodeset returned -1!\n");
     }
+    if (policy != HWLOC_MEMBIND_BIND) {
+        fprintf(stderr, "Lace warning: Lace worker memory not bound with BIND policy!\n");
+    }
+#endif
+
+    // check if CPU and node are on the same place
+    if (!hwloc_bitmap_isincluded(memlocation, cpunodes)) {
+        fprintf(stderr, "Lace warning: Lace thread not on same memory domain as data!\n");
+
+        char *strp, *strp2, *strp3;
+        hwloc_bitmap_list_asprintf(&strp, cpuset);
+        hwloc_bitmap_list_asprintf(&strp2, cpunodes);
+        hwloc_bitmap_list_asprintf(&strp3, memlocation);
+        fprintf(stderr, "Worker %d is pinned on PUs %s, node %s; memory is pinned on node %s\n", w->worker, strp, strp2, strp3);
+        free(strp);
+        free(strp2);
+        free(strp3);
+    }
+
+    // free allocated memory
+    hwloc_bitmap_free(cpuset);
+    hwloc_bitmap_free(cpunodes);
+    hwloc_bitmap_free(memlocation);
+#endif
 }
 
 void
-lace_init_worker(int worker, size_t dq_size)
+lace_pin_worker(void)
 {
-    Worker *wt = NULL;
-    WorkerP *w = NULL;
+#if LACE_USE_HWLOC
+    // Get our worker
+    unsigned int worker = lace_get_worker()->worker;
 
-    if (dq_size == 0) dq_size = default_dqsize;
+    // Get our core (hwloc object)
+    hwloc_obj_t pu = hwloc_get_obj_by_type(topo, HWLOC_OBJ_CORE, worker % n_cores);
 
-#if USE_HWLOC
-    // Get our logical processor
-    hwloc_obj_t pu = hwloc_get_obj_by_type(topo, HWLOC_OBJ_PU, worker % n_pus);
+    // Get our copy of the bitmap
+    hwloc_cpuset_t bmp = hwloc_bitmap_dup(pu->cpuset);
+
+    // Get number of PUs in bitmap
+    int n = -1, count=0;
+    while ((n=hwloc_bitmap_next(bmp, n)) != -1) count++;
+
+    // Check if we actually have any logical processors
+    if (count == 0) {
+        fprintf(stderr, "Lace error: trying to pin a worker on an empty core?\n");
+        exit(-1);
+    }
+
+    // Select the correct PU on the core (in case of hyperthreading)
+    int idx = worker / n_cores;
+    if (idx >= count) {
+        fprintf(stderr, "Lace warning: more workers than available logical processors!\n");
+        idx %= count;
+    }
+
+    // Find index of PU and restrict bitmap
+    n = -1;
+    for (int i=0; i<=idx; i++) n = hwloc_bitmap_next(bmp, n);
+    hwloc_bitmap_only(bmp, n);
 
     // Pin our thread...
-    hwloc_set_cpubind(topo, pu->cpuset, HWLOC_CPUBIND_THREAD);
+    if (hwloc_set_cpubind(topo, bmp, HWLOC_CPUBIND_THREAD) == -1) {
+        fprintf(stderr, "Lace warning: hwloc_set_cpubind returned -1!\n");
+    }
+
+    // Free our copy of the bitmap
+    hwloc_bitmap_free(bmp);
+
+    // Pin the memory area (using the appropriate hwloc function)
+#ifdef HWLOC_MEMBIND_BYNODESET
+    int res = hwloc_set_area_membind(topo, workers_memory[worker], workers_memory_size, pu->nodeset, HWLOC_MEMBIND_BIND, HWLOC_MEMBIND_STRICT | HWLOC_MEMBIND_MIGRATE | HWLOC_MEMBIND_BYNODESET);
+#else
+    int res = hwloc_set_area_membind_nodeset(topo, workers_memory[worker], workers_memory_size, pu->nodeset, HWLOC_MEMBIND_BIND, HWLOC_MEMBIND_STRICT | HWLOC_MEMBIND_MIGRATE);
+#endif
+    if (res != 0) {
+        fprintf(stderr, "Lace error: Unable to bind worker memory to node!\n");
+    }
+
+    // Check if everything is on the correct node
+    lace_check_memory();
+#endif
+}
 
-    // Allocate memory on our node...
-    lock_acquire();
-    wt = (Worker *)hwloc_alloc_membind(topo, sizeof(Worker), pu->cpuset, HWLOC_MEMBIND_BIND, 0);
-    w = (WorkerP *)hwloc_alloc_membind(topo, sizeof(WorkerP), pu->cpuset, HWLOC_MEMBIND_BIND, 0);
-    if (wt == NULL || w == NULL || (w->dq = (Task*)hwloc_alloc_membind(topo, dq_size * sizeof(Task), pu->cpuset, HWLOC_MEMBIND_BIND, 0)) == NULL) {
+void
+lace_init_worker(unsigned int worker)
+{
+    // Allocate our memory
+    workers_memory[worker] = mmap(NULL, workers_memory_size, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0);
+    if (workers_memory[worker] == MAP_FAILED) {
         fprintf(stderr, "Lace error: Unable to allocate memory for the Lace worker!\n");
         exit(1);
     }
-    lock_release();
+
+    // Set pointers
+    Worker *wt = workers[worker] = &workers_memory[worker]->worker_public;
+    WorkerP *w = workers_p[worker] = &workers_memory[worker]->worker_private;
+    w->dq = workers_memory[worker]->deque;
+#ifdef __linux__
+    current_worker = w;
 #else
-    // Allocate memory...
-    if (posix_memalign((void**)&wt, LINE_SIZE, sizeof(Worker)) ||
-        posix_memalign((void**)&w, LINE_SIZE, sizeof(WorkerP)) ||
-        posix_memalign((void**)&w->dq, LINE_SIZE, dq_size * sizeof(Task))) {
-            fprintf(stderr, "Lace error: Unable to allocate memory for the Lace worker!\n");
-            exit(1);
-    }
+    pthread_setspecific(worker_key, w);
 #endif
 
     // Initialize public worker data
@@ -273,12 +420,12 @@ lace_init_worker(int worker, size_t dq_size)
 
     // Initialize private worker data
     w->_public = wt;
-    w->end = w->dq + dq_size;
+    w->end = w->dq + default_dqsize;
     w->split = w->dq;
     w->allstolen = 0;
     w->worker = worker;
-#if USE_HWLOC
-    w->pu = worker % n_pus;
+#if LACE_USE_HWLOC
+    w->pu = worker % n_cores;
 #else
     w->pu = -1;
 #endif
@@ -288,21 +435,13 @@ lace_init_worker(int worker, size_t dq_size)
     } else {
         w->stack_trigger = 0;
     }
+    w->rng = (((uint64_t)rand())<<32 | rand());
 
 #if LACE_COUNT_EVENTS
-    // Reset counters
+    // Initialize counters
     { int k; for (k=0; k<CTR_MAX; k++) w->ctr[k] = 0; }
 #endif
 
-    // Set pointers
-#ifdef __linux__
-    current_worker = w;
-#else
-    pthread_setspecific(worker_key, w);
-#endif
-    workers[worker] = wt;
-    workers_p[worker] = w;
-
     // Synchronize with others
     lace_barrier();
 
@@ -310,8 +449,15 @@ lace_init_worker(int worker, size_t dq_size)
     w->time = gethrtime();
     w->level = 0;
 #endif
+
+    if (worker == 0) {
+        lace_time_event(w, 1);
+    }
 }
 
+/**
+ * Some OSX systems do not implement pthread_barrier_t, so we provide an implementation here.
+ */
 #if defined(__APPLE__) && !defined(pthread_barrier_t)
 
 typedef int pthread_barrierattr_t;
@@ -401,13 +547,13 @@ lace_resume()
 }
 
 /**
- * With set_workers, all workers 0..(N-1) are enabled and N..max are disabled.
- * You can never disable the current worker or reduce the number of workers below 1.
+ * Disable worker <worker>.
+ * If the given worker is the current worker, this function does nothing.
  */
 void
-lace_disable_worker(int worker)
+lace_disable_worker(unsigned int worker)
 {
-    int self = lace_get_worker()->worker;
+    unsigned int self = lace_get_worker()->worker;
     if (worker == self) return;
     if (workers_p[worker]->enabled == 1) {
         workers_p[worker]->enabled = 0;
@@ -415,10 +561,14 @@ lace_disable_worker(int worker)
     }
 }
 
+/**
+ * Enable worker <worker>.
+ * If the given worker is the current worker, this function does nothing.
+ */
 void
-lace_enable_worker(int worker)
+lace_enable_worker(unsigned int worker)
 {
-    int self = lace_get_worker()->worker;
+    unsigned int self = lace_get_worker()->worker;
     if (worker == self) return;
     if (workers_p[worker]->enabled == 0) {
         workers_p[worker]->enabled = 1;
@@ -426,25 +576,38 @@ lace_enable_worker(int worker)
     }
 }
 
+/**
+ * Enables all workers 0..(N-1) and disables workers N..max.
+ * This function _should_ be called by worker 0.
+ * Ignores the current worker if >= N.
+ * The number of workers is never reduces below 1.
+ */
 void
-lace_set_workers(int workercount)
-{  int i;
+lace_set_workers(unsigned int workercount)
+{
     if (workercount < 1) workercount = 1;
     if (workercount > n_workers) workercount = n_workers;
     enabled_workers = workercount;
-    int self = lace_get_worker()->worker;
+    unsigned int self = lace_get_worker()->worker;
     if (self >= workercount) workercount--;
-    for ( i=0; i<n_workers; i++) {
+    for (unsigned int i=0; i<n_workers; i++) {
         workers_p[i]->enabled = (i < workercount || i == self) ? 1 : 0;
     }
 }
 
-int
+/**
+ * Get the number of currently enabled workers.
+ */
+unsigned int
 lace_enabled_workers()
 {
     return enabled_workers;
 }
 
+/**
+ * Simple random number generated (like rand) using the given seed.
+ * (Used for thread-specific (scalable) random number generation.
+ */
 static inline uint32_t
 rng(uint32_t *seed, int max)
 {
@@ -458,7 +621,10 @@ rng(uint32_t *seed, int max)
     return next % max;
 }
 
-VOID_TASK_IMPL_0(lace_steal_random)
+/**
+ * (Try to) steal and execute a task from a random worker.
+ */
+VOID_TASK_0(lace_steal_random)
 {
     Worker *victim = workers[(__lace_worker->worker + 1 + rng(&__lace_worker->seed, n_workers-1)) % n_workers];
 
@@ -473,41 +639,36 @@ VOID_TASK_IMPL_0(lace_steal_random)
     }
 }
 
-VOID_TASK_IMPL_1(lace_steal_random_loop, int*, quit)
-{
-    while(!(*(volatile int*)quit)) {
-        lace_steal_random();
-
-        if (must_suspend) {
-            lace_barrier();
-            do {
-                pthread_barrier_wait(&suspend_barrier);
-            } while (__lace_worker->enabled == 0);
-        }
-    }
-}
-
+/**
+ * Variable to hold the main/root task.
+ */
 static lace_startup_cb main_cb;
 
+/**
+ * Wrapper around the main/root task.
+ */
 static void*
 lace_main_wrapper(void *arg)
 {
-    lace_init_worker(0, 0);
-    WorkerP *self = lace_get_worker();
-
-#if LACE_PIE_TIMES
-    self->time = gethrtime();
-#endif
-
-    lace_time_event(self, 1);
-    main_cb(self, self->dq, arg);
+    lace_init_worker(0);
+    lace_pin_worker();
+    LACE_ME;
+    WRAP(main_cb, arg);
     lace_exit();
+
+    // Now signal that we're done
+    pthread_mutex_lock(&wait_until_done_mutex);
     pthread_cond_broadcast(&wait_until_done);
+    pthread_mutex_unlock(&wait_until_done_mutex);
 
     return NULL;
 }
 
-VOID_TASK_IMPL_1(lace_steal_loop, int*, quit)
+/**
+ * Main Lace worker implementation.
+ * Steal from random victims until "quit" is set.
+ */
+VOID_TASK_1(lace_steal_loop, int*, quit)
 {
     // Determine who I am
     const int worker_id = __lace_worker->worker;
@@ -556,15 +717,43 @@ VOID_TASK_IMPL_1(lace_steal_loop, int*, quit)
     }
 }
 
-static void*
-lace_default_worker(void* arg)
+/**
+ * Initialize worker 0.
+ * Calls lace_init_worker and then signals the event.
+ */
+void
+lace_init_main()
+{
+    lace_init_worker(0);
+}
+
+/**
+ * Initialize the current thread as a Lace thread, and perform work-stealing
+ * as worker <worker> until lace_exit() is called.
+ *
+ * For worker 0, use lace_init_main
+ */
+void
+lace_run_worker(void)
 {
-    lace_init_worker((size_t)arg, 0);
-    WorkerP *__lace_worker = lace_get_worker();
-    Task *__lace_dq_head = __lace_worker->dq;
-    lace_steal_loop(&lace_quits);
+    // Run the steal loop
+    LACE_ME;
+    CALL(lace_steal_loop, &lace_quits);
+
+    // Time worker exit event
     lace_time_event(__lace_worker, 9);
+
+    // Synchronize with lace_exit
     lace_barrier();
+}
+
+static void*
+lace_default_worker_thread(void* arg)
+{
+    int worker = (int)(size_t)arg;
+    lace_init_worker(worker);
+    lace_pin_worker();
+    lace_run_worker();
     return NULL;
 }
 
@@ -577,24 +766,18 @@ lace_spawn_worker(int worker, size_t stacksize, void* (*fun)(void*), void* arg)
     size_t pagesize = sysconf(_SC_PAGESIZE);
     stacksize = (stacksize + pagesize - 1) & ~(pagesize - 1); // ceil(stacksize, pagesize)
 
-#if USE_HWLOC
+#if LACE_USE_HWLOC
     // Get our logical processor
     hwloc_obj_t pu = hwloc_get_obj_by_type(topo, HWLOC_OBJ_PU, worker % n_pus);
 
     // Allocate memory for the program stack
-    lock_acquire();
     void *stack_location = hwloc_alloc_membind(topo, stacksize + pagesize, pu->cpuset, HWLOC_MEMBIND_BIND, 0);
-    lock_release();
     if (stack_location == 0) {
         fprintf(stderr, "Lace error: Unable to allocate memory for the pthread stack!\n");
         exit(1);
     }
 #else
-    void *stack_location = mmap(NULL, stacksize + pagesize, PROT_READ | PROT_WRITE, MAP_ANON | MAP_PRIVATE, -1, 0);
-    if (stack_location == MAP_FAILED) {
-        fprintf(stderr, "Lace error: Cannot allocate program stack: %s!\n", strerror(errno));
-        exit(1);
-    }
+    void *stack_location = mmap(NULL, stacksize+  pagesize, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0);
 #endif
 
     if (0 != mprotect(stack_location, pagesize, PROT_NONE)) {
@@ -611,7 +794,7 @@ lace_spawn_worker(int worker, size_t stacksize, void* (*fun)(void*), void* arg)
     workers_init[worker].stacksize = stacksize;
 
     if (fun == 0) {
-        fun = lace_default_worker;
+        fun = lace_default_worker_thread;
         arg = (void*)(size_t)worker;
     }
 
@@ -620,53 +803,47 @@ lace_spawn_worker(int worker, size_t stacksize, void* (*fun)(void*), void* arg)
     return res;
 }
 
-static int
-get_cpu_count()
-{
-#if USE_HWLOC
-    int count = hwloc_get_nbobjs_by_type(topo, HWLOC_OBJ_PU);
-#elif defined(sched_getaffinity)
-    /* Best solution: find actual available cpus */
-    cpu_set_t cs;
-    CPU_ZERO(&cs);
-    sched_getaffinity(0, sizeof(cs), &cs);
-    int count = CPU_COUNT(&cs);
-#elif defined(_SC_NPROCESSORS_ONLN)
-    /* Fallback */
-    int count = sysconf(_SC_NPROCESSORS_ONLN);
-#else
-    /* Okay... */
-    int count = 1;
-#endif
-    return count < 1 ? 1 : count;
-}
-
+/**
+ * Set the verbosity of Lace.
+ */
 void
 lace_set_verbosity(int level)
 {
     verbosity = level;
 }
 
+/**
+ * Initialize Lace for work-stealing with <n> workers, where
+ * each worker gets a task deque with <dqsize> elements.
+ */
 void
-lace_init(int n, size_t dqsize)
+lace_init(unsigned int _n_workers, size_t dqsize)
 {
-#if USE_HWLOC
+#if LACE_USE_HWLOC
+    // Initialize topology and information about cpus
     hwloc_topology_init(&topo);
     hwloc_topology_load(topo);
 
     n_nodes = hwloc_get_nbobjs_by_type(topo, HWLOC_OBJ_NODE);
     n_cores = hwloc_get_nbobjs_by_type(topo, HWLOC_OBJ_CORE);
     n_pus = hwloc_get_nbobjs_by_type(topo, HWLOC_OBJ_PU);
+#elif defined(sched_getaffinity)
+    cpu_set_t cs;
+    CPU_ZERO(&cs);
+    sched_getaffinity(0, sizeof(cs), &cs);
+    unsigned int n_pus = CPU_COUNT(&cs);
+#else
+    unsigned int n_pus = sysconf(_SC_NPROCESSORS_ONLN);
 #endif
 
     // Initialize globals
-    n_workers = n;
-    if (n_workers == 0) n_workers = get_cpu_count();
+    n_workers = _n_workers == 0 ? n_pus : _n_workers;
     enabled_workers = n_workers;
     if (dqsize != 0) default_dqsize = dqsize;
+    else dqsize = default_dqsize;
     lace_quits = 0;
 
-    // Create barrier for all workers
+    // Initialize Lace barrier
     lace_barrier_init();
 
     // Create suspend barrier
@@ -674,11 +851,15 @@ lace_init(int n, size_t dqsize)
 
     // Allocate array with all workers
     if (posix_memalign((void**)&workers, LINE_SIZE, n_workers*sizeof(Worker*)) != 0 ||
-        posix_memalign((void**)&workers_p, LINE_SIZE, n_workers*sizeof(WorkerP*)) != 0) {
+        posix_memalign((void**)&workers_p, LINE_SIZE, n_workers*sizeof(WorkerP*)) != 0 ||
+        posix_memalign((void**)&workers_memory, LINE_SIZE, n_workers*sizeof(worker_data*)) != 0) {
         fprintf(stderr, "Lace error: unable to allocate memory!\n");
         exit(1);
     }
 
+    // Compute memory size for each worker
+    workers_memory_size = sizeof(worker_data) + sizeof(Task) * dqsize;
+
     // Create pthread key
 #ifndef __linux__
     pthread_key_create(&worker_key, NULL);
@@ -697,10 +878,10 @@ lace_init(int n, size_t dqsize)
     }
 
     if (verbosity) {
-#if USE_HWLOC
+#if LACE_USE_HWLOC
         fprintf(stderr, "Initializing Lace, %u nodes, %u cores, %u logical processors, %d workers.\n", n_nodes, n_cores, n_pus, n_workers);
 #else
-        fprintf(stderr, "Initializing Lace, %d workers.\n", n_workers);
+        fprintf(stderr, "Initializing Lace, %u available cores, %d workers.\n", n_pus, n_workers);
 #endif
     }
 
@@ -716,11 +897,18 @@ lace_init(int n, size_t dqsize)
 #endif
 }
 
+/**
+ * Start the worker threads.
+ * If cb is set, then the current thread is suspended and Worker 0 is a new thread that starts with
+ * the given cb(arg) as the root task.
+ * If cb is not set, then the current thread is Worker 0 and this function returns.
+ */
 void
 lace_startup(size_t stacksize, lace_startup_cb cb, void *arg)
 {
     if (stacksize == 0) stacksize = default_stacksize;
 
+    /* Report startup if verbose */
     if (verbosity) {
         if (cb != 0) {
             fprintf(stderr, "Lace startup, creating %d worker threads with program stack %zu bytes.\n", n_workers, stacksize);
@@ -731,22 +919,21 @@ lace_startup(size_t stacksize, lace_startup_cb cb, void *arg)
         }
     }
 
-    /* Spawn workers */
-    int i;
-    for (i=1; i<n_workers; i++) lace_spawn_worker(i, stacksize, 0, 0);
+    /* Spawn all other workers */
+    for (unsigned int i=1; i<n_workers; i++) lace_spawn_worker(i, stacksize, 0, 0);
 
     if (cb != 0) {
+        /* If cb set, spawn worker 0 */
         main_cb = cb;
         lace_spawn_worker(0, stacksize, lace_main_wrapper, arg);
 
-        // Suspend this thread until cb returns
+        /* Suspend this thread until cb returns */
         pthread_mutex_lock(&wait_until_done_mutex);
-        pthread_cond_wait(&wait_until_done, &wait_until_done_mutex);
+        if (lace_quits == 0) pthread_cond_wait(&wait_until_done, &wait_until_done_mutex);
         pthread_mutex_unlock(&wait_until_done_mutex);
     } else {
-        // use this thread as worker and return control
-        lace_init_worker(0, 0);
-        lace_time_event(lace_get_worker(), 1);
+        /* If cb not set, use current thread as worker 0 */
+        lace_init_worker(0);
     }
 }
 
@@ -754,6 +941,9 @@ lace_startup(size_t stacksize, lace_startup_cb cb, void *arg)
 static uint64_t ctr_all[CTR_MAX];
 #endif
 
+/**
+ * Reset the counters of Lace.
+ */
 void
 lace_count_reset()
 {
@@ -779,6 +969,9 @@ lace_count_reset()
 #endif
 }
 
+/**
+ * Report counters to the given file.
+ */
 void
 lace_count_report_file(FILE *file)
 {
@@ -809,7 +1002,7 @@ lace_count_report_file(FILE *file)
             workers_p[i]->ctr[CTR_steal_tries], workers_p[i]->ctr[CTR_leaps],
             workers_p[i]->ctr[CTR_leap_busy], workers_p[i]->ctr[CTR_leap_tries]);
     }
-    fprintf(file, "Steals (sum): %zu good/%zu busy of %zu tries; leaps: %zu good/%zu busy of %zu tries\n",
+    fprintf(file, "Steals (sum): %zu good/%zu busy of %zu tries; leaps: %zu good/%zu busy of %zu tries\n", 
         ctr_all[CTR_steals], ctr_all[CTR_steal_busy],
         ctr_all[CTR_steal_tries], ctr_all[CTR_leaps],
         ctr_all[CTR_leap_busy], ctr_all[CTR_leap_tries]);
@@ -876,11 +1069,15 @@ lace_count_report_file(FILE *file)
     (void)file;
 }
 
+/**
+ * End Lace. All disabled threads are re-enabled, and then all Workers are signaled to quit.
+ * This function waits until all threads are done, then returns.
+ */
 void lace_exit()
 {
     lace_time_event(lace_get_worker(), 2);
 
-    // first suspend all other threads
+    // first suspend all enabled threads
     lace_suspend();
 
     // now enable all threads and tell them to quit
@@ -946,7 +1143,7 @@ lace_exec_in_new_frame(WorkerP *__lace_worker, Task *__lace_dq_head, Task *root)
     }
 }
 
-VOID_TASK_IMPL_2(lace_steal_loop_root, Task*, t, int*, done)
+VOID_TASK_2(lace_steal_loop_root, Task*, t, int*, done)
 {
     t->f(__lace_worker, __lace_dq_head, t);
     *done = 1;
@@ -958,7 +1155,7 @@ VOID_TASK_2(lace_together_helper, Task*, t, volatile int*, finished)
 
     for (;;) {
         int f = *finished;
-        if (cas(finished, f, f-1)) break;
+        if (__sync_bool_compare_and_swap(finished, f, f-1)) break;
     }
 
     while (*finished != 0) STEAL_RANDOM();
@@ -972,7 +1169,7 @@ lace_sync_and_exec(WorkerP *__lace_worker, Task *__lace_dq_head, Task *root)
 
     // one worker sets t to 0 again
     if (LACE_WORKER_ID == 0) lace_newframe.t = 0;
-    // else while (*(volatile Task**)&lace_newframe.t != 0) {}
+    // else while (*(Task* volatile *)&lace_newframe.t != 0) {}
 
     // the above line is commented out since lace_exec_in_new_frame includes
     // a lace_barrier before the task is executed
@@ -992,7 +1189,7 @@ lace_yield(WorkerP *__lace_worker, Task *__lace_dq_head)
 
     // one worker sets t to 0 again
     if (LACE_WORKER_ID == 0) lace_newframe.t = 0;
-    // else while (*(volatile Task**)&lace_newframe.t != 0) {}
+    // else while (*(Task* volatile *)&lace_newframe.t != 0) {}
 
     // the above line is commented out since lace_exec_in_new_frame includes
     // a lace_barrier before the task is executed
@@ -1014,7 +1211,7 @@ lace_do_together(WorkerP *__lace_worker, Task *__lace_dq_head, Task *t)
     t2->d.args.arg_1 = t;
     t2->d.args.arg_2 = &done;
 
-    while (!cas(&lace_newframe.t, 0, &_t2)) lace_yield(__lace_worker, __lace_dq_head);
+    while (!__sync_bool_compare_and_swap(&lace_newframe.t, 0, &_t2)) lace_yield(__lace_worker, __lace_dq_head);
     lace_sync_and_exec(__lace_worker, __lace_dq_head, &_t2);
 }
 
@@ -1041,6 +1238,16 @@ lace_do_newframe(WorkerP *__lace_worker, Task *__lace_dq_head, Task *t)
 
     compiler_barrier();
 
-    while (!cas(&lace_newframe.t, 0, &_s)) lace_yield(__lace_worker, __lace_dq_head);
+    while (!__sync_bool_compare_and_swap(&lace_newframe.t, 0, &_s)) lace_yield(__lace_worker, __lace_dq_head);
     lace_sync_and_exec(__lace_worker, __lace_dq_head, &_t2);
 }
+
+/**
+ * Called by _SPAWN functions when the Task stack is full.
+ */
+void
+lace_abort_stack_overflow(void)
+{
+    fprintf(stderr, "Lace fatal error: Task stack overflow! Aborting.\n");
+    exit(-1);
+}
diff --git a/lace.h b/lace.h
old mode 100644
new mode 100755
index 645baa4c94fbc28cb5dfd8f973406df2b392adc0..0b4b7a06f61b07911ddc10a7935fd2aa3d5fad6c
--- a/lace.h
+++ b/lace.h
@@ -1,5 +1,6 @@
-/*
- * Copyright 2013-2015 Formal Methods and Tools, University of Twente
+/* 
+ * Copyright 2013-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.
@@ -22,40 +23,281 @@
 #ifndef __LACE_H__
 #define __LACE_H__
 
+#ifdef __has_include
+#  if __has_include("lace_config.h")
+#    include <lace_config.h>
+#  else
+#    define LACE_PIE_TIMES     0
+#    define LACE_COUNT_TASKS   0
+#    define LACE_COUNT_STEALS  0
+#    define LACE_COUNT_SPLITS  0
+#    define LACE_USE_HWLOC     0
+#  endif
+#endif
+
 #ifdef __cplusplus
 extern "C" {
 #endif /* __cplusplus */
 
-/* Some flags */
+/**
+ * Using Lace.
+ *
+ * Optionally set the verbosity level with lace_set_verbosity.
+ * Then call lace_init to initialize the system.
+ * - lace_init(n_workers, deque_size);
+ *   set both parameters to 0 for reasonable defaults, using all available cores.
+ *
+ * You can create Worker threads yourself or let Lace create threads with lace_startup.
+ *
+ * When creating threads yourself, call the following functions:
+ *   - lace_init_worker to allocate and initialize the worker data structures
+ *     this method returns when all workers have called lace_init_worker
+ *   - lace_pin_worker (optional) to pin the thread and memory to a core
+ * The main worker can now start its root task. All other workers:
+ *   - lace_run_worker to perform work-stealing until the main worker calls lace_exit
+ *
+ * When letting Lace create threads with lace_startup
+ * - Call lace_startup with a callback to create N threads.
+ *   Returns after the callback has returned and all created threads are destroyed
+ * - Call lace_startup without a callback to create N-1 threads.
+ *   Returns control to the caller. When lace_exit is called, all created threads are terminated.
+ */
 
-#ifndef LACE_DEBUG_PROGRAMSTACK /* Write to stderr when 95% program stack reached */
-#define LACE_DEBUG_PROGRAMSTACK 0
-#endif
+/**
+ * Type definitions used in the functions below.
+ * - WorkerP contains the (private) Worker data
+ * - Task contains a single Task
+ */
+typedef struct _WorkerP WorkerP;
+typedef struct _Task Task;
 
-#ifndef LACE_LEAP_RANDOM /* Use random leaping when leapfrogging fails */
-#define LACE_LEAP_RANDOM 1
-#endif
+/**
+ * The macro LACE_TYPEDEF_CB(typedefname, taskname, parametertypes) defines
+ * a Task for use as a callback function.
+ */
+#define LACE_TYPEDEF_CB(t, f, ...) typedef t (*f)(WorkerP *, Task *, ##__VA_ARGS__);
 
-#ifndef LACE_PIE_TIMES /* Record time spent stealing and leapfrogging */
-#define LACE_PIE_TIMES 0
-#endif
+/**
+ * The lace_startup_cb type for a void Task with one void* parameter.
+ */
+LACE_TYPEDEF_CB(void, lace_startup_cb, void*);
 
-#ifndef LACE_COUNT_TASKS /* Count number of tasks executed */
-#define LACE_COUNT_TASKS 0
-#endif
+/**
+ * Set verbosity level (0 = no startup messages, 1 = startup messages)
+ * Default level: 0
+ */
+void lace_set_verbosity(int level);
+
+/**
+ * Initialize Lace for <n_workers> workers with a deque size of <dqsize> per worker.
+ * If <n_workers> is set to 0, automatically detects available cores.
+ * If <dqsize> is est to 0, uses a reasonable default value.
+ */
+void lace_init(unsigned int n_workers, size_t dqsize);
+
+/**
+ * Let Lace create worker threads.
+ * If <stacksize> is set to 0, uses a reaonable default value.
+ * If cb, arg are set to 0, then the current thread is initialized as the main Worker (Worker 0).
+ *
+ * If cb,arg are set, then the current thread is suspended. A new thread is made for Worker 0 and
+ * the task cb with paremeter arg is called; when cb returns, Lace is exited automatically.
+ */
+void lace_startup(size_t stacksize, lace_startup_cb, void* arg);
+
+/**
+ * Initialize worker <worker>, allocating memory.
+ * If <worker> is 0, then the current thread is the main worker.
+ */
+void lace_init_worker(unsigned int worker);
+
+/**
+ * Use hwloc to pin the current thread to a CPU and its allocated memory in the closest domain.
+ * Call this *after* lace_init_worker and *before* lace_run_worker.
+ */
+void lace_pin_worker(void);
+
+/**
+ * Perform work-stealing until lace_exit is called.
+ */
+void lace_run_worker(void);
+
+/**
+ * Steal a random task.
+ */
+#define lace_steal_random() CALL(lace_steal_random)
+void lace_steal_random_CALL(WorkerP*, Task*);
+
+/**
+ * Enter the Lace barrier. (all active workers must enter it before we can continue)
+ */
+void lace_barrier();
+
+/**
+ * Suspend all workers except the current worker.
+ * May only be used when all other workers are idle.
+ */
+void lace_suspend();
+
+/**
+ * Resume all workers.
+ */
+void lace_resume();
+
+/**
+ * When all other workers are suspended, some workers can be disabled using the following functions.
+ * With set_workers, all workers 0..(N-1) are enabled and N..max are disabled.
+ * You can never disable the current worker or reduce the number of workers below 1.
+ * You cannot add workers.
+ */
+void lace_set_workers(unsigned int workercount);
+
+/**
+ * Disable a suspended worker.
+ */
+void lace_disable_worker(unsigned int worker);
+
+/**
+ * Enable a suspended worker.
+ */
+void lace_enable_worker(unsigned int worker);
+
+/**
+ * Retrieve the number of enabled/active workers.
+ */
+unsigned int lace_enabled_workers();
+
+/**
+ * Retrieve the number of Lace workers
+ */
+unsigned int lace_workers();
+
+/**
+ * Retrieve the default program stack size
+ */
+size_t lace_default_stacksize();
+
+/**
+ * Retrieve the current worker data.
+ */
+WorkerP *lace_get_worker();
+
+/**
+ * Retrieve the current head of the deque
+ */
+Task *lace_get_head(WorkerP *);
+
+/**
+ * Exit Lace.
+ * This function is automatically called when lace_startup is called with a callback.
+ * This function must be called to exit Lace when lace_startup is called without a callback.
+ */
+void lace_exit();
+
+/**
+ * Create a pointer to a Tasks main function.
+ */
+#define TASK(f)           ( f##_CALL )
+
+/**
+ * Call a Tasks implementation (adds Lace variables to call)
+ */
+#define WRAP(f, ...)      ( f((WorkerP *)__lace_worker, (Task *)__lace_dq_head, ##__VA_ARGS__) )
+
+/**
+ * Sync a task.
+ */
+#define SYNC(f)           ( __lace_dq_head--, WRAP(f##_SYNC) )
+
+/**
+ * Sync a task, but if the task is not stolen, then do not execute it.
+ */
+#define DROP()            ( __lace_dq_head--, WRAP(lace_drop) )
+
+/**
+ * Spawn a task.
+ */
+#define SPAWN(f, ...)     ( WRAP(f##_SPAWN, ##__VA_ARGS__), __lace_dq_head++ )
+
+/**
+ * Directly execute a task.
+ */
+#define CALL(f, ...)      ( WRAP(f##_CALL, ##__VA_ARGS__) )
+
+/**
+ * Signal all workers to interrupt their current tasks and instead perform (a personal copy of) the given task.
+ */
+#define TOGETHER(f, ...)  ( WRAP(f##_TOGETHER, ##__VA_ARGS__) )
+
+/**
+ * Signal all workers to interrupt their current tasks and help the current thread with the given task.
+ */
+#define NEWFRAME(f, ...)  ( WRAP(f##_NEWFRAME, ##__VA_ARGS__) )
+
+/**
+ * (Try to) steal a task from a random worker.
+ */
+#define STEAL_RANDOM()    ( CALL(lace_steal_random) )
 
-#ifndef LACE_COUNT_STEALS /* Count number of steals performed */
-#define LACE_COUNT_STEALS 0
+/**
+ * Get the current worker id.
+ */
+#define LACE_WORKER_ID    ( __lace_worker->worker )
+
+/**
+ * Get the core where the current worker is pinned.
+ */
+#define LACE_WORKER_PU    ( __lace_worker->pu )
+
+/**
+ * Initialize local variables __lace_worker and __lace_dq_head which are required for most Lace functionality.
+ */
+#define LACE_ME WorkerP * __attribute__((unused)) __lace_worker = lace_get_worker(); Task * __attribute__((unused)) __lace_dq_head = lace_get_head(__lace_worker);
+
+/**
+ * Check if current tasks must be interrupted, and if so, interrupt.
+ */
+void lace_yield(WorkerP *__lace_worker, Task *__lace_dq_head);
+#define YIELD_NEWFRAME() { if (unlikely((*(Task* volatile *)&lace_newframe.t) != NULL)) lace_yield(__lace_worker, __lace_dq_head); }
+
+/**
+ * True if the given task is stolen, False otherwise.
+ */
+#define TASK_IS_STOLEN(t) ((size_t)t->thief > 1)
+
+/**
+ * True if the given task is completed, False otherwise.
+ */
+#define TASK_IS_COMPLETED(t) ((size_t)t->thief == 2)
+
+/**
+ * Retrieves a pointer to the result of the given task.
+ */
+#define TASK_RESULT(t) (&t->d[0])
+
+/**
+ * Compute a random number, thread-local (so scalable)
+ */
+#define LACE_TRNG (__lace_worker->rng = 2862933555777941757ULL * __lace_worker->rng + 3037000493ULL)
+
+/* Some flags that influence Lace behavior */
+
+#ifndef LACE_DEBUG_PROGRAMSTACK /* Write to stderr when 95% program stack reached */
+#define LACE_DEBUG_PROGRAMSTACK 0
 #endif
 
-#ifndef LACE_COUNT_SPLITS /* Count number of times the split point is moved */
-#define LACE_COUNT_SPLITS 0
+#ifndef LACE_LEAP_RANDOM /* Use random leaping when leapfrogging fails */
+#define LACE_LEAP_RANDOM 1
 #endif
 
 #ifndef LACE_COUNT_EVENTS
 #define LACE_COUNT_EVENTS (LACE_PIE_TIMES || LACE_COUNT_TASKS || LACE_COUNT_STEALS || LACE_COUNT_SPLITS)
 #endif
 
+/**
+ * Now follows the implementation of Lace
+ */
+
 /* Typical cacheline size of system architectures */
 #ifndef LINE_SIZE
 #define LINE_SIZE 64
@@ -166,10 +408,6 @@ typedef enum {
     CTR_MAX
 } CTR_index;
 
-struct _WorkerP;
-struct _Worker;
-struct _Task;
-
 #define THIEF_EMPTY     ((struct _Worker*)0x0)
 #define THIEF_TASK      ((struct _Worker*)0x1)
 #define THIEF_COMPLETED ((struct _Worker*)0x2)
@@ -212,8 +450,9 @@ typedef struct _WorkerP {
     Task *end;                  // dq+dq_size
     Worker *_public;            // pointer to public Worker struct
     size_t stack_trigger;       // for stack overflow detection
-    int16_t worker;             // what is my worker id?
-    int16_t pu;                 // my pu (for HWLOC)
+    uint64_t rng;               // my random seed (for lace_trng)
+    uint32_t seed;              // my random seed (for lace_steal_random)
+    uint16_t worker;            // what is my worker id?
     uint8_t allstolen;          // my allstolen
     volatile int8_t enabled;    // if this worker is enabled
 
@@ -223,131 +462,13 @@ typedef struct _WorkerP {
     volatile int level;
 #endif
 
-    uint32_t seed;              // my random seed (for lace_steal_random)
+    int16_t pu;                 // my pu (for HWLOC)
 } WorkerP;
 
-#define LACE_TYPEDEF_CB(t, f, ...) typedef t (*f)(WorkerP *, Task *, ##__VA_ARGS__);
-LACE_TYPEDEF_CB(void, lace_startup_cb, void*);
-
-/**
- * Set verbosity level (0 = no startup messages, 1 = startup messages)
- * Default level: 0
- */
-void lace_set_verbosity(int level);
-
-/**
- * Initialize master structures for Lace with <n_workers> workers
- * and default deque size of <dqsize>.
- * Does not create new threads.
- * Tries to detect number of cpus, if n_workers equals 0.
- */
-void lace_init(int n_workers, size_t dqsize);
-
-/**
- * After lace_init, start all worker threads.
- * If cb,arg are set, suspend this thread, call cb(arg) in a new thread
- * and exit Lace upon return
- * Otherwise, the current thread is initialized as a Lace thread.
- */
-void lace_startup(size_t stacksize, lace_startup_cb, void* arg);
-
-/**
- * Initialize current thread as worker <idx> and allocate a deque with size <dqsize>.
- * Use this when manually creating worker threads.
- */
-void lace_init_worker(int idx, size_t dqsize);
-
-/**
- * Manually spawn worker <idx> with (optional) program stack size <stacksize>.
- * If fun,arg are set, overrides default startup method.
- * Typically: for workers 1...(n_workers-1): lace_spawn_worker(i, stack_size, 0, 0);
- */
-pthread_t lace_spawn_worker(int idx, size_t stacksize, void *(*fun)(void*), void* arg);
-
-/**
- * Steal a random task.
- */
-#define lace_steal_random() CALL(lace_steal_random)
-void lace_steal_random_CALL(WorkerP*, Task*);
-
-/**
- * Steal random tasks until parameter *quit is set
- * Note: task declarations at end; quit is of type int*
- */
-#define lace_steal_random_loop(quit) CALL(lace_steal_random_loop, quit)
-#define lace_steal_loop(quit) CALL(lace_steal_loop, quit)
-
-/**
- * Barrier (all workers must enter it before progressing)
- */
-void lace_barrier();
-
-/**
- * Suspend and resume all other workers.
- * May only be used when all other workers are idle.
- */
-void lace_suspend();
-void lace_resume();
-
-/**
- * When all tasks are suspended, workers can be temporarily disabled.
- * With set_workers, all workers 0..(N-1) are enabled and N..max are disabled.
- * You can never disable the current worker or reduce the number of workers below 1.
- * You cannot add workers.
- */
-void lace_disable_worker(int worker);
-void lace_enable_worker(int worker);
-void lace_set_workers(int workercount);
-int lace_enabled_workers();
-
-/**
- * Retrieve number of Lace workers
- */
-size_t lace_workers();
-
-/**
- * Retrieve default program stack size
- */
-size_t lace_default_stacksize();
-
-/**
- * Retrieve current worker.
- */
-WorkerP *lace_get_worker();
-
-/**
- * Retrieve the current head of the deque
- */
-Task *lace_get_head(WorkerP *);
-
-/**
- * Exit Lace. Automatically called when started with cb,arg.
- */
-void lace_exit();
-
 #define LACE_STOLEN   ((Worker*)0)
 #define LACE_BUSY     ((Worker*)1)
 #define LACE_NOWORK   ((Worker*)2)
 
-#define TASK(f)           ( f##_CALL )
-#define WRAP(f, ...)      ( f((WorkerP *)__lace_worker, (Task *)__lace_dq_head, ##__VA_ARGS__) )
-#define SYNC(f)           ( __lace_dq_head--, WRAP(f##_SYNC) )
-#define DROP()            ( __lace_dq_head--, WRAP(lace_drop) )
-#define SPAWN(f, ...)     ( WRAP(f##_SPAWN, ##__VA_ARGS__), __lace_dq_head++ )
-#define CALL(f, ...)      ( WRAP(f##_CALL, ##__VA_ARGS__) )
-#define TOGETHER(f, ...)  ( WRAP(f##_TOGETHER, ##__VA_ARGS__) )
-#define NEWFRAME(f, ...)  ( WRAP(f##_NEWFRAME, ##__VA_ARGS__) )
-#define STEAL_RANDOM()    ( CALL(lace_steal_random) )
-#define LACE_WORKER_ID    ( __lace_worker->worker )
-#define LACE_WORKER_PU    ( __lace_worker->pu )
-
-/* Use LACE_ME to initialize Lace variables, in case you want to call multiple Lace tasks */
-#define LACE_ME WorkerP * __attribute__((unused)) __lace_worker = lace_get_worker(); Task * __attribute__((unused)) __lace_dq_head = lace_get_head(__lace_worker);
-
-#define TASK_IS_STOLEN(t) ((size_t)t->thief > 1)
-#define TASK_IS_COMPLETED(t) ((size_t)t->thief == 2)
-#define TASK_RESULT(t) (&t->d[0])
-
 #if LACE_DEBUG_PROGRAMSTACK
 static inline void CHECKSTACK(WorkerP *w)
 {
@@ -364,6 +485,8 @@ static inline void CHECKSTACK(WorkerP *w)
 #define CHECKSTACK(w) {}
 #endif
 
+void lace_abort_stack_overflow(void) __attribute__((noreturn));
+
 typedef struct
 {
     Task *t;
@@ -381,8 +504,18 @@ extern lace_newframe_t lace_newframe;
 void lace_do_together(WorkerP *__lace_worker, Task *__lace_dq_head, Task *task);
 void lace_do_newframe(WorkerP *__lace_worker, Task *__lace_dq_head, Task *task);
 
-void lace_yield(WorkerP *__lace_worker, Task *__lace_dq_head);
-#define YIELD_NEWFRAME() { if (unlikely((*(volatile Task**)&lace_newframe.t) != NULL)) lace_yield(__lace_worker, __lace_dq_head); }
+/**
+ * Make all tasks of the current worker shared.
+ */
+#define LACE_MAKE_ALL_SHARED() lace_make_all_shared(__lace_worker, __lace_dq_head)
+static inline void __attribute__((unused))
+lace_make_all_shared( WorkerP *w, Task *__lace_dq_head)
+{
+    if (w->split != __lace_dq_head) {
+        w->split = __lace_dq_head;
+        w->_public->ts.ts.split = __lace_dq_head - w->dq;
+    }
+}
 
 #if LACE_PIE_TIMES
 static void lace_time_event( WorkerP *w, int event )
@@ -636,7 +769,7 @@ void NAME##_SPAWN(WorkerP *w, Task *__dq_head )
     TailSplit ts;                                                                     \
     uint32_t head, split, newsplit;                                                   \
                                                                                       \
-    /* assert(__dq_head < w->end); */ /* Assuming to be true */                       \
+    if (__dq_head == w->end) lace_abort_stack_overflow();                             \
                                                                                       \
     t = (TD_##NAME *)__dq_head;                                                       \
     t->f = &NAME##_WRAP;                                                              \
@@ -786,7 +919,7 @@ void NAME##_SPAWN(WorkerP *w, Task *__dq_head )
     TailSplit ts;                                                                     \
     uint32_t head, split, newsplit;                                                   \
                                                                                       \
-    /* assert(__dq_head < w->end); */ /* Assuming to be true */                       \
+    if (__dq_head == w->end) lace_abort_stack_overflow();                             \
                                                                                       \
     t = (TD_##NAME *)__dq_head;                                                       \
     t->f = &NAME##_WRAP;                                                              \
@@ -939,7 +1072,7 @@ void NAME##_SPAWN(WorkerP *w, Task *__dq_head , ATYPE_1 arg_1)
     TailSplit ts;                                                                     \
     uint32_t head, split, newsplit;                                                   \
                                                                                       \
-    /* assert(__dq_head < w->end); */ /* Assuming to be true */                       \
+    if (__dq_head == w->end) lace_abort_stack_overflow();                             \
                                                                                       \
     t = (TD_##NAME *)__dq_head;                                                       \
     t->f = &NAME##_WRAP;                                                              \
@@ -1089,7 +1222,7 @@ void NAME##_SPAWN(WorkerP *w, Task *__dq_head , ATYPE_1 arg_1)
     TailSplit ts;                                                                     \
     uint32_t head, split, newsplit;                                                   \
                                                                                       \
-    /* assert(__dq_head < w->end); */ /* Assuming to be true */                       \
+    if (__dq_head == w->end) lace_abort_stack_overflow();                             \
                                                                                       \
     t = (TD_##NAME *)__dq_head;                                                       \
     t->f = &NAME##_WRAP;                                                              \
@@ -1242,7 +1375,7 @@ void NAME##_SPAWN(WorkerP *w, Task *__dq_head , ATYPE_1 arg_1, ATYPE_2 arg_2)
     TailSplit ts;                                                                     \
     uint32_t head, split, newsplit;                                                   \
                                                                                       \
-    /* assert(__dq_head < w->end); */ /* Assuming to be true */                       \
+    if (__dq_head == w->end) lace_abort_stack_overflow();                             \
                                                                                       \
     t = (TD_##NAME *)__dq_head;                                                       \
     t->f = &NAME##_WRAP;                                                              \
@@ -1392,7 +1525,7 @@ void NAME##_SPAWN(WorkerP *w, Task *__dq_head , ATYPE_1 arg_1, ATYPE_2 arg_2)
     TailSplit ts;                                                                     \
     uint32_t head, split, newsplit;                                                   \
                                                                                       \
-    /* assert(__dq_head < w->end); */ /* Assuming to be true */                       \
+    if (__dq_head == w->end) lace_abort_stack_overflow();                             \
                                                                                       \
     t = (TD_##NAME *)__dq_head;                                                       \
     t->f = &NAME##_WRAP;                                                              \
@@ -1545,7 +1678,7 @@ void NAME##_SPAWN(WorkerP *w, Task *__dq_head , ATYPE_1 arg_1, ATYPE_2 arg_2, AT
     TailSplit ts;                                                                     \
     uint32_t head, split, newsplit;                                                   \
                                                                                       \
-    /* assert(__dq_head < w->end); */ /* Assuming to be true */                       \
+    if (__dq_head == w->end) lace_abort_stack_overflow();                             \
                                                                                       \
     t = (TD_##NAME *)__dq_head;                                                       \
     t->f = &NAME##_WRAP;                                                              \
@@ -1695,7 +1828,7 @@ void NAME##_SPAWN(WorkerP *w, Task *__dq_head , ATYPE_1 arg_1, ATYPE_2 arg_2, AT
     TailSplit ts;                                                                     \
     uint32_t head, split, newsplit;                                                   \
                                                                                       \
-    /* assert(__dq_head < w->end); */ /* Assuming to be true */                       \
+    if (__dq_head == w->end) lace_abort_stack_overflow();                             \
                                                                                       \
     t = (TD_##NAME *)__dq_head;                                                       \
     t->f = &NAME##_WRAP;                                                              \
@@ -1848,7 +1981,7 @@ void NAME##_SPAWN(WorkerP *w, Task *__dq_head , ATYPE_1 arg_1, ATYPE_2 arg_2, AT
     TailSplit ts;                                                                     \
     uint32_t head, split, newsplit;                                                   \
                                                                                       \
-    /* assert(__dq_head < w->end); */ /* Assuming to be true */                       \
+    if (__dq_head == w->end) lace_abort_stack_overflow();                             \
                                                                                       \
     t = (TD_##NAME *)__dq_head;                                                       \
     t->f = &NAME##_WRAP;                                                              \
@@ -1998,7 +2131,7 @@ void NAME##_SPAWN(WorkerP *w, Task *__dq_head , ATYPE_1 arg_1, ATYPE_2 arg_2, AT
     TailSplit ts;                                                                     \
     uint32_t head, split, newsplit;                                                   \
                                                                                       \
-    /* assert(__dq_head < w->end); */ /* Assuming to be true */                       \
+    if (__dq_head == w->end) lace_abort_stack_overflow();                             \
                                                                                       \
     t = (TD_##NAME *)__dq_head;                                                       \
     t->f = &NAME##_WRAP;                                                              \
@@ -2151,7 +2284,7 @@ void NAME##_SPAWN(WorkerP *w, Task *__dq_head , ATYPE_1 arg_1, ATYPE_2 arg_2, AT
     TailSplit ts;                                                                     \
     uint32_t head, split, newsplit;                                                   \
                                                                                       \
-    /* assert(__dq_head < w->end); */ /* Assuming to be true */                       \
+    if (__dq_head == w->end) lace_abort_stack_overflow();                             \
                                                                                       \
     t = (TD_##NAME *)__dq_head;                                                       \
     t->f = &NAME##_WRAP;                                                              \
@@ -2301,7 +2434,7 @@ void NAME##_SPAWN(WorkerP *w, Task *__dq_head , ATYPE_1 arg_1, ATYPE_2 arg_2, AT
     TailSplit ts;                                                                     \
     uint32_t head, split, newsplit;                                                   \
                                                                                       \
-    /* assert(__dq_head < w->end); */ /* Assuming to be true */                       \
+    if (__dq_head == w->end) lace_abort_stack_overflow();                             \
                                                                                       \
     t = (TD_##NAME *)__dq_head;                                                       \
     t->f = &NAME##_WRAP;                                                              \
@@ -2454,7 +2587,7 @@ void NAME##_SPAWN(WorkerP *w, Task *__dq_head , ATYPE_1 arg_1, ATYPE_2 arg_2, AT
     TailSplit ts;                                                                     \
     uint32_t head, split, newsplit;                                                   \
                                                                                       \
-    /* assert(__dq_head < w->end); */ /* Assuming to be true */                       \
+    if (__dq_head == w->end) lace_abort_stack_overflow();                             \
                                                                                       \
     t = (TD_##NAME *)__dq_head;                                                       \
     t->f = &NAME##_WRAP;                                                              \
@@ -2604,7 +2737,7 @@ void NAME##_SPAWN(WorkerP *w, Task *__dq_head , ATYPE_1 arg_1, ATYPE_2 arg_2, AT
     TailSplit ts;                                                                     \
     uint32_t head, split, newsplit;                                                   \
                                                                                       \
-    /* assert(__dq_head < w->end); */ /* Assuming to be true */                       \
+    if (__dq_head == w->end) lace_abort_stack_overflow();                             \
                                                                                       \
     t = (TD_##NAME *)__dq_head;                                                       \
     t->f = &NAME##_WRAP;                                                              \
@@ -2731,10 +2864,6 @@ void NAME##_WORK(WorkerP *__lace_worker __attribute__((unused)), Task *__lace_dq
 #define VOID_TASK_6(NAME, ATYPE_1, ARG_1, ATYPE_2, ARG_2, ATYPE_3, ARG_3, ATYPE_4, ARG_4, ATYPE_5, ARG_5, ATYPE_6, ARG_6) VOID_TASK_DECL_6(NAME, ATYPE_1, ATYPE_2, ATYPE_3, ATYPE_4, ATYPE_5, ATYPE_6) VOID_TASK_IMPL_6(NAME, ATYPE_1, ARG_1, ATYPE_2, ARG_2, ATYPE_3, ARG_3, ATYPE_4, ARG_4, ATYPE_5, ARG_5, ATYPE_6, ARG_6)
 
 
-VOID_TASK_DECL_0(lace_steal_random);
-VOID_TASK_DECL_1(lace_steal_random_loop, int*);
-VOID_TASK_DECL_1(lace_steal_loop, int*);
-VOID_TASK_DECL_2(lace_steal_loop_root, Task *, int*);
 
 #ifdef __cplusplus
 }
diff --git a/sha2.c b/sha2.c
old mode 100644
new mode 100755
index db17dbb2d726b42586cc1169e6f08a7eb8397c3f..69c7e7d5afaeb298f285170e687e9323864969f7
--- a/sha2.c
+++ b/sha2.c
@@ -509,9 +509,6 @@ void SHA256_Transform(SHA256_CTX* context, const sha2_word32* data) {
 	context->state[5] += f;
 	context->state[6] += g;
 	context->state[7] += h;
-
-	/* Clean up */
-	a = b = c = d = e = f = g = h = T1 = T2 = 0;
 }
 
 #endif /* SHA2_UNROLL_TRANSFORM */
@@ -543,8 +540,6 @@ void SHA256_Update(SHA256_CTX* context, const sha2_byte *data, size_t len) {
 			/* The buffer is not yet full */
 			MEMCPY_BCOPY(&context->buffer[usedspace], data, len);
 			context->bitcount += len << 3;
-			/* Clean up: */
-			usedspace = freespace = 0;
 			return;
 		}
 	}
@@ -560,8 +555,6 @@ void SHA256_Update(SHA256_CTX* context, const sha2_byte *data, size_t len) {
 		MEMCPY_BCOPY(context->buffer, data, len);
 		context->bitcount += len << 3;
 	}
-	/* Clean up: */
-	usedspace = freespace = 0;
 }
 
 void SHA256_Final(sha2_byte digest[], SHA256_CTX* context) {
@@ -625,7 +618,6 @@ void SHA256_Final(sha2_byte digest[], SHA256_CTX* context) {
 
 	/* Clean up state data: */
 	MEMSET_BZERO(context, sizeof(SHA256_CTX));
-	usedspace = 0;
 }
 
 char *SHA256_End(SHA256_CTX* context, char buffer[]) {
@@ -832,9 +824,6 @@ void SHA512_Transform(SHA512_CTX* context, const sha2_word64* data) {
 	context->state[5] += f;
 	context->state[6] += g;
 	context->state[7] += h;
-
-	/* Clean up */
-	a = b = c = d = e = f = g = h = T1 = T2 = 0;
 }
 
 #endif /* SHA2_UNROLL_TRANSFORM */
@@ -866,8 +855,6 @@ void SHA512_Update(SHA512_CTX* context, const sha2_byte *data, size_t len) {
 			/* The buffer is not yet full */
 			MEMCPY_BCOPY(&context->buffer[usedspace], data, len);
 			ADDINC128(context->bitcount, len << 3);
-			/* Clean up: */
-			usedspace = freespace = 0;
 			return;
 		}
 	}
@@ -883,8 +870,6 @@ void SHA512_Update(SHA512_CTX* context, const sha2_byte *data, size_t len) {
 		MEMCPY_BCOPY(context->buffer, data, len);
 		ADDINC128(context->bitcount, len << 3);
 	}
-	/* Clean up: */
-	usedspace = freespace = 0;
 }
 
 void SHA512_Last(SHA512_CTX* context) {
diff --git a/sha2.h b/sha2.h
old mode 100644
new mode 100755
diff --git a/stats.c b/stats.c
old mode 100644
new mode 100755
diff --git a/stats.h b/stats.h
old mode 100644
new mode 100755
diff --git a/sylvan.h b/sylvan.h
old mode 100644
new mode 100755
index 25d8fc15ea7414f0528a3f986995b415228489d5..21d70c6003bd66be969bed570e005242f1a4f642
--- a/sylvan.h
+++ b/sylvan.h
@@ -1,5 +1,6 @@
 /*
- * Copyright 2011-2015 Formal Methods and Tools, University of Twente
+ * 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.
@@ -15,169 +16,50 @@
  */
 
 /**
- * Sylvan: parallel BDD/ListDD package.
- *
- * This is a multi-core implementation of BDDs with complement edges.
- *
- * This package requires parallel the work-stealing framework Lace.
- * Lace must be initialized before initializing Sylvan
- *
- * This package uses explicit referencing.
- * Use sylvan_ref and sylvan_deref to manage external references.
- *
- * Garbage collection requires all workers to cooperate. Garbage collection is either initiated
- * by the user (calling sylvan_gc) or when the nodes table is full. All Sylvan operations
- * check whether they need to cooperate on garbage collection. Garbage collection cannot occur
- * otherwise. This means that it is perfectly fine to do this:
- *              BDD a = sylvan_ref(sylvan_and(b, c));
- * since it is not possible that garbage collection occurs between the two calls.
- *
- * To temporarily disable garbage collection, use sylvan_gc_disable() and sylvan_gc_enable().
+ * Sylvan: parallel MTBDD/ListDD package.
+ * Include this file.
  */
 
 #include <sylvan_config.h>
 
+#include <assert.h>
+#include <stddef.h>
 #include <stdint.h>
 #include <stdio.h> // for FILE
-#include <stdlib.h>
-#include <lace.h> // for definitions
-
-#include <sylvan_cache.h>
-#include <llmsset.h>
-#include <stats.h>
-
-#ifndef SYLVAN_H
-#define SYLVAN_H
-
-#ifdef __cplusplus
-extern "C" {
-#endif /* __cplusplus */
-
-#ifndef SYLVAN_SIZE_FIBONACCI
-#define SYLVAN_SIZE_FIBONACCI 0
+#include <stdlib.h> // for realloc
+#include <unistd.h>
+#include <pthread.h>
+
+#if SYLVAN_STATS
+#ifdef __MACH__
+#include <mach/mach_time.h>
+#else
+#include <time.h>
+#endif
 #endif
-
-// For now, only support 64-bit systems
-typedef char __sylvan_check_size_t_is_8_bytes[(sizeof(uint64_t) == sizeof(size_t))?1:-1];
-
-/**
- * Initialize the Sylvan parallel decision diagrams package.
- *
- * After initialization, call sylvan_init_bdd and/or sylvan_init_ldd if you want to use
- * the BDD and/or LDD functionality.
- *
- * BDDs and LDDs share a common node table and operations cache.
- *
- * The node table is resizable.
- * The table is resized automatically when >50% of the table is filled during garbage collection.
- * This behavior can be customized by overriding the gc hook.
- *
- * Memory usage:
- * Every node requires 24 bytes memory. (16 bytes data + 8 bytes overhead)
- * Every operation cache entry requires 36 bytes memory. (32 bytes data + 4 bytes overhead)
- *
- * Reasonable defaults: datasize of 1L<<26 (2048 MB), cachesize of 1L<<25 (1152 MB)
- */
-void sylvan_init_package(size_t initial_tablesize, size_t max_tablesize, size_t initial_cachesize, size_t max_cachesize);
-
-/**
- * Frees all Sylvan data (also calls the quit() functions of BDD/MDD parts)
- */
-void sylvan_quit();
-
-/**
- * Return number of occupied buckets in nodes table and total number of buckets.
- */
-VOID_TASK_DECL_2(sylvan_table_usage, size_t*, size_t*);
-#define sylvan_table_usage(filled, total) (CALL(sylvan_table_usage, filled, total))
-
-/**
- * Perform garbage collection.
- *
- * Garbage collection is performed in a new Lace frame, interrupting all ongoing work
- * until garbage collection is completed.
- *
- * Garbage collection procedure:
- * 1) The operation cache is cleared and the hash table is reset.
- * 2) All live nodes are marked (to be rehashed). This is done by the "mark" callbacks.
- * 3) The "hook" callback is called.
- *    By default, this doubles the hash table size when it is >50% full.
- * 4) All live nodes are rehashed into the hash table.
- *
- * The behavior of garbage collection can be customized by adding "mark" callbacks and
- * replacing the "hook" callback.
- */
-VOID_TASK_DECL_0(sylvan_gc);
-#define sylvan_gc() (CALL(sylvan_gc))
-
-/**
- * Enable or disable garbage collection.
- *
- * This affects both automatic and manual garbage collection, i.e.,
- * calling sylvan_gc() while garbage collection is disabled does not have any effect.
- */
-void sylvan_gc_enable();
-void sylvan_gc_disable();
-
-/**
- * Add a "mark" callback to the list of callbacks.
- *
- * These are called during garbage collection to recursively mark nodes.
- *
- * Default "mark" functions that mark external references (via sylvan_ref) and internal
- * references (inside operations) are added by sylvan_init_bdd/sylvan_init_bdd.
- *
- * Functions are called in order.
- * level 10: marking functions of Sylvan (external/internal references)
- * level 20: call the hook function (for resizing)
- * level 30: rehashing
- */
-LACE_TYPEDEF_CB(void, gc_mark_cb);
-void sylvan_gc_add_mark(int order, gc_mark_cb callback);
-
-/**
- * Set "hook" callback. There can be only one.
- *
- * The hook is called after the "mark" phase and before the "rehash" phase.
- * This allows users to perform certain actions, such as resizing the nodes table
- * and the operation cache. Also, dynamic resizing could be performed then.
- */
-LACE_TYPEDEF_CB(void, gc_hook_cb);
-void sylvan_gc_set_hook(gc_hook_cb new_hook);
 
 /**
- * One of the hooks for resizing behavior.
- * Default if SYLVAN_AGGRESSIVE_RESIZE is set.
- * Always double size on gc() until maximum reached.
+ * Sylvan header files outside the namespace
  */
-VOID_TASK_DECL_0(sylvan_gc_aggressive_resize);
 
-/**
- * One of the hooks for resizing behavior.
- * Default if SYLVAN_AGGRESSIVE_RESIZE is not set.
- * Double size on gc() whenever >50% is used.
- */
-VOID_TASK_DECL_0(sylvan_gc_default_hook);
+#include <lace.h>
+#include <sylvan_tls.h>
 
-/**
- * Set "notify on dead" callback for the nodes table.
- * See also documentation in llmsset.h
- */
-#define sylvan_set_ondead(cb, ctx) llmsset_set_ondead(nodes, cb, ctx)
+#ifdef __cplusplus
+namespace sylvan {
+#endif
 
 /**
- * Global variables (number of workers, nodes table)
+ * Sylvan header files inside the namespace
  */
 
-extern llmsset_t nodes;
-
-#ifdef __cplusplus
-}
-#endif /* __cplusplus */
-
+#include <sylvan_common.h>
+#include <sylvan_stats.h>
+#include <sylvan_mt.h>
+#include <sylvan_mtbdd.h>
 #include <sylvan_bdd.h>
 #include <sylvan_ldd.h>
-#include <sylvan_seq.h>
-#include <sylvan_mtbdd.h>
 
+#ifdef __cplusplus
+}
 #endif
diff --git a/sylvan_bdd.c b/sylvan_bdd.c
old mode 100644
new mode 100755
index 45b59c411bae3820126710dd45497f2f81f29882..2d910e295b7a1164cdd230c0a61e36b864764f44
--- a/sylvan_bdd.c
+++ b/sylvan_bdd.c
@@ -1,5 +1,6 @@
 /*
- * Copyright 2011-2014 Formal Methods and Tools, University of Twente
+ * 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.
@@ -14,373 +15,32 @@
  * limitations under the License.
  */
 
-#include <sylvan_config.h>
+#include <sylvan_int.h>
 
-#include <assert.h>
 #include <inttypes.h>
 #include <math.h>
-#include <pthread.h>
-#include <stdint.h>
-#include <stdio.h>
-#include <stdlib.h>
 #include <string.h>
 
 #include <avl.h>
-#include <refs.h>
-#include <sha2.h>
-#include <sylvan.h>
-#include <sylvan_common.h>
-
-/**
- * Complement handling macros
- */
-#define BDD_HASMARK(s)              (s&sylvan_complement?1:0)
-#define BDD_TOGGLEMARK(s)           (s^sylvan_complement)
-#define BDD_STRIPMARK(s)            (s&~sylvan_complement)
-#define BDD_TRANSFERMARK(from, to)  (to ^ (from & sylvan_complement))
-// Equal under mark
-#define BDD_EQUALM(a, b)            ((((a)^(b))&(~sylvan_complement))==0)
-
-/**
- * BDD node structure
- */
-typedef struct __attribute__((packed)) bddnode {
-    uint64_t a, b;
-} * bddnode_t; // 16 bytes
-
-#define GETNODE(bdd) ((bddnode_t)llmsset_index_to_ptr(nodes, bdd&0x000000ffffffffff))
-
-static inline int __attribute__((unused))
-bddnode_getcomp(bddnode_t n)
-{
-    return n->a & 0x8000000000000000 ? 1 : 0;
-}
-
-static inline uint64_t
-bddnode_getlow(bddnode_t n)
-{
-    return n->b & 0x000000ffffffffff; // 40 bits
-}
-
-static inline uint64_t
-bddnode_gethigh(bddnode_t n)
-{
-    return n->a & 0x800000ffffffffff; // 40 bits plus high bit of first
-}
-
-static inline uint32_t
-bddnode_getvariable(bddnode_t n)
-{
-    return (uint32_t)(n->b >> 40);
-}
-
-static inline int
-bddnode_getmark(bddnode_t n)
-{
-    return n->a & 0x2000000000000000 ? 1 : 0;
-}
-
-static inline void
-bddnode_setmark(bddnode_t n, int mark)
-{
-    if (mark) n->a |= 0x2000000000000000;
-    else n->a &= 0xdfffffffffffffff;
-}
-
-static inline void
-bddnode_makenode(bddnode_t n, uint32_t var, uint64_t low, uint64_t high)
-{
-    n->a = high;
-    n->b = ((uint64_t)var)<<40 | low;
-}
-
-/**
- * Implementation of garbage collection.
- */
-
-/* Recursively mark BDD nodes as 'in use' */
-VOID_TASK_IMPL_1(sylvan_gc_mark_rec, syBDD, bdd)
-{
-    if (bdd == sylvan_false || bdd == sylvan_true) return;
-
-    if (llmsset_mark(nodes, bdd&0x000000ffffffffff)) {
-        bddnode_t n = GETNODE(bdd);
-        SPAWN(sylvan_gc_mark_rec, bddnode_getlow(n));
-        CALL(sylvan_gc_mark_rec, bddnode_gethigh(n));
-        SYNC(sylvan_gc_mark_rec);
-    }
-}
-
-/**
- * External references
- */
-
-refs_table_t bdd_refs;
-refs_table_t bdd_protected;
-static int bdd_protected_created = 0;
-
-syBDD
-sylvan_ref(syBDD a)
-{
-    if (a == sylvan_false || a == sylvan_true) return a;
-    refs_up(&bdd_refs, BDD_STRIPMARK(a));
-    return a;
-}
-
-void
-sylvan_deref(syBDD a)
-{
-    if (a == sylvan_false || a == sylvan_true) return;
-    refs_down(&bdd_refs, BDD_STRIPMARK(a));
-}
-
-void
-sylvan_protect(syBDD *a)
-{
-    if (!bdd_protected_created) {
-        // In C++, sometimes sylvan_protect is called before Sylvan is initialized. Just create a table.
-        protect_create(&bdd_protected, 4096);
-        bdd_protected_created = 1;
-    }
-    protect_up(&bdd_protected, (size_t)a);
-}
-
-void
-sylvan_unprotect(syBDD *a)
-{
-    if (bdd_protected.refs_table != NULL) protect_down(&bdd_protected, (size_t)a);
-}
-
-size_t
-sylvan_count_refs()
-{
-    return refs_count(&bdd_refs);
-}
-
-size_t
-sylvan_count_protected()
-{
-    return protect_count(&bdd_protected);
-}
-
-/* Called during garbage collection */
-VOID_TASK_0(sylvan_gc_mark_external_refs)
-{
-    // iterate through refs hash table, mark all found
-    size_t count=0;
-    uint64_t *it = refs_iter(&bdd_refs, 0, bdd_refs.refs_size);
-    while (it != NULL) {
-        syBDD to_mark = refs_next(&bdd_refs, &it, bdd_refs.refs_size);
-        SPAWN(sylvan_gc_mark_rec, to_mark);
-        count++;
-    }
-    while (count--) {
-        SYNC(sylvan_gc_mark_rec);
-    }
-}
-
-VOID_TASK_0(sylvan_gc_mark_protected)
-{
-    // iterate through refs hash table, mark all found
-    size_t count=0;
-    uint64_t *it = protect_iter(&bdd_protected, 0, bdd_protected.refs_size);
-    while (it != NULL) {
-        syBDD *to_mark = (syBDD*)protect_next(&bdd_protected, &it, bdd_protected.refs_size);
-        SPAWN(sylvan_gc_mark_rec, *to_mark);
-        count++;
-    }
-    while (count--) {
-        SYNC(sylvan_gc_mark_rec);
-    }
-}
-
-/* Infrastructure for internal markings */
-DECLARE_THREAD_LOCAL(bdd_refs_key, bdd_refs_internal_t);
-
-VOID_TASK_0(bdd_refs_mark_task)
-{
-    LOCALIZE_THREAD_LOCAL(bdd_refs_key, bdd_refs_internal_t);
-    size_t i, j=0;
-    for (i=0; i<bdd_refs_key->r_count; i++) {
-        if (j >= 40) {
-            while (j--) SYNC(sylvan_gc_mark_rec);
-            j=0;
-        }
-        SPAWN(sylvan_gc_mark_rec, bdd_refs_key->results[i]);
-        j++;
-    }
-    for (i=0; i<bdd_refs_key->s_count; i++) {
-        Task *t = bdd_refs_key->spawns[i];
-        if (!TASK_IS_STOLEN(t)) break;
-        if (TASK_IS_COMPLETED(t)) {
-            if (j >= 40) {
-                while (j--) SYNC(sylvan_gc_mark_rec);
-                j=0;
-            }
-            SPAWN(sylvan_gc_mark_rec, *(syBDD*)TASK_RESULT(t));
-            j++;
-        }
-    }
-    while (j--) SYNC(sylvan_gc_mark_rec);
-}
-
-VOID_TASK_0(bdd_refs_mark)
-{
-    TOGETHER(bdd_refs_mark_task);
-}
-
-VOID_TASK_0(bdd_refs_init_task)
-{
-    bdd_refs_internal_t s = (bdd_refs_internal_t)malloc(sizeof(struct bdd_refs_internal));
-    s->r_size = 128;
-    s->r_count = 0;
-    s->s_size = 128;
-    s->s_count = 0;
-    s->results = (syBDD*)malloc(sizeof(syBDD) * 128);
-    s->spawns = (Task**)malloc(sizeof(Task*) * 128);
-    SET_THREAD_LOCAL(bdd_refs_key, s);
-}
-
-VOID_TASK_0(bdd_refs_init)
-{
-    INIT_THREAD_LOCAL(bdd_refs_key);
-    TOGETHER(bdd_refs_init_task);
-    sylvan_gc_add_mark(10, TASK(bdd_refs_mark));
-}
-
-/**
- * Initialize and quit functions
- */
 
 static int granularity = 1; // default
 
-static void
-sylvan_quit_bdd()
-{
-    refs_free(&bdd_refs);
-    if (bdd_protected_created) {
-        protect_free(&bdd_protected);
-        bdd_protected_created = 0;
-    }
-}
-
 void
-sylvan_init_bdd(int _granularity)
+sylvan_set_granularity(int value)
 {
-    sylvan_register_quit(sylvan_quit_bdd);
-    sylvan_gc_add_mark(10, TASK(sylvan_gc_mark_external_refs));
-    sylvan_gc_add_mark(10, TASK(sylvan_gc_mark_protected));
-
-    granularity = _granularity;
-
-    // Sanity check
-    if (sizeof(struct bddnode) != 16) {
-        fprintf(stderr, "Invalid size of bdd nodes: %ld\n", sizeof(struct bddnode));
-        exit(1);
-    }
-
-    refs_create(&bdd_refs, 1024);
-    if (!bdd_protected_created) {
-        protect_create(&bdd_protected, 4096);
-        bdd_protected_created = 1;
-    }
-
-    LACE_ME;
-    CALL(bdd_refs_init);
+    granularity = value;
 }
 
-/**
- * Core BDD operations
- */
-
-syBDD
-sylvan_makenode(BDDVAR level, syBDD low, syBDD high)
-{
-    if (low == high) return low;
-
-    // Normalization to keep canonicity
-    // low will have no mark
-
-    struct bddnode n;
-    int mark;
-
-    if (BDD_HASMARK(low)) {
-        mark = 1;
-        low = BDD_TOGGLEMARK(low);
-        high = BDD_TOGGLEMARK(high);
-    } else {
-        mark = 0;
-    }
-
-    bddnode_makenode(&n, level, low, high);
-
-    syBDD result;
-    int created;
-    uint64_t index = llmsset_lookup(nodes, n.a, n.b, &created);
-    if (index == 0) {
-        LACE_ME;
-
-        bdd_refs_push(low);
-        bdd_refs_push(high);
-        sylvan_gc();
-        bdd_refs_pop(2);
-
-        index = llmsset_lookup(nodes, n.a, n.b, &created);
-        if (index == 0) {
-            fprintf(stderr, "BDD Unique table full, %zu of %zu buckets filled!\n", llmsset_count_marked(nodes), llmsset_get_size(nodes));
-            exit(1);
-        }
-    }
-
-    if (created) sylvan_stats_count(BDD_NODES_CREATED);
-    else sylvan_stats_count(BDD_NODES_REUSED);
-
-    result = index;
-    return mark ? result | sylvan_complement : result;
-}
-
-syBDD
-sylvan_ithvar(BDDVAR level)
-{
-    return sylvan_makenode(level, sylvan_false, sylvan_true);
-}
-
-BDDVAR
-sylvan_var(syBDD bdd)
-{
-    return bddnode_getvariable(GETNODE(bdd));
-}
-
-static syBDD
-node_low(syBDD bdd, bddnode_t node)
-{
-    return BDD_TRANSFERMARK(bdd, bddnode_getlow(node));
-}
-
-static syBDD
-node_high(syBDD bdd, bddnode_t node)
-{
-    return BDD_TRANSFERMARK(bdd, bddnode_gethigh(node));
-}
-
-syBDD
-sylvan_low(syBDD bdd)
-{
-    if (sylvan_isconst(bdd)) return bdd;
-    return node_low(bdd, GETNODE(bdd));
-}
-
-syBDD
-sylvan_high(syBDD bdd)
+int
+sylvan_get_granularity()
 {
-    if (sylvan_isconst(bdd)) return bdd;
-    return node_high(bdd, GETNODE(bdd));
+    return granularity;
 }
 
 /**
  * Implementation of unary, binary and if-then-else operators.
  */
-TASK_IMPL_3(syBDD, sylvan_and, syBDD, a, syBDD, b, BDDVAR, prev_level)
+TASK_IMPL_3(SyBDD, sylvan_and, SyBDD, a, SyBDD, b, BDDVAR, prev_level)
 {
     /* Terminal cases */
     if (a == sylvan_true) return b;
@@ -392,17 +52,18 @@ TASK_IMPL_3(syBDD, sylvan_and, syBDD, a, syBDD, b, BDDVAR, prev_level)
 
     sylvan_gc_test();
 
+    /* Count operation */
     sylvan_stats_count(BDD_AND);
 
     /* Improve for caching */
     if (BDD_STRIPMARK(a) > BDD_STRIPMARK(b)) {
-        syBDD t = b;
+        SyBDD t = b;
         b = a;
         a = t;
     }
 
-    bddnode_t na = GETNODE(a);
-    bddnode_t nb = GETNODE(b);
+    bddnode_t na = MTBDD_GETNODE(a);
+    bddnode_t nb = MTBDD_GETNODE(b);
 
     BDDVAR va = bddnode_getvariable(na);
     BDDVAR vb = bddnode_getvariable(nb);
@@ -410,7 +71,7 @@ TASK_IMPL_3(syBDD, sylvan_and, syBDD, a, syBDD, b, BDDVAR, prev_level)
 
     int cachenow = granularity < 2 || prev_level == 0 ? 1 : prev_level / granularity != level / granularity;
     if (cachenow) {
-        syBDD result;
+        SyBDD result;
         if (cache_get3(CACHE_BDD_AND, a, b, sylvan_false, &result)) {
             sylvan_stats_count(BDD_AND_CACHED);
             return result;
@@ -418,8 +79,8 @@ TASK_IMPL_3(syBDD, sylvan_and, syBDD, a, syBDD, b, BDDVAR, prev_level)
     }
 
     // Get cofactors
-    syBDD aLow = a, aHigh = a;
-    syBDD bLow = b, bHigh = b;
+    SyBDD aLow = a, aHigh = a;
+    SyBDD bLow = b, bHigh = b;
     if (level == va) {
         aLow = node_low(a, na);
         aHigh = node_high(a, na);
@@ -430,7 +91,7 @@ TASK_IMPL_3(syBDD, sylvan_and, syBDD, a, syBDD, b, BDDVAR, prev_level)
     }
 
     // Recursive computation
-    syBDD low=sylvan_invalid, high=sylvan_invalid, result;
+    SyBDD low=sylvan_invalid, high=sylvan_invalid, result;
 
     int n=0;
 
@@ -470,7 +131,7 @@ TASK_IMPL_3(syBDD, sylvan_and, syBDD, a, syBDD, b, BDDVAR, prev_level)
     return result;
 }
 
-TASK_IMPL_3(syBDD, sylvan_xor, syBDD, a, syBDD, b, BDDVAR, prev_level)
+TASK_IMPL_3(SyBDD, sylvan_xor, SyBDD, a, SyBDD, b, BDDVAR, prev_level)
 {
     /* Terminal cases */
     if (a == sylvan_false) return b;
@@ -482,11 +143,12 @@ TASK_IMPL_3(syBDD, sylvan_xor, syBDD, a, syBDD, b, BDDVAR, prev_level)
 
     sylvan_gc_test();
 
+    /* Count operation */
     sylvan_stats_count(BDD_XOR);
 
     /* Improve for caching */
     if (BDD_STRIPMARK(a) > BDD_STRIPMARK(b)) {
-        syBDD t = b;
+        SyBDD t = b;
         b = a;
         a = t;
     }
@@ -497,8 +159,8 @@ TASK_IMPL_3(syBDD, sylvan_xor, syBDD, a, syBDD, b, BDDVAR, prev_level)
         b = sylvan_not(b);
     }
 
-    bddnode_t na = GETNODE(a);
-    bddnode_t nb = GETNODE(b);
+    bddnode_t na = MTBDD_GETNODE(a);
+    bddnode_t nb = MTBDD_GETNODE(b);
 
     BDDVAR va = bddnode_getvariable(na);
     BDDVAR vb = bddnode_getvariable(nb);
@@ -506,7 +168,7 @@ TASK_IMPL_3(syBDD, sylvan_xor, syBDD, a, syBDD, b, BDDVAR, prev_level)
 
     int cachenow = granularity < 2 || prev_level == 0 ? 1 : prev_level / granularity != level / granularity;
     if (cachenow) {
-        syBDD result;
+        SyBDD result;
         if (cache_get3(CACHE_BDD_XOR, a, b, sylvan_false, &result)) {
             sylvan_stats_count(BDD_XOR_CACHED);
             return result;
@@ -514,8 +176,8 @@ TASK_IMPL_3(syBDD, sylvan_xor, syBDD, a, syBDD, b, BDDVAR, prev_level)
     }
 
     // Get cofactors
-    syBDD aLow = a, aHigh = a;
-    syBDD bLow = b, bHigh = b;
+    SyBDD aLow = a, aHigh = a;
+    SyBDD bLow = b, bHigh = b;
     if (level == va) {
         aLow = node_low(a, na);
         aHigh = node_high(a, na);
@@ -526,7 +188,7 @@ TASK_IMPL_3(syBDD, sylvan_xor, syBDD, a, syBDD, b, BDDVAR, prev_level)
     }
 
     // Recursive computation
-    syBDD low, high, result;
+    SyBDD low, high, result;
 
     bdd_refs_spawn(SPAWN(sylvan_xor, aHigh, bHigh, level));
     low = CALL(sylvan_xor, aLow, bLow, level);
@@ -544,7 +206,7 @@ TASK_IMPL_3(syBDD, sylvan_xor, syBDD, a, syBDD, b, BDDVAR, prev_level)
 }
 
 
-TASK_IMPL_4(syBDD, sylvan_ite, syBDD, a, syBDD, b, syBDD, c, BDDVAR, prev_level)
+TASK_IMPL_4(SyBDD, sylvan_ite, SyBDD, a, SyBDD, b, SyBDD, c, BDDVAR, prev_level)
 {
     /* Terminal cases */
     if (a == sylvan_true) return b;
@@ -581,7 +243,7 @@ TASK_IMPL_4(syBDD, sylvan_ite, syBDD, a, syBDD, b, syBDD, c, BDDVAR, prev_level)
     // ITE(~A,B,C) => ITE(A,C,B)
     if (BDD_HASMARK(a)) {
         a = BDD_STRIPMARK(a);
-        syBDD t = c;
+        SyBDD t = c;
         c = b;
         b = t;
     }
@@ -594,9 +256,9 @@ TASK_IMPL_4(syBDD, sylvan_ite, syBDD, a, syBDD, b, syBDD, c, BDDVAR, prev_level)
         mark = 1;
     }
 
-    bddnode_t na = GETNODE(a);
-    bddnode_t nb = GETNODE(b);
-    bddnode_t nc = GETNODE(c);
+    bddnode_t na = MTBDD_GETNODE(a);
+    bddnode_t nb = MTBDD_GETNODE(b);
+    bddnode_t nc = MTBDD_GETNODE(c);
 
     BDDVAR va = bddnode_getvariable(na);
     BDDVAR vb = bddnode_getvariable(nb);
@@ -607,7 +269,7 @@ TASK_IMPL_4(syBDD, sylvan_ite, syBDD, a, syBDD, b, syBDD, c, BDDVAR, prev_level)
 
     // Fast case
     if (va < level && node_low(a, na) == sylvan_false && node_high(a, na) == sylvan_true) {
-        syBDD result = sylvan_makenode(va, c, b);
+        SyBDD result = sylvan_makenode(va, c, b);
         return mark ? sylvan_not(result) : result;
     }
 
@@ -615,11 +277,12 @@ TASK_IMPL_4(syBDD, sylvan_ite, syBDD, a, syBDD, b, syBDD, c, BDDVAR, prev_level)
 
     sylvan_gc_test();
 
+    /* Count operation */
     sylvan_stats_count(BDD_ITE);
 
     int cachenow = granularity < 2 || prev_level == 0 ? 1 : prev_level / granularity != level / granularity;
     if (cachenow) {
-        syBDD result;
+        SyBDD result;
         if (cache_get3(CACHE_BDD_ITE, a, b, c, &result)) {
             sylvan_stats_count(BDD_ITE_CACHED);
             return mark ? sylvan_not(result) : result;
@@ -627,9 +290,9 @@ TASK_IMPL_4(syBDD, sylvan_ite, syBDD, a, syBDD, b, syBDD, c, BDDVAR, prev_level)
     }
 
     // Get cofactors
-    syBDD aLow = a, aHigh = a;
-    syBDD bLow = b, bHigh = b;
-    syBDD cLow = c, cHigh = c;
+    SyBDD aLow = a, aHigh = a;
+    SyBDD bLow = b, bHigh = b;
+    SyBDD cLow = c, cHigh = c;
     if (level == va) {
         aLow = node_low(a, na);
         aHigh = node_high(a, na);
@@ -644,7 +307,7 @@ TASK_IMPL_4(syBDD, sylvan_ite, syBDD, a, syBDD, b, syBDD, c, BDDVAR, prev_level)
     }
 
     // Recursive computation
-    syBDD low=sylvan_invalid, high=sylvan_invalid, result;
+    SyBDD low=sylvan_invalid, high=sylvan_invalid, result;
 
     int n=0;
 
@@ -681,16 +344,17 @@ TASK_IMPL_4(syBDD, sylvan_ite, syBDD, a, syBDD, b, syBDD, c, BDDVAR, prev_level)
 }
 
 /**
- * Calculate constrain a @ c
+ * Compute constrain f@c, also called the generalized co-factor.
+ * c is the "care function" - f@c equals f when c evaluates to True.
  */
-TASK_IMPL_3(syBDD, sylvan_constrain, syBDD, a, syBDD, b, BDDVAR, prev_level)
+TASK_IMPL_3(SyBDD, sylvan_constrain, SyBDD, f, SyBDD, c, BDDVAR, prev_level)
 {
     /* Trivial cases */
-    if (b == sylvan_true) return a;
-    if (b == sylvan_false) return sylvan_false;
-    if (sylvan_isconst(a)) return a;
-    if (a == b) return sylvan_true;
-    if (a == sylvan_not(b)) return sylvan_false;
+    if (c == sylvan_true) return f;
+    if (c == sylvan_false) return sylvan_false;
+    if (sylvan_isconst(f)) return f;
+    if (f == c) return sylvan_true;
+    if (f == sylvan_not(c)) return sylvan_false;
 
     /* Perhaps execute garbage collection */
     sylvan_gc_test();
@@ -698,88 +362,92 @@ TASK_IMPL_3(syBDD, sylvan_constrain, syBDD, a, syBDD, b, BDDVAR, prev_level)
     /* Count operation */
     sylvan_stats_count(BDD_CONSTRAIN);
 
-    // a != constant and b != constant
-    bddnode_t na = GETNODE(a);
-    bddnode_t nb = GETNODE(b);
+    bddnode_t nf = MTBDD_GETNODE(f);
+    bddnode_t nc = MTBDD_GETNODE(c);
 
-    BDDVAR va = bddnode_getvariable(na);
-    BDDVAR vb = bddnode_getvariable(nb);
-    BDDVAR level = va < vb ? va : vb;
+    BDDVAR vf = bddnode_getvariable(nf);
+    BDDVAR vc = bddnode_getvariable(nc);
+    BDDVAR level = vf < vc ? vf : vc;
 
-    // CONSULT CACHE
+    /* Make canonical */
+    int mark = 0;
+    if (BDD_HASMARK(f)) {
+        f = BDD_STRIPMARK(f);
+        mark = 1;
+    }
 
+    /* Consult cache */
     int cachenow = granularity < 2 || prev_level == 0 ? 1 : prev_level / granularity != level / granularity;
     if (cachenow) {
-        syBDD result;
-        if (cache_get3(CACHE_BDD_CONSTRAIN, a, b, 0, &result)) {
+        SyBDD result;
+        if (cache_get3(CACHE_BDD_CONSTRAIN, f, c, 0, &result)) {
             sylvan_stats_count(BDD_CONSTRAIN_CACHED);
-            return result;
+            return mark ? sylvan_not(result) : result;
         }
     }
 
-    // DETERMINE TOP BDDVAR AND COFACTORS
-
-    syBDD aLow, aHigh, bLow, bHigh;
+    SyBDD fLow, fHigh, cLow, cHigh;
 
-    if (level == va) {
-        aLow = node_low(a, na);
-        aHigh = node_high(a, na);
+    if (level == vf) {
+        fLow = node_low(f, nf);
+        fHigh = node_high(f, nf);
     } else {
-        aLow = aHigh = a;
+        fLow = fHigh = f;
     }
 
-    if (level == vb) {
-        bLow = node_low(b, nb);
-        bHigh = node_high(b, nb);
+    if (level == vc) {
+        cLow = node_low(c, nc);
+        cHigh = node_high(c, nc);
     } else {
-        bLow = bHigh = b;
-    }
-
-    syBDD result;
-
-    syBDD low=sylvan_invalid, high=sylvan_invalid;
-    if (bLow == sylvan_false) return CALL(sylvan_constrain, aHigh, bHigh, level);
-    if (bLow == sylvan_true) {
-        if (bHigh == sylvan_false) return aLow;
-        if (bHigh == sylvan_true) {
-            result = sylvan_makenode(level, aLow, bHigh);
-        } else {
-            high = CALL(sylvan_constrain, aHigh, bHigh, level);
-            result = sylvan_makenode(level, aLow, high);
-        }
+        cLow = cHigh = c;
+    }
+
+    SyBDD result;
+
+    if (cLow == sylvan_false) {
+        /* cLow is False, so result equals fHigh @ cHigh */
+        if (cHigh == sylvan_true) result = fHigh;
+        else result = CALL(sylvan_constrain, fHigh, cHigh, level);
+    } else if (cHigh == sylvan_false) {
+        /* cHigh is False, so result equals fLow @ cLow */
+        if (cLow == sylvan_true) result = fLow;
+        else result = CALL(sylvan_constrain, fLow, cLow, level);
+    } else if (cLow == sylvan_true) {
+        /* cLow is True, so low result equals fLow */
+        SyBDD high = CALL(sylvan_constrain, fHigh, cHigh, level);
+        result = sylvan_makenode(level, fLow, high);
+    } else if (cHigh == sylvan_true) {
+        /* cHigh is True, so high result equals fHigh */
+        SyBDD low = CALL(sylvan_constrain, fLow, cLow, level);
+        result = sylvan_makenode(level, low, fHigh);
     } else {
-        if (bHigh == sylvan_false) return CALL(sylvan_constrain, aLow, bLow, level);
-        if (bHigh == sylvan_true) {
-            low = CALL(sylvan_constrain, aLow, bLow, level);
-            result = sylvan_makenode(level, low, bHigh);
-        } else {
-            bdd_refs_spawn(SPAWN(sylvan_constrain, aLow, bLow, level));
-            high = CALL(sylvan_constrain, aHigh, bHigh, level);
-            bdd_refs_push(high);
-            low = bdd_refs_sync(SYNC(sylvan_constrain));
-            bdd_refs_pop(1);
-            result = sylvan_makenode(level, low, high);
-        }
+        /* cLow and cHigh are not constrants... normal parallel recursion */
+        bdd_refs_spawn(SPAWN(sylvan_constrain, fLow, cLow, level));
+        SyBDD high = CALL(sylvan_constrain, fHigh, cHigh, level);
+        bdd_refs_push(high);
+        SyBDD low = bdd_refs_sync(SYNC(sylvan_constrain));
+        bdd_refs_pop(1);
+        result = sylvan_makenode(level, low, high);
     }
 
     if (cachenow) {
-        if (cache_put3(CACHE_BDD_CONSTRAIN, a, b, 0, result)) sylvan_stats_count(BDD_CONSTRAIN_CACHEDPUT);
+        if (cache_put3(CACHE_BDD_CONSTRAIN, f, c, 0, result)) sylvan_stats_count(BDD_CONSTRAIN_CACHEDPUT);
     }
 
-    return result;
+    return mark ? sylvan_not(result) : result;
 }
 
 /**
- * Calculate restrict a @ b
+ * Compute restrict f@c, which uses a heuristic to try and minimize a SyBDD f with respect to a care function c
  */
-TASK_IMPL_3(syBDD, sylvan_restrict, syBDD, a, syBDD, b, BDDVAR, prev_level)
+TASK_IMPL_3(SyBDD, sylvan_restrict, SyBDD, f, SyBDD, c, BDDVAR, prev_level)
 {
     /* Trivial cases */
-    if (b == sylvan_true) return a;
-    if (b == sylvan_false) return sylvan_false;
-    if (sylvan_isconst(a)) return a;
-    if (a == b) return sylvan_true;
-    if (a == sylvan_not(b)) return sylvan_false;
+    if (c == sylvan_true) return f;
+    if (c == sylvan_false) return sylvan_false;
+    if (sylvan_isconst(f)) return f;
+    if (f == c) return sylvan_true;
+    if (f == sylvan_not(c)) return sylvan_false;
 
     /* Perhaps execute garbage collection */
     sylvan_gc_test();
@@ -787,62 +455,75 @@ TASK_IMPL_3(syBDD, sylvan_restrict, syBDD, a, syBDD, b, BDDVAR, prev_level)
     /* Count operation */
     sylvan_stats_count(BDD_RESTRICT);
 
-    // a != constant and b != constant
-    bddnode_t na = GETNODE(a);
-    bddnode_t nb = GETNODE(b);
+    bddnode_t nf = MTBDD_GETNODE(f);
+    bddnode_t nc = MTBDD_GETNODE(c);
 
-    BDDVAR va = bddnode_getvariable(na);
-    BDDVAR vb = bddnode_getvariable(nb);
-    BDDVAR level = va < vb ? va : vb;
+    BDDVAR vf = bddnode_getvariable(nf);
+    BDDVAR vc = bddnode_getvariable(nc);
+    BDDVAR level = vf < vc ? vf : vc;
+
+    /* Make canonical */
+    int mark = 0;
+    if (BDD_HASMARK(f)) {
+        f = BDD_STRIPMARK(f);
+        mark = 1;
+    }
 
     /* Consult cache */
     int cachenow = granularity < 2 || prev_level == 0 ? 1 : prev_level / granularity != level / granularity;
     if (cachenow) {
-        syBDD result;
-        if (cache_get3(CACHE_BDD_RESTRICT, a, b, 0, &result)) {
+        SyBDD result;
+        if (cache_get3(CACHE_BDD_RESTRICT, f, c, 0, &result)) {
             sylvan_stats_count(BDD_RESTRICT_CACHED);
-            return result;
+            return mark ? sylvan_not(result) : result;
         }
     }
 
-    syBDD result;
+    SyBDD result;
 
-    if (vb < va) {
-        syBDD c = CALL(sylvan_ite, node_low(b,nb), sylvan_true, node_high(b,nb), 0);
-        bdd_refs_push(c);
-        result = CALL(sylvan_restrict, a, c, level);
+    if (vc < vf) {
+        /* f is independent of c, so result is f @ (cLow \/ cHigh) */
+        SyBDD new_c = sylvan_or(node_low(c, nc), node_high(c, nc));
+        bdd_refs_push(new_c);
+        result = CALL(sylvan_restrict, f, new_c, level);
         bdd_refs_pop(1);
     } else {
-        syBDD aLow=node_low(a,na),aHigh=node_high(a,na),bLow=b,bHigh=b;
-        if (va == vb) {
-            bLow = node_low(b,nb);
-            bHigh = node_high(b,nb);
+        SyBDD fLow = node_low(f,nf), fHigh = node_high(f,nf);
+        SyBDD cLow, cHigh;
+        if (vf == vc) {
+            cLow = node_low(c, nc);
+            cHigh = node_high(c, nc);
+        } else {
+            cLow = cHigh = c;
         }
-        if (bLow == sylvan_false) {
-            result = CALL(sylvan_restrict, aHigh, bHigh, level);
-        } else if (bHigh == sylvan_false) {
-            result = CALL(sylvan_restrict, aLow, bLow, level);
+        if (cLow == sylvan_false) {
+            /* sibling-substitution */
+            result = CALL(sylvan_restrict, fHigh, cHigh, level);
+        } else if (cHigh == sylvan_false) {
+            /* sibling-substitution */
+            result = CALL(sylvan_restrict, fLow, cLow, level);
         } else {
-            bdd_refs_spawn(SPAWN(sylvan_restrict, aLow, bLow, level));
-            syBDD high = CALL(sylvan_restrict, aHigh, bHigh, level);
+            /* parallel recursion */
+            bdd_refs_spawn(SPAWN(sylvan_restrict, fLow, cLow, level));
+            SyBDD high = CALL(sylvan_restrict, fHigh, cHigh, level);
             bdd_refs_push(high);
-            syBDD low = bdd_refs_sync(SYNC(sylvan_restrict));
+            SyBDD low = bdd_refs_sync(SYNC(sylvan_restrict));
             bdd_refs_pop(1);
             result = sylvan_makenode(level, low, high);
         }
     }
 
     if (cachenow) {
-        if (cache_put3(CACHE_BDD_RESTRICT, a, b, 0, result)) sylvan_stats_count(BDD_RESTRICT_CACHEDPUT);
+        if (cache_put3(CACHE_BDD_RESTRICT, f, c, 0, result)) sylvan_stats_count(BDD_RESTRICT_CACHEDPUT);
     }
 
-    return result;
+    return mark ? sylvan_not(result) : result;
 }
 
 /**
  * Calculates \exists variables . a
  */
-TASK_IMPL_3(syBDD, sylvan_exists, syBDD, a, syBDD, variables, BDDVAR, prev_level)
+TASK_IMPL_3(SyBDD, sylvan_exists, SyBDD, a, SyBDD, variables, BDDVAR, prev_level)
 {
     /* Terminal cases */
     if (a == sylvan_true) return sylvan_true;
@@ -850,25 +531,26 @@ TASK_IMPL_3(syBDD, sylvan_exists, syBDD, a, syBDD, variables, BDDVAR, prev_level
     if (sylvan_set_isempty(variables)) return a;
 
     // a != constant
-    bddnode_t na = GETNODE(a);
+    bddnode_t na = MTBDD_GETNODE(a);
     BDDVAR level = bddnode_getvariable(na);
 
-    bddnode_t nv = GETNODE(variables);
+    bddnode_t nv = MTBDD_GETNODE(variables);
     BDDVAR vv = bddnode_getvariable(nv);
     while (vv < level) {
         variables = node_high(variables, nv);
         if (sylvan_set_isempty(variables)) return a;
-        nv = GETNODE(variables);
+        nv = MTBDD_GETNODE(variables);
         vv = bddnode_getvariable(nv);
     }
 
     sylvan_gc_test();
 
+    /* Count operation */
     sylvan_stats_count(BDD_EXISTS);
 
     int cachenow = granularity < 2 || prev_level == 0 ? 1 : prev_level / granularity != level / granularity;
     if (cachenow) {
-        syBDD result;
+        SyBDD result;
         if (cache_get3(CACHE_BDD_EXISTS, a, variables, 0, &result)) {
             sylvan_stats_count(BDD_EXISTS_CACHED);
             return result;
@@ -876,23 +558,23 @@ TASK_IMPL_3(syBDD, sylvan_exists, syBDD, a, syBDD, variables, BDDVAR, prev_level
     }
 
     // Get cofactors
-    syBDD aLow = node_low(a, na);
-    syBDD aHigh = node_high(a, na);
+    SyBDD aLow = node_low(a, na);
+    SyBDD aHigh = node_high(a, na);
 
-    syBDD result;
+    SyBDD result;
 
     if (vv == level) {
         // level is in variable set, perform abstraction
         if (aLow == sylvan_true || aHigh == sylvan_true || aLow == sylvan_not(aHigh)) {
             result = sylvan_true;
         } else {
-            syBDD _v = sylvan_set_next(variables);
-            syBDD low = CALL(sylvan_exists, aLow, _v, level);
+            SyBDD _v = sylvan_set_next(variables);
+            SyBDD low = CALL(sylvan_exists, aLow, _v, level);
             if (low == sylvan_true) {
                 result = sylvan_true;
             } else {
                 bdd_refs_push(low);
-                syBDD high = CALL(sylvan_exists, aHigh, _v, level);
+                SyBDD high = CALL(sylvan_exists, aHigh, _v, level);
                 if (high == sylvan_true) {
                     result = sylvan_true;
                     bdd_refs_pop(1);
@@ -908,7 +590,7 @@ TASK_IMPL_3(syBDD, sylvan_exists, syBDD, a, syBDD, variables, BDDVAR, prev_level
         }
     } else {
         // level is not in variable set
-        syBDD low, high;
+        SyBDD low, high;
         bdd_refs_spawn(SPAWN(sylvan_exists, aHigh, variables, level));
         low = CALL(sylvan_exists, aLow, variables, level);
         bdd_refs_push(low);
@@ -924,10 +606,100 @@ TASK_IMPL_3(syBDD, sylvan_exists, syBDD, a, syBDD, variables, BDDVAR, prev_level
     return result;
 }
 
+
+/**
+ * Calculate projection of <a> unto <v>
+ * (Expects Boolean <a>)
+ */
+TASK_IMPL_2(MTBDD, sylvan_project, MTBDD, a, MTBDD, v)
+{
+    /**
+     * Terminal cases
+     */
+    if (a == sylvan_false) return sylvan_false;
+    if (a == sylvan_true) return sylvan_true;
+    if (sylvan_set_isempty(v)) return sylvan_true;
+
+    /**
+     * Obtain variables
+     */
+    const mtbddnode_t a_node = MTBDD_GETNODE(a);
+    const uint32_t a_var = mtbddnode_getvariable(a_node);
+
+    /**
+     * Skip <vars>
+     */
+    mtbddnode_t v_node = MTBDD_GETNODE(v);
+    uint32_t v_var = mtbddnode_getvariable(v_node);
+    MTBDD v_next = mtbddnode_followhigh(v, v_node);
+
+    while (v_var < a_var) {
+        if (sylvan_set_isempty(v_next)) return sylvan_true;
+        v = v_next;
+        v_node = MTBDD_GETNODE(v);
+        v_var = mtbddnode_getvariable(v_node);
+        v_next = mtbddnode_followhigh(v, v_node);
+    }
+
+    /**
+     * Maybe perform garbage collection
+     */
+    sylvan_gc_test();
+
+    /**
+     * Count operation
+     */
+    sylvan_stats_count(BDD_PROJECT);
+
+    /**
+     * Check the cache
+     */
+    MTBDD result;
+    if (cache_get3(CACHE_BDD_PROJECT, a, 0, v, &result)) {
+        sylvan_stats_count(BDD_PROJECT_CACHED);
+        return result;
+    }
+
+    /**
+     * Get cofactors
+     */
+    const MTBDD a0 = mtbddnode_followlow(a, a_node);
+    const MTBDD a1 = mtbddnode_followhigh(a, a_node);
+
+    /**
+     * Compute recursive result
+     */
+    if (v_var == a_var) {
+        // variable in projection variables
+        mtbdd_refs_spawn(SPAWN(sylvan_project, a0, v_next));
+        const MTBDD high = mtbdd_refs_push(sylvan_project(a1, v_next));
+        const MTBDD low = mtbdd_refs_sync(SYNC(sylvan_project));
+        mtbdd_refs_pop(1);
+        result = mtbdd_makenode(a_var, low, high);
+    } else {
+        // variable not in projection variables
+        mtbdd_refs_spawn(SPAWN(sylvan_project, a0, v));
+        const MTBDD high = mtbdd_refs_push(sylvan_project(a1, v));
+        const MTBDD low = mtbdd_refs_push(mtbdd_refs_sync(SYNC(sylvan_project)));
+        result = sylvan_or(low, high);
+        mtbdd_refs_pop(2);
+    }
+
+    /**
+     * Put in cache
+     */
+    if (cache_put3(CACHE_BDD_PROJECT, a, 0, v, result)) {
+        sylvan_stats_count(BDD_PROJECT_CACHEDPUT);
+    }
+
+    return result;
+}
+
+
 /**
  * Calculate exists(a AND b, v)
  */
-TASK_IMPL_4(syBDD, sylvan_and_exists, syBDD, a, syBDD, b, BDDSET, v, BDDVAR, prev_level)
+TASK_IMPL_4(SyBDD, sylvan_and_exists, SyBDD, a, SyBDD, b, BDDSET, v, BDDVAR, prev_level)
 {
     /* Terminal cases */
     if (a == sylvan_false) return sylvan_false;
@@ -945,7 +717,7 @@ TASK_IMPL_4(syBDD, sylvan_and_exists, syBDD, a, syBDD, b, BDDSET, v, BDDVAR, pre
 
     /* Improve for caching */
     if (BDD_STRIPMARK(a) > BDD_STRIPMARK(b)) {
-        syBDD t = b;
+        SyBDD t = b;
         b = a;
         a = t;
     }
@@ -953,12 +725,13 @@ TASK_IMPL_4(syBDD, sylvan_and_exists, syBDD, a, syBDD, b, BDDSET, v, BDDVAR, pre
     /* Maybe perform garbage collection */
     sylvan_gc_test();
 
+    /* Count operation */
     sylvan_stats_count(BDD_AND_EXISTS);
 
     // a != constant
-    bddnode_t na = GETNODE(a);
-    bddnode_t nb = GETNODE(b);
-    bddnode_t nv = GETNODE(v);
+    bddnode_t na = MTBDD_GETNODE(a);
+    bddnode_t nb = MTBDD_GETNODE(b);
+    bddnode_t nv = MTBDD_GETNODE(v);
 
     BDDVAR va = bddnode_getvariable(na);
     BDDVAR vb = bddnode_getvariable(nb);
@@ -969,11 +742,11 @@ TASK_IMPL_4(syBDD, sylvan_and_exists, syBDD, a, syBDD, b, BDDSET, v, BDDVAR, pre
     while (vv < level) {
         v = node_high(v, nv); // get next variable in conjunction
         if (sylvan_set_isempty(v)) return sylvan_and(a, b);
-        nv = GETNODE(v);
+        nv = MTBDD_GETNODE(v);
         vv = bddnode_getvariable(nv);
     }
 
-    syBDD result;
+    SyBDD result;
 
     int cachenow = granularity < 2 || prev_level == 0 ? 1 : prev_level / granularity != level / granularity;
     if (cachenow) {
@@ -984,7 +757,7 @@ TASK_IMPL_4(syBDD, sylvan_and_exists, syBDD, a, syBDD, b, BDDSET, v, BDDVAR, pre
     }
 
     // Get cofactors
-    syBDD aLow, aHigh, bLow, bHigh;
+    SyBDD aLow, aHigh, bLow, bHigh;
     if (level == va) {
         aLow = node_low(a, na);
         aHigh = node_high(a, na);
@@ -1002,13 +775,13 @@ TASK_IMPL_4(syBDD, sylvan_and_exists, syBDD, a, syBDD, b, BDDSET, v, BDDVAR, pre
 
     if (level == vv) {
         // level is in variable set, perform abstraction
-        syBDD _v = node_high(v, nv);
-        syBDD low = CALL(sylvan_and_exists, aLow, bLow, _v, level);
+        SyBDD _v = node_high(v, nv);
+        SyBDD low = CALL(sylvan_and_exists, aLow, bLow, _v, level);
         if (low == sylvan_true || low == aHigh || low == bHigh) {
             result = low;
         } else {
             bdd_refs_push(low);
-            syBDD high;
+            SyBDD high;
             if (low == sylvan_not(aHigh)) {
                 high = CALL(sylvan_exists, bHigh, _v, 0);
             } else if (low == sylvan_not(bHigh)) {
@@ -1032,24 +805,134 @@ TASK_IMPL_4(syBDD, sylvan_and_exists, syBDD, a, syBDD, b, BDDSET, v, BDDVAR, pre
             }
         }
     } else {
-        // level is not in variable set
-        bdd_refs_spawn(SPAWN(sylvan_and_exists, aHigh, bHigh, v, level));
-        syBDD low = CALL(sylvan_and_exists, aLow, bLow, v, level);
-        bdd_refs_push(low);
-        syBDD high = bdd_refs_sync(SYNC(sylvan_and_exists));
-        bdd_refs_pop(1);
-        result = sylvan_makenode(level, low, high);
+        // level is not in variable set
+        bdd_refs_spawn(SPAWN(sylvan_and_exists, aHigh, bHigh, v, level));
+        SyBDD low = CALL(sylvan_and_exists, aLow, bLow, v, level);
+        bdd_refs_push(low);
+        SyBDD high = bdd_refs_sync(SYNC(sylvan_and_exists));
+        bdd_refs_pop(1);
+        result = sylvan_makenode(level, low, high);
+    }
+
+    if (cachenow) {
+        if (cache_put3(CACHE_BDD_AND_EXISTS, a, b, v, result)) sylvan_stats_count(BDD_AND_EXISTS_CACHEDPUT);
+    }
+
+    return result;
+}
+
+
+/**
+ * Calculate projection of (<a> AND <b>) unto <v>
+ * (Expects Boolean <a> and <b>)
+ */
+TASK_IMPL_3(MTBDD, sylvan_and_project, MTBDD, a, MTBDD, b, MTBDD, v)
+{
+    /**
+     * Terminal cases
+     */
+    if (a == sylvan_false) return sylvan_false;
+    if (b == sylvan_false) return sylvan_false;
+    if (a == sylvan_not(b)) return sylvan_false;
+    if (a == sylvan_true && b == sylvan_true) return sylvan_true;
+    if (sylvan_set_isempty(v)) return sylvan_true;
+
+    /**
+     * Cases that reduce to sylvan_project
+     */
+    if (a == sylvan_true || b == sylvan_true || a == b) return sylvan_project(b, v);
+
+    /**
+     * Normalization (only for caching)
+     */
+    if (BDD_STRIPMARK(a) > BDD_STRIPMARK(b)) {
+        SyBDD t = b;
+        b = a;
+        a = t;
+    }
+
+    /**
+     * Maybe perform garbage collection
+     */
+    sylvan_gc_test();
+
+    /**
+     * Count operation
+     */
+    sylvan_stats_count(BDD_AND_PROJECT);
+
+    /**
+     * Obtain variables
+     */
+    const mtbddnode_t a_node = MTBDD_GETNODE(a);
+    const mtbddnode_t b_node = MTBDD_GETNODE(b);
+    const uint32_t a_var = mtbddnode_getvariable(a_node);
+    const uint32_t b_var = mtbddnode_getvariable(b_node);
+    const uint32_t minvar = a_var < b_var ? a_var : b_var;
+
+    /**
+     * Skip <vars>
+     */
+    mtbddnode_t v_node = MTBDD_GETNODE(v);
+    uint32_t v_var = mtbddnode_getvariable(v_node);
+    MTBDD v_next = mtbddnode_followhigh(v, v_node);
+
+    while (v_var < minvar) {
+        if (sylvan_set_isempty(v_next)) return sylvan_true;
+        v = v_next;
+        v_node = MTBDD_GETNODE(v);
+        v_var = mtbddnode_getvariable(v_node);
+        v_next = mtbddnode_followhigh(v, v_node);
+    }
+
+    /**
+     * Check the cache
+     */
+    MTBDD result;
+    if (cache_get3(CACHE_BDD_AND_PROJECT, a, b, v, &result)) {
+        sylvan_stats_count(BDD_AND_PROJECT_CACHED);
+        return result;
+    }
+
+    /**
+     * Get cofactors
+     */
+    const MTBDD a0 = a_var == minvar ? mtbddnode_followlow(a, a_node) : a;
+    const MTBDD a1 = a_var == minvar ? mtbddnode_followhigh(a, a_node) : a;
+    const MTBDD b0 = b_var == minvar ? mtbddnode_followlow(b, b_node) : b;
+    const MTBDD b1 = b_var == minvar ? mtbddnode_followhigh(b, b_node) : b;
+
+    /**
+     * Compute recursive result
+     */
+    if (v_var == minvar) {
+        // variable in projection variables
+        mtbdd_refs_spawn(SPAWN(sylvan_and_project, a0, b0, v_next));
+        const MTBDD high = mtbdd_refs_push(sylvan_and_project(a1, b1, v_next));
+        const MTBDD low = mtbdd_refs_sync(SYNC(sylvan_and_project));
+        mtbdd_refs_pop(1);
+        result = mtbdd_makenode(minvar, low, high);
+    } else {
+        // variable not in projection variables
+        mtbdd_refs_spawn(SPAWN(sylvan_and_project, a0, b0, v));
+        const MTBDD high = mtbdd_refs_push(sylvan_and_project(a1, b1, v));
+        const MTBDD low = mtbdd_refs_push(mtbdd_refs_sync(SYNC(sylvan_and_project)));
+        result = sylvan_or(low, high);
+        mtbdd_refs_pop(2);
     }
 
-    if (cachenow) {
-        if (cache_put3(CACHE_BDD_AND_EXISTS, a, b, v, result)) sylvan_stats_count(BDD_AND_EXISTS_CACHEDPUT);
+    /**
+     * Put in cache
+     */
+    if (cache_put3(CACHE_BDD_AND_PROJECT, a, b, v, result)) {
+        sylvan_stats_count(BDD_AND_PROJECT_CACHEDPUT);
     }
 
     return result;
 }
 
 
-TASK_IMPL_4(syBDD, sylvan_relnext, syBDD, a, syBDD, b, BDDSET, vars, BDDVAR, prev_level)
+TASK_IMPL_4(SyBDD, sylvan_relnext, SyBDD, a, SyBDD, b, BDDSET, vars, BDDVAR, prev_level)
 {
     /* Compute R(s) = \exists x: A(x) \and B(x,s) with support(result) = s, support(A) = s, support(B) = s+t
      * if vars == sylvan_false, then every level is in s or t
@@ -1069,8 +952,8 @@ TASK_IMPL_4(syBDD, sylvan_relnext, syBDD, a, syBDD, b, BDDSET, vars, BDDVAR, pre
     sylvan_stats_count(BDD_RELNEXT);
 
     /* Determine top level */
-    bddnode_t na = sylvan_isconst(a) ? 0 : GETNODE(a);
-    bddnode_t nb = sylvan_isconst(b) ? 0 : GETNODE(b);
+    bddnode_t na = sylvan_isconst(a) ? 0 : MTBDD_GETNODE(a);
+    bddnode_t nb = sylvan_isconst(b) ? 0 : MTBDD_GETNODE(b);
 
     BDDVAR va = na ? bddnode_getvariable(na) : 0xffffffff;
     BDDVAR vb = nb ? bddnode_getvariable(nb) : 0xffffffff;
@@ -1082,7 +965,7 @@ TASK_IMPL_4(syBDD, sylvan_relnext, syBDD, a, syBDD, b, BDDSET, vars, BDDVAR, pre
     if (vars == sylvan_false) {
         is_s_or_t = 1;
     } else {
-        nv = GETNODE(vars);
+        nv = MTBDD_GETNODE(vars);
         for (;;) {
             /* check if level is s/t */
             BDDVAR vv = bddnode_getvariable(nv);
@@ -1094,28 +977,28 @@ TASK_IMPL_4(syBDD, sylvan_relnext, syBDD, a, syBDD, b, BDDSET, vars, BDDVAR, pre
             if (level < vv) break;
             vars = node_high(vars, nv); // get next in vars
             if (sylvan_set_isempty(vars)) return a;
-            nv = GETNODE(vars);
+            nv = MTBDD_GETNODE(vars);
         }
     }
 
     /* Consult cache */
     int cachenow = granularity < 2 || prev_level == 0 ? 1 : prev_level / granularity != level / granularity;
     if (cachenow) {
-        syBDD result;
+        SyBDD result;
         if (cache_get3(CACHE_BDD_RELNEXT, a, b, vars, &result)) {
             sylvan_stats_count(BDD_RELNEXT_CACHED);
             return result;
         }
     }
 
-    syBDD result;
+    SyBDD result;
 
     if (is_s_or_t) {
         /* Get s and t */
         BDDVAR s = level & (~1);
         BDDVAR t = s+1;
 
-        syBDD a0, a1, b0, b1;
+        SyBDD a0, a1, b0, b1;
         if (na && va == s) {
             a0 = node_low(a, na);
             a1 = node_high(a, na);
@@ -1129,9 +1012,9 @@ TASK_IMPL_4(syBDD, sylvan_relnext, syBDD, a, syBDD, b, BDDSET, vars, BDDVAR, pre
             b0 = b1 = b;
         }
 
-        syBDD b00, b01, b10, b11;
+        SyBDD b00, b01, b10, b11;
         if (!sylvan_isconst(b0)) {
-            bddnode_t nb0 = GETNODE(b0);
+            bddnode_t nb0 = MTBDD_GETNODE(b0);
             if (bddnode_getvariable(nb0) == t) {
                 b00 = node_low(b0, nb0);
                 b01 = node_high(b0, nb0);
@@ -1142,7 +1025,7 @@ TASK_IMPL_4(syBDD, sylvan_relnext, syBDD, a, syBDD, b, BDDSET, vars, BDDVAR, pre
             b00 = b01 = b0;
         }
         if (!sylvan_isconst(b1)) {
-            bddnode_t nb1 = GETNODE(b1);
+            bddnode_t nb1 = MTBDD_GETNODE(b1);
             if (bddnode_getvariable(nb1) == t) {
                 b10 = node_low(b1, nb1);
                 b11 = node_high(b1, nb1);
@@ -1153,17 +1036,17 @@ TASK_IMPL_4(syBDD, sylvan_relnext, syBDD, a, syBDD, b, BDDSET, vars, BDDVAR, pre
             b10 = b11 = b1;
         }
 
-        syBDD _vars = vars == sylvan_false ? sylvan_false : node_high(vars, nv);
+        SyBDD _vars = vars == sylvan_false ? sylvan_false : node_high(vars, nv);
 
         bdd_refs_spawn(SPAWN(sylvan_relnext, a0, b00, _vars, level));
         bdd_refs_spawn(SPAWN(sylvan_relnext, a1, b10, _vars, level));
         bdd_refs_spawn(SPAWN(sylvan_relnext, a0, b01, _vars, level));
         bdd_refs_spawn(SPAWN(sylvan_relnext, a1, b11, _vars, level));
 
-        syBDD f = bdd_refs_sync(SYNC(sylvan_relnext)); bdd_refs_push(f);
-        syBDD e = bdd_refs_sync(SYNC(sylvan_relnext)); bdd_refs_push(e);
-        syBDD d = bdd_refs_sync(SYNC(sylvan_relnext)); bdd_refs_push(d);
-        syBDD c = bdd_refs_sync(SYNC(sylvan_relnext)); bdd_refs_push(c);
+        SyBDD f = bdd_refs_sync(SYNC(sylvan_relnext)); bdd_refs_push(f);
+        SyBDD e = bdd_refs_sync(SYNC(sylvan_relnext)); bdd_refs_push(e);
+        SyBDD d = bdd_refs_sync(SYNC(sylvan_relnext)); bdd_refs_push(d);
+        SyBDD c = bdd_refs_sync(SYNC(sylvan_relnext)); bdd_refs_push(c);
 
         bdd_refs_spawn(SPAWN(sylvan_ite, c, sylvan_true, d, 0)); /* a0 b00  \or  a1 b01 */
         bdd_refs_spawn(SPAWN(sylvan_ite, e, sylvan_true, f, 0)); /* a0 b01  \or  a1 b11 */
@@ -1175,7 +1058,7 @@ TASK_IMPL_4(syBDD, sylvan_relnext, syBDD, a, syBDD, b, BDDSET, vars, BDDVAR, pre
         result = sylvan_makenode(s, c, d);
     } else {
         /* Variable not in vars! Take a, quantify b */
-        syBDD a0, a1, b0, b1;
+        SyBDD a0, a1, b0, b1;
         if (na && va == level) {
             a0 = node_low(a, na);
             a1 = node_high(a, na);
@@ -1195,9 +1078,9 @@ TASK_IMPL_4(syBDD, sylvan_relnext, syBDD, a, syBDD, b, BDDSET, vars, BDDVAR, pre
                 bdd_refs_spawn(SPAWN(sylvan_relnext, a0, b0, vars, level));
                 bdd_refs_spawn(SPAWN(sylvan_relnext, a1, b1, vars, level));
 
-                syBDD r1 = bdd_refs_sync(SYNC(sylvan_relnext));
+                SyBDD r1 = bdd_refs_sync(SYNC(sylvan_relnext));
                 bdd_refs_push(r1);
-                syBDD r0 = bdd_refs_sync(SYNC(sylvan_relnext));
+                SyBDD r0 = bdd_refs_sync(SYNC(sylvan_relnext));
                 bdd_refs_push(r0);
                 result = sylvan_or(r0, r1);
                 bdd_refs_pop(2);
@@ -1208,21 +1091,21 @@ TASK_IMPL_4(syBDD, sylvan_relnext, syBDD, a, syBDD, b, BDDSET, vars, BDDVAR, pre
                 bdd_refs_spawn(SPAWN(sylvan_relnext, a1, b0, vars, level));
                 bdd_refs_spawn(SPAWN(sylvan_relnext, a1, b1, vars, level));
 
-                syBDD r11 = bdd_refs_sync(SYNC(sylvan_relnext));
+                SyBDD r11 = bdd_refs_sync(SYNC(sylvan_relnext));
                 bdd_refs_push(r11);
-                syBDD r10 = bdd_refs_sync(SYNC(sylvan_relnext));
+                SyBDD r10 = bdd_refs_sync(SYNC(sylvan_relnext));
                 bdd_refs_push(r10);
-                syBDD r01 = bdd_refs_sync(SYNC(sylvan_relnext));
+                SyBDD r01 = bdd_refs_sync(SYNC(sylvan_relnext));
                 bdd_refs_push(r01);
-                syBDD r00 = bdd_refs_sync(SYNC(sylvan_relnext));
+                SyBDD r00 = bdd_refs_sync(SYNC(sylvan_relnext));
                 bdd_refs_push(r00);
 
                 bdd_refs_spawn(SPAWN(sylvan_ite, r00, sylvan_true, r01, 0));
                 bdd_refs_spawn(SPAWN(sylvan_ite, r10, sylvan_true, r11, 0));
 
-                syBDD r1 = bdd_refs_sync(SYNC(sylvan_ite));
+                SyBDD r1 = bdd_refs_sync(SYNC(sylvan_ite));
                 bdd_refs_push(r1);
-                syBDD r0 = bdd_refs_sync(SYNC(sylvan_ite));
+                SyBDD r0 = bdd_refs_sync(SYNC(sylvan_ite));
                 bdd_refs_pop(5);
 
                 result = sylvan_makenode(level, r0, r1);
@@ -1232,9 +1115,9 @@ TASK_IMPL_4(syBDD, sylvan_relnext, syBDD, a, syBDD, b, BDDSET, vars, BDDVAR, pre
             bdd_refs_spawn(SPAWN(sylvan_relnext, a0, b0, vars, level));
             bdd_refs_spawn(SPAWN(sylvan_relnext, a1, b1, vars, level));
 
-            syBDD r1 = bdd_refs_sync(SYNC(sylvan_relnext));
+            SyBDD r1 = bdd_refs_sync(SYNC(sylvan_relnext));
             bdd_refs_push(r1);
-            syBDD r0 = bdd_refs_sync(SYNC(sylvan_relnext));
+            SyBDD r0 = bdd_refs_sync(SYNC(sylvan_relnext));
             bdd_refs_pop(1);
             result = sylvan_makenode(level, r0, r1);
         }
@@ -1247,7 +1130,7 @@ TASK_IMPL_4(syBDD, sylvan_relnext, syBDD, a, syBDD, b, BDDSET, vars, BDDVAR, pre
     return result;
 }
 
-TASK_IMPL_4(syBDD, sylvan_relprev, syBDD, a, syBDD, b, BDDSET, vars, BDDVAR, prev_level)
+TASK_IMPL_4(SyBDD, sylvan_relprev, SyBDD, a, SyBDD, b, BDDSET, vars, BDDVAR, prev_level)
 {
     /* Compute \exists x: A(s,x) \and B(x,t)
      * if vars == sylvan_false, then every level is in s or t
@@ -1267,8 +1150,8 @@ TASK_IMPL_4(syBDD, sylvan_relprev, syBDD, a, syBDD, b, BDDSET, vars, BDDVAR, pre
     sylvan_stats_count(BDD_RELPREV);
 
     /* Determine top level */
-    bddnode_t na = sylvan_isconst(a) ? 0 : GETNODE(a);
-    bddnode_t nb = sylvan_isconst(b) ? 0 : GETNODE(b);
+    bddnode_t na = sylvan_isconst(a) ? 0 : MTBDD_GETNODE(a);
+    bddnode_t nb = sylvan_isconst(b) ? 0 : MTBDD_GETNODE(b);
 
     BDDVAR va = na ? bddnode_getvariable(na) : 0xffffffff;
     BDDVAR vb = nb ? bddnode_getvariable(nb) : 0xffffffff;
@@ -1280,7 +1163,7 @@ TASK_IMPL_4(syBDD, sylvan_relprev, syBDD, a, syBDD, b, BDDSET, vars, BDDVAR, pre
     if (vars == sylvan_false) {
         is_s_or_t = 1;
     } else {
-        nv = GETNODE(vars);
+        nv = MTBDD_GETNODE(vars);
         for (;;) {
             /* check if level is s/t */
             BDDVAR vv = bddnode_getvariable(nv);
@@ -1292,28 +1175,28 @@ TASK_IMPL_4(syBDD, sylvan_relprev, syBDD, a, syBDD, b, BDDSET, vars, BDDVAR, pre
             if (level < vv) break;
             vars = node_high(vars, nv); // get next in vars
             if (sylvan_set_isempty(vars)) return b;
-            nv = GETNODE(vars);
+            nv = MTBDD_GETNODE(vars);
         }
     }
 
     /* Consult cache */
     int cachenow = granularity < 2 || prev_level == 0 ? 1 : prev_level / granularity != level / granularity;
     if (cachenow) {
-        syBDD result;
+        SyBDD result;
         if (cache_get3(CACHE_BDD_RELPREV, a, b, vars, &result)) {
             sylvan_stats_count(BDD_RELPREV_CACHED);
             return result;
         }
     }
 
-    syBDD result;
+    SyBDD result;
 
     if (is_s_or_t) {
         /* Get s and t */
         BDDVAR s = level & (~1);
         BDDVAR t = s+1;
 
-        syBDD a0, a1, b0, b1;
+        SyBDD a0, a1, b0, b1;
         if (na && va == s) {
             a0 = node_low(a, na);
             a1 = node_high(a, na);
@@ -1327,9 +1210,9 @@ TASK_IMPL_4(syBDD, sylvan_relprev, syBDD, a, syBDD, b, BDDSET, vars, BDDVAR, pre
             b0 = b1 = b;
         }
 
-        syBDD a00, a01, a10, a11;
+        SyBDD a00, a01, a10, a11;
         if (!sylvan_isconst(a0)) {
-            bddnode_t na0 = GETNODE(a0);
+            bddnode_t na0 = MTBDD_GETNODE(a0);
             if (bddnode_getvariable(na0) == t) {
                 a00 = node_low(a0, na0);
                 a01 = node_high(a0, na0);
@@ -1340,7 +1223,7 @@ TASK_IMPL_4(syBDD, sylvan_relprev, syBDD, a, syBDD, b, BDDSET, vars, BDDVAR, pre
             a00 = a01 = a0;
         }
         if (!sylvan_isconst(a1)) {
-            bddnode_t na1 = GETNODE(a1);
+            bddnode_t na1 = MTBDD_GETNODE(a1);
             if (bddnode_getvariable(na1) == t) {
                 a10 = node_low(a1, na1);
                 a11 = node_high(a1, na1);
@@ -1351,9 +1234,9 @@ TASK_IMPL_4(syBDD, sylvan_relprev, syBDD, a, syBDD, b, BDDSET, vars, BDDVAR, pre
             a10 = a11 = a1;
         }
 
-        syBDD b00, b01, b10, b11;
+        SyBDD b00, b01, b10, b11;
         if (!sylvan_isconst(b0)) {
-            bddnode_t nb0 = GETNODE(b0);
+            bddnode_t nb0 = MTBDD_GETNODE(b0);
             if (bddnode_getvariable(nb0) == t) {
                 b00 = node_low(b0, nb0);
                 b01 = node_high(b0, nb0);
@@ -1364,7 +1247,7 @@ TASK_IMPL_4(syBDD, sylvan_relprev, syBDD, a, syBDD, b, BDDSET, vars, BDDVAR, pre
             b00 = b01 = b0;
         }
         if (!sylvan_isconst(b1)) {
-            bddnode_t nb1 = GETNODE(b1);
+            bddnode_t nb1 = MTBDD_GETNODE(b1);
             if (bddnode_getvariable(nb1) == t) {
                 b10 = node_low(b1, nb1);
                 b11 = node_high(b1, nb1);
@@ -1375,10 +1258,10 @@ TASK_IMPL_4(syBDD, sylvan_relprev, syBDD, a, syBDD, b, BDDSET, vars, BDDVAR, pre
             b10 = b11 = b1;
         }
 
-        syBDD _vars;
+        SyBDD _vars;
         if (vars != sylvan_false) {
             _vars = node_high(vars, nv);
-            if (sylvan_set_var(_vars) == t) _vars = sylvan_set_next(_vars);
+            if (sylvan_set_first(_vars) == t) _vars = sylvan_set_next(_vars);
         } else {
             _vars = sylvan_false;
         }
@@ -1403,19 +1286,19 @@ TASK_IMPL_4(syBDD, sylvan_relprev, syBDD, a, syBDD, b, BDDSET, vars, BDDVAR, pre
             bdd_refs_spawn(SPAWN(sylvan_relprev, a11, b11, _vars, level));
         }
 
-        syBDD r00, r01, r10, r11;
+        SyBDD r00, r01, r10, r11;
 
         if (b10 == b11) {
             r11 = bdd_refs_push(bdd_refs_sync(SYNC(sylvan_relprev)));
             r01 = bdd_refs_push(bdd_refs_sync(SYNC(sylvan_relprev)));
         } else {
-            syBDD r111 = bdd_refs_push(bdd_refs_sync(SYNC(sylvan_relprev)));
-            syBDD r110 = bdd_refs_push(bdd_refs_sync(SYNC(sylvan_relprev)));
+            SyBDD r111 = bdd_refs_push(bdd_refs_sync(SYNC(sylvan_relprev)));
+            SyBDD r110 = bdd_refs_push(bdd_refs_sync(SYNC(sylvan_relprev)));
             r11 = sylvan_makenode(t, r110, r111);
             bdd_refs_pop(2);
             bdd_refs_push(r11);
-            syBDD r011 = bdd_refs_push(bdd_refs_sync(SYNC(sylvan_relprev)));
-            syBDD r010 = bdd_refs_push(bdd_refs_sync(SYNC(sylvan_relprev)));
+            SyBDD r011 = bdd_refs_push(bdd_refs_sync(SYNC(sylvan_relprev)));
+            SyBDD r010 = bdd_refs_push(bdd_refs_sync(SYNC(sylvan_relprev)));
             r01 = sylvan_makenode(t, r010, r011);
             bdd_refs_pop(2);
             bdd_refs_push(r01);
@@ -1425,13 +1308,13 @@ TASK_IMPL_4(syBDD, sylvan_relprev, syBDD, a, syBDD, b, BDDSET, vars, BDDVAR, pre
             r10 = bdd_refs_push(bdd_refs_sync(SYNC(sylvan_relprev)));
             r00 = bdd_refs_push(bdd_refs_sync(SYNC(sylvan_relprev)));
         } else {
-            syBDD r101 = bdd_refs_push(bdd_refs_sync(SYNC(sylvan_relprev)));
-            syBDD r100 = bdd_refs_push(bdd_refs_sync(SYNC(sylvan_relprev)));
+            SyBDD r101 = bdd_refs_push(bdd_refs_sync(SYNC(sylvan_relprev)));
+            SyBDD r100 = bdd_refs_push(bdd_refs_sync(SYNC(sylvan_relprev)));
             r10 = sylvan_makenode(t, r100, r101);
             bdd_refs_pop(2);
             bdd_refs_push(r10);
-            syBDD r001 = bdd_refs_push(bdd_refs_sync(SYNC(sylvan_relprev)));
-            syBDD r000 = bdd_refs_push(bdd_refs_sync(SYNC(sylvan_relprev)));
+            SyBDD r001 = bdd_refs_push(bdd_refs_sync(SYNC(sylvan_relprev)));
+            SyBDD r000 = bdd_refs_push(bdd_refs_sync(SYNC(sylvan_relprev)));
             r00 = sylvan_makenode(t, r000, r001);
             bdd_refs_pop(2);
             bdd_refs_push(r00);
@@ -1440,12 +1323,12 @@ TASK_IMPL_4(syBDD, sylvan_relprev, syBDD, a, syBDD, b, BDDSET, vars, BDDVAR, pre
         bdd_refs_spawn(SPAWN(sylvan_and, sylvan_not(r00), sylvan_not(r01), 0));
         bdd_refs_spawn(SPAWN(sylvan_and, sylvan_not(r10), sylvan_not(r11), 0));
 
-        syBDD r1 = sylvan_not(bdd_refs_push(bdd_refs_sync(SYNC(sylvan_and))));
-        syBDD r0 = sylvan_not(bdd_refs_sync(SYNC(sylvan_and)));
+        SyBDD r1 = sylvan_not(bdd_refs_push(bdd_refs_sync(SYNC(sylvan_and))));
+        SyBDD r0 = sylvan_not(bdd_refs_sync(SYNC(sylvan_and)));
         bdd_refs_pop(5);
         result = sylvan_makenode(s, r0, r1);
     } else {
-        syBDD a0, a1, b0, b1;
+        SyBDD a0, a1, b0, b1;
         if (na && va == level) {
             a0 = node_low(a, na);
             a1 = node_high(a, na);
@@ -1465,9 +1348,9 @@ TASK_IMPL_4(syBDD, sylvan_relprev, syBDD, a, syBDD, b, BDDSET, vars, BDDVAR, pre
                 bdd_refs_spawn(SPAWN(sylvan_relprev, a0, b0, vars, level));
                 bdd_refs_spawn(SPAWN(sylvan_relprev, a1, b1, vars, level));
 
-                syBDD r1 = bdd_refs_sync(SYNC(sylvan_relprev));
+                SyBDD r1 = bdd_refs_sync(SYNC(sylvan_relprev));
                 bdd_refs_push(r1);
-                syBDD r0 = bdd_refs_sync(SYNC(sylvan_relprev));
+                SyBDD r0 = bdd_refs_sync(SYNC(sylvan_relprev));
                 bdd_refs_push(r0);
                 result = CALL(sylvan_ite, r0, sylvan_true, r1, 0);
                 bdd_refs_pop(2);
@@ -1479,21 +1362,21 @@ TASK_IMPL_4(syBDD, sylvan_relprev, syBDD, a, syBDD, b, BDDSET, vars, BDDVAR, pre
                 bdd_refs_spawn(SPAWN(sylvan_relnext, a0, b1, vars, level));
                 bdd_refs_spawn(SPAWN(sylvan_relnext, a1, b1, vars, level));
 
-                syBDD r11 = bdd_refs_sync(SYNC(sylvan_relnext));
+                SyBDD r11 = bdd_refs_sync(SYNC(sylvan_relnext));
                 bdd_refs_push(r11);
-                syBDD r01 = bdd_refs_sync(SYNC(sylvan_relnext));
+                SyBDD r01 = bdd_refs_sync(SYNC(sylvan_relnext));
                 bdd_refs_push(r01);
-                syBDD r10 = bdd_refs_sync(SYNC(sylvan_relnext));
+                SyBDD r10 = bdd_refs_sync(SYNC(sylvan_relnext));
                 bdd_refs_push(r10);
-                syBDD r00 = bdd_refs_sync(SYNC(sylvan_relnext));
+                SyBDD r00 = bdd_refs_sync(SYNC(sylvan_relnext));
                 bdd_refs_push(r00);
 
                 bdd_refs_spawn(SPAWN(sylvan_ite, r00, sylvan_true, r10, 0));
                 bdd_refs_spawn(SPAWN(sylvan_ite, r01, sylvan_true, r11, 0));
 
-                syBDD r1 = bdd_refs_sync(SYNC(sylvan_ite));
+                SyBDD r1 = bdd_refs_sync(SYNC(sylvan_ite));
                 bdd_refs_push(r1);
-                syBDD r0 = bdd_refs_sync(SYNC(sylvan_ite));
+                SyBDD r0 = bdd_refs_sync(SYNC(sylvan_ite));
                 bdd_refs_pop(5);
 
                 result = sylvan_makenode(level, r0, r1);
@@ -1502,9 +1385,9 @@ TASK_IMPL_4(syBDD, sylvan_relprev, syBDD, a, syBDD, b, BDDSET, vars, BDDVAR, pre
             bdd_refs_spawn(SPAWN(sylvan_relprev, a0, b0, vars, level));
             bdd_refs_spawn(SPAWN(sylvan_relprev, a1, b1, vars, level));
 
-            syBDD r1 = bdd_refs_sync(SYNC(sylvan_relprev));
+            SyBDD r1 = bdd_refs_sync(SYNC(sylvan_relprev));
             bdd_refs_push(r1);
-            syBDD r0 = bdd_refs_sync(SYNC(sylvan_relprev));
+            SyBDD r0 = bdd_refs_sync(SYNC(sylvan_relprev));
             bdd_refs_pop(1);
             result = sylvan_makenode(level, r0, r1);
         }
@@ -1518,12 +1401,12 @@ TASK_IMPL_4(syBDD, sylvan_relprev, syBDD, a, syBDD, b, BDDSET, vars, BDDVAR, pre
 }
 
 /**
- * Computes the transitive closure by traversing the BDD recursively.
+ * Computes the transitive closure by traversing the SyBDD recursively.
  * See Y. Matsunaga, P. C. McGeer, R. K. Brayton
  *     On Computing the Transitive Closre of a State Transition Relation
  *     30th ACM Design Automation Conference, 1993.
  */
-TASK_IMPL_2(syBDD, sylvan_closure, syBDD, a, BDDVAR, prev_level)
+TASK_IMPL_2(SyBDD, sylvan_closure, SyBDD, a, BDDVAR, prev_level)
 {
     /* Terminals */
     if (a == sylvan_true) return a;
@@ -1536,13 +1419,13 @@ TASK_IMPL_2(syBDD, sylvan_closure, syBDD, a, BDDVAR, prev_level)
     sylvan_stats_count(BDD_CLOSURE);
 
     /* Determine top level */
-    bddnode_t n = GETNODE(a);
+    bddnode_t n = MTBDD_GETNODE(a);
     BDDVAR level = bddnode_getvariable(n);
 
     /* Consult cache */
     int cachenow = granularity < 2 || prev_level == 0 ? 1 : prev_level / granularity != level / granularity;
     if (cachenow) {
-        syBDD result;
+        SyBDD result;
         if (cache_get3(CACHE_BDD_CLOSURE, a, 0, 0, &result)) {
             sylvan_stats_count(BDD_CLOSURE_CACHED);
             return result;
@@ -1552,7 +1435,7 @@ TASK_IMPL_2(syBDD, sylvan_closure, syBDD, a, BDDVAR, prev_level)
     BDDVAR s = level & (~1);
     BDDVAR t = s+1;
 
-    syBDD a0, a1;
+    SyBDD a0, a1;
     if (level == s) {
         a0 = node_low(a, n);
         a1 = node_high(a, n);
@@ -1560,9 +1443,9 @@ TASK_IMPL_2(syBDD, sylvan_closure, syBDD, a, BDDVAR, prev_level)
         a0 = a1 = a;
     }
 
-    syBDD a00, a01, a10, a11;
+    SyBDD a00, a01, a10, a11;
     if (!sylvan_isconst(a0)) {
-        bddnode_t na0 = GETNODE(a0);
+        bddnode_t na0 = MTBDD_GETNODE(a0);
         if (bddnode_getvariable(na0) == t) {
             a00 = node_low(a0, na0);
             a01 = node_high(a0, na0);
@@ -1573,7 +1456,7 @@ TASK_IMPL_2(syBDD, sylvan_closure, syBDD, a, BDDVAR, prev_level)
         a00 = a01 = a0;
     }
     if (!sylvan_isconst(a1)) {
-        bddnode_t na1 = GETNODE(a1);
+        bddnode_t na1 = MTBDD_GETNODE(a1);
         if (bddnode_getvariable(na1) == t) {
             a10 = node_low(a1, na1);
             a11 = node_high(a1, na1);
@@ -1584,12 +1467,12 @@ TASK_IMPL_2(syBDD, sylvan_closure, syBDD, a, BDDVAR, prev_level)
         a10 = a11 = a1;
     }
 
-    syBDD u1 = CALL(sylvan_closure, a11, level);
+    SyBDD u1 = CALL(sylvan_closure, a11, level);
     bdd_refs_push(u1);
     /* u3 = */ bdd_refs_spawn(SPAWN(sylvan_relprev, a01, u1, sylvan_false, level));
-    syBDD u2 = CALL(sylvan_relprev, u1, a10, sylvan_false, level);
+    SyBDD u2 = CALL(sylvan_relprev, u1, a10, sylvan_false, level);
     bdd_refs_push(u2);
-    syBDD e = CALL(sylvan_relprev, a01, u2, sylvan_false, level);
+    SyBDD e = CALL(sylvan_relprev, a01, u2, sylvan_false, level);
     bdd_refs_push(e);
     e = CALL(sylvan_ite, a00, sylvan_true, e, level);
     bdd_refs_pop(1);
@@ -1597,25 +1480,25 @@ TASK_IMPL_2(syBDD, sylvan_closure, syBDD, a, BDDVAR, prev_level)
     e = CALL(sylvan_closure, e, level);
     bdd_refs_pop(1);
     bdd_refs_push(e);
-    syBDD g = CALL(sylvan_relprev, u2, e, sylvan_false, level);
+    SyBDD g = CALL(sylvan_relprev, u2, e, sylvan_false, level);
     bdd_refs_push(g);
-    syBDD u3 = bdd_refs_sync(SYNC(sylvan_relprev));
+    SyBDD u3 = bdd_refs_sync(SYNC(sylvan_relprev));
     bdd_refs_push(u3);
-    syBDD f = CALL(sylvan_relprev, e, u3, sylvan_false, level);
+    SyBDD f = CALL(sylvan_relprev, e, u3, sylvan_false, level);
     bdd_refs_push(f);
-    syBDD h = CALL(sylvan_relprev, u2, f, sylvan_false, level);
+    SyBDD h = CALL(sylvan_relprev, u2, f, sylvan_false, level);
     bdd_refs_push(h);
     h = CALL(sylvan_ite, u1, sylvan_true, h, level);
     bdd_refs_pop(1);
     bdd_refs_push(h);
 
-    syBDD r0, r1;
+    SyBDD r0, r1;
     /* R0 */ r0 = sylvan_makenode(t, e, f);
     bdd_refs_pop(7);
     bdd_refs_push(r0);
     /* R1 */ r1 = sylvan_makenode(t, g, h);
     bdd_refs_pop(1);
-    syBDD result = sylvan_makenode(s, r0, r1);
+    SyBDD result = sylvan_makenode(s, r0, r1);
 
     if (cachenow) {
         if (cache_put3(CACHE_BDD_CLOSURE, a, 0, 0, result)) sylvan_stats_count(BDD_CLOSURE_CACHEDPUT);
@@ -1628,7 +1511,7 @@ TASK_IMPL_2(syBDD, sylvan_closure, syBDD, a, BDDVAR, prev_level)
 /**
  * Function composition
  */
-TASK_IMPL_3(syBDD, sylvan_compose, syBDD, a, BDDMAP, map, BDDVAR, prev_level)
+TASK_IMPL_3(SyBDD, sylvan_compose, SyBDD, a, BDDMAP, map, BDDVAR, prev_level)
 {
     /* Trivial cases */
     if (a == sylvan_false || a == sylvan_true) return a;
@@ -1641,23 +1524,23 @@ TASK_IMPL_3(syBDD, sylvan_compose, syBDD, a, BDDMAP, map, BDDVAR, prev_level)
     sylvan_stats_count(BDD_COMPOSE);
 
     /* Determine top level */
-    bddnode_t n = GETNODE(a);
+    bddnode_t n = MTBDD_GETNODE(a);
     BDDVAR level = bddnode_getvariable(n);
 
     /* Skip map */
-    bddnode_t map_node = GETNODE(map);
+    bddnode_t map_node = MTBDD_GETNODE(map);
     BDDVAR map_var = bddnode_getvariable(map_node);
     while (map_var < level) {
         map = node_low(map, map_node);
         if (sylvan_map_isempty(map)) return a;
-        map_node = GETNODE(map);
+        map_node = MTBDD_GETNODE(map);
         map_var = bddnode_getvariable(map_node);
     }
 
     /* Consult cache */
     int cachenow = granularity < 2 || prev_level == 0 ? 1 : prev_level / granularity != level / granularity;
     if (cachenow) {
-        syBDD result;
+        SyBDD result;
         if (cache_get3(CACHE_BDD_COMPOSE, a, map, 0, &result)) {
             sylvan_stats_count(BDD_COMPOSE_CACHED);
             return result;
@@ -1666,15 +1549,15 @@ TASK_IMPL_3(syBDD, sylvan_compose, syBDD, a, BDDMAP, map, BDDVAR, prev_level)
 
     /* Recursively calculate low and high */
     bdd_refs_spawn(SPAWN(sylvan_compose, node_low(a, n), map, level));
-    syBDD high = CALL(sylvan_compose, node_high(a, n), map, level);
+    SyBDD high = CALL(sylvan_compose, node_high(a, n), map, level);
     bdd_refs_push(high);
-    syBDD low = bdd_refs_sync(SYNC(sylvan_compose));
+    SyBDD low = bdd_refs_sync(SYNC(sylvan_compose));
     bdd_refs_push(low);
 
     /* Calculate result */
-    syBDD root = map_var == level ? node_high(map, map_node) : sylvan_ithvar(level);
+    SyBDD root = map_var == level ? node_high(map, map_node) : sylvan_ithvar(level);
     bdd_refs_push(root);
-    syBDD result = CALL(sylvan_ite, root, high, low, 0);
+    SyBDD result = CALL(sylvan_ite, root, high, low, 0);
     bdd_refs_pop(3);
 
     if (cachenow) {
@@ -1684,42 +1567,10 @@ TASK_IMPL_3(syBDD, sylvan_compose, syBDD, a, BDDMAP, map, BDDVAR, prev_level)
     return result;
 }
 
-/**
- * Count number of nodes in BDD
- */
-uint64_t sylvan_nodecount_do_1(syBDD a)
-{
-    if (sylvan_isconst(a)) return 0;
-    bddnode_t na = GETNODE(a);
-    if (bddnode_getmark(na)) return 0;
-    bddnode_setmark(na, 1);
-    uint64_t result = 1;
-    result += sylvan_nodecount_do_1(bddnode_getlow(na));
-    result += sylvan_nodecount_do_1(bddnode_gethigh(na));
-    return result;
-}
-
-void sylvan_nodecount_do_2(syBDD a)
-{
-    if (sylvan_isconst(a)) return;
-    bddnode_t na = GETNODE(a);
-    if (!bddnode_getmark(na)) return;
-    bddnode_setmark(na, 0);
-    sylvan_nodecount_do_2(bddnode_getlow(na));
-    sylvan_nodecount_do_2(bddnode_gethigh(na));
-}
-
-size_t sylvan_nodecount(syBDD a)
-{
-    uint32_t result = sylvan_nodecount_do_1(a);
-    sylvan_nodecount_do_2(a);
-    return result;
-}
-
 /**
  * Calculate the number of distinct paths to True.
  */
-TASK_IMPL_2(double, sylvan_pathcount, syBDD, bdd, BDDVAR, prev_level)
+TASK_IMPL_2(double, sylvan_pathcount, SyBDD, bdd, BDDVAR, prev_level)
 {
     /* Trivial cases */
     if (bdd == sylvan_false) return 0.0;
@@ -1728,9 +1579,10 @@ TASK_IMPL_2(double, sylvan_pathcount, syBDD, bdd, BDDVAR, prev_level)
     /* Perhaps execute garbage collection */
     sylvan_gc_test();
 
+    /* Count operation */
     sylvan_stats_count(BDD_PATHCOUNT);
 
-    syBDD level = sylvan_var(bdd);
+    SyBDD level = sylvan_var(bdd);
 
     /* Consult cache */
     int cachenow = granularity < 2 || prev_level == 0 ? 1 : prev_level / granularity != level / granularity;
@@ -1757,7 +1609,7 @@ TASK_IMPL_2(double, sylvan_pathcount, syBDD, bdd, BDDVAR, prev_level)
 /**
  * Calculate the number of satisfying variable assignments according to <variables>.
  */
-TASK_IMPL_3(double, sylvan_satcount, syBDD, bdd, BDDSET, variables, BDDVAR, prev_level)
+TASK_IMPL_3(double, sylvan_satcount, SyBDD, bdd, BDDSET, variables, BDDVAR, prev_level)
 {
     /* Trivial cases */
     if (bdd == sylvan_false) return 0.0;
@@ -1766,19 +1618,20 @@ TASK_IMPL_3(double, sylvan_satcount, syBDD, bdd, BDDSET, variables, BDDVAR, prev
     /* Perhaps execute garbage collection */
     sylvan_gc_test();
 
+    /* Count operation */
     sylvan_stats_count(BDD_SATCOUNT);
 
     /* Count variables before var(bdd) */
     size_t skipped = 0;
     BDDVAR var = sylvan_var(bdd);
-    bddnode_t set_node = GETNODE(variables);
+    bddnode_t set_node = MTBDD_GETNODE(variables);
     BDDVAR set_var = bddnode_getvariable(set_node);
     while (var != set_var) {
         skipped++;
         variables = node_high(variables, set_node);
         // if this assertion fails, then variables is not the support of <bdd>
         assert(!sylvan_set_isempty(variables));
-        set_node = GETNODE(variables);
+        set_node = MTBDD_GETNODE(variables);
         set_var = bddnode_getvariable(set_node);
     }
 
@@ -1809,18 +1662,18 @@ TASK_IMPL_3(double, sylvan_satcount, syBDD, bdd, BDDSET, variables, BDDVAR, prev
 }
 
 int
-sylvan_sat_one(syBDD bdd, BDDSET vars, uint8_t *str)
+sylvan_sat_one(SyBDD bdd, BDDSET vars, uint8_t *str)
 {
     if (bdd == sylvan_false) return 0;
     if (str == NULL) return 0;
     if (sylvan_set_isempty(vars)) return 1;
 
     for (;;) {
-        bddnode_t n_vars = GETNODE(vars);
+        bddnode_t n_vars = MTBDD_GETNODE(vars);
         if (bdd == sylvan_true) {
             *str = 0;
         } else {
-            bddnode_t n_bdd = GETNODE(bdd);
+            bddnode_t n_bdd = MTBDD_GETNODE(bdd);
             if (bddnode_getvariable(n_bdd) != bddnode_getvariable(n_vars)) {
                 *str = 0;
             } else {
@@ -1843,8 +1696,8 @@ sylvan_sat_one(syBDD bdd, BDDSET vars, uint8_t *str)
     return 1;
 }
 
-syBDD
-sylvan_sat_single(syBDD bdd, BDDSET vars)
+SyBDD
+sylvan_sat_single(SyBDD bdd, BDDSET vars)
 {
     if (bdd == sylvan_false) return sylvan_false;
     if (sylvan_set_isempty(vars)) {
@@ -1852,44 +1705,44 @@ sylvan_sat_single(syBDD bdd, BDDSET vars)
         return sylvan_true;
     }
 
-    bddnode_t n_vars = GETNODE(vars);
+    bddnode_t n_vars = MTBDD_GETNODE(vars);
     uint32_t var = bddnode_getvariable(n_vars);
-    syBDD next_vars = node_high(vars, n_vars);
+    SyBDD next_vars = node_high(vars, n_vars);
     if (bdd == sylvan_true) {
         // take false
-        syBDD res = sylvan_sat_single(bdd, next_vars);
+        SyBDD res = sylvan_sat_single(bdd, next_vars);
         return sylvan_makenode(var, res, sylvan_false);
     }
-    bddnode_t n_bdd = GETNODE(bdd);
+    bddnode_t n_bdd = MTBDD_GETNODE(bdd);
     if (bddnode_getvariable(n_bdd) != var) {
         assert(bddnode_getvariable(n_bdd)>var);
         // take false
-        syBDD res = sylvan_sat_single(bdd, next_vars);
+        SyBDD res = sylvan_sat_single(bdd, next_vars);
         return sylvan_makenode(var, res, sylvan_false);
     }
     if (node_high(bdd, n_bdd) == sylvan_false) {
         // take false
-        syBDD res = sylvan_sat_single(node_low(bdd, n_bdd), next_vars);
+        SyBDD res = sylvan_sat_single(node_low(bdd, n_bdd), next_vars);
         return sylvan_makenode(var, res, sylvan_false);
     }
     // take true
-    syBDD res = sylvan_sat_single(node_high(bdd, n_bdd), next_vars);
+    SyBDD res = sylvan_sat_single(node_high(bdd, n_bdd), next_vars);
     return sylvan_makenode(var, sylvan_false, res);
 }
 
-syBDD
-sylvan_sat_one_bdd(syBDD bdd)
+SyBDD
+sylvan_sat_one_bdd(SyBDD bdd)
 {
     if (bdd == sylvan_false) return sylvan_false;
     if (bdd == sylvan_true) return sylvan_true;
 
-    bddnode_t node = GETNODE(bdd);
-    syBDD low = node_low(bdd, node);
-    syBDD high = node_high(bdd, node);
+    bddnode_t node = MTBDD_GETNODE(bdd);
+    SyBDD low = node_low(bdd, node);
+    SyBDD high = node_high(bdd, node);
 
-    syBDD m;
+    SyBDD m;
 
-    syBDD result;
+    SyBDD result;
     if (low == sylvan_false) {
         m = sylvan_sat_one_bdd(high);
         result = sylvan_makenode(bddnode_getvariable(node), sylvan_false, m);
@@ -1909,16 +1762,16 @@ sylvan_sat_one_bdd(syBDD bdd)
     return result;
 }
 
-syBDD
+SyBDD
 sylvan_cube(BDDSET vars, uint8_t *cube)
 {
     if (sylvan_set_isempty(vars)) return sylvan_true;
 
-    bddnode_t n = GETNODE(vars);
+    bddnode_t n = MTBDD_GETNODE(vars);
     BDDVAR v = bddnode_getvariable(n);
     vars = node_high(vars, n);
 
-    syBDD result = sylvan_cube(vars, cube+1);
+    SyBDD result = sylvan_cube(vars, cube+1);
     if (*cube == 0) {
         result = sylvan_makenode(v, result, sylvan_false);
     } else if (*cube == 1) {
@@ -1928,14 +1781,14 @@ sylvan_cube(BDDSET vars, uint8_t *cube)
     return result;
 }
 
-TASK_IMPL_3(syBDD, sylvan_union_cube, syBDD, bdd, BDDSET, vars, uint8_t *, cube)
+TASK_IMPL_3(SyBDD, sylvan_union_cube, SyBDD, bdd, BDDSET, vars, uint8_t *, cube)
 {
     /* Terminal cases */
     if (bdd == sylvan_true) return sylvan_true;
     if (bdd == sylvan_false) return sylvan_cube(vars, cube);
     if (sylvan_set_isempty(vars)) return sylvan_true;
 
-    bddnode_t nv = GETNODE(vars);
+    bddnode_t nv = MTBDD_GETNODE(vars);
 
     for (;;) {
         if (*cube == 0 || *cube == 1) break;
@@ -1943,15 +1796,15 @@ TASK_IMPL_3(syBDD, sylvan_union_cube, syBDD, bdd, BDDSET, vars, uint8_t *, cube)
         cube++;
         vars = node_high(vars, nv);
         if (sylvan_set_isempty(vars)) return sylvan_true;
-        nv = GETNODE(vars);
+        nv = MTBDD_GETNODE(vars);
     }
 
     sylvan_gc_test();
 
     // missing: SV_CNT_OP
 
-    bddnode_t n = GETNODE(bdd);
-    syBDD result = bdd;
+    bddnode_t n = MTBDD_GETNODE(bdd);
+    SyBDD result = bdd;
     BDDVAR v = bddnode_getvariable(nv);
     BDDVAR n_level = bddnode_getvariable(n);
 
@@ -1965,27 +1818,27 @@ TASK_IMPL_3(syBDD, sylvan_union_cube, syBDD, bdd, BDDSET, vars, uint8_t *, cube)
             result = sylvan_makenode(v, bdd, result);
         }
     } else if (v > n_level) {
-        syBDD high = node_high(bdd, n);
-        syBDD low = node_low(bdd, n);
-        SPAWN(sylvan_union_cube, high, vars, cube);
-        syBDD new_low = sylvan_union_cube(low, vars, cube);
+        SyBDD high = node_high(bdd, n);
+        SyBDD low = node_low(bdd, n);
+        bdd_refs_spawn(SPAWN(sylvan_union_cube, high, vars, cube));
+        SyBDD new_low = sylvan_union_cube(low, vars, cube);
         bdd_refs_push(new_low);
-        syBDD new_high = SYNC(sylvan_union_cube);
+        SyBDD new_high = bdd_refs_sync(SYNC(sylvan_union_cube));
         bdd_refs_pop(1);
         if (new_low != low || new_high != high) {
             result = sylvan_makenode(n_level, new_low, new_high);
         }
     } else /* v == n_level */ {
         vars = node_high(vars, nv);
-        syBDD high = node_high(bdd, n);
-        syBDD low = node_low(bdd, n);
+        SyBDD high = node_high(bdd, n);
+        SyBDD low = node_low(bdd, n);
         if (*cube == 0) {
-            syBDD new_low = sylvan_union_cube(low, vars, cube+1);
+            SyBDD new_low = sylvan_union_cube(low, vars, cube+1);
             if (new_low != low) {
                 result = sylvan_makenode(n_level, new_low, high);
             }
         } else /* *cube == 1 */ {
-            syBDD new_high = sylvan_union_cube(high, vars, cube+1);
+            SyBDD new_high = sylvan_union_cube(high, vars, cube+1);
             if (new_high != high) {
                 result = sylvan_makenode(n_level, low, new_high);
             }
@@ -2002,7 +1855,7 @@ struct bdd_path
     int8_t val; // 0=false, 1=true, 2=both
 };
 
-VOID_TASK_5(sylvan_enum_do, syBDD, bdd, BDDSET, vars, enum_cb, cb, void*, context, struct bdd_path*, path)
+VOID_TASK_5(sylvan_enum_do, SyBDD, bdd, BDDSET, vars, enum_cb, cb, void*, context, struct bdd_path*, path)
 {
     if (bdd == sylvan_false) return;
 
@@ -2050,7 +1903,7 @@ VOID_TASK_5(sylvan_enum_do, syBDD, bdd, BDDSET, vars, enum_cb, cb, void*, contex
     }
 }
 
-VOID_TASK_5(sylvan_enum_par_do, syBDD, bdd, BDDSET, vars, enum_cb, cb, void*, context, struct bdd_path*, path)
+VOID_TASK_5(sylvan_enum_par_do, SyBDD, bdd, BDDSET, vars, enum_cb, cb, void*, context, struct bdd_path*, path)
 {
     if (bdd == sylvan_false) return;
 
@@ -2077,9 +1930,9 @@ VOID_TASK_5(sylvan_enum_par_do, syBDD, bdd, BDDSET, vars, enum_cb, cb, void*, co
         return;
     }
 
-    syBDD var = sylvan_var(vars);
+    SyBDD var = sylvan_var(vars);
     vars = sylvan_set_next(vars);
-    syBDD bdd_var = sylvan_var(bdd);
+    SyBDD bdd_var = sylvan_var(bdd);
 
     /* assert var <= bdd_var */
     if (var < bdd_var) {
@@ -2099,474 +1952,81 @@ VOID_TASK_5(sylvan_enum_par_do, syBDD, bdd, BDDSET, vars, enum_cb, cb, void*, co
     }
 }
 
-VOID_TASK_IMPL_4(sylvan_enum, syBDD, bdd, BDDSET, vars, enum_cb, cb, void*, context)
+VOID_TASK_IMPL_4(sylvan_enum, SyBDD, bdd, BDDSET, vars, enum_cb, cb, void*, context)
 {
     CALL(sylvan_enum_do, bdd, vars, cb, context, 0);
 }
 
-VOID_TASK_IMPL_4(sylvan_enum_par, syBDD, bdd, BDDSET, vars, enum_cb, cb, void*, context)
+VOID_TASK_IMPL_4(sylvan_enum_par, SyBDD, bdd, BDDSET, vars, enum_cb, cb, void*, context)
 {
     CALL(sylvan_enum_par_do, bdd, vars, cb, context, 0);
 }
 
-TASK_5(syBDD, sylvan_collect_do, syBDD, bdd, BDDSET, vars, sylvan_collect_cb, cb, void*, context, struct bdd_path*, path)
+TASK_5(SyBDD, sylvan_collect_do, SyBDD, bdd, BDDSET, vars, sylvan_collect_cb, cb, void*, context, struct bdd_path*, path)
 {
-    if (bdd == sylvan_false) return sylvan_false;
-
-    if (sylvan_set_isempty(vars)) {
-        /* compute length of path */
-        int i=0;
-        struct bdd_path *pp;
-        for (pp = path; pp != NULL; pp = pp->prev) i++;
-        /* if length is 0 (enum called with empty vars??), return */
-        if (i == 0) return WRAP(cb, context, NULL);
-        /* fill cube and vars with trace */
-        uint8_t cube[i];
-        int j=0;
-        for (pp = path; pp != NULL; pp = pp->prev) {
-            cube[i-j-1] = pp->val;
-            j++;
+    if (bdd == sylvan_false) {
+         return sylvan_false;
+    } else if (sylvan_set_isempty(vars)) {
+        /**
+         * Compute trace length
+         */
+        size_t len = 0;
+        struct bdd_path *p = path;
+        while (p != NULL) {
+            len++;
+            p = p->prev;
         }
-        /* call callback */
-        return WRAP(cb, context, cube);
-    } else {
-        syBDD var = sylvan_var(vars);
-        vars = sylvan_set_next(vars);
-        syBDD bdd_var = sylvan_var(bdd);
-
-        /* if fails, then <bdd> has variables not in <vars> */
-        assert(var <= bdd_var);
-
-        struct bdd_path pp1 = (struct bdd_path){path, var, 1};
-        struct bdd_path pp0 = (struct bdd_path){path, var, 0};
-        if (var < bdd_var) {
-            bdd_refs_spawn(SPAWN(sylvan_collect_do, bdd, vars, cb, context, &pp1));
-            syBDD low = bdd_refs_push(CALL(sylvan_collect_do, bdd, vars, cb, context, &pp0));
-            syBDD high = bdd_refs_push(bdd_refs_sync(SYNC(sylvan_collect_do)));
-            syBDD res = sylvan_or(low, high);
-            bdd_refs_pop(2);
-            return res;
-        } else if (var == bdd_var) {
-            bdd_refs_spawn(SPAWN(sylvan_collect_do, sylvan_high(bdd), vars, cb, context, &pp1));
-            syBDD low = bdd_refs_push(CALL(sylvan_collect_do, sylvan_low(bdd), vars, cb, context, &pp0));
-            syBDD high = bdd_refs_push(bdd_refs_sync(SYNC(sylvan_collect_do)));
-            syBDD res = sylvan_or(low, high);
-            bdd_refs_pop(2);
-            return res;
-        } else {
-            return sylvan_invalid; // unreachable
+        /**
+         * Fill array
+         */
+        uint8_t arr[len];
+        for (size_t i=0; i<len; i++) {
+            arr[len-i-1] = path->val;
+            path = path->prev;
         }
-    }
-}
-
-TASK_IMPL_4(syBDD, sylvan_collect, syBDD, bdd, BDDSET, vars, sylvan_collect_cb, cb, void*, context)
-{
-    return CALL(sylvan_collect_do, bdd, vars, cb, context, 0);
-}
-
-/**
- * IMPLEMENTATION OF BDDSET
- */
-
-int
-sylvan_set_in(BDDSET set, BDDVAR level)
-{
-    while (!sylvan_set_isempty(set)) {
-        bddnode_t n = GETNODE(set);
-        BDDVAR n_level = bddnode_getvariable(n);
-        if (n_level == level) return 1;
-        if (n_level > level) return 0; // BDDs are ordered
-        set = node_high(set, n);
-    }
-
-    return 0;
-}
-
-size_t
-sylvan_set_count(BDDSET set)
-{
-    size_t result = 0;
-    for (;!sylvan_set_isempty(set);set = sylvan_set_next(set)) result++;
-    return result;
-}
-
-void
-sylvan_set_toarray(BDDSET set, BDDVAR *arr)
-{
-    size_t i = 0;
-    while (!sylvan_set_isempty(set)) {
-        bddnode_t n = GETNODE(set);
-        arr[i++] = bddnode_getvariable(n);
-        set = node_high(set, n);
-    }
-}
-
-TASK_IMPL_2(BDDSET, sylvan_set_fromarray, BDDVAR*, arr, size_t, length)
-{
-    if (length == 0) return sylvan_set_empty();
-    BDDSET sub = sylvan_set_fromarray(arr+1, length-1);
-    bdd_refs_push(sub);
-    BDDSET result = sylvan_set_add(sub, *arr);
-    bdd_refs_pop(1);
-    return result;
-}
-
-void
-sylvan_test_isset(BDDSET set)
-{
-    while (set != sylvan_false) {
-        assert(set != sylvan_true);
-        assert(llmsset_is_marked(nodes, set));
-        bddnode_t n = GETNODE(set);
-        assert(node_low(set, n) == sylvan_true);
-        set = node_high(set, n);
-    }
-}
-
-/**
- * IMPLEMENTATION OF BDDMAP
- */
-
-BDDMAP
-sylvan_map_add(BDDMAP map, BDDVAR key, syBDD value)
-{
-    if (sylvan_map_isempty(map)) return sylvan_makenode(key, sylvan_map_empty(), value);
-
-    bddnode_t n = GETNODE(map);
-    BDDVAR key_m = bddnode_getvariable(n);
-
-    if (key_m < key) {
-        // add recursively and rebuild tree
-        BDDMAP low = sylvan_map_add(node_low(map, n), key, value);
-        BDDMAP result = sylvan_makenode(key_m, low, node_high(map, n));
-        return result;
-    } else if (key_m > key) {
-        return sylvan_makenode(key, map, value);
-    } else {
-        // replace old
-        return sylvan_makenode(key, node_low(map, n), value);
-    }
-}
-
-BDDMAP
-sylvan_map_addall(BDDMAP map_1, BDDMAP map_2)
-{
-    // one of the maps is empty
-    if (sylvan_map_isempty(map_1)) return map_2;
-    if (sylvan_map_isempty(map_2)) return map_1;
-
-    bddnode_t n_1 = GETNODE(map_1);
-    BDDVAR key_1 = bddnode_getvariable(n_1);
-
-    bddnode_t n_2 = GETNODE(map_2);
-    BDDVAR key_2 = bddnode_getvariable(n_2);
-
-    BDDMAP result;
-    if (key_1 < key_2) {
-        // key_1, recurse on n_1->low, map_2
-        BDDMAP low = sylvan_map_addall(node_low(map_1, n_1), map_2);
-        result = sylvan_makenode(key_1, low, node_high(map_1, n_1));
-    } else if (key_1 > key_2) {
-        // key_2, recurse on map_1, n_2->low
-        BDDMAP low = sylvan_map_addall(map_1, node_low(map_2, n_2));
-        result = sylvan_makenode(key_2, low, node_high(map_2, n_2));
-    } else {
-        // equal: key_2, recurse on n_1->low, n_2->low
-        BDDMAP low = sylvan_map_addall(node_low(map_1, n_1), node_low(map_2, n_2));
-        result = sylvan_makenode(key_2, low, node_high(map_2, n_2));
-    }
-    return result;
-}
-
-BDDMAP
-sylvan_map_remove(BDDMAP map, BDDVAR key)
-{
-    if (sylvan_map_isempty(map)) return map;
-
-    bddnode_t n = GETNODE(map);
-    BDDVAR key_m = bddnode_getvariable(n);
-
-    if (key_m < key) {
-        BDDMAP low = sylvan_map_remove(node_low(map, n), key);
-        BDDMAP result = sylvan_makenode(key_m, low, node_high(map, n));
-        return result;
-    } else if (key_m > key) {
-        return map;
-    } else {
-        return node_low(map, n);
-    }
-}
-
-BDDMAP
-sylvan_map_removeall(BDDMAP map, BDDSET toremove)
-{
-    if (sylvan_map_isempty(map)) return map;
-    if (sylvan_set_isempty(toremove)) return map;
-
-    bddnode_t n_1 = GETNODE(map);
-    BDDVAR key_1 = bddnode_getvariable(n_1);
-
-    bddnode_t n_2 = GETNODE(toremove);
-    BDDVAR key_2 = bddnode_getvariable(n_2);
-
-    if (key_1 < key_2) {
-        BDDMAP low = sylvan_map_removeall(node_low(map, n_1), toremove);
-        BDDMAP result = sylvan_makenode(key_1, low, node_high(map, n_1));
-        return result;
-    } else if (key_1 > key_2) {
-        return sylvan_map_removeall(map, node_high(toremove, n_2));
+        /**
+         * Call callback
+         */
+        return WRAP(cb, context, arr);
     } else {
-        return sylvan_map_removeall(node_low(map, n_1), node_high(toremove, n_2));
-    }
-}
-
-int
-sylvan_map_in(BDDMAP map, BDDVAR key)
-{
-    while (!sylvan_map_isempty(map)) {
-        bddnode_t n = GETNODE(map);
-        BDDVAR n_level = bddnode_getvariable(n);
-        if (n_level == key) return 1;
-        if (n_level > key) return 0; // BDDs are ordered
-        map = node_low(map, n);
-    }
-
-    return 0;
-}
-
-size_t
-sylvan_map_count(BDDMAP map)
-{
-    size_t r=0;
-    while (!sylvan_map_isempty(map)) { r++; map=sylvan_map_next(map); }
-    return r;
-}
-
-BDDMAP
-sylvan_set_to_map(BDDSET set, syBDD value)
-{
-    if (sylvan_set_isempty(set)) return sylvan_map_empty();
-    bddnode_t set_n = GETNODE(set);
-    syBDD sub = sylvan_set_to_map(node_high(set, set_n), value);
-    syBDD result = sylvan_makenode(sub, bddnode_getvariable(set_n), value);
-    return result;
-}
-
-/**
- * Determine the support of a BDD (all variables used in the BDD)
- */
-TASK_IMPL_1(syBDD, sylvan_support, syBDD, bdd)
-{
-    if (bdd == sylvan_true || bdd == sylvan_false) return sylvan_set_empty(); // return empty set
-
-    sylvan_gc_test();
-
-    sylvan_stats_count(BDD_SUPPORT);
-
-    syBDD result;
-    if (cache_get3(CACHE_BDD_SUPPORT, bdd, 0, 0, &result)) {
-        sylvan_stats_count(BDD_SUPPORT_CACHED);
-        return result;
-    }
-
-    bddnode_t n = GETNODE(bdd);
-    syBDD high, low, set;
-
-    /* compute recursively */
-    bdd_refs_spawn(SPAWN(sylvan_support, bddnode_getlow(n)));
-    high = bdd_refs_push(CALL(sylvan_support, bddnode_gethigh(n)));
-    low = bdd_refs_push(bdd_refs_sync(SYNC(sylvan_support)));
-
-    /* take intersection of support of low and support of high */
-    set = sylvan_and(low, high);
-    bdd_refs_pop(2);
-
-    /* add current level to set */
-    result = sylvan_makenode(bddnode_getvariable(n), sylvan_false, set);
-
-    if (cache_put3(CACHE_BDD_SUPPORT, bdd, 0, 0, result)) sylvan_stats_count(BDD_SUPPORT_CACHEDPUT);
-    return result;
-}
-
-static void
-sylvan_unmark_rec(bddnode_t node)
-{
-    if (bddnode_getmark(node)) {
-        bddnode_setmark(node, 0);
-        if (!sylvan_isconst(bddnode_getlow(node))) sylvan_unmark_rec(GETNODE(bddnode_getlow(node)));
-        if (!sylvan_isconst(bddnode_gethigh(node))) sylvan_unmark_rec(GETNODE(bddnode_gethigh(node)));
-    }
-}
-
-/**
- * fprint, print
- */
-void
-sylvan_fprint(FILE *f, syBDD bdd)
-{
-    sylvan_serialize_reset();
-    size_t v = sylvan_serialize_add(bdd);
-    fprintf(f, "%s%zu,", bdd&sylvan_complement?"!":"", v);
-    sylvan_serialize_totext(f);
-}
-
-void
-sylvan_print(syBDD bdd)
-{
-    sylvan_fprint(stdout, bdd);
-}
-
-/**
- * Output to .DOT files
- */
-
-/***
- * We keep a set [level -> [node]] using AVLset
- */
-struct level_to_nodeset {
-    BDDVAR level;
-    avl_node_t *set;
-};
-
-AVL(level_to_nodeset, struct level_to_nodeset)
-{
-    if (left->level > right->level) return 1;
-    if (right->level > left->level) return -1;
-    return 0;
-}
-
-AVL(nodeset, syBDD)
-{
-    if (*left > *right) return 1;
-    if (*right > *left) return -1;
-    return 0;
-}
-
-/* returns 1 if inserted, 0 if already existed */
-static int __attribute__((noinline))
-sylvan_dothelper_register(avl_node_t **set, syBDD bdd)
-{
-    struct level_to_nodeset s, *ss;
-    s.level = sylvan_var(bdd);
-    ss = level_to_nodeset_search(*set, &s);
-    if (ss == NULL) {
-        s.set = NULL;
-        ss = level_to_nodeset_put(set, &s, NULL);
-    }
-    assert(ss != NULL);
-    return nodeset_insert(&ss->set, &bdd);
-}
-
-static void
-sylvan_fprintdot_rec(FILE *out, syBDD bdd, avl_node_t **levels)
-{
-    bdd = BDD_STRIPMARK(bdd);
-    if (bdd == sylvan_false) return;
-    if (!sylvan_dothelper_register(levels, bdd)) return;
-
-    syBDD low = sylvan_low(bdd);
-    syBDD high = sylvan_high(bdd);
-    fprintf(out, "\"%" PRIx64 "\" [label=\"%d\"];\n", bdd, sylvan_var(bdd));
-    fprintf(out, "\"%" PRIx64 "\" -> \"%" PRIx64 "\" [style=dashed];\n", bdd, low);
-    fprintf(out, "\"%" PRIx64 "\" -> \"%" PRIx64 "\" [style=solid dir=both arrowtail=%s];\n", bdd, BDD_STRIPMARK(high), BDD_HASMARK(high) ? "dot" : "none");
-    sylvan_fprintdot_rec(out, low, levels);
-    sylvan_fprintdot_rec(out, high, levels);
-}
-
-void
-sylvan_fprintdot(FILE *out, syBDD bdd)
-{
-    fprintf(out, "digraph \"DD\" {\n");
-    fprintf(out, "graph [dpi = 300];\n");
-    fprintf(out, "center = true;\n");
-    fprintf(out, "edge [dir = forward];\n");
-    fprintf(out, "0 [label=\"0\", style=filled, shape=box, height=0.3, width=0.3];\n");
-    fprintf(out, "root [style=invis];\n");
-    fprintf(out, "root -> \"%" PRIx64 "\" [style=solid dir=both arrowtail=%s];\n", BDD_STRIPMARK(bdd), BDD_HASMARK(bdd) ? "dot" : "none");
-
-    avl_node_t *levels = NULL;
-    sylvan_fprintdot_rec(out, bdd, &levels);
-
-    if (levels != NULL) {
-        size_t levels_count = avl_count(levels);
-        struct level_to_nodeset *arr = level_to_nodeset_toarray(levels);
-        size_t i;
-        for (i=0;i<levels_count;i++) {
-            fprintf(out, "{ rank=same; ");
-            size_t node_count = avl_count(arr[i].set);
-            size_t j;
-            syBDD *arr_j = nodeset_toarray(arr[i].set);
-            for (j=0;j<node_count;j++) {
-                fprintf(out, "\"%" PRIx64 "\"; ", arr_j[j]);
-            }
-            fprintf(out, "}\n");
-        }
-        level_to_nodeset_free(&levels);
-    }
-
-    fprintf(out, "}\n");
-}
-
-void
-sylvan_printdot(syBDD bdd)
-{
-    sylvan_fprintdot(stdout, bdd);
-}
-
-static void
-sylvan_fprintdot_nc_rec(FILE *out, syBDD bdd, avl_node_t **levels)
-{
-    if (bdd == sylvan_true || bdd == sylvan_false) return;
-    if (!sylvan_dothelper_register(levels, bdd)) return;
-
-    syBDD low = sylvan_low(bdd);
-    syBDD high = sylvan_high(bdd);
-    fprintf(out, "\"%" PRIx64 "\" [label=\"%d\"];\n", bdd, sylvan_var(bdd));
-    fprintf(out, "\"%" PRIx64 "\" -> \"%" PRIx64 "\" [style=dashed];\n", bdd, low);
-    fprintf(out, "\"%" PRIx64 "\" -> \"%" PRIx64 "\" [style=solid];\n", bdd, high);
-    sylvan_fprintdot_nc_rec(out, low, levels);
-    sylvan_fprintdot_nc_rec(out, high, levels);
-}
-
-void
-sylvan_fprintdot_nc(FILE *out, syBDD bdd)
-{
-    fprintf(out, "digraph \"DD\" {\n");
-    fprintf(out, "graph [dpi = 300];\n");
-    fprintf(out, "center = true;\n");
-    fprintf(out, "edge [dir = forward];\n");
-    fprintf(out, "\"%" PRIx64 "\" [shape=box, label=\"F\", style=filled, shape=box, height=0.3, width=0.3];\n", sylvan_false);
-    fprintf(out, "\"%" PRIx64 "\" [shape=box, label=\"T\", style=filled, shape=box, height=0.3, width=0.3];\n", sylvan_true);
-    fprintf(out, "root [style=invis];\n");
-    fprintf(out, "root -> \"%" PRIx64 "\" [style=solid];\n", bdd);
-
-    avl_node_t *levels = NULL;
-    sylvan_fprintdot_nc_rec(out, bdd, &levels);
-
-    if (levels != NULL) {
-        size_t levels_count = avl_count(levels);
-        struct level_to_nodeset *arr = level_to_nodeset_toarray(levels);
-        size_t i;
-        for (i=0;i<levels_count;i++) {
-            fprintf(out, "{ rank=same; ");
-            size_t node_count = avl_count(arr[i].set);
-            size_t j;
-            syBDD *arr_j = nodeset_toarray(arr[i].set);
-            for (j=0;j<node_count;j++) {
-                fprintf(out, "\"%" PRIx64 "\"; ", arr_j[j]);
+        /**
+         * Obtain domain variable
+         */
+        const uint32_t dom_var = sylvan_var(vars);
+        const SyBDD dom_next = sylvan_set_next(vars);
+        /**
+         * Obtain cofactors
+         */
+        SyBDD bdd0, bdd1;
+        if (bdd == sylvan_true) {
+            bdd0 = bdd1 = bdd;
+        } else {
+            const uint32_t bdd_var = sylvan_var(bdd);
+            assert(dom_var <= bdd_var);
+            if (dom_var < bdd_var) {
+                bdd0 = bdd1 = bdd;
+            } else {
+                bdd0 = sylvan_low(bdd);
+                bdd1 = sylvan_high(bdd);
             }
-            fprintf(out, "}\n");
-        }
-        level_to_nodeset_free(&levels);
+       }
+        /**
+         * Call recursive functions
+         */
+        struct bdd_path p0 = (struct bdd_path){path, dom_var, 0};
+        struct bdd_path p1 = (struct bdd_path){path, dom_var, 1};
+        bdd_refs_spawn(SPAWN(sylvan_collect_do, bdd1, dom_next, cb, context, &p1));
+        SyBDD low = bdd_refs_push(CALL(sylvan_collect_do, bdd0, dom_next, cb, context, &p0));
+        SyBDD high = bdd_refs_push(bdd_refs_sync(SYNC(sylvan_collect_do)));
+        SyBDD res = sylvan_or(low, high);
+        bdd_refs_pop(2);
+        return res;
     }
-
-    fprintf(out, "}\n");
 }
 
-void
-sylvan_printdot_nc(syBDD bdd)
+TASK_IMPL_4(SyBDD, sylvan_collect, SyBDD, bdd, BDDSET, vars, sylvan_collect_cb, cb, void*, context)
 {
-    sylvan_fprintdot_nc(stdout, bdd);
+    return CALL(sylvan_collect_do, bdd, vars, cb, context, NULL);
 }
 
 /**
@@ -2574,7 +2034,7 @@ sylvan_printdot_nc(syBDD bdd)
  */
 
 struct sylvan_ser {
-    syBDD bdd;
+    SyBDD bdd;
     size_t assigned;
 };
 
@@ -2604,12 +2064,12 @@ static avl_node_t *sylvan_ser_reversed_set = NULL;
 static size_t sylvan_ser_counter = 1;
 static size_t sylvan_ser_done = 0;
 
-// Given a BDD, assign unique numbers to all nodes
+// Given a SyBDD, assign unique numbers to all nodes
 static size_t
-sylvan_serialize_assign_rec(syBDD bdd)
+sylvan_serialize_assign_rec(SyBDD bdd)
 {
     if (sylvan_isnode(bdd)) {
-        bddnode_t n = GETNODE(bdd);
+        bddnode_t n = MTBDD_GETNODE(bdd);
 
         struct sylvan_ser s, *ss;
         s.bdd = BDD_STRIPMARK(bdd);
@@ -2637,7 +2097,7 @@ sylvan_serialize_assign_rec(syBDD bdd)
 }
 
 size_t
-sylvan_serialize_add(syBDD bdd)
+sylvan_serialize_add(SyBDD bdd)
 {
     return BDD_TRANSFERMARK(bdd, sylvan_serialize_assign_rec(bdd));
 }
@@ -2652,7 +2112,7 @@ sylvan_serialize_reset()
 }
 
 size_t
-sylvan_serialize_get(syBDD bdd)
+sylvan_serialize_get(SyBDD bdd)
 {
     if (!sylvan_isnode(bdd)) return bdd;
     struct sylvan_ser s, *ss;
@@ -2662,7 +2122,7 @@ sylvan_serialize_get(syBDD bdd)
     return BDD_TRANSFERMARK(bdd, ss->assigned);
 }
 
-syBDD
+SyBDD
 sylvan_serialize_get_reversed(size_t value)
 {
     if (!sylvan_isnode(value)) return value;
@@ -2681,8 +2141,8 @@ sylvan_serialize_totext(FILE *out)
     struct sylvan_ser *s;
 
     while ((s=sylvan_ser_reversed_iter_next(it))) {
-        syBDD bdd = s->bdd;
-        bddnode_t n = GETNODE(bdd);
+        SyBDD bdd = s->bdd;
+        bddnode_t n = MTBDD_GETNODE(bdd);
         fprintf(out, "(%zu,%u,%zu,%zu,%u),", s->assigned,
                                              bddnode_getvariable(n),
                                              (size_t)bddnode_getlow(n),
@@ -2717,7 +2177,7 @@ sylvan_serialize_tofile(FILE *out)
         index++;
         assert(s->assigned == index);
 
-        bddnode_t n = GETNODE(s->bdd);
+        bddnode_t n = MTBDD_GETNODE(s->bdd);
 
         struct bddnode node;
         bddnode_makenode(&node, bddnode_getvariable(n), sylvan_serialize_get(bddnode_getlow(n)), sylvan_serialize_get(bddnode_gethigh(n)));
@@ -2747,8 +2207,8 @@ sylvan_serialize_fromfile(FILE *in)
             exit(-1);
         }
 
-        syBDD low = sylvan_serialize_get_reversed(bddnode_getlow(&node));
-        syBDD high = sylvan_serialize_get_reversed(bddnode_gethigh(&node));
+        SyBDD low = sylvan_serialize_get_reversed(bddnode_getlow(&node));
+        SyBDD high = sylvan_serialize_get_reversed(bddnode_gethigh(&node));
 
         struct sylvan_ser s;
         s.bdd = sylvan_makenode(bddnode_getvariable(&node), low, high);
@@ -2759,96 +2219,3 @@ sylvan_serialize_fromfile(FILE *in)
     }
 }
 
-/**
- * Generate SHA2 structural hashes.
- * Hashes are independent of location.
- * Mainly useful for debugging purposes.
- */
-static void
-sylvan_sha2_rec(syBDD bdd, SHA256_CTX *ctx)
-{
-    if (bdd == sylvan_true || bdd == sylvan_false) {
-        SHA256_Update(ctx, (void*)&bdd, sizeof(syBDD));
-        return;
-    }
-
-    bddnode_t node = GETNODE(bdd);
-    if (bddnode_getmark(node) == 0) {
-        bddnode_setmark(node, 1);
-        uint32_t level = bddnode_getvariable(node);
-        if (BDD_STRIPMARK(bddnode_gethigh(node))) level |= 0x80000000;
-        SHA256_Update(ctx, (void*)&level, sizeof(uint32_t));
-        sylvan_sha2_rec(bddnode_gethigh(node), ctx);
-        sylvan_sha2_rec(bddnode_getlow(node), ctx);
-    }
-}
-
-void
-sylvan_printsha(syBDD bdd)
-{
-    sylvan_fprintsha(stdout, bdd);
-}
-
-void
-sylvan_fprintsha(FILE *f, syBDD bdd)
-{
-    char buf[80];
-    sylvan_getsha(bdd, buf);
-    fprintf(f, "%s", buf);
-}
-
-void
-sylvan_getsha(syBDD bdd, char *target)
-{
-    SHA256_CTX ctx;
-    SHA256_Init(&ctx);
-    sylvan_sha2_rec(bdd, &ctx);
-    if (bdd != sylvan_true && bdd != sylvan_false) sylvan_unmark_rec(GETNODE(bdd));
-    SHA256_End(&ctx, target);
-}
-
-/**
- * Debug tool to check that a BDD is properly ordered.
- * Also that every BDD node is marked 'in-use' in the hash table.
- */
-TASK_2(int, sylvan_test_isbdd_rec, syBDD, bdd, BDDVAR, parent_var)
-{
-    if (bdd == sylvan_true || bdd == sylvan_false) return 1;
-    assert(llmsset_is_marked(nodes, BDD_STRIPMARK(bdd)));
-
-    sylvan_stats_count(BDD_ISBDD);
-
-    uint64_t result;
-    if (cache_get3(CACHE_BDD_ISBDD, bdd, 0, 0, &result)) {
-        sylvan_stats_count(BDD_ISBDD_CACHED);
-        return result;
-    }
-
-    bddnode_t n = GETNODE(bdd);
-    BDDVAR var = bddnode_getvariable(n);
-    if (var <= parent_var) {
-        result = 0;
-    } else {
-        SPAWN(sylvan_test_isbdd_rec, node_low(bdd, n), var);
-        result = (uint64_t)CALL(sylvan_test_isbdd_rec, node_high(bdd, n), var);
-        if (!SYNC(sylvan_test_isbdd_rec)) result = 0;
-    }
-
-    if (cache_put3(CACHE_BDD_ISBDD, bdd, 0, 0, result)) sylvan_stats_count(BDD_ISBDD_CACHEDPUT);
-    return result;
-}
-
-TASK_IMPL_1(int, sylvan_test_isbdd, syBDD, bdd)
-{
-    if (bdd == sylvan_true) return 1;
-    if (bdd == sylvan_false) return 1;
-
-    assert(llmsset_is_marked(nodes, BDD_STRIPMARK(bdd)));
-
-    bddnode_t n = GETNODE(bdd);
-    BDDVAR var = bddnode_getvariable(n);
-    SPAWN(sylvan_test_isbdd_rec, node_low(bdd, n), var);
-    int result = CALL(sylvan_test_isbdd_rec, node_high(bdd, n), var);
-    if (!SYNC(sylvan_test_isbdd_rec)) result = 0;
-    return result;
-}
diff --git a/sylvan_bdd.h b/sylvan_bdd.h
old mode 100644
new mode 100755
index ee36526ce3c3d4d473482676e3799d8a21b9e033..e8d935ea5423472206ee0998751a4d41a334bb4c
--- a/sylvan_bdd.h
+++ b/sylvan_bdd.h
@@ -1,5 +1,6 @@
 /*
- * Copyright 2011-2015 Formal Methods and Tools, University of Twente
+ * 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.
@@ -16,86 +17,57 @@
 
 /* Do not include this file directly. Instead, include sylvan.h */
 
-#include <tls.h>
-
 #ifndef SYLVAN_BDD_H
 #define SYLVAN_BDD_H
 
-typedef uint64_t syBDD;       // low 40 bits used for index, highest bit for complement, rest 0
-// BDDSET uses the BDD node hash table. A BDDSET is an ordered BDD.
-typedef uint64_t BDDSET;    // encodes a set of variables (e.g. for exists etc.)
-// BDDMAP also uses the BDD node hash table. A BDDMAP is *not* an ordered BDD.
-typedef uint64_t BDDMAP;    // encodes a function of variable->BDD (e.g. for substitute)
-typedef uint32_t BDDVAR;    // low 24 bits only
-
 #ifdef __cplusplus
 extern "C" {
 #endif /* __cplusplus */
 
+/* For strictly non-MT BDDs */
+static inline int
+sylvan_isconst(MTBDD bdd)
+{
+    return bdd == mtbdd_true || bdd == mtbdd_false ? 1 : 0;
+}
 
-
-#define sylvan_complement   ((uint64_t)0x8000000000000000)
-#define sylvan_false        ((syBDD)0x0000000000000000)
-#define sylvan_true         (sylvan_false|sylvan_complement)
-#define sylvan_invalid      ((syBDD)0x7fffffffffffffff)
-
-#define sylvan_isconst(bdd) (bdd == sylvan_true || bdd == sylvan_false)
-#define sylvan_isnode(bdd)  (bdd != sylvan_true && bdd != sylvan_false)
+static inline int
+sylvan_isnode(MTBDD bdd)
+{
+    return bdd != mtbdd_true && bdd != mtbdd_false ? 1 : 0;
+}
 
 /**
- * Initialize BDD functionality.
- *
- * Granularity (BDD only) determines usage of operation cache. Smallest value is 1: use the operation cache always.
- * Higher values mean that the cache is used less often. Variables are grouped such that
- * the cache is used when going to the next group, i.e., with granularity=3, variables [0,1,2] are in the
- * first group, [3,4,5] in the next, etc. Then no caching occur between 0->1, 1->2, 0->2. Caching occurs
- * on 0->3, 1->4, 2->3, etc.
+ * Granularity (SyBDD only) determines usage of operation cache.
+ * The smallest value is 1: use the operation cache always.
+ * Higher values mean that the cache is used less often. Variables are grouped
+ * such that the cache is used when going to the next group, i.e., with
+ * granularity=3, variables [0,1,2] are in the first group, [3,4,5] in the next, etc.
+ * Then no caching occur between 0->1, 1->2, 0->2. Caching occurs on 0->3, 1->4, 2->3, etc.
  *
- * A reasonable default is a granularity of 4-16, strongly depending on the structure of the BDDs.
+ * The appropriate value depends on the number of variables and the structure of
+ * the decision diagrams. When in doubt, choose a low value (1-5). The performance
+ * gain can be around 0-10%, so it is not extremely important.
  */
-void sylvan_init_bdd(int granularity);
-
-/* Create a BDD representing just <var> or the negation of <var> */
-syBDD sylvan_ithvar(BDDVAR var);
-static inline syBDD sylvan_nithvar(syBDD var) { return sylvan_ithvar(var) ^ sylvan_complement; }
-
-/* Retrieve the <var> of the BDD node <bdd> */
-BDDVAR sylvan_var(syBDD bdd);
-
-/* Follow <low> and <high> edges */
-syBDD sylvan_low(syBDD bdd);
-syBDD sylvan_high(syBDD bdd);
-
-/* Add or remove external reference to BDD */
-syBDD sylvan_ref(syBDD a);
-void sylvan_deref(syBDD a);
-
-/* For use in custom mark functions */
-VOID_TASK_DECL_1(sylvan_gc_mark_rec, syBDD);
-#define sylvan_gc_mark_rec(mdd) CALL(sylvan_gc_mark_rec, mdd)
-
-/* Return the number of external references */
-size_t sylvan_count_refs();
-
-/* Add or remove BDD pointers to protect (indirect external references) */
-void sylvan_protect(syBDD* ptr);
-void sylvan_unprotect(syBDD* ptr);
-
-/* Return the number of protected BDD pointers */
-size_t sylvan_count_protected();
+void sylvan_set_granularity(int granularity);
+int sylvan_get_granularity(void);
 
-/* Mark BDD for "notify on dead" */
-#define sylvan_notify_ondead(bdd) llmsset_notify_ondead(nodes, bdd&~sylvan_complement)
+/*
+ * Unary, binary and if-then-else operations.
+ * These operations are all implemented by NOT, AND and XOR.
+ */
+static inline SyBDD
+sylvan_not(SyBDD a)
+{
+    return a ^ sylvan_complement;
+}
 
-/* Unary, binary and if-then-else operations */
-#define sylvan_not(a) (((syBDD)a)^sylvan_complement)
-TASK_DECL_4(syBDD, sylvan_ite, syBDD, syBDD, syBDD, BDDVAR);
+TASK_DECL_4(SyBDD, sylvan_ite, SyBDD, SyBDD, SyBDD, BDDVAR);
 #define sylvan_ite(a,b,c) (CALL(sylvan_ite,a,b,c,0))
-TASK_DECL_3(syBDD, sylvan_and, syBDD, syBDD, BDDVAR);
+TASK_DECL_3(SyBDD, sylvan_and, SyBDD, SyBDD, BDDVAR);
 #define sylvan_and(a,b) (CALL(sylvan_and,a,b,0))
-TASK_DECL_3(syBDD, sylvan_xor, syBDD, syBDD, BDDVAR);
+TASK_DECL_3(SyBDD, sylvan_xor, SyBDD, SyBDD, BDDVAR);
 #define sylvan_xor(a,b) (CALL(sylvan_xor,a,b,0))
-/* Do not use nested calls for xor/equiv parameter b! */
 #define sylvan_equiv(a,b) sylvan_not(sylvan_xor(a,b))
 #define sylvan_or(a,b) sylvan_not(sylvan_and(sylvan_not(a),sylvan_not(b)))
 #define sylvan_nand(a,b) sylvan_not(sylvan_and(a,b))
@@ -106,18 +78,38 @@ TASK_DECL_3(syBDD, sylvan_xor, syBDD, syBDD, BDDVAR);
 #define sylvan_diff(a,b) sylvan_and(a,sylvan_not(b))
 #define sylvan_less(a,b) sylvan_and(sylvan_not(a),b)
 
-/* Existential and Universal quantifiers */
-TASK_DECL_3(syBDD, sylvan_exists, syBDD, syBDD, BDDVAR);
+/* Create a SyBDD representing just <var> or the negation of <var> */
+static inline SyBDD
+sylvan_nithvar(uint32_t var)
+{
+    return sylvan_not(sylvan_ithvar(var));
+}
+
+/**
+ * Existential and universal quantification.
+ */
+TASK_DECL_3(SyBDD, sylvan_exists, SyBDD, SyBDD, BDDVAR);
 #define sylvan_exists(a, vars) (CALL(sylvan_exists, a, vars, 0))
 #define sylvan_forall(a, vars) (sylvan_not(CALL(sylvan_exists, sylvan_not(a), vars, 0)))
 
 /**
- * Compute \exists v: A(...) \and B(...)
- * Parameter vars is the cube (conjunction) of all v variables.
+ * Projection. (Same as existential quantification, but <vars> contains variables to keep.
+ */
+TASK_DECL_2(SyBDD, sylvan_project, SyBDD, SyBDD);
+#define sylvan_project(a, vars) CALL(sylvan_project, a, vars)
+
+/**
+ * Compute \exists <vars>: <a> \and <b>
  */
-TASK_DECL_4(syBDD, sylvan_and_exists, syBDD, syBDD, BDDSET, BDDVAR);
+TASK_DECL_4(SyBDD, sylvan_and_exists, SyBDD, SyBDD, BDDSET, BDDVAR);
 #define sylvan_and_exists(a,b,vars) CALL(sylvan_and_exists,a,b,vars,0)
 
+/**
+ * Compute and_exists, but as a projection (only keep given variables)
+ */
+TASK_DECL_3(SyBDD, sylvan_and_project, SyBDD, SyBDD, BDDSET);
+#define sylvan_and_project(a,b,vars) CALL(sylvan_and_project,a,b,vars)
+
 /**
  * Compute R(s,t) = \exists x: A(s,x) \and B(x,t)
  *      or R(s)   = \exists x: A(s,x) \and B(x)
@@ -130,7 +122,7 @@ TASK_DECL_4(syBDD, sylvan_and_exists, syBDD, syBDD, BDDSET, BDDVAR);
  * Use this function to concatenate two relations   --> -->
  * or to take the 'previous' of a set               -->  S
  */
-TASK_DECL_4(syBDD, sylvan_relprev, syBDD, syBDD, BDDSET, BDDVAR);
+TASK_DECL_4(SyBDD, sylvan_relprev, SyBDD, SyBDD, BDDSET, BDDVAR);
 #define sylvan_relprev(a,b,vars) CALL(sylvan_relprev,a,b,vars,0)
 
 /**
@@ -144,139 +136,66 @@ TASK_DECL_4(syBDD, sylvan_relprev, syBDD, syBDD, BDDSET, BDDVAR);
  *
  * Use this function to take the 'next' of a set     S  -->
  */
-TASK_DECL_4(syBDD, sylvan_relnext, syBDD, syBDD, BDDSET, BDDVAR);
+TASK_DECL_4(SyBDD, sylvan_relnext, SyBDD, SyBDD, BDDSET, BDDVAR);
 #define sylvan_relnext(a,b,vars) CALL(sylvan_relnext,a,b,vars,0)
 
 /**
- * Computes the transitive closure by traversing the BDD recursively.
+ * Computes the transitive closure by traversing the SyBDD recursively.
  * See Y. Matsunaga, P. C. McGeer, R. K. Brayton
  *     On Computing the Transitive Closure of a State Transition Relation
  *     30th ACM Design Automation Conference, 1993.
  *
- * The input BDD must be a transition relation that only has levels of s,t
+ * The input SyBDD must be a transition relation that only has levels of s,t
  * with s,t interleaved with s even and t odd, i.e.
  * s level 0,2,4 matches with t level 1,3,5 and so forth.
  */
-TASK_DECL_2(syBDD, sylvan_closure, syBDD, BDDVAR);
+TASK_DECL_2(SyBDD, sylvan_closure, SyBDD, BDDVAR);
 #define sylvan_closure(a) CALL(sylvan_closure,a,0);
 
 /**
- * Calculate a@b (a constrain b), such that (b -> a@b) = (b -> a)
+ * Compute f@c (f constrain c), such that f and f@c are the same when c is true
+ * The SyBDD c is also called the "care function"
  * Special cases:
- *   - a@0 = 0
- *   - a@1 = f
- *   - 0@b = 0
- *   - 1@b = 1
- *   - a@a = 1
- *   - a@not(a) = 0
- */
-TASK_DECL_3(syBDD, sylvan_constrain, syBDD, syBDD, BDDVAR);
-#define sylvan_constrain(f,c) (CALL(sylvan_constrain, (f), (c), 0))
-
-TASK_DECL_3(syBDD, sylvan_restrict, syBDD, syBDD, BDDVAR);
-#define sylvan_restrict(f,c) (CALL(sylvan_restrict, (f), (c), 0))
-
-TASK_DECL_3(syBDD, sylvan_compose, syBDD, BDDMAP, BDDVAR);
-#define sylvan_compose(f,m) (CALL(sylvan_compose, (f), (m), 0))
-
-/**
- * Calculate the support of a BDD.
- * A variable v is in the support of a Boolean function f iff f[v<-0] != f[v<-1]
- * It is also the set of all variables in the BDD nodes of the BDD.
+ *   - f@0 = 0
+ *   - f@1 = f
+ *   - 0@c = 0
+ *   - 1@c = 1
+ *   - f@f = 1
+ *   - f@not(f) = 0
  */
-TASK_DECL_1(syBDD, sylvan_support, syBDD);
-#define sylvan_support(bdd) (CALL(sylvan_support, bdd))
+TASK_DECL_3(SyBDD, sylvan_constrain, SyBDD, SyBDD, BDDVAR);
+#define sylvan_constrain(f,c) (CALL(sylvan_constrain, f, c, 0))
 
 /**
- * A set of BDD variables is a cube (conjunction) of variables in their positive form.
- * Note 2015-06-10: This used to be a union (disjunction) of variables in their positive form.
+ * Compute restrict f@c, which uses a heuristic to try and minimize a SyBDD f with respect to a care function c
+ * Similar to constrain, but avoids introducing variables from c into f.
  */
-// empty bddset
-#define sylvan_set_empty() sylvan_true
-#define sylvan_set_isempty(set) (set == sylvan_true)
-// add variables to the bddset
-#define sylvan_set_add(set, var) sylvan_and(set, sylvan_ithvar(var))
-#define sylvan_set_addall(set, set_to_add) sylvan_and(set, set_to_add)
-// remove variables from the bddset
-#define sylvan_set_remove(set, var) sylvan_exists(set, var)
-#define sylvan_set_removeall(set, set_to_remove) sylvan_exists(set, set_to_remove)
-// iterate through all variables
-#define sylvan_set_var(set) (sylvan_var(set))
-#define sylvan_set_next(set) (sylvan_high(set))
-int sylvan_set_in(BDDSET set, BDDVAR var);
-size_t sylvan_set_count(BDDSET set);
-void sylvan_set_toarray(BDDSET set, BDDVAR *arr);
-// variables in arr should be ordered
-TASK_DECL_2(BDDSET, sylvan_set_fromarray, BDDVAR*, size_t);
-#define sylvan_set_fromarray(arr, length) ( CALL(sylvan_set_fromarray, arr, length) )
-void sylvan_test_isset(BDDSET set);
+TASK_DECL_3(SyBDD, sylvan_restrict, SyBDD, SyBDD, BDDVAR);
+#define sylvan_restrict(f,c) (CALL(sylvan_restrict, f, c, 0))
 
 /**
- * BDDMAP maps BDDVAR-->BDD, implemented using BDD nodes.
- * Based on disjunction of variables, but with high edges to BDDs instead of True terminals.
+ * Function composition.
+ * For each node with variable <key> which has a <key,value> pair in <map>,
+ * replace the node by the result of sylvan_ite(<value>, <low>, <high>).
  */
-// empty bddmap
-static inline BDDMAP sylvan_map_empty() { return sylvan_false; }
-static inline int sylvan_map_isempty(BDDMAP map) { return map == sylvan_false ? 1 : 0; }
-// add key-value pairs to the bddmap
-BDDMAP sylvan_map_add(BDDMAP map, BDDVAR key, syBDD value);
-BDDMAP sylvan_map_addall(BDDMAP map_1, BDDMAP map_2);
-// remove key-value pairs from the bddmap
-BDDMAP sylvan_map_remove(BDDMAP map, BDDVAR key);
-BDDMAP sylvan_map_removeall(BDDMAP map, BDDSET toremove);
-// iterate through all pairs
-static inline BDDVAR sylvan_map_key(BDDMAP map) { return sylvan_var(map); }
-static inline syBDD sylvan_map_value(BDDMAP map) { return sylvan_high(map); }
-static inline BDDMAP sylvan_map_next(BDDMAP map) { return sylvan_low(map); }
-// is a key in the map
-int sylvan_map_in(BDDMAP map, BDDVAR key);
-// count number of keys
-size_t sylvan_map_count(BDDMAP map);
-// convert a BDDSET (cube of variables) to a map, with all variables pointing on the value
-BDDMAP sylvan_set_to_map(BDDSET set, syBDD value);
-
-/**
- * Node creation primitive.
- * Careful: does not check ordering!
- * Preferably only use when debugging!
- */
-syBDD sylvan_makenode(BDDVAR level, syBDD low, syBDD high);
-
-/**
- * Write a DOT representation of a BDD
- */
-void sylvan_printdot(syBDD bdd);
-void sylvan_fprintdot(FILE *out, syBDD bdd);
-
-/**
- * Write a DOT representation of a BDD.
- * This variant does not print complement edges.
- */
-void sylvan_printdot_nc(syBDD bdd);
-void sylvan_fprintdot_nc(FILE *out, syBDD bdd);
-
-void sylvan_print(syBDD bdd);
-void sylvan_fprint(FILE *f, syBDD bdd);
-
-void sylvan_printsha(syBDD bdd);
-void sylvan_fprintsha(FILE *f, syBDD bdd);
-void sylvan_getsha(syBDD bdd, char *target); // target must be at least 65 bytes...
+TASK_DECL_3(SyBDD, sylvan_compose, SyBDD, BDDMAP, BDDVAR);
+#define sylvan_compose(f,m) (CALL(sylvan_compose, (f), (m), 0))
 
 /**
  * Calculate number of satisfying variable assignments.
- * The set of variables must be >= the support of the BDD.
+ * The set of variables must be >= the support of the SyBDD.
  */
 
-TASK_DECL_3(double, sylvan_satcount, syBDD, BDDSET, BDDVAR);
+TASK_DECL_3(double, sylvan_satcount, SyBDD, BDDSET, BDDVAR);
 #define sylvan_satcount(bdd, variables) CALL(sylvan_satcount, bdd, variables, 0)
 
 /**
- * Create a BDD cube representing the conjunction of variables in their positive or negative
+ * Create a SyBDD cube representing the conjunction of variables in their positive or negative
  * form depending on whether the cube[idx] equals 0 (negative), 1 (positive) or 2 (any).
  * CHANGED 2014/09/19: vars is now a BDDSET (ordered!)
  */
-syBDD sylvan_cube(BDDSET variables, uint8_t *cube);
-TASK_DECL_3(syBDD, sylvan_union_cube, syBDD, BDDSET, uint8_t*);
+SyBDD sylvan_cube(BDDSET variables, uint8_t *cube);
+TASK_DECL_3(SyBDD, sylvan_union_cube, SyBDD, BDDSET, uint8_t*);
 #define sylvan_union_cube(bdd, variables, cube) CALL(sylvan_union_cube, bdd, variables, cube)
 
 /**
@@ -291,55 +210,50 @@ TASK_DECL_3(syBDD, sylvan_union_cube, syBDD, BDDSET, uint8_t*);
  *
  * Returns 1 when succesful, or 0 when no assignment is found (i.e. bdd==sylvan_false).
  */
-int sylvan_sat_one(syBDD bdd, BDDSET variables, uint8_t* str);
+int sylvan_sat_one(SyBDD bdd, BDDSET variables, uint8_t* str);
 
 /**
  * Pick one satisfying variable assignment randomly from the given <bdd>.
  * Functionally equivalent to performing sylvan_cube on the result of sylvan_sat_one.
  * For the result: sylvan_and(res, bdd) = res.
  */
-syBDD sylvan_sat_one_bdd(syBDD bdd);
+SyBDD sylvan_sat_one_bdd(SyBDD bdd);
 #define sylvan_pick_cube sylvan_sat_one_bdd
 
-syBDD sylvan_sat_single(syBDD bdd, BDDSET vars);
+SyBDD sylvan_sat_single(SyBDD bdd, BDDSET vars);
 #define sylvan_pick_single_cube sylvan_sat_single
 
 /**
  * Enumerate all satisfying variable assignments from the given <bdd> using variables <vars>.
- * Calls <cb> with four parameters: a user-supplied context, the array of BDD variables in <vars>,
+ * Calls <cb> with four parameters: a user-supplied context, the array of SyBDD variables in <vars>,
  * the cube (array of values 0 and 1 for each variables in <vars>) and the length of the two arrays.
  */
 LACE_TYPEDEF_CB(void, enum_cb, void*, BDDVAR*, uint8_t*, int);
-VOID_TASK_DECL_4(sylvan_enum, syBDD, BDDSET, enum_cb, void*);
+VOID_TASK_DECL_4(sylvan_enum, SyBDD, BDDSET, enum_cb, void*);
 #define sylvan_enum(bdd, vars, cb, context) CALL(sylvan_enum, bdd, vars, cb, context)
-VOID_TASK_DECL_4(sylvan_enum_par, syBDD, BDDSET, enum_cb, void*);
+VOID_TASK_DECL_4(sylvan_enum_par, SyBDD, BDDSET, enum_cb, void*);
 #define sylvan_enum_par(bdd, vars, cb, context) CALL(sylvan_enum_par, bdd, vars, cb, context)
 
 /**
  * Enumerate all satisfyable variable assignments of the given <bdd> using variables <vars>.
  * Calls <cb> with two parameters: a user-supplied context and the cube (array of
  * values 0 and 1 for each variable in <vars>).
- * The BDD that <cb> returns is pair-wise merged (using or) and returned.
+ * The SyBDD that <cb> returns is pair-wise merged (using or) and returned.
  */
-LACE_TYPEDEF_CB(syBDD, sylvan_collect_cb, void*, uint8_t*);
-TASK_DECL_4(syBDD, sylvan_collect, syBDD, BDDSET, sylvan_collect_cb, void*);
+LACE_TYPEDEF_CB(SyBDD, sylvan_collect_cb, void*, uint8_t*);
+TASK_DECL_4(SyBDD, sylvan_collect, SyBDD, BDDSET, sylvan_collect_cb, void*);
 #define sylvan_collect(bdd, vars, cb, context) CALL(sylvan_collect, bdd, vars, cb, context)
 
 /**
- * Compute the number of distinct paths to sylvan_true in the BDD
+ * Compute the number of distinct paths to sylvan_true in the SyBDD
  */
-TASK_DECL_2(double, sylvan_pathcount, syBDD, BDDVAR);
+TASK_DECL_2(double, sylvan_pathcount, SyBDD, BDDVAR);
 #define sylvan_pathcount(bdd) (CALL(sylvan_pathcount, bdd, 0))
 
-/**
- * Compute the number of BDD nodes in the BDD
- */
-size_t sylvan_nodecount(syBDD a);
-
 /**
  * SAVING:
- * use sylvan_serialize_add on every BDD you want to store
- * use sylvan_serialize_get to retrieve the key of every stored BDD
+ * use sylvan_serialize_add on every SyBDD you want to store
+ * use sylvan_serialize_get to retrieve the key of every stored SyBDD
  * use sylvan_serialize_tofile
  *
  * LOADING:
@@ -350,73 +264,28 @@ size_t sylvan_nodecount(syBDD a);
  * use sylvan_serialize_reset to free all allocated structures
  * use sylvan_serialize_totext to write a textual list of tuples of all BDDs.
  *         format: [(<key>,<level>,<key_low>,<key_high>,<complement_high>),...]
- *
- * for the old sylvan_print functions, use sylvan_serialize_totext
  */
-size_t sylvan_serialize_add(syBDD bdd);
-size_t sylvan_serialize_get(syBDD bdd);
-syBDD sylvan_serialize_get_reversed(size_t value);
-void sylvan_serialize_reset();
+size_t sylvan_serialize_add(SyBDD bdd);
+size_t sylvan_serialize_get(SyBDD bdd);
+SyBDD sylvan_serialize_get_reversed(size_t value);
+void sylvan_serialize_reset(void);
 void sylvan_serialize_totext(FILE *out);
 void sylvan_serialize_tofile(FILE *out);
 void sylvan_serialize_fromfile(FILE *in);
 
-/**
- * For debugging
- * if (part of) the BDD is not 'marked' in the nodes table, assertion fails
- * if the BDD is not ordered, returns 0
- * if nicely ordered, returns 1
- */
-TASK_DECL_1(int, sylvan_test_isbdd, syBDD);
-#define sylvan_test_isbdd(bdd) CALL(sylvan_test_isbdd, bdd)
-
-/* Infrastructure for internal markings */
-typedef struct bdd_refs_internal
-{
-    size_t r_size, r_count;
-    size_t s_size, s_count;
-    syBDD *results;
-    Task **spawns;
-} *bdd_refs_internal_t;
-
-extern DECLARE_THREAD_LOCAL(bdd_refs_key, bdd_refs_internal_t);
-
-static inline syBDD
-bdd_refs_push(syBDD bdd)
-{
-    LOCALIZE_THREAD_LOCAL(bdd_refs_key, bdd_refs_internal_t);
-    if (bdd_refs_key->r_count >= bdd_refs_key->r_size) {
-        bdd_refs_key->r_size *= 2;
-        bdd_refs_key->results = (syBDD*)realloc(bdd_refs_key->results, sizeof(syBDD) * bdd_refs_key->r_size);
-    }
-    bdd_refs_key->results[bdd_refs_key->r_count++] = bdd;
-    return bdd;
-}
-
-static inline void
-bdd_refs_pop(int amount)
-{
-    LOCALIZE_THREAD_LOCAL(bdd_refs_key, bdd_refs_internal_t);
-    bdd_refs_key->r_count-=amount;
-}
-
-static inline void
-bdd_refs_spawn(Task *t)
+static void __attribute__((unused))
+sylvan_fprint(FILE *f, SyBDD bdd)
 {
-    LOCALIZE_THREAD_LOCAL(bdd_refs_key, bdd_refs_internal_t);
-    if (bdd_refs_key->s_count >= bdd_refs_key->s_size) {
-        bdd_refs_key->s_size *= 2;
-        bdd_refs_key->spawns = (Task**)realloc(bdd_refs_key->spawns, sizeof(Task*) * bdd_refs_key->s_size);
-    }
-    bdd_refs_key->spawns[bdd_refs_key->s_count++] = t;
+    sylvan_serialize_reset();
+    size_t v = sylvan_serialize_add(bdd);
+    fprintf(f, "%s%zu,", bdd&sylvan_complement?"!":"", v);
+    sylvan_serialize_totext(f);
 }
 
-static inline syBDD
-bdd_refs_sync(syBDD result)
+static void __attribute__((unused))
+sylvan_print(SyBDD bdd)
 {
-    LOCALIZE_THREAD_LOCAL(bdd_refs_key, bdd_refs_internal_t);
-    bdd_refs_key->s_count--;
-    return result;
+    return sylvan_fprint(stdout, bdd);
 }
 
 #ifdef __cplusplus
diff --git a/sylvan_cache.c b/sylvan_cache.c
old mode 100644
new mode 100755
index 39dadac6bfaa34a26507c13aebc10b63ea52ee7c..8a39d751a80856ec94bc32dc91b60c68ca5c9d8a
--- a/sylvan_cache.c
+++ b/sylvan_cache.c
@@ -1,5 +1,6 @@
 /*
- * Copyright 2011-2015 Formal Methods and Tools, University of Twente
+ * 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.
@@ -14,19 +15,20 @@
  * limitations under the License.
  */
 
+#include <sylvan_int.h>
+
 #include <errno.h>  // for errno
-#include <stdio.h>  // for fprintf
-#include <stdint.h> // for uint32_t etc
-#include <stdlib.h> // for exit
 #include <string.h> // for strerror
 #include <sys/mman.h> // for mmap
 
-#include <sylvan_cache.h>
-
 #ifndef MAP_ANONYMOUS
 #define MAP_ANONYMOUS MAP_ANON
 #endif
 
+#ifndef CACHE_MASK
+#define CACHE_MASK 1
+#endif
+
 #ifndef compiler_barrier
 #define compiler_barrier() { asm volatile("" ::: "memory"); }
 #endif
@@ -43,6 +45,18 @@
  * Therefore, size 2^N = 36*(2^N) bytes.
  */
 
+struct __attribute__((packed)) cache6_entry {
+    uint64_t            a;
+    uint64_t            b;
+    uint64_t            c;
+    uint64_t            res;
+    uint64_t            d;
+    uint64_t            e;
+    uint64_t            f;
+    uint64_t            res2;
+};
+typedef struct cache6_entry *cache6_entry_t;
+
 struct __attribute__((packed)) cache_entry {
     uint64_t            a;
     uint64_t            b;
@@ -83,6 +97,84 @@ cache_hash(uint64_t a, uint64_t b, uint64_t c)
     return hash;
 }
 
+static uint64_t
+cache_hash6(uint64_t a, uint64_t b, uint64_t c, uint64_t d, uint64_t e, uint64_t f)
+{
+    const uint64_t prime = 1099511628211;
+    uint64_t hash = 14695981039346656037LLU;
+    hash = (hash ^ (a>>32));
+    hash = (hash ^ a) * prime;
+    hash = (hash ^ b) * prime;
+    hash = (hash ^ c) * prime;
+    hash = (hash ^ d) * prime;
+    hash = (hash ^ e) * prime;
+    hash = (hash ^ f) * prime;
+    return hash;
+}
+
+int
+cache_get6(uint64_t a, uint64_t b, uint64_t c, uint64_t d, uint64_t e, uint64_t f, uint64_t *res1, uint64_t *res2)
+{
+    const uint64_t hash = cache_hash6(a, b, c, d, e, f);
+#if CACHE_MASK
+    volatile uint64_t *s_bucket = (uint64_t*)cache_status + (hash & cache_mask)/2;
+    cache6_entry_t bucket = (cache6_entry_t)cache_table + (hash & cache_mask)/2;
+#else
+    volatile uint64_t *s_bucket = (uint64_t*)cache_status + (hash % cache_size)/2;
+    cache6_entry_t bucket = (cache6_entry_t)cache_table + (hash % cache_size)/2;
+#endif
+    const uint64_t s = *s_bucket;
+    compiler_barrier();
+    // abort if locked or second part of 2-part entry or if different hash
+    uint64_t x = ((hash>>32) & 0x7fff0000) | 0x04000000;
+    x = x | (x<<32);
+    if ((s & 0xffff0000ffff0000) != x) return 0;
+    // abort if key different
+    if (bucket->a != a || bucket->b != b || bucket->c != c) return 0;
+    if (bucket->d != d || bucket->e != e || bucket->f != f) return 0;
+    *res1 = bucket->res;
+    if (res2) *res2 = bucket->res2;
+    compiler_barrier();
+    // abort if status field changed after compiler_barrier()
+    return *s_bucket == s ? 1 : 0;
+}
+
+int
+cache_put6(uint64_t a, uint64_t b, uint64_t c, uint64_t d, uint64_t e, uint64_t f, uint64_t res1, uint64_t res2)
+{
+    const uint64_t hash = cache_hash6(a, b, c, d, e, f);
+#if CACHE_MASK
+    volatile uint64_t *s_bucket = (uint64_t*)cache_status + (hash & cache_mask)/2;
+    cache6_entry_t bucket = (cache6_entry_t)cache_table + (hash & cache_mask)/2;
+#else
+    volatile uint64_t *s_bucket = (uint64_t*)cache_status + (hash % cache_size)/2;
+    cache6_entry_t bucket = (cache6_entry_t)cache_table + (hash % cache_size)/2;
+#endif
+    const uint64_t s = *s_bucket;
+    // abort if locked
+    if (s & 0x8000000080000000LL) return 0;
+    // create new
+    uint64_t new_s = ((hash>>32) & 0x7fff0000) | 0x04000000;
+    new_s |= (new_s<<32);
+    new_s |= (((s>>32)+1)&0xffff)<<32;
+    new_s |= (s+1)&0xffff;
+    // use cas to claim bucket
+    if (!cas(s_bucket, s, new_s | 0x8000000080000000LL)) return 0;
+    // cas succesful: write data
+    bucket->a = a;
+    bucket->b = b;
+    bucket->c = c;
+    bucket->d = d;
+    bucket->e = e;
+    bucket->f = f;
+    bucket->res = res1;
+    bucket->res2 = res2;
+    compiler_barrier();
+    // after compiler_barrier(), unlock status field
+    *s_bucket = new_s;
+    return 1;
+}
+
 int
 cache_get(uint64_t a, uint64_t b, uint64_t c, uint64_t *res)
 {
@@ -96,10 +188,10 @@ cache_get(uint64_t a, uint64_t b, uint64_t c, uint64_t *res)
 #endif
     const uint32_t s = *s_bucket;
     compiler_barrier();
-    // abort if locked
-    if (s & 0x80000000) return 0;
+    // abort if locked or if part of a 2-part cache entry
+    if (s & 0xc0000000) return 0;
     // abort if different hash
-    if ((s ^ (hash>>32)) & 0x7fff0000) return 0;
+    if ((s ^ (hash>>32)) & 0x3fff0000) return 0;
     // abort if key different
     if (bucket->a != a || bucket->b != b || bucket->c != c) return 0;
     *res = bucket->res;
@@ -123,7 +215,7 @@ cache_put(uint64_t a, uint64_t b, uint64_t c, uint64_t res)
     // abort if locked
     if (s & 0x80000000) return 0;
     // abort if hash identical -> no: in iscasmc this occasionally causes timeouts?!
-    const uint32_t hash_mask = (hash>>32) & 0x7fff0000;
+    const uint32_t hash_mask = (hash>>32) & 0x3fff0000;
     // if ((s & 0x7fff0000) == hash_mask) return 0;
     // use cas to claim bucket
     const uint32_t new_s = ((s+1) & 0x0000ffff) | hash_mask;
@@ -205,8 +297,7 @@ size_t
 cache_getused()
 {
     size_t result = 0;
-    size_t i;
-    for ( i=0;i<cache_size;i++) {
+    for (size_t i=0;i<cache_size;i++) {
         uint32_t s = cache_status[i];
         if (s & 0x80000000) fprintf(stderr, "cache_getuser: cache in use during cache_getused()\n");
         if (s) result++;
diff --git a/sylvan_cache.h b/sylvan_cache.h
old mode 100644
new mode 100755
index babc74f65ca5703ab648153c07fee4291a158966..ba0bc83ad4567a3f00fab9b778a96da8bc688e2b
--- a/sylvan_cache.h
+++ b/sylvan_cache.h
@@ -1,18 +1,29 @@
-#include <stdint.h> // for uint32_t etc
+/*
+ * 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_config.h>
+/* Do not include this file directly. Instead, include sylvan_int.h */
 
-#ifndef CACHE_H
-#define CACHE_H
+#ifndef SYLVAN_CACHE_H
+#define SYLVAN_CACHE_H
 
 #ifdef __cplusplus
 extern "C" {
 #endif /* __cplusplus */
 
-#ifndef CACHE_MASK
-#define CACHE_MASK 1
-#endif
-
 /**
  * Operation cache
  *
@@ -20,7 +31,7 @@ extern "C" {
  * You should store these operation identifiers as static globals in your implementation .C/.CPP file.
  *
  * For custom operations, just use the following functions:
- * - cache_get3/cache_put3 for any operation with 1 BDD and 2 other values (that can be BDDs)
+ * - cache_get3/cache_put3 for any operation with 1 SyBDD and 2 other values (that can be BDDs)
  *   int success = cache_get3(opid, dd1, value2, value3, &result);
  *   int success = cache_put3(opid, dd1, value2, value3, result);
  * - cache_get4/cache_put4 for any operation with 4 BDDs
@@ -40,10 +51,16 @@ typedef struct cache_entry *cache_entry_t;
 int cache_get(uint64_t a, uint64_t b, uint64_t c, uint64_t *res);
 int cache_put(uint64_t a, uint64_t b, uint64_t c, uint64_t res);
 
+/**
+ * Primitives for cache get/put that use two buckets
+ */
+int cache_get6(uint64_t a, uint64_t b, uint64_t c, uint64_t d, uint64_t e, uint64_t f, uint64_t *res1, uint64_t *res2);
+int cache_put6(uint64_t a, uint64_t b, uint64_t c, uint64_t d, uint64_t e, uint64_t f, uint64_t res1, uint64_t res2);
+
 /**
  * Helper function to get next 'operation id' (during initialization of modules)
  */
-uint64_t cache_next_opid();
+uint64_t cache_next_opid(void);
 
 /**
  * dd must be MTBDD, d2/d3 can be anything
@@ -94,17 +111,17 @@ cache_put4(uint64_t opid, uint64_t dd, uint64_t dd2, uint64_t dd3, uint64_t dd4,
 
 void cache_create(size_t _cache_size, size_t _max_size);
 
-void cache_free();
+void cache_free(void);
 
-void cache_clear();
+void cache_clear(void);
 
 void cache_setsize(size_t size);
 
-size_t cache_getused();
+size_t cache_getused(void);
 
-size_t cache_getsize();
+size_t cache_getsize(void);
 
-size_t cache_getmaxsize();
+size_t cache_getmaxsize(void);
 
 #ifdef __cplusplus
 }
diff --git a/sylvan_common.c b/sylvan_common.c
old mode 100644
new mode 100755
index aa0374a7b7ea0046e8d0888c12b6b6fbc830641c..7aa7d7ed291e8307e0db886264abe2948b6c2e07
--- a/sylvan_common.c
+++ b/sylvan_common.c
@@ -1,5 +1,6 @@
 /*
- * Copyright 2011-2015 Formal Methods and Tools, University of Twente
+ * 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.
@@ -14,166 +15,194 @@
  * limitations under the License.
  */
 
-#include <sylvan_config.h>
-
-#include <sylvan.h>
-#include <sylvan_common.h>
+#include <sylvan_int.h>
 
 #ifndef cas
 #define cas(ptr, old, new) (__sync_bool_compare_and_swap((ptr),(old),(new)))
 #endif
 
 /**
- * Static global variables
+ * Implementation of garbage collection
  */
 
-llmsset_t nodes;
-
 /**
- * Retrieve nodes
+ * Whether garbage collection is enabled or not.
  */
+static int gc_enabled = 1;
 
-llmsset_t
-__sylvan_get_internal_data()
+/**
+ * Enable garbage collection (both automatic and manual).
+ */
+void
+sylvan_gc_enable()
 {
-    return nodes;
+    gc_enabled = 1;
 }
 
 /**
- * Calculate table usage (in parallel)
+ * Disable garbage collection (both automatic and manual).
  */
-VOID_TASK_IMPL_2(sylvan_table_usage, size_t*, filled, size_t*, total)
+void
+sylvan_gc_disable()
 {
-    size_t tot = llmsset_get_size(nodes);
-    if (filled != NULL) *filled = llmsset_count_marked(nodes);
-    if (total != NULL) *total = tot;
+    gc_enabled = 0;
 }
 
 /**
- * Implementation of garbage collection
+ * This variable is used for a cas flag so only one gc runs at one time
  */
-static int gc_enabled = 1;
-static volatile int gc; // variable used in cas switch to ensure only one gc at a time
+static volatile int gc;
 
-struct reg_gc_mark_entry
+/**
+ * Structures for the marking mechanisms
+ */
+typedef struct gc_hook_entry
 {
-    struct reg_gc_mark_entry *next;
-    gc_mark_cb cb;
-    int order;
-};
+    struct gc_hook_entry *next;
+    gc_hook_cb cb;
+} * gc_hook_entry_t;
 
-static struct reg_gc_mark_entry *gc_mark_register = NULL;
+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_add_mark(int order, gc_mark_cb cb)
+sylvan_gc_hook_pregc(gc_hook_cb callback)
 {
-    struct reg_gc_mark_entry *e = (struct reg_gc_mark_entry*)malloc(sizeof(struct reg_gc_mark_entry));
-    e->cb = cb;
-    e->order = order;
-    if (gc_mark_register == NULL || gc_mark_register->order>order) {
-        e->next = gc_mark_register;
-        gc_mark_register = e;
-        return;
-    }
-    struct reg_gc_mark_entry *f = gc_mark_register;
-    for (;;) {
-        if (f->next == NULL) {
-            e->next = NULL;
-            f->next = e;
-            return;
-        }
-        if (f->next->order > order) {
-            e->next = f->next;
-            f->next = e;
-            return;
-        }
-        f = f->next;
-    }
+    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;
 }
 
-static gc_hook_cb gc_hook;
-
 void
-sylvan_gc_set_hook(gc_hook_cb new_hook)
+sylvan_gc_hook_postgc(gc_hook_cb callback)
 {
-    gc_hook = new_hook;
+    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_enable()
+sylvan_gc_add_mark(gc_hook_cb callback)
 {
-    gc_enabled = 1;
+    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_disable()
+sylvan_gc_hook_main(gc_hook_cb callback)
 {
-    gc_enabled = 0;
+    main_hook = callback;
 }
 
-/* Mark hook for cache */
-VOID_TASK_0(sylvan_gc_mark_cache)
+/**
+ * Clear the operation cache.
+ */
+VOID_TASK_IMPL_0(sylvan_clear_cache)
 {
-    /* We simply clear the cache.
-     * Alternatively, we could implement for example some strategy
-     * where part of the cache is cleared and part is marked
-     */
-    cache_clear();
+   cache_clear();
 }
 
-/* Default hook */
+/**
+ * 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) {
+        //e->cb();
+        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 n)
+next_size(size_t current_size)
 {
 #if SYLVAN_SIZE_FIBONACCI
     size_t f1=1, f2=1;
     for (;;) {
         f2 += f1;
-        if (f2 > n) return f2;
+        if (f2 > current_size) return f2;
         f1 += f2;
-        if (f1 > n) return f1;
+        if (f1 > current_size) return f1;
     }
 #else
-    return n*2;
+    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)
 {
-    /**
-     * Always resize when gc called
-     */
-    size_t max_size = llmsset_get_max_size(nodes);
-    size_t size = llmsset_get_size(nodes);
-    if (size < max_size) {
-        size_t new_size = next_size(size);
-        if (new_size > max_size) new_size = max_size;
+    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) {
-            new_size = next_size(cache_size);
-            if (new_size > cache_max) new_size = cache_max;
-            cache_setsize(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);
     }
 }
 
-VOID_TASK_IMPL_0(sylvan_gc_default_hook)
+/**
+ * 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)
 {
-    /**
-     * Default behavior:
-     * if we can resize the nodes set, and if we use more than 50%, then increase size
-     */
-    size_t max_size = llmsset_get_max_size(nodes);
-    size_t size = llmsset_get_size(nodes);
-    if (size < max_size) {
+    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 > size) {
-            size_t new_size = next_size(size);
-            if (new_size > max_size) new_size = max_size;
+        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) {
@@ -185,82 +214,163 @@ VOID_TASK_IMPL_0(sylvan_gc_default_hook)
     }
 }
 
-VOID_TASK_0(sylvan_gc_call_hook)
+/**
+ * Actual implementation of garbage collection
+ */
+VOID_TASK_0(sylvan_gc_go)
 {
-    // call hook function (resizing, reordering, etc)
-    WRAP(gc_hook);
+    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);
 }
 
-VOID_TASK_0(sylvan_gc_rehash)
+/**
+ * Perform garbage collection
+ */
+VOID_TASK_IMPL_0(sylvan_gc)
 {
-    // rehash marked nodes
-    llmsset_rehash(nodes);
+    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);
+        }
+    }
 }
 
-VOID_TASK_0(sylvan_gc_destroy_unmarked)
+/**
+ * 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)
 {
-    llmsset_destroy_unmarked(nodes);
+    return __builtin_popcount(size) == 1 ? 1 : 0;
 }
 
-VOID_TASK_0(sylvan_gc_go)
+void
+sylvan_set_sizes(size_t min_tablesize, size_t max_tablesize, size_t min_cachesize, size_t max_cachesize)
 {
-    sylvan_stats_count(SYLVAN_GC_COUNT);
-    sylvan_timer_start(SYLVAN_GC);
+    /* Some sanity checks */
+    if (min_tablesize > max_tablesize) min_tablesize = max_tablesize;
+    if (min_cachesize > max_cachesize) min_cachesize = max_cachesize;
 
-    // clear hash array
-    llmsset_clear(nodes);
+    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);
+    }
 
-    // call mark functions, hook and rehash
-    struct reg_gc_mark_entry *e = gc_mark_register;
-    while (e != NULL) {
-        WRAP(e->cb);
-        e = e->next;
+    if (max_tablesize > 0x0000040000000000) {
+        fprintf(stderr, "sylvan_set_sizes error: tablesize must be <= 42 bits!\n");
+        exit(1);
     }
 
-    sylvan_timer_stop(SYLVAN_GC);
+    table_min = min_tablesize;
+    table_max = max_tablesize;
+    cache_min = min_cachesize;
+    cache_max = max_cachesize;
 }
 
-/* Perform garbage collection */
-VOID_TASK_IMPL_0(sylvan_gc)
+void
+sylvan_set_limits(size_t memorycap, int table_ratio, int initial_ratio)
 {
-    if (!gc_enabled) return;
-    if (cas(&gc, 0, 1)) {
-        NEWFRAME(sylvan_gc_go);
-        gc = 0;
+    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 {
-        /* wait for new frame to appear */
-        while (*(Task* volatile*)&(lace_newframe.t) == 0) {}
-        lace_yield(__lace_worker, __lace_dq_head);
+        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;
 }
 
 /**
- * Package init and quit functions
+ * Initializes Sylvan.
  */
 void
-sylvan_init_package(size_t tablesize, size_t maxsize, size_t cachesize, size_t max_cachesize)
+sylvan_init_package(void)
 {
-    if (tablesize > maxsize) tablesize = maxsize;
-    if (cachesize > max_cachesize) cachesize = max_cachesize;
-
-    if (maxsize > 0x000003ffffffffff) {
-        fprintf(stderr, "sylvan_init_package error: tablesize must be <= 42 bits!\n");
+    if (table_max == 0) {
+        fprintf(stderr, "sylvan_init_package error: table sizes not set (sylvan_set_sizes or sylvan_set_limits)!");
         exit(1);
     }
 
-    nodes = llmsset_create(tablesize, maxsize);
-    cache_create(cachesize, max_cachesize);
+    /* Create tables */
+    nodes = llmsset_create(table_min, table_max);
+    cache_create(cache_min, cache_max);
 
+    /* Initialize garbage collection */
     gc = 0;
 #if SYLVAN_AGGRESSIVE_RESIZE
-    gc_hook = TASK(sylvan_gc_aggressive_resize);
+    main_hook = TASK(sylvan_gc_aggressive_resize);
 #else
-    gc_hook = TASK(sylvan_gc_default_hook);
+    main_hook = TASK(sylvan_gc_normal_resize);
 #endif
-    sylvan_gc_add_mark(10, TASK(sylvan_gc_mark_cache));
-    sylvan_gc_add_mark(19, TASK(sylvan_gc_destroy_unmarked));
-    sylvan_gc_add_mark(20, TASK(sylvan_gc_call_hook));
-    sylvan_gc_add_mark(30, TASK(sylvan_gc_rehash));
 
     LACE_ME;
     sylvan_stats_init();
@@ -293,12 +403,63 @@ sylvan_quit()
         free(e);
     }
 
-    while (gc_mark_register != NULL) {
-        struct reg_gc_mark_entry *e = gc_mark_register;
-        gc_mark_register = e->next;
+    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;
+    sylvan_gc_seq();
+}
+
+/*******************************************************************/
+/***************SEQUENTIAL*****************************************/
+/******************************************************************/
+void mark_nodes() {
+//for (gc_hook_entry_t e = mark_list; e != NULL; e = e->next) {
+        //e->cb();
+        ldd_gc_mark_protected();
+  //  }
+
+}
+
+
+/***
+ * Sequential VERSION of rehash_all
+ */
+ void sylvan_rehash_all_seq()
+{
+    // clear hash array
+    llmsset_clear_hashes_seq(nodes);
+
+    // rehash marked nodes
+    if (llmsset_rehash_seq(nodes) != 0) {
+        fprintf(stderr, "sylvan_gc_rehash error: not all nodes could be rehashed!\n");
+        exit(1);
+    }
+}
+
diff --git a/sylvan_common.h b/sylvan_common.h
old mode 100644
new mode 100755
index 61f34cc5b50bb3df027722a657aa14b46d5aefd0..432adf200f9eb01f34b8a0585b07c3a9b506cb4c
--- a/sylvan_common.h
+++ b/sylvan_common.h
@@ -1,5 +1,6 @@
 /*
- * Copyright 2011-2015 Formal Methods and Tools, University of Twente
+ * 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.
@@ -14,6 +15,8 @@
  * limitations under the License.
  */
 
+/* Do not include this file directly. Instead, include sylvan.h */
+
 #ifndef SYLVAN_COMMON_H
 #define SYLVAN_COMMON_H
 
@@ -21,62 +24,198 @@
 extern "C" {
 #endif /* __cplusplus */
 
-/* Garbage collection test task - t */
-#define sylvan_gc_test() YIELD_NEWFRAME()
+/**
+ * Initialize the Sylvan parallel decision diagrams package.
+ *
+ * First, Sylvan must know how big the nodes table and cache may be.
+ * Either use sylvan_set_sizes to explicitly set the table sizes, or use sylvan_set_limits
+ * to let Sylvan compute the sizes for you.
+ *
+ * Then, call sylvan_init_package. This allocates the tables and other support structures.
+ * Sylvan allocates virtual memory to accomodate the maximum sizes of both tables.
+ * Initially, Sylvan only uses the minimum sizes.
+ * During garbage collection, table sizes may be doubled until the maximum size is reached.
+ *
+ * Then, call initialization functions for the MTBDD/LDD modules like sylvan_init_mtbdd
+ * and sylvan_init_ldd.
+ *
+ * Memory usage:
+ * Every node requires 24 bytes memory. (16 bytes data + 8 bytes table overhead)
+ * Every operation cache entry requires 36 bytes memory. (32 bytes data + 4 bytes table overhead)
+ */
+void sylvan_init_package(void);
+
+/**
+ * Explicitly set the sizes of the nodes table and the operation cache.
+ * The sizes are in bytes, but they must be powers of two.
+ * The minimum size is the size initially used.
+ * The maximum size is the size allocated in virtual memory.
+ */
+void sylvan_set_sizes(size_t min_tablesize, size_t max_tablesize, size_t min_cachesize, size_t max_cachesize);
 
-// BDD operations
-#define CACHE_BDD_ITE             (0LL<<40)
-#define CACHE_BDD_AND             (1LL<<40)
-#define CACHE_BDD_XOR             (2LL<<40)
-#define CACHE_BDD_EXISTS          (3LL<<40)
-#define CACHE_BDD_AND_EXISTS      (4LL<<40)
-#define CACHE_BDD_RELNEXT         (5LL<<40)
-#define CACHE_BDD_RELPREV         (6LL<<40)
-#define CACHE_BDD_SATCOUNT        (7LL<<40)
-#define CACHE_BDD_COMPOSE         (8LL<<40)
-#define CACHE_BDD_RESTRICT        (9LL<<40)
-#define CACHE_BDD_CONSTRAIN       (10LL<<40)
-#define CACHE_BDD_CLOSURE         (11LL<<40)
-#define CACHE_BDD_ISBDD           (12LL<<40)
-#define CACHE_BDD_SUPPORT         (13LL<<40)
-#define CACHE_BDD_PATHCOUNT       (14LL<<40)
-
-// MDD operations
-#define CACHE_MDD_RELPROD         (20LL<<40)
-#define CACHE_MDD_MINUS           (21LL<<40)
-#define CACHE_MDD_UNION           (22LL<<40)
-#define CACHE_MDD_INTERSECT       (23LL<<40)
-#define CACHE_MDD_PROJECT         (24LL<<40)
-#define CACHE_MDD_JOIN            (25LL<<40)
-#define CACHE_MDD_MATCH           (26LL<<40)
-#define CACHE_MDD_RELPREV         (27LL<<40)
-#define CACHE_MDD_SATCOUNT        (28LL<<40)
-#define CACHE_MDD_SATCOUNTL1      (29LL<<40)
-#define CACHE_MDD_SATCOUNTL2      (30LL<<40)
-
-// MTBDD operations
-#define CACHE_MTBDD_APPLY         (40LL<<40)
-#define CACHE_MTBDD_UAPPLY        (41LL<<40)
-#define CACHE_MTBDD_ABSTRACT      (42LL<<40)
-#define CACHE_MTBDD_ITE           (43LL<<40)
-#define CACHE_MTBDD_AND_EXISTS    (44LL<<40)
-#define CACHE_MTBDD_SUPPORT       (45LL<<40)
-#define CACHE_MTBDD_COMPOSE       (46LL<<40)
-#define CACHE_MTBDD_EQUAL_NORM    (47LL<<40)
-#define CACHE_MTBDD_EQUAL_NORM_REL (48LL<<40)
-#define CACHE_MTBDD_MINIMUM       (49LL<<40)
-#define CACHE_MTBDD_MAXIMUM       (50LL<<40)
-#define CACHE_MTBDD_LEQ           (51LL<<40)
-#define CACHE_MTBDD_LESS          (52LL<<40)
-#define CACHE_MTBDD_GEQ           (53LL<<40)
-#define CACHE_MTBDD_GREATER       (54LL<<40)
-
-/**
- * Registration of quit functions
- */
-typedef void (*quit_cb)();
+/**
+ * Implicitly compute and set the sizes of the nodes table and the operation cache.
+ *
+ * This function computes max_tablesize and max_cachesize to fit the memory cap.
+ * The memory cap is in bytes.
+ *
+ * The parameter table_ratio controls the ratio between the nodes table and the cache.
+ * For the value 0, both tables are of the same size.
+ * For values 1, 2, 3 ... the nodes table will be 2x, 4x, 8x ... as big as the cache
+ * For values -1, -2, -3 ... the cache will be 2x, 4x, 8x ... as big as the nodes table
+ *
+ * The parameter initial_ratio controls how much smaller the initial table sizes are.
+ * For values of 1, 2, 3, 4 the tables will initially be 2, 4, 8, 16 times smaller.
+ */
+void sylvan_set_limits(size_t memory_cap, int table_ratio, int initial_ratio);
+
+/**
+ * Frees all Sylvan data (also calls the quit() functions of SyBDD/LDD parts)
+ */
+void sylvan_quit(void);
+
+/**
+ * Registers a hook callback called during sylvan_quit()
+ */
+typedef void (*quit_cb)(void);
 void sylvan_register_quit(quit_cb cb);
 
+/**
+ * Return number of occupied buckets in nodes table and total number of buckets.
+ */
+VOID_TASK_DECL_2(sylvan_table_usage, size_t*, size_t*);
+#define sylvan_table_usage(filled, total) (CALL(sylvan_table_usage, filled, total))
+
+/**
+ * GARBAGE COLLECTION
+ *
+ * Garbage collection is performed in a new Lace frame, interrupting all ongoing work
+ * until garbage collection is completed.
+ *
+ * By default, garbage collection is triggered when no new nodes can be added to the nodes table.
+ * This is detected when there are no more available buckets in the bounded probe sequence.
+ * Garbage collection can also be triggered manually with sylvan_gc()
+ *
+ * Garbage collection procedure:
+ * 1) All installed pre_gc hooks are called.
+ *    See sylvan_gc_hook_pre to add hooks.
+ * 2) The operation cache is cleared.
+ * 3) The nodes table (data part) is cleared.
+ * 4) All nodes are marked (to be rehashed) using the various marking callbacks.
+ *    See sylvan_gc_add_mark to add marking callbacks.
+ *    Afterwards, the ondead hook is called for all now-dead nodes with the custom flag set.
+ * 5) The main gc hook is called. The function of this hook is to perform resizing.
+ *    The default implementation doubles the nodes table and operation cache sizes.
+ *    See sylvan_gc_hook_main to set the hook.
+ * 5) The nodes table (hash part) is cleared.
+ * 6) All marked nodes are rehashed.
+ * 7) All installed post_gc hooks are called.
+ *    See sylvan_gc_hook_post to add hooks.
+ *
+ * For parts of the garbage collection process, specific methods exist.
+ * - sylvan_clear_cache() clears the operation cache (step 2)
+ * - sylvan_clear_and_mark() performs steps 3 and 4.
+ * - sylvan_rehash_all() performs steps 5 and 6.
+ */
+
+/**
+ * Trigger garbage collection manually.
+ */
+
+/**
+ * Trigger garbage collection manually.
+ */
+VOID_TASK_DECL_0(sylvan_gc);
+#define sylvan_gc() (CALL(sylvan_gc))
+
+/**
+ * Enable or disable garbage collection.
+ *
+ * This affects both automatic and manual garbage collection, i.e.,
+ * calling sylvan_gc() while garbage collection is disabled does not have any effect.
+ * If no new nodes can be added, Sylvan will write an error and abort.
+ */
+void sylvan_gc_enable(void);
+void sylvan_gc_disable(void);
+
+/**
+ * Test if garbage collection must happen now.
+ * This is just a call to the Lace framework to see if NEWFRAME has been used.
+ * Before calling this, make sure all used BDDs are referenced.
+ */
+#define sylvan_gc_test() YIELD_NEWFRAME()
+
+/**
+ * Clear the operation cache.
+ */
+VOID_TASK_DECL_0(sylvan_clear_cache);
+#define sylvan_clear_cache() CALL(sylvan_clear_cache)
+
+/**
+ * Clear the nodes table (data part) and mark all nodes with the marking mechanisms.
+ */
+VOID_TASK_DECL_0(sylvan_clear_and_mark);
+#define sylvan_clear_and_mark() CALL(sylvan_clear_and_mark)
+
+/**
+ * Clear the nodes table (hash part) and rehash all marked nodes.
+ */
+VOID_TASK_DECL_0(sylvan_rehash_all);
+#define sylvan_rehash_all() CALL(sylvan_rehash_all)
+
+/**
+ * Callback type
+ */
+LACE_TYPEDEF_CB(void, gc_hook_cb);
+
+/**
+ * Add a hook that is called before garbage collection begins.
+ */
+void sylvan_gc_hook_pregc(gc_hook_cb callback);
+
+/**
+ * Add a hook that is called after garbage collection is finished.
+ */
+void sylvan_gc_hook_postgc(gc_hook_cb callback);
+
+/**
+ * Replace the hook called between node marking and rehashing.
+ * Typically, the hook resizes the hash table and operation cache according to some heuristic.
+ */
+void sylvan_gc_hook_main(gc_hook_cb callback);
+
+/**
+ * Add a marking mechanism.
+ *
+ * The mark_cb callback is called during garbage collection and should call the
+ * appropriate recursive marking functions for the decision diagram nodes, for example
+ * mtbdd_gc_mark_rec() for MTBDDs or lddmc_gc_mark_rec() for LDDs.
+ *
+ * The sylvan_count_refs() function uses the count_cb callbacks to compute the number
+ * of references.
+ */
+void sylvan_gc_add_mark(gc_hook_cb mark_cb);
+
+/**
+ * One of the hooks for resizing behavior.
+ * Default if SYLVAN_AGGRESSIVE_RESIZE is set.
+ * Always double size on gc() until maximum reached.
+ * Use sylvan_gc_hook_main() to set this heuristic.
+ */
+VOID_TASK_DECL_0(sylvan_gc_aggressive_resize);
+
+/**
+ * One of the hooks for resizing behavior.
+ * Default if SYLVAN_AGGRESSIVE_RESIZE is not set.
+ * Double size on gc() whenever >50% is used.
+ * Use sylvan_gc_hook_main() to set this heuristic.
+ */
+VOID_TASK_DECL_0(sylvan_gc_normal_resize);
+
+
+
+void mark_nodes();
+
 #ifdef __cplusplus
 }
 #endif /* __cplusplus */
diff --git a/sylvan_config.h b/sylvan_config.h
old mode 100644
new mode 100755
diff --git a/sylvan_gmp.c b/sylvan_gmp.c
old mode 100644
new mode 100755
index 2613d50a44f1b266fb372c26e863553efab6f2a3..7d6da54634483e3e9e7e7b2fce5740f937a96222
--- a/sylvan_gmp.c
+++ b/sylvan_gmp.c
@@ -1,5 +1,6 @@
 /*
- * Copyright 2011-2015 Formal Methods and Tools, University of Twente
+ * 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.
@@ -14,22 +15,13 @@
  * limitations under the License.
  */
 
-#include <sylvan_config.h>
+#include <sylvan_int.h>
+#include <sylvan_gmp.h>
 
-#include <assert.h>
-#include <inttypes.h>
 #include <math.h>
-#include <stdint.h>
-#include <stdio.h>
-#include <stdlib.h>
 #include <string.h>
 
-#include <sylvan.h>
-#include <sylvan_common.h>
-#include <sylvan_mtbdd_int.h>
-#include <sylvan_gmp.h>
-#include <gmp.h>
-
+static uint32_t gmp_type;
 
 /**
  * helper function for hash
@@ -57,8 +49,7 @@ gmp_hash(const uint64_t v, const uint64_t seed)
 
     // hash "numerator" limbs
     limbs = x[0]._mp_num._mp_d;
-    int i;
-    for ( i=0; i<x[0]._mp_num._mp_size; i++) {
+    for (int i=0; i<x[0]._mp_num._mp_size; i++) {
         hash = hash ^ limbs[i];
         hash = rotl64(hash, 47);
         hash = hash * prime;
@@ -66,8 +57,7 @@ gmp_hash(const uint64_t v, const uint64_t seed)
 
     // hash "denominator" limbs
     limbs = x[0]._mp_den._mp_d;
-
-    for ( i=0; i<x[0]._mp_den._mp_size; i++) {
+    for (int i=0; i<x[0]._mp_den._mp_size; i++) {
         hash = hash ^ limbs[i];
         hash = rotl64(hash, 31);
         hash = hash * prime;
@@ -108,8 +98,50 @@ gmp_destroy(uint64_t val)
     free((void*)val);
 }
 
-static uint32_t gmp_type;
-static uint64_t CACHE_GMP_AND_EXISTS;
+static char*
+gmp_to_str(int comp, uint64_t val, char *buf, size_t buflen)
+{
+    mpq_ptr op = (mpq_ptr)val;
+    size_t minsize = mpz_sizeinbase(mpq_numref(op), 10) + mpz_sizeinbase (mpq_denref(op), 10) + 3;
+    if (buflen >= minsize) return mpq_get_str(buf, 10, op);
+    else return mpq_get_str(NULL, 10, op);
+    (void)comp;
+}
+
+static int
+gmp_write_binary(FILE* out, uint64_t val)
+{
+    mpq_ptr op = (mpq_ptr)val;
+
+    mpz_t i;
+    mpz_init(i);
+    mpq_get_num(i, op);
+    if (mpz_out_raw(out, i) == 0) return -1;
+    mpq_get_den(i, op);
+    if (mpz_out_raw(out, i) == 0) return -1;
+    mpz_clear(i);
+
+    return 0;
+}
+
+static int
+gmp_read_binary(FILE* in, uint64_t *val)
+{
+    mpq_ptr mres = (mpq_ptr)malloc(sizeof(__mpq_struct));
+    mpq_init(mres);
+
+    mpz_t i;
+    mpz_init(i);
+    if (mpz_inp_raw(i, in) == 0) return -1;
+    mpq_set_num(mres, i);
+    if (mpz_inp_raw(i, in) == 0) return -1;
+    mpq_set_den(mres, i);
+    mpz_clear(i);
+
+    *(mpq_ptr*)val = mres;
+
+    return 0;
+}
 
 /**
  * Initialize gmp custom leaves
@@ -117,9 +149,15 @@ static uint64_t CACHE_GMP_AND_EXISTS;
 void
 gmp_init()
 {
-    /* Register custom leaf 3 */
-    gmp_type = mtbdd_register_custom_leaf(gmp_hash, gmp_equals, gmp_create, gmp_destroy);
-    CACHE_GMP_AND_EXISTS = cache_next_opid();
+    /* Register custom leaf */
+    gmp_type = sylvan_mt_create_type();
+    sylvan_mt_set_hash(gmp_type, gmp_hash);
+    sylvan_mt_set_equals(gmp_type, gmp_equals);
+    sylvan_mt_set_create(gmp_type, gmp_create);
+    sylvan_mt_set_destroy(gmp_type, gmp_destroy);
+    sylvan_mt_set_to_str(gmp_type, gmp_to_str);
+    sylvan_mt_set_write_binary(gmp_type, gmp_write_binary);
+    sylvan_mt_set_read_binary(gmp_type, gmp_read_binary);
 }
 
 /**
@@ -146,6 +184,8 @@ TASK_IMPL_2(MTBDD, gmp_op_plus, MTBDD*, pa, MTBDD*, pb)
 
     /* If both leaves, compute plus */
     if (mtbdd_isleaf(a) && mtbdd_isleaf(b)) {
+        assert(mtbdd_gettype(a) == gmp_type && mtbdd_gettype(b) == gmp_type);
+
         mpq_ptr ma = (mpq_ptr)mtbdd_getvalue(a);
         mpq_ptr mb = (mpq_ptr)mtbdd_getvalue(b);
 
@@ -180,6 +220,8 @@ TASK_IMPL_2(MTBDD, gmp_op_minus, MTBDD*, pa, MTBDD*, pb)
 
     /* If both leaves, compute plus */
     if (mtbdd_isleaf(a) && mtbdd_isleaf(b)) {
+        assert(mtbdd_gettype(a) == gmp_type && mtbdd_gettype(b) == gmp_type);
+
         mpq_ptr ma = (mpq_ptr)mtbdd_getvalue(a);
         mpq_ptr mb = (mpq_ptr)mtbdd_getvalue(b);
 
@@ -196,7 +238,7 @@ TASK_IMPL_2(MTBDD, gmp_op_minus, MTBDD*, pa, MTBDD*, pb)
 
 /**
  * Operation "times" for two mpq MTBDDs.
- * One of the parameters can be a BDD, then it is interpreted as a filter.
+ * One of the parameters can be a SyBDD, then it is interpreted as a filter.
  * For partial functions, domain is intersection
  */
 TASK_IMPL_2(MTBDD, gmp_op_times, MTBDD*, pa, MTBDD*, pb)
@@ -212,6 +254,8 @@ TASK_IMPL_2(MTBDD, gmp_op_times, MTBDD*, pa, MTBDD*, pb)
 
     /* Handle multiplication of leaves */
     if (mtbdd_isleaf(a) && mtbdd_isleaf(b)) {
+        assert(mtbdd_gettype(a) == gmp_type && mtbdd_gettype(b) == gmp_type);
+
         mpq_ptr ma = (mpq_ptr)mtbdd_getvalue(a);
         mpq_ptr mb = (mpq_ptr)mtbdd_getvalue(b);
 
@@ -246,6 +290,8 @@ TASK_IMPL_2(MTBDD, gmp_op_divide, MTBDD*, pa, MTBDD*, pb)
 
     /* Handle division of leaves */
     if (mtbdd_isleaf(a) && mtbdd_isleaf(b)) {
+        assert(mtbdd_gettype(a) == gmp_type && mtbdd_gettype(b) == gmp_type);
+
         mpq_ptr ma = (mpq_ptr)mtbdd_getvalue(a);
         mpq_ptr mb = (mpq_ptr)mtbdd_getvalue(b);
 
@@ -277,6 +323,8 @@ TASK_IMPL_2(MTBDD, gmp_op_min, MTBDD*, pa, MTBDD*, pb)
 
     /* Compute result for leaves */
     if (mtbdd_isleaf(a) && mtbdd_isleaf(b)) {
+        assert(mtbdd_gettype(a) == gmp_type && mtbdd_gettype(b) == gmp_type);
+
         mpq_ptr ma = (mpq_ptr)mtbdd_getvalue(a);
         mpq_ptr mb = (mpq_ptr)mtbdd_getvalue(b);
         int cmp = mpq_cmp(ma, mb);
@@ -308,6 +356,8 @@ TASK_IMPL_2(MTBDD, gmp_op_max, MTBDD*, pa, MTBDD*, pb)
 
     /* Compute result for leaves */
     if (mtbdd_isleaf(a) && mtbdd_isleaf(b)) {
+        assert(mtbdd_gettype(a) == gmp_type && mtbdd_gettype(b) == gmp_type);
+
         mpq_ptr ma = (mpq_ptr)mtbdd_getvalue(a);
         mpq_ptr mb = (mpq_ptr)mtbdd_getvalue(b);
         int cmp = mpq_cmp(ma, mb);
@@ -333,6 +383,8 @@ TASK_IMPL_2(MTBDD, gmp_op_neg, MTBDD, dd, size_t, p)
 
     /* Compute result for leaf */
     if (mtbdd_isleaf(dd)) {
+        assert(mtbdd_gettype(dd) == gmp_type);
+
         mpq_ptr m = (mpq_ptr)mtbdd_getvalue(dd);
 
         mpq_t mres;
@@ -357,6 +409,8 @@ TASK_IMPL_2(MTBDD, gmp_op_abs, MTBDD, dd, size_t, p)
 
     /* Compute result for leaf */
     if (mtbdd_isleaf(dd)) {
+        assert(mtbdd_gettype(dd) == gmp_type);
+
         mpq_ptr m = (mpq_ptr)mtbdd_getvalue(dd);
 
         mpq_t mres;
@@ -383,8 +437,7 @@ TASK_IMPL_3(MTBDD, gmp_abstract_op_plus, MTBDD, a, MTBDD, b, int, k)
         return mtbdd_apply(a, b, TASK(gmp_op_plus));
     } else {
         MTBDD res = a;
-        int i;
-        for ( i=0; i<k; i++) {
+        for (int i=0; i<k; i++) {
             mtbdd_refs_push(res);
             res = mtbdd_apply(res, res, TASK(gmp_op_plus));
             mtbdd_refs_pop(1);
@@ -399,8 +452,7 @@ TASK_IMPL_3(MTBDD, gmp_abstract_op_times, MTBDD, a, MTBDD, b, int, k)
         return mtbdd_apply(a, b, TASK(gmp_op_times));
     } else {
         MTBDD res = a;
-        int i;
-        for ( i=0; i<k; i++) {
+        for (int i=0; i<k; i++) {
             mtbdd_refs_push(res);
             res = mtbdd_apply(res, res, TASK(gmp_op_times));
             mtbdd_refs_pop(1);
@@ -439,6 +491,8 @@ TASK_2(MTBDD, gmp_op_threshold_d, MTBDD, a, size_t, svalue)
 
     /* Compute result */
     if (mtbdd_isleaf(a)) {
+        assert(mtbdd_gettype(a) == gmp_type);
+
         double value = *(double*)&svalue;
         mpq_ptr ma = (mpq_ptr)mtbdd_getvalue(a);
         return mpq_get_d(ma) >= value ? mtbdd_true : mtbdd_false;
@@ -457,6 +511,8 @@ TASK_2(MTBDD, gmp_op_strict_threshold_d, MTBDD, a, size_t, svalue)
 
     /* Compute result */
     if (mtbdd_isleaf(a)) {
+        assert(mtbdd_gettype(a) == gmp_type);
+
         double value = *(double*)&svalue;
         mpq_ptr ma = (mpq_ptr)mtbdd_getvalue(a);
         return mpq_get_d(ma) > value ? mtbdd_true : mtbdd_false;
@@ -488,6 +544,8 @@ TASK_IMPL_2(MTBDD, gmp_op_threshold, MTBDD*, pa, MTBDD*, pb)
 
     /* Handle comparison of leaves */
     if (mtbdd_isleaf(a)) {
+        assert(mtbdd_gettype(a) == gmp_type);
+
         mpq_ptr ma = (mpq_ptr)mtbdd_getvalue(a);
         mpq_ptr mb = (mpq_ptr)mtbdd_getvalue(b);
         int cmp = mpq_cmp(ma, mb);
@@ -510,6 +568,8 @@ TASK_IMPL_2(MTBDD, gmp_op_strict_threshold, MTBDD*, pa, MTBDD*, pb)
 
     /* Handle comparison of leaves */
     if (mtbdd_isleaf(a)) {
+        assert(mtbdd_gettype(a) == gmp_type);
+
         mpq_ptr ma = (mpq_ptr)mtbdd_getvalue(a);
         mpq_ptr mb = (mpq_ptr)mtbdd_getvalue(b);
         int cmp = mpq_cmp(ma, mb);
@@ -523,7 +583,7 @@ TASK_IMPL_2(MTBDD, gmp_op_strict_threshold, MTBDD*, pa, MTBDD*, pb)
  * Multiply <a> and <b>, and abstract variables <vars> using summation.
  * This is similar to the "and_exists" operation in BDDs.
  */
-TASK_IMPL_3(MTBDD, gmp_and_exists, MTBDD, a, MTBDD, b, MTBDD, v)
+TASK_IMPL_3(MTBDD, gmp_and_abstract_plus, MTBDD, a, MTBDD, b, MTBDD, v)
 {
     /* Check terminal cases */
 
@@ -545,26 +605,32 @@ TASK_IMPL_3(MTBDD, gmp_and_exists, MTBDD, a, MTBDD, b, MTBDD, v)
     /* Maybe perform garbage collection */
     sylvan_gc_test();
 
+    /* Count operation */
+    sylvan_stats_count(MTBDD_AND_ABSTRACT_PLUS);
+
     /* Check cache. Note that we do this now, since the times operator might swap a and b (commutative) */
-    if (cache_get3(CACHE_GMP_AND_EXISTS, a, b, v, &result)) return result;
+    if (cache_get3(CACHE_MTBDD_AND_ABSTRACT_PLUS, a, b, v, &result)) {
+        sylvan_stats_count(MTBDD_AND_ABSTRACT_PLUS_CACHED);
+        return result;
+    }
 
     /* Now, v is not a constant, and either a or b is not a constant */
 
     /* Get top variable */
     int la = mtbdd_isleaf(a);
     int lb = mtbdd_isleaf(b);
-    mtbddnode_t na = la ? 0 : GETNODE(a);
-    mtbddnode_t nb = lb ? 0 : GETNODE(b);
+    mtbddnode_t na = la ? 0 : MTBDD_GETNODE(a);
+    mtbddnode_t nb = lb ? 0 : MTBDD_GETNODE(b);
     uint32_t va = la ? 0xffffffff : mtbddnode_getvariable(na);
     uint32_t vb = lb ? 0xffffffff : mtbddnode_getvariable(nb);
     uint32_t var = va < vb ? va : vb;
 
-    mtbddnode_t nv = GETNODE(v);
+    mtbddnode_t nv = MTBDD_GETNODE(v);
     uint32_t vv = mtbddnode_getvariable(nv);
 
     if (vv < var) {
         /* Recursive, then abstract result */
-        result = CALL(gmp_and_exists, a, b, node_gethigh(v, nv));
+        result = CALL(gmp_and_abstract_plus, a, b, node_gethigh(v, nv));
         mtbdd_refs_push(result);
         result = mtbdd_apply(result, result, TASK(gmp_op_plus));
         mtbdd_refs_pop(1);
@@ -578,22 +644,112 @@ TASK_IMPL_3(MTBDD, gmp_and_exists, MTBDD, a, MTBDD, b, MTBDD, v)
 
         if (vv == var) {
             /* Recursive, then abstract result */
-            mtbdd_refs_spawn(SPAWN(gmp_and_exists, ahigh, bhigh, node_gethigh(v, nv)));
-            MTBDD low = mtbdd_refs_push(CALL(gmp_and_exists, alow, blow, node_gethigh(v, nv)));
-            MTBDD high = mtbdd_refs_push(mtbdd_refs_sync(SYNC(gmp_and_exists)));
+            mtbdd_refs_spawn(SPAWN(gmp_and_abstract_plus, ahigh, bhigh, node_gethigh(v, nv)));
+            MTBDD low = mtbdd_refs_push(CALL(gmp_and_abstract_plus, alow, blow, node_gethigh(v, nv)));
+            MTBDD high = mtbdd_refs_push(mtbdd_refs_sync(SYNC(gmp_and_abstract_plus)));
             result = CALL(mtbdd_apply, low, high, TASK(gmp_op_plus));
             mtbdd_refs_pop(2);
         } else /* vv > v */ {
             /* Recursive, then create node */
-            mtbdd_refs_spawn(SPAWN(gmp_and_exists, ahigh, bhigh, v));
-            MTBDD low = mtbdd_refs_push(CALL(gmp_and_exists, alow, blow, v));
-            MTBDD high = mtbdd_refs_sync(SYNC(gmp_and_exists));
+            mtbdd_refs_spawn(SPAWN(gmp_and_abstract_plus, ahigh, bhigh, v));
+            MTBDD low = mtbdd_refs_push(CALL(gmp_and_abstract_plus, alow, blow, v));
+            MTBDD high = mtbdd_refs_sync(SYNC(gmp_and_abstract_plus));
             mtbdd_refs_pop(1);
             result = mtbdd_makenode(var, low, high);
         }
     }
 
     /* Store in cache */
-    cache_put3(CACHE_GMP_AND_EXISTS, a, b, v, result);
+    if (cache_put3(CACHE_MTBDD_AND_ABSTRACT_PLUS, a, b, v, result)) {
+        sylvan_stats_count(MTBDD_AND_ABSTRACT_PLUS_CACHEDPUT);
+    }
+
+    return result;
+}
+
+/**
+ * Multiply <a> and <b>, and abstract variables <vars> by taking the maximum.
+ */
+TASK_IMPL_3(MTBDD, gmp_and_abstract_max, MTBDD, a, MTBDD, b, MTBDD, v)
+{
+    /* Check terminal cases */
+
+    /* If v == true, then <vars> is an empty set */
+    if (v == mtbdd_true) return mtbdd_apply(a, b, TASK(gmp_op_times));
+
+    /* Try the times operator on a and b */
+    MTBDD result = CALL(gmp_op_times, &a, &b);
+    if (result != mtbdd_invalid) {
+        /* Times operator successful, store reference (for garbage collection) */
+        mtbdd_refs_push(result);
+        /* ... and perform abstraction */
+        result = mtbdd_abstract(result, v, TASK(gmp_abstract_op_max));
+        mtbdd_refs_pop(1);
+        /* Note that the operation cache is used in mtbdd_abstract */
+        return result;
+    }
+
+    /* Now, v is not a constant, and either a or b is not a constant */
+
+    /* Get top variable */
+    int la = mtbdd_isleaf(a);
+    int lb = mtbdd_isleaf(b);
+    mtbddnode_t na = la ? 0 : MTBDD_GETNODE(a);
+    mtbddnode_t nb = lb ? 0 : MTBDD_GETNODE(b);
+    uint32_t va = la ? 0xffffffff : mtbddnode_getvariable(na);
+    uint32_t vb = lb ? 0xffffffff : mtbddnode_getvariable(nb);
+    uint32_t var = va < vb ? va : vb;
+
+    mtbddnode_t nv = MTBDD_GETNODE(v);
+    uint32_t vv = mtbddnode_getvariable(nv);
+
+    while (vv < var) {
+        /* we can skip variables, because max(r,r) = r */
+        v = node_high(v, nv);
+        if (v == mtbdd_true) return mtbdd_apply(a, b, TASK(gmp_op_times));
+        nv = MTBDD_GETNODE(v);
+        vv = mtbddnode_getvariable(nv);
+    }
+
+    /* Maybe perform garbage collection */
+    sylvan_gc_test();
+
+    /* Count operation */
+    sylvan_stats_count(MTBDD_AND_ABSTRACT_MAX);
+
+    /* Check cache. Note that we do this now, since the times operator might swap a and b (commutative) */
+    if (cache_get3(CACHE_MTBDD_AND_ABSTRACT_MAX, a, b, v, &result)) {
+        sylvan_stats_count(MTBDD_AND_ABSTRACT_MAX_CACHED);
+        return result;
+    }
+
+    /* Get cofactors */
+    MTBDD alow, ahigh, blow, bhigh;
+    alow  = (!la && va == var) ? node_getlow(a, na)  : a;
+    ahigh = (!la && va == var) ? node_gethigh(a, na) : a;
+    blow  = (!lb && vb == var) ? node_getlow(b, nb)  : b;
+    bhigh = (!lb && vb == var) ? node_gethigh(b, nb) : b;
+
+    if (vv == var) {
+        /* Recursive, then abstract result */
+        mtbdd_refs_spawn(SPAWN(gmp_and_abstract_max, ahigh, bhigh, node_gethigh(v, nv)));
+        MTBDD low = mtbdd_refs_push(CALL(gmp_and_abstract_max, alow, blow, node_gethigh(v, nv)));
+        MTBDD high = mtbdd_refs_push(mtbdd_refs_sync(SYNC(gmp_and_abstract_max)));
+        result = CALL(mtbdd_apply, low, high, TASK(gmp_op_max));
+        mtbdd_refs_pop(2);
+    } else /* vv > v */ {
+        /* Recursive, then create node */
+        mtbdd_refs_spawn(SPAWN(gmp_and_abstract_max, ahigh, bhigh, v));
+        MTBDD low = mtbdd_refs_push(CALL(gmp_and_abstract_max, alow, blow, v));
+        MTBDD high = mtbdd_refs_sync(SYNC(gmp_and_abstract_max));
+        mtbdd_refs_pop(1);
+        result = mtbdd_makenode(var, low, high);
+    }
+
+    /* Store in cache */
+    if (cache_put3(CACHE_MTBDD_AND_ABSTRACT_MAX, a, b, v, result)) {
+        sylvan_stats_count(MTBDD_AND_ABSTRACT_MAX_CACHEDPUT);
+    }
+
     return result;
 }
diff --git a/sylvan_gmp.h b/sylvan_gmp.h
old mode 100644
new mode 100755
index fbf6bc2ad67d7a2375f5c2fe35b6b5921fe92762..8bf3b909a6304217a4c726f4177f4e01d5d325a0
--- a/sylvan_gmp.h
+++ b/sylvan_gmp.h
@@ -1,5 +1,6 @@
 /*
- * Copyright 2011-2015 Formal Methods and Tools, University of Twente
+ * 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.
@@ -18,20 +19,21 @@
  * This is an implementation of GMP mpq custom leaves of MTBDDs
  */
 
-#ifndef SYLVAN_GMP_H
-#define SYLVAN_GMP_H
-
 #include <sylvan.h>
 #include <gmp.h>
 
+#ifndef SYLVAN_GMP_H
+#define SYLVAN_GMP_H
+
 #ifdef __cplusplus
+namespace sylvan {
 extern "C" {
 #endif /* __cplusplus */
 
 /**
  * Initialize GMP custom leaves
  */
-void gmp_init();
+void gmp_init(void);
 
 /**
  * Create MPQ leaf
@@ -146,8 +148,15 @@ TASK_DECL_2(MTBDD, gmp_op_abs, MTBDD, size_t);
  * Multiply <a> and <b>, and abstract variables <vars> using summation.
  * This is similar to the "and_exists" operation in BDDs.
  */
-TASK_DECL_3(MTBDD, gmp_and_exists, MTBDD, MTBDD, MTBDD);
-#define gmp_and_exists(a, b, vars) CALL(gmp_and_exists, a, b, vars)
+TASK_DECL_3(MTBDD, gmp_and_abstract_plus, MTBDD, MTBDD, MTBDD);
+#define gmp_and_abstract_plus(a, b, vars) CALL(gmp_and_abstract_plus, a, b, vars)
+#define gmp_and_exists gmp_and_abstract_plus
+
+/**
+ * Multiply <a> and <b>, and abstract variables <vars> by taking the maximum.
+ */
+TASK_DECL_3(MTBDD, gmp_and_abstract_max, MTBDD, MTBDD, MTBDD);
+#define gmp_and_abstract_max(a, b, vars) CALL(gmp_and_abstract_max, a, b, vars)
 
 /**
  * Convert to a Boolean MTBDD, translate terminals >= value to 1 and to 0 otherwise;
@@ -177,6 +186,7 @@ TASK_DECL_2(MTBDD, gmp_strict_threshold_d, MTBDD, double);
 
 #ifdef __cplusplus
 }
+}
 #endif /* __cplusplus */
 
 #endif
diff --git a/sylvan_int.h b/sylvan_int.h
new file mode 100755
index 0000000000000000000000000000000000000000..61ea5707c1b5d3110a26a89a30ca558b447ee6ed
--- /dev/null
+++ b/sylvan_int.h
@@ -0,0 +1,114 @@
+/*
+ * 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.
+ */
+
+/**
+ * Sylvan: parallel MTBDD/ListDD package.
+ * Include this file for access to internals.
+ */
+
+#include <sylvan.h>
+
+#ifdef __cplusplus
+namespace sylvan {
+#endif
+
+/**
+ * Sylvan internal header files inside the namespace
+ */
+
+#include <sylvan_cache.h>
+#include <sylvan_table.h>
+
+#ifndef SYLVAN_INT_H
+#define SYLVAN_INT_H
+
+#ifdef __cplusplus
+extern "C" {
+#endif /* __cplusplus */
+
+/**
+ * Nodes table.
+ */
+extern llmsset_t nodes;
+
+/**
+ * Macros for all operation identifiers for the operation cache
+ */
+
+// SyBDD operations
+static const uint64_t CACHE_BDD_ITE                 = (0LL<<40);
+static const uint64_t CACHE_BDD_AND                 = (1LL<<40);
+static const uint64_t CACHE_BDD_XOR                 = (2LL<<40);
+static const uint64_t CACHE_BDD_EXISTS              = (3LL<<40);
+static const uint64_t CACHE_BDD_PROJECT             = (4LL<<40);
+static const uint64_t CACHE_BDD_AND_EXISTS          = (5LL<<40);
+static const uint64_t CACHE_BDD_AND_PROJECT         = (6LL<<40);
+static const uint64_t CACHE_BDD_RELNEXT             = (7LL<<40);
+static const uint64_t CACHE_BDD_RELPREV             = (8LL<<40);
+static const uint64_t CACHE_BDD_SATCOUNT            = (9LL<<40);
+static const uint64_t CACHE_BDD_COMPOSE             = (10LL<<40);
+static const uint64_t CACHE_BDD_RESTRICT            = (11LL<<40);
+static const uint64_t CACHE_BDD_CONSTRAIN           = (12LL<<40);
+static const uint64_t CACHE_BDD_CLOSURE             = (13LL<<40);
+static const uint64_t CACHE_BDD_ISBDD               = (14LL<<40);
+static const uint64_t CACHE_BDD_SUPPORT             = (15LL<<40);
+static const uint64_t CACHE_BDD_PATHCOUNT           = (16LL<<40);
+
+// MDD operations
+static const uint64_t CACHE_MDD_RELPROD             = (20LL<<40);
+static const uint64_t CACHE_MDD_MINUS               = (21LL<<40);
+static const uint64_t CACHE_MDD_UNION               = (22LL<<40);
+static const uint64_t CACHE_MDD_INTERSECT           = (23LL<<40);
+static const uint64_t CACHE_MDD_PROJECT             = (24LL<<40);
+static const uint64_t CACHE_MDD_JOIN                = (25LL<<40);
+static const uint64_t CACHE_MDD_MATCH               = (26LL<<40);
+static const uint64_t CACHE_MDD_RELPREV             = (27LL<<40);
+static const uint64_t CACHE_MDD_SATCOUNT            = (28LL<<40);
+static const uint64_t CACHE_MDD_SATCOUNTL1          = (29LL<<40);
+static const uint64_t CACHE_MDD_SATCOUNTL2          = (30LL<<40);
+
+// MTBDD operations
+static const uint64_t CACHE_MTBDD_APPLY             = (40LL<<40);
+static const uint64_t CACHE_MTBDD_UAPPLY            = (41LL<<40);
+static const uint64_t CACHE_MTBDD_ABSTRACT          = (42LL<<40);
+static const uint64_t CACHE_MTBDD_ITE               = (43LL<<40);
+static const uint64_t CACHE_MTBDD_AND_ABSTRACT_PLUS = (44LL<<40);
+static const uint64_t CACHE_MTBDD_AND_ABSTRACT_MAX  = (45LL<<40);
+static const uint64_t CACHE_MTBDD_SUPPORT           = (46LL<<40);
+static const uint64_t CACHE_MTBDD_COMPOSE           = (47LL<<40);
+static const uint64_t CACHE_MTBDD_EQUAL_NORM        = (48LL<<40);
+static const uint64_t CACHE_MTBDD_EQUAL_NORM_REL    = (49LL<<40);
+static const uint64_t CACHE_MTBDD_MINIMUM           = (50LL<<40);
+static const uint64_t CACHE_MTBDD_MAXIMUM           = (51LL<<40);
+static const uint64_t CACHE_MTBDD_LEQ               = (52LL<<40);
+static const uint64_t CACHE_MTBDD_LESS              = (53LL<<40);
+static const uint64_t CACHE_MTBDD_GEQ               = (54LL<<40);
+static const uint64_t CACHE_MTBDD_GREATER           = (55LL<<40);
+static const uint64_t CACHE_MTBDD_EVAL_COMPOSE      = (56LL<<40);
+
+#ifdef __cplusplus
+}
+#endif /* __cplusplus */
+
+#include <sylvan_mtbdd_int.h>
+#include <sylvan_ldd_int.h>
+
+#ifdef __cplusplus
+} /* namespace */
+#endif
+
+#endif
diff --git a/sylvan_ldd.c b/sylvan_ldd.c
old mode 100644
new mode 100755
index 618834d7c0b1d88cd83f4734c87c6b1a73f18ea5..a42f83d4cef9a06c4d0780e447c790c8988afd49
--- a/sylvan_ldd.c
+++ b/sylvan_ldd.c
@@ -1,5 +1,6 @@
 /*
- * Copyright 2011-2014 Formal Methods and Tools, University of Twente
+ * 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.
@@ -14,106 +15,15 @@
  * limitations under the License.
  */
 
-#include <sylvan_config.h>
+#include <sylvan_int.h>
 
-#include <assert.h>
 #include <inttypes.h>
 #include <math.h>
-#include <pthread.h>
-#include <stdint.h>
-#include <stdio.h>
-#include <stdlib.h>
 #include <string.h>
-
+#include <sys/mman.h>
 #include <avl.h>
-#include <refs.h>
+#include <sylvan_refs.h>
 #include <sha2.h>
-#include <sylvan.h>
-#include <sylvan_common.h>
-
-/**
- * MDD node structure
- */
-typedef struct __attribute__((packed)) mddnode {
-    uint64_t a, b;
-} * mddnode_t; // 16 bytes
-
-// RmRR RRRR RRRR VVVV | VVVV DcDD DDDD DDDD (little endian - in memory)
-// VVVV RRRR RRRR RRRm | DDDD DDDD DDDc VVVV (big endian)
-
-// Ensure our mddnode is 16 bytes
-typedef char __lddmc_check_mddnode_t_is_16_bytes[(sizeof(struct mddnode)==16) ? 1 : -1];
-
-static inline uint32_t __attribute__((unused))
-mddnode_getvalue(mddnode_t n)
-{
-    return *(uint32_t*)((uint8_t*)n+6);
-}
-
-static inline uint8_t __attribute__((unused))
-mddnode_getmark(mddnode_t n)
-{
-    return n->a & 1;
-}
-
-static inline uint8_t __attribute__((unused))
-mddnode_getcopy(mddnode_t n)
-{
-    return n->b & 0x10000 ? 1 : 0;
-}
-
-static inline uint64_t __attribute__((unused))
-mddnode_getright(mddnode_t n)
-{
-    return (n->a & 0x0000ffffffffffff) >> 1;
-}
-
-static inline uint64_t __attribute__((unused))
-mddnode_getdown(mddnode_t n)
-{
-    return n->b >> 17;
-}
-
-static inline void __attribute__((unused))
-mddnode_setvalue(mddnode_t n, uint32_t value)
-{
-    *(uint32_t*)((uint8_t*)n+6) = value;
-}
-
-static inline void __attribute__((unused))
-mddnode_setmark(mddnode_t n, uint8_t mark)
-{
-    n->a = (n->a & 0xfffffffffffffffe) | (mark ? 1 : 0);
-}
-
-static inline void __attribute__((unused))
-mddnode_setright(mddnode_t n, uint64_t right)
-{
-    n->a = (n->a & 0xffff000000000001) | (right << 1);
-}
-
-static inline void __attribute__((unused))
-mddnode_setdown(mddnode_t n, uint64_t down)
-{
-    n->b = (n->b & 0x000000000001ffff) | (down << 17);
-}
-
-static inline void __attribute__((unused))
-mddnode_make(mddnode_t n, uint32_t value, uint64_t right, uint64_t down)
-{
-    n->a = right << 1;
-    n->b = down << 17;
-    *(uint32_t*)((uint8_t*)n+6) = value;
-}
-
-static inline void __attribute__((unused))
-mddnode_makecopy(mddnode_t n, uint64_t right, uint64_t down)
-{
-    n->a = right << 1;
-    n->b = ((down << 1) | 1) << 16;
-}
-
-#define GETNODE(mdd) ((mddnode_t)llmsset_index_to_ptr(nodes, mdd))
 
 /**
  * Implementation of garbage collection
@@ -124,8 +34,9 @@ VOID_TASK_IMPL_1(lddmc_gc_mark_rec, MDD, mdd)
 {
     if (mdd <= lddmc_true) return;
 
-    if (llmsset_mark(nodes, mdd)) {
-        mddnode_t n = GETNODE(mdd);
+    if (llmsset_mark(nodes, mdd))
+    {
+        mddnode_t n = LDD_GETNODE(mdd);
         SPAWN(lddmc_gc_mark_rec, mddnode_getright(n));
         CALL(lddmc_gc_mark_rec, mddnode_getdown(n));
         SYNC(lddmc_gc_mark_rec);
@@ -136,13 +47,15 @@ VOID_TASK_IMPL_1(lddmc_gc_mark_rec, MDD, mdd)
  * External references
  */
 
-refs_table_t mdd_refs;
+refs_table_t lddmc_refs;
+refs_table_t lddmc_protected;
+static int lddmc_protected_created = 0;
 
 MDD
 lddmc_ref(MDD a)
 {
     if (a == lddmc_true || a == lddmc_false) return a;
-    refs_up(&mdd_refs, a);
+    refs_up(&lddmc_refs, a);
     return a;
 }
 
@@ -150,13 +63,37 @@ void
 lddmc_deref(MDD a)
 {
     if (a == lddmc_true || a == lddmc_false) return;
-    refs_down(&mdd_refs, a);
+    refs_down(&lddmc_refs, a);
 }
 
 size_t
 lddmc_count_refs()
 {
-    return refs_count(&mdd_refs);
+    return refs_count(&lddmc_refs);
+}
+
+void
+lddmc_protect(MDD *a)
+{
+    if (!lddmc_protected_created)
+    {
+        // In C++, sometimes lddmc_protect is called before Sylvan is initialized. Just create a table.
+        protect_create(&lddmc_protected, 4096);
+        lddmc_protected_created = 1;
+    }
+    protect_up(&lddmc_protected, (size_t)a);
+}
+
+void
+lddmc_unprotect(MDD *a)
+{
+    if (lddmc_protected.refs_table != NULL) protect_down(&lddmc_protected, (size_t)a);
+}
+
+size_t
+lddmc_count_protected(void)
+{
+    return protect_count(&lddmc_protected);
 }
 
 /* Called during garbage collection */
@@ -164,44 +101,124 @@ VOID_TASK_0(lddmc_gc_mark_external_refs)
 {
     // iterate through refs hash table, mark all found
     size_t count=0;
-    uint64_t *it = refs_iter(&mdd_refs, 0, mdd_refs.refs_size);
-    while (it != NULL) {
-        SPAWN(lddmc_gc_mark_rec, refs_next(&mdd_refs, &it, mdd_refs.refs_size));
+    uint64_t *it = refs_iter(&lddmc_refs, 0, lddmc_refs.refs_size);
+    while (it != NULL)
+    {
+        SPAWN(lddmc_gc_mark_rec, refs_next(&lddmc_refs, &it, lddmc_refs.refs_size));
         count++;
     }
-    while (count--) {
+    while (count--)
+    {
+        SYNC(lddmc_gc_mark_rec);
+    }
+}
+
+VOID_TASK_0(lddmc_gc_mark_protected)
+{
+    // iterate through refs hash table, mark all found
+    size_t count=0;
+    uint64_t *it = protect_iter(&lddmc_protected, 0, lddmc_protected.refs_size);
+    while (it != NULL)
+    {
+        MDD *to_mark = (MDD*)protect_next(&lddmc_protected, &it, lddmc_protected.refs_size);
+        SPAWN(lddmc_gc_mark_rec, *to_mark);
+        count++;
+    }
+    while (count--)
+    {
         SYNC(lddmc_gc_mark_rec);
     }
 }
 
 /* Infrastructure for internal markings */
+typedef struct lddmc_refs_task
+{
+    Task *t;
+    void *f;
+} *lddmc_refs_task_t;
+
+typedef struct lddmc_refs_internal
+{
+    const MDD **pbegin, **pend, **pcur;
+    MDD *rbegin, *rend, *rcur;
+    lddmc_refs_task_t sbegin, send, scur;
+} *lddmc_refs_internal_t;
+
+
+
+lddmc_refs_internal_t sequentiel_refs;
+
 DECLARE_THREAD_LOCAL(lddmc_refs_key, lddmc_refs_internal_t);
 
-VOID_TASK_0(lddmc_refs_mark_task)
+VOID_TASK_2(lddmc_refs_mark_p_par, const MDD**, begin, size_t, count)
 {
-    LOCALIZE_THREAD_LOCAL(lddmc_refs_key, lddmc_refs_internal_t);
-    size_t i, j=0;
-    for (i=0; i<lddmc_refs_key->r_count; i++) {
-        if (j >= 40) {
-            while (j--) SYNC(lddmc_gc_mark_rec);
-            j=0;
+    if (count < 32)
+    {
+        while (count)
+        {
+            lddmc_gc_mark_rec(**(begin++));
+            count--;
         }
-        SPAWN(lddmc_gc_mark_rec, lddmc_refs_key->results[i]);
-        j++;
-    }
-    for (i=0; i<lddmc_refs_key->s_count; i++) {
-        Task *t = lddmc_refs_key->spawns[i];
-        if (!TASK_IS_STOLEN(t)) break;
-        if (TASK_IS_COMPLETED(t)) {
-            if (j >= 40) {
-                while (j--) SYNC(lddmc_gc_mark_rec);
-                j=0;
+    }
+    else
+    {
+        SPAWN(lddmc_refs_mark_p_par, begin, count / 2);
+        CALL(lddmc_refs_mark_p_par, begin + (count / 2), count - count / 2);
+        SYNC(lddmc_refs_mark_p_par);
+    }
+}
+
+VOID_TASK_2(lddmc_refs_mark_r_par, MDD*, begin, size_t, count)
+{
+    if (count < 32)
+    {
+        while (count)
+        {
+            lddmc_gc_mark_rec(*begin++);
+            count--;
+        }
+    }
+    else
+    {
+        SPAWN(lddmc_refs_mark_r_par, begin, count / 2);
+        CALL(lddmc_refs_mark_r_par, begin + (count / 2), count - count / 2);
+        SYNC(lddmc_refs_mark_r_par);
+    }
+}
+
+VOID_TASK_2(lddmc_refs_mark_s_par, lddmc_refs_task_t, begin, size_t, count)
+{
+    if (count < 32)
+    {
+        while (count)
+        {
+            Task *t = begin->t;
+            if (!TASK_IS_STOLEN(t)) return;
+            if (t->f == begin->f && TASK_IS_COMPLETED(t))
+            {
+                lddmc_gc_mark_rec(*(SyBDD*)TASK_RESULT(t));
             }
-            SPAWN(lddmc_gc_mark_rec, *(syBDD*)TASK_RESULT(t));
-            j++;
+            begin += 1;
+            count -= 1;
         }
     }
-    while (j--) SYNC(lddmc_gc_mark_rec);
+    else
+    {
+        if (!TASK_IS_STOLEN(begin->t)) return;
+        SPAWN(lddmc_refs_mark_s_par, begin, count / 2);
+        CALL(lddmc_refs_mark_s_par, begin + (count / 2), count - count / 2);
+        SYNC(lddmc_refs_mark_s_par);
+    }
+}
+
+VOID_TASK_0(lddmc_refs_mark_task)
+{
+    LOCALIZE_THREAD_LOCAL(lddmc_refs_key, lddmc_refs_internal_t);
+    SPAWN(lddmc_refs_mark_p_par, lddmc_refs_key->pbegin, lddmc_refs_key->pcur-lddmc_refs_key->pbegin);
+    SPAWN(lddmc_refs_mark_r_par, lddmc_refs_key->rbegin, lddmc_refs_key->rcur-lddmc_refs_key->rbegin);
+    CALL(lddmc_refs_mark_s_par, lddmc_refs_key->sbegin, lddmc_refs_key->scur-lddmc_refs_key->sbegin);
+    SYNC(lddmc_refs_mark_r_par);
+    SYNC(lddmc_refs_mark_p_par);
 }
 
 VOID_TASK_0(lddmc_refs_mark)
@@ -212,12 +229,12 @@ VOID_TASK_0(lddmc_refs_mark)
 VOID_TASK_0(lddmc_refs_init_task)
 {
     lddmc_refs_internal_t s = (lddmc_refs_internal_t)malloc(sizeof(struct lddmc_refs_internal));
-    s->r_size = 128;
-    s->r_count = 0;
-    s->s_size = 128;
-    s->s_count = 0;
-    s->results = (syBDD*)malloc(sizeof(syBDD) * 128);
-    s->spawns = (Task**)malloc(sizeof(Task*) * 128);
+    s->pcur = s->pbegin = (const MDD**)malloc(sizeof(MDD*) * 1024);
+    s->pend = s->pbegin + 1024;
+    s->rcur = s->rbegin = (MDD*)malloc(sizeof(MDD) * 1024);
+    s->rend = s->rbegin + 1024;
+    s->scur = s->sbegin = (lddmc_refs_task_t)malloc(sizeof(struct lddmc_refs_task) * 1024);
+    s->send = s->sbegin + 1024;
     SET_THREAD_LOCAL(lddmc_refs_key, s);
 }
 
@@ -225,9 +242,89 @@ VOID_TASK_0(lddmc_refs_init)
 {
     INIT_THREAD_LOCAL(lddmc_refs_key);
     TOGETHER(lddmc_refs_init_task);
-    sylvan_gc_add_mark(10, TASK(lddmc_refs_mark));
+    sylvan_gc_add_mark(TASK(lddmc_refs_mark));
+}
+
+void
+lddmc_refs_ptrs_up(lddmc_refs_internal_t lddmc_refs_key)
+{
+    size_t size = lddmc_refs_key->pend - lddmc_refs_key->pbegin;
+    lddmc_refs_key->pbegin = (const MDD**)realloc(lddmc_refs_key->pbegin, sizeof(MDD*) * size * 2);
+    lddmc_refs_key->pcur = lddmc_refs_key->pbegin + size;
+    lddmc_refs_key->pend = lddmc_refs_key->pbegin + (size * 2);
+}
+
+MDD __attribute__((noinline))
+lddmc_refs_refs_up(lddmc_refs_internal_t lddmc_refs_key, MDD res)
+{
+    long size = lddmc_refs_key->rend - lddmc_refs_key->rbegin;
+    lddmc_refs_key->rbegin = (MDD*)realloc(lddmc_refs_key->rbegin, sizeof(MDD) * size * 2);
+    lddmc_refs_key->rcur = lddmc_refs_key->rbegin + size;
+    lddmc_refs_key->rend = lddmc_refs_key->rbegin + (size * 2);
+    return res;
+}
+
+void __attribute__((noinline))
+lddmc_refs_tasks_up(lddmc_refs_internal_t lddmc_refs_key)
+{
+    long size = lddmc_refs_key->send - lddmc_refs_key->sbegin;
+    lddmc_refs_key->sbegin = (lddmc_refs_task_t)realloc(lddmc_refs_key->sbegin, sizeof(struct lddmc_refs_task) * size * 2);
+    lddmc_refs_key->scur = lddmc_refs_key->sbegin + size;
+    lddmc_refs_key->send = lddmc_refs_key->sbegin + (size * 2);
+}
+
+void __attribute__((unused))
+lddmc_refs_pushptr(const MDD *ptr)
+{
+    LOCALIZE_THREAD_LOCAL(lddmc_refs_key, lddmc_refs_internal_t);
+    *lddmc_refs_key->pcur++ = ptr;
+    if (lddmc_refs_key->pcur == lddmc_refs_key->pend) lddmc_refs_ptrs_up(lddmc_refs_key);
+}
+
+void __attribute__((unused))
+lddmc_refs_popptr(size_t amount)
+{
+    LOCALIZE_THREAD_LOCAL(lddmc_refs_key, lddmc_refs_internal_t);
+    lddmc_refs_key->pcur -= amount;
+}
+
+MDD __attribute__((unused))
+lddmc_refs_push(MDD lddmc)
+{
+
+    LOCALIZE_THREAD_LOCAL(lddmc_refs_key, lddmc_refs_internal_t);
+    *(lddmc_refs_key->rcur++) = lddmc;
+    if (lddmc_refs_key->rcur == lddmc_refs_key->rend) return lddmc_refs_refs_up(lddmc_refs_key, lddmc);
+    else return lddmc;
+}
+
+void __attribute__((unused))
+lddmc_refs_pop(long amount)
+{
+    LOCALIZE_THREAD_LOCAL(lddmc_refs_key, lddmc_refs_internal_t);
+    lddmc_refs_key->rcur -= amount;
+}
+
+void __attribute__((unused))
+lddmc_refs_spawn(Task *t)
+{
+    LOCALIZE_THREAD_LOCAL(lddmc_refs_key, lddmc_refs_internal_t);
+    lddmc_refs_key->scur->t = t;
+    lddmc_refs_key->scur->f = t->f;
+    lddmc_refs_key->scur += 1;
+    if (lddmc_refs_key->scur == lddmc_refs_key->send) lddmc_refs_tasks_up(lddmc_refs_key);
 }
 
+MDD __attribute__((unused))
+lddmc_refs_sync(MDD result)
+{
+    LOCALIZE_THREAD_LOCAL(lddmc_refs_key, lddmc_refs_internal_t);
+    lddmc_refs_key->scur -= 1;
+    return result;
+}
+
+VOID_TASK_DECL_0(lddmc_gc_mark_serialize);
+
 /**
  * Initialize and quit functions
  */
@@ -235,23 +332,24 @@ VOID_TASK_0(lddmc_refs_init)
 static void
 lddmc_quit()
 {
-    refs_free(&mdd_refs);
+    refs_free(&lddmc_refs);
 }
 
 void
 sylvan_init_ldd()
 {
     sylvan_register_quit(lddmc_quit);
-    sylvan_gc_add_mark(10, TASK(lddmc_gc_mark_external_refs));
+    sylvan_gc_add_mark(TASK(lddmc_gc_mark_external_refs));
+    sylvan_gc_add_mark(TASK(lddmc_gc_mark_protected));
+    sylvan_gc_add_mark(TASK(lddmc_gc_mark_serialize));
 
-    // Sanity check
-    if (sizeof(struct mddnode) != 16) {
-        fprintf(stderr, "Invalid size of mdd nodes: %ld\n", sizeof(struct mddnode));
-        exit(1);
+    refs_create(&lddmc_refs, 1024);
+    if (!lddmc_protected_created)
+    {
+        protect_create(&lddmc_protected, 4096);
+        lddmc_protected_created = 1;
     }
 
-    refs_create(&mdd_refs, 1024);
-
     LACE_ME;
     CALL(lddmc_refs_init);
 }
@@ -265,16 +363,17 @@ lddmc_makenode(uint32_t value, MDD ifeq, MDD ifneq)
 {
     if (ifeq == lddmc_false) return ifneq;
 
-    // check if correct (should be false, or next in value)
+
     assert(ifneq != lddmc_true);
-    if (ifneq != lddmc_false) assert(value < mddnode_getvalue(GETNODE(ifneq)));
+    if (ifneq != lddmc_false) assert(value < mddnode_getvalue(LDD_GETNODE(ifneq)));
 
     struct mddnode n;
     mddnode_make(&n, value, ifneq, ifeq);
 
     int created;
     uint64_t index = llmsset_lookup(nodes, n.a, n.b, &created);
-    if (index == 0) {
+    if (index == 0)
+    {
         lddmc_refs_push(ifeq);
         lddmc_refs_push(ifneq);
         LACE_ME;
@@ -282,7 +381,8 @@ lddmc_makenode(uint32_t value, MDD ifeq, MDD ifneq)
         lddmc_refs_pop(1);
 
         index = llmsset_lookup(nodes, n.a, n.b, &created);
-        if (index == 0) {
+        if (index == 0)
+        {
             fprintf(stderr, "MDD Unique table full, %zu of %zu buckets filled!\n", llmsset_count_marked(nodes), llmsset_get_size(nodes));
             exit(1);
         }
@@ -302,7 +402,8 @@ lddmc_make_copynode(MDD ifeq, MDD ifneq)
 
     int created;
     uint64_t index = llmsset_lookup(nodes, n.a, n.b, &created);
-    if (index == 0) {
+    if (index == 0)
+    {
         lddmc_refs_push(ifeq);
         lddmc_refs_push(ifneq);
         LACE_ME;
@@ -310,7 +411,8 @@ lddmc_make_copynode(MDD ifeq, MDD ifneq)
         lddmc_refs_pop(1);
 
         index = llmsset_lookup(nodes, n.a, n.b, &created);
-        if (index == 0) {
+        if (index == 0)
+        {
             fprintf(stderr, "MDD Unique table full, %zu of %zu buckets filled!\n", llmsset_count_marked(nodes), llmsset_get_size(nodes));
             exit(1);
         }
@@ -327,7 +429,7 @@ lddmc_extendnode(MDD mdd, uint32_t value, MDD ifeq)
 {
     if (mdd <= lddmc_true) return lddmc_makenode(value, ifeq, mdd);
 
-    mddnode_t n = GETNODE(mdd);
+    mddnode_t n = LDD_GETNODE(mdd);
     if (mddnode_getcopy(n)) return lddmc_make_copynode(mddnode_getdown(n), lddmc_extendnode(mddnode_getright(n), value, ifeq));
     uint32_t n_value = mddnode_getvalue(n);
     if (n_value < value) return lddmc_makenode(n_value, mddnode_getdown(n), lddmc_extendnode(mddnode_getright(n), value, ifeq));
@@ -338,28 +440,30 @@ lddmc_extendnode(MDD mdd, uint32_t value, MDD ifeq)
 uint32_t
 lddmc_getvalue(MDD mdd)
 {
-    return mddnode_getvalue(GETNODE(mdd));
+    return mddnode_getvalue(LDD_GETNODE(mdd));
 }
 
 MDD
 lddmc_getdown(MDD mdd)
 {
-    return mddnode_getdown(GETNODE(mdd));
+    return mddnode_getdown(LDD_GETNODE(mdd));
 }
 
 MDD
 lddmc_getright(MDD mdd)
 {
-    return mddnode_getright(GETNODE(mdd));
+    return mddnode_getright(LDD_GETNODE(mdd));
 }
 
 MDD
 lddmc_follow(MDD mdd, uint32_t value)
 {
-    for (;;) {
+    for (;;)
+    {
         if (mdd <= lddmc_true) return mdd;
-        const mddnode_t n = GETNODE(mdd);
-        if (!mddnode_getcopy(n)) {
+        const mddnode_t n = LDD_GETNODE(mdd);
+        if (!mddnode_getcopy(n))
+        {
             const uint32_t v = mddnode_getvalue(n);
             if (v == value) return mddnode_getdown(n);
             if (v > value) return lddmc_false;
@@ -373,7 +477,7 @@ lddmc_iscopy(MDD mdd)
 {
     if (mdd <= lddmc_true) return 0;
 
-    mddnode_t n = GETNODE(mdd);
+    mddnode_t n = LDD_GETNODE(mdd);
     return mddnode_getcopy(n) ? 1 : 0;
 }
 
@@ -382,7 +486,7 @@ lddmc_followcopy(MDD mdd)
 {
     if (mdd <= lddmc_true) return lddmc_false;
 
-    mddnode_t n = GETNODE(mdd);
+    mddnode_t n = LDD_GETNODE(mdd);
     if (mddnode_getcopy(n)) return mddnode_getdown(n);
     else return lddmc_false;
 }
@@ -395,18 +499,22 @@ match_ldds(MDD *one, MDD *two)
 {
     MDD m1 = *one, m2 = *two;
     if (m1 == lddmc_false || m2 == lddmc_false) return 0;
-    mddnode_t n1 = GETNODE(m1), n2 = GETNODE(m2);
+    mddnode_t n1 = LDD_GETNODE(m1), n2 = LDD_GETNODE(m2);
     uint32_t v1 = mddnode_getvalue(n1), v2 = mddnode_getvalue(n2);
-    while (v1 != v2) {
-        if (v1 < v2) {
+    while (v1 != v2)
+    {
+        if (v1 < v2)
+        {
             m1 = mddnode_getright(n1);
             if (m1 == lddmc_false) return 0;
-            n1 = GETNODE(m1);
+            n1 = LDD_GETNODE(m1);
             v1 = mddnode_getvalue(n1);
-        } else if (v1 > v2) {
+        }
+        else if (v1 > v2)
+        {
             m2 = mddnode_getright(n2);
             if (m2 == lddmc_false) return 0;
-            n2 = GETNODE(m2);
+            n2 = LDD_GETNODE(m2);
             v2 = mddnode_getvalue(n2);
         }
     }
@@ -418,7 +526,6 @@ match_ldds(MDD *one, MDD *two)
 TASK_IMPL_2(MDD, lddmc_union, MDD, a, MDD, b)
 {
     /* Terminal cases */
-
     if (a == b) return a;
     if (a == lddmc_false) return b;
     if (b == lddmc_false) return a;
@@ -430,18 +537,24 @@ TASK_IMPL_2(MDD, lddmc_union, MDD, a, MDD, b)
     sylvan_stats_count(LDD_UNION);
 
     /* Improve cache behavior */
-    if (a < b) { MDD tmp=b; b=a; a=tmp; }
+    if (a < b)
+    {
+        MDD tmp=b;
+        b=a;
+        a=tmp;
+    }
 
     /* Access cache */
     MDD result;
-    if (cache_get3(CACHE_MDD_UNION, a, b, 0, &result)) {
+    if (cache_get3(CACHE_MDD_UNION, a, b, 0, &result))
+    {
         sylvan_stats_count(LDD_UNION_CACHED);
         return result;
     }
 
     /* Get nodes */
-    mddnode_t na = GETNODE(a);
-    mddnode_t nb = GETNODE(b);
+    mddnode_t na = LDD_GETNODE(a);
+    mddnode_t nb = LDD_GETNODE(b);
 
     const int na_copy = mddnode_getcopy(na) ? 1 : 0;
     const int nb_copy = mddnode_getcopy(nb) ? 1 : 0;
@@ -449,30 +562,41 @@ TASK_IMPL_2(MDD, lddmc_union, MDD, a, MDD, b)
     const uint32_t nb_value = mddnode_getvalue(nb);
 
     /* Perform recursive calculation */
-    if (na_copy && nb_copy) {
+    if (na_copy && nb_copy)
+    {
         lddmc_refs_spawn(SPAWN(lddmc_union, mddnode_getdown(na), mddnode_getdown(nb)));
         MDD right = CALL(lddmc_union, mddnode_getright(na), mddnode_getright(nb));
         lddmc_refs_push(right);
         MDD down = lddmc_refs_sync(SYNC(lddmc_union));
         lddmc_refs_pop(1);
         result = lddmc_make_copynode(down, right);
-    } else if (na_copy) {
+    }
+    else if (na_copy)
+    {
         MDD right = CALL(lddmc_union, mddnode_getright(na), b);
         result = lddmc_make_copynode(mddnode_getdown(na), right);
-    } else if (nb_copy) {
+    }
+    else if (nb_copy)
+    {
         MDD right = CALL(lddmc_union, a, mddnode_getright(nb));
         result = lddmc_make_copynode(mddnode_getdown(nb), right);
-    } else if (na_value < nb_value) {
+    }
+    else if (na_value < nb_value)
+    {
         MDD right = CALL(lddmc_union, mddnode_getright(na), b);
         result = lddmc_makenode(na_value, mddnode_getdown(na), right);
-    } else if (na_value == nb_value) {
+    }
+    else if (na_value == nb_value)
+    {
         lddmc_refs_spawn(SPAWN(lddmc_union, mddnode_getdown(na), mddnode_getdown(nb)));
         MDD right = CALL(lddmc_union, mddnode_getright(na), mddnode_getright(nb));
         lddmc_refs_push(right);
         MDD down = lddmc_refs_sync(SYNC(lddmc_union));
         lddmc_refs_pop(1);
         result = lddmc_makenode(na_value, down, right);
-    } else /* na_value > nb_value */ {
+    }
+    else /* na_value > nb_value */
+    {
         MDD right = CALL(lddmc_union, a, mddnode_getright(nb));
         result = lddmc_makenode(nb_value, mddnode_getdown(nb), right);
     }
@@ -483,10 +607,6 @@ TASK_IMPL_2(MDD, lddmc_union, MDD, a, MDD, b)
     return result;
 }
 
-
-
-
-
 TASK_IMPL_2(MDD, lddmc_minus, MDD, a, MDD, b)
 {
     /* Terminal cases */
@@ -503,29 +623,35 @@ TASK_IMPL_2(MDD, lddmc_minus, MDD, a, MDD, b)
 
     /* Access cache */
     MDD result;
-    if (cache_get3(CACHE_MDD_MINUS, a, b, 0, &result)) {
+    if (cache_get3(CACHE_MDD_MINUS, a, b, 0, &result))
+    {
         sylvan_stats_count(LDD_MINUS_CACHED);
         return result;
     }
 
     /* Get nodes */
-    mddnode_t na = GETNODE(a);
-    mddnode_t nb = GETNODE(b);
+    mddnode_t na = LDD_GETNODE(a);
+    mddnode_t nb = LDD_GETNODE(b);
     uint32_t na_value = mddnode_getvalue(na);
     uint32_t nb_value = mddnode_getvalue(nb);
 
     /* Perform recursive calculation */
-    if (na_value < nb_value) {
+    if (na_value < nb_value)
+    {
         MDD right = CALL(lddmc_minus, mddnode_getright(na), b);
         result = lddmc_makenode(na_value, mddnode_getdown(na), right);
-    } else if (na_value == nb_value) {
+    }
+    else if (na_value == nb_value)
+    {
         lddmc_refs_spawn(SPAWN(lddmc_minus, mddnode_getright(na), mddnode_getright(nb)));
         MDD down = CALL(lddmc_minus, mddnode_getdown(na), mddnode_getdown(nb));
         lddmc_refs_push(down);
         MDD right = lddmc_refs_sync(SYNC(lddmc_minus));
         lddmc_refs_pop(1);
         result = lddmc_makenode(na_value, down, right);
-    } else /* na_value > nb_value */ {
+    }
+    else /* na_value > nb_value */
+    {
         result = CALL(lddmc_minus, a, mddnode_getright(nb));
     }
 
@@ -539,15 +665,18 @@ TASK_IMPL_2(MDD, lddmc_minus, MDD, a, MDD, b)
 TASK_IMPL_3(MDD, lddmc_zip, MDD, a, MDD, b, MDD*, res2)
 {
     /* Terminal cases */
-    if (a == b) {
+    if (a == b)
+    {
         *res2 = lddmc_false;
         return a;
     }
-    if (a == lddmc_false) {
+    if (a == lddmc_false)
+    {
         *res2 = b;
         return b;
     }
-    if (b == lddmc_false) {
+    if (b == lddmc_false)
+    {
         *res2 = lddmc_false;
         return a;
     }
@@ -563,22 +692,26 @@ TASK_IMPL_3(MDD, lddmc_zip, MDD, a, MDD, b, MDD*, res2)
     /* Access cache */
     MDD result;
     if (cache_get3(CACHE_MDD_UNION, a, b, 0, &result) &&
-        cache_get3(CACHE_MDD_MINUS, b, a, 0, res2)) {
+            cache_get3(CACHE_MDD_MINUS, b, a, 0, res2))
+    {
         sylvan_stats_count(LDD_ZIP);
         return result;
     }
 
     /* Get nodes */
-    mddnode_t na = GETNODE(a);
-    mddnode_t nb = GETNODE(b);
+    mddnode_t na = LDD_GETNODE(a);
+    mddnode_t nb = LDD_GETNODE(b);
     uint32_t na_value = mddnode_getvalue(na);
     uint32_t nb_value = mddnode_getvalue(nb);
 
     /* Perform recursive calculation */
-    if (na_value < nb_value) {
+    if (na_value < nb_value)
+    {
         MDD right = CALL(lddmc_zip, mddnode_getright(na), b, res2);
         result = lddmc_makenode(na_value, mddnode_getdown(na), right);
-    } else if (na_value == nb_value) {
+    }
+    else if (na_value == nb_value)
+    {
         MDD down2, right2;
         lddmc_refs_spawn(SPAWN(lddmc_zip, mddnode_getdown(na), mddnode_getdown(nb), &down2));
         MDD right = CALL(lddmc_zip, mddnode_getright(na), mddnode_getright(nb), &right2);
@@ -588,7 +721,9 @@ TASK_IMPL_3(MDD, lddmc_zip, MDD, a, MDD, b, MDD*, res2)
         lddmc_refs_pop(2);
         result = lddmc_makenode(na_value, down, right);
         *res2 = lddmc_makenode(na_value, down2, right2);
-    } else /* na_value > nb_value */ {
+    }
+    else /* na_value > nb_value */
+    {
         MDD right2;
         MDD right = CALL(lddmc_zip, a, mddnode_getright(nb), &right2);
         result = lddmc_makenode(nb_value, mddnode_getdown(nb), right);
@@ -616,30 +751,34 @@ TASK_IMPL_2(MDD, lddmc_intersect, MDD, a, MDD, b)
     sylvan_stats_count(LDD_INTERSECT);
 
     /* Get nodes */
-    mddnode_t na = GETNODE(a);
-    mddnode_t nb = GETNODE(b);
+    mddnode_t na = LDD_GETNODE(a);
+    mddnode_t nb = LDD_GETNODE(b);
     uint32_t na_value = mddnode_getvalue(na);
     uint32_t nb_value = mddnode_getvalue(nb);
 
     /* Skip nodes if possible */
-    while (na_value != nb_value) {
-        if (na_value < nb_value) {
+    while (na_value != nb_value)
+    {
+        if (na_value < nb_value)
+        {
             a = mddnode_getright(na);
             if (a == lddmc_false) return lddmc_false;
-            na = GETNODE(a);
+            na = LDD_GETNODE(a);
             na_value = mddnode_getvalue(na);
         }
-        if (nb_value < na_value) {
+        if (nb_value < na_value)
+        {
             b = mddnode_getright(nb);
             if (b == lddmc_false) return lddmc_false;
-            nb = GETNODE(b);
+            nb = LDD_GETNODE(b);
             nb_value = mddnode_getvalue(nb);
         }
     }
 
     /* Access cache */
     MDD result;
-    if (cache_get3(CACHE_MDD_INTERSECT, a, b, 0, &result)) {
+    if (cache_get3(CACHE_MDD_INTERSECT, a, b, 0, &result))
+    {
         sylvan_stats_count(LDD_INTERSECT_CACHED);
         return result;
     }
@@ -664,7 +803,7 @@ TASK_IMPL_3(MDD, lddmc_match, MDD, a, MDD, b, MDD, proj)
     if (a == b) return a;
     if (a == lddmc_false || b == lddmc_false) return lddmc_false;
 
-    mddnode_t p_node = GETNODE(proj);
+    mddnode_t p_node = LDD_GETNODE(proj);
     uint32_t p_val = mddnode_getvalue(p_node);
     if (p_val == (uint32_t)-1) return a;
 
@@ -675,7 +814,8 @@ TASK_IMPL_3(MDD, lddmc_match, MDD, a, MDD, b, MDD, proj)
     sylvan_gc_test();
 
     /* Skip nodes if possible */
-    if (p_val == 1) {
+    if (p_val == 1)
+    {
         if (!match_ldds(&a, &b)) return lddmc_false;
     }
 
@@ -683,19 +823,23 @@ TASK_IMPL_3(MDD, lddmc_match, MDD, a, MDD, b, MDD, proj)
 
     /* Access cache */
     MDD result;
-    if (cache_get3(CACHE_MDD_MATCH, a, b, proj, &result)) {
+    if (cache_get3(CACHE_MDD_MATCH, a, b, proj, &result))
+    {
         sylvan_stats_count(LDD_MATCH_CACHED);
         return result;
     }
 
     /* Perform recursive calculation */
-    mddnode_t na = GETNODE(a);
+    mddnode_t na = LDD_GETNODE(a);
     MDD down;
-    if (p_val == 1) {
-        mddnode_t nb = GETNODE(b);
+    if (p_val == 1)
+    {
+        mddnode_t nb = LDD_GETNODE(b);
         /* right = */ lddmc_refs_spawn(SPAWN(lddmc_match, mddnode_getright(na), mddnode_getright(nb), proj));
         down = CALL(lddmc_match, mddnode_getdown(na), mddnode_getdown(nb), mddnode_getdown(p_node));
-    } else {
+    }
+    else
+    {
         /* right = */ lddmc_refs_spawn(SPAWN(lddmc_match, mddnode_getright(na), b, proj));
         down = CALL(lddmc_match, mddnode_getdown(na), b, mddnode_getdown(p_node));
     }
@@ -715,111 +859,7 @@ TASK_4(MDD, lddmc_relprod_help, uint32_t, val, MDD, set, MDD, rel, MDD, proj)
     return lddmc_makenode(val, CALL(lddmc_relprod, set, rel, proj), lddmc_false);
 }
 
-
-
-TASK_4(MDD, lddmc_firing_help, uint32_t, val, MDD, mark, MDD, minus, MDD, plus)
-{
-    return lddmc_makenode(val, CALL(lddmc_firing, mark, minus, plus), lddmc_false);
-}
-// cmark : markings enabling ; minus : tokens to remove ; plus : tokens to add
-TASK_IMPL_3(MDD,lddmc_firing, MDD, cmark, MDD, minus, MDD, plus) {
-
-
-
-     // for an empty set of source states, or an empty transition relation, return the empty set
-    if (cmark == lddmc_true) return lddmc_true;
-    if (minus == lddmc_false) return lddmc_false;
-    if (plus  == lddmc_false) return lddmc_false; // we assume that if meta is finished, then the rest is not in rel
-
-
-
-
-
-
-    /* Test gc */
-    sylvan_gc_test();
-
-    // sylvan_stats_count(LDD_RELPROD);
-
-    /* Access cache */
-    MDD result;
-    MDD _cmark=cmark, _minus=minus, _plus=plus;
-    if (cache_get3(CACHE_MDD_RELPROD, cmark, minus, plus, &result)) {
-        sylvan_stats_count(LDD_RELPROD_CACHED);
-        return result;
-    }
-
-    mddnode_t n_cmark = GETNODE(cmark);
-    mddnode_t n_plus = GETNODE(plus);
-    mddnode_t n_minus = GETNODE(minus);
-    // meta: -1 (end; rest not in rel), 0 (not in rel), 1 (read), 2 (write), 3 (only-read), 4 (only-write)
-
-    /* Recursive operations */
-
-        // write, only-write
-//        if (m_val == 4) {
-//            // only-write, so we need to include 'for all variables'
-//            // the reason is that we did not have a read phase, so we need to 'insert' a read phase here
-//
-//        }
-
-        // if we're here and we are only-write, then we read the current value
-
-        // spawn for every value to write (rel)
-
-        //lddmc_refs_spawn(SPAWN(lddmc_firing, mddnode_getright(n_cmark), minus, plus)); // next in set
-        int count = 0;
-        for (;;) {
-            uint32_t value;
-            value = mddnode_getvalue(n_cmark);
-            uint32_t value_minus = mddnode_getvalue(n_minus);
-            uint32_t value_plus = mddnode_getvalue(n_plus);
-            if (value>=value_minus) {
-               lddmc_refs_spawn(SPAWN(lddmc_firing_help, value-value_minus+value_plus, mddnode_getdown(n_cmark), mddnode_getdown(n_minus), mddnode_getdown(n_plus)));
-               count++;
-               }
-            /*else
-                return lddmc_false;*/
-
-
-            // minus = mddnode_getright(n_minus);
-            cmark = mddnode_getright(n_cmark);
-            if (cmark == lddmc_false) break;
-             n_cmark = GETNODE(cmark);
-            // n_plus = GETNODE(plus);
-        }
-
-        // sync+union (one by one)
-        result = lddmc_false;
-        while (count--) {
-            lddmc_refs_push(result);
-            MDD result2 = lddmc_refs_sync(SYNC(lddmc_firing_help));
-            lddmc_refs_push(result2);
-            result = CALL(lddmc_union, result, result2);
-            //lddmc_fprintdot(stdout,result2);
-            lddmc_refs_pop(2);
-        }
-//
-//        if (m_val == 4) {
-//            // sync+union with other variables
-//            lddmc_refs_push(result);
-//            MDD result2 = lddmc_refs_sync(SYNC(lddmc_firing));
-//            lddmc_refs_push(result2);
-//            result = CALL(lddmc_union, result, result2);
- //           lddmc_refs_pop(2);
-//        }
-
-
-    /* Write to cache */
-    if (cache_put3(CACHE_MDD_RELPROD, _cmark, _minus, _plus, result)) sylvan_stats_count(LDD_RELPROD_CACHEDPUT);
-
-    return result;
-}
-
-
-
-
-// meta: -1 (end; rest not in rel), 0 (not in rel), 1 (read), 2 (write), 3 (only-read), 4 (only-write)
+// meta: -1 (end; rest not in rel), 0 (not in rel), 1 (read), 2 (write), 3 (only-read), 4 (only-write), 5 (action label)
 TASK_IMPL_3(MDD, lddmc_relprod, MDD, set, MDD, rel, MDD, meta)
 {
     // for an empty set of source states, or an empty transition relation, return the empty set
@@ -827,20 +867,22 @@ TASK_IMPL_3(MDD, lddmc_relprod, MDD, set, MDD, rel, MDD, meta)
     if (rel == lddmc_false) return lddmc_false;
     if (meta == lddmc_true) return set; // we assume that if meta is finished, then the rest is not in rel
 
-    mddnode_t n_meta = GETNODE(meta);
+    mddnode_t n_meta = LDD_GETNODE(meta);
     uint32_t m_val = mddnode_getvalue(n_meta);
 
     // if meta is -1, then no other variables are in the transition relation
     if (m_val == (uint32_t)-1) return set;
 
     // if meta is not 0, then both set and rel must be an internal LDD node
-    if (m_val != 0) assert(set != lddmc_true && rel != lddmc_true);
+    if (m_val != 0 && m_val != 5) assert(set != lddmc_true && rel != lddmc_true);
 
     /* Skip nodes if possible */
-    if (!mddnode_getcopy(GETNODE(rel))) {
+    if (!mddnode_getcopy(LDD_GETNODE(rel)))
+    {
         // if we "read" or "only-read", then match LDDs set and rel
         // if no match, then return the empty set
-        if (m_val == 1 || m_val == 3) {
+        if (m_val == 1 || m_val == 3)
+        {
             if (!match_ldds(&set, &rel)) return lddmc_false;
         }
     }
@@ -853,49 +895,68 @@ TASK_IMPL_3(MDD, lddmc_relprod, MDD, set, MDD, rel, MDD, meta)
     /* Access cache */
     MDD result;
     MDD _set=set, _rel=rel;
-    if (cache_get3(CACHE_MDD_RELPROD, set, rel, meta, &result)) {
+    if (cache_get3(CACHE_MDD_RELPROD, set, rel, meta, &result))
+    {
         sylvan_stats_count(LDD_RELPROD_CACHED);
         return result;
     }
 
-    mddnode_t n_set = GETNODE(set);
-    mddnode_t n_rel = GETNODE(rel);
+    mddnode_t n_set = LDD_GETNODE(set);
+    mddnode_t n_rel = LDD_GETNODE(rel);
 
     /* Recursive operations */
-    if (m_val == 0) { // not in rel
+    if (m_val == 0)   // not in rel
+    {
         lddmc_refs_spawn(SPAWN(lddmc_relprod, mddnode_getright(n_set), rel, meta));
         MDD down = CALL(lddmc_relprod, mddnode_getdown(n_set), rel, mddnode_getdown(n_meta));
         lddmc_refs_push(down);
         MDD right = lddmc_refs_sync(SYNC(lddmc_relprod));
         lddmc_refs_pop(1);
         result = lddmc_makenode(mddnode_getvalue(n_set), down, right);
-    } else if (m_val == 1) { // read
+    }
+    else if (m_val == 5)     // action label
+    {
+        lddmc_refs_spawn(SPAWN(lddmc_relprod, set, mddnode_getright(n_rel), meta));
+        MDD down = CALL(lddmc_relprod, set, mddnode_getdown(n_rel), mddnode_getdown(n_meta));
+        lddmc_refs_push(down);
+        MDD right = lddmc_refs_sync(SYNC(lddmc_relprod));
+        lddmc_refs_push(right);
+        result = CALL(lddmc_union, down, right);
+        lddmc_refs_pop(2);
+    }
+    else if (m_val == 1)     // read
+    {
         // read layer: if not copy, then set&rel are already matched
         lddmc_refs_spawn(SPAWN(lddmc_relprod, set, mddnode_getright(n_rel), meta)); // spawn next read in list
 
         // for this read, either it is copy ('for all') or it is normal match
-        if (mddnode_getcopy(n_rel)) {
+        if (mddnode_getcopy(n_rel))
+        {
             // spawn for every value to copy (set)
             int count = 0;
-            for (;;) {
+            for (;;)
+            {
                 // stay same level of set (for write)
                 lddmc_refs_spawn(SPAWN(lddmc_relprod, set, mddnode_getdown(n_rel), mddnode_getdown(n_meta)));
                 count++;
                 set = mddnode_getright(n_set);
                 if (set == lddmc_false) break;
-                n_set = GETNODE(set);
+                n_set = LDD_GETNODE(set);
             }
 
             // sync+union (one by one)
             result = lddmc_false;
-            while (count--) {
+            while (count--)
+            {
                 lddmc_refs_push(result);
                 MDD result2 = lddmc_refs_sync(SYNC(lddmc_relprod));
                 lddmc_refs_push(result2);
                 result = CALL(lddmc_union, result, result2);
                 lddmc_refs_pop(2);
             }
-        } else {
+        }
+        else
+        {
             // stay same level of set (for write)
             result = CALL(lddmc_relprod, set, mddnode_getdown(n_rel), mddnode_getdown(n_meta));
         }
@@ -905,25 +966,30 @@ TASK_IMPL_3(MDD, lddmc_relprod, MDD, set, MDD, rel, MDD, meta)
         lddmc_refs_push(result2);
         result = CALL(lddmc_union, result, result2);
         lddmc_refs_pop(2);
-    } else if (m_val == 3) { // only-read
-        if (mddnode_getcopy(n_rel)) {
+    }
+    else if (m_val == 3)     // only-read
+    {
+        if (mddnode_getcopy(n_rel))
+        {
             // copy on read ('for any value')
             // result = union(result_with_copy, result_without_copy)
             lddmc_refs_spawn(SPAWN(lddmc_relprod, set, mddnode_getright(n_rel), meta)); // spawn without_copy
 
             // spawn for every value to copy (set)
             int count = 0;
-            for (;;) {
+            for (;;)
+            {
                 lddmc_refs_spawn(SPAWN(lddmc_relprod_help, mddnode_getvalue(n_set), mddnode_getdown(n_set), mddnode_getdown(n_rel), mddnode_getdown(n_meta)));
                 count++;
                 set = mddnode_getright(n_set);
                 if (set == lddmc_false) break;
-                n_set = GETNODE(set);
+                n_set = LDD_GETNODE(set);
             }
 
             // sync+union (one by one)
             result = lddmc_false;
-            while (count--) {
+            while (count--)
+            {
                 lddmc_refs_push(result);
                 MDD result2 = lddmc_refs_sync(SYNC(lddmc_relprod_help));
                 lddmc_refs_push(result2);
@@ -937,7 +1003,9 @@ TASK_IMPL_3(MDD, lddmc_relprod, MDD, set, MDD, rel, MDD, meta)
             lddmc_refs_push(result2);
             result = CALL(lddmc_union, result, result2);
             lddmc_refs_pop(2);
-        } else {
+        }
+        else
+        {
             // only-read, without copy
             lddmc_refs_spawn(SPAWN(lddmc_relprod, mddnode_getright(n_set), mddnode_getright(n_rel), meta));
             MDD down = CALL(lddmc_relprod, mddnode_getdown(n_set), mddnode_getdown(n_rel), mddnode_getdown(n_meta));
@@ -946,9 +1014,12 @@ TASK_IMPL_3(MDD, lddmc_relprod, MDD, set, MDD, rel, MDD, meta)
             lddmc_refs_pop(1);
             result = lddmc_makenode(mddnode_getvalue(n_set), down, right);
         }
-    } else if (m_val == 2 || m_val == 4) {
+    }
+    else if (m_val == 2 || m_val == 4)
+    {
         // write, only-write
-        if (m_val == 4) {
+        if (m_val == 4)
+        {
             // only-write, so we need to include 'for all variables'
             // the reason is that we did not have a read phase, so we need to 'insert' a read phase here
             lddmc_refs_spawn(SPAWN(lddmc_relprod, mddnode_getright(n_set), rel, meta)); // next in set
@@ -958,7 +1029,8 @@ TASK_IMPL_3(MDD, lddmc_relprod, MDD, set, MDD, rel, MDD, meta)
 
         // spawn for every value to write (rel)
         int count = 0;
-        for (;;) {
+        for (;;)
+        {
             uint32_t value;
             if (mddnode_getcopy(n_rel)) value = mddnode_getvalue(n_set);
             else value = mddnode_getvalue(n_rel);
@@ -966,12 +1038,13 @@ TASK_IMPL_3(MDD, lddmc_relprod, MDD, set, MDD, rel, MDD, meta)
             count++;
             rel = mddnode_getright(n_rel);
             if (rel == lddmc_false) break;
-            n_rel = GETNODE(rel);
+            n_rel = LDD_GETNODE(rel);
         }
 
         // sync+union (one by one)
         result = lddmc_false;
-        while (count--) {
+        while (count--)
+        {
             lddmc_refs_push(result);
             MDD result2 = lddmc_refs_sync(SYNC(lddmc_relprod_help));
             lddmc_refs_push(result2);
@@ -979,7 +1052,8 @@ TASK_IMPL_3(MDD, lddmc_relprod, MDD, set, MDD, rel, MDD, meta)
             lddmc_refs_pop(2);
         }
 
-        if (m_val == 4) {
+        if (m_val == 4)
+        {
             // sync+union with other variables
             lddmc_refs_push(result);
             MDD result2 = lddmc_refs_sync(SYNC(lddmc_relprod));
@@ -1000,12 +1074,6 @@ TASK_5(MDD, lddmc_relprod_union_help, uint32_t, val, MDD, set, MDD, rel, MDD, pr
     return lddmc_makenode(val, CALL(lddmc_relprod_union, set, rel, proj, un), lddmc_false);
 }
 
-// cmark : markings enabling ; minus : tokens to remove ; plus : tokens to add
-TASK_IMPL_3(MDD,lddmc_firing_union, MDD, cmark, MDD, minus, MDD, plus) {
-    return lddmc_union(cmark,lddmc_firing(cmark,minus,plus));
-}
-
-
 // meta: -1 (end; rest not in rel), 0 (not in rel), 1 (read), 2 (write), 3 (only-read), 4 (only-write)
 TASK_IMPL_4(MDD, lddmc_relprod_union, MDD, set, MDD, rel, MDD, meta, MDD, un)
 {
@@ -1014,18 +1082,20 @@ TASK_IMPL_4(MDD, lddmc_relprod_union, MDD, set, MDD, rel, MDD, meta, MDD, un)
     if (un == lddmc_false) return CALL(lddmc_relprod, set, rel, meta);
     if (meta == lddmc_true) return CALL(lddmc_union, set, un);
 
-    mddnode_t n_meta = GETNODE(meta);
+    mddnode_t n_meta = LDD_GETNODE(meta);
     uint32_t m_val = mddnode_getvalue(n_meta);
     if (m_val == (uint32_t)-1) return CALL(lddmc_union, set, un);
 
     // check depths (this triggers on logic error)
-    if (m_val != 0) assert(set != lddmc_true && rel != lddmc_true && un != lddmc_true);
+    if (m_val != 0 && m_val != 5) assert(set != lddmc_true && rel != lddmc_true && un != lddmc_true);
 
     /* Skip nodes if possible */
-    if (!mddnode_getcopy(GETNODE(rel))) {
+    if (!mddnode_getcopy(LDD_GETNODE(rel)))
+    {
         // if we "read" or "only-read", then match LDDs set and rel
         // if no match, then return un (the empty set union un)
-        if (m_val == 1 || m_val == 3) {
+        if (m_val == 1 || m_val == 3)
+        {
             if (!match_ldds(&set, &rel)) return un;
         }
     }
@@ -1038,34 +1108,41 @@ TASK_IMPL_4(MDD, lddmc_relprod_union, MDD, set, MDD, rel, MDD, meta, MDD, un)
     /* Access cache */
     MDD result;
     MDD _set=set, _rel=rel, _un=un;
-    if (cache_get4(CACHE_MDD_RELPROD, set, rel, meta, un, &result)) {
+    if (cache_get4(CACHE_MDD_RELPROD, set, rel, meta, un, &result))
+    {
         sylvan_stats_count(LDD_RELPROD_UNION_CACHED);
         return result;
     }
 
     /* Get nodes */
-    mddnode_t n_set = GETNODE(set);
-    mddnode_t n_rel = GETNODE(rel);
-    mddnode_t n_un = GETNODE(un);
+    mddnode_t n_set = LDD_GETNODE(set);
+    mddnode_t n_rel = LDD_GETNODE(rel);
+    mddnode_t n_un = LDD_GETNODE(un);
 
     /* Now check the special cases where we can determine that un.value < result.value */
-    if (m_val == 0 || m_val == 3) {
+    if (m_val == 0 || m_val == 3)
+    {
         // if m_val == 0, no read/write, then result.value = set.value
         // if m_val == 3, only read (write same regardless of copy), then result.value = set.value
         uint32_t set_value = mddnode_getvalue(n_set);
         uint32_t un_value = mddnode_getvalue(n_un);
-        if (un_value < set_value) {
+        if (un_value < set_value)
+        {
             MDD right = CALL(lddmc_relprod_union, set, rel, meta, mddnode_getright(n_un));
             if (right == mddnode_getright(n_un)) return un;
             else return lddmc_makenode(mddnode_getvalue(n_un), mddnode_getdown(n_un), right);
         }
-    } else if (m_val == 2 || m_val == 4) {
+    }
+    else if (m_val == 2 || m_val == 4)
+    {
         // if we write, then we only know for certain that un.value < result.value if
         // the root of rel is not a copy node
-        if (!mddnode_getcopy(n_rel)) {
+        if (!mddnode_getcopy(n_rel))
+        {
             uint32_t rel_value = mddnode_getvalue(n_rel);
             uint32_t un_value = mddnode_getvalue(n_un);
-            if (un_value < rel_value) {
+            if (un_value < rel_value)
+            {
                 MDD right = CALL(lddmc_relprod_union, set, rel, meta, mddnode_getright(n_un));
                 if (right == mddnode_getright(n_un)) return un;
                 else return lddmc_makenode(mddnode_getvalue(n_un), mddnode_getdown(n_un), right);
@@ -1074,12 +1151,14 @@ TASK_IMPL_4(MDD, lddmc_relprod_union, MDD, set, MDD, rel, MDD, meta, MDD, un)
     }
 
     /* Recursive operations */
-    if (m_val == 0) {
+    if (m_val == 0)
+    {
         // current <set> is not in the transition relation
         uint32_t set_value = mddnode_getvalue(n_set);
         uint32_t un_value = mddnode_getvalue(n_un);
         // set_value > un_value already checked above
-        if (set_value < un_value) {
+        if (set_value < un_value)
+        {
             lddmc_refs_spawn(SPAWN(lddmc_relprod_union, mddnode_getright(n_set), rel, meta, un));
             // going down, we don't need _union, since un does not contain this subtree
             MDD down = CALL(lddmc_relprod, mddnode_getdown(n_set), rel, mddnode_getdown(n_meta));
@@ -1087,7 +1166,9 @@ TASK_IMPL_4(MDD, lddmc_relprod_union, MDD, set, MDD, rel, MDD, meta, MDD, un)
             MDD right = lddmc_refs_sync(SYNC(lddmc_relprod_union));
             lddmc_refs_pop(1);
             result = lddmc_makenode(mddnode_getvalue(n_set), down, right);
-        } else /* set_value == un_value */ {
+        }
+        else /* set_value == un_value */
+        {
             assert(set_value == un_value);
             lddmc_refs_spawn(SPAWN(lddmc_relprod_union, mddnode_getright(n_set), rel, meta, mddnode_getright(n_un)));
             MDD down = CALL(lddmc_relprod_union, mddnode_getdown(n_set), rel, mddnode_getdown(n_meta), mddnode_getdown(n_un));
@@ -1097,33 +1178,50 @@ TASK_IMPL_4(MDD, lddmc_relprod_union, MDD, set, MDD, rel, MDD, meta, MDD, un)
             if (right == mddnode_getright(n_un) && down == mddnode_getdown(n_un)) result = un;
             else result = lddmc_makenode(mddnode_getvalue(n_set), down, right);
         }
-    } else if (m_val == 1) {
+    }
+    else if (m_val == 5)
+    {
+        lddmc_refs_spawn(SPAWN(lddmc_relprod_union, set, mddnode_getright(n_rel), meta, un));
+        MDD down = CALL(lddmc_relprod_union, set, mddnode_getdown(n_rel), mddnode_getdown(n_meta), un);
+        lddmc_refs_push(down);
+        MDD right = lddmc_refs_sync(SYNC(lddmc_relprod_union));
+        lddmc_refs_push(right);
+        result = CALL(lddmc_union, down, right);
+        lddmc_refs_pop(2);
+    }
+    else if (m_val == 1)
+    {
         // First we also spawn for the next read value, and merge results after
         lddmc_refs_spawn(SPAWN(lddmc_relprod_union, set, mddnode_getright(n_rel), meta, un));
 
         // for this read, either it is a copy read ('for all') or it is normal match
-        if (mddnode_getcopy(n_rel)) {
+        if (mddnode_getcopy(n_rel))
+        {
             // spawn for every value in set (copy = for all)
             int count = 0;
-            for (;;) {
+            for (;;)
+            {
                 // stay same level of set and un (for write level, this was no only-read)
                 lddmc_refs_spawn(SPAWN(lddmc_relprod_union, set, mddnode_getdown(n_rel), mddnode_getdown(n_meta), un));
                 count++;
                 set = mddnode_getright(n_set);
                 if (set == lddmc_false) break;
-                n_set = GETNODE(set);
+                n_set = LDD_GETNODE(set);
             }
 
             // sync+union (one by one)
             result = lddmc_false;
-            while (count--) {
+            while (count--)
+            {
                 lddmc_refs_push(result);
                 MDD result2 = lddmc_refs_sync(SYNC(lddmc_relprod_union));
                 lddmc_refs_push(result2);
                 result = CALL(lddmc_union, result, result2);
                 lddmc_refs_pop(2);
             }
-        } else {
+        }
+        else
+        {
             // read level: if not copy read, then set and rel are already matched
             // stay same level of set and un (for write level, this was no only-read)
             result = CALL(lddmc_relprod_union, set, mddnode_getdown(n_rel), mddnode_getdown(n_meta), un);
@@ -1135,9 +1233,12 @@ TASK_IMPL_4(MDD, lddmc_relprod_union, MDD, set, MDD, rel, MDD, meta, MDD, un)
         lddmc_refs_push(result2);
         result = CALL(lddmc_union, result, result2);
         lddmc_refs_pop(2);
-    } else if (m_val == 3) { // only-read
+    }
+    else if (m_val == 3)     // only-read
+    {
         // un < set already checked above
-        if (mddnode_getcopy(n_rel)) {
+        if (mddnode_getcopy(n_rel))
+        {
             // copy on read ('for any value')
             // result = union(result_with_copy, result_without_copy)
             lddmc_refs_spawn(SPAWN(lddmc_relprod_union, set, mddnode_getright(n_rel), meta, un)); // spawn without_copy
@@ -1145,54 +1246,66 @@ TASK_IMPL_4(MDD, lddmc_relprod_union, MDD, set, MDD, rel, MDD, meta, MDD, un)
             // spawn for every value to copy (iterate over set)
             int count = 0;
             //result = lddmc_false;
-            for (;;) {
+            for (;;)
+            {
                 uint32_t set_value = mddnode_getvalue(n_set);
                 uint32_t un_value = mddnode_getvalue(n_un);
-                if (un_value < set_value) {
+                if (un_value < set_value)
+                {
                     // this is a bit tricky because the SYNC assumes we SPAWN a relprod_union_help
                     // the result of this will simply be "un_value, mddnode_getdown(n_un), false" which is intended
                     lddmc_refs_spawn(SPAWN(lddmc_relprod_union_help, un_value, lddmc_false, lddmc_false, lddmc_true, mddnode_getdown(n_un)));
                     count++;
                     un = mddnode_getright(n_un);
-                    if (un == lddmc_false) {
+                    if (un == lddmc_false)
+                    {
                         // if un is now false, then we have a normal relprod for the rest...
                         result = CALL(lddmc_relprod, set, rel, meta);
                         break;
                     }
-                    n_un = GETNODE(un);
-                } else if (un_value > set_value) {
+                    n_un = LDD_GETNODE(un);
+                }
+                else if (un_value > set_value)
+                {
                     // this is a bit tricky because the SYNC assumes we SPAWN a relprod_union_help
                     // the result of this will simply be a normal relprod
                     lddmc_refs_spawn(SPAWN(lddmc_relprod_union_help, set_value, mddnode_getdown(n_set), mddnode_getdown(n_rel), mddnode_getdown(n_meta), lddmc_false));
                     count++;
                     set = mddnode_getright(n_set);
-                    if (set == lddmc_false) {
+                    if (set == lddmc_false)
+                    {
                         // if set is now false, then the tail result to merge with is un
                         result = un;
                         break;
                     }
-                    n_set = GETNODE(set);
-                } else /* un_value == set_value */ {
+                    n_set = LDD_GETNODE(set);
+                }
+                else /* un_value == set_value */
+                {
                     lddmc_refs_spawn(SPAWN(lddmc_relprod_union_help, set_value, mddnode_getdown(n_set), mddnode_getdown(n_rel), mddnode_getdown(n_meta), mddnode_getdown(n_un)));
                     count++;
                     set = mddnode_getright(n_set);
                     un = mddnode_getright(n_un);
-                    if (set == lddmc_false) {
+                    if (set == lddmc_false)
+                    {
                         // if set is now false, then the tail result to merge with is un
                         result = un;
                         break;
-                    } else if (un == lddmc_false) {
+                    }
+                    else if (un == lddmc_false)
+                    {
                         // if un is now false, then we have a normal relprod for the rest...
                         result = CALL(lddmc_relprod, set, rel, meta);
                         break;
                     }
-                    n_set = GETNODE(set);
-                    n_un = GETNODE(un);
+                    n_set = LDD_GETNODE(set);
+                    n_un = LDD_GETNODE(un);
                 }
             }
 
             // sync+union (one by one)
-            while (count--) {
+            while (count--)
+            {
                 lddmc_refs_push(result);
                 MDD result2 = lddmc_refs_sync(SYNC(lddmc_relprod_union_help));
                 lddmc_refs_push(result2);
@@ -1206,20 +1319,25 @@ TASK_IMPL_4(MDD, lddmc_relprod_union, MDD, set, MDD, rel, MDD, meta, MDD, un)
             lddmc_refs_push(result2);
             result = CALL(lddmc_union, result, result2);
             lddmc_refs_pop(2);
-        } else {
+        }
+        else
+        {
             // only-read, not a copy node
             uint32_t set_value = mddnode_getvalue(n_set);
             uint32_t un_value = mddnode_getvalue(n_un);
 
             // we already checked un_value < set_value
-            if (un_value > set_value) {
+            if (un_value > set_value)
+            {
                 lddmc_refs_spawn(SPAWN(lddmc_relprod_union, mddnode_getright(n_set), mddnode_getright(n_rel), meta, un));
                 MDD down = CALL(lddmc_relprod, mddnode_getdown(n_set), mddnode_getdown(n_rel), mddnode_getdown(n_meta));
                 lddmc_refs_push(down);
                 MDD right = lddmc_refs_sync(SYNC(lddmc_relprod_union));
                 lddmc_refs_pop(1);
                 result = lddmc_makenode(set_value, down, right);
-            } else /* un_value == set_value */ {
+            }
+            else /* un_value == set_value */
+            {
                 assert(un_value == set_value);
                 lddmc_refs_spawn(SPAWN(lddmc_relprod_union, mddnode_getright(n_set), mddnode_getright(n_rel), meta, mddnode_getright(n_un)));
                 MDD down = CALL(lddmc_relprod_union, mddnode_getdown(n_set), mddnode_getdown(n_rel), mddnode_getdown(n_meta), mddnode_getdown(n_un));
@@ -1229,58 +1347,73 @@ TASK_IMPL_4(MDD, lddmc_relprod_union, MDD, set, MDD, rel, MDD, meta, MDD, un)
                 result = lddmc_makenode(set_value, down, right);
             }
         }
-    } else if (m_val == 2 || m_val == 4) { // write, only-write
-        if (m_val == 4) {
+    }
+    else if (m_val == 2 || m_val == 4)     // write, only-write
+    {
+        if (m_val == 4)
+        {
             // only-write, so we need to include 'for all variables' because we did not 'read'
             lddmc_refs_spawn(SPAWN(lddmc_relprod_union, mddnode_getright(n_set), rel, meta, un)); // next in set
         }
 
         // spawn for every value to write (rel)
         int count = 0;
-        for (;;) {
+        for (;;)
+        {
             uint32_t value;
             // we write 'set' if copy node, or 'rel' otherwise
             if (mddnode_getcopy(n_rel)) value = mddnode_getvalue(n_set);
             else value = mddnode_getvalue(n_rel);
             uint32_t un_value = mddnode_getvalue(n_un);
-            if (un_value < value) {
+            if (un_value < value)
+            {
                 // the result of this will simply be "un_value, mddnode_getdown(n_un), false" which is intended
                 lddmc_refs_spawn(SPAWN(lddmc_relprod_union_help, un_value, lddmc_false, lddmc_false, lddmc_true, mddnode_getdown(n_un)));
                 count++;
                 un = mddnode_getright(n_un);
-                if (un == lddmc_false) {
+                if (un == lddmc_false)
+                {
                     result = CALL(lddmc_relprod, set, rel, meta);
                     break;
                 }
-                n_un = GETNODE(un);
-            } else if (un_value > value) {
+                n_un = LDD_GETNODE(un);
+            }
+            else if (un_value > value)
+            {
                 lddmc_refs_spawn(SPAWN(lddmc_relprod_union_help, value, mddnode_getdown(n_set), mddnode_getdown(n_rel), mddnode_getdown(n_meta), lddmc_false));
                 count++;
                 rel = mddnode_getright(n_rel);
-                if (rel == lddmc_false) {
+                if (rel == lddmc_false)
+                {
                     result = un;
                     break;
                 }
-                n_rel = GETNODE(rel);
-            } else /* un_value == value */ {
+                n_rel = LDD_GETNODE(rel);
+            }
+            else /* un_value == value */
+            {
                 lddmc_refs_spawn(SPAWN(lddmc_relprod_union_help, value, mddnode_getdown(n_set), mddnode_getdown(n_rel), mddnode_getdown(n_meta), mddnode_getdown(n_un)));
                 count++;
                 rel = mddnode_getright(n_rel);
                 un = mddnode_getright(n_un);
-                if (rel == lddmc_false) {
+                if (rel == lddmc_false)
+                {
                     result = un;
                     break;
-                } else if (un == lddmc_false) {
+                }
+                else if (un == lddmc_false)
+                {
                     result = CALL(lddmc_relprod, set, rel, meta);
                     break;
                 }
-                n_rel = GETNODE(rel);
-                n_un = GETNODE(un);
+                n_rel = LDD_GETNODE(rel);
+                n_un = LDD_GETNODE(un);
             }
         }
 
         // sync+union (one by one)
-        while (count--) {
+        while (count--)
+        {
             lddmc_refs_push(result);
             MDD result2 = lddmc_refs_sync(SYNC(lddmc_relprod_union_help));
             lddmc_refs_push(result2);
@@ -1288,7 +1421,8 @@ TASK_IMPL_4(MDD, lddmc_relprod_union, MDD, set, MDD, rel, MDD, meta, MDD, un)
             lddmc_refs_pop(2);
         }
 
-        if (m_val == 4) {
+        if (m_val == 4)
+        {
             // sync+union with other variables
             lddmc_refs_push(result);
             MDD result2 = lddmc_refs_sync(SYNC(lddmc_relprod_union));
@@ -1312,7 +1446,7 @@ TASK_5(MDD, lddmc_relprev_help, uint32_t, val, MDD, set, MDD, rel, MDD, proj, MD
 /**
  * Calculate all predecessors to a in uni according to rel[meta]
  * <meta> follows the same semantics as relprod
- * i.e. 0 (not in rel), 1 (read), 2 (write), 3 (only-read), 4 (only-write), -1 (end; rest=0)
+ * i.e. 0 (not in rel), 1 (read), 2 (write), 3 (only-read), 4 (only-write), -1 (end; rest=0), 5 (action label)
  */
 TASK_IMPL_4(MDD, lddmc_relprev, MDD, set, MDD, rel, MDD, meta, MDD, uni)
 {
@@ -1320,62 +1454,79 @@ TASK_IMPL_4(MDD, lddmc_relprev, MDD, set, MDD, rel, MDD, meta, MDD, uni)
     if (rel == lddmc_false) return lddmc_false;
     if (uni == lddmc_false) return lddmc_false;
 
-    mddnode_t n_meta = GETNODE(meta);
+    mddnode_t n_meta = LDD_GETNODE(meta);
     uint32_t m_val = mddnode_getvalue(n_meta);
-    if (m_val == (uint32_t)-1) {
+    if (m_val == (uint32_t)-1)
+    {
         if (set == uni) return set;
         else return lddmc_intersect(set, uni);
     }
 
-    if (m_val != 0) assert(set != lddmc_true && rel != lddmc_true && uni != lddmc_true);
+    if (m_val != 0 && m_val != 5) assert(set != lddmc_true && rel != lddmc_true && uni != lddmc_true);
 
     /* Skip nodes if possible */
-    if (m_val == 0) {
+    if (m_val == 0)
+    {
         // not in rel: match set and uni ('intersect')
         if (!match_ldds(&set, &uni)) return lddmc_false;
-    } else if (mddnode_getcopy(GETNODE(rel))) {
+    }
+    else if (mddnode_getcopy(LDD_GETNODE(rel)))
+    {
         // read+copy: no matching (pre is everything in uni)
         // write+copy: no matching (match after split: set and uni)
         // only-read+copy: match set and uni
         // only-write+copy: no matching (match after split: set and uni)
-        if (m_val == 3) {
+        if (m_val == 3)
+        {
             if (!match_ldds(&set, &uni)) return lddmc_false;
         }
-    } else if (m_val == 1) {
+    }
+    else if (m_val == 1)
+    {
         // read: match uni and rel
         if (!match_ldds(&uni, &rel)) return lddmc_false;
-    } else if (m_val == 2) {
+    }
+    else if (m_val == 2)
+    {
         // write: match set and rel
         if (!match_ldds(&set, &rel)) return lddmc_false;
-    } else if (m_val == 3) {
+    }
+    else if (m_val == 3)
+    {
         // only-read: match uni and set and rel
-        mddnode_t n_set = GETNODE(set);
-        mddnode_t n_rel = GETNODE(rel);
-        mddnode_t n_uni = GETNODE(uni);
+        mddnode_t n_set = LDD_GETNODE(set);
+        mddnode_t n_rel = LDD_GETNODE(rel);
+        mddnode_t n_uni = LDD_GETNODE(uni);
         uint32_t n_set_value = mddnode_getvalue(n_set);
         uint32_t n_rel_value = mddnode_getvalue(n_rel);
         uint32_t n_uni_value = mddnode_getvalue(n_uni);
-        while (n_uni_value != n_rel_value || n_rel_value != n_set_value) {
-            if (n_uni_value < n_rel_value || n_uni_value < n_set_value) {
+        while (n_uni_value != n_rel_value || n_rel_value != n_set_value)
+        {
+            if (n_uni_value < n_rel_value || n_uni_value < n_set_value)
+            {
                 uni = mddnode_getright(n_uni);
                 if (uni == lddmc_false) return lddmc_false;
-                n_uni = GETNODE(uni);
+                n_uni = LDD_GETNODE(uni);
                 n_uni_value = mddnode_getvalue(n_uni);
             }
-            if (n_set_value < n_rel_value || n_set_value < n_uni_value) {
+            if (n_set_value < n_rel_value || n_set_value < n_uni_value)
+            {
                 set = mddnode_getright(n_set);
                 if (set == lddmc_false) return lddmc_false;
-                n_set = GETNODE(set);
+                n_set = LDD_GETNODE(set);
                 n_set_value = mddnode_getvalue(n_set);
             }
-            if (n_rel_value < n_set_value || n_rel_value < n_uni_value) {
+            if (n_rel_value < n_set_value || n_rel_value < n_uni_value)
+            {
                 rel = mddnode_getright(n_rel);
                 if (rel == lddmc_false) return lddmc_false;
-                n_rel = GETNODE(rel);
+                n_rel = LDD_GETNODE(rel);
                 n_rel_value = mddnode_getvalue(n_rel);
             }
         }
-    } else if (m_val == 4) {
+    }
+    else if (m_val == 4)
+    {
         // only-write: match set and rel (then use whole universe)
         if (!match_ldds(&set, &rel)) return lddmc_false;
     }
@@ -1388,17 +1539,19 @@ TASK_IMPL_4(MDD, lddmc_relprev, MDD, set, MDD, rel, MDD, meta, MDD, uni)
     /* Access cache */
     MDD result;
     MDD _set=set, _rel=rel, _uni=uni;
-    if (cache_get4(CACHE_MDD_RELPREV, set, rel, meta, uni, &result)) {
+    if (cache_get4(CACHE_MDD_RELPREV, set, rel, meta, uni, &result))
+    {
         sylvan_stats_count(LDD_RELPREV_CACHED);
         return result;
     }
 
-    mddnode_t n_set = GETNODE(set);
-    mddnode_t n_rel = GETNODE(rel);
-    mddnode_t n_uni = GETNODE(uni);
+    mddnode_t n_set = LDD_GETNODE(set);
+    mddnode_t n_rel = LDD_GETNODE(rel);
+    mddnode_t n_uni = LDD_GETNODE(uni);
 
     /* Recursive operations */
-    if (m_val == 0) { // not in rel
+    if (m_val == 0)   // not in rel
+    {
         // m_val == 0 : not in rel (intersection set and universe)
         lddmc_refs_spawn(SPAWN(lddmc_relprev, mddnode_getright(n_set), rel, meta, mddnode_getright(n_uni)));
         MDD down = CALL(lddmc_relprev, mddnode_getdown(n_set), rel, mddnode_getdown(n_meta), mddnode_getdown(n_uni));
@@ -1406,32 +1559,49 @@ TASK_IMPL_4(MDD, lddmc_relprev, MDD, set, MDD, rel, MDD, meta, MDD, uni)
         MDD right = lddmc_refs_sync(SYNC(lddmc_relprev));
         lddmc_refs_pop(1);
         result = lddmc_makenode(mddnode_getvalue(n_set), down, right);
-    } else if (m_val == 1) { // read level
+    }
+    else if (m_val == 5)
+    {
+        lddmc_refs_spawn(SPAWN(lddmc_relprev, set, mddnode_getright(n_rel), meta, uni));
+        MDD down = CALL(lddmc_relprev, set, mddnode_getdown(n_rel), mddnode_getdown(n_meta), uni);
+        lddmc_refs_push(down);
+        MDD right = lddmc_refs_sync(SYNC(lddmc_relprev));
+        lddmc_refs_push(right);
+        result = CALL(lddmc_union, down, right);
+        lddmc_refs_pop(2);
+    }
+    else if (m_val == 1)     // read level
+    {
         // result value is in case of copy: everything in uni!
         // result value is in case of not-copy: match uni and rel!
         lddmc_refs_spawn(SPAWN(lddmc_relprev, set, mddnode_getright(n_rel), meta, uni)); // next in rel
-        if (mddnode_getcopy(n_rel)) {
+        if (mddnode_getcopy(n_rel))
+        {
             // result is everything in uni
             // spawn for every value to have been read (uni)
             int count = 0;
-            for (;;) {
+            for (;;)
+            {
                 lddmc_refs_spawn(SPAWN(lddmc_relprev_help, mddnode_getvalue(n_uni), set, mddnode_getdown(n_rel), mddnode_getdown(n_meta), uni));
                 count++;
                 uni = mddnode_getright(n_uni);
                 if (uni == lddmc_false) break;
-                n_uni = GETNODE(uni);
+                n_uni = LDD_GETNODE(uni);
             }
 
             // sync+union (one by one)
             result = lddmc_false;
-            while (count--) {
+            while (count--)
+            {
                 lddmc_refs_push(result);
                 MDD result2 = lddmc_refs_sync(SYNC(lddmc_relprev_help));
                 lddmc_refs_push(result2);
                 result = CALL(lddmc_union, result, result2);
                 lddmc_refs_pop(2);
             }
-        } else {
+        }
+        else
+        {
             // already matched
             MDD down = CALL(lddmc_relprev, set, mddnode_getdown(n_rel), mddnode_getdown(n_meta), uni);
             result = lddmc_makenode(mddnode_getvalue(n_uni), down, lddmc_false);
@@ -1441,32 +1611,39 @@ TASK_IMPL_4(MDD, lddmc_relprev, MDD, set, MDD, rel, MDD, meta, MDD, uni)
         lddmc_refs_push(result2);
         result = CALL(lddmc_union, result, result2);
         lddmc_refs_pop(2);
-    } else if (m_val == 3) { // only-read level
+    }
+    else if (m_val == 3)     // only-read level
+    {
         // result value is in case of copy: match set and uni! (already done first match)
         // result value is in case of not-copy: match set and uni and rel!
         lddmc_refs_spawn(SPAWN(lddmc_relprev, set, mddnode_getright(n_rel), meta, uni)); // next in rel
-        if (mddnode_getcopy(n_rel)) {
+        if (mddnode_getcopy(n_rel))
+        {
             // spawn for every matching set+uni
             int count = 0;
-            for (;;) {
+            for (;;)
+            {
                 lddmc_refs_spawn(SPAWN(lddmc_relprev_help, mddnode_getvalue(n_uni), mddnode_getdown(n_set), mddnode_getdown(n_rel), mddnode_getdown(n_meta), mddnode_getdown(n_uni)));
                 count++;
                 uni = mddnode_getright(n_uni);
                 if (!match_ldds(&set, &uni)) break;
-                n_set = GETNODE(set);
-                n_uni = GETNODE(uni);
+                n_set = LDD_GETNODE(set);
+                n_uni = LDD_GETNODE(uni);
             }
 
             // sync+union (one by one)
             result = lddmc_false;
-            while (count--) {
+            while (count--)
+            {
                 lddmc_refs_push(result);
                 MDD result2 = lddmc_refs_sync(SYNC(lddmc_relprev_help));
                 lddmc_refs_push(result2);
                 result = CALL(lddmc_union, result, result2);
                 lddmc_refs_pop(2);
             }
-        } else {
+        }
+        else
+        {
             // already matched
             MDD down = CALL(lddmc_relprev, mddnode_getdown(n_set), mddnode_getdown(n_rel), mddnode_getdown(n_meta), mddnode_getdown(n_uni));
             result = lddmc_makenode(mddnode_getvalue(n_uni), down, lddmc_false);
@@ -1476,19 +1653,27 @@ TASK_IMPL_4(MDD, lddmc_relprev, MDD, set, MDD, rel, MDD, meta, MDD, uni)
         lddmc_refs_push(result2);
         result = CALL(lddmc_union, result, result2);
         lddmc_refs_pop(2);
-    } else if (m_val == 2) { // write level
+    }
+    else if (m_val == 2)     // write level
+    {
         // note: the read level has already matched the uni that was read.
         // write+copy: only for the one set equal to uni...
         // write: match set and rel (already done)
         lddmc_refs_spawn(SPAWN(lddmc_relprev, set, mddnode_getright(n_rel), meta, uni));
-        if (mddnode_getcopy(n_rel)) {
+        if (mddnode_getcopy(n_rel))
+        {
             MDD down = lddmc_follow(set, mddnode_getvalue(n_uni));
-            if (down != lddmc_false) {
+            if (down != lddmc_false)
+            {
                 result = CALL(lddmc_relprev, down, mddnode_getdown(n_rel), mddnode_getdown(n_meta), mddnode_getdown(n_uni));
-            } else {
+            }
+            else
+            {
                 result = lddmc_false;
             }
-        } else {
+        }
+        else
+        {
             result = CALL(lddmc_relprev, mddnode_getdown(n_set), mddnode_getdown(n_rel), mddnode_getdown(n_meta), mddnode_getdown(n_uni));
         }
         lddmc_refs_push(result);
@@ -1496,17 +1681,21 @@ TASK_IMPL_4(MDD, lddmc_relprev, MDD, set, MDD, rel, MDD, meta, MDD, uni)
         lddmc_refs_push(result2);
         result = CALL(lddmc_union, result, result2);
         lddmc_refs_pop(2);
-    } else if (m_val == 4) { // only-write level
+    }
+    else if (m_val == 4)     // only-write level
+    {
         // only-write+copy: match set and uni after spawn
         // only-write: match set and rel (already done)
         lddmc_refs_spawn(SPAWN(lddmc_relprev, set, mddnode_getright(n_rel), meta, uni));
-        if (mddnode_getcopy(n_rel)) {
+        if (mddnode_getcopy(n_rel))
+        {
             // spawn for every matching set+uni
             int count = 0;
-            for (;;) {
+            for (;;)
+            {
                 if (!match_ldds(&set, &uni)) break;
-                n_set = GETNODE(set);
-                n_uni = GETNODE(uni);
+                n_set = LDD_GETNODE(set);
+                n_uni = LDD_GETNODE(uni);
                 lddmc_refs_spawn(SPAWN(lddmc_relprev_help, mddnode_getvalue(n_uni), mddnode_getdown(n_set), mddnode_getdown(n_rel), mddnode_getdown(n_meta), mddnode_getdown(n_uni)));
                 count++;
                 uni = mddnode_getright(n_uni);
@@ -1514,27 +1703,32 @@ TASK_IMPL_4(MDD, lddmc_relprev, MDD, set, MDD, rel, MDD, meta, MDD, uni)
 
             // sync+union (one by one)
             result = lddmc_false;
-            while (count--) {
+            while (count--)
+            {
                 lddmc_refs_push(result);
                 MDD result2 = lddmc_refs_sync(SYNC(lddmc_relprev_help));
                 lddmc_refs_push(result2);
                 result = CALL(lddmc_union, result, result2);
                 lddmc_refs_pop(2);
             }
-        } else {
+        }
+        else
+        {
             // spawn for every value in universe!!
             int count = 0;
-            for (;;) {
+            for (;;)
+            {
                 lddmc_refs_spawn(SPAWN(lddmc_relprev_help, mddnode_getvalue(n_uni), mddnode_getdown(n_set), mddnode_getdown(n_rel), mddnode_getdown(n_meta), mddnode_getdown(n_uni)));
                 count++;
                 uni = mddnode_getright(n_uni);
                 if (uni == lddmc_false) break;
-                n_uni = GETNODE(uni);
+                n_uni = LDD_GETNODE(uni);
             }
 
             // sync+union (one by one)
             result = lddmc_false;
-            while (count--) {
+            while (count--)
+            {
                 lddmc_refs_push(result);
                 MDD result2 = lddmc_refs_sync(SYNC(lddmc_relprev_help));
                 lddmc_refs_push(result2);
@@ -1563,16 +1757,17 @@ TASK_IMPL_4(MDD, lddmc_join, MDD, a, MDD, b, MDD, a_proj, MDD, b_proj)
     /* Test gc */
     sylvan_gc_test();
 
-    mddnode_t n_a_proj = GETNODE(a_proj);
-    mddnode_t n_b_proj = GETNODE(b_proj);
+    mddnode_t n_a_proj = LDD_GETNODE(a_proj);
+    mddnode_t n_b_proj = LDD_GETNODE(b_proj);
     uint32_t a_proj_val = mddnode_getvalue(n_a_proj);
     uint32_t b_proj_val = mddnode_getvalue(n_b_proj);
 
-    while (a_proj_val == 0 && b_proj_val == 0) {
+    while (a_proj_val == 0 && b_proj_val == 0)
+    {
         a_proj = mddnode_getdown(n_a_proj);
         b_proj = mddnode_getdown(n_b_proj);
-        n_a_proj = GETNODE(a_proj);
-        n_b_proj = GETNODE(b_proj);
+        n_a_proj = LDD_GETNODE(a_proj);
+        n_b_proj = LDD_GETNODE(b_proj);
         a_proj_val = mddnode_getvalue(n_a_proj);
         b_proj_val = mddnode_getvalue(n_b_proj);
     }
@@ -1585,7 +1780,8 @@ TASK_IMPL_4(MDD, lddmc_join, MDD, a, MDD, b, MDD, a_proj, MDD, b_proj)
     const int keep_a = a_proj_val != 0;
     const int keep_b = b_proj_val != 0;
 
-    if (keep_a && keep_b) {
+    if (keep_a && keep_b)
+    {
         // If both 'keep', then match values
         if (!match_ldds(&a, &b)) return lddmc_false;
     }
@@ -1594,34 +1790,41 @@ TASK_IMPL_4(MDD, lddmc_join, MDD, a, MDD, b, MDD, a_proj, MDD, b_proj)
 
     /* Access cache */
     MDD result;
-    if (cache_get4(CACHE_MDD_JOIN, a, b, a_proj, b_proj, &result)) {
+    if (cache_get4(CACHE_MDD_JOIN, a, b, a_proj, b_proj, &result))
+    {
         sylvan_stats_count(LDD_JOIN_CACHED);
         return result;
     }
 
     /* Perform recursive calculation */
-    const mddnode_t na = GETNODE(a);
-    const mddnode_t nb = GETNODE(b);
+    const mddnode_t na = LDD_GETNODE(a);
+    const mddnode_t nb = LDD_GETNODE(b);
     uint32_t val;
     MDD down;
 
     // Make copies (for cache)
     MDD _a_proj = a_proj, _b_proj = b_proj;
-    if (keep_a) {
-        if (keep_b) {
+    if (keep_a)
+    {
+        if (keep_b)
+        {
             val = mddnode_getvalue(nb);
             lddmc_refs_spawn(SPAWN(lddmc_join, mddnode_getright(na), mddnode_getright(nb), a_proj, b_proj));
             if (a_proj_val != (uint32_t)-1) a_proj = mddnode_getdown(n_a_proj);
             if (b_proj_val != (uint32_t)-1) b_proj = mddnode_getdown(n_b_proj);
             down = CALL(lddmc_join, mddnode_getdown(na), mddnode_getdown(nb), a_proj, b_proj);
-        } else {
+        }
+        else
+        {
             val = mddnode_getvalue(na);
             lddmc_refs_spawn(SPAWN(lddmc_join, mddnode_getright(na), b, a_proj, b_proj));
             if (a_proj_val != (uint32_t)-1) a_proj = mddnode_getdown(n_a_proj);
             if (b_proj_val != (uint32_t)-1) b_proj = mddnode_getdown(n_b_proj);
             down = CALL(lddmc_join, mddnode_getdown(na), b, a_proj, b_proj);
         }
-    } else {
+    }
+    else
+    {
         val = mddnode_getvalue(nb);
         lddmc_refs_spawn(SPAWN(lddmc_join, a, mddnode_getright(nb), a_proj, b_proj));
         if (a_proj_val != (uint32_t)-1) a_proj = mddnode_getdown(n_a_proj);
@@ -1646,7 +1849,7 @@ TASK_IMPL_2(MDD, lddmc_project, const MDD, mdd, const MDD, proj)
     if (mdd == lddmc_false) return lddmc_false; // projection of empty is empty
     if (mdd == lddmc_true) return lddmc_true; // projection of universe is universe...
 
-    mddnode_t p_node = GETNODE(proj);
+    mddnode_t p_node = LDD_GETNODE(proj);
     uint32_t p_val = mddnode_getvalue(p_node);
     if (p_val == (uint32_t)-1) return mdd;
     if (p_val == (uint32_t)-2) return lddmc_true; // because we always end with true.
@@ -1656,36 +1859,45 @@ TASK_IMPL_2(MDD, lddmc_project, const MDD, mdd, const MDD, proj)
     sylvan_stats_count(LDD_PROJECT);
 
     MDD result;
-    if (cache_get3(CACHE_MDD_PROJECT, mdd, proj, 0, &result)) {
+    if (cache_get3(CACHE_MDD_PROJECT, mdd, proj, 0, &result))
+    {
         sylvan_stats_count(LDD_PROJECT_CACHED);
         return result;
     }
 
-    mddnode_t n = GETNODE(mdd);
+    mddnode_t n = LDD_GETNODE(mdd);
 
-    if (p_val == 1) { // keep
+    if (p_val == 1)   // keep
+    {
         lddmc_refs_spawn(SPAWN(lddmc_project, mddnode_getright(n), proj));
         MDD down = CALL(lddmc_project, mddnode_getdown(n), mddnode_getdown(p_node));
         lddmc_refs_push(down);
         MDD right = lddmc_refs_sync(SYNC(lddmc_project));
         lddmc_refs_pop(1);
         result = lddmc_makenode(mddnode_getvalue(n), down, right);
-    } else { // quantify
-        if (mddnode_getdown(n) == lddmc_true) { // assume lowest level
+    }
+    else     // quantify
+    {
+        if (mddnode_getdown(n) == lddmc_true)   // assume lowest level
+        {
             result = lddmc_true;
-        } else {
+        }
+        else
+        {
             int count = 0;
             MDD p_down = mddnode_getdown(p_node), _mdd=mdd;
-            while (1) {
+            while (1)
+            {
                 lddmc_refs_spawn(SPAWN(lddmc_project, mddnode_getdown(n), p_down));
                 count++;
                 _mdd = mddnode_getright(n);
                 assert(_mdd != lddmc_true);
                 if (_mdd == lddmc_false) break;
-                n = GETNODE(_mdd);
+                n = LDD_GETNODE(_mdd);
             }
             result = lddmc_false;
-            while (count--) {
+            while (count--)
+            {
                 lddmc_refs_push(result);
                 MDD down = lddmc_refs_sync(SYNC(lddmc_project));
                 lddmc_refs_push(down);
@@ -1709,7 +1921,7 @@ TASK_IMPL_3(MDD, lddmc_project_minus, const MDD, mdd, const MDD, proj, MDD, avoi
     if (mdd == lddmc_false) return lddmc_false; // projection of empty is empty
     if (mdd == lddmc_true) return lddmc_true; // avoid != lddmc_true
 
-    mddnode_t p_node = GETNODE(proj);
+    mddnode_t p_node = LDD_GETNODE(proj);
     uint32_t p_val = mddnode_getvalue(p_node);
     if (p_val == (uint32_t)-1) return lddmc_minus(mdd, avoid);
     if (p_val == (uint32_t)-2) return lddmc_true;
@@ -1719,23 +1931,29 @@ TASK_IMPL_3(MDD, lddmc_project_minus, const MDD, mdd, const MDD, proj, MDD, avoi
     sylvan_stats_count(LDD_PROJECT_MINUS);
 
     MDD result;
-    if (cache_get3(CACHE_MDD_PROJECT, mdd, proj, avoid, &result)) {
+    if (cache_get3(CACHE_MDD_PROJECT, mdd, proj, avoid, &result))
+    {
         sylvan_stats_count(LDD_PROJECT_MINUS_CACHED);
         return result;
     }
 
-    mddnode_t n = GETNODE(mdd);
+    mddnode_t n = LDD_GETNODE(mdd);
 
-    if (p_val == 1) { // keep
+    if (p_val == 1)   // keep
+    {
         // move 'avoid' until it matches
         uint32_t val = mddnode_getvalue(n);
         MDD a_down = lddmc_false;
-        while (avoid != lddmc_false) {
-            mddnode_t a_node = GETNODE(avoid);
+        while (avoid != lddmc_false)
+        {
+            mddnode_t a_node = LDD_GETNODE(avoid);
             uint32_t a_val = mddnode_getvalue(a_node);
-            if (a_val > val) {
+            if (a_val > val)
+            {
                 break;
-            } else if (a_val == val) {
+            }
+            else if (a_val == val)
+            {
                 a_down = mddnode_getdown(a_node);
                 break;
             }
@@ -1747,22 +1965,29 @@ TASK_IMPL_3(MDD, lddmc_project_minus, const MDD, mdd, const MDD, proj, MDD, avoi
         MDD right = lddmc_refs_sync(SYNC(lddmc_project_minus));
         lddmc_refs_pop(1);
         result = lddmc_makenode(val, down, right);
-    } else { // quantify
-        if (mddnode_getdown(n) == lddmc_true) { // assume lowest level
+    }
+    else     // quantify
+    {
+        if (mddnode_getdown(n) == lddmc_true)   // assume lowest level
+        {
             result = lddmc_true;
-        } else {
+        }
+        else
+        {
             int count = 0;
             MDD p_down = mddnode_getdown(p_node), _mdd=mdd;
-            while (1) {
+            while (1)
+            {
                 lddmc_refs_spawn(SPAWN(lddmc_project_minus, mddnode_getdown(n), p_down, avoid));
                 count++;
                 _mdd = mddnode_getright(n);
                 assert(_mdd != lddmc_true);
                 if (_mdd == lddmc_false) break;
-                n = GETNODE(_mdd);
+                n = LDD_GETNODE(_mdd);
             }
             result = lddmc_false;
-            while (count--) {
+            while (count--)
+            {
                 lddmc_refs_push(result);
                 MDD down = lddmc_refs_sync(SYNC(lddmc_project_minus));
                 lddmc_refs_push(down);
@@ -1781,26 +2006,32 @@ MDD
 lddmc_union_cube(MDD a, uint32_t* values, size_t count)
 {
     if (a == lddmc_false) return lddmc_cube(values, count);
-    if (a == lddmc_true) {
+    if (a == lddmc_true)
+    {
         assert(count == 0);
         return lddmc_true;
     }
     assert(count != 0);
 
-    mddnode_t na = GETNODE(a);
+    mddnode_t na = LDD_GETNODE(a);
     uint32_t na_value = mddnode_getvalue(na);
 
     /* Only create a new node if something actually changed */
 
-    if (na_value < *values) {
+    if (na_value < *values)
+    {
         MDD right = lddmc_union_cube(mddnode_getright(na), values, count);
         if (right == mddnode_getright(na)) return a; // no actual change
         return lddmc_makenode(na_value, mddnode_getdown(na), right);
-    } else if (na_value == *values) {
+    }
+    else if (na_value == *values)
+    {
         MDD down = lddmc_union_cube(mddnode_getdown(na), values+1, count-1);
         if (down == mddnode_getdown(na)) return a; // no actual change
         return lddmc_makenode(na_value, down, mddnode_getright(na));
-    } else /* na_value > *values */ {
+    }
+    else /* na_value > *values */
+    {
         return lddmc_makenode(*values, lddmc_cube(values+1, count-1), a);
     }
 }
@@ -1809,39 +2040,50 @@ MDD
 lddmc_union_cube_copy(MDD a, uint32_t* values, int* copy, size_t count)
 {
     if (a == lddmc_false) return lddmc_cube_copy(values, copy, count);
-    if (a == lddmc_true) {
+    if (a == lddmc_true)
+    {
         assert(count == 0);
         return lddmc_true;
     }
     assert(count != 0);
 
-    mddnode_t na = GETNODE(a);
+    mddnode_t na = LDD_GETNODE(a);
 
     /* Only create a new node if something actually changed */
 
     int na_copy = mddnode_getcopy(na);
-    if (na_copy && *copy) {
+    if (na_copy && *copy)
+    {
         MDD down = lddmc_union_cube_copy(mddnode_getdown(na), values+1, copy+1, count-1);
         if (down == mddnode_getdown(na)) return a; // no actual change
         return lddmc_make_copynode(down, mddnode_getright(na));
-    } else if (na_copy) {
+    }
+    else if (na_copy)
+    {
         MDD right = lddmc_union_cube_copy(mddnode_getright(na), values, copy, count);
         if (right == mddnode_getright(na)) return a; // no actual change
         return lddmc_make_copynode(mddnode_getdown(na), right);
-    } else if (*copy) {
+    }
+    else if (*copy)
+    {
         return lddmc_make_copynode(lddmc_cube_copy(values+1, copy+1, count-1), a);
     }
 
     uint32_t na_value = mddnode_getvalue(na);
-    if (na_value < *values) {
+    if (na_value < *values)
+    {
         MDD right = lddmc_union_cube_copy(mddnode_getright(na), values, copy, count);
         if (right == mddnode_getright(na)) return a; // no actual change
         return lddmc_makenode(na_value, mddnode_getdown(na), right);
-    } else if (na_value == *values) {
+    }
+    else if (na_value == *values)
+    {
         MDD down = lddmc_union_cube_copy(mddnode_getdown(na), values+1, copy+1, count-1);
         if (down == mddnode_getdown(na)) return a; // no actual change
         return lddmc_makenode(na_value, down, mddnode_getright(na));
-    } else /* na_value > *values */ {
+    }
+    else /* na_value > *values */
+    {
         return lddmc_makenode(*values, lddmc_cube_copy(values+1, copy+1, count-1), a);
     }
 }
@@ -1849,7 +2091,8 @@ lddmc_union_cube_copy(MDD a, uint32_t* values, int* copy, size_t count)
 int
 lddmc_member_cube(MDD a, uint32_t* values, size_t count)
 {
-    while (1) {
+    while (1)
+    {
         if (a == lddmc_false) return 0;
         if (a == lddmc_true) return 1;
         assert(count > 0); // size mismatch
@@ -1863,7 +2106,8 @@ lddmc_member_cube(MDD a, uint32_t* values, size_t count)
 int
 lddmc_member_cube_copy(MDD a, uint32_t* values, int* copy, size_t count)
 {
-    while (1) {
+    while (1)
+    {
         if (a == lddmc_false) return 0;
         if (a == lddmc_true) return 1;
         assert(count > 0); // size mismatch
@@ -1898,8 +2142,9 @@ static void
 lddmc_nodecount_levels_mark(MDD mdd, size_t *variables)
 {
     if (mdd <= lddmc_true) return;
-    mddnode_t n = GETNODE(mdd);
-    if (!mddnode_getmark(n)) {
+    mddnode_t n = LDD_GETNODE(mdd);
+    if (!mddnode_getmark(n))
+    {
         mddnode_setmark(n, 1);
         (*variables) += 1;
         lddmc_nodecount_levels_mark(mddnode_getright(n), variables);
@@ -1911,8 +2156,9 @@ static void
 lddmc_nodecount_levels_unmark(MDD mdd)
 {
     if (mdd <= lddmc_true) return;
-    mddnode_t n = GETNODE(mdd);
-    if (mddnode_getmark(n)) {
+    mddnode_t n = LDD_GETNODE(mdd);
+    if (mddnode_getmark(n))
+    {
         mddnode_setmark(n, 0);
         lddmc_nodecount_levels_unmark(mddnode_getright(n));
         lddmc_nodecount_levels_unmark(mddnode_getdown(n));
@@ -1934,7 +2180,7 @@ static size_t
 lddmc_nodecount_mark(MDD mdd)
 {
     if (mdd <= lddmc_true) return 0;
-    mddnode_t n = GETNODE(mdd);
+    mddnode_t n = LDD_GETNODE(mdd);
     if (mddnode_getmark(n)) return 0;
     mddnode_setmark(n, 1);
     return 1 + lddmc_nodecount_mark(mddnode_getdown(n)) + lddmc_nodecount_mark(mddnode_getright(n));
@@ -1944,8 +2190,9 @@ static void
 lddmc_nodecount_unmark(MDD mdd)
 {
     if (mdd <= lddmc_true) return;
-    mddnode_t n = GETNODE(mdd);
-    if (mddnode_getmark(n)) {
+    mddnode_t n = LDD_GETNODE(mdd);
+    if (mddnode_getmark(n))
+    {
         mddnode_setmark(n, 0);
         lddmc_nodecount_unmark(mddnode_getright(n));
         lddmc_nodecount_unmark(mddnode_getdown(n));
@@ -1972,19 +2219,21 @@ TASK_IMPL_1(lddmc_satcount_double_t, lddmc_satcount_cached, MDD, mdd)
     /* Perhaps execute garbage collection */
     sylvan_gc_test();
 
-    union {
+    union
+    {
         lddmc_satcount_double_t d;
         uint64_t s;
     } hack;
 
     sylvan_stats_count(LDD_SATCOUNT);
 
-    if (cache_get3(CACHE_MDD_SATCOUNT, mdd, 0, 0, &hack.s)) {
+    if (cache_get3(CACHE_MDD_SATCOUNT, mdd, 0, 0, &hack.s))
+    {
         sylvan_stats_count(LDD_SATCOUNT_CACHED);
         return hack.d;
     }
 
-    mddnode_t n = GETNODE(mdd);
+    mddnode_t n = LDD_GETNODE(mdd);
 
     SPAWN(lddmc_satcount_cached, mddnode_getdown(n));
     lddmc_satcount_double_t right = CALL(lddmc_satcount_cached, mddnode_getright(n));
@@ -2005,21 +2254,24 @@ TASK_IMPL_1(long double, lddmc_satcount, MDD, mdd)
 
     sylvan_stats_count(LDD_SATCOUNTL);
 
-    union {
+    union
+    {
         long double d;
-        struct {
+        struct
+        {
             uint64_t s1;
             uint64_t s2;
         } s;
     } hack;
 
     if (cache_get3(CACHE_MDD_SATCOUNTL1, mdd, 0, 0, &hack.s.s1) &&
-        cache_get3(CACHE_MDD_SATCOUNTL2, mdd, 0, 0, &hack.s.s2)) {
+            cache_get3(CACHE_MDD_SATCOUNTL2, mdd, 0, 0, &hack.s.s2))
+    {
         sylvan_stats_count(LDD_SATCOUNTL_CACHED);
         return hack.d;
     }
 
-    mddnode_t n = GETNODE(mdd);
+    mddnode_t n = LDD_GETNODE(mdd);
 
     SPAWN(lddmc_satcount, mddnode_getdown(n));
     long double right = CALL(lddmc_satcount, mddnode_getright(n));
@@ -2035,11 +2287,12 @@ TASK_IMPL_1(long double, lddmc_satcount, MDD, mdd)
 TASK_IMPL_5(MDD, lddmc_collect, MDD, mdd, lddmc_collect_cb, cb, void*, context, uint32_t*, values, size_t, count)
 {
     if (mdd == lddmc_false) return lddmc_false;
-    if (mdd == lddmc_true) {
+    if (mdd == lddmc_true)
+    {
         return WRAP(cb, values, count, context);
     }
 
-    mddnode_t n = GETNODE(mdd);
+    mddnode_t n = LDD_GETNODE(mdd);
 
     lddmc_refs_spawn(SPAWN(lddmc_collect, mddnode_getright(n), cb, context, values, count));
 
@@ -2048,7 +2301,8 @@ TASK_IMPL_5(MDD, lddmc_collect, MDD, mdd, lddmc_collect_cb, cb, void*, context,
     newvalues[count] = mddnode_getvalue(n);
     MDD down = CALL(lddmc_collect, mddnode_getdown(n), cb, context, newvalues, count+1);
 
-    if (down == lddmc_false) {
+    if (down == lddmc_false)
+    {
         MDD result = lddmc_refs_sync(SYNC(lddmc_collect));
         return result;
     }
@@ -2056,10 +2310,13 @@ TASK_IMPL_5(MDD, lddmc_collect, MDD, mdd, lddmc_collect_cb, cb, void*, context,
     lddmc_refs_push(down);
     MDD right = lddmc_refs_sync(SYNC(lddmc_collect));
 
-    if (right == lddmc_false) {
+    if (right == lddmc_false)
+    {
         lddmc_refs_pop(1);
         return down;
-    } else {
+    }
+    else
+    {
         lddmc_refs_push(right);
         MDD result = CALL(lddmc_union, down, right);
         lddmc_refs_pop(2);
@@ -2070,12 +2327,13 @@ TASK_IMPL_5(MDD, lddmc_collect, MDD, mdd, lddmc_collect_cb, cb, void*, context,
 VOID_TASK_5(_lddmc_sat_all_nopar, MDD, mdd, lddmc_enum_cb, cb, void*, context, uint32_t*, values, size_t, count)
 {
     if (mdd == lddmc_false) return;
-    if (mdd == lddmc_true) {
+    if (mdd == lddmc_true)
+    {
         WRAP(cb, values, count, context);
         return;
     }
 
-    mddnode_t n = GETNODE(mdd);
+    mddnode_t n = LDD_GETNODE(mdd);
     values[count] = mddnode_getvalue(n);
     CALL(_lddmc_sat_all_nopar, mddnode_getdown(n), cb, context, values, count+1);
     CALL(_lddmc_sat_all_nopar, mddnode_getright(n), cb, context, values, count);
@@ -2086,8 +2344,9 @@ VOID_TASK_IMPL_3(lddmc_sat_all_nopar, MDD, mdd, lddmc_enum_cb, cb, void*, contex
     // determine depth
     size_t count=0;
     MDD _mdd = mdd;
-    while (_mdd > lddmc_true) {
-        _mdd = mddnode_getdown(GETNODE(_mdd));
+    while (_mdd > lddmc_true)
+    {
+        _mdd = mddnode_getdown(LDD_GETNODE(_mdd));
         assert(_mdd != lddmc_false);
         count++;
     }
@@ -2099,12 +2358,13 @@ VOID_TASK_IMPL_3(lddmc_sat_all_nopar, MDD, mdd, lddmc_enum_cb, cb, void*, contex
 VOID_TASK_IMPL_5(lddmc_sat_all_par, MDD, mdd, lddmc_enum_cb, cb, void*, context, uint32_t*, values, size_t, count)
 {
     if (mdd == lddmc_false) return;
-    if (mdd == lddmc_true) {
+    if (mdd == lddmc_true)
+    {
         WRAP(cb, values, count, context);
         return;
     }
 
-    mddnode_t n = GETNODE(mdd);
+    mddnode_t n = LDD_GETNODE(mdd);
 
     SPAWN(lddmc_sat_all_par, mddnode_getright(n), cb, context, values, count);
 
@@ -2132,39 +2392,45 @@ VOID_TASK_3(lddmc_match_sat, struct lddmc_match_sat_info *, info, lddmc_enum_cb,
 
     if (a == lddmc_false || b == lddmc_false) return;
 
-    if (a == lddmc_true) {
+    if (a == lddmc_true)
+    {
         assert(b == lddmc_true);
         WRAP(cb, info->values, info->count, context);
         return;
     }
 
-    mddnode_t p_node = GETNODE(proj);
+    mddnode_t p_node = LDD_GETNODE(proj);
     uint32_t p_val = mddnode_getvalue(p_node);
-    if (p_val == (uint32_t)-1) {
+    if (p_val == (uint32_t)-1)
+    {
         assert(b == lddmc_true);
         CALL(lddmc_sat_all_par, a, cb, context, info->values, info->count);
         return;
     }
 
     /* Get nodes */
-    mddnode_t na = GETNODE(a);
-    mddnode_t nb = GETNODE(b);
+    mddnode_t na = LDD_GETNODE(a);
+    mddnode_t nb = LDD_GETNODE(b);
     uint32_t na_value = mddnode_getvalue(na);
     uint32_t nb_value = mddnode_getvalue(nb);
 
     /* Skip nodes if possible */
-    if (p_val == 1) {
-        while (na_value != nb_value) {
-            if (na_value < nb_value) {
+    if (p_val == 1)
+    {
+        while (na_value != nb_value)
+        {
+            if (na_value < nb_value)
+            {
                 a = mddnode_getright(na);
                 if (a == lddmc_false) return;
-                na = GETNODE(a);
+                na = LDD_GETNODE(a);
                 na_value = mddnode_getvalue(na);
             }
-            if (nb_value < na_value) {
+            if (nb_value < na_value)
+            {
                 b = mddnode_getright(nb);
                 if (b == lddmc_false) return;
-                nb = GETNODE(b);
+                nb = LDD_GETNODE(b);
                 nb_value = mddnode_getvalue(nb);
             }
         }
@@ -2176,7 +2442,7 @@ VOID_TASK_3(lddmc_match_sat, struct lddmc_match_sat_info *, info, lddmc_enum_cb,
     ri->mdd = mddnode_getright(na);
     di->mdd = mddnode_getdown(na);
     ri->match = b;
-    di->match = mddnode_getdown(nb);
+    di->match = p_val == 1 ? mddnode_getdown(nb) : b;
     ri->proj = proj;
     di->proj = mddnode_getdown(p_node);
     ri->count = info->count;
@@ -2206,7 +2472,7 @@ lddmc_sat_one(MDD mdd, uint32_t* values, size_t count)
     if (mdd == lddmc_false) return 0;
     if (mdd == lddmc_true) return 1;
     assert(count != 0);
-    mddnode_t n = GETNODE(mdd);
+    mddnode_t n = LDD_GETNODE(mdd);
     *values = mddnode_getvalue(n);
     return lddmc_sat_one(mddnode_getdown(n), values+1, count-1);
 }
@@ -2216,17 +2482,20 @@ lddmc_sat_one_mdd(MDD mdd)
 {
     if (mdd == lddmc_false) return lddmc_false;
     if (mdd == lddmc_true) return lddmc_true;
-    mddnode_t n = GETNODE(mdd);
+    mddnode_t n = LDD_GETNODE(mdd);
     MDD down = lddmc_sat_one_mdd(mddnode_getdown(n));
     return lddmc_makenode(mddnode_getvalue(n), down, lddmc_false);
 }
 
 TASK_IMPL_4(MDD, lddmc_compose, MDD, mdd, lddmc_compose_cb, cb, void*, context, int, depth)
 {
-    if (depth == 0 || mdd == lddmc_false || mdd == lddmc_true) {
+    if (depth == 0 || mdd == lddmc_false || mdd == lddmc_true)
+    {
         return WRAP(cb, mdd, context);
-    } else {
-        mddnode_t n = GETNODE(mdd);
+    }
+    else
+    {
+        mddnode_t n = LDD_GETNODE(mdd);
         lddmc_refs_spawn(SPAWN(lddmc_compose, mddnode_getright(n), cb, context, depth));
         MDD down = lddmc_compose(mddnode_getdown(n), cb, context, depth-1);
         lddmc_refs_push(down);
@@ -2245,8 +2514,8 @@ VOID_TASK_IMPL_4(lddmc_visit_seq, MDD, mdd, lddmc_visit_callbacks_t*, cbs, size_
     WRAP(cbs->lddmc_visit_init_context, context_down, context, 1);
     WRAP(cbs->lddmc_visit_init_context, context_right, context, 0);
 
-    CALL(lddmc_visit_seq, mddnode_getdown(GETNODE(mdd)), cbs, ctx_size, context_down);
-    CALL(lddmc_visit_seq, mddnode_getright(GETNODE(mdd)), cbs, ctx_size, context_right);
+    CALL(lddmc_visit_seq, mddnode_getdown(LDD_GETNODE(mdd)), cbs, ctx_size, context_down);
+    CALL(lddmc_visit_seq, mddnode_getright(LDD_GETNODE(mdd)), cbs, ctx_size, context_right);
 
     WRAP(cbs->lddmc_visit_post, mdd, context);
 }
@@ -2260,8 +2529,8 @@ VOID_TASK_IMPL_4(lddmc_visit_par, MDD, mdd, lddmc_visit_callbacks_t*, cbs, size_
     WRAP(cbs->lddmc_visit_init_context, context_down, context, 1);
     WRAP(cbs->lddmc_visit_init_context, context_right, context, 0);
 
-    SPAWN(lddmc_visit_par, mddnode_getdown(GETNODE(mdd)), cbs, ctx_size, context_down);
-    CALL(lddmc_visit_par, mddnode_getright(GETNODE(mdd)), cbs, ctx_size, context_right);
+    SPAWN(lddmc_visit_par, mddnode_getdown(LDD_GETNODE(mdd)), cbs, ctx_size, context_down);
+    CALL(lddmc_visit_par, mddnode_getright(LDD_GETNODE(mdd)), cbs, ctx_size, context_right);
     SYNC(lddmc_visit_par);
 
     WRAP(cbs->lddmc_visit_post, mdd, context);
@@ -2282,10 +2551,13 @@ lddmc_mark(mddnode_t node)
 static inline int
 lddmc_unmark(mddnode_t node)
 {
-    if (mddnode_getmark(node)) {
+    if (mddnode_getmark(node))
+    {
         mddnode_setmark(node, 0);
         return 1;
-    } else {
+    }
+    else
+    {
         return 0;
     }
 }
@@ -2293,11 +2565,12 @@ lddmc_unmark(mddnode_t node)
 static void
 lddmc_unmark_rec(mddnode_t node)
 {
-    if (lddmc_unmark(node)) {
+    if (lddmc_unmark(node))
+    {
         MDD node_right = mddnode_getright(node);
-        if (node_right > lddmc_true) lddmc_unmark_rec(GETNODE(node_right));
+        if (node_right > lddmc_true) lddmc_unmark_rec(LDD_GETNODE(node_right));
         MDD node_down = mddnode_getdown(node);
-        if (node_down > lddmc_true) lddmc_unmark_rec(GETNODE(node_down));
+        if (node_down > lddmc_true) lddmc_unmark_rec(LDD_GETNODE(node_down));
     }
 }
 
@@ -2311,7 +2584,7 @@ lddmc_fprintdot_rec(FILE* out, MDD mdd)
     // assert(mdd > lddmc_true);
 
     // check mark
-    mddnode_t n = GETNODE(mdd);
+    mddnode_t n = LDD_GETNODE(mdd);
     if (mddnode_getmark(n)) return;
     mddnode_setmark(n, 1);
 
@@ -2321,8 +2594,9 @@ lddmc_fprintdot_rec(FILE* out, MDD mdd)
     if (mddnode_getcopy(n)) fprintf(out, "<c> *");
     else fprintf(out, "<%u> %u", val, val);
     MDD right = mddnode_getright(n);
-    while (right != lddmc_false) {
-        mddnode_t n2 = GETNODE(right);
+    while (right != lddmc_false)
+    {
+        mddnode_t n2 = LDD_GETNODE(right);
         uint32_t val2 = mddnode_getvalue(n2);
         fprintf(out, "|<%u> %u", val2, val2);
         right = mddnode_getright(n2);
@@ -2331,25 +2605,33 @@ lddmc_fprintdot_rec(FILE* out, MDD mdd)
     fprintf(out, "\"];\n");
 
     // recurse and print the edges
-    for (;;) {
+    for (;;)
+    {
         MDD down = mddnode_getdown(n);
         // assert(down != lddmc_false);
-        if (down > lddmc_true) {
-          //  lddmc_fprintdot_rec(out, down);
-            if (mddnode_getcopy(n)) {
+        if (down > lddmc_true)
+        {
+            lddmc_fprintdot_rec(out, down);
+            if (mddnode_getcopy(n))
+            {
                 fprintf(out, "%" PRIu64 ":c -> ", mdd);
-            } else {
+            }
+            else
+            {
                 fprintf(out, "%" PRIu64 ":%u -> ", mdd, mddnode_getvalue(n));
             }
-            if (mddnode_getcopy(GETNODE(down))) {
+            if (mddnode_getcopy(LDD_GETNODE(down)))
+            {
                 fprintf(out, "%" PRIu64 ":c [style=solid];\n", down);
-            } else {
-                fprintf(out, "%" PRIu64 ":%u [style=solid];\n", down, mddnode_getvalue(GETNODE(down)));
+            }
+            else
+            {
+                fprintf(out, "%" PRIu64 ":%u [style=solid];\n", down, mddnode_getvalue(LDD_GETNODE(down)));
             }
         }
         MDD right = mddnode_getright(n);
         if (right == lddmc_false) break;
-        n = GETNODE(right);
+        n = LDD_GETNODE(right);
     }
 }
 
@@ -2357,14 +2639,16 @@ static void
 lddmc_fprintdot_unmark(MDD mdd)
 {
     if (mdd <= lddmc_true) return;
-    mddnode_t n = GETNODE(mdd);
-    if (mddnode_getmark(n)) {
+    mddnode_t n = LDD_GETNODE(mdd);
+    if (mddnode_getmark(n))
+    {
         mddnode_setmark(n, 0);
-        for (;;) {
+        for (;;)
+        {
             lddmc_fprintdot_unmark(mddnode_getdown(n));
             mdd = mddnode_getright(n);
             if (mdd == lddmc_false) return;
-            n = GETNODE(mdd);
+            n = LDD_GETNODE(mdd);
         }
     }
 }
@@ -2378,20 +2662,22 @@ lddmc_fprintdot(FILE *out, MDD mdd)
     fprintf(out, "edge [dir = forward];\n");
 
     // Special case: false
-    if (mdd == lddmc_false) {
+    if (mdd == lddmc_false)
+    {
         fprintf(out, "0 [shape=record, label=\"False\"];\n");
         fprintf(out, "}\n");
         return;
     }
 
     // Special case: true
-    if (mdd == lddmc_true) {
+    if (mdd == lddmc_true)
+    {
         fprintf(out, "1 [shape=record, label=\"True\"];\n");
         fprintf(out, "}\n");
         return;
     }
 
-    //lddmc_fprintdot_rec(out, mdd);
+    lddmc_fprintdot_rec(out, mdd);
     lddmc_fprintdot_unmark(mdd);
 
     fprintf(out, "}\n");
@@ -2425,7 +2711,8 @@ lddmc_print(MDD mdd)
  * SERIALIZATION
  */
 
-struct lddmc_ser {
+struct lddmc_ser
+{
     MDD mdd;
     size_t assigned;
 };
@@ -2462,12 +2749,13 @@ lddmc_serialize_assign_rec(MDD mdd)
 {
     if (mdd <= lddmc_true) return mdd;
 
-    mddnode_t n = GETNODE(mdd);
+    mddnode_t n = LDD_GETNODE(mdd);
 
     struct lddmc_ser s, *ss;
     s.mdd = mdd;
     ss = lddmc_ser_search(lddmc_ser_set, &s);
-    if (ss == NULL) {
+    if (ss == NULL)
+    {
         // assign dummy value
         s.assigned = 0;
         ss = lddmc_ser_put(&lddmc_ser_set, &s, NULL);
@@ -2530,13 +2818,14 @@ lddmc_serialize_totext(FILE *out)
     struct lddmc_ser *s;
 
     fprintf(out, "[");
-    while ((s=lddmc_ser_reversed_iter_next(it))) {
+    while ((s=lddmc_ser_reversed_iter_next(it)))
+    {
         MDD mdd = s->mdd;
-        mddnode_t n = GETNODE(mdd);
+        mddnode_t n = LDD_GETNODE(mdd);
         fprintf(out, "(%zu,v=%u,d=%zu,r=%zu),", s->assigned,
-                                                mddnode_getvalue(n),
-                                                lddmc_serialize_get(mddnode_getdown(n)),
-                                                lddmc_serialize_get(mddnode_getright(n)));
+                mddnode_getvalue(n),
+                lddmc_serialize_get(mddnode_getdown(n)),
+                lddmc_serialize_get(mddnode_getright(n)));
     }
     fprintf(out, "]");
 
@@ -2557,16 +2846,18 @@ lddmc_serialize_tofile(FILE *out)
 
     /* Skip already written entries */
     size_t index = 0;
-    while (index < lddmc_ser_done && (s=lddmc_ser_reversed_iter_next(it))) {
+    while (index < lddmc_ser_done && (s=lddmc_ser_reversed_iter_next(it)))
+    {
         assert(s->assigned == index+2);
         index++;
     }
 
-    while ((s=lddmc_ser_reversed_iter_next(it))) {
+    while ((s=lddmc_ser_reversed_iter_next(it)))
+    {
         assert(s->assigned == index+2);
         index++;
 
-        mddnode_t n = GETNODE(s->mdd);
+        mddnode_t n = LDD_GETNODE(s->mdd);
 
         struct mddnode node;
         uint64_t right = lddmc_serialize_get(mddnode_getright(n));
@@ -2588,15 +2879,18 @@ void
 lddmc_serialize_fromfile(FILE *in)
 {
     size_t count, i;
-    if (fread(&count, sizeof(size_t), 1, in) != 1) {
+    if (fread(&count, sizeof(size_t), 1, in) != 1)
+    {
         // TODO FIXME return error
         printf("sylvan_serialize_fromfile: file format error, giving up\n");
         exit(-1);
     }
 
-    for (i=1; i<=count; i++) {
+    for (i=1; i<=count; i++)
+    {
         struct mddnode node;
-        if (fread(&node, sizeof(struct mddnode), 1, in) != 1) {
+        if (fread(&node, sizeof(struct mddnode), 1, in) != 1)
+        {
             // TODO FIXME return error
             printf("sylvan_serialize_fromfile: file format error, giving up\n");
             exit(-1);
@@ -2619,16 +2913,30 @@ lddmc_serialize_fromfile(FILE *in)
     }
 }
 
+VOID_TASK_IMPL_0(lddmc_gc_mark_serialize)
+{
+    struct lddmc_ser *s;
+    avl_iter_t *it = lddmc_ser_iter(lddmc_ser_set);
+
+    /* Iterate through nodes in serialization */
+    while ((s=lddmc_ser_iter_next(it)))
+    {
+        CALL(lddmc_gc_mark_rec, s->mdd);
+    }
+}
+
 static void
 lddmc_sha2_rec(MDD mdd, SHA256_CTX *ctx)
 {
-    if (mdd <= lddmc_true) {
+    if (mdd <= lddmc_true)
+    {
         SHA256_Update(ctx, (void*)&mdd, sizeof(uint64_t));
         return;
     }
 
-    mddnode_t node = GETNODE(mdd);
-    if (lddmc_mark(node)) {
+    mddnode_t node = LDD_GETNODE(mdd);
+    if (lddmc_mark(node))
+    {
         uint32_t val = mddnode_getvalue(node);
         SHA256_Update(ctx, (void*)&val, sizeof(uint32_t));
         lddmc_sha2_rec(mddnode_getdown(node), ctx);
@@ -2656,7 +2964,7 @@ lddmc_getsha(MDD mdd, char *target)
     SHA256_CTX ctx;
     SHA256_Init(&ctx);
     lddmc_sha2_rec(mdd, &ctx);
-    if (mdd > lddmc_true) lddmc_unmark_rec(GETNODE(mdd));
+    if (mdd > lddmc_true) lddmc_unmark_rec(LDD_GETNODE(mdd));
     SHA256_End(&ctx, target);
 }
 
@@ -2670,9 +2978,11 @@ lddmc_test_ismdd(MDD mdd)
     int first = 1;
     size_t depth = 0;
 
-    if (mdd != lddmc_false) {
-        mddnode_t n = GETNODE(mdd);
-        if (mddnode_getcopy(n)) {
+    if (mdd != lddmc_false)
+    {
+        mddnode_t n = LDD_GETNODE(mdd);
+        if (mddnode_getcopy(n))
+        {
             mdd = mddnode_getright(n);
             depth = lddmc_test_ismdd(mddnode_getdown(n));
             assert(depth >= 1);
@@ -2680,17 +2990,21 @@ lddmc_test_ismdd(MDD mdd)
     }
 
     uint32_t value = 0;
-    while (mdd != lddmc_false) {
+    while (mdd != lddmc_false)
+    {
         assert(llmsset_is_marked(nodes, mdd));
 
-        mddnode_t n = GETNODE(mdd);
+        mddnode_t n = LDD_GETNODE(mdd);
         uint32_t next_value = mddnode_getvalue(n);
         assert(mddnode_getcopy(n) == 0);
-        if (first) {
+        if (first)
+        {
             first = 0;
             depth = lddmc_test_ismdd(mddnode_getdown(n));
             assert(depth >= 1);
-        } else {
+        }
+        else
+        {
             assert(value < next_value);
             assert(depth == lddmc_test_ismdd(mddnode_getdown(n)));
         }
@@ -2701,58 +3015,208 @@ lddmc_test_ismdd(MDD mdd)
 
     return 1 + depth;
 }
-int ldd_equal(MDD a, MDD b)
+/////////////////////////////////////////////////////////////
+
+void init_gc_seq()
 {
+    LACE_ME;
+    LOCALIZE_THREAD_LOCAL(lddmc_refs_key, lddmc_refs_internal_t);
+    sequentiel_refs=lddmc_refs_key;
+}
 
-    if (a == lddmc_false && b == lddmc_false) return 1;
-    else if (a==lddmc_false || b == lddmc_false) return 0;
+MDD __attribute__((unused))
+ldd_refs_push(MDD lddmc)
+{
+    *(sequentiel_refs->rcur++) = lddmc;
+    if (sequentiel_refs->rcur == sequentiel_refs->rend) return lddmc_refs_refs_up(sequentiel_refs, lddmc);
+    else return lddmc;
+}
 
+void __attribute__((unused))
+ldd_refs_pop(long amount)
+{
 
-    /* Perform recursive calculation */
-    mddnode_t na = GETNODE(a);
-    mddnode_t nb = GETNODE(b);
+    lddmc_refs_key->rcur -= amount;
+}
+
+llmsset_destroy_seq(llmsset_t  dbs, size_t  first, size_t  count)
+{
+    if (count > 1024)
+    {
+        size_t split = count/2;
+        llmsset_destroy_seq( dbs, first, split);
+        llmsset_destroy_seq( dbs, first + split, count - split);
 
-    uint32_t va,vb;
-    MDD mdda,mddb;
+    }
+    else
+    {
+        for (size_t k=first; k<first+count; k++)
+        {
+            volatile uint64_t *ptr2 = dbs->bitmap2 + (k/64);
+            volatile uint64_t *ptrc = dbs->bitmapc + (k/64);
+            uint64_t mask = 0x8000000000000000LL >> (k&63);
 
-    do {
-        va = mddnode_getvalue(na);
-        vb = mddnode_getvalue(nb);
+            // if not marked but is custom
+            if ((*ptr2 & mask) == 0 && (*ptrc & mask))
+            {
+                uint64_t *d_ptr = ((uint64_t*)dbs->data) + 2*k;
+                dbs->destroy_cb(d_ptr[0], d_ptr[1]);
+                *ptrc &= ~mask;
+            }
+        }
+    }
+}
+
+void llmsset_destroy_unmarked_seq( llmsset_t dbs)
+{
+    if (dbs->destroy_cb == NULL) return; // no custom function
+    llmsset_destroy_seq( dbs, 0, dbs->table_size);
+}
 
 
-        if (va==vb) {
+void sylvan_gc_seq()
+{
+    if (seq_llmsset_count_marked(nodes)>llmsset_get_size(nodes)/2)
+    {
+    printf("GC active \n ");
+        cache_clear();
+        llmsset_clear_data_seq(nodes);
+        ldd_gc_mark_protected();
+        llmsset_destroy_unmarked_seq(nodes);
+        sylvan_rehash_all_seq();
+    }
+}
 
-            MDD mddnar = mddnode_getright(na);
-            MDD mddnbr = mddnode_getright(nb);
 
-            if (mddnar == lddmc_false && mddnar == lddmc_false) ;
-            else if (mddnar==lddmc_false || mddnbr == lddmc_false) return 0;
+void ldd_refs_mark_p_par( const MDD** begin, size_t count)
+{
+    if (count < 32)
+    {
+        while (count)
+        {
+            ldd_gc_mark_rec(**(begin++));
+            count--;
+        }
+    }
+    else
+    {
+        ldd_refs_mark_p_par( begin, count / 2);
+        ldd_refs_mark_p_par( begin + (count / 2), count - count / 2);
 
-            uint32_t var,vbr;
-            while (mddnar!=lddmc_false && mddnbr!=lddmc_false) {
-                mddnode_t nar=GETNODE(mddnar);
-                mddnode_t nbr=GETNODE(mddnbr);
-                var = mddnode_getvalue(nar);
-                vbr = mddnode_getvalue(nbr);
+    }
+}
 
-                if (var==vbr) {
-                    if (ldd_equal(mddnode_getdown(nar),mddnode_getdown(nbr))) {
-                        mddnar = mddnode_getright(nar);
-                        mddnbr = mddnode_getright(nbr);
-                    }
-                    else return 0;
-                }
-                else return 0;
+void ldd_refs_mark_r_par( MDD* begin, size_t count)
+{
+    if (count < 32)
+    {
+        while (count)
+        {
+            ldd_gc_mark_rec(*begin++);
+            count--;
+        }
+    }
+    else
+    {
+        ldd_refs_mark_r_par( begin, count / 2);
+        ldd_refs_mark_r_par( begin + (count / 2), count - count / 2);
+
+    }
+}
+
+
+void ldd_refs_mark_s_par( lddmc_refs_task_t begin, size_t count)
+{
+    LACE_ME
+    if (count < 32)
+    {
+        while (count)
+        {
+            Task *t = begin->t;
+            if (!TASK_IS_STOLEN(t)) return;
+            if (t->f == begin->f && TASK_IS_COMPLETED(t))
+            {
+                ldd_gc_mark_rec(*(SyBDD*)TASK_RESULT(t));
             }
-            mdda=mddnode_getdown(na);
-            mddb=mddnode_getdown(nb);
-            na=GETNODE(mdda);
-            nb=GETNODE(mddb);
+            begin += 1;
+            count -= 1;
         }
-        else
-            return 0;
+    }
+    else
+    {
+        if (!TASK_IS_STOLEN(begin->t)) return;
+        ldd_refs_mark_s_par( begin, count / 2);
+        ldd_refs_mark_s_par( begin + (count / 2), count - count / 2);
 
-    } while (mdda!=lddmc_false && mddb!=lddmc_false);
-    return 1;
+    }
 }
+
+
+void ldd_gc_mark_protected()
+{
+    ldd_refs_mark_p_par( sequentiel_refs->pbegin, sequentiel_refs->pcur-sequentiel_refs->pbegin);
+    ldd_refs_mark_r_par( sequentiel_refs->rbegin, sequentiel_refs->rcur-sequentiel_refs->rbegin);
+
+
+
+//    ldd_refs_mark_s_par( lddmc_refs_key->sbegin, lddmc_refs_key->scur-lddmc_refs_key->sbegin);
+    //SYNC(lddmc_refs_mark_r_par);
+    //SYNC(lddmc_refs_mark_p_par);
+    //***********************external
+    // iterate through refs hash table, mark all found
+    /*size_t count=0;
+    uint64_t *it = refs_iter(&lddmc_refs, 0, lddmc_refs.refs_size);
+
+    while (it != NULL) {
+        ldd_gc_mark_rec(refs_next(&lddmc_refs, &it, lddmc_refs.refs_size));
+        count++;
+    }
+    /*while (count--) {
+        SYNC(lddmc_gc_mark_rec);
+    }*/
+    // iterate through refs hash table, mark all found
+    /*    count=0;
+        *it = protect_iter(&lddmc_protected, 0, lddmc_protected.refs_size);
+
+        while (it != NULL) {
+            MDD *to_mark = (MDD*)protect_next(&lddmc_protected, &it, lddmc_protected.refs_size);
+            ldd_gc_mark_rec(*to_mark);
+            count++;
+        }
+       /* while (count--) {
+            SYNC(lddmc_gc_mark_rec);
+        }*/
+    /******************************************/
+    /*  struct lddmc_ser *s;
+      avl_iter_t *it_avl = lddmc_ser_iter(lddmc_ser_set);
+
+      /* Iterate through nodes in serialization */
+    /*while ((s=lddmc_ser_iter_next(it_avl))) {
+        ldd_gc_mark_rec(s->mdd);
+    }*/
+}
+
+ldd_gc_mark_rec(MDD mdd)
+{
+    if (mdd <= lddmc_true) return;
+
+    if (llmsset_mark(nodes, mdd))
+    {
+        mddnode_t n = LDD_GETNODE(mdd);
+        ldd_gc_mark_rec(mddnode_getright(n));
+        ldd_gc_mark_rec(mddnode_getdown(n));
+
+    }
+}
+
+void  displayMDDTableInfo()
+{
+
+    printf("%zu of %zu buckets filled!\n", seq_llmsset_count_marked(nodes), llmsset_get_size(nodes));
+}
+int isGCRequired()
+{
+    return (seq_llmsset_count_marked(nodes)>llmsset_get_size(nodes)/2);
+}
+
 #endif
diff --git a/sylvan_ldd.h b/sylvan_ldd.h
old mode 100644
new mode 100755
index 4b69eb693b1d560d2d9418a965834f289174964f..62d76f87305894a20832fe953d29c115e86de0af
--- a/sylvan_ldd.h
+++ b/sylvan_ldd.h
@@ -1,5 +1,6 @@
 /*
- * Copyright 2011-2014 Formal Methods and Tools, University of Twente
+ * 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.
@@ -23,14 +24,13 @@
 extern "C" {
 #endif /* __cplusplus */
 
-
 typedef uint64_t MDD;       // Note: low 40 bits only
 
-#define lddmc_false         ((MDD)0)
-#define lddmc_true          ((MDD)1)
+static const MDD lddmc_false = 0;
+static const MDD lddmc_true = 1;
 
 /* Initialize LDD functionality */
-void sylvan_init_ldd();
+void sylvan_init_ldd(void);
 
 /* Primitives */
 MDD lddmc_makenode(uint32_t value, MDD ifeq, MDD ifneq);
@@ -52,19 +52,49 @@ MDD lddmc_make_copynode(MDD ifeq, MDD ifneq);
 int lddmc_iscopy(MDD mdd);
 MDD lddmc_followcopy(MDD mdd);
 
-/* Add or remove external reference to MDD */
-MDD lddmc_ref(MDD a);
-void lddmc_deref(MDD a);
+/**
+ * Infrastructure for external references using a hash table.
+ * Two hash tables store external references: a pointers table and a values table.
+ * The pointers table stores pointers to MDD variables, manipulated with protect and unprotect.
+ * The values table stores MDD, manipulated with ref and deref.
+ * We strongly recommend using the pointers table whenever possible.
+ */
 
-/* For use in custom mark functions */
-VOID_TASK_DECL_1(lddmc_gc_mark_rec, MDD)
-#define lddmc_gc_mark_rec(mdd) CALL(lddmc_gc_mark_rec, mdd)
+/**
+ * Store the pointer <ptr> in the pointers table.
+ */
+void lddmc_protect(MDD* ptr);
+
+/**
+ * Delete the pointer <ptr> from the pointers table.
+ */
+void lddmc_unprotect(MDD* ptr);
+
+/**
+ * Compute the number of pointers in the pointers table.
+ */
+size_t lddmc_count_protected(void);
 
-/* Return the number of external references */
-size_t lddmc_count_refs();
+/**
+ * Store the MDD <dd> in the values table.
+ */
+MDD lddmc_ref(MDD dd);
 
-/* Mark MDD for "notify on dead" */
-#define lddmc_notify_ondead(mdd) llmsset_notify_ondead(nodes, mdd)
+/**
+ * Delete the MDD <dd> from the values table.
+ */
+void lddmc_deref(MDD dd);
+
+/**
+ * Compute the number of values in the values table.
+ */
+size_t lddmc_count_refs(void);
+
+/**
+ * Call mtbdd_gc_mark_rec for every mtbdd you want to keep in your custom mark functions.
+ */
+VOID_TASK_DECL_1(lddmc_gc_mark_rec, MDD)
+#define lddmc_gc_mark_rec(mdd) CALL(lddmc_gc_mark_rec, mdd)
 
 /* Sanity check - returns depth of MDD including 'true' terminal or 0 for empty set */
 #ifndef NDEBUG
@@ -98,16 +128,9 @@ MDD lddmc_cube_copy(uint32_t* values, int* copy, size_t count);
 TASK_DECL_3(MDD, lddmc_relprod, MDD, MDD, MDD);
 #define lddmc_relprod(a, b, proj) CALL(lddmc_relprod, a, b, proj)
 
-TASK_DECL_3(MDD, lddmc_firing, MDD, MDD, MDD);
-#define lddmc_firing(cmark, minus, plus) CALL(lddmc_firing, cmark, minus, plus)
-
 TASK_DECL_4(MDD, lddmc_relprod_union, MDD, MDD, MDD, MDD);
 #define lddmc_relprod_union(a, b, meta, un) CALL(lddmc_relprod_union, a, b, meta, un)
 
-
-TASK_DECL_3(MDD, lddmc_firing_union, MDD, MDD, MDD);
-#define lddmc_firing_union(cmark, minus, plus) CALL(lddmc_firing_union, cmark, minus, plus)
-
 /**
  * Calculate all predecessors to a in uni according to rel[proj]
  * <proj> follows the same semantics as relprod
@@ -234,60 +257,63 @@ TASK_DECL_4(MDD, lddmc_compose, MDD, lddmc_compose_cb, void*, int);
 size_t lddmc_serialize_add(MDD mdd);
 size_t lddmc_serialize_get(MDD mdd);
 MDD lddmc_serialize_get_reversed(size_t value);
-void lddmc_serialize_reset();
+void lddmc_serialize_reset(void);
 void lddmc_serialize_totext(FILE *out);
 void lddmc_serialize_tofile(FILE *out);
 void lddmc_serialize_fromfile(FILE *in);
 
-/* Infrastructure for internal markings */
-typedef struct lddmc_refs_internal
-{
-    size_t r_size, r_count;
-    size_t s_size, s_count;
-    MDD *results;
-    Task **spawns;
-} *lddmc_refs_internal_t;
-
-extern DECLARE_THREAD_LOCAL(lddmc_refs_key, lddmc_refs_internal_t);
-
-static inline MDD
-lddmc_refs_push(MDD ldd)
-{
-    LOCALIZE_THREAD_LOCAL(lddmc_refs_key, lddmc_refs_internal_t);
-    if (lddmc_refs_key->r_count >= lddmc_refs_key->r_size) {
-        lddmc_refs_key->r_size *= 2;
-        lddmc_refs_key->results = (MDD*)realloc(lddmc_refs_key->results, sizeof(MDD) * lddmc_refs_key->r_size);
-    }
-    lddmc_refs_key->results[lddmc_refs_key->r_count++] = ldd;
-    return ldd;
-}
+/**
+ * Infrastructure for internal references.
+ * Every thread has its own reference stacks. There are three stacks: pointer, values, tasks stack.
+ * The pointers stack stores pointers to LDD variables, manipulated with pushptr and popptr.
+ * The values stack stores LDD, manipulated with push and pop.
+ * The tasks stack stores Lace tasks (that return LDD), manipulated with spawn and sync.
+ *
+ * It is recommended to use the pointers stack for local variables and the tasks stack for tasks.
+ */
 
-static inline void
-lddmc_refs_pop(int amount)
-{
-    LOCALIZE_THREAD_LOCAL(lddmc_refs_key, lddmc_refs_internal_t);
-    lddmc_refs_key->r_count-=amount;
-}
+/**
+ * Push a LDD variable to the pointer reference stack.
+ * During garbage collection the variable will be inspected and the contents will be marked.
+ */
+void lddmc_refs_pushptr(const MDD *ptr);
+
+/**
+ * Pop the last <amount> LDD variables from the pointer reference stack.
+ */
+void lddmc_refs_popptr(size_t amount);
+
+/**
+ * Push an LDD to the values reference stack.
+ * During garbage collection the references LDD will be marked.
+ */
+MDD lddmc_refs_push(MDD dd);
+
+/**
+ * Pop the last <amount> LDD from the values reference stack.
+ */
+void lddmc_refs_pop(long amount);
+
+/**
+ * Push a Task that returns an LDD to the tasks reference stack.
+ * Usage: lddmc_refs_spawn(SPAWN(function, ...));
+ */
+void lddmc_refs_spawn(Task *t);
+
+/**
+ * Pop a Task from the task reference stack.
+ * Usage: MDD result = lddmc_refs_sync(SYNC(function));
+ */
+MDD lddmc_refs_sync(MDD dd);
+/****************************************************************************/
+MDD __attribute__((unused)) ldd_refs_push(MDD lddmc);
+void init_gc_seq();
+void __attribute__((unused)) ldd_refs_pop(long amount);
 
-static inline void
-lddmc_refs_spawn(Task *t)
-{
-    LOCALIZE_THREAD_LOCAL(lddmc_refs_key, lddmc_refs_internal_t);
-    if (lddmc_refs_key->s_count >= lddmc_refs_key->s_size) {
-        lddmc_refs_key->s_size *= 2;
-        lddmc_refs_key->spawns = (Task**)realloc(lddmc_refs_key->spawns, sizeof(Task*) * lddmc_refs_key->s_size);
-    }
-    lddmc_refs_key->spawns[lddmc_refs_key->s_count++] = t;
-}
 
-static inline MDD
-lddmc_refs_sync(MDD result)
-{
-    LOCALIZE_THREAD_LOCAL(lddmc_refs_key, lddmc_refs_internal_t);
-    lddmc_refs_key->s_count--;
-    return result;
-}
 
+void  displayMDDTableInfo();
+int isGCRequired();
 #ifdef __cplusplus
 }
 #endif /* __cplusplus */
diff --git a/sylvan_ldd_int.h b/sylvan_ldd_int.h
new file mode 100755
index 0000000000000000000000000000000000000000..7cf65e3ba786aa8ce816b54d53222dba19f67b0e
--- /dev/null
+++ b/sylvan_ldd_int.h
@@ -0,0 +1,112 @@
+/*
+ * 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.
+ */
+
+/* Do not include this file directly. Instead, include sylvan_int.h */
+
+/**
+ * Internals for LDDs
+ */
+
+#ifndef SYLVAN_LDD_INT_H
+#define SYLVAN_LDD_INT_H
+
+/**
+ * LDD node structure
+ *
+ * RmRR RRRR RRRR VVVV | VVVV DcDD DDDD DDDD (little endian - in memory)
+ * VVVV RRRR RRRR RRRm | DDDD DDDD DDDc VVVV (big endian)
+ */
+typedef struct __attribute__((packed)) mddnode {
+    uint64_t a, b;
+} * mddnode_t; // 16 bytes
+
+static inline mddnode_t
+LDD_GETNODE(MDD mdd)
+{
+    return ((mddnode_t)llmsset_index_to_ptr(nodes, mdd));
+}
+
+static inline uint32_t __attribute__((unused))
+mddnode_getvalue(mddnode_t n)
+{
+    return *(uint32_t*)((uint8_t*)n+6);
+}
+
+static inline uint8_t __attribute__((unused))
+mddnode_getmark(mddnode_t n)
+{
+    return n->a & 1;
+}
+
+static inline uint8_t __attribute__((unused))
+mddnode_getcopy(mddnode_t n)
+{
+    return n->b & 0x10000 ? 1 : 0;
+}
+
+static inline uint64_t __attribute__((unused))
+mddnode_getright(mddnode_t n)
+{
+    return (n->a & 0x0000ffffffffffff) >> 1;
+}
+
+static inline uint64_t __attribute__((unused))
+mddnode_getdown(mddnode_t n)
+{
+    return n->b >> 17;
+}
+
+static inline void __attribute__((unused))
+mddnode_setvalue(mddnode_t n, uint32_t value)
+{
+    *(uint32_t*)((uint8_t*)n+6) = value;
+}
+
+static inline void __attribute__((unused))
+mddnode_setmark(mddnode_t n, uint8_t mark)
+{
+    n->a = (n->a & 0xfffffffffffffffe) | (mark ? 1 : 0);
+}
+
+static inline void __attribute__((unused))
+mddnode_setright(mddnode_t n, uint64_t right)
+{
+    n->a = (n->a & 0xffff000000000001) | (right << 1);
+}
+
+static inline void __attribute__((unused))
+mddnode_setdown(mddnode_t n, uint64_t down)
+{
+    n->b = (n->b & 0x000000000001ffff) | (down << 17);
+}
+
+static inline void __attribute__((unused))
+mddnode_make(mddnode_t n, uint32_t value, uint64_t right, uint64_t down)
+{
+    n->a = right << 1;
+    n->b = down << 17;
+    *(uint32_t*)((uint8_t*)n+6) = value;
+}
+
+static inline void __attribute__((unused))
+mddnode_makecopy(mddnode_t n, uint64_t right, uint64_t down)
+{
+    n->a = right << 1;
+    n->b = ((down << 1) | 1) << 16;
+}
+
+#endif
diff --git a/sylvan_mt.c b/sylvan_mt.c
new file mode 100755
index 0000000000000000000000000000000000000000..543a81e985f84d969a7bb24b180197db841ca419
--- /dev/null
+++ b/sylvan_mt.c
@@ -0,0 +1,261 @@
+/*
+ * 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> // for llmsset*, nodes, sylvan_register_quit
+
+#include <inttypes.h>
+#include <string.h>
+
+/**
+ * Handling of custom leaves "registry"
+ */
+
+typedef struct
+{
+    sylvan_mt_hash_cb hash_cb;
+    sylvan_mt_equals_cb equals_cb;
+    sylvan_mt_create_cb create_cb;
+    sylvan_mt_destroy_cb destroy_cb;
+    sylvan_mt_to_str_cb to_str_cb;
+    sylvan_mt_write_binary_cb write_binary_cb;
+    sylvan_mt_read_binary_cb read_binary_cb;
+} customleaf_t;
+
+static customleaf_t *cl_registry;
+static size_t cl_registry_count;
+static size_t cl_registry_size;
+
+/**
+ * Implementation of hooks for llmsset
+ */
+
+/**
+ * Internal helper function
+ */
+static inline customleaf_t*
+sylvan_mt_from_node(uint64_t a, uint64_t b)
+{
+    uint32_t type = a & 0xffffffff;
+    assert(type < cl_registry_count);
+    return cl_registry + type;
+    (void)b;
+}
+
+static void
+_sylvan_create_cb(uint64_t *a, uint64_t *b)
+{
+    customleaf_t *c = sylvan_mt_from_node(*a, *b);
+    if (c->create_cb != NULL) c->create_cb(b);
+}
+
+static void
+_sylvan_destroy_cb(uint64_t a, uint64_t b)
+{
+    // for leaf
+    customleaf_t *c = sylvan_mt_from_node(a, b);
+    if (c->destroy_cb != NULL) c->destroy_cb(b);
+}
+
+static uint64_t
+_sylvan_hash_cb(uint64_t a, uint64_t b, uint64_t seed)
+{
+    customleaf_t *c = sylvan_mt_from_node(a, b);
+    if (c->hash_cb != NULL) return c->hash_cb(b, seed ^ a);
+    else return llmsset_hash(a, b, seed);
+}
+
+static int
+_sylvan_equals_cb(uint64_t a, uint64_t b, uint64_t aa, uint64_t bb)
+{
+    if (a != aa) return 0;
+    customleaf_t *c = sylvan_mt_from_node(a, b);
+    if (c->equals_cb != NULL) return c->equals_cb(b, bb);
+    else return b == bb ? 1 : 0;
+}
+
+uint32_t
+sylvan_mt_create_type()
+{
+    if (cl_registry_count == cl_registry_size) {
+        // resize registry array
+        cl_registry_size += 8;
+        cl_registry = (customleaf_t *)realloc(cl_registry, sizeof(customleaf_t) * (cl_registry_size));
+        memset(cl_registry + cl_registry_count, 0, sizeof(customleaf_t) * (cl_registry_size-cl_registry_count));
+    }
+    return cl_registry_count++;
+}
+
+void sylvan_mt_set_hash(uint32_t type, sylvan_mt_hash_cb hash_cb)
+{
+    customleaf_t *c = cl_registry + type;
+    c->hash_cb = hash_cb;
+}
+
+void sylvan_mt_set_equals(uint32_t type, sylvan_mt_equals_cb equals_cb)
+{
+    customleaf_t *c = cl_registry + type;
+    c->equals_cb = equals_cb;
+}
+
+void sylvan_mt_set_create(uint32_t type, sylvan_mt_create_cb create_cb)
+{
+    customleaf_t *c = cl_registry + type;
+    c->create_cb = create_cb;
+}
+
+void sylvan_mt_set_destroy(uint32_t type, sylvan_mt_destroy_cb destroy_cb)
+{
+    customleaf_t *c = cl_registry + type;
+    c->destroy_cb = destroy_cb;
+}
+
+void sylvan_mt_set_to_str(uint32_t type, sylvan_mt_to_str_cb to_str_cb)
+{
+    customleaf_t *c = cl_registry + type;
+    c->to_str_cb = to_str_cb;
+}
+
+void sylvan_mt_set_write_binary(uint32_t type, sylvan_mt_write_binary_cb write_binary_cb)
+{
+    customleaf_t *c = cl_registry + type;
+    c->write_binary_cb = write_binary_cb;
+}
+
+void sylvan_mt_set_read_binary(uint32_t type, sylvan_mt_read_binary_cb read_binary_cb)
+{
+    customleaf_t *c = cl_registry + type;
+    c->read_binary_cb = read_binary_cb;
+}
+
+/**
+ * Initialize and quit functions
+ */
+
+static int mt_initialized = 0;
+
+static void
+sylvan_mt_quit()
+{
+    if (mt_initialized == 0) return;
+    mt_initialized = 0;
+
+    free(cl_registry);
+    cl_registry = NULL;
+    cl_registry_count = 0;
+    cl_registry_size = 0;
+}
+
+void
+sylvan_init_mt()
+{
+    if (mt_initialized) return;
+    mt_initialized = 1;
+
+    // Register quit handler to free structures
+    sylvan_register_quit(sylvan_mt_quit);
+
+    // Tell llmsset to use our custom hooks
+    llmsset_set_custom(nodes, _sylvan_hash_cb, _sylvan_equals_cb, _sylvan_create_cb, _sylvan_destroy_cb);
+
+    // Initialize data structures
+    cl_registry_size = 8;
+    cl_registry = (customleaf_t *)calloc(sizeof(customleaf_t), cl_registry_size);
+    cl_registry_count = 3; // 0, 1, 2 are taken
+}
+
+/**
+ * Return 1 if the given <type> has a custom hash callback, or 0 otherwise.
+ */
+int
+sylvan_mt_has_custom_hash(uint32_t type)
+{
+    assert(type < cl_registry_count);
+    customleaf_t *c = cl_registry + type;
+    return c->hash_cb != NULL ? 1 : 0;
+}
+
+/**
+ * Convert a leaf (possibly complemented) to a string representation.
+ * If it does not fit in <buf> of size <buflen>, returns a freshly allocated char* array.
+ */
+char*
+sylvan_mt_to_str(int complement, uint32_t type, uint64_t value, char* buf, size_t buflen)
+{
+    assert(type < cl_registry_count);
+    customleaf_t *c = cl_registry + type;
+    if (type == 0) {
+        size_t required = (size_t)snprintf(NULL, 0, "%" PRId64, (int64_t)value);
+        char *ptr = buf;
+        if (buflen < required) {
+            ptr = (char*)malloc(required);
+            buflen = required;
+        }
+        if (ptr != NULL) snprintf(ptr, buflen, "%" PRId64, (int64_t)value);
+        return ptr;
+    } else if (type == 1) {
+        size_t required = (size_t)snprintf(NULL, 0, "%f", *(double*)&value);
+        char *ptr = buf;
+        if (buflen < required) {
+            ptr = (char*)malloc(required);
+            buflen = required;
+        }
+        if (ptr != NULL) snprintf(ptr, buflen, "%f", *(double*)&value);
+        return ptr;
+    } else if (type == 2) {
+        int32_t num = (int32_t)(value>>32);
+        uint32_t denom = value&0xffffffff;
+        size_t required = (size_t)snprintf(NULL, 0, "%" PRId32 "/%" PRIu32, num, denom);
+        char *ptr = buf;
+        if (buflen < required) {
+            ptr = (char*)malloc(required);
+            buflen = required;
+        }
+        if (ptr != NULL) snprintf(ptr, buflen, "%" PRId32 "/%" PRIu32, num, denom);
+        return ptr;
+    } else if (c->to_str_cb != NULL) {
+        return c->to_str_cb(complement, value, buf, buflen);
+    } else {
+        return NULL;
+    }
+}
+
+uint64_t
+sylvan_mt_hash(uint32_t type, uint64_t value, uint64_t seed)
+{
+    assert(type < cl_registry_count);
+    customleaf_t *c = cl_registry + type;
+    if (c->hash_cb != NULL) return c->hash_cb(value, seed);
+    else return llmsset_hash((uint64_t)type, value, seed);
+}
+
+int
+sylvan_mt_write_binary(uint32_t type, uint64_t value, FILE *out)
+{
+    assert(type < cl_registry_count);
+    customleaf_t *c = cl_registry + type;
+    if (c->write_binary_cb != NULL) return c->write_binary_cb(out, value);
+    else return 0;
+}
+
+int
+sylvan_mt_read_binary(uint32_t type, uint64_t *value, FILE *in)
+{
+    assert(type < cl_registry_count);
+    customleaf_t *c = cl_registry + type;
+    if (c->read_binary_cb != NULL) return c->read_binary_cb(in, value);
+    else return 0;
+}
diff --git a/sylvan_mt.h b/sylvan_mt.h
new file mode 100755
index 0000000000000000000000000000000000000000..280bc620ba73eb5ba7b44beeea606d9c829b82ac
--- /dev/null
+++ b/sylvan_mt.h
@@ -0,0 +1,130 @@
+/*
+ * 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.
+ */
+
+/**
+ * This file contains declarations for custom Multi-Terminal support.
+ */
+
+/* Do not include this file directly. Instead, include sylvan.h */
+
+#ifndef SYLVAN_MT_H
+#define SYLVAN_MT_H
+
+#ifdef __cplusplus
+extern "C" {
+#endif /* __cplusplus */
+
+/**
+ * Helper implementation for custom terminal (multi-terminal support)
+ * Types can implement the following callback functions:
+ *
+ * hash(value, seed)
+ *     return hash of value with given seed
+ * equals(value1, value2)
+ *     return 1 if equal, 0 if not equal
+ * create(&value)
+ *     optionally allocate object and update value with the pointer
+ * destroy(value)
+ *     optionally destroy/free value if value points to an allocated object
+ * to_str(complemented, value, buf, bufsize)
+ *     return string representation of (complemented) value to buf if bufsize large enough,
+ *     otherwise return newly allocated string
+ * write_binary(fp, value)
+ *     write binary representation of a leaf to given FILE* fp
+ *     return 0 if successful
+ * read_binary(fp, &value)
+ *     read binary representation of a leaf from given FILE* fp
+ *     treat allocated objects like create (and destroy)
+ *     return 0 if successful
+ *
+ * If the 64-byte value already completely describes the leaf, then the functions
+ * write_binary and read_binary should be set to NULL.
+ *
+ * If the 64-byte value is also already a canonical representation, then the functions
+ * hash, equals, create and destroy should be set to NULL.
+ *
+ * Two values are equal (with equals) iff they have the same hash (with hash)
+ *
+ * A value v obtained due to create must be equal to the original value (with equals):
+ *     create(v) => equals(\old(v), \new(v))
+ *
+ * NOTE ON EXPECTED LEAF NODE STRUCTURE: the implementation expects leaves in a specific format:
+ * - 16-byte node { uint64_t a, b; }
+ * - type  == a & 0x00000000ffffffff
+ * - value == b
+ */
+typedef uint64_t (*sylvan_mt_hash_cb)(uint64_t, uint64_t);
+typedef int (*sylvan_mt_equals_cb)(uint64_t, uint64_t);
+typedef void (*sylvan_mt_create_cb)(uint64_t*);
+typedef void (*sylvan_mt_destroy_cb)(uint64_t);
+typedef char* (*sylvan_mt_to_str_cb)(int, uint64_t, char*, size_t);
+typedef int (*sylvan_mt_write_binary_cb)(FILE*, uint64_t);
+typedef int (*sylvan_mt_read_binary_cb)(FILE*, uint64_t*);
+
+/**
+ * Initialize the multi-terminal subsystem
+ */
+void sylvan_init_mt(void);
+
+/**
+ * Register a new leaf type.
+ */
+uint32_t sylvan_mt_create_type(void);
+
+/**
+ * Set the callback handlers for <type>
+ */
+void sylvan_mt_set_hash(uint32_t type, sylvan_mt_hash_cb hash_cb);
+void sylvan_mt_set_equals(uint32_t type, sylvan_mt_equals_cb equals_cb);
+void sylvan_mt_set_create(uint32_t type, sylvan_mt_create_cb create_cb);
+void sylvan_mt_set_destroy(uint32_t type, sylvan_mt_destroy_cb destroy_cb);
+void sylvan_mt_set_to_str(uint32_t type, sylvan_mt_to_str_cb to_str_cb);
+void sylvan_mt_set_write_binary(uint32_t type, sylvan_mt_write_binary_cb write_binary_cb);
+void sylvan_mt_set_read_binary(uint32_t type, sylvan_mt_read_binary_cb read_binary_cb);
+
+/**
+ * Returns 1 if the given type implements hash, or 0 otherwise.
+ * (used when inserting into the unique table)
+ */
+int sylvan_mt_has_custom_hash(uint32_t type);
+
+/**
+ * Get a hash for given value (calls hash callback of type).
+ * If the type does not implement hash, then this is the same hash as used by the unique table.
+ */
+uint64_t sylvan_mt_hash(uint32_t type, uint64_t value, uint64_t seed);
+
+/**
+ * Get text representation of leaf (calls to_str callback of type).
+ */
+char *sylvan_mt_to_str(int complement, uint32_t type, uint64_t value, char *buf, size_t buflen);
+
+/**
+ * Write a leaf in binary form (calls write_binary callback of type).
+ */
+int sylvan_mt_write_binary(uint32_t type, uint64_t value, FILE *out);
+
+/**
+ * Read a leaf in binary form (calls read_binary callback of type).
+ */
+int sylvan_mt_read_binary(uint32_t type, uint64_t *value, FILE *in);
+
+#ifdef __cplusplus
+}
+#endif /* __cplusplus */
+
+#endif
diff --git a/sylvan_mtbdd.c b/sylvan_mtbdd.c
old mode 100644
new mode 100755
index 2ac6bfe0989f7aeefcc7ae233547f254e452475b..49602dcfc558207769f9593a7546110326c191f0
--- a/sylvan_mtbdd.c
+++ b/sylvan_mtbdd.c
@@ -1,5 +1,6 @@
 /*
- * Copyright 2011-2015 Formal Methods and Tools, University of Twente
+ * 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.
@@ -14,61 +15,54 @@
  * limitations under the License.
  */
 
-#include <sylvan_config.h>
+#include <sylvan_int.h>
 
-#include <assert.h>
 #include <inttypes.h>
 #include <math.h>
-#include <pthread.h>
-#include <stdint.h>
-#include <stdio.h>
-#include <stdlib.h>
 #include <string.h>
 
-#include <refs.h>
+#include <sylvan_refs.h>
+#include <sylvan_sl.h>
 #include <sha2.h>
-#include <sylvan.h>
-#include <sylvan_common.h>
-#include <sylvan_mtbdd_int.h>
 
 /* Primitives */
 int
 mtbdd_isleaf(MTBDD bdd)
 {
     if (bdd == mtbdd_true || bdd == mtbdd_false) return 1;
-    return mtbddnode_isleaf(GETNODE(bdd));
+    return mtbddnode_isleaf(MTBDD_GETNODE(bdd));
 }
 
 // for nodes
 uint32_t
 mtbdd_getvar(MTBDD node)
 {
-    return mtbddnode_getvariable(GETNODE(node));
+    return mtbddnode_getvariable(MTBDD_GETNODE(node));
 }
 
 MTBDD
 mtbdd_getlow(MTBDD mtbdd)
 {
-    return node_getlow(mtbdd, GETNODE(mtbdd));
+    return node_getlow(mtbdd, MTBDD_GETNODE(mtbdd));
 }
 
 MTBDD
 mtbdd_gethigh(MTBDD mtbdd)
 {
-    return node_gethigh(mtbdd, GETNODE(mtbdd));
+    return node_gethigh(mtbdd, MTBDD_GETNODE(mtbdd));
 }
 
 // for leaves
 uint32_t
 mtbdd_gettype(MTBDD leaf)
 {
-    return mtbddnode_gettype(GETNODE(leaf));
+    return mtbddnode_gettype(MTBDD_GETNODE(leaf));
 }
 
 uint64_t
 mtbdd_getvalue(MTBDD leaf)
 {
-    return mtbddnode_getvalue(GETNODE(leaf));
+    return mtbddnode_getvalue(MTBDD_GETNODE(leaf));
 }
 
 // for leaf type 0 (integer)
@@ -97,8 +91,8 @@ VOID_TASK_IMPL_1(mtbdd_gc_mark_rec, MDD, mtbdd)
     if (mtbdd == mtbdd_true) return;
     if (mtbdd == mtbdd_false) return;
 
-    if (llmsset_mark(nodes, mtbdd&(~mtbdd_complement))) {
-        mtbddnode_t n = GETNODE(mtbdd);
+    if (llmsset_mark(nodes, MTBDD_STRIPMARK(mtbdd))) {
+        mtbddnode_t n = MTBDD_GETNODE(mtbdd);
         if (!mtbddnode_isleaf(n)) {
             SPAWN(mtbdd_gc_mark_rec, mtbddnode_getlow(n));
             CALL(mtbdd_gc_mark_rec, mtbddnode_gethigh(n));
@@ -119,7 +113,7 @@ MDD
 mtbdd_ref(MDD a)
 {
     if (a == mtbdd_true || a == mtbdd_false) return a;
-    refs_up(&mtbdd_refs, a);
+    refs_up(&mtbdd_refs, MTBDD_STRIPMARK(a));
     return a;
 }
 
@@ -127,7 +121,7 @@ void
 mtbdd_deref(MDD a)
 {
     if (a == mtbdd_true || a == mtbdd_false) return;
-    refs_down(&mtbdd_refs, a);
+    refs_down(&mtbdd_refs, MTBDD_STRIPMARK(a));
 }
 
 size_t
@@ -180,7 +174,7 @@ VOID_TASK_0(mtbdd_gc_mark_protected)
     size_t count=0;
     uint64_t *it = protect_iter(&mtbdd_protected, 0, mtbdd_protected.refs_size);
     while (it != NULL) {
-        syBDD *to_mark = (syBDD*)protect_next(&mtbdd_protected, &it, mtbdd_protected.refs_size);
+        SyBDD *to_mark = (SyBDD*)protect_next(&mtbdd_protected, &it, mtbdd_protected.refs_size);
         SPAWN(mtbdd_gc_mark_rec, *to_mark);
         count++;
     }
@@ -190,33 +184,77 @@ VOID_TASK_0(mtbdd_gc_mark_protected)
 }
 
 /* Infrastructure for internal markings */
+typedef struct mtbdd_refs_task
+{
+    Task *t;
+    void *f;
+} *mtbdd_refs_task_t;
+
+typedef struct mtbdd_refs_internal
+{
+    const MTBDD **pbegin, **pend, **pcur;
+    MTBDD *rbegin, *rend, *rcur;
+    mtbdd_refs_task_t sbegin, send, scur;
+} *mtbdd_refs_internal_t;
+
 DECLARE_THREAD_LOCAL(mtbdd_refs_key, mtbdd_refs_internal_t);
 
-VOID_TASK_0(mtbdd_refs_mark_task)
+VOID_TASK_2(mtbdd_refs_mark_p_par, const MTBDD**, begin, size_t, count)
 {
-    LOCALIZE_THREAD_LOCAL(mtbdd_refs_key, mtbdd_refs_internal_t);
-    size_t i, j=0;
-    for (i=0; i<mtbdd_refs_key->r_count; i++) {
-        if (j >= 40) {
-            while (j--) SYNC(mtbdd_gc_mark_rec);
-            j=0;
+    if (count < 32) {
+        while (count) {
+            mtbdd_gc_mark_rec(**(begin++));
+            count--;
+        }
+    } else {
+        SPAWN(mtbdd_refs_mark_p_par, begin, count / 2);
+        CALL(mtbdd_refs_mark_p_par, begin + (count / 2), count - count / 2);
+        SYNC(mtbdd_refs_mark_p_par);
+    }
+}
+
+VOID_TASK_2(mtbdd_refs_mark_r_par, MTBDD*, begin, size_t, count)
+{
+    if (count < 32) {
+        while (count) {
+            mtbdd_gc_mark_rec(*begin++);
+            count--;
         }
-        SPAWN(mtbdd_gc_mark_rec, mtbdd_refs_key->results[i]);
-        j++;
-    }
-    for (i=0; i<mtbdd_refs_key->s_count; i++) {
-        Task *t = mtbdd_refs_key->spawns[i];
-        if (!TASK_IS_STOLEN(t)) break;
-        if (TASK_IS_COMPLETED(t)) {
-            if (j >= 40) {
-                while (j--) SYNC(mtbdd_gc_mark_rec);
-                j=0;
+    } else {
+        SPAWN(mtbdd_refs_mark_r_par, begin, count / 2);
+        CALL(mtbdd_refs_mark_r_par, begin + (count / 2), count - count / 2);
+        SYNC(mtbdd_refs_mark_r_par);
+    }
+}
+
+VOID_TASK_2(mtbdd_refs_mark_s_par, mtbdd_refs_task_t, begin, size_t, count)
+{
+    if (count < 32) {
+        while (count > 0) {
+            Task *t = begin->t;
+            if (!TASK_IS_STOLEN(t)) return;
+            if (t->f == begin->f && TASK_IS_COMPLETED(t)) {
+                mtbdd_gc_mark_rec(*(MTBDD*)TASK_RESULT(t));
             }
-            SPAWN(mtbdd_gc_mark_rec, *(syBDD*)TASK_RESULT(t));
-            j++;
+            begin += 1;
+            count -= 1;
         }
+    } else {
+        if (!TASK_IS_STOLEN(begin->t)) return;
+        SPAWN(mtbdd_refs_mark_s_par, begin, count / 2);
+        CALL(mtbdd_refs_mark_s_par, begin + (count / 2), count - count / 2);
+        SYNC(mtbdd_refs_mark_s_par);
     }
-    while (j--) SYNC(mtbdd_gc_mark_rec);
+}
+
+VOID_TASK_0(mtbdd_refs_mark_task)
+{
+    LOCALIZE_THREAD_LOCAL(mtbdd_refs_key, mtbdd_refs_internal_t);
+    SPAWN(mtbdd_refs_mark_p_par, mtbdd_refs_key->pbegin, mtbdd_refs_key->pcur-mtbdd_refs_key->pbegin);
+    SPAWN(mtbdd_refs_mark_r_par, mtbdd_refs_key->rbegin, mtbdd_refs_key->rcur-mtbdd_refs_key->rbegin);
+    CALL(mtbdd_refs_mark_s_par, mtbdd_refs_key->sbegin, mtbdd_refs_key->scur-mtbdd_refs_key->sbegin);
+    SYNC(mtbdd_refs_mark_r_par);
+    SYNC(mtbdd_refs_mark_p_par);
 }
 
 VOID_TASK_0(mtbdd_refs_mark)
@@ -227,12 +265,12 @@ VOID_TASK_0(mtbdd_refs_mark)
 VOID_TASK_0(mtbdd_refs_init_task)
 {
     mtbdd_refs_internal_t s = (mtbdd_refs_internal_t)malloc(sizeof(struct mtbdd_refs_internal));
-    s->r_size = 128;
-    s->r_count = 0;
-    s->s_size = 128;
-    s->s_count = 0;
-    s->results = (syBDD*)malloc(sizeof(syBDD) * 128);
-    s->spawns = (Task**)malloc(sizeof(Task*) * 128);
+    s->pcur = s->pbegin = (const MTBDD**)malloc(sizeof(MTBDD*) * 1024);
+    s->pend = s->pbegin + 1024;
+    s->rcur = s->rbegin = (MTBDD*)malloc(sizeof(MTBDD) * 1024);
+    s->rend = s->rbegin + 1024;
+    s->scur = s->sbegin = (mtbdd_refs_task_t)malloc(sizeof(struct mtbdd_refs_task) * 1024);
+    s->send = s->sbegin + 1024;
     SET_THREAD_LOCAL(mtbdd_refs_key, s);
 }
 
@@ -240,100 +278,93 @@ VOID_TASK_0(mtbdd_refs_init)
 {
     INIT_THREAD_LOCAL(mtbdd_refs_key);
     TOGETHER(mtbdd_refs_init_task);
-    sylvan_gc_add_mark(10, TASK(mtbdd_refs_mark));
+    sylvan_gc_add_mark(TASK(mtbdd_refs_mark));
 }
 
-/**
- * Handling of custom leaves "registry"
- */
+void
+mtbdd_refs_ptrs_up(mtbdd_refs_internal_t mtbdd_refs_key)
+{
+    size_t cur = mtbdd_refs_key->pcur - mtbdd_refs_key->pbegin;
+    size_t size = mtbdd_refs_key->pend - mtbdd_refs_key->pbegin;
+    mtbdd_refs_key->pbegin = (const MTBDD**)realloc(mtbdd_refs_key->pbegin, sizeof(MTBDD*) * size * 2);
+    mtbdd_refs_key->pcur = mtbdd_refs_key->pbegin + cur;
+    mtbdd_refs_key->pend = mtbdd_refs_key->pbegin + (size * 2);
+}
 
-typedef struct
+MTBDD __attribute__((noinline))
+mtbdd_refs_refs_up(mtbdd_refs_internal_t mtbdd_refs_key, MTBDD res)
 {
-    mtbdd_hash_cb hash_cb;
-    mtbdd_equals_cb equals_cb;
-    mtbdd_create_cb create_cb;
-    mtbdd_destroy_cb destroy_cb;
-} customleaf_t;
+    long size = mtbdd_refs_key->rend - mtbdd_refs_key->rbegin;
+    mtbdd_refs_key->rbegin = (MTBDD*)realloc(mtbdd_refs_key->rbegin, sizeof(MTBDD) * size * 2);
+    mtbdd_refs_key->rcur = mtbdd_refs_key->rbegin + size;
+    mtbdd_refs_key->rend = mtbdd_refs_key->rbegin + (size * 2);
+    return res;
+}
 
-static customleaf_t *cl_registry;
-static size_t cl_registry_count;
+void __attribute__((noinline))
+mtbdd_refs_tasks_up(mtbdd_refs_internal_t mtbdd_refs_key)
+{
+    long size = mtbdd_refs_key->send - mtbdd_refs_key->sbegin;
+    mtbdd_refs_key->sbegin = (mtbdd_refs_task_t)realloc(mtbdd_refs_key->sbegin, sizeof(struct mtbdd_refs_task) * size * 2);
+    mtbdd_refs_key->scur = mtbdd_refs_key->sbegin + size;
+    mtbdd_refs_key->send = mtbdd_refs_key->sbegin + (size * 2);
+}
 
-static void
-_mtbdd_create_cb(uint64_t *a, uint64_t *b)
+void __attribute__((unused))
+mtbdd_refs_pushptr(const MTBDD *ptr)
 {
-    // for leaf
-    if ((*a & 0x4000000000000000) == 0) return; // huh?
-    uint32_t type = *a & 0xffffffff;
-    if (type >= cl_registry_count) return; // not in registry
-    customleaf_t *c = cl_registry + type;
-    if (c->create_cb == NULL) return; // not in registry
-    c->create_cb(b);
+    LOCALIZE_THREAD_LOCAL(mtbdd_refs_key, mtbdd_refs_internal_t);
+    *mtbdd_refs_key->pcur++ = ptr;
+    if (mtbdd_refs_key->pcur == mtbdd_refs_key->pend) mtbdd_refs_ptrs_up(mtbdd_refs_key);
 }
 
-static void
-_mtbdd_destroy_cb(uint64_t a, uint64_t b)
+void __attribute__((unused))
+mtbdd_refs_popptr(size_t amount)
 {
-    // for leaf
-    if ((a & 0x4000000000000000) == 0) return; // huh?
-    uint32_t type = a & 0xffffffff;
-    if (type >= cl_registry_count) return; // not in registry
-    customleaf_t *c = cl_registry + type;
-    if (c->destroy_cb == NULL) return; // not in registry
-    c->destroy_cb(b);
+    LOCALIZE_THREAD_LOCAL(mtbdd_refs_key, mtbdd_refs_internal_t);
+    mtbdd_refs_key->pcur -= amount;
 }
 
-static uint64_t
-_mtbdd_hash_cb(uint64_t a, uint64_t b, uint64_t seed)
+MTBDD __attribute__((unused))
+mtbdd_refs_push(MTBDD mtbdd)
 {
-    // for leaf
-    if ((a & 0x4000000000000000) == 0) return llmsset_hash(a, b, seed);
-    uint32_t type = a & 0xffffffff;
-    if (type >= cl_registry_count) return llmsset_hash(a, b, seed);
-    customleaf_t *c = cl_registry + type;
-    if (c->hash_cb == NULL) return llmsset_hash(a, b, seed);
-    return c->hash_cb(b, seed ^ a);
+    LOCALIZE_THREAD_LOCAL(mtbdd_refs_key, mtbdd_refs_internal_t);
+    *(mtbdd_refs_key->rcur++) = mtbdd;
+    if (mtbdd_refs_key->rcur == mtbdd_refs_key->rend) return mtbdd_refs_refs_up(mtbdd_refs_key, mtbdd);
+    else return mtbdd;
 }
 
-static int
-_mtbdd_equals_cb(uint64_t a, uint64_t b, uint64_t aa, uint64_t bb)
+void __attribute__((unused))
+mtbdd_refs_pop(long amount)
 {
-    // for leaf
-    if (a != aa) return 0;
-    if ((a & 0x4000000000000000) == 0) return b == bb ? 1 : 0;
-    if ((aa & 0x4000000000000000) == 0) return b == bb ? 1 : 0;
-    uint32_t type = a & 0xffffffff;
-    if (type >= cl_registry_count) return b == bb ? 1 : 0;
-    customleaf_t *c = cl_registry + type;
-    if (c->equals_cb == NULL) return b == bb ? 1 : 0;
-    return c->equals_cb(b, bb);
+    LOCALIZE_THREAD_LOCAL(mtbdd_refs_key, mtbdd_refs_internal_t);
+    mtbdd_refs_key->rcur -= amount;
 }
 
-uint32_t
-mtbdd_register_custom_leaf(mtbdd_hash_cb hash_cb, mtbdd_equals_cb equals_cb, mtbdd_create_cb create_cb, mtbdd_destroy_cb destroy_cb)
+void
+mtbdd_refs_spawn(Task *t)
 {
-    uint32_t type = cl_registry_count;
-    if (type == 0) type = 3;
-    if (cl_registry == NULL) {
-        cl_registry = (customleaf_t *)calloc(sizeof(customleaf_t), (type+1));
-        cl_registry_count = type+1;
-        llmsset_set_custom(nodes, _mtbdd_hash_cb, _mtbdd_equals_cb, _mtbdd_create_cb, _mtbdd_destroy_cb);
-    } else if (cl_registry_count <= type) {
-        cl_registry = (customleaf_t *)realloc(cl_registry, sizeof(customleaf_t) * (type+1));
-        memset(cl_registry + cl_registry_count, 0, sizeof(customleaf_t) * (type+1-cl_registry_count));
-        cl_registry_count = type+1;
-    }
-    customleaf_t *c = cl_registry + type;
-    c->hash_cb = hash_cb;
-    c->equals_cb = equals_cb;
-    c->create_cb = create_cb;
-    c->destroy_cb = destroy_cb;
-    return type;
+    LOCALIZE_THREAD_LOCAL(mtbdd_refs_key, mtbdd_refs_internal_t);
+    mtbdd_refs_key->scur->t = t;
+    mtbdd_refs_key->scur->f = t->f;
+    mtbdd_refs_key->scur += 1;
+    if (mtbdd_refs_key->scur == mtbdd_refs_key->send) mtbdd_refs_tasks_up(mtbdd_refs_key);
+}
+
+MTBDD
+mtbdd_refs_sync(MTBDD result)
+{
+    LOCALIZE_THREAD_LOCAL(mtbdd_refs_key, mtbdd_refs_internal_t);
+    mtbdd_refs_key->scur -= 1;
+    return result;
 }
 
 /**
  * Initialize and quit functions
  */
 
+static int mtbdd_initialized = 0;
+
 static void
 mtbdd_quit()
 {
@@ -342,25 +373,21 @@ mtbdd_quit()
         protect_free(&mtbdd_protected);
         mtbdd_protected_created = 0;
     }
-    if (cl_registry != NULL) {
-        free(cl_registry);
-        cl_registry = NULL;
-        cl_registry_count = 0;
-    }
+
+    mtbdd_initialized = 0;
 }
 
 void
 sylvan_init_mtbdd()
 {
-    sylvan_register_quit(mtbdd_quit);
-    sylvan_gc_add_mark(10, TASK(mtbdd_gc_mark_external_refs));
-    sylvan_gc_add_mark(10, TASK(mtbdd_gc_mark_protected));
+    sylvan_init_mt();
 
-    // Sanity check
-    if (sizeof(struct mtbddnode) != 16) {
-        fprintf(stderr, "Invalid size of mtbdd nodes: %ld\n", sizeof(struct mtbddnode));
-        exit(1);
-    }
+    if (mtbdd_initialized) return;
+    mtbdd_initialized = 1;
+
+    sylvan_register_quit(mtbdd_quit);
+    sylvan_gc_add_mark(TASK(mtbdd_gc_mark_external_refs));
+    sylvan_gc_add_mark(TASK(mtbdd_gc_mark_protected));
 
     refs_create(&mtbdd_refs, 1024);
     if (!mtbdd_protected_created) {
@@ -370,9 +397,6 @@ sylvan_init_mtbdd()
 
     LACE_ME;
     CALL(mtbdd_refs_init);
-
-    cl_registry = NULL;
-    cl_registry_count = 0;
 }
 
 /**
@@ -384,7 +408,7 @@ mtbdd_makeleaf(uint32_t type, uint64_t value)
     struct mtbddnode n;
     mtbddnode_makeleaf(&n, type, value);
 
-    int custom = type < cl_registry_count && cl_registry[type].hash_cb != NULL ? 1 : 0;
+    int custom = sylvan_mt_has_custom_hash(type);
 
     int created;
     uint64_t index = custom ? llmsset_lookupc(nodes, n.a, n.b, &created) : llmsset_lookup(nodes, n.a, n.b, &created);
@@ -395,19 +419,20 @@ mtbdd_makeleaf(uint32_t type, uint64_t value)
 
         index = custom ? llmsset_lookupc(nodes, n.a, n.b, &created) : llmsset_lookup(nodes, n.a, n.b, &created);
         if (index == 0) {
-            fprintf(stderr, "BDD Unique table full, %zu of %zu buckets filled!\n", llmsset_count_marked(nodes), llmsset_get_size(nodes));
+            fprintf(stderr, "SyBDD Unique table full, %zu of %zu buckets filled!\n", llmsset_count_marked(nodes), llmsset_get_size(nodes));
             exit(1);
         }
     }
 
+    if (created) sylvan_stats_count(BDD_NODES_CREATED);
+    else sylvan_stats_count(BDD_NODES_REUSED);
+
     return (MTBDD)index;
 }
 
 MTBDD
-mtbdd_makenode(uint32_t var, MTBDD low, MTBDD high)
+_mtbdd_makenode(uint32_t var, MTBDD low, MTBDD high)
 {
-    if (low == high) return low;
-
     // Normalization to keep canonicity
     // low will have no mark
 
@@ -436,15 +461,57 @@ mtbdd_makenode(uint32_t var, MTBDD low, MTBDD high)
 
         index = llmsset_lookup(nodes, n.a, n.b, &created);
         if (index == 0) {
-            fprintf(stderr, "BDD Unique table full, %zu of %zu buckets filled!\n", llmsset_count_marked(nodes), llmsset_get_size(nodes));
+            fprintf(stderr, "SyBDD Unique table full, %zu of %zu buckets filled!\n", llmsset_count_marked(nodes), llmsset_get_size(nodes));
             exit(1);
         }
     }
 
+    if (created) sylvan_stats_count(BDD_NODES_CREATED);
+    else sylvan_stats_count(BDD_NODES_REUSED);
+
     result = index;
     return mark ? result | mtbdd_complement : result;
 }
 
+MTBDD
+mtbdd_makemapnode(uint32_t var, MTBDD low, MTBDD high)
+{
+    struct mtbddnode n;
+    uint64_t index;
+    int created;
+
+    // in an MTBDDMAP, the low edges eventually lead to 0 and cannot have a low mark
+    assert(!MTBDD_HASMARK(low));
+
+    mtbddnode_makemapnode(&n, var, low, high);
+    index = llmsset_lookup(nodes, n.a, n.b, &created);
+    if (index == 0) {
+        LACE_ME;
+
+        mtbdd_refs_push(low);
+        mtbdd_refs_push(high);
+        sylvan_gc();
+        mtbdd_refs_pop(2);
+
+        index = llmsset_lookup(nodes, n.a, n.b, &created);
+        if (index == 0) {
+            fprintf(stderr, "SyBDD Unique table full, %zu of %zu buckets filled!\n", llmsset_count_marked(nodes), llmsset_get_size(nodes));
+            exit(1);
+        }
+    }
+
+    if (created) sylvan_stats_count(BDD_NODES_CREATED);
+    else sylvan_stats_count(BDD_NODES_REUSED);
+
+    return index;
+}
+
+MTBDD
+mtbdd_ithvar(uint32_t var)
+{
+    return mtbdd_makenode(var, mtbdd_false, mtbdd_true);
+}
+
 /* Operations */
 
 /**
@@ -500,17 +567,6 @@ mtbdd_fraction(int64_t nom, uint64_t denom)
     return mtbdd_makeleaf(2, (nom<<32)|denom);
 }
 
-/**
- * Create the cube of variables in arr.
- */
-MTBDD
-mtbdd_fromarray(uint32_t* arr, size_t length)
-{
-    if (length == 0) return mtbdd_true;
-    else if (length == 1) return mtbdd_makenode(*arr, mtbdd_false, mtbdd_true);
-    else return mtbdd_makenode(*arr, mtbdd_false, mtbdd_fromarray(arr+1, length-1));
-}
-
 /**
  * Create a MTBDD cube representing the conjunction of variables in their positive or negative
  * form depending on whether the cube[idx] equals 0 (negative), 1 (positive) or 2 (any).
@@ -521,9 +577,9 @@ MTBDD
 mtbdd_cube(MTBDD variables, uint8_t *cube, MTBDD terminal)
 {
     if (variables == mtbdd_true) return terminal;
-    mtbddnode_t n = GETNODE(variables);
+    mtbddnode_t n = MTBDD_GETNODE(variables);
 
-    syBDD result;
+    SyBDD result;
     switch (*cube) {
     case 0:
         result = mtbdd_cube(node_gethigh(variables, n), cube+1, terminal);
@@ -538,12 +594,12 @@ mtbdd_cube(MTBDD variables, uint8_t *cube, MTBDD terminal)
     case 3:
     {
         MTBDD variables2 = node_gethigh(variables, n);
-        mtbddnode_t n2 = GETNODE(variables2);
+        mtbddnode_t n2 = MTBDD_GETNODE(variables2);
         uint32_t var2 = mtbddnode_getvariable(n2);
         result = mtbdd_cube(node_gethigh(variables2, n2), cube+2, terminal);
-        syBDD low = mtbdd_makenode(var2, result, mtbdd_false);
+        SyBDD low = mtbdd_makenode(var2, result, mtbdd_false);
         mtbdd_refs_push(low);
-        syBDD high = mtbdd_makenode(var2, mtbdd_false, result);
+        SyBDD high = mtbdd_makenode(var2, mtbdd_false, result);
         mtbdd_refs_pop(1);
         result = mtbdd_makenode(mtbddnode_getvariable(n), low, high);
         return result;
@@ -566,19 +622,19 @@ TASK_IMPL_4(MTBDD, mtbdd_union_cube, MTBDD, mtbdd, MTBDD, vars, uint8_t*, cube,
 
     sylvan_gc_test();
 
-    mtbddnode_t nv = GETNODE(vars);
+    mtbddnode_t nv = MTBDD_GETNODE(vars);
     uint32_t v = mtbddnode_getvariable(nv);
 
-    mtbddnode_t na = GETNODE(mtbdd);
+    mtbddnode_t na = MTBDD_GETNODE(mtbdd);
     uint32_t va = mtbddnode_getvariable(na);
 
     if (va < v) {
         MTBDD low = node_getlow(mtbdd, na);
         MTBDD high = node_gethigh(mtbdd, na);
-        SPAWN(mtbdd_union_cube, high, vars, cube, terminal);
-        syBDD new_low = mtbdd_union_cube(low, vars, cube, terminal);
+        mtbdd_refs_spawn(SPAWN(mtbdd_union_cube, high, vars, cube, terminal));
+        SyBDD new_low = mtbdd_union_cube(low, vars, cube, terminal);
         mtbdd_refs_push(new_low);
-        syBDD new_high = SYNC(mtbdd_union_cube);
+        SyBDD new_high = mtbdd_refs_sync(SYNC(mtbdd_union_cube));
         mtbdd_refs_pop(1);
         if (new_low != low || new_high != high) return mtbdd_makenode(va, new_low, new_high);
         else return mtbdd;
@@ -600,10 +656,10 @@ TASK_IMPL_4(MTBDD, mtbdd_union_cube, MTBDD, mtbdd, MTBDD, vars, uint8_t*, cube,
         }
         case 2:
         {
-            SPAWN(mtbdd_union_cube, high, node_gethigh(vars, nv), cube+1, terminal);
+            mtbdd_refs_spawn(SPAWN(mtbdd_union_cube, high, node_gethigh(vars, nv), cube+1, terminal));
             MTBDD new_low = mtbdd_union_cube(low, node_gethigh(vars, nv), cube+1, terminal);
             mtbdd_refs_push(new_low);
-            MTBDD new_high = SYNC(mtbdd_union_cube);
+            MTBDD new_high = mtbdd_refs_sync(SYNC(mtbdd_union_cube));
             mtbdd_refs_pop(1);
             if (new_low != low || new_high != high) return mtbdd_makenode(v, new_low, new_high);
             return mtbdd;
@@ -629,10 +685,10 @@ TASK_IMPL_4(MTBDD, mtbdd_union_cube, MTBDD, mtbdd, MTBDD, vars, uint8_t*, cube,
         }
         case 2:
         {
-            SPAWN(mtbdd_union_cube, mtbdd, node_gethigh(vars, nv), cube+1, terminal);
+            mtbdd_refs_spawn(SPAWN(mtbdd_union_cube, mtbdd, node_gethigh(vars, nv), cube+1, terminal));
             MTBDD new_low = mtbdd_union_cube(mtbdd, node_gethigh(vars, nv), cube+1, terminal);
             mtbdd_refs_push(new_low);
-            MTBDD new_high = SYNC(mtbdd_union_cube);
+            MTBDD new_high = mtbdd_refs_sync(SYNC(mtbdd_union_cube));
             mtbdd_refs_pop(1);
             return mtbdd_makenode(v, new_low, new_high);
         }
@@ -658,23 +714,30 @@ TASK_IMPL_3(MTBDD, mtbdd_apply, MTBDD, a, MTBDD, b, mtbdd_apply_op, op)
     /* Maybe perform garbage collection */
     sylvan_gc_test();
 
+    /* Count operation */
+    sylvan_stats_count(MTBDD_APPLY);
+
     /* Check cache */
-    if (cache_get3(CACHE_MTBDD_APPLY, a, b, (size_t)op, &result)) return result;
+    if (cache_get3(CACHE_MTBDD_APPLY, a, b, (size_t)op, &result)) {
+        sylvan_stats_count(MTBDD_APPLY_CACHED);
+        return result;
+    }
 
     /* Get top variable */
     int la = mtbdd_isleaf(a);
     int lb = mtbdd_isleaf(b);
+    assert(!la || !lb);
     mtbddnode_t na, nb;
     uint32_t va, vb;
     if (!la) {
-        na = GETNODE(a);
+        na = MTBDD_GETNODE(a);
         va = mtbddnode_getvariable(na);
     } else {
         na = 0;
         va = 0xffffffff;
     }
     if (!lb) {
-        nb = GETNODE(b);
+        nb = MTBDD_GETNODE(b);
         vb = mtbddnode_getvariable(nb);
     } else {
         nb = 0;
@@ -707,7 +770,10 @@ TASK_IMPL_3(MTBDD, mtbdd_apply, MTBDD, a, MTBDD, b, mtbdd_apply_op, op)
     result = mtbdd_makenode(v, low, high);
 
     /* Store in cache */
-    cache_put3(CACHE_MTBDD_APPLY, a, b, (size_t)op, result);
+    if (cache_put3(CACHE_MTBDD_APPLY, a, b, (size_t)op, result)) {
+        sylvan_stats_count(MTBDD_APPLY_CACHEDPUT);
+    }
+
     return result;
 }
 
@@ -723,8 +789,14 @@ TASK_IMPL_5(MTBDD, mtbdd_applyp, MTBDD, a, MTBDD, b, size_t, p, mtbdd_applyp_op,
     /* Maybe perform garbage collection */
     sylvan_gc_test();
 
+    /* Count operation */
+    sylvan_stats_count(MTBDD_APPLY);
+
     /* Check cache */
-    if (cache_get3(opid, a, b, p, &result)) return result;
+    if (cache_get3(opid, a, b, p, &result)) {
+        sylvan_stats_count(MTBDD_APPLY_CACHED);
+        return result;
+    }
 
     /* Get top variable */
     int la = mtbdd_isleaf(a);
@@ -732,14 +804,14 @@ TASK_IMPL_5(MTBDD, mtbdd_applyp, MTBDD, a, MTBDD, b, size_t, p, mtbdd_applyp_op,
     mtbddnode_t na, nb;
     uint32_t va, vb;
     if (!la) {
-        na = GETNODE(a);
+        na = MTBDD_GETNODE(a);
         va = mtbddnode_getvariable(na);
     } else {
         na = 0;
         va = 0xffffffff;
     }
     if (!lb) {
-        nb = GETNODE(b);
+        nb = MTBDD_GETNODE(b);
         vb = mtbddnode_getvariable(nb);
     } else {
         nb = 0;
@@ -772,7 +844,10 @@ TASK_IMPL_5(MTBDD, mtbdd_applyp, MTBDD, a, MTBDD, b, size_t, p, mtbdd_applyp_op,
     result = mtbdd_makenode(v, low, high);
 
     /* Store in cache */
-    cache_put3(opid, a, b, p, result);
+    if (cache_put3(opid, a, b, p, result)) {
+        sylvan_stats_count(MTBDD_APPLY_CACHEDPUT);
+    }
+
     return result;
 }
 
@@ -784,20 +859,29 @@ TASK_IMPL_3(MTBDD, mtbdd_uapply, MTBDD, dd, mtbdd_uapply_op, op, size_t, param)
     /* Maybe perform garbage collection */
     sylvan_gc_test();
 
+    /* Count operation */
+    sylvan_stats_count(MTBDD_UAPPLY);
+
     /* Check cache */
     MTBDD result;
-    if (cache_get3(CACHE_MTBDD_UAPPLY, dd, (size_t)op, param, &result)) return result;
+    if (cache_get3(CACHE_MTBDD_UAPPLY, dd, (size_t)op, param, &result)) {
+        sylvan_stats_count(MTBDD_UAPPLY_CACHED);
+        return result;
+    }
 
     /* Check terminal case */
     result = WRAP(op, dd, param);
     if (result != mtbdd_invalid) {
         /* Store in cache */
-        cache_put3(CACHE_MTBDD_UAPPLY, dd, (size_t)op, param, result);
+        if (cache_put3(CACHE_MTBDD_UAPPLY, dd, (size_t)op, param, result)) {
+            sylvan_stats_count(MTBDD_UAPPLY_CACHEDPUT);
+        }
+
         return result;
     }
 
     /* Get cofactors */
-    mtbddnode_t ndd = GETNODE(dd);
+    mtbddnode_t ndd = MTBDD_GETNODE(dd);
     MTBDD ddlow = node_getlow(dd, ndd);
     MTBDD ddhigh = node_gethigh(dd, ndd);
 
@@ -809,7 +893,10 @@ TASK_IMPL_3(MTBDD, mtbdd_uapply, MTBDD, dd, mtbdd_uapply_op, op, size_t, param)
     result = mtbdd_makenode(mtbddnode_getvariable(ndd), low, high);
 
     /* Store in cache */
-    cache_put3(CACHE_MTBDD_UAPPLY, dd, (size_t)op, param, result);
+    if (cache_put3(CACHE_MTBDD_UAPPLY, dd, (size_t)op, param, result)) {
+        sylvan_stats_count(MTBDD_UAPPLY_CACHEDPUT);
+    }
+
     return result;
 }
 
@@ -819,7 +906,7 @@ TASK_2(MTBDD, mtbdd_uop_times_uint, MTBDD, a, size_t, k)
     if (a == mtbdd_true) return mtbdd_true;
 
     // a != constant
-    mtbddnode_t na = GETNODE(a);
+    mtbddnode_t na = MTBDD_GETNODE(a);
 
     if (mtbddnode_isleaf(na)) {
         if (mtbddnode_gettype(na) == 0) {
@@ -834,6 +921,8 @@ TASK_2(MTBDD, mtbdd_uop_times_uint, MTBDD, a, size_t, k)
             uint32_t d = v;
             uint32_t c = gcd(d, (uint32_t)k);
             return mtbdd_fraction(n*(k/c), d/c);
+        } else {
+            assert(0); // failure
         }
     }
 
@@ -846,7 +935,7 @@ TASK_2(MTBDD, mtbdd_uop_pow_uint, MTBDD, a, size_t, k)
     if (a == mtbdd_true) return mtbdd_true;
 
     // a != constant
-    mtbddnode_t na = GETNODE(a);
+    mtbddnode_t na = MTBDD_GETNODE(a);
 
     if (mtbddnode_isleaf(na)) {
         if (mtbddnode_gettype(na) == 0) {
@@ -858,6 +947,8 @@ TASK_2(MTBDD, mtbdd_uop_pow_uint, MTBDD, a, size_t, k)
         } else if (mtbddnode_gettype(na) == 2) {
             uint64_t v = mtbddnode_getvalue(na);
             return mtbdd_fraction(pow((int32_t)(v>>32), k), (uint32_t)v);
+        } else {
+            assert(0); // failure
         }
     }
 
@@ -907,31 +998,40 @@ TASK_IMPL_3(MTBDD, mtbdd_abstract, MTBDD, a, MTBDD, v, mtbdd_abstract_op, op)
     /* Maybe perform garbage collection */
     sylvan_gc_test();
 
+    /* Count operation */
+    sylvan_stats_count(MTBDD_ABSTRACT);
+
     /* a != constant, v != constant */
-    mtbddnode_t na = GETNODE(a);
+    mtbddnode_t na = MTBDD_GETNODE(a);
 
     if (mtbddnode_isleaf(na)) {
         /* Count number of variables */
         uint64_t k = 0;
         while (v != mtbdd_true) {
             k++;
-            v = node_gethigh(v, GETNODE(v));
+            v = node_gethigh(v, MTBDD_GETNODE(v));
         }
 
         /* Check cache */
         MTBDD result;
-        if (cache_get3(CACHE_MTBDD_ABSTRACT, a, v | (k << 40), (size_t)op, &result)) return result;
+        if (cache_get3(CACHE_MTBDD_ABSTRACT, a, v | (k << 40), (size_t)op, &result)) {
+            sylvan_stats_count(MTBDD_ABSTRACT_CACHED);
+            return result;
+        }
 
         /* Compute result */
         result = WRAP(op, a, a, k);
 
         /* Store in cache */
-        cache_put3(CACHE_MTBDD_ABSTRACT, a, v | (k << 40), (size_t)op, result);
+        if (cache_put3(CACHE_MTBDD_ABSTRACT, a, v | (k << 40), (size_t)op, result)) {
+            sylvan_stats_count(MTBDD_ABSTRACT_CACHEDPUT);
+        }
+
         return result;
     }
 
     /* Possibly skip k variables */
-    mtbddnode_t nv = GETNODE(v);
+    mtbddnode_t nv = MTBDD_GETNODE(v);
     uint32_t var_a = mtbddnode_getvariable(na);
     uint32_t var_v = mtbddnode_getvariable(nv);
     uint64_t k = 0;
@@ -939,13 +1039,16 @@ TASK_IMPL_3(MTBDD, mtbdd_abstract, MTBDD, a, MTBDD, v, mtbdd_abstract_op, op)
         k++;
         v = node_gethigh(v, nv);
         if (v == mtbdd_true) break;
-        nv = GETNODE(v);
+        nv = MTBDD_GETNODE(v);
         var_v = mtbddnode_getvariable(nv);
     }
 
     /* Check cache */
     MTBDD result;
-    if (cache_get3(CACHE_MTBDD_ABSTRACT, a, v | (k << 40), (size_t)op, &result)) return result;
+    if (cache_get3(CACHE_MTBDD_ABSTRACT, a, v | (k << 40), (size_t)op, &result)) {
+        sylvan_stats_count(MTBDD_ABSTRACT_CACHED);
+        return result;
+    }
 
     /* Recursive */
     if (v == mtbdd_true) {
@@ -971,7 +1074,10 @@ TASK_IMPL_3(MTBDD, mtbdd_abstract, MTBDD, a, MTBDD, v, mtbdd_abstract_op, op)
     }
 
     /* Store in cache */
-    cache_put3(CACHE_MTBDD_ABSTRACT, a, v | (k << 40), (size_t)op, result);
+    if (cache_put3(CACHE_MTBDD_ABSTRACT, a, v | (k << 40), (size_t)op, result)) {
+        sylvan_stats_count(MTBDD_ABSTRACT_CACHEDPUT);
+    }
+
     return result;
 }
 
@@ -990,8 +1096,8 @@ TASK_IMPL_2(MTBDD, mtbdd_op_plus, MTBDD*, pa, MTBDD*, pb)
     if (a == mtbdd_true) return mtbdd_true;
     if (b == mtbdd_true) return mtbdd_true;
 
-    mtbddnode_t na = GETNODE(a);
-    mtbddnode_t nb = GETNODE(b);
+    mtbddnode_t na = MTBDD_GETNODE(a);
+    mtbddnode_t nb = MTBDD_GETNODE(b);
 
     if (mtbddnode_isleaf(na) && mtbddnode_isleaf(nb)) {
         uint64_t val_a = mtbddnode_getvalue(na);
@@ -1018,6 +1124,8 @@ TASK_IMPL_2(MTBDD, mtbdd_op_plus, MTBDD*, pa, MTBDD*, pb)
             denom_a *= denom_b/c;
             // add
             return mtbdd_fraction(nom_a + nom_b, denom_a);
+        } else {
+            assert(0); // failure
         }
     }
 
@@ -1040,8 +1148,8 @@ TASK_IMPL_2(MTBDD, mtbdd_op_minus, MTBDD*, pa, MTBDD*, pb)
     if (a == mtbdd_false) return mtbdd_negate(b);
     if (b == mtbdd_false) return a;
 
-    mtbddnode_t na = GETNODE(a);
-    mtbddnode_t nb = GETNODE(b);
+    mtbddnode_t na = MTBDD_GETNODE(a);
+    mtbddnode_t nb = MTBDD_GETNODE(b);
 
     if (mtbddnode_isleaf(na) && mtbddnode_isleaf(nb)) {
         uint64_t val_a = mtbddnode_getvalue(na);
@@ -1067,6 +1175,8 @@ TASK_IMPL_2(MTBDD, mtbdd_op_minus, MTBDD*, pa, MTBDD*, pb)
             denom_a *= denom_b/c;
             // subtract
             return mtbdd_fraction(nom_a - nom_b, denom_a);
+        } else {
+            assert(0); // failure
         }
     }
 
@@ -1088,8 +1198,8 @@ TASK_IMPL_2(MTBDD, mtbdd_op_times, MTBDD*, pa, MTBDD*, pb)
     if (a == mtbdd_true) return b;
     if (b == mtbdd_true) return a;
 
-    mtbddnode_t na = GETNODE(a);
-    mtbddnode_t nb = GETNODE(b);
+    mtbddnode_t na = MTBDD_GETNODE(a);
+    mtbddnode_t nb = MTBDD_GETNODE(b);
 
     if (mtbddnode_isleaf(na) && mtbddnode_isleaf(nb)) {
         uint64_t val_a = mtbddnode_getvalue(na);
@@ -1128,6 +1238,8 @@ TASK_IMPL_2(MTBDD, mtbdd_op_times, MTBDD*, pa, MTBDD*, pb)
             nom_a *= (nom_b/c);
             denom_a *= (denom_b/d);
             return mtbdd_fraction(nom_a, denom_a);
+        } else {
+            assert(0); // failure
         }
     }
 
@@ -1153,11 +1265,11 @@ TASK_IMPL_2(MTBDD, mtbdd_op_min, MTBDD*, pa, MTBDD*, pb)
     if (a == b) return a;
 
     // Special case where "false" indicates a partial function
-    if (a == mtbdd_false && b != mtbdd_false && mtbddnode_isleaf(GETNODE(b))) return b;
-    if (b == mtbdd_false && a != mtbdd_false && mtbddnode_isleaf(GETNODE(a))) return a;
+    if (a == mtbdd_false && b != mtbdd_false && mtbddnode_isleaf(MTBDD_GETNODE(b))) return b;
+    if (b == mtbdd_false && a != mtbdd_false && mtbddnode_isleaf(MTBDD_GETNODE(a))) return a;
 
-    mtbddnode_t na = GETNODE(a);
-    mtbddnode_t nb = GETNODE(b);
+    mtbddnode_t na = MTBDD_GETNODE(a);
+    mtbddnode_t nb = MTBDD_GETNODE(b);
 
     if (mtbddnode_isleaf(na) && mtbddnode_isleaf(nb)) {
         uint64_t val_a = mtbddnode_getvalue(na);
@@ -1184,6 +1296,8 @@ TASK_IMPL_2(MTBDD, mtbdd_op_min, MTBDD*, pa, MTBDD*, pb)
             nom_b *= denom_a/c;
             // compute lowest
             return nom_a < nom_b ? a : b;
+        } else {
+            assert(0); // failure
         }
     }
 
@@ -1210,8 +1324,8 @@ TASK_IMPL_2(MTBDD, mtbdd_op_max, MTBDD*, pa, MTBDD*, pb)
     if (b == mtbdd_false) return a;
     if (a == b) return a;
 
-    mtbddnode_t na = GETNODE(a);
-    mtbddnode_t nb = GETNODE(b);
+    mtbddnode_t na = MTBDD_GETNODE(a);
+    mtbddnode_t nb = MTBDD_GETNODE(b);
 
     if (mtbddnode_isleaf(na) && mtbddnode_isleaf(nb)) {
         uint64_t val_a = mtbddnode_getvalue(na);
@@ -1238,6 +1352,8 @@ TASK_IMPL_2(MTBDD, mtbdd_op_max, MTBDD*, pa, MTBDD*, pb)
             nom_b *= denom_a/c;
             // compute highest
             return nom_a > nom_b ? a : b;
+        } else {
+            assert(0); // failure
         }
     }
 
@@ -1249,13 +1365,43 @@ TASK_IMPL_2(MTBDD, mtbdd_op_max, MTBDD*, pa, MTBDD*, pb)
     return mtbdd_invalid;
 }
 
+TASK_IMPL_2(MTBDD, mtbdd_op_cmpl, MTBDD, a, size_t, k)
+{
+    // if a is false, then it is a partial function. Keep partial!
+    if (a == mtbdd_false) return mtbdd_false;
+
+    // a != constant
+    mtbddnode_t na = MTBDD_GETNODE(a);
+
+    if (mtbddnode_isleaf(na)) {
+        if (mtbddnode_gettype(na) == 0) {
+            int64_t v = mtbdd_getint64(a);
+            if (v == 0) return mtbdd_int64(1);
+            else return mtbdd_int64(0);
+        } else if (mtbddnode_gettype(na) == 1) {
+            double d = mtbdd_getdouble(a);
+            if (d == 0.0) return mtbdd_double(1.0);
+            else return mtbdd_double(0.0);
+        } else if (mtbddnode_gettype(na) == 2) {
+            uint64_t v = mtbddnode_getvalue(na);
+            if (v == 1) return mtbdd_fraction(1, 1);
+            else return mtbdd_fraction(0, 1);
+        } else {
+            assert(0); // failure
+        }
+    }
+
+    return mtbdd_invalid;
+    (void)k; // unused variable
+}
+
 TASK_IMPL_2(MTBDD, mtbdd_op_negate, MTBDD, a, size_t, k)
 {
     // if a is false, then it is a partial function. Keep partial!
     if (a == mtbdd_false) return mtbdd_false;
 
     // a != constant
-    mtbddnode_t na = GETNODE(a);
+    mtbddnode_t na = MTBDD_GETNODE(a);
 
     if (mtbddnode_isleaf(na)) {
         if (mtbddnode_gettype(na) == 0) {
@@ -1267,6 +1413,8 @@ TASK_IMPL_2(MTBDD, mtbdd_op_negate, MTBDD, a, size_t, k)
         } else if (mtbddnode_gettype(na) == 2) {
             uint64_t v = mtbddnode_getvalue(na);
             return mtbdd_fraction(-(int32_t)(v>>32), (uint32_t)v);
+        } else {
+            assert(0); // failure
         }
     }
 
@@ -1276,7 +1424,7 @@ TASK_IMPL_2(MTBDD, mtbdd_op_negate, MTBDD, a, size_t, k)
 
 /**
  * Compute IF <f> THEN <g> ELSE <h>.
- * <f> must be a Boolean MTBDD (or standard BDD).
+ * <f> must be a Boolean MTBDD (or standard SyBDD).
  */
 TASK_IMPL_3(MTBDD, mtbdd_ite, MTBDD, f, MTBDD, g, MTBDD, h)
 {
@@ -1292,16 +1440,22 @@ TASK_IMPL_3(MTBDD, mtbdd_ite, MTBDD, f, MTBDD, g, MTBDD, h)
     /* Maybe perform garbage collection */
     sylvan_gc_test();
 
+    /* Count operation */
+    sylvan_stats_count(MTBDD_ITE);
+
     /* Check cache */
     MTBDD result;
-    if (cache_get3(CACHE_MTBDD_ITE, f, g, h, &result)) return result;
+    if (cache_get3(CACHE_MTBDD_ITE, f, g, h, &result)) {
+        sylvan_stats_count(MTBDD_ITE_CACHED);
+        return result;
+    }
 
     /* Get top variable */
     int lg = mtbdd_isleaf(g);
     int lh = mtbdd_isleaf(h);
-    mtbddnode_t nf = GETNODE(f);
-    mtbddnode_t ng = lg ? 0 : GETNODE(g);
-    mtbddnode_t nh = lh ? 0 : GETNODE(h);
+    mtbddnode_t nf = MTBDD_GETNODE(f);
+    mtbddnode_t ng = lg ? 0 : MTBDD_GETNODE(g);
+    mtbddnode_t nh = lh ? 0 : MTBDD_GETNODE(h);
     uint32_t vf = mtbddnode_getvariable(nf);
     uint32_t vg = lg ? 0 : mtbddnode_getvariable(ng);
     uint32_t vh = lh ? 0 : mtbddnode_getvariable(nh);
@@ -1326,7 +1480,10 @@ TASK_IMPL_3(MTBDD, mtbdd_ite, MTBDD, f, MTBDD, g, MTBDD, h)
     result = mtbdd_makenode(v, low, high);
 
     /* Store in cache */
-    cache_put3(CACHE_MTBDD_ITE, f, g, h, result);
+    if (cache_put3(CACHE_MTBDD_ITE, f, g, h, result)) {
+        sylvan_stats_count(MTBDD_ITE_CACHEDPUT);
+    }
+
     return result;
 }
 
@@ -1340,7 +1497,7 @@ TASK_IMPL_2(MTBDD, mtbdd_op_threshold_double, MTBDD, a, size_t, svalue)
     if (a == mtbdd_true) return mtbdd_invalid;
 
     // a != constant
-    mtbddnode_t na = GETNODE(a);
+    mtbddnode_t na = MTBDD_GETNODE(a);
 
     if (mtbddnode_isleaf(na)) {
         double value = *(double*)&svalue;
@@ -1350,6 +1507,8 @@ TASK_IMPL_2(MTBDD, mtbdd_op_threshold_double, MTBDD, a, size_t, svalue)
             double d = (double)mtbdd_getnumer(a);
             d /= mtbdd_getdenom(a);
             return d >= value ? mtbdd_true : mtbdd_false;
+        } else {
+            assert(0); // failure
         }
     }
 
@@ -1357,7 +1516,7 @@ TASK_IMPL_2(MTBDD, mtbdd_op_threshold_double, MTBDD, a, size_t, svalue)
 }
 
 /**
- * Monad that converts double/fraction to a Boolean BDD, translate terminals > value to 1 and to 0 otherwise;
+ * Monad that converts double/fraction to a Boolean SyBDD, translate terminals > value to 1 and to 0 otherwise;
  */
 TASK_IMPL_2(MTBDD, mtbdd_op_strict_threshold_double, MTBDD, a, size_t, svalue)
 {
@@ -1366,7 +1525,7 @@ TASK_IMPL_2(MTBDD, mtbdd_op_strict_threshold_double, MTBDD, a, size_t, svalue)
     if (a == mtbdd_true) return mtbdd_invalid;
 
     // a != constant
-    mtbddnode_t na = GETNODE(a);
+    mtbddnode_t na = MTBDD_GETNODE(a);
 
     if (mtbddnode_isleaf(na)) {
         double value = *(double*)&svalue;
@@ -1376,6 +1535,8 @@ TASK_IMPL_2(MTBDD, mtbdd_op_strict_threshold_double, MTBDD, a, size_t, svalue)
             double d = (double)mtbdd_getnumer(a);
             d /= mtbdd_getdenom(a);
             return d > value ? mtbdd_true : mtbdd_false;
+        } else {
+            assert(0); // failure
         }
     }
 
@@ -1405,8 +1566,8 @@ TASK_4(MTBDD, mtbdd_equal_norm_d2, MTBDD, a, MTBDD, b, size_t, svalue, int*, sho
     if (a == mtbdd_false) return mtbdd_false;
     if (b == mtbdd_false) return mtbdd_false;
 
-    mtbddnode_t na = GETNODE(a);
-    mtbddnode_t nb = GETNODE(b);
+    mtbddnode_t na = MTBDD_GETNODE(a);
+    mtbddnode_t nb = MTBDD_GETNODE(b);
     int la = mtbddnode_isleaf(na);
     int lb = mtbddnode_isleaf(nb);
 
@@ -1428,9 +1589,15 @@ TASK_4(MTBDD, mtbdd_equal_norm_d2, MTBDD, a, MTBDD, b, size_t, svalue, int*, sho
     /* Maybe perform garbage collection */
     sylvan_gc_test();
 
+    /* Count operation */
+    sylvan_stats_count(MTBDD_EQUAL_NORM);
+
     /* Check cache */
     MTBDD result;
-    if (cache_get3(CACHE_MTBDD_EQUAL_NORM, a, b, svalue, &result)) return result;
+    if (cache_get3(CACHE_MTBDD_EQUAL_NORM, a, b, svalue, &result)) {
+        sylvan_stats_count(MTBDD_EQUAL_NORM_CACHED);
+        return result;
+    }
 
     /* Get top variable */
     uint32_t va = la ? 0xffffffff : mtbddnode_getvariable(na);
@@ -1451,7 +1618,10 @@ TASK_4(MTBDD, mtbdd_equal_norm_d2, MTBDD, a, MTBDD, b, size_t, svalue, int*, sho
     if (result == mtbdd_false) *shortcircuit = 1;
 
     /* Store in cache */
-    cache_put3(CACHE_MTBDD_EQUAL_NORM, a, b, svalue, result);
+    if (cache_put3(CACHE_MTBDD_EQUAL_NORM, a, b, svalue, result)) {
+        sylvan_stats_count(MTBDD_EQUAL_NORM_CACHEDPUT);
+    }
+
     return result;
 }
 
@@ -1477,8 +1647,8 @@ TASK_4(MTBDD, mtbdd_equal_norm_rel_d2, MTBDD, a, MTBDD, b, size_t, svalue, int*,
     if (a == mtbdd_false) return mtbdd_false;
     if (b == mtbdd_false) return mtbdd_false;
 
-    mtbddnode_t na = GETNODE(a);
-    mtbddnode_t nb = GETNODE(b);
+    mtbddnode_t na = MTBDD_GETNODE(a);
+    mtbddnode_t nb = MTBDD_GETNODE(b);
     int la = mtbddnode_isleaf(na);
     int lb = mtbddnode_isleaf(nb);
 
@@ -1495,9 +1665,15 @@ TASK_4(MTBDD, mtbdd_equal_norm_rel_d2, MTBDD, a, MTBDD, b, size_t, svalue, int*,
     /* Maybe perform garbage collection */
     sylvan_gc_test();
 
+    /* Count operation */
+    sylvan_stats_count(MTBDD_EQUAL_NORM_REL);
+
     /* Check cache */
     MTBDD result;
-    if (cache_get3(CACHE_MTBDD_EQUAL_NORM_REL, a, b, svalue, &result)) return result;
+    if (cache_get3(CACHE_MTBDD_EQUAL_NORM_REL, a, b, svalue, &result)) {
+        sylvan_stats_count(MTBDD_EQUAL_NORM_REL_CACHED);
+        return result;
+    }
 
     /* Get top variable */
     uint32_t va = la ? 0xffffffff : mtbddnode_getvariable(na);
@@ -1518,7 +1694,10 @@ TASK_4(MTBDD, mtbdd_equal_norm_rel_d2, MTBDD, a, MTBDD, b, size_t, svalue, int*,
     if (result == mtbdd_false) *shortcircuit = 1;
 
     /* Store in cache */
-    cache_put3(CACHE_MTBDD_EQUAL_NORM_REL, a, b, svalue, result);
+    if (cache_put3(CACHE_MTBDD_EQUAL_NORM_REL, a, b, svalue, result)) {
+        sylvan_stats_count(MTBDD_EQUAL_NORM_REL_CACHEDPUT);
+    }
+
     return result;
 }
 
@@ -1549,12 +1728,18 @@ TASK_3(MTBDD, mtbdd_leq_rec, MTBDD, a, MTBDD, b, int*, shortcircuit)
     /* Maybe perform garbage collection */
     sylvan_gc_test();
 
+    /* Count operation */
+    sylvan_stats_count(MTBDD_LEQ);
+
     /* Check cache */
     MTBDD result;
-    if (cache_get3(CACHE_MTBDD_LEQ, a, b, 0, &result)) return result;
+    if (cache_get3(CACHE_MTBDD_LEQ, a, b, 0, &result)) {
+        sylvan_stats_count(MTBDD_LEQ_CACHED);
+        return result;
+    }
 
-    mtbddnode_t na = GETNODE(a);
-    mtbddnode_t nb = GETNODE(b);
+    mtbddnode_t na = MTBDD_GETNODE(a);
+    mtbddnode_t nb = MTBDD_GETNODE(b);
     int la = mtbddnode_isleaf(na);
     int lb = mtbddnode_isleaf(nb);
 
@@ -1581,6 +1766,8 @@ TASK_3(MTBDD, mtbdd_leq_rec, MTBDD, a, MTBDD, b, int*, shortcircuit)
             nom_a *= db/c;
             nom_b *= da/c;
             result = nom_a <= nom_b ? mtbdd_true : mtbdd_false;
+        } else {
+            assert(0); // failure
         }
     } else {
         /* Get top variable */
@@ -1603,7 +1790,10 @@ TASK_3(MTBDD, mtbdd_leq_rec, MTBDD, a, MTBDD, b, int*, shortcircuit)
     if (result == mtbdd_false) *shortcircuit = 1;
 
     /* Store in cache */
-    cache_put3(CACHE_MTBDD_LEQ, a, b, 0, result);
+    if (cache_put3(CACHE_MTBDD_LEQ, a, b, 0, result)) {
+        sylvan_stats_count(MTBDD_LEQ_CACHEDPUT);
+    }
+
     return result;
 }
 
@@ -1634,12 +1824,18 @@ TASK_3(MTBDD, mtbdd_less_rec, MTBDD, a, MTBDD, b, int*, shortcircuit)
     /* Maybe perform garbage collection */
     sylvan_gc_test();
 
+    /* Count operation */
+    sylvan_stats_count(MTBDD_LESS);
+
     /* Check cache */
     MTBDD result;
-    if (cache_get3(CACHE_MTBDD_LESS, a, b, 0, &result)) return result;
+    if (cache_get3(CACHE_MTBDD_LESS, a, b, 0, &result)) {
+        sylvan_stats_count(MTBDD_LESS_CACHED);
+        return result;
+    }
 
-    mtbddnode_t na = GETNODE(a);
-    mtbddnode_t nb = GETNODE(b);
+    mtbddnode_t na = MTBDD_GETNODE(a);
+    mtbddnode_t nb = MTBDD_GETNODE(b);
     int la = mtbddnode_isleaf(na);
     int lb = mtbddnode_isleaf(nb);
 
@@ -1666,6 +1862,8 @@ TASK_3(MTBDD, mtbdd_less_rec, MTBDD, a, MTBDD, b, int*, shortcircuit)
             nom_a *= db/c;
             nom_b *= da/c;
             result = nom_a < nom_b ? mtbdd_true : mtbdd_false;
+        } else {
+            assert(0); // failure
         }
     } else {
         /* Get top variable */
@@ -1688,7 +1886,10 @@ TASK_3(MTBDD, mtbdd_less_rec, MTBDD, a, MTBDD, b, int*, shortcircuit)
     if (result == mtbdd_false) *shortcircuit = 1;
 
     /* Store in cache */
-    cache_put3(CACHE_MTBDD_LESS, a, b, 0, result);
+    if (cache_put3(CACHE_MTBDD_LESS, a, b, 0, result)) {
+        sylvan_stats_count(MTBDD_LESS_CACHEDPUT);
+    }
+
     return result;
 }
 
@@ -1719,12 +1920,18 @@ TASK_3(MTBDD, mtbdd_geq_rec, MTBDD, a, MTBDD, b, int*, shortcircuit)
     /* Maybe perform garbage collection */
     sylvan_gc_test();
 
+    /* Count operation */
+    sylvan_stats_count(MTBDD_GEQ);
+
     /* Check cache */
     MTBDD result;
-    if (cache_get3(CACHE_MTBDD_GEQ, a, b, 0, &result)) return result;
+    if (cache_get3(CACHE_MTBDD_GEQ, a, b, 0, &result)) {
+        sylvan_stats_count(MTBDD_GEQ_CACHED);
+        return result;
+    }
 
-    mtbddnode_t na = GETNODE(a);
-    mtbddnode_t nb = GETNODE(b);
+    mtbddnode_t na = MTBDD_GETNODE(a);
+    mtbddnode_t nb = MTBDD_GETNODE(b);
     int la = mtbddnode_isleaf(na);
     int lb = mtbddnode_isleaf(nb);
 
@@ -1751,6 +1958,8 @@ TASK_3(MTBDD, mtbdd_geq_rec, MTBDD, a, MTBDD, b, int*, shortcircuit)
             nom_a *= db/c;
             nom_b *= da/c;
             result = nom_a >= nom_b ? mtbdd_true : mtbdd_false;
+        } else {
+            assert(0); // failure
         }
     } else {
         /* Get top variable */
@@ -1773,7 +1982,10 @@ TASK_3(MTBDD, mtbdd_geq_rec, MTBDD, a, MTBDD, b, int*, shortcircuit)
     if (result == mtbdd_false) *shortcircuit = 1;
 
     /* Store in cache */
-    cache_put3(CACHE_MTBDD_GEQ, a, b, 0, result);
+    if (cache_put3(CACHE_MTBDD_GEQ, a, b, 0, result)) {
+        sylvan_stats_count(MTBDD_GEQ_CACHEDPUT);
+    }
+
     return result;
 }
 
@@ -1804,12 +2016,18 @@ TASK_3(MTBDD, mtbdd_greater_rec, MTBDD, a, MTBDD, b, int*, shortcircuit)
     /* Maybe perform garbage collection */
     sylvan_gc_test();
 
+    /* Count operation */
+    sylvan_stats_count(MTBDD_GREATER);
+
     /* Check cache */
     MTBDD result;
-    if (cache_get3(CACHE_MTBDD_GREATER, a, b, 0, &result)) return result;
+    if (cache_get3(CACHE_MTBDD_GREATER, a, b, 0, &result)) {
+        sylvan_stats_count(MTBDD_GREATER_CACHED);
+        return result;
+    }
 
-    mtbddnode_t na = GETNODE(a);
-    mtbddnode_t nb = GETNODE(b);
+    mtbddnode_t na = MTBDD_GETNODE(a);
+    mtbddnode_t nb = MTBDD_GETNODE(b);
     int la = mtbddnode_isleaf(na);
     int lb = mtbddnode_isleaf(nb);
 
@@ -1836,6 +2054,8 @@ TASK_3(MTBDD, mtbdd_greater_rec, MTBDD, a, MTBDD, b, int*, shortcircuit)
             nom_a *= db/c;
             nom_b *= da/c;
             result = nom_a > nom_b ? mtbdd_true : mtbdd_false;
+        } else {
+            assert(0); // failure
         }
     } else {
         /* Get top variable */
@@ -1858,7 +2078,10 @@ TASK_3(MTBDD, mtbdd_greater_rec, MTBDD, a, MTBDD, b, int*, shortcircuit)
     if (result == mtbdd_false) *shortcircuit = 1;
 
     /* Store in cache */
-    cache_put3(CACHE_MTBDD_GREATER, a, b, 0, result);
+    if (cache_put3(CACHE_MTBDD_GREATER, a, b, 0, result)) {
+        sylvan_stats_count(MTBDD_GREATER_CACHEDPUT);
+    }
+
     return result;
 }
 
@@ -1874,7 +2097,7 @@ TASK_IMPL_2(MTBDD, mtbdd_greater, MTBDD, a, MTBDD, b)
  * Multiply <a> and <b>, and abstract variables <vars> using summation.
  * This is similar to the "and_exists" operation in BDDs.
  */
-TASK_IMPL_3(MTBDD, mtbdd_and_exists, MTBDD, a, MTBDD, b, MTBDD, v)
+TASK_IMPL_3(MTBDD, mtbdd_and_abstract_plus, MTBDD, a, MTBDD, b, MTBDD, v)
 {
     /* Check terminal case */
     if (v == mtbdd_true) return mtbdd_apply(a, b, TASK(mtbdd_op_times));
@@ -1889,26 +2112,32 @@ TASK_IMPL_3(MTBDD, mtbdd_and_exists, MTBDD, a, MTBDD, b, MTBDD, v)
     /* Maybe perform garbage collection */
     sylvan_gc_test();
 
+    /* Count operation */
+    sylvan_stats_count(MTBDD_AND_ABSTRACT_PLUS);
+
     /* Check cache */
-    if (cache_get3(CACHE_MTBDD_AND_EXISTS, a, b, v, &result)) return result;
+    if (cache_get3(CACHE_MTBDD_AND_ABSTRACT_PLUS, a, b, v, &result)) {
+        sylvan_stats_count(MTBDD_AND_ABSTRACT_PLUS_CACHED);
+        return result;
+    }
 
     /* Now, v is not a constant, and either a or b is not a constant */
 
     /* Get top variable */
     int la = mtbdd_isleaf(a);
     int lb = mtbdd_isleaf(b);
-    mtbddnode_t na = la ? 0 : GETNODE(a);
-    mtbddnode_t nb = lb ? 0 : GETNODE(b);
+    mtbddnode_t na = la ? 0 : MTBDD_GETNODE(a);
+    mtbddnode_t nb = lb ? 0 : MTBDD_GETNODE(b);
     uint32_t va = la ? 0xffffffff : mtbddnode_getvariable(na);
     uint32_t vb = lb ? 0xffffffff : mtbddnode_getvariable(nb);
     uint32_t var = va < vb ? va : vb;
 
-    mtbddnode_t nv = GETNODE(v);
+    mtbddnode_t nv = MTBDD_GETNODE(v);
     uint32_t vv = mtbddnode_getvariable(nv);
 
     if (vv < var) {
         /* Recursive, then abstract result */
-        result = CALL(mtbdd_and_exists, a, b, node_gethigh(v, nv));
+        result = CALL(mtbdd_and_abstract_plus, a, b, node_gethigh(v, nv));
         mtbdd_refs_push(result);
         result = mtbdd_apply(result, result, TASK(mtbdd_op_plus));
         mtbdd_refs_pop(1);
@@ -1922,23 +2151,106 @@ TASK_IMPL_3(MTBDD, mtbdd_and_exists, MTBDD, a, MTBDD, b, MTBDD, v)
 
         if (vv == var) {
             /* Recursive, then abstract result */
-            mtbdd_refs_spawn(SPAWN(mtbdd_and_exists, ahigh, bhigh, node_gethigh(v, nv)));
-            MTBDD low = mtbdd_refs_push(CALL(mtbdd_and_exists, alow, blow, node_gethigh(v, nv)));
-            MTBDD high = mtbdd_refs_push(mtbdd_refs_sync(SYNC(mtbdd_and_exists)));
+            mtbdd_refs_spawn(SPAWN(mtbdd_and_abstract_plus, ahigh, bhigh, node_gethigh(v, nv)));
+            MTBDD low = mtbdd_refs_push(CALL(mtbdd_and_abstract_plus, alow, blow, node_gethigh(v, nv)));
+            MTBDD high = mtbdd_refs_push(mtbdd_refs_sync(SYNC(mtbdd_and_abstract_plus)));
             result = CALL(mtbdd_apply, low, high, TASK(mtbdd_op_plus));
             mtbdd_refs_pop(2);
         } else /* vv > v */ {
             /* Recursive, then create node */
-            mtbdd_refs_spawn(SPAWN(mtbdd_and_exists, ahigh, bhigh, v));
-            MTBDD low = mtbdd_refs_push(CALL(mtbdd_and_exists, alow, blow, v));
-            MTBDD high = mtbdd_refs_sync(SYNC(mtbdd_and_exists));
+            mtbdd_refs_spawn(SPAWN(mtbdd_and_abstract_plus, ahigh, bhigh, v));
+            MTBDD low = mtbdd_refs_push(CALL(mtbdd_and_abstract_plus, alow, blow, v));
+            MTBDD high = mtbdd_refs_sync(SYNC(mtbdd_and_abstract_plus));
             mtbdd_refs_pop(1);
             result = mtbdd_makenode(var, low, high);
         }
     }
 
     /* Store in cache */
-    cache_put3(CACHE_MTBDD_AND_EXISTS, a, b, v, result);
+    if (cache_put3(CACHE_MTBDD_AND_ABSTRACT_PLUS, a, b, v, result)) {
+        sylvan_stats_count(MTBDD_AND_ABSTRACT_PLUS_CACHEDPUT);
+    }
+
+    return result;
+}
+
+/**
+ * Multiply <a> and <b>, and abstract variables <vars> by taking the maximum.
+ */
+TASK_IMPL_3(MTBDD, mtbdd_and_abstract_max, MTBDD, a, MTBDD, b, MTBDD, v)
+{
+    /* Check terminal case */
+    if (v == mtbdd_true) return mtbdd_apply(a, b, TASK(mtbdd_op_times));
+    MTBDD result = CALL(mtbdd_op_times, &a, &b);
+    if (result != mtbdd_invalid) {
+        mtbdd_refs_push(result);
+        result = mtbdd_abstract(result, v, TASK(mtbdd_abstract_op_max));
+        mtbdd_refs_pop(1);
+        return result;
+    }
+
+    /* Now, v is not a constant, and either a or b is not a constant */
+
+    /* Get top variable */
+    int la = mtbdd_isleaf(a);
+    int lb = mtbdd_isleaf(b);
+    mtbddnode_t na = la ? 0 : MTBDD_GETNODE(a);
+    mtbddnode_t nb = lb ? 0 : MTBDD_GETNODE(b);
+    uint32_t va = la ? 0xffffffff : mtbddnode_getvariable(na);
+    uint32_t vb = lb ? 0xffffffff : mtbddnode_getvariable(nb);
+    uint32_t var = va < vb ? va : vb;
+
+    mtbddnode_t nv = MTBDD_GETNODE(v);
+    uint32_t vv = mtbddnode_getvariable(nv);
+
+    while (vv < var) {
+        /* we can skip variables, because max(r,r) = r */
+        v = node_gethigh(v, nv);
+        if (v == mtbdd_true) return mtbdd_apply(a, b, TASK(mtbdd_op_times));
+        nv = MTBDD_GETNODE(v);
+        vv = mtbddnode_getvariable(nv);
+    }
+
+    /* Maybe perform garbage collection */
+    sylvan_gc_test();
+
+    /* Count operation */
+    sylvan_stats_count(MTBDD_AND_ABSTRACT_MAX);
+
+    /* Check cache */
+    if (cache_get3(CACHE_MTBDD_AND_ABSTRACT_MAX, a, b, v, &result)) {
+        sylvan_stats_count(MTBDD_AND_ABSTRACT_MAX_CACHED);
+        return result;
+    }
+
+    /* Get cofactors */
+    MTBDD alow, ahigh, blow, bhigh;
+    alow  = (!la && va == var) ? node_getlow(a, na)  : a;
+    ahigh = (!la && va == var) ? node_gethigh(a, na) : a;
+    blow  = (!lb && vb == var) ? node_getlow(b, nb)  : b;
+    bhigh = (!lb && vb == var) ? node_gethigh(b, nb) : b;
+
+    if (vv == var) {
+        /* Recursive, then abstract result */
+        mtbdd_refs_spawn(SPAWN(mtbdd_and_abstract_max, ahigh, bhigh, node_gethigh(v, nv)));
+        MTBDD low = mtbdd_refs_push(CALL(mtbdd_and_abstract_max, alow, blow, node_gethigh(v, nv)));
+        MTBDD high = mtbdd_refs_push(mtbdd_refs_sync(SYNC(mtbdd_and_abstract_max)));
+        result = CALL(mtbdd_apply, low, high, TASK(mtbdd_op_max));
+        mtbdd_refs_pop(2);
+    } else /* vv > v */ {
+        /* Recursive, then create node */
+        mtbdd_refs_spawn(SPAWN(mtbdd_and_abstract_max, ahigh, bhigh, v));
+        MTBDD low = mtbdd_refs_push(CALL(mtbdd_and_abstract_max, alow, blow, v));
+        MTBDD high = mtbdd_refs_sync(SYNC(mtbdd_and_abstract_max));
+        mtbdd_refs_pop(1);
+        result = mtbdd_makenode(var, low, high);
+    }
+
+    /* Store in cache */
+    if (cache_put3(CACHE_MTBDD_AND_ABSTRACT_MAX, a, b, v, result)) {
+        sylvan_stats_count(MTBDD_AND_ABSTRACT_MAX_CACHEDPUT);
+    }
+
     return result;
 }
 
@@ -1953,22 +2265,31 @@ TASK_IMPL_1(MTBDD, mtbdd_support, MTBDD, dd)
     /* Maybe perform garbage collection */
     sylvan_gc_test();
 
+    /* Count operation */
+    sylvan_stats_count(BDD_SUPPORT);
+
     /* Check cache */
     MTBDD result;
-    if (cache_get3(CACHE_MTBDD_SUPPORT, dd, 0, 0, &result)) return result;
+    if (cache_get3(CACHE_MTBDD_SUPPORT, dd, 0, 0, &result)) {
+        sylvan_stats_count(BDD_SUPPORT_CACHED);
+        return result;
+    }
 
     /* Recursive calls */
-    mtbddnode_t n = GETNODE(dd);
+    mtbddnode_t n = MTBDD_GETNODE(dd);
     mtbdd_refs_spawn(SPAWN(mtbdd_support, node_getlow(dd, n)));
     MTBDD high = mtbdd_refs_push(CALL(mtbdd_support, node_gethigh(dd, n)));
     MTBDD low = mtbdd_refs_push(mtbdd_refs_sync(SYNC(mtbdd_support)));
 
     /* Compute result */
-    result = mtbdd_makenode(mtbddnode_getvariable(n), mtbdd_false, mtbdd_times(low, high));
+    result = mtbdd_makenode(mtbddnode_getvariable(n), mtbdd_false, sylvan_and(low, high));
     mtbdd_refs_pop(2);
 
     /* Write to cache */
-    cache_put3(CACHE_MTBDD_SUPPORT, dd, 0, 0, result);
+    if (cache_put3(CACHE_MTBDD_SUPPORT, dd, 0, 0, result)) {
+        sylvan_stats_count(BDD_SUPPORT_CACHEDPUT);
+    }
+
     return result;
 }
 
@@ -1983,7 +2304,7 @@ TASK_IMPL_2(MTBDD, mtbdd_compose, MTBDD, a, MTBDDMAP, map)
     if (mtbdd_isleaf(a) || mtbdd_map_isempty(map)) return a;
 
     /* Determine top level */
-    mtbddnode_t n = GETNODE(a);
+    mtbddnode_t n = MTBDD_GETNODE(a);
     uint32_t v = mtbddnode_getvariable(n);
 
     /* Find in map */
@@ -1995,9 +2316,15 @@ TASK_IMPL_2(MTBDD, mtbdd_compose, MTBDD, a, MTBDDMAP, map)
     /* Perhaps execute garbage collection */
     sylvan_gc_test();
 
+    /* Count operation */
+    sylvan_stats_count(MTBDD_COMPOSE);
+
     /* Check cache */
     MTBDD result;
-    if (cache_get3(CACHE_MTBDD_COMPOSE, a, map, 0, &result)) return result;
+    if (cache_get3(CACHE_MTBDD_COMPOSE, a, map, 0, &result)) {
+        sylvan_stats_count(MTBDD_COMPOSE_CACHED);
+        return result;
+    }
 
     /* Recursive calls */
     mtbdd_refs_spawn(SPAWN(mtbdd_compose, node_getlow(a, n), map));
@@ -2011,7 +2338,10 @@ TASK_IMPL_2(MTBDD, mtbdd_compose, MTBDD, a, MTBDDMAP, map)
     mtbdd_refs_pop(3);
 
     /* Store in cache */
-    cache_put3(CACHE_MTBDD_COMPOSE, a, map, 0, result);
+    if (cache_put3(CACHE_MTBDD_COMPOSE, a, map, 0, result)) {
+        sylvan_stats_count(MTBDD_COMPOSE_CACHEDPUT);
+    }
+
     return result;
 }
 
@@ -2022,15 +2352,21 @@ TASK_IMPL_1(MTBDD, mtbdd_minimum, MTBDD, a)
 {
     /* Check terminal case */
     if (a == mtbdd_false) return mtbdd_false;
-    mtbddnode_t na = GETNODE(a);
+    mtbddnode_t na = MTBDD_GETNODE(a);
     if (mtbddnode_isleaf(na)) return a;
 
     /* Maybe perform garbage collection */
     sylvan_gc_test();
 
+    /* Count operation */
+    sylvan_stats_count(MTBDD_MINIMUM);
+
     /* Check cache */
     MTBDD result;
-    if (cache_get3(CACHE_MTBDD_MINIMUM, a, 0, 0, &result)) return result;
+    if (cache_get3(CACHE_MTBDD_MINIMUM, a, 0, 0, &result)) {
+        sylvan_stats_count(MTBDD_MINIMUM_CACHED);
+        return result;
+    }
 
     /* Call recursive */
     SPAWN(mtbdd_minimum, node_getlow(a, na));
@@ -2038,8 +2374,8 @@ TASK_IMPL_1(MTBDD, mtbdd_minimum, MTBDD, a)
     MTBDD low = SYNC(mtbdd_minimum);
 
     /* Determine lowest */
-    mtbddnode_t nl = GETNODE(low);
-    mtbddnode_t nh = GETNODE(high);
+    mtbddnode_t nl = MTBDD_GETNODE(low);
+    mtbddnode_t nh = MTBDD_GETNODE(high);
 
     if (mtbddnode_gettype(nl) == 0 && mtbddnode_gettype(nh) == 0) {
         result = mtbdd_getint64(low) < mtbdd_getint64(high) ? low : high;
@@ -2056,11 +2392,16 @@ TASK_IMPL_1(MTBDD, mtbdd_minimum, MTBDD, a)
         nom_l *= denom_h/c;
         nom_h *= denom_l/c;
         result = nom_l < nom_h ? low : high;
+    } else {
+        assert(0); // failure
     }
 
     /* Store in cache */
-    cache_put3(CACHE_MTBDD_MINIMUM, a, 0, 0, result);
-    return result;
+    if (cache_put3(CACHE_MTBDD_MINIMUM, a, 0, 0, result)) {
+        sylvan_stats_count(MTBDD_MINIMUM_CACHEDPUT);
+    }
+
+    return result;
 }
 
 /**
@@ -2070,15 +2411,21 @@ TASK_IMPL_1(MTBDD, mtbdd_maximum, MTBDD, a)
 {
     /* Check terminal case */
     if (a == mtbdd_false) return mtbdd_false;
-    mtbddnode_t na = GETNODE(a);
+    mtbddnode_t na = MTBDD_GETNODE(a);
     if (mtbddnode_isleaf(na)) return a;
 
     /* Maybe perform garbage collection */
     sylvan_gc_test();
 
+    /* Count operation */
+    sylvan_stats_count(MTBDD_MAXIMUM);
+
     /* Check cache */
     MTBDD result;
-    if (cache_get3(CACHE_MTBDD_MAXIMUM, a, 0, 0, &result)) return result;
+    if (cache_get3(CACHE_MTBDD_MAXIMUM, a, 0, 0, &result)) {
+        sylvan_stats_count(MTBDD_MAXIMUM_CACHED);
+        return result;
+    }
 
     /* Call recursive */
     SPAWN(mtbdd_maximum, node_getlow(a, na));
@@ -2086,8 +2433,8 @@ TASK_IMPL_1(MTBDD, mtbdd_maximum, MTBDD, a)
     MTBDD low = SYNC(mtbdd_maximum);
 
     /* Determine highest */
-    mtbddnode_t nl = GETNODE(low);
-    mtbddnode_t nh = GETNODE(high);
+    mtbddnode_t nl = MTBDD_GETNODE(low);
+    mtbddnode_t nh = MTBDD_GETNODE(high);
 
     if (mtbddnode_gettype(nl) == 0 && mtbddnode_gettype(nh) == 0) {
         result = mtbdd_getint64(low) > mtbdd_getint64(high) ? low : high;
@@ -2104,10 +2451,15 @@ TASK_IMPL_1(MTBDD, mtbdd_maximum, MTBDD, a)
         nom_l *= denom_h/c;
         nom_h *= denom_l/c;
         result = nom_l > nom_h ? low : high;
+    } else {
+        assert(0); // failure
     }
 
     /* Store in cache */
-    cache_put3(CACHE_MTBDD_MAXIMUM, a, 0, 0, result);
+    if (cache_put3(CACHE_MTBDD_MAXIMUM, a, 0, 0, result)) {
+        sylvan_stats_count(MTBDD_MAXIMUM_CACHEDPUT);
+    }
+
     return result;
 }
 
@@ -2118,7 +2470,17 @@ TASK_IMPL_2(double, mtbdd_satcount, MTBDD, dd, size_t, nvars)
 {
     /* Trivial cases */
     if (dd == mtbdd_false) return 0.0;
-    if (mtbdd_isleaf(dd)) return powl(2.0L, nvars);
+
+    if (mtbdd_isleaf(dd)) {
+        // test if 0
+        mtbddnode_t dd_node = MTBDD_GETNODE(dd);
+        if (dd != mtbdd_true) {
+            if (mtbddnode_gettype(dd_node) == 0 && mtbdd_getint64(dd) == 0) return 0.0;
+            else if (mtbddnode_gettype(dd_node) == 1 && mtbdd_getdouble(dd) == 0.0) return 0.0;
+            else if (mtbddnode_gettype(dd_node) == 2 && mtbdd_getvalue(dd) == 1) return 0.0;
+        }
+        return powl(2.0L, nvars);
+    }
 
     /* Perhaps execute garbage collection */
     sylvan_gc_test();
@@ -2138,7 +2500,10 @@ TASK_IMPL_2(double, mtbdd_satcount, MTBDD, dd, size_t, nvars)
     double low = CALL(mtbdd_satcount, mtbdd_getlow(dd), nvars-1);
     hack.d = low + SYNC(mtbdd_satcount);
 
-    cache_put3(CACHE_BDD_SATCOUNT, dd, 0, nvars, hack.s);
+    if (cache_put3(CACHE_BDD_SATCOUNT, dd, 0, nvars, hack.s)) {
+        sylvan_stats_count(BDD_SATCOUNT_CACHEDPUT);
+    }
+
     return hack.d;
 }
 
@@ -2167,7 +2532,7 @@ mtbdd_enum_first(MTBDD dd, MTBDD variables, uint8_t *arr, mtbdd_enum_filter_cb f
         variables = mtbdd_gethigh(variables);
 
         // check if MTBDD is on this variable
-        mtbddnode_t n = GETNODE(dd);
+        mtbddnode_t n = MTBDD_GETNODE(dd);
         if (mtbddnode_getvariable(n) != v) {
             *arr = 2;
             return mtbdd_enum_first(dd, variables, arr+1, filter_cb);
@@ -2207,7 +2572,7 @@ mtbdd_enum_next(MTBDD dd, MTBDD variables, uint8_t *arr, mtbdd_enum_filter_cb fi
 
         if (*arr == 0) {
             // previous was low
-            mtbddnode_t n = GETNODE(dd);
+            mtbddnode_t n = MTBDD_GETNODE(dd);
             MTBDD res = mtbdd_enum_next(node_getlow(dd, n), variables, arr+1, filter_cb);
             if (res != mtbdd_false) {
                 return res;
@@ -2223,7 +2588,7 @@ mtbdd_enum_next(MTBDD dd, MTBDD variables, uint8_t *arr, mtbdd_enum_filter_cb fi
             }
         } else if (*arr == 1) {
             // previous was high
-            mtbddnode_t n = GETNODE(dd);
+            mtbddnode_t n = MTBDD_GETNODE(dd);
             return mtbdd_enum_next(node_gethigh(dd, n), variables, arr+1, filter_cb);
         } else {
             // previous was either
@@ -2232,13 +2597,229 @@ mtbdd_enum_next(MTBDD dd, MTBDD variables, uint8_t *arr, mtbdd_enum_filter_cb fi
     }
 }
 
+MTBDD
+mtbdd_enum_all_first(MTBDD dd, MTBDD variables, uint8_t *arr, mtbdd_enum_filter_cb filter_cb)
+{
+    if (dd == mtbdd_false) {
+        // the leaf False is skipped
+        return mtbdd_false;
+    } else if (variables == mtbdd_true) {
+        // if this assertion fails, then <variables> is not the support of <dd>.
+        assert(mtbdd_isleaf(dd));
+        // for _first, just return the leaf; there is nothing to set, though.
+        return dd;
+    } else if (mtbdd_isleaf(dd)) {
+        // a leaf for which the filter returns 0 is skipped
+        if (filter_cb != NULL && filter_cb(dd) == 0) return mtbdd_false;
+        // for all remaining variables, set to 0
+        while (variables != mtbdd_true) {
+            *arr++ = 0;
+            variables = mtbdd_gethigh(variables);
+        }
+        return dd;
+    } else {
+        // get next variable from <variables>
+        mtbddnode_t nv = MTBDD_GETNODE(variables);
+        variables = node_gethigh(variables, nv);
+
+        // get cofactors
+        mtbddnode_t ndd = MTBDD_GETNODE(dd);
+        MTBDD low, high;
+        if (mtbddnode_getvariable(ndd) == mtbddnode_getvariable(nv)) {
+            low = node_getlow(dd, ndd);
+            high = node_gethigh(dd, ndd);
+        } else {
+            low = high = dd;
+        }
+
+        // first maybe follow low
+        MTBDD res = mtbdd_enum_all_first(low, variables, arr+1, filter_cb);
+        if (res != mtbdd_false) {
+            *arr = 0;
+            return res;
+        }
+
+        // if not low, try following high
+        res = mtbdd_enum_all_first(high, variables, arr+1, filter_cb);
+        if (res != mtbdd_false) {
+            *arr = 1;
+            return res;
+        }
+
+        // we've tried low and high, return false
+        return mtbdd_false;
+    }
+}
+
+MTBDD
+mtbdd_enum_all_next(MTBDD dd, MTBDD variables, uint8_t *arr, mtbdd_enum_filter_cb filter_cb)
+{
+    if (dd == mtbdd_false) {
+        // the leaf False is skipped
+        return mtbdd_false;
+    } else if (variables == mtbdd_true) {
+        // if this assertion fails, then <variables> is not the support of <dd>.
+        assert(mtbdd_isleaf(dd));
+        // no next if there are no variables
+        return mtbdd_false;
+    } else {
+        // get next variable from <variables>
+        mtbddnode_t nv = MTBDD_GETNODE(variables);
+        variables = node_gethigh(variables, nv);
+
+        // filter leaf (if leaf) or get cofactors (if not leaf)
+        mtbddnode_t ndd = MTBDD_GETNODE(dd);
+        MTBDD low, high;
+        if (mtbdd_isleaf(dd)) {
+            // a leaf for which the filter returns 0 is skipped
+            if (filter_cb != NULL && filter_cb(dd) == 0) return mtbdd_false;
+            low = high = dd;
+        } else {
+            // get cofactors
+            if (mtbddnode_getvariable(ndd) == mtbddnode_getvariable(nv)) {
+                low = node_getlow(dd, ndd);
+                high = node_gethigh(dd, ndd);
+            } else {
+                low = high = dd;
+            }
+        }
+
+        // try recursive next first
+        if (*arr == 0) {
+            MTBDD res = mtbdd_enum_all_next(low, variables, arr+1, filter_cb);
+            if (res != mtbdd_false) return res;
+        } else if (*arr == 1) {
+            return mtbdd_enum_all_next(high, variables, arr+1, filter_cb);
+            // if *arr was 1 and _next returns False, return False
+        } else {
+            // the array is invalid...
+            assert(*arr == 0 || *arr == 1);
+            return mtbdd_invalid;  // in Release mode, the assertion is empty code
+        }
+
+        // previous was low, try following high
+        MTBDD res = mtbdd_enum_all_first(high, variables, arr+1, filter_cb);
+        if (res == mtbdd_false) return mtbdd_false;
+
+        // succesful, set arr
+        *arr = 1;
+        return res;
+    }
+}
+
+/**
+ * Given a MTBDD <dd>, call <cb> with context <context> for every unique path in <dd> ending in leaf <leaf>.
+ *
+ * Usage:
+ * VOID_TASK_3(cb, mtbdd_enum_trace_t, trace, MTBDD, leaf, void*, context) { ... do something ... }
+ * mtbdd_enum_par(dd, cb, context);
+ */
+VOID_TASK_4(mtbdd_enum_par_do, MTBDD, dd, mtbdd_enum_cb, cb, void*, context, mtbdd_enum_trace_t, trace)
+{
+    if (mtbdd_isleaf(dd)) {
+        WRAP(cb, trace, dd, context);
+        return;
+    }
+
+    mtbddnode_t ndd = MTBDD_GETNODE(dd);
+    uint32_t var = mtbddnode_getvariable(ndd);
+
+    struct mtbdd_enum_trace t0 = (struct mtbdd_enum_trace){trace, var, 0};
+    struct mtbdd_enum_trace t1 = (struct mtbdd_enum_trace){trace, var, 1};
+    SPAWN(mtbdd_enum_par_do, node_getlow(dd, ndd), cb, context, &t0);
+    CALL(mtbdd_enum_par_do, node_gethigh(dd, ndd), cb, context, &t1);
+    SYNC(mtbdd_enum_par_do);
+}
+
+VOID_TASK_IMPL_3(mtbdd_enum_par, MTBDD, dd, mtbdd_enum_cb, cb, void*, context)
+{
+    CALL(mtbdd_enum_par_do, dd, cb, context, NULL);
+}
+
+/**
+ * Function composition after partial evaluation.
+ *
+ * Given a function F(X) = f, compute the composition F'(X) = g(f) for every assignment to X.
+ * All variables X in <vars> must appear before all variables in f and g(f).
+ *
+ * Usage:
+ * TASK_2(MTBDD, g, MTBDD, in) { ... return g of <in> ... }
+ * MTBDD x_vars = ...;  // the cube of variables x
+ * MTBDD result = mtbdd_eval_compose(dd, x_vars, TASK(g));
+ */
+TASK_IMPL_3(MTBDD, mtbdd_eval_compose, MTBDD, dd, MTBDD, vars, mtbdd_eval_compose_cb, cb)
+{
+    /* Maybe perform garbage collection */
+    sylvan_gc_test();
+
+    /* Count operation */
+    sylvan_stats_count(MTBDD_EVAL_COMPOSE);
+
+    /* Check cache */
+    MTBDD result;
+    if (cache_get3(CACHE_MTBDD_EVAL_COMPOSE, dd, vars, (size_t)cb, &result)) {
+        sylvan_stats_count(MTBDD_EVAL_COMPOSE_CACHED);
+        return result;
+    }
+
+    if (mtbdd_isleaf(dd) || vars == mtbdd_true) {
+        /* Apply */
+        result = WRAP(cb, dd);
+    } else {
+        /* Get top variable in dd */
+        mtbddnode_t ndd = MTBDD_GETNODE(dd);
+        uint32_t var = mtbddnode_getvariable(ndd);
+
+        /* Check if <var> is in <vars> */
+        mtbddnode_t nvars = MTBDD_GETNODE(vars);
+        uint32_t vv = mtbddnode_getvariable(nvars);
+
+        /* Search/forward <vars> */
+        MTBDD _vars = vars;
+        while (vv < var) {
+            _vars = node_gethigh(_vars, nvars);
+            if (_vars == mtbdd_true) break;
+            nvars = MTBDD_GETNODE(_vars);
+            vv = mtbddnode_getvariable(nvars);
+        }
+
+        if (_vars == mtbdd_true) {
+            /* Apply */
+            result = WRAP(cb, dd);
+        } else {
+            /* If this fails, then there are variables in f/g BEFORE vars, which breaks functionality. */
+            assert(vv == var);
+            if (vv != var) return mtbdd_invalid;
+
+            /* Get cofactors */
+            MTBDD ddlow = node_getlow(dd, ndd);
+            MTBDD ddhigh = node_gethigh(dd, ndd);
+
+            /* Recursive */
+            _vars = node_gethigh(_vars, nvars);
+            mtbdd_refs_spawn(SPAWN(mtbdd_eval_compose, ddhigh, _vars, cb));
+            MTBDD low = mtbdd_refs_push(CALL(mtbdd_eval_compose, ddlow, _vars, cb));
+            MTBDD high = mtbdd_refs_sync(SYNC(mtbdd_eval_compose));
+            mtbdd_refs_pop(1);
+            result = mtbdd_makenode(var, low, high);
+        }
+    }
+
+    /* Store in cache */
+    if (cache_put3(CACHE_MTBDD_EVAL_COMPOSE, dd, vars, (size_t)cb, result)) {
+        sylvan_stats_count(MTBDD_EVAL_COMPOSE_CACHEDPUT);
+    }
+
+    return result;
+}
+
 /**
  * Helper function for recursive unmarking
  */
 static void
 mtbdd_unmark_rec(MTBDD mtbdd)
 {
-    mtbddnode_t n = GETNODE(mtbdd);
+    mtbddnode_t n = MTBDD_GETNODE(mtbdd);
     if (!mtbddnode_getmark(n)) return;
     mtbddnode_setmark(n, 0);
     if (mtbddnode_isleaf(n)) return;
@@ -2255,7 +2836,7 @@ mtbdd_leafcount_mark(MTBDD mtbdd)
 {
     if (mtbdd == mtbdd_true) return 0; // do not count true/false leaf
     if (mtbdd == mtbdd_false) return 0; // do not count true/false leaf
-    mtbddnode_t n = GETNODE(mtbdd);
+    mtbddnode_t n = MTBDD_GETNODE(mtbdd);
     if (mtbddnode_getmark(n)) return 0;
     mtbddnode_setmark(n, 1);
     if (mtbddnode_isleaf(n)) return 1; // count leaf as 1
@@ -2263,10 +2844,11 @@ mtbdd_leafcount_mark(MTBDD mtbdd)
 }
 
 size_t
-mtbdd_leafcount(MTBDD mtbdd)
+mtbdd_leafcount_more(const MTBDD *mtbdds, size_t count)
 {
-    size_t result = mtbdd_leafcount_mark(mtbdd);
-    mtbdd_unmark_rec(mtbdd);
+    size_t result = 0, i;
+    for (i=0; i<count; i++) result += mtbdd_leafcount_mark(mtbdds[i]);
+    for (i=0; i<count; i++) mtbdd_unmark_rec(mtbdds[i]);
     return result;
 }
 
@@ -2277,9 +2859,7 @@ mtbdd_leafcount(MTBDD mtbdd)
 static size_t
 mtbdd_nodecount_mark(MTBDD mtbdd)
 {
-    if (mtbdd == mtbdd_true) return 0; // do not count true/false leaf
-    if (mtbdd == mtbdd_false) return 0; // do not count true/false leaf
-    mtbddnode_t n = GETNODE(mtbdd);
+    mtbddnode_t n = MTBDD_GETNODE(mtbdd);
     if (mtbddnode_getmark(n)) return 0;
     mtbddnode_setmark(n, 1);
     if (mtbddnode_isleaf(n)) return 1; // count leaf as 1
@@ -2287,10 +2867,11 @@ mtbdd_nodecount_mark(MTBDD mtbdd)
 }
 
 size_t
-mtbdd_nodecount(MTBDD mtbdd)
+mtbdd_nodecount_more(const MTBDD *mtbdds, size_t count)
 {
-    size_t result = mtbdd_nodecount_mark(mtbdd);
-    mtbdd_unmark_rec(mtbdd);
+    size_t result = 0, i;
+    for (i=0; i<count; i++) result += mtbdd_nodecount_mark(mtbdds[i]);
+    for (i=0; i<count; i++) mtbdd_unmark_rec(mtbdds[i]);
     return result;
 }
 
@@ -2310,7 +2891,7 @@ TASK_2(int, mtbdd_test_isvalid_rec, MTBDD, dd, uint32_t, parent_var)
     if (marked == 0) return 0;
 
     // check if leaf
-    mtbddnode_t n = GETNODE(dd);
+    mtbddnode_t n = MTBDD_GETNODE(dd);
     if (mtbddnode_isleaf(n)) return 1; // we're fine
 
     // check variable order
@@ -2321,6 +2902,7 @@ TASK_2(int, mtbdd_test_isvalid_rec, MTBDD, dd, uint32_t, parent_var)
     // check cache
     uint64_t result;
     if (cache_get3(CACHE_BDD_ISBDD, dd, 0, 0, &result)) {
+        sylvan_stats_count(BDD_ISBDD_CACHED);
         return result;
     }
 
@@ -2330,7 +2912,10 @@ TASK_2(int, mtbdd_test_isvalid_rec, MTBDD, dd, uint32_t, parent_var)
     if (!SYNC(mtbdd_test_isvalid_rec)) result = 0;
 
     // put in cache and return result
-    cache_put3(CACHE_BDD_ISBDD, dd, 0, 0, result);
+    if (cache_put3(CACHE_BDD_ISBDD, dd, 0, 0, result)) {
+        sylvan_stats_count(BDD_ISBDD_CACHEDPUT);
+    }
+
     return result;
 }
 
@@ -2350,7 +2935,7 @@ TASK_IMPL_1(int, mtbdd_test_isvalid, MTBDD, dd)
     if (marked == 0) return 0;
 
     // check if leaf
-    mtbddnode_t n = GETNODE(dd);
+    mtbddnode_t n = MTBDD_GETNODE(dd);
     if (mtbddnode_isleaf(n)) return 1; // we're fine
 
     // check recursively
@@ -2361,44 +2946,68 @@ TASK_IMPL_1(int, mtbdd_test_isvalid, MTBDD, dd)
     return result;
 }
 
+/**
+ * Write a text representation of a leaf to the given file.
+ */
+void
+mtbdd_fprint_leaf(FILE *out, MTBDD leaf)
+{
+    char buf[64];
+    char *ptr = mtbdd_leaf_to_str(leaf, buf, 64);
+    if (ptr != NULL) {
+        fputs(ptr, out);
+        if (ptr != buf) free(ptr);
+    }
+}
+
+/**
+ * Write a text representation of a leaf to stdout.
+ */
+void
+mtbdd_print_leaf(MTBDD leaf)
+{
+    mtbdd_fprint_leaf(stdout, leaf);
+}
+
+/**
+ * Obtain the textual representation of a leaf.
+ * The returned result is either equal to the given <buf> (if the results fits)
+ * or to a newly allocated array (with malloc).
+ */
+char *
+mtbdd_leaf_to_str(MTBDD leaf, char *buf, size_t buflen)
+{
+    mtbddnode_t n = MTBDD_GETNODE(leaf);
+    uint32_t type = mtbddnode_gettype(n);
+    uint64_t value = mtbddnode_getvalue(n);
+    int complement = MTBDD_HASMARK(leaf) ? 1 : 0;
+
+    return sylvan_mt_to_str(complement, type, value, buf, buflen);
+}
+
 /**
  * Export to .dot file
  */
 
 static void
-mtbdd_fprintdot_rec(FILE *out, MTBDD mtbdd, print_terminal_label_cb cb)
+mtbdd_fprintdot_rec(FILE *out, MTBDD mtbdd)
 {
-    mtbddnode_t n = GETNODE(mtbdd); // also works for mtbdd_false
+    mtbddnode_t n = MTBDD_GETNODE(mtbdd); // also works for mtbdd_false
     if (mtbddnode_getmark(n)) return;
     mtbddnode_setmark(n, 1);
 
     if (mtbdd == mtbdd_true || mtbdd == mtbdd_false) {
         fprintf(out, "0 [shape=box, style=filled, label=\"F\"];\n");
     } else if (mtbddnode_isleaf(n)) {
-        uint32_t type = mtbddnode_gettype(n);
-        uint64_t value = mtbddnode_getvalue(n);
         fprintf(out, "%" PRIu64 " [shape=box, style=filled, label=\"", MTBDD_STRIPMARK(mtbdd));
-        switch (type) {
-        case 0:
-            fprintf(out, "%" PRIu64, value);
-            break;
-        case 1:
-            fprintf(out, "%f", *(double*)&value);
-            break;
-        case 2:
-            fprintf(out, "%u/%u", (uint32_t)(value>>32), (uint32_t)value);
-            break;
-        default:
-            cb(out, type, value);
-            break;
-        }
+        mtbdd_fprint_leaf(out, mtbdd);
         fprintf(out, "\"];\n");
     } else {
         fprintf(out, "%" PRIu64 " [label=\"%" PRIu32 "\"];\n",
                 MTBDD_STRIPMARK(mtbdd), mtbddnode_getvariable(n));
 
-        mtbdd_fprintdot_rec(out, mtbddnode_getlow(n), cb);
-        mtbdd_fprintdot_rec(out, mtbddnode_gethigh(n), cb);
+        mtbdd_fprintdot_rec(out, mtbddnode_getlow(n));
+        mtbdd_fprintdot_rec(out, mtbddnode_gethigh(n));
 
         fprintf(out, "%" PRIu64 " -> %" PRIu64 " [style=dashed];\n",
                 MTBDD_STRIPMARK(mtbdd), mtbddnode_getlow(n));
@@ -2409,7 +3018,7 @@ mtbdd_fprintdot_rec(FILE *out, MTBDD mtbdd, print_terminal_label_cb cb)
 }
 
 void
-mtbdd_fprintdot(FILE *out, MTBDD mtbdd, print_terminal_label_cb cb)
+mtbdd_fprintdot(FILE *out, MTBDD mtbdd)
 {
     fprintf(out, "digraph \"DD\" {\n");
     fprintf(out, "graph [dpi = 300];\n");
@@ -2419,12 +3028,509 @@ mtbdd_fprintdot(FILE *out, MTBDD mtbdd, print_terminal_label_cb cb)
     fprintf(out, "root -> %" PRIu64 " [style=solid dir=both arrowtail=%s];\n",
             MTBDD_STRIPMARK(mtbdd), MTBDD_HASMARK(mtbdd) ? "dot" : "none");
 
-    mtbdd_fprintdot_rec(out, mtbdd, cb);
+    mtbdd_fprintdot_rec(out, mtbdd);
+    mtbdd_unmark_rec(mtbdd);
+
+    fprintf(out, "}\n");
+}
+
+/**
+ * Export to .dot file, but do not display complement edges. Expand instead.
+ */
+
+static void
+mtbdd_fprintdot_nc_rec(FILE *out, MTBDD mtbdd)
+{
+    mtbddnode_t n = MTBDD_GETNODE(mtbdd); // also works for mtbdd_false
+    if (mtbddnode_getmark(n)) return;
+    mtbddnode_setmark(n, 1);
+
+    if (mtbdd == mtbdd_true) {
+        fprintf(out, "%" PRIu64 " [shape=box, style=filled, label=\"T\"];\n", mtbdd);
+    } else if (mtbdd == mtbdd_false) {
+        fprintf(out, "0 [shape=box, style=filled, label=\"F\"];\n");
+    } else if (mtbddnode_isleaf(n)) {
+        fprintf(out, "%" PRIu64 " [shape=box, style=filled, label=\"", mtbdd);
+        mtbdd_fprint_leaf(out, mtbdd);
+        fprintf(out, "\"];\n");
+    } else {
+        fprintf(out, "%" PRIu64 " [label=\"%" PRIu32 "\"];\n", mtbdd, mtbddnode_getvariable(n));
+
+        mtbdd_fprintdot_nc_rec(out, mtbddnode_getlow(n));
+        mtbdd_fprintdot_nc_rec(out, mtbddnode_gethigh(n));
+
+        fprintf(out, "%" PRIu64 " -> %" PRIu64 " [style=dashed];\n", mtbdd, node_getlow(mtbdd, n));
+        fprintf(out, "%" PRIu64 " -> %" PRIu64 " [style=solid];\n", mtbdd, node_gethigh(mtbdd, n));
+    }
+}
+
+void
+mtbdd_fprintdot_nc(FILE *out, MTBDD mtbdd)
+{
+    fprintf(out, "digraph \"DD\" {\n");
+    fprintf(out, "graph [dpi = 300];\n");
+    fprintf(out, "center = true;\n");
+    fprintf(out, "edge [dir = forward];\n");
+    fprintf(out, "root [style=invis];\n");
+    fprintf(out, "root -> %" PRIu64 " [style=solid];\n", mtbdd);
+
+    mtbdd_fprintdot_nc_rec(out, mtbdd);
     mtbdd_unmark_rec(mtbdd);
 
     fprintf(out, "}\n");
 }
 
+/**
+ * Generate SHA2 structural hashes.
+ * Hashes are independent of location.
+ * Mainly useful for debugging purposes.
+ */
+static void
+mtbdd_sha2_rec(MTBDD dd, SHA256_CTX *ctx)
+{
+    if (dd == mtbdd_true || dd == mtbdd_false) {
+        SHA256_Update(ctx, (void*)&dd, sizeof(MTBDD));
+        return;
+    }
+
+    mtbddnode_t node = MTBDD_GETNODE(dd);
+    if (mtbddnode_getmark(node) == 0) {
+        mtbddnode_setmark(node, 1);
+        if (mtbddnode_isleaf(node)) {
+            uint32_t type = mtbddnode_gettype(node);
+            SHA256_Update(ctx, (void*)&type, sizeof(uint32_t));
+            uint64_t value = mtbddnode_getvalue(node);
+            value = sylvan_mt_hash(type, value, value);
+            SHA256_Update(ctx, (void*)&value, sizeof(uint64_t));
+        } else {
+            uint32_t level = mtbddnode_getvariable(node);
+            if (MTBDD_STRIPMARK(mtbddnode_gethigh(node))) level |= 0x80000000;
+            SHA256_Update(ctx, (void*)&level, sizeof(uint32_t));
+            mtbdd_sha2_rec(mtbddnode_gethigh(node), ctx);
+            mtbdd_sha2_rec(mtbddnode_getlow(node), ctx);
+        }
+    }
+}
+
+void
+mtbdd_printsha(MTBDD dd)
+{
+    mtbdd_fprintsha(stdout, dd);
+}
+
+void
+mtbdd_fprintsha(FILE *f, MTBDD dd)
+{
+    char buf[80];
+    mtbdd_getsha(dd, buf);
+    fprintf(f, "%s", buf);
+}
+
+void
+mtbdd_getsha(MTBDD dd, char *target)
+{
+    SHA256_CTX ctx;
+    SHA256_Init(&ctx);
+    mtbdd_sha2_rec(dd, &ctx);
+    if (dd != mtbdd_true && dd != mtbdd_false) mtbdd_unmark_rec(dd);
+    SHA256_End(&ctx, target);
+}
+
+/**
+ * Implementation of visitor operations
+ */
+
+VOID_TASK_IMPL_4(mtbdd_visit_seq, MTBDD, dd, mtbdd_visit_pre_cb, pre_cb, mtbdd_visit_post_cb, post_cb, void*, ctx)
+{
+    int children = 1;
+    if (pre_cb != NULL) children = WRAP(pre_cb, dd, ctx);
+    if (children && !mtbdd_isleaf(dd)) {
+        CALL(mtbdd_visit_seq, mtbdd_getlow(dd), pre_cb, post_cb, ctx);
+        CALL(mtbdd_visit_seq, mtbdd_gethigh(dd), pre_cb, post_cb, ctx);
+    }
+    if (post_cb != NULL) WRAP(post_cb, dd, ctx);
+}
+
+VOID_TASK_IMPL_4(mtbdd_visit_par, MTBDD, dd, mtbdd_visit_pre_cb, pre_cb, mtbdd_visit_post_cb, post_cb, void*, ctx)
+{
+    int children = 1;
+    if (pre_cb != NULL) children = WRAP(pre_cb, dd, ctx);
+    if (children && !mtbdd_isleaf(dd)) {
+        SPAWN(mtbdd_visit_par, mtbdd_getlow(dd), pre_cb, post_cb, ctx);
+        CALL(mtbdd_visit_par, mtbdd_gethigh(dd), pre_cb, post_cb, ctx);
+        SYNC(mtbdd_visit_par);
+    }
+    if (post_cb != NULL) WRAP(post_cb, dd, ctx);
+}
+
+/**
+ * Writing MTBDD files using a skiplist as a backend
+ */
+
+TASK_2(int, mtbdd_writer_add_visitor_pre, MTBDD, dd, sylvan_skiplist_t, sl)
+{
+    if (mtbdd_isleaf(dd)) return 0;
+    return sylvan_skiplist_get(sl, MTBDD_STRIPMARK(dd)) == 0 ? 1 : 0;
+}
+
+VOID_TASK_2(mtbdd_writer_add_visitor_post, MTBDD, dd, sylvan_skiplist_t, sl)
+{
+    if (dd == mtbdd_true || dd == mtbdd_false) return;
+    sylvan_skiplist_assign_next(sl, MTBDD_STRIPMARK(dd));
+}
+
+sylvan_skiplist_t
+mtbdd_writer_start()
+{
+    size_t sl_size = nodes->table_size > 0x7fffffff ? 0x7fffffff : nodes->table_size;
+    return sylvan_skiplist_alloc(sl_size);
+}
+
+VOID_TASK_IMPL_2(mtbdd_writer_add, sylvan_skiplist_t, sl, MTBDD, dd)
+{
+    mtbdd_visit_seq(dd, (mtbdd_visit_pre_cb)TASK(mtbdd_writer_add_visitor_pre), (mtbdd_visit_post_cb)TASK(mtbdd_writer_add_visitor_post), (void*)sl);
+}
+
+void
+mtbdd_writer_writebinary(FILE *out, sylvan_skiplist_t sl)
+{
+    size_t nodecount = sylvan_skiplist_count(sl);
+    fwrite(&nodecount, sizeof(size_t), 1, out);
+    for (size_t i=1; i<=nodecount; i++) {
+        MTBDD dd = sylvan_skiplist_getr(sl, i);
+
+        mtbddnode_t n = MTBDD_GETNODE(dd);
+        if (mtbddnode_isleaf(n)) {
+            /* write leaf */
+            fwrite(n, sizeof(struct mtbddnode), 1, out);
+            uint32_t type = mtbddnode_gettype(n);
+            uint64_t value = mtbddnode_getvalue(n);
+            sylvan_mt_write_binary(type, value, out);
+        } else {
+            struct mtbddnode node;
+            MTBDD low = sylvan_skiplist_get(sl, mtbddnode_getlow(n));
+            MTBDD high = mtbddnode_gethigh(n);
+            high = MTBDD_TRANSFERMARK(high, sylvan_skiplist_get(sl, MTBDD_STRIPMARK(high)));
+            mtbddnode_makenode(&node, mtbddnode_getvariable(n), low, high);
+            fwrite(&node, sizeof(struct mtbddnode), 1, out);
+        }
+    }
+}
+
+uint64_t
+mtbdd_writer_get(sylvan_skiplist_t sl, MTBDD dd)
+{
+    return MTBDD_TRANSFERMARK(dd, sylvan_skiplist_get(sl, MTBDD_STRIPMARK(dd)));
+}
+
+void
+mtbdd_writer_end(sylvan_skiplist_t sl)
+{
+    sylvan_skiplist_free(sl);
+}
+
+VOID_TASK_IMPL_3(mtbdd_writer_tobinary, FILE *, out, MTBDD *, dds, int, count)
+{
+    sylvan_skiplist_t sl = mtbdd_writer_start();
+
+    for (int i=0; i<count; i++) {
+        CALL(mtbdd_writer_add, sl, dds[i]);
+    }
+
+    mtbdd_writer_writebinary(out, sl);
+
+    fwrite(&count, sizeof(int), 1, out);
+
+    for (int i=0; i<count; i++) {
+        uint64_t v = mtbdd_writer_get(sl, dds[i]);
+        fwrite(&v, sizeof(uint64_t), 1, out);
+    }
+
+    mtbdd_writer_end(sl);
+}
+
+void
+mtbdd_writer_writetext(FILE *out, sylvan_skiplist_t sl)
+{
+    fprintf(out, "[\n");
+    size_t nodecount = sylvan_skiplist_count(sl);
+    for (size_t i=1; i<=nodecount; i++) {
+        MTBDD dd = sylvan_skiplist_getr(sl, i);
+
+        mtbddnode_t n = MTBDD_GETNODE(dd);
+        if (mtbddnode_isleaf(n)) {
+            /* serialize leaf, does not support customs yet */
+            fprintf(out, "  leaf(%zu,%u,\"", i, mtbddnode_gettype(n));
+            mtbdd_fprint_leaf(out, MTBDD_STRIPMARK(dd));
+            fprintf(out, "\"),\n");
+        } else {
+            MTBDD low = sylvan_skiplist_get(sl, mtbddnode_getlow(n));
+            MTBDD high = mtbddnode_gethigh(n);
+            high = MTBDD_TRANSFERMARK(high, sylvan_skiplist_get(sl, MTBDD_STRIPMARK(high)));
+            fprintf(out, "  node(%zu,%u,%zu,%s%zu),\n", i, mtbddnode_getvariable(n), (size_t)low, MTBDD_HASMARK(high)?"~":"", (size_t)MTBDD_STRIPMARK(high));
+        }
+    }
+
+    fprintf(out, "]");
+}
+
+VOID_TASK_IMPL_3(mtbdd_writer_totext, FILE *, out, MTBDD *, dds, int, count)
+{
+    sylvan_skiplist_t sl = mtbdd_writer_start();
+
+    for (int i=0; i<count; i++) {
+        CALL(mtbdd_writer_add, sl, dds[i]);
+    }
+
+    mtbdd_writer_writetext(out, sl);
+
+    fprintf(out, ",[");
+
+    for (int i=0; i<count; i++) {
+        uint64_t v = mtbdd_writer_get(sl, dds[i]);
+        fprintf(out, "%s%zu,", MTBDD_HASMARK(v)?"~":"", (size_t)MTBDD_STRIPMARK(v));
+    }
+
+    fprintf(out, "]\n");
+
+    mtbdd_writer_end(sl);
+}
+
+/**
+ * Reading a file earlier written with mtbdd_writer_writebinary
+ * Returns an array with the conversion from stored identifier to MTBDD
+ * This array is allocated with malloc and must be freed afterwards.
+ * This method does not support custom leaves.
+ */
+TASK_IMPL_1(uint64_t*, mtbdd_reader_readbinary, FILE*, in)
+{
+    size_t nodecount;
+    if (fread(&nodecount, sizeof(size_t), 1, in) != 1) {
+        return NULL;
+    }
+
+    uint64_t *arr = malloc(sizeof(uint64_t)*(nodecount+1));
+    arr[0] = 0;
+    for (size_t i=1; i<=nodecount; i++) {
+        struct mtbddnode node;
+        if (fread(&node, sizeof(struct mtbddnode), 1, in) != 1) {
+            free(arr);
+            return NULL;
+        }
+
+        if (mtbddnode_isleaf(&node)) {
+            /* serialize leaf */
+            uint32_t type = mtbddnode_gettype(&node);
+            uint64_t value = mtbddnode_getvalue(&node);
+            sylvan_mt_read_binary(type, &value, in);
+            arr[i] = mtbdd_makeleaf(type, value);
+        } else {
+            MTBDD low = arr[mtbddnode_getlow(&node)];
+            MTBDD high = mtbddnode_gethigh(&node);
+            high = MTBDD_TRANSFERMARK(high, arr[MTBDD_STRIPMARK(high)]);
+            arr[i] = mtbdd_makenode(mtbddnode_getvariable(&node), low, high);
+        }
+    }
+
+    return arr;
+}
+
+/**
+ * Retrieve the MTBDD of the given stored identifier.
+ */
+MTBDD
+mtbdd_reader_get(uint64_t* arr, uint64_t identifier)
+{
+    return MTBDD_TRANSFERMARK(identifier, arr[MTBDD_STRIPMARK(identifier)]);
+}
+
+/**
+ * Free the allocated translation array
+ */
+void
+mtbdd_reader_end(uint64_t *arr)
+{
+    free(arr);
+}
+
+/**
+ * Reading a file earlier written with mtbdd_writer_tobinary
+ */
+TASK_IMPL_3(int, mtbdd_reader_frombinary, FILE*, in, MTBDD*, dds, int, count)
+{
+    uint64_t *arr = CALL(mtbdd_reader_readbinary, in);
+    if (arr == NULL) return -1;
+
+    /* Read stored count */
+    int actual_count;
+    if (fread(&actual_count, sizeof(int), 1, in) != 1) {
+        mtbdd_reader_end(arr);
+        return -1;
+    }
+
+    /* If actual count does not agree with given count, abort */
+    if (actual_count != count) {
+        mtbdd_reader_end(arr);
+        return -1;
+    }
+
+    /* Read every stored identifier, and translate to MTBDD */
+    for (int i=0; i<count; i++) {
+        uint64_t v;
+        if (fread(&v, sizeof(uint64_t), 1, in) != 1) {
+            mtbdd_reader_end(arr);
+            return -1;
+        }
+        dds[i] = mtbdd_reader_get(arr, v);
+    }
+
+    mtbdd_reader_end(arr);
+    return 0;
+}
+
+/**
+ * Implementation of variable sets, i.e., cubes of (positive) variables.
+ */
+
+/**
+ * Create a set of variables, represented as the conjunction of (positive) variables.
+ */
+MTBDD
+mtbdd_set_from_array(uint32_t* arr, size_t length)
+{
+    if (length == 0) return mtbdd_true;
+    else if (length == 1) return mtbdd_makenode(*arr, mtbdd_false, mtbdd_true);
+    else return mtbdd_set_add(mtbdd_fromarray(arr+1, length-1), *arr);
+}
+
+/**
+ * Write all variables in a variable set to the given array.
+ * The array must be sufficiently large.
+ */
+void
+mtbdd_set_to_array(MTBDD set, uint32_t *arr)
+{
+    while (set != mtbdd_true) {
+        mtbddnode_t n = MTBDD_GETNODE(set);
+        *arr++ = mtbddnode_getvariable(n);
+        set = node_gethigh(set, n);
+    }
+}
+
+/**
+ * Add the variable <var> to <set>.
+ */
+MTBDD
+mtbdd_set_add(MTBDD set, uint32_t var)
+{
+    if (set == mtbdd_true) return mtbdd_makenode(var, mtbdd_false, mtbdd_true);
+
+    mtbddnode_t set_node = MTBDD_GETNODE(set);
+    uint32_t set_var = mtbddnode_getvariable(set_node);
+    if (var < set_var) return mtbdd_makenode(var, mtbdd_false, set);
+    else if (set_var == var) return set;
+    else {
+        MTBDD sub = mtbddnode_followhigh(set, set_node);
+        MTBDD res = mtbdd_set_add(sub, var);
+        res = sub == res ? set : mtbdd_makenode(set_var, mtbdd_false, res);
+        return res;
+    }
+}
+
+/**
+ * Remove the variable <var> from <set>.
+ */
+MTBDD
+mtbdd_set_remove(MTBDD set, uint32_t var)
+{
+    if (set == mtbdd_true) return mtbdd_true;
+
+    mtbddnode_t set_node = MTBDD_GETNODE(set);
+    uint32_t set_var = mtbddnode_getvariable(set_node);
+    if (var < set_var) return set;
+    else if (set_var == var) return mtbddnode_followhigh(set, set_node);
+    else {
+        MTBDD sub = mtbddnode_followhigh(set, set_node);
+        MTBDD res = mtbdd_set_remove(sub, var);
+        res = sub == res ? set : mtbdd_makenode(set_var, mtbdd_false, res);
+        return res;
+    }
+}
+
+/**
+ * Remove variables in <set2> from <set1>.
+ */
+TASK_IMPL_2(MTBDD, mtbdd_set_minus, MTBDD, set1, MTBDD, set2)
+{
+    if (set1 == mtbdd_true) return mtbdd_true;
+    if (set2 == mtbdd_true) return set1;
+    if (set1 == set2) return mtbdd_true;
+
+    mtbddnode_t set1_node = MTBDD_GETNODE(set1);
+    mtbddnode_t set2_node = MTBDD_GETNODE(set2);
+    uint32_t set1_var = mtbddnode_getvariable(set1_node);
+    uint32_t set2_var = mtbddnode_getvariable(set2_node);
+
+    if (set1_var == set2_var) {
+        return mtbdd_set_minus(mtbddnode_followhigh(set1, set1_node), mtbddnode_followhigh(set2, set2_node));
+    }
+
+    if (set1_var > set2_var) {
+        return mtbdd_set_minus(set1, mtbddnode_followhigh(set2, set2_node));
+    }
+
+    /* set1_var < set2_var */
+    MTBDD sub = mtbddnode_followhigh(set1, set1_node);
+    MTBDD res = mtbdd_set_minus(sub, set2);
+    return res == sub ? set1 : mtbdd_makenode(set1_var, mtbdd_false, res);
+}
+
+/**
+ * Return 1 if <set> contains <var>, 0 otherwise.
+ */
+int
+mtbdd_set_contains(MTBDD set, uint32_t var)
+{
+    while (set != mtbdd_true) {
+        mtbddnode_t n = MTBDD_GETNODE(set);
+        uint32_t v = mtbddnode_getvariable(n);
+        if (v == var) return 1;
+        if (v > var) return 0;
+        set = node_gethigh(set, n);
+    }
+    return 0;
+}
+
+/**
+ * Compute the number of variables in a given set of variables.
+ */
+size_t
+mtbdd_set_count(MTBDD set)
+{
+    size_t result = 0;
+    while (set != mtbdd_true) {
+        result++;
+        set = mtbdd_gethigh(set);
+    }
+    return result;
+}
+
+/**
+ * Sanity check if the given MTBDD is a conjunction of positive variables,
+ * and if all nodes are marked in the nodes table (detects violations after garbage collection).
+ */
+void
+mtbdd_test_isset(MTBDD set)
+{
+    while (set != mtbdd_true) {
+        assert(set != mtbdd_false);
+        assert(llmsset_is_marked(nodes, set));
+        mtbddnode_t n = MTBDD_GETNODE(set);
+        assert(node_getlow(set, n) == mtbdd_false);
+        set = node_gethigh(set, n);
+    }
+}
+
 /**
  * Return 1 if the map contains the key, 0 otherwise.
  */
@@ -2432,7 +3538,7 @@ int
 mtbdd_map_contains(MTBDDMAP map, uint32_t key)
 {
     while (!mtbdd_map_isempty(map)) {
-        mtbddnode_t n = GETNODE(map);
+        mtbddnode_t n = MTBDD_GETNODE(map);
         uint32_t k = mtbddnode_getvariable(n);
         if (k == key) return 1;
         if (k > key) return 0;
@@ -2464,20 +3570,22 @@ mtbdd_map_count(MTBDDMAP map)
 MTBDDMAP
 mtbdd_map_add(MTBDDMAP map, uint32_t key, MTBDD value)
 {
-    if (mtbdd_map_isempty(map)) return mtbdd_makenode(key, mtbdd_map_empty(), value);
+    if (mtbdd_map_isempty(map)) {
+        return mtbdd_makemapnode(key, mtbdd_map_empty(), value);
+    }
 
-    mtbddnode_t n = GETNODE(map);
+    mtbddnode_t n = MTBDD_GETNODE(map);
     uint32_t k = mtbddnode_getvariable(n);
 
     if (k < key) {
         // add recursively and rebuild tree
         MTBDDMAP low = mtbdd_map_add(node_getlow(map, n), key, value);
-        return mtbdd_makenode(k, low, node_gethigh(map, n));
+        return mtbdd_makemapnode(k, low, node_gethigh(map, n));
     } else if (k > key) {
-        return mtbdd_makenode(key, map, value);
+        return mtbdd_makemapnode(key, map, value);
     } else {
         // replace old
-        return mtbdd_makenode(key, node_getlow(map, n), value);
+        return mtbdd_makemapnode(key, node_getlow(map, n), value);
     }
 }
 
@@ -2485,26 +3593,26 @@ mtbdd_map_add(MTBDDMAP map, uint32_t key, MTBDD value)
  * Add all values from map2 to map1, overwrites if key already in map1.
  */
 MTBDDMAP
-mtbdd_map_addall(MTBDDMAP map1, MTBDDMAP map2)
+mtbdd_map_update(MTBDDMAP map1, MTBDDMAP map2)
 {
     if (mtbdd_map_isempty(map1)) return map2;
     if (mtbdd_map_isempty(map2)) return map1;
 
-    mtbddnode_t n1 = GETNODE(map1);
-    mtbddnode_t n2 = GETNODE(map2);
+    mtbddnode_t n1 = MTBDD_GETNODE(map1);
+    mtbddnode_t n2 = MTBDD_GETNODE(map2);
     uint32_t k1 = mtbddnode_getvariable(n1);
     uint32_t k2 = mtbddnode_getvariable(n2);
 
     MTBDDMAP result;
     if (k1 < k2) {
-        MTBDDMAP low = mtbdd_map_addall(node_getlow(map1, n1), map2);
-        result = mtbdd_makenode(k1, low, node_gethigh(map1, n1));
+        MTBDDMAP low = mtbdd_map_update(node_getlow(map1, n1), map2);
+        result = mtbdd_makemapnode(k1, low, node_gethigh(map1, n1));
     } else if (k1 > k2) {
-        MTBDDMAP low = mtbdd_map_addall(map1, node_getlow(map2, n2));
-        result = mtbdd_makenode(k2, low, node_gethigh(map2, n2));
+        MTBDDMAP low = mtbdd_map_update(map1, node_getlow(map2, n2));
+        result = mtbdd_makemapnode(k2, low, node_gethigh(map2, n2));
     } else {
-        MTBDDMAP low = mtbdd_map_addall(node_getlow(map1, n1), node_getlow(map2, n2));
-        result = mtbdd_makenode(k2, low, node_gethigh(map2, n2));
+        MTBDDMAP low = mtbdd_map_update(node_getlow(map1, n1), node_getlow(map2, n2));
+        result = mtbdd_makemapnode(k2, low, node_gethigh(map2, n2));
     }
 
     return result;
@@ -2518,12 +3626,12 @@ mtbdd_map_remove(MTBDDMAP map, uint32_t key)
 {
     if (mtbdd_map_isempty(map)) return map;
 
-    mtbddnode_t n = GETNODE(map);
+    mtbddnode_t n = MTBDD_GETNODE(map);
     uint32_t k = mtbddnode_getvariable(n);
 
     if (k < key) {
         MTBDDMAP low = mtbdd_map_remove(node_getlow(map, n), key);
-        return mtbdd_makenode(k, low, node_gethigh(map, n));
+        return mtbdd_makemapnode(k, low, node_gethigh(map, n));
     } else if (k > key) {
         return map;
     } else {
@@ -2540,14 +3648,14 @@ mtbdd_map_removeall(MTBDDMAP map, MTBDD variables)
     if (mtbdd_map_isempty(map)) return map;
     if (variables == mtbdd_true) return map;
 
-    mtbddnode_t n1 = GETNODE(map);
-    mtbddnode_t n2 = GETNODE(variables);
+    mtbddnode_t n1 = MTBDD_GETNODE(map);
+    mtbddnode_t n2 = MTBDD_GETNODE(variables);
     uint32_t k1 = mtbddnode_getvariable(n1);
     uint32_t k2 = mtbddnode_getvariable(n2);
 
     if (k1 < k2) {
         MTBDDMAP low = mtbdd_map_removeall(node_getlow(map, n1), variables);
-        return mtbdd_makenode(k1, low, node_gethigh(map, n1));
+        return mtbdd_makemapnode(k1, low, node_gethigh(map, n1));
     } else if (k1 > k2) {
         return mtbdd_map_removeall(map, node_gethigh(variables, n2));
     } else {
diff --git a/sylvan_mtbdd.h b/sylvan_mtbdd.h
old mode 100644
new mode 100755
index a56d68ebfe4d88e6ae3473174eaa928caf9ef3b5..bdcd26525a27ae88fd0108dfa5c5d92c582b9915
--- a/sylvan_mtbdd.h
+++ b/sylvan_mtbdd.h
@@ -1,5 +1,6 @@
 /*
- * Copyright 2011-2015 Formal Methods and Tools, University of Twente
+ * 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.
@@ -45,25 +46,97 @@ extern "C" {
 /**
  * An MTBDD is a 64-bit value. The low 40 bits are an index into the unique table.
  * The highest 1 bit is the complement edge, indicating negation.
- * For Boolean MTBDDs, this means "not X", for Integer and Real MTBDDs, this means "-X".
+ *
+ * Currently, negation using complement edges is only implemented for Boolean MTBDDs.
+ * For Integer/Real MTBDDs, negation is not well-defined, as "-0" = "0".
+ *
+ * A MTBDD node has 24 bits for the variable.
+ * A set of MTBDD variables is represented by the MTBDD of the conjunction of these variables.
+ * A MTBDDMAP uses special "MAP" nodes in the MTBDD nodes table.
  */
 typedef uint64_t MTBDD;
 typedef MTBDD MTBDDMAP;
 
 /**
- * mtbdd_true is only used in Boolean MTBDDs. mtbdd_false has multiple roles (see above).
- */
-#define mtbdd_complement    ((MTBDD)0x8000000000000000LL)
-#define mtbdd_false         ((MTBDD)0)
-#define mtbdd_true          (mtbdd_false|mtbdd_complement)
-#define mtbdd_invalid       ((MTBDD)0xffffffffffffffffLL)
+ * mtbdd_true and mtbdd_false are the Boolean leaves representing True and False.
+ * False is also used in Integer/Real/Fraction MTBDDs for partially defined functions.
+ */
+static const MTBDD mtbdd_complement = 0x8000000000000000LL;
+static const MTBDD mtbdd_false      = 0;
+static const MTBDD mtbdd_true       = 0x8000000000000000LL;
+static const MTBDD mtbdd_invalid    = 0xffffffffffffffffLL;
+
+/**
+ * Definitions for backward compatibility...
+ * We now consider BDDs to be a special case of MTBDDs.
+ */
+typedef MTBDD SyBDD;
+typedef MTBDDMAP BDDMAP;
+typedef MTBDD BDDSET;
+typedef uint32_t BDDVAR;
+static const MTBDD sylvan_complement = 0x8000000000000000LL;
+static const MTBDD sylvan_false      = 0;
+static const MTBDD sylvan_true       = 0x8000000000000000LL;
+static const MTBDD sylvan_invalid    = 0xffffffffffffffffLL;
+#define sylvan_init_bdd         sylvan_init_mtbdd
+#define sylvan_ref              mtbdd_ref
+#define sylvan_deref            mtbdd_deref
+#define sylvan_count_refs       mtbdd_count_refs
+#define sylvan_protect          mtbdd_protect
+#define sylvan_unprotect        mtbdd_unprotect
+#define sylvan_count_protected  mtbdd_count_protected
+#define sylvan_gc_mark_rec      mtbdd_gc_mark_rec
+#define sylvan_ithvar           mtbdd_ithvar
+#define bdd_refs_pushptr        mtbdd_refs_pushptr
+#define bdd_refs_popptr         mtbdd_refs_popptr
+#define bdd_refs_push           mtbdd_refs_push
+#define bdd_refs_pop            mtbdd_refs_pop
+#define bdd_refs_spawn          mtbdd_refs_spawn
+#define bdd_refs_sync           mtbdd_refs_sync
+#define sylvan_map_empty        mtbdd_map_empty
+#define sylvan_map_isempty      mtbdd_map_isempty
+#define sylvan_map_key          mtbdd_map_key
+#define sylvan_map_value        mtbdd_map_value
+#define sylvan_map_next         mtbdd_map_next
+#define sylvan_map_contains     mtbdd_map_contains
+#define sylvan_map_count        mtbdd_map_count
+#define sylvan_map_add          mtbdd_map_add
+#define sylvan_map_addall       mtbdd_map_addall
+#define sylvan_map_remove       mtbdd_map_remove
+#define sylvan_map_removeall    mtbdd_map_removeall
+#define sylvan_set_empty        mtbdd_set_empty
+#define sylvan_set_isempty      mtbdd_set_isempty
+#define sylvan_set_add          mtbdd_set_add
+#define sylvan_set_addall       mtbdd_set_addall
+#define sylvan_set_remove       mtbdd_set_remove
+#define sylvan_set_removeall    mtbdd_set_removeall
+#define sylvan_set_first        mtbdd_set_first
+#define sylvan_set_next         mtbdd_set_next
+#define sylvan_set_fromarray    mtbdd_set_fromarray
+#define sylvan_set_toarray      mtbdd_set_toarray
+#define sylvan_set_in           mtbdd_set_in
+#define sylvan_set_count        mtbdd_set_count
+#define sylvan_test_isset       mtbdd_test_isset
+#define sylvan_var              mtbdd_getvar
+#define sylvan_low              mtbdd_getlow
+#define sylvan_high             mtbdd_gethigh
+#define sylvan_makenode         mtbdd_makenode
+#define sylvan_makemapnode      mtbdd_makemapnode
+#define sylvan_support          mtbdd_support
+#define sylvan_test_isbdd       mtbdd_test_isvalid
+#define sylvan_nodecount        mtbdd_nodecount
+#define sylvan_printdot         mtbdd_printdot
+#define sylvan_fprintdot        mtbdd_fprintdot
+#define sylvan_printsha         mtbdd_printsha
+#define sylvan_fprintsha        mtbdd_fprintsha
+#define sylvan_getsha           mtbdd_getsha
 
 /**
  * Initialize MTBDD functionality.
  * This initializes internal and external referencing datastructures,
  * and registers them in the garbage collection framework.
  */
-void sylvan_init_mtbdd();
+void sylvan_init_mtbdd(void);
 
 /**
  * Create a MTBDD terminal of type <type> and value <value>.
@@ -74,56 +147,208 @@ MTBDD mtbdd_makeleaf(uint32_t type, uint64_t value);
 /**
  * Create an internal MTBDD node of Boolean variable <var>, with low edge <low> and high edge <high>.
  * <var> is a 24-bit integer.
+ * Please note that this does NOT check variable ordering!
  */
-MTBDD mtbdd_makenode(uint32_t var, MTBDD low, MTBDD high);
+MTBDD _mtbdd_makenode(uint32_t var, MTBDD low, MTBDD high);
+static inline MTBDD mtbdd_makenode(uint32_t var, MTBDD low, MTBDD high)
+{
+    return low == high ? low : _mtbdd_makenode(var, low, high);
+}
 
 /**
- * Returns 1 is the MTBDD is a terminal, or 0 otherwise.
+ * Return 1 if the MTBDD is a terminal, or 0 otherwise.
  */
 int mtbdd_isleaf(MTBDD mtbdd);
-#define mtbdd_isnode(mtbdd) (mtbdd_isleaf(mtbdd) ? 0 : 1)
 
 /**
- * For MTBDD terminals, returns <type> and <value>
+ * Return 1 if the MTBDD is an internal node, or 0 otherwise.
+ */
+static inline int mtbdd_isnode(MTBDD mtbdd) { return mtbdd_isleaf(mtbdd) ? 0 : 1; }
+
+/**
+ * Return the <type> field of the given leaf.
+ */
+uint32_t mtbdd_gettype(MTBDD leaf);
+
+/**
+ * Return the <value> field of the given leaf.
  */
-uint32_t mtbdd_gettype(MTBDD terminal);
-uint64_t mtbdd_getvalue(MTBDD terminal);
+uint64_t mtbdd_getvalue(MTBDD leaf);
 
 /**
- * For internal MTBDD nodes, returns <var>, <low> and <high>
+ * Return the variable field of the given internal node.
  */
 uint32_t mtbdd_getvar(MTBDD node);
+
+/**
+ * Follow the low/false edge of the given internal node.
+ * Also takes complement edges into account.
+ */
 MTBDD mtbdd_getlow(MTBDD node);
+
+/**
+ * Follow the high/true edge of the given internal node.
+ * Also takes complement edges into account.
+ */
 MTBDD mtbdd_gethigh(MTBDD node);
 
 /**
- * Compute the complement of the MTBDD.
- * For Boolean MTBDDs, this means "not X".
+ * Obtain the complement of the MTBDD.
+ * This is only valid for Boolean MTBDDs or custom implementations that support it.
  */
-#define mtbdd_hascomp(dd) ((dd & mtbdd_complement) ? 1 : 0)
-#define mtbdd_comp(dd) (dd ^ mtbdd_complement)
-#define mtbdd_not(dd) (dd ^ mtbdd_complement)
+
+static inline int
+mtbdd_hascomp(MTBDD dd)
+{
+    return (dd & mtbdd_complement) ? 1 : 0;
+}
+
+static inline MTBDD
+mtbdd_comp(MTBDD dd)
+{
+    return dd ^ mtbdd_complement;
+}
+
+static inline MTBDD
+mtbdd_not(MTBDD dd)
+{
+    return dd ^ mtbdd_complement;
+}
 
 /**
- * Create terminals representing int64_t (type 0), double (type 1), or fraction (type 2) values
+ * Create an Integer leaf with the given value.
  */
 MTBDD mtbdd_int64(int64_t value);
+
+/**
+ * Create a Real leaf with the given value.
+ */
 MTBDD mtbdd_double(double value);
+
+/**
+ * Create a Fraction leaf with the given numerator and denominator.
+ */
 MTBDD mtbdd_fraction(int64_t numer, uint64_t denom);
 
 /**
- * Get the value of a terminal (for Integer, Real and Fraction terminals, types 0, 1 and 2)
+ * Obtain the value of an Integer leaf.
  */
 int64_t mtbdd_getint64(MTBDD terminal);
+
+/**
+ * Obtain the value of a Real leaf.
+ */
 double mtbdd_getdouble(MTBDD terminal);
-#define mtbdd_getnumer(terminal) ((int32_t)(mtbdd_getvalue(terminal)>>32))
-#define mtbdd_getdenom(terminal) ((uint32_t)(mtbdd_getvalue(terminal)&0xffffffff))
 
 /**
- * Create the conjunction of variables in arr.
- * I.e. arr[0] \and arr[1] \and ... \and arr[length-1]
+ * Obtain the numerator of a Fraction leaf.
+ */
+static inline int32_t
+mtbdd_getnumer(MTBDD terminal)
+{
+    return (int32_t)(mtbdd_getvalue(terminal)>>32);
+}
+
+/**
+ * Obtain the denominator of a Fraction leaf.
+ */
+static inline uint32_t
+mtbdd_getdenom(MTBDD terminal)
+{
+    return (uint32_t)(mtbdd_getvalue(terminal)&0xffffffff);
+}
+
+/**
+ * Create the Boolean MTBDD representing "if <var> then True else False"
  */
-MTBDD mtbdd_fromarray(uint32_t* arr, size_t length);
+MTBDD mtbdd_ithvar(uint32_t var);
+
+/**
+ * Functions to manipulate sets of MTBDD variables.
+ *
+ * A set of variables is represented by a cube/conjunction of (positive) variables.
+ */
+static inline MTBDD
+mtbdd_set_empty()
+{
+    return mtbdd_true;
+}
+
+static inline int
+mtbdd_set_isempty(MTBDD set)
+{
+    return (set == mtbdd_true) ? 1 : 0;
+}
+
+static inline uint32_t
+mtbdd_set_first(MTBDD set)
+{
+    return mtbdd_getvar(set);
+}
+
+static inline MTBDD
+mtbdd_set_next(MTBDD set)
+{
+    return mtbdd_gethigh(set);
+}
+
+/**
+ * Create a set of variables, represented as the conjunction of (positive) variables.
+ */
+MTBDD mtbdd_set_from_array(uint32_t* arr, size_t length);
+
+/**
+ * Write all variables in a variable set to the given array.
+ * The array must be sufficiently large.
+ */
+void mtbdd_set_to_array(MTBDD set, uint32_t *arr);
+
+/**
+ * Compute the number of variables in a given set of variables.
+ */
+size_t mtbdd_set_count(MTBDD set);
+
+/**
+ * Compute the union of <set1> and <set2>
+ */
+#define mtbdd_set_union(set1, set2) sylvan_and(set1, set2)
+
+/**
+ * Remove variables in <set2> from <set1>
+ */
+#define mtbdd_set_minus(set1, set2) CALL(mtbdd_set_minus, set1, set2)
+TASK_DECL_2(MTBDD, mtbdd_set_minus, MTBDD, MTBDD);
+
+/**
+ * Return 1 if <set> contains <var>, 0 otherwise.
+ */
+int mtbdd_set_contains(MTBDD set, uint32_t var);
+
+/**
+ * Add the variable <var> to <set>.
+ */
+MTBDD mtbdd_set_add(MTBDD set, uint32_t var);
+
+/**
+ * Remove the variable <var> from <set>.
+ */
+MTBDD mtbdd_set_remove(MTBDD set, uint32_t var);
+
+/**
+ * Sanity check if the given MTBDD is a conjunction of positive variables,
+ * and if all nodes are marked in the nodes table (detects violations after garbage collection).
+ */
+void mtbdd_test_isset(MTBDD set);
+
+/**
+ * Definitions for backwards compatibility
+ */
+#define mtbdd_fromarray mtbdd_set_from_array
+#define mtbdd_set_fromarray mtbdd_set_from_array
+#define mtbdd_set_toarray mtbdd_set_to_array
+#define mtbdd_set_addall mtbdd_set_union
+#define mtbdd_set_removeall mtbdd_set_minus
+#define mtbdd_set_in mtbdd_set_contains
 
 /**
  * Create a MTBDD cube representing the conjunction of variables in their positive or negative
@@ -139,7 +364,7 @@ MTBDD mtbdd_cube(MTBDD variables, uint8_t *cube, MTBDD terminal);
  * Does not support cube[idx]==3.
  */
 #define mtbdd_union_cube(mtbdd, variables, cube, terminal) CALL(mtbdd_union_cube, mtbdd, variables, cube, terminal)
-TASK_DECL_4(syBDD, mtbdd_union_cube, MTBDD, MTBDD, uint8_t*, MTBDD);
+TASK_DECL_4(SyBDD, mtbdd_union_cube, MTBDD, MTBDD, uint8_t*, MTBDD);
 
 /**
  * Count the number of satisfying assignments (minterms) leading to a non-false leaf
@@ -148,14 +373,20 @@ TASK_DECL_2(double, mtbdd_satcount, MTBDD, size_t);
 #define mtbdd_satcount(dd, nvars) CALL(mtbdd_satcount, dd, nvars)
 
 /**
- * Count the number of MTBDD leaves (excluding mtbdd_false and mtbdd_true) in the MTBDD
+ * Count the number of MTBDD leaves (excluding mtbdd_false and mtbdd_true) in the given <count> MTBDDs
  */
-size_t mtbdd_leafcount(MTBDD mtbdd);
+size_t mtbdd_leafcount_more(const MTBDD *mtbdds, size_t count);
+#define mtbdd_leafcount(dd) mtbdd_leafcount_more(&dd, 1)
 
 /**
- * Count the number of MTBDD nodes and terminals (excluding mtbdd_false and mtbdd_true) in a MTBDD
+ * Count the number of MTBDD nodes and terminals (excluding mtbdd_false and mtbdd_true) in the given <count> MTBDDs
  */
-size_t mtbdd_nodecount(MTBDD mtbdd);
+size_t mtbdd_nodecount_more(const MTBDD *mtbdds, size_t count);
+
+static inline size_t
+mtbdd_nodecount(const MTBDD dd) {
+    return mtbdd_nodecount_more(&dd, 1);
+}
 
 /**
  * Callback function types for binary ("dyadic") and unary ("monadic") operations.
@@ -192,7 +423,7 @@ TASK_DECL_3(MTBDD, mtbdd_uapply, MTBDD, mtbdd_uapply_op, size_t);
 /**
  * Callback function types for abstraction.
  * MTBDD mtbdd_abstract_op(MTBDD a, MTBDD b, int k).
- * The function is either called with k==0 (apply to two arguments) or k>0 (k skipped BDD variables)
+ * The function is either called with k==0 (apply to two arguments) or k>0 (k skipped SyBDD variables)
  * k == 0  =>  res := apply op to a and b
  * k  > 0  =>  res := apply op to op(a, a, k-1) and op(a, a, k-1)
  */
@@ -210,6 +441,12 @@ TASK_DECL_3(MTBDD, mtbdd_abstract, MTBDD, MTBDD, mtbdd_abstract_op);
  */
 TASK_DECL_2(MTBDD, mtbdd_op_negate, MTBDD, size_t);
 
+/**
+ * Unary opeation Complement.
+ * Supported domains: Integer, Real, Fraction
+ */
+TASK_DECL_2(MTBDD, mtbdd_op_cmpl, MTBDD, size_t);
+
 /**
  * Binary operation Plus (for MTBDDs of same type)
  * Only for MTBDDs where either all leaves are Boolean, or Integer, or Double.
@@ -254,9 +491,17 @@ TASK_DECL_3(MTBDD, mtbdd_abstract_op_max, MTBDD, MTBDD, int);
 
 /**
  * Compute -a
+ * (negation, where 0 stays 0, and x into -x)
  */
 #define mtbdd_negate(a) mtbdd_uapply(a, TASK(mtbdd_op_negate), 0)
 
+/**
+ * Compute ~a for partial MTBDDs.
+ * Does not negate Boolean True/False.
+ * (complement, where 0 is turned into 1, and non-0 into 0)
+ */
+#define mtbdd_cmpl(a) mtbdd_uapply(a, TASK(mtbdd_op_cmpl), 0)
+
 /**
  * Compute a + b
  */
@@ -304,7 +549,7 @@ TASK_DECL_3(MTBDD, mtbdd_abstract_op_max, MTBDD, MTBDD, int);
 
 /**
  * Compute IF <f> THEN <g> ELSE <h>.
- * <f> must be a Boolean MTBDD (or standard BDD).
+ * <f> must be a Boolean MTBDD (or standard SyBDD).
  */
 TASK_DECL_3(MTBDD, mtbdd_ite, MTBDD, MTBDD, MTBDD);
 #define mtbdd_ite(f, g, h) CALL(mtbdd_ite, f, g, h);
@@ -313,8 +558,15 @@ TASK_DECL_3(MTBDD, mtbdd_ite, MTBDD, MTBDD, MTBDD);
  * Multiply <a> and <b>, and abstract variables <vars> using summation.
  * This is similar to the "and_exists" operation in BDDs.
  */
-TASK_DECL_3(MTBDD, mtbdd_and_exists, MTBDD, MTBDD, MTBDD);
-#define mtbdd_and_exists(a, b, vars) CALL(mtbdd_and_exists, a, b, vars)
+TASK_DECL_3(MTBDD, mtbdd_and_abstract_plus, MTBDD, MTBDD, MTBDD);
+#define mtbdd_and_abstract_plus(a, b, vars) CALL(mtbdd_and_abstract_plus, a, b, vars)
+#define mtbdd_and_exists mtbdd_and_abstract_plus
+
+/**
+ * Multiply <a> and <b>, and abstract variables <vars> by taking the maximum.
+ */
+TASK_DECL_3(MTBDD, mtbdd_and_abstract_max, MTBDD, MTBDD, MTBDD);
+#define mtbdd_and_abstract_max(a, b, vars) CALL(mtbdd_and_abstract_max, a, b, vars)
 
 /**
  * Monad that converts double to a Boolean MTBDD, translate terminals >= value to 1 and to 0 otherwise;
@@ -429,6 +681,60 @@ typedef int (*mtbdd_enum_filter_cb)(MTBDD);
 MTBDD mtbdd_enum_first(MTBDD dd, MTBDD variables, uint8_t *arr, mtbdd_enum_filter_cb filter_cb);
 MTBDD mtbdd_enum_next(MTBDD dd, MTBDD variables, uint8_t *arr, mtbdd_enum_filter_cb filter_cb);
 
+/**
+ * Given an MTBDD <dd> and a cube of variables <variables> expected in <dd>,
+ * mtbdd_enum_all_first and mtbdd_enum_all_next enumerate all satisfying assignments in <dd> that lead
+ * to a non-False leaf.
+ *
+ * The functions return the leaf (or mtbdd_false if no new satisfying assignment is found) and encodes
+ * the assignment in the supplied array <arr>, 0 for False and 1 for True.
+ *
+ * The supplied array <arr> must be large enough for all variables in <variables>.
+ *
+ * Usage:
+ * MTBDD leaf = mtbdd_enum_first(dd, variables, arr, NULL);
+ * while (leaf != mtbdd_false) {
+ *     .... // do something with arr/leaf
+ *     leaf = mtbdd_enum_next(dd, variables, arr, NULL);
+ * }
+ *
+ * The callback is an optional function that returns 0 when the given terminal node should be skipped.
+ */
+MTBDD mtbdd_enum_all_first(MTBDD dd, MTBDD variables, uint8_t *arr, mtbdd_enum_filter_cb filter_cb);
+MTBDD mtbdd_enum_all_next(MTBDD dd, MTBDD variables, uint8_t *arr, mtbdd_enum_filter_cb filter_cb);
+
+/**
+ * Given a MTBDD <dd>, call <cb> with context <context> for every unique path in <dd> ending in leaf <leaf>.
+ *
+ * Usage:
+ * VOID_TASK_3(cb, mtbdd_enum_trace_t, trace, MTBDD, leaf, void*, context) { ... do something ... }
+ * mtbdd_enum_par(dd, cb, context);
+ */
+typedef struct mtbdd_enum_trace {
+    struct mtbdd_enum_trace *prev;
+    uint32_t var;
+    int val;  // 0 or 1
+} * mtbdd_enum_trace_t;
+
+LACE_TYPEDEF_CB(void, mtbdd_enum_cb, mtbdd_enum_trace_t, MTBDD, void*)
+VOID_TASK_DECL_3(mtbdd_enum_par, MTBDD, mtbdd_enum_cb, void*);
+#define mtbdd_enum_par(dd, cb, context) CALL(mtbdd_enum_par, dd, cb, context)
+
+/**
+ * Function composition after partial evaluation.
+ *
+ * Given a function F(X) = f, compute the composition F'(X) = g(f) for every assignment to X.
+ * All variables X in <vars> must appear before all variables in f and g(f).
+ *
+ * Usage:
+ * TASK_2(MTBDD, g, MTBDD, in) { ... return g of <in> ... }
+ * MTBDD x_vars = ...;  // the cube of variables x
+ * MTBDD result = mtbdd_eval_compose(dd, x_vars, TASK(g));
+ */
+LACE_TYPEDEF_CB(MTBDD, mtbdd_eval_compose_cb, MTBDD);
+TASK_DECL_3(MTBDD, mtbdd_eval_compose, MTBDD, MTBDD, mtbdd_eval_compose_cb);
+#define mtbdd_eval_compose(dd, vars, cb) CALL(mtbdd_eval_compose, dd, vars, cb)
+
 /**
  * For debugging.
  * Tests if all nodes in the MTBDD are correctly ``marked'' in the nodes table.
@@ -440,22 +746,232 @@ TASK_DECL_1(int, mtbdd_test_isvalid, MTBDD);
 #define mtbdd_test_isvalid(mtbdd) CALL(mtbdd_test_isvalid, mtbdd)
 
 /**
- * Write a DOT representation of a MTBDD
+ * Write a .dot representation of a given MTBDD
  * The callback function is required for custom terminals.
  */
-typedef void (*print_terminal_label_cb)(FILE *out, uint32_t type, uint64_t value);
-void mtbdd_fprintdot(FILE *out, MTBDD mtbdd, print_terminal_label_cb cb);
-#define mtbdd_printdot(mtbdd, cb) mtbdd_fprintdot(stdout, mtbdd, cb)
+void mtbdd_fprintdot(FILE *out, MTBDD mtbdd);
+#define mtbdd_printdot(mtbdd, cb) mtbdd_fprintdot(stdout, mtbdd)
+
+/**
+ * Write a .dot representation of a given MTBDD, but without complement edges.
+ */
+void mtbdd_fprintdot_nc(FILE *out, MTBDD mtbdd);
+#define mtbdd_printdot_nc(mtbdd, cb) mtbdd_fprintdot_nc(stdout, mtbdd)
+
+/**
+ * Write a text representation of a leaf to the given file.
+ */
+void mtbdd_fprint_leaf(FILE *out, MTBDD leaf);
+
+/**
+ * Write a text representation of a leaf to stdout.
+ */
+void mtbdd_print_leaf(MTBDD leaf);
+
+/**
+ * Obtain the textual representation of a leaf.
+ * The returned result is either equal to the given <buf> (if the results fits)
+ * or to a newly allocated array (with malloc).
+ */
+char *mtbdd_leaf_to_str(MTBDD leaf, char *buf, size_t buflen);
+
+/**
+ * Some debugging functions that generate SHA2 hashes of MTBDDs.
+ * They are independent of where nodes are located in hash tables.
+ * Note that they are not "perfect", but they can be useful to run easy sanity checks.
+ */
+
+/**
+ * Print SHA2 hash to stdout.
+ */
+void mtbdd_printsha(MTBDD dd);
+
+/**
+ * Print SHA2 hash to given file.
+ */
+void mtbdd_fprintsha(FILE *f, MTBDD dd);
+
+/**
+ * Obtain SHA2 hash; target array must be at least 65 bytes long.
+ */
+void mtbdd_getsha(MTBDD dd, char *target);
+
+/**
+ * Visitor functionality for MTBDDs.
+ * Visits internal nodes and leafs.
+ */
+
+/**
+ * pre_cb callback: given input MTBDD and context,
+ *                  return whether to visit children (if not leaf)
+ * post_cb callback: given input MTBDD and context
+ */
+LACE_TYPEDEF_CB(int, mtbdd_visit_pre_cb, MTBDD, void*);
+LACE_TYPEDEF_CB(void, mtbdd_visit_post_cb, MTBDD, void*);
+
+/**
+ * Sequential visit operation
+ */
+VOID_TASK_DECL_4(mtbdd_visit_seq, MTBDD, mtbdd_visit_pre_cb, mtbdd_visit_post_cb, void*);
+#define mtbdd_visit_seq(...) CALL(mtbdd_visit_seq, __VA_ARGS__)
+
+/**
+ * Parallel visit operation
+ */
+VOID_TASK_DECL_4(mtbdd_visit_par, MTBDD, mtbdd_visit_pre_cb, mtbdd_visit_post_cb, void*);
+#define mtbdd_visit_par(...) CALL(mtbdd_visit_par, __VA_ARGS__)
+
+/**
+ * Writing MTBDDs to file.
+ *
+ * Every node that is to be written is assigned a number, starting from 1,
+ * such that reading the result in the future can be done in one pass.
+ *
+ * We use a skiplist to store the assignment.
+ *
+ * The functions mtbdd_writer_tobinary and mtbdd_writer_totext can be used to
+ * store an array of MTBDDs to binary format or text format.
+ *
+ * One could also do the procedure manually instead.
+ * - call mtbdd_writer_start to allocate the skiplist.
+ * - call mtbdd_writer_add to add a given MTBDD to the skiplist
+ * - call mtbdd_writer_writebinary to write all added nodes to a file
+ * - OR:  mtbdd_writer_writetext to write all added nodes in text format
+ * - call mtbdd_writer_get to obtain the MTBDD identifier as stored in the skiplist
+ * - call mtbdd_writer_end to free the skiplist
+ */
+
+/**
+ * Write <count> decision diagrams given in <dds> in internal binary form to <file>.
+ *
+ * The internal binary format is as follows, to store <count> decision diagrams...
+ * uint64_t: nodecount -- number of nodes
+ * <nodecount> times uint128_t: each leaf/node
+ * uint64_t: count -- number of stored decision diagrams
+ * <count> times uint64_t: each stored decision diagram
+ */
+VOID_TASK_DECL_3(mtbdd_writer_tobinary, FILE *, MTBDD *, int);
+#define mtbdd_writer_tobinary(file, dds, count) CALL(mtbdd_writer_tobinary, file, dds, count)
+
+/**
+ * Write <count> decision diagrams given in <dds> in ASCII form to <file>.
+ * Also supports custom leaves using the leaf_to_str callback.
+ *
+ * The text format writes in the same order as the binary format, except...
+ * [
+ *   node(id, var, low, high), -- for a normal node (no complement on high)
+ *   node(id, var, low, ~high), -- for a normal node (complement on high)
+ *   leaf(id, type, "value"), -- for a leaf (with value between "")
+ * ],[dd1, dd2, dd3, ...,] -- and each the stored decision diagram.
+ */
+
+VOID_TASK_DECL_3(mtbdd_writer_totext, FILE *, MTBDD *, int);
+#define mtbdd_writer_totext(file, dds, count) CALL(mtbdd_writer_totext, file, dds, count)
+
+/**
+ * Skeleton typedef for the skiplist
+ */
+typedef struct sylvan_skiplist *sylvan_skiplist_t;
+
+/**
+ * Allocate a skiplist for writing an MTBDD.
+ */
+sylvan_skiplist_t mtbdd_writer_start(void);
+
+/**
+ * Add the given MTBDD to the skiplist.
+ */
+VOID_TASK_DECL_2(mtbdd_writer_add, sylvan_skiplist_t, MTBDD);
+#define mtbdd_writer_add(sl, dd) CALL(mtbdd_writer_add, sl, dd)
+
+/**
+ * Write all assigned MTBDD nodes in binary format to the file.
+ */
+void mtbdd_writer_writebinary(FILE *out, sylvan_skiplist_t sl);
+
+/**
+ * Retrieve the identifier of the given stored MTBDD.
+ * This is useful if you want to be able to retrieve the stored MTBDD later.
+ */
+uint64_t mtbdd_writer_get(sylvan_skiplist_t sl, MTBDD dd);
+
+/**
+ * Free the allocated skiplist.
+ */
+void mtbdd_writer_end(sylvan_skiplist_t sl);
+
+/**
+ * Reading MTBDDs from file.
+ *
+ * The function mtbdd_reader_frombinary is basically the reverse of mtbdd_writer_tobinary.
+ *
+ * One can also perform the procedure manually.
+ * - call mtbdd_reader_readbinary to read the nodes from file
+ * - call mtbdd_reader_get to obtain the MTBDD for the given identifier as stored in the file.
+ * - call mtbdd_reader_end to free the array returned by mtbdd_reader_readbinary
+ *
+ * Returns 0 if successful, -1 otherwise.
+ */
+
+/*
+ * Read <count> decision diagrams to <dds> from <file> in internal binary form.
+ */
+TASK_DECL_3(int, mtbdd_reader_frombinary, FILE*, MTBDD*, int);
+#define mtbdd_reader_frombinary(file, dds, count) CALL(mtbdd_reader_frombinary, file, dds, count)
+
+/**
+ * Reading a file earlier written with mtbdd_writer_writebinary
+ * Returns an array with the conversion from stored identifier to MTBDD
+ * This array is allocated with malloc and must be freed afterwards.
+ * Returns NULL if there was an error.
+ */
+
+TASK_DECL_1(uint64_t*, mtbdd_reader_readbinary, FILE*);
+#define mtbdd_reader_readbinary(file) CALL(mtbdd_reader_readbinary, file)
+
+/**
+ * Retrieve the MTBDD of the given stored identifier.
+ */
+MTBDD mtbdd_reader_get(uint64_t* arr, uint64_t identifier);
+
+/**
+ * Free the allocated translation array
+ */
+void mtbdd_reader_end(uint64_t *arr);
 
 /**
  * MTBDDMAP, maps uint32_t variables to MTBDDs.
- * A MTBDDMAP node has variable level, low edge going to the next MTBDDMAP, high edge to the mapped MTBDD
+ * A MTBDDMAP node has variable level, low edge going to the next MTBDDMAP, high edge to the mapped MTBDD.
  */
-#define mtbdd_map_empty() mtbdd_false
-#define mtbdd_map_isempty(map) (map == mtbdd_false ? 1 : 0)
-#define mtbdd_map_key(map) mtbdd_getvar(map)
-#define mtbdd_map_value(map) mtbdd_gethigh(map)
-#define mtbdd_map_next(map) mtbdd_getlow(map)
+static inline MTBDD
+mtbdd_map_empty()
+{
+    return mtbdd_false;
+}
+
+static inline int
+mtbdd_map_isempty(MTBDD map)
+{
+    return (map == mtbdd_false) ? 1 : 0;
+}
+
+static inline uint32_t
+mtbdd_map_key(MTBDD map)
+{
+    return mtbdd_getvar(map);
+}
+
+static inline MTBDD
+mtbdd_map_value(MTBDD map)
+{
+    return mtbdd_gethigh(map);
+}
+
+static inline MTBDD
+mtbdd_map_next(MTBDD map)
+{
+    return mtbdd_getlow(map);
+}
 
 /**
  * Return 1 if the map contains the key, 0 otherwise.
@@ -475,7 +991,8 @@ MTBDDMAP mtbdd_map_add(MTBDDMAP map, uint32_t key, MTBDD value);
 /**
  * Add all values from map2 to map1, overwrites if key already in map1.
  */
-MTBDDMAP mtbdd_map_addall(MTBDDMAP map1, MTBDDMAP map2);
+MTBDDMAP mtbdd_map_update(MTBDDMAP map1, MTBDDMAP map2);
+#define mtbdd_map_addall mtbdd_map_update
 
 /**
  * Remove the key <key> from the map and return the result
@@ -487,26 +1004,6 @@ MTBDDMAP mtbdd_map_remove(MTBDDMAP map, uint32_t key);
  */
 MTBDDMAP mtbdd_map_removeall(MTBDDMAP map, MTBDD variables);
 
-/**
- * Custom node types
- * Overrides standard hash/equality/notify_on_dead behavior
- * hash(value, seed) return hash version
- * equals(value1, value2) return 1 if equal, 0 if not equal
- * create(&value) replace value by new value for object allocation
- * destroy(value)
- * NOTE: equals(value1, value2) must imply: hash(value1, seed) == hash(value2,seed)
- * NOTE: new value of create must imply: equals(old, new)
- */
-typedef uint64_t (*mtbdd_hash_cb)(uint64_t, uint64_t);
-typedef int (*mtbdd_equals_cb)(uint64_t, uint64_t);
-typedef void (*mtbdd_create_cb)(uint64_t*);
-typedef void (*mtbdd_destroy_cb)(uint64_t);
-
-/**
- * Registry callback handlers for <type>.
- */
-uint32_t mtbdd_register_custom_leaf(mtbdd_hash_cb hash_cb, mtbdd_equals_cb equals_cb, mtbdd_create_cb create_cb, mtbdd_destroy_cb destroy_cb);
-
 /**
  * Garbage collection
  * Sylvan supplies two default methods to handle references to nodes, but the user
@@ -522,82 +1019,86 @@ VOID_TASK_DECL_1(mtbdd_gc_mark_rec, MTBDD);
 #define mtbdd_gc_mark_rec(mtbdd) CALL(mtbdd_gc_mark_rec, mtbdd)
 
 /**
- * Default external referencing. During garbage collection, MTBDDs marked with mtbdd_ref will
- * be kept in the forest.
- * It is recommended to prefer mtbdd_protect and mtbdd_unprotect.
+ * Infrastructure for external references using a hash table.
+ * Two hash tables store external references: a pointers table and a values table.
+ * The pointers table stores pointers to MTBDD variables, manipulated with protect and unprotect.
+ * The values table stores MTBDDs, manipulated with ref and deref.
+ * We strongly recommend using the pointers table whenever possible.
  */
-MTBDD mtbdd_ref(MTBDD a);
-void mtbdd_deref(MTBDD a);
-size_t mtbdd_count_refs();
 
 /**
- * Default external pointer referencing. During garbage collection, the pointers are followed and the MTBDD
- * that they refer to are kept in the forest.
+ * Store the pointer <ptr> in the pointers table.
  */
 void mtbdd_protect(MTBDD* ptr);
+
+/**
+ * Delete the pointer <ptr> from the pointers table.
+ */
 void mtbdd_unprotect(MTBDD* ptr);
-size_t mtbdd_count_protected();
 
 /**
- * If sylvan_set_ondead is set to a callback, then this function marks MTBDDs (terminals).
- * When they are dead after the mark phase in garbage collection, the callback is called for marked MTBDDs.
- * The ondead callback can either perform cleanup or resurrect dead terminals.
+ * Compute the number of pointers in the pointers table.
  */
-#define mtbdd_notify_ondead(dd) llmsset_notify_ondead(nodes, dd&~mtbdd_complement)
+size_t mtbdd_count_protected(void);
 
 /**
- * Infrastructure for internal references (per-thread, e.g. during MTBDD operations)
- * Use mtbdd_refs_push and mtbdd_refs_pop to put MTBDDs on a thread-local reference stack.
- * Use mtbdd_refs_spawn and mtbdd_refs_sync around SPAWN and SYNC operations when the result
- * of the spawned Task is a MTBDD that must be kept during garbage collection.
+ * Store the MTBDD <dd> in the values table.
  */
-typedef struct mtbdd_refs_internal
-{
-    size_t r_size, r_count;
-    size_t s_size, s_count;
-    MTBDD *results;
-    Task **spawns;
-} *mtbdd_refs_internal_t;
+MTBDD mtbdd_ref(MTBDD dd);
 
-extern DECLARE_THREAD_LOCAL(mtbdd_refs_key, mtbdd_refs_internal_t);
+/**
+ * Delete the MTBDD <dd> from the values table.
+ */
+void mtbdd_deref(MTBDD dd);
 
-static inline MTBDD
-mtbdd_refs_push(MTBDD mtbdd)
-{
-    LOCALIZE_THREAD_LOCAL(mtbdd_refs_key, mtbdd_refs_internal_t);
-    if (mtbdd_refs_key->r_count >= mtbdd_refs_key->r_size) {
-        mtbdd_refs_key->r_size *= 2;
-        mtbdd_refs_key->results = (MTBDD*)realloc(mtbdd_refs_key->results, sizeof(MTBDD) * mtbdd_refs_key->r_size);
-    }
-    mtbdd_refs_key->results[mtbdd_refs_key->r_count++] = mtbdd;
-    return mtbdd;
-}
+/**
+ * Compute the number of values in the values table.
+ */
+size_t mtbdd_count_refs(void);
 
-static inline void
-mtbdd_refs_pop(int amount)
-{
-    LOCALIZE_THREAD_LOCAL(mtbdd_refs_key, mtbdd_refs_internal_t);
-    mtbdd_refs_key->r_count-=amount;
-}
+/**
+ * Infrastructure for internal references.
+ * Every thread has its own reference stacks. There are three stacks: pointer, values, tasks stack.
+ * The pointers stack stores pointers to MTBDD variables, manipulated with pushptr and popptr.
+ * The values stack stores MTBDDs, manipulated with push and pop.
+ * The tasks stack stores Lace tasks (that return MTBDDs), manipulated with spawn and sync.
+ *
+ * It is recommended to use the pointers stack for local variables and the tasks stack for tasks.
+ */
 
-static inline void
-mtbdd_refs_spawn(Task *t)
-{
-    LOCALIZE_THREAD_LOCAL(mtbdd_refs_key, mtbdd_refs_internal_t);
-    if (mtbdd_refs_key->s_count >= mtbdd_refs_key->s_size) {
-        mtbdd_refs_key->s_size *= 2;
-        mtbdd_refs_key->spawns = (Task**)realloc(mtbdd_refs_key->spawns, sizeof(Task*) * mtbdd_refs_key->s_size);
-    }
-    mtbdd_refs_key->spawns[mtbdd_refs_key->s_count++] = t;
-}
+/**
+ * Push a MTBDD variable to the pointer reference stack.
+ * During garbage collection the variable will be inspected and the contents will be marked.
+ */
+void mtbdd_refs_pushptr(const MTBDD *ptr);
 
-static inline MTBDD
-mtbdd_refs_sync(MTBDD result)
-{
-    LOCALIZE_THREAD_LOCAL(mtbdd_refs_key, mtbdd_refs_internal_t);
-    mtbdd_refs_key->s_count--;
-    return result;
-}
+/**
+ * Pop the last <amount> MTBDD variables from the pointer reference stack.
+ */
+void mtbdd_refs_popptr(size_t amount);
+
+/**
+ * Push an MTBDD to the values reference stack.
+ * During garbage collection the references MTBDD will be marked.
+ */
+MTBDD mtbdd_refs_push(MTBDD mtbdd);
+
+/**
+ * Pop the last <amount> MTBDDs from the values reference stack.
+ */
+void mtbdd_refs_pop(long amount);
+
+/**
+ * Push a Task that returns an MTBDD to the tasks reference stack.
+ * Usage: mtbdd_refs_spawn(SPAWN(function, ...));
+ */
+void mtbdd_refs_spawn(Task *t);
+
+/**
+ * Pop a Task from the task reference stack.
+ * Usage: MTBDD result = mtbdd_refs_sync(SYNC(function));
+ */
+MTBDD mtbdd_refs_sync(MTBDD mtbdd);
 
 #ifdef __cplusplus
 }
diff --git a/sylvan_mtbdd_int.h b/sylvan_mtbdd_int.h
old mode 100644
new mode 100755
index 940250b9a83eee2b20923ebfaeb78d28060647e0..36efe1b2189efd74b1f6fce1242e49f0c1829dac
--- a/sylvan_mtbdd_int.h
+++ b/sylvan_mtbdd_int.h
@@ -1,5 +1,6 @@
 /*
- * Copyright 2011-2015 Formal Methods and Tools, University of Twente
+ * 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.
@@ -14,6 +15,8 @@
  * limitations under the License.
  */
 
+/* Do not include this file directly. Instead, include sylvan_int.h */
+
 /**
  * Internals for MTBDDs
  */
@@ -22,107 +25,177 @@
 #define SYLVAN_MTBDD_INT_H
 
 /**
- * MTBDD node structure
+ * SyBDD/MTBDD node structure
  */
 typedef struct __attribute__((packed)) mtbddnode {
     uint64_t a, b;
 } * mtbddnode_t; // 16 bytes
 
-#define GETNODE(mtbdd) ((mtbddnode_t)llmsset_index_to_ptr(nodes, mtbdd&0x000000ffffffffff))
+static inline mtbddnode_t
+MTBDD_GETNODE(MTBDD dd)
+{
+    return (mtbddnode_t)llmsset_index_to_ptr(nodes, dd&0x000000ffffffffff);
+}
 
 /**
  * Complement handling macros
  */
-#define MTBDD_HASMARK(s)              (s&mtbdd_complement?1:0)
-#define MTBDD_TOGGLEMARK(s)           (s^mtbdd_complement)
-#define MTBDD_STRIPMARK(s)            (s&~mtbdd_complement)
-#define MTBDD_TRANSFERMARK(from, to)  (to ^ (from & mtbdd_complement))
-// Equal under mark
-#define MTBDD_EQUALM(a, b)            ((((a)^(b))&(~mtbdd_complement))==0)
+
+static inline int
+MTBDD_HASMARK(MTBDD dd)
+{
+    return (dd & mtbdd_complement) ? 1 : 0;
+}
+
+static inline MTBDD
+MTBDD_TOGGLEMARK(MTBDD dd)
+{
+    return dd ^ mtbdd_complement;
+}
+
+static inline MTBDD
+MTBDD_STRIPMARK(MTBDD dd)
+{
+    return dd & (~mtbdd_complement);
+}
+
+static inline MTBDD
+MTBDD_TRANSFERMARK(MTBDD from, MTBDD to)
+{
+    return (to ^ (from & mtbdd_complement));
+}
+
+/**
+ * Are two MTBDDs equal modulo mark?
+ */
+static inline int
+MTBDD_EQUALM(MTBDD a, MTBDD b)
+{
+    return ((a^b)&(~mtbdd_complement)) ? 0 : 1;
+}
 
 // Leaf: a = L=1, M, type; b = value
 // Node: a = L=0, C, M, high; b = variable, low
 // Only complement edge on "high"
 
-static inline int
+static inline int __attribute__((unused))
 mtbddnode_isleaf(mtbddnode_t n)
 {
     return n->a & 0x4000000000000000 ? 1 : 0;
 }
 
-static inline uint32_t
+static inline uint32_t __attribute__((unused))
 mtbddnode_gettype(mtbddnode_t n)
 {
     return n->a & 0x00000000ffffffff;
 }
 
-static inline uint64_t
+static inline uint64_t __attribute__((unused))
 mtbddnode_getvalue(mtbddnode_t n)
 {
     return n->b;
 }
 
-static inline int
+static inline int __attribute__((unused))
 mtbddnode_getcomp(mtbddnode_t n)
 {
     return n->a & 0x8000000000000000 ? 1 : 0;
 }
 
-static inline uint64_t
+static inline uint64_t __attribute__((unused))
 mtbddnode_getlow(mtbddnode_t n)
 {
     return n->b & 0x000000ffffffffff; // 40 bits
 }
 
-static inline uint64_t
+static inline uint64_t __attribute__((unused))
 mtbddnode_gethigh(mtbddnode_t n)
 {
     return n->a & 0x800000ffffffffff; // 40 bits plus high bit of first
 }
 
-static inline uint32_t
+static inline uint32_t __attribute__((unused))
 mtbddnode_getvariable(mtbddnode_t n)
 {
     return (uint32_t)(n->b >> 40);
 }
 
-static inline int
+static inline int __attribute__((unused))
 mtbddnode_getmark(mtbddnode_t n)
 {
     return n->a & 0x2000000000000000 ? 1 : 0;
 }
 
-static inline void
+static inline void __attribute__((unused))
 mtbddnode_setmark(mtbddnode_t n, int mark)
 {
     if (mark) n->a |= 0x2000000000000000;
     else n->a &= 0xdfffffffffffffff;
 }
 
-static inline void
+static inline void __attribute__((unused))
 mtbddnode_makeleaf(mtbddnode_t n, uint32_t type, uint64_t value)
 {
     n->a = 0x4000000000000000 | (uint64_t)type;
     n->b = value;
 }
 
-static inline void
+static inline void __attribute__((unused))
 mtbddnode_makenode(mtbddnode_t n, uint32_t var, uint64_t low, uint64_t high)
 {
     n->a = high;
     n->b = ((uint64_t)var)<<40 | low;
 }
 
-static MTBDD
-node_getlow(MTBDD mtbdd, mtbddnode_t node)
+static inline void __attribute__((unused))
+mtbddnode_makemapnode(mtbddnode_t n, uint32_t var, uint64_t low, uint64_t high)
+{
+    n->a = high | 0x1000000000000000;
+    n->b = ((uint64_t)var)<<40 | low;
+}
+
+static inline int __attribute__((unused))
+mtbddnode_ismapnode(mtbddnode_t n)
+{
+    return n->a & 0x1000000000000000 ? 1 : 0;
+}
+
+static MTBDD __attribute__((unused))
+mtbddnode_followlow(MTBDD mtbdd, mtbddnode_t node)
 {
     return MTBDD_TRANSFERMARK(mtbdd, mtbddnode_getlow(node));
 }
 
-static MTBDD
-node_gethigh(MTBDD mtbdd, mtbddnode_t node)
+static MTBDD __attribute__((unused))
+mtbddnode_followhigh(MTBDD mtbdd, mtbddnode_t node)
 {
     return MTBDD_TRANSFERMARK(mtbdd, mtbddnode_gethigh(node));
 }
 
+/**
+ * Compatibility
+ */
+
+#define node_getlow mtbddnode_followlow
+#define node_gethigh mtbddnode_followhigh
+
+#define BDD_HASMARK MTBDD_HASMARK
+#define BDD_TOGGLEMARK MTBDD_TOGGLEMARK
+#define BDD_STRIPMARK MTBDD_STRIPMARK
+#define BDD_TRANSFERMARK MTBDD_TRANSFERMARK
+#define BDD_EQUALM MTBDD_EQUALM
+#define bddnode mtbddnode
+#define bddnode_t mtbddnode_t
+#define bddnode_getcomp mtbddnode_getcomp
+#define bddnode_getlow mtbddnode_getlow
+#define bddnode_gethigh mtbddnode_gethigh
+#define bddnode_getvariable mtbddnode_getvariable
+#define bddnode_getmark mtbddnode_getmark
+#define bddnode_setmark mtbddnode_setmark
+#define bddnode_makenode mtbddnode_makenode
+#define bddnode_makemapnode mtbddnode_makemapnode
+#define bddnode_ismapnode mtbddnode_ismapnode
+#define node_low node_getlow
+#define node_high node_gethigh
+
 #endif
diff --git a/sylvan_obj.cpp b/sylvan_obj.cpp
old mode 100644
new mode 100755
index 5d7d8dd53e92890b44b407726a6befae84a995ab..7f99f629a7df3b05a538bc547fd8db4606c69764
--- a/sylvan_obj.cpp
+++ b/sylvan_obj.cpp
@@ -1,5 +1,6 @@
 /*
- * Copyright 2011-2015 Formal Methods and Tools, University of Twente
+ * Copyright 2011-2016 Formal Methods and Tools, University of Twente
+ * Copyright 2016 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.
@@ -44,16 +45,16 @@ Bdd::operator=(const Bdd& right)
 int
 Bdd::operator<=(const Bdd& other) const
 {
-    // TODO: better implementation, since we are not interested in the BDD result
+    // TODO: better implementation, since we are not interested in the SyBDD result
     LACE_ME;
-    syBDD r = sylvan_ite(this->bdd, sylvan_not(other.bdd), sylvan_false);
+    SyBDD r = sylvan_ite(this->bdd, sylvan_not(other.bdd), sylvan_false);
     return r == sylvan_false;
 }
 
 int
 Bdd::operator>=(const Bdd& other) const
 {
-    // TODO: better implementation, since we are not interested in the BDD result
+    // TODO: better implementation, since we are not interested in the SyBDD result
     return other <= *this;
 }
 
@@ -244,9 +245,9 @@ Bdd::Xnor(const Bdd &g) const
 int
 Bdd::Leq(const Bdd &g) const
 {
-    // TODO: better implementation, since we are not interested in the BDD result
+    // TODO: better implementation, since we are not interested in the SyBDD result
     LACE_ME;
-    syBDD r = sylvan_ite(bdd, sylvan_not(g.bdd), sylvan_false);
+    SyBDD r = sylvan_ite(bdd, sylvan_not(g.bdd), sylvan_false);
     return r == sylvan_false;
 }
 
@@ -313,7 +314,7 @@ Bdd::Support() const
     return sylvan_support(bdd);
 }
 
-syBDD
+SyBDD
 Bdd::GetBDD() const
 {
     return bdd;
@@ -366,13 +367,13 @@ Bdd::PickOneCube(const BddSet &variables) const
 {
     std::vector<bool> result = std::vector<bool>();
 
-    syBDD bdd = this->bdd;
-    syBDD vars = variables.set.bdd;
+    SyBDD bdd = this->bdd;
+    SyBDD vars = variables.set.bdd;
 
     if (bdd == sylvan_false) return result;
 
     for (; !sylvan_set_isempty(vars); vars = sylvan_set_next(vars)) {
-        uint32_t var = sylvan_set_var(vars);
+        uint32_t var = sylvan_set_first(vars);
         if (bdd == sylvan_true) {
             // pick 0
             result.push_back(false);
@@ -438,7 +439,7 @@ Bdd::VectorCube(const std::vector<Bdd> variables)
 Bdd
 Bdd::VariablesCube(std::vector<uint32_t> variables)
 {
-    syBDD result = sylvan_true;
+    SyBDD result = sylvan_true;
     for (int i=variables.size()-1; i>=0; i--) {
         result = sylvan_makenode(variables[i], sylvan_false, result);
     }
@@ -1015,13 +1016,26 @@ MtbddMap::isEmpty()
 void
 Sylvan::initPackage(size_t initialTableSize, size_t maxTableSize, size_t initialCacheSize, size_t maxCacheSize)
 {
-    sylvan_init_package(initialTableSize, maxTableSize, initialCacheSize, maxCacheSize);
+    sylvan_set_sizes(initialTableSize, maxTableSize, initialCacheSize, maxCacheSize);
+    sylvan_init_package();
 }
 
 void
-Sylvan::initBdd(int granularity)
+Sylvan::setGranularity(int granularity)
 {
-    sylvan_init_bdd(granularity);
+    sylvan_set_granularity(granularity);
+}
+
+int
+Sylvan::getGranularity()
+{
+    return sylvan_get_granularity();
+}
+
+void
+Sylvan::initBdd()
+{
+    sylvan_init_bdd();
 }
 
 void
diff --git a/sylvan_obj.hpp b/sylvan_obj.hpp
old mode 100644
new mode 100755
index 1ea0c2e49d3b72c8fecc4f8e9eb5fc2686bf425b..c4492f876673ab7904708b05cfbbadadbc8d24e7
--- a/sylvan_obj.hpp
+++ b/sylvan_obj.hpp
@@ -36,7 +36,7 @@ class Bdd {
 
 public:
     Bdd() { bdd = sylvan_false; sylvan_protect(&bdd); }
-    Bdd(const syBDD from) : bdd(from) { sylvan_protect(&bdd); }
+    Bdd(const SyBDD from) : bdd(from) { sylvan_protect(&bdd); }
     Bdd(const Bdd &from) : bdd(from.bdd) { sylvan_protect(&bdd); }
     Bdd(const uint32_t var) { bdd = sylvan_ithvar(var); sylvan_protect(&bdd); }
     ~Bdd() { sylvan_unprotect(&bdd); }
@@ -217,7 +217,7 @@ public:
     Bdd RelNext(const Bdd& relation, const BddSet& cube) const;
 
     /**
-     * @brief Computes the transitive closure by traversing the BDD recursively.
+     * @brief Computes the transitive closure by traversing the SyBDD recursively.
      * See Y. Matsunaga, P. C. McGeer, R. K. Brayton
      *     On Computing the Transitive Closre of a State Transition Relation
      *     30th ACM Design Automation Conference, 1993.
@@ -230,12 +230,12 @@ public:
     Bdd Constrain(const Bdd &c) const;
 
     /**
-     * @brief Computes the BDD restrict according to Coudert and Madre's algorithm (ICCAD90).
+     * @brief Computes the SyBDD restrict according to Coudert and Madre's algorithm (ICCAD90).
      */
     Bdd Restrict(const Bdd &c) const;
 
     /**
-     * @brief Functional composition. Whenever a variable v in the map m is found in the BDD,
+     * @brief Functional composition. Whenever a variable v in the map m is found in the SyBDD,
      *        it is substituted by the associated function.
      * You can also use this function to implement variable reordering.
      */
@@ -252,9 +252,9 @@ public:
     Bdd Support() const;
 
     /**
-     * @brief Gets the BDD of this Bdd (for C functions)
+     * @brief Gets the SyBDD of this Bdd (for C functions)
      */
-    syBDD GetBDD() const;
+    SyBDD GetBDD() const;
 
     /**
      * @brief Writes .dot file of this Bdd. Not thread-safe!
@@ -264,7 +264,7 @@ public:
     /**
      * @brief Gets a SHA2 hash that describes the structure of this Bdd.
      * @param string a character array of at least 65 characters (includes zero-termination)
-     * This hash is 64 characters long and is independent of the memory locations of BDD nodes.
+     * This hash is 64 characters long and is independent of the memory locations of SyBDD nodes.
      */
     void GetShaHash(char *string) const;
 
@@ -325,7 +325,7 @@ public:
     size_t NodeCount() const;
 
 private:
-    syBDD bdd;
+    SyBDD bdd;
 };
 
 class BddSet
@@ -341,7 +341,7 @@ public:
     BddSet() : set(Bdd::bddOne()) {}
 
     /**
-     * @brief Wrap the BDD cube <other> in a set.
+     * @brief Wrap the SyBDD cube <other> in a set.
      */
     BddSet(const Bdd &other) : set(other) {}
 
@@ -481,8 +481,8 @@ public:
 class BddMap
 {
     friend class Bdd;
-    syBDD bdd;
-    BddMap(const syBDD from) : bdd(from) { sylvan_protect(&bdd); }
+    SyBDD bdd;
+    BddMap(const SyBDD from) : bdd(from) { sylvan_protect(&bdd); }
     BddMap(const Bdd &from) : bdd(from.bdd) { sylvan_protect(&bdd); }
 public:
     BddMap() : bdd(sylvan_map_empty()) { sylvan_protect(&bdd); }
@@ -826,12 +826,22 @@ public:
     static void initPackage(size_t initialTableSize, size_t maxTableSize, size_t initialCacheSize, size_t maxCacheSize);
 
     /**
-     * @brief Initializes the BDD module of the Sylvan framework.
+     * @brief Set the granularity for the SyBDD operations.
      * @param granularity determins operation cache behavior; for higher values (2+) it will use the operation cache less often.
      * Values of 3-7 may result in better performance, since occasionally not using the operation cache is fine in practice.
-     * A granularity of 1 means that every BDD operation will be cached at every variable level.
+     * A granularity of 1 means that every SyBDD operation will be cached at every variable level.
      */
-    static void initBdd(int granularity);
+    static void setGranularity(int granularity);
+
+    /**
+     * @brief Retrieve the granularity for the SyBDD operations.
+     */
+    static int getGranularity();
+
+    /**
+     * @brief Initializes the SyBDD module of the Sylvan framework.
+     */
+    static void initBdd();
 
     /**
      * @brief Initializes the MTBDD module of the Sylvan framework.
diff --git a/sylvan_refs.c b/sylvan_refs.c
new file mode 100755
index 0000000000000000000000000000000000000000..968721370c6460cb48eb601afd870d4686bd31db
--- /dev/null
+++ b/sylvan_refs.c
@@ -0,0 +1,594 @@
+/*
+ * 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.h>
+#include <sylvan_refs.h>
+
+#include <errno.h>  // for errno
+#include <string.h> // for strerror
+#include <sys/mman.h> // for mmap
+
+#ifndef compiler_barrier
+#define compiler_barrier() { asm volatile("" ::: "memory"); }
+#endif
+
+#ifndef cas
+#define cas(ptr, old, new) (__sync_bool_compare_and_swap((ptr),(old),(new)))
+#endif
+
+/**
+ * Implementation of external references
+ * Based on a hash table for 40-bit non-null values, linear probing
+ * Use tombstones for deleting, higher bits for reference count
+ */
+static const uint64_t refs_ts = 0x7fffffffffffffff; // tombstone
+
+/* FNV-1a 64-bit hash */
+static inline uint64_t
+fnv_hash(uint64_t a)
+{
+    const uint64_t prime = 1099511628211;
+    uint64_t hash = 14695981039346656037LLU;
+    hash = (hash ^ a) * prime;
+    hash = (hash ^ ((a << 25) | (a >> 39))) * prime;
+    return hash ^ (hash >> 32);
+}
+
+// Count number of unique entries (not number of references)
+size_t
+refs_count(refs_table_t *tbl)
+{
+    size_t count = 0;
+    uint64_t *bucket = tbl->refs_table;
+    uint64_t * const end = bucket + tbl->refs_size;
+    while (bucket != end) {
+        if (*bucket != 0 && *bucket != refs_ts) count++;
+        bucket++;
+    }
+    return count;
+}
+
+static inline void
+refs_rehash(refs_table_t *tbl, uint64_t v)
+{
+    if (v == 0) return; // do not rehash empty value
+    if (v == refs_ts) return; // do not rehash tombstone
+
+    volatile uint64_t *bucket = tbl->refs_table + (fnv_hash(v & 0x000000ffffffffff) % tbl->refs_size);
+    uint64_t * const end = tbl->refs_table + tbl->refs_size;
+
+    int i = 128; // try 128 times linear probing
+    while (i--) {
+        if (*bucket == 0) { if (cas(bucket, 0, v)) return; }
+        if (++bucket == end) bucket = tbl->refs_table;
+    }
+
+    // assert(0); // impossible!
+}
+
+/**
+ * Called internally to assist resize operations
+ * Returns 1 for retry, 0 for done
+ */
+static int
+refs_resize_help(refs_table_t *tbl)
+{
+    if (0 == (tbl->refs_control & 0xf0000000)) return 0; // no resize in progress (anymore)
+    if (tbl->refs_control & 0x80000000) return 1; // still waiting for preparation
+
+    if (tbl->refs_resize_part >= tbl->refs_resize_size / 128) return 1; // all parts claimed
+    size_t part = __sync_fetch_and_add(&tbl->refs_resize_part, 1);
+    if (part >= tbl->refs_resize_size/128) return 1; // all parts claimed
+
+    // rehash all
+    int i;
+    volatile uint64_t *bucket = tbl->refs_resize_table + part * 128;
+    for (i=0; i<128; i++) refs_rehash(tbl, *bucket++);
+
+    __sync_fetch_and_add(&tbl->refs_resize_done, 1);
+    return 1;
+}
+
+static void
+refs_resize(refs_table_t *tbl)
+{
+    while (1) {
+        uint32_t v = tbl->refs_control;
+        if (v & 0xf0000000) {
+            // someone else started resize
+            // just rehash blocks until done
+            while (refs_resize_help(tbl)) continue;
+            return;
+        }
+        if (cas(&tbl->refs_control, v, 0x80000000 | v)) {
+            // wait until all users gone
+            while (tbl->refs_control != 0x80000000) continue;
+            break;
+        }
+    }
+
+    tbl->refs_resize_table = tbl->refs_table;
+    tbl->refs_resize_size = tbl->refs_size;
+    tbl->refs_resize_part = 0;
+    tbl->refs_resize_done = 0;
+
+    // calculate new size
+    size_t new_size = tbl->refs_size;
+    size_t count = refs_count(tbl);
+    if (count*4 > tbl->refs_size) new_size *= 2;
+
+    // allocate new table
+    uint64_t *new_table = (uint64_t*)mmap(0, new_size * sizeof(uint64_t), PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANON, -1, 0);
+    if (new_table == (uint64_t*)-1) {
+        fprintf(stderr, "refs: Unable to allocate memory: %s!\n", strerror(errno));
+        exit(1);
+    }
+
+    // set new data and go
+    tbl->refs_table = new_table;
+    tbl->refs_size = new_size;
+    compiler_barrier();
+    tbl->refs_control = 0x40000000;
+
+    // until all parts are done, rehash blocks
+    while (tbl->refs_resize_done != tbl->refs_resize_size/128) refs_resize_help(tbl);
+
+    // done!
+    compiler_barrier();
+    tbl->refs_control = 0;
+
+    // unmap old table
+    munmap(tbl->refs_resize_table, tbl->refs_resize_size * sizeof(uint64_t));
+}
+
+/* Enter refs_modify */
+static inline void
+refs_enter(refs_table_t *tbl)
+{
+    for (;;) {
+        uint32_t v = tbl->refs_control;
+        if (v & 0xf0000000) {
+            while (refs_resize_help(tbl)) continue;
+        } else {
+            if (cas(&tbl->refs_control, v, v+1)) return;
+        }
+    }
+}
+
+/* Leave refs_modify */
+static inline void
+refs_leave(refs_table_t *tbl)
+{
+    for (;;) {
+        uint32_t v = tbl->refs_control;
+        if (cas(&tbl->refs_control, v, v-1)) return;
+    }
+}
+
+static inline int
+refs_modify(refs_table_t *tbl, const uint64_t a, const int dir)
+{
+    volatile uint64_t *bucket;
+    volatile uint64_t *ts_bucket;
+    uint64_t v, new_v;
+    int res, i;
+
+    refs_enter(tbl);
+
+ref_retry:
+    bucket = tbl->refs_table + (fnv_hash(a) & (tbl->refs_size - 1));
+    ts_bucket = NULL; // tombstone
+    i = 128; // try 128 times linear probing
+
+    while (i--) {
+ref_restart:
+        v = *bucket;
+        if (v == refs_ts) {
+            if (ts_bucket == NULL) ts_bucket = bucket;
+        } else if (v == 0) {
+            // not found
+            res = 0;
+            if (dir < 0) goto ref_exit;
+            if (ts_bucket != NULL) {
+                bucket = ts_bucket;
+                ts_bucket = NULL;
+                v = refs_ts;
+            }
+            new_v = a | (1ULL << 40);
+            goto ref_mod;
+        } else if ((v & 0x000000ffffffffff) == a) {
+            // found
+            res = 1;
+            uint64_t count = v >> 40;
+            if (count == 0x7fffff) goto ref_exit;
+            count += dir;
+            if (count == 0) new_v = refs_ts;
+            else new_v = a | (count << 40);
+            goto ref_mod;
+        }
+
+        if (++bucket == tbl->refs_table + tbl->refs_size) bucket = tbl->refs_table;
+    }
+
+    // not found after linear probing
+    if (dir < 0) {
+        res = 0;
+        goto ref_exit;
+    } else if (ts_bucket != NULL) {
+        bucket = ts_bucket;
+        ts_bucket = NULL;
+        v = refs_ts;
+        new_v = a | (1ULL << 40);
+        if (!cas(bucket, v, new_v)) goto ref_retry;
+        res = 1;
+        goto ref_exit;
+    } else {
+        // hash table full
+        refs_leave(tbl);
+        refs_resize(tbl);
+        return refs_modify(tbl, a, dir);
+    }
+
+ref_mod:
+    if (!cas(bucket, v, new_v)) goto ref_restart;
+
+ref_exit:
+    refs_leave(tbl);
+    return res;
+}
+
+void
+refs_up(refs_table_t *tbl, uint64_t a)
+{
+    refs_modify(tbl, a, 1);
+}
+
+void
+refs_down(refs_table_t *tbl, uint64_t a)
+{
+#ifdef NDEBUG
+    refs_modify(tbl, a, -1);
+#else
+    int res = refs_modify(tbl, a, -1);
+    assert(res != 0);
+#endif
+}
+
+uint64_t*
+refs_iter(refs_table_t *tbl, size_t first, size_t end)
+{
+    // assert(first < tbl->refs_size);
+    // assert(end <= tbl->refs_size);
+
+    uint64_t *bucket = tbl->refs_table + first;
+    while (bucket != tbl->refs_table + end) {
+        if (*bucket != 0 && *bucket != refs_ts) return bucket;
+        bucket++;
+    }
+    return NULL;
+}
+
+uint64_t
+refs_next(refs_table_t *tbl, uint64_t **_bucket, size_t end)
+{
+    uint64_t *bucket = *_bucket;
+    // assert(bucket != NULL);
+    // assert(end <= tbl->refs_size);
+    uint64_t result = *bucket & 0x000000ffffffffff;
+    bucket++;
+    while (bucket != tbl->refs_table + end) {
+        if (*bucket != 0 && *bucket != refs_ts) {
+            *_bucket = bucket;
+            return result;
+        }
+        bucket++;
+    }
+    *_bucket = NULL;
+    return result;
+}
+
+void
+refs_create(refs_table_t *tbl, size_t _refs_size)
+{
+    if (__builtin_popcountll(_refs_size) != 1) {
+        fprintf(stderr, "refs: Table size must be a power of 2!\n");
+        exit(1);
+    }
+
+    tbl->refs_size = _refs_size;
+    tbl->refs_table = (uint64_t*)mmap(0, tbl->refs_size * sizeof(uint64_t), PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANON, -1, 0);
+    if (tbl->refs_table == (uint64_t*)-1) {
+        fprintf(stderr, "refs: Unable to allocate memory: %s!\n", strerror(errno));
+        exit(1);
+    }
+}
+
+void
+refs_free(refs_table_t *tbl)
+{
+    munmap(tbl->refs_table, tbl->refs_size * sizeof(uint64_t));
+}
+
+/**
+ * Simple implementation of a 64-bit resizable hash-table
+ * No idea if this is scalable... :( but it seems thread-safe
+ */
+
+// Count number of unique entries (not number of references)
+size_t
+protect_count(refs_table_t *tbl)
+{
+    size_t count = 0;
+    uint64_t *bucket = tbl->refs_table;
+    uint64_t * const end = bucket + tbl->refs_size;
+    while (bucket != end) {
+        if (*bucket != 0 && *bucket != refs_ts) count++;
+        bucket++;
+    }
+    return count;
+}
+
+static inline void
+protect_rehash(refs_table_t *tbl, uint64_t v)
+{
+    if (v == 0) return; // do not rehash empty value
+    if (v == refs_ts) return; // do not rehash tombstone
+
+    volatile uint64_t *bucket = tbl->refs_table + (fnv_hash(v) % tbl->refs_size);
+    uint64_t * const end = tbl->refs_table + tbl->refs_size;
+
+    int i = 128; // try 128 times linear probing
+    while (i--) {
+        if (*bucket == 0 && cas(bucket, 0, v)) return;
+        if (++bucket == end) bucket = tbl->refs_table;
+    }
+
+    assert(0); // whoops!
+}
+
+/**
+ * Called internally to assist resize operations
+ * Returns 1 for retry, 0 for done
+ */
+static int
+protect_resize_help(refs_table_t *tbl)
+{
+    if (0 == (tbl->refs_control & 0xf0000000)) return 0; // no resize in progress (anymore)
+    if (tbl->refs_control & 0x80000000) return 1; // still waiting for preparation
+    if (tbl->refs_resize_part >= tbl->refs_resize_size / 128) return 1; // all parts claimed
+    size_t part = __sync_fetch_and_add(&tbl->refs_resize_part, 1);
+    if (part >= tbl->refs_resize_size/128) return 1; // all parts claimed
+
+    // rehash all
+    int i;
+    volatile uint64_t *bucket = tbl->refs_resize_table + part * 128;
+    for (i=0; i<128; i++) protect_rehash(tbl, *bucket++);
+
+    __sync_fetch_and_add(&tbl->refs_resize_done, 1);
+    return 1;
+}
+
+static void
+protect_resize(refs_table_t *tbl)
+{
+    while (1) {
+        uint32_t v = tbl->refs_control;
+        if (v & 0xf0000000) {
+            // someone else started resize
+            // just rehash blocks until done
+            while (protect_resize_help(tbl)) continue;
+            return;
+        }
+        if (cas(&tbl->refs_control, v, 0x80000000 | v)) {
+            // wait until all users gone
+            while (tbl->refs_control != 0x80000000) continue;
+            break;
+        }
+    }
+
+    tbl->refs_resize_table = tbl->refs_table;
+    tbl->refs_resize_size = tbl->refs_size;
+    tbl->refs_resize_part = 0;
+    tbl->refs_resize_done = 0;
+
+    // calculate new size
+    size_t new_size = tbl->refs_size;
+    size_t count = refs_count(tbl);
+    if (count*4 > tbl->refs_size) new_size *= 2;
+
+    // allocate new table
+    uint64_t *new_table = (uint64_t*)mmap(0, new_size * sizeof(uint64_t), PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANON, -1, 0);
+    if (new_table == (uint64_t*)-1) {
+        fprintf(stderr, "refs: Unable to allocate memory: %s!\n", strerror(errno));
+        exit(1);
+    }
+
+    // set new data and go
+    tbl->refs_table = new_table;
+    tbl->refs_size = new_size;
+    compiler_barrier();
+    tbl->refs_control = 0x40000000;
+
+    // until all parts are done, rehash blocks
+    while (tbl->refs_resize_done < tbl->refs_resize_size/128) protect_resize_help(tbl);
+
+    // done!
+    compiler_barrier();
+    tbl->refs_control = 0;
+
+    // unmap old table
+    munmap(tbl->refs_resize_table, tbl->refs_resize_size * sizeof(uint64_t));
+}
+
+static inline void
+protect_enter(refs_table_t *tbl)
+{
+    for (;;) {
+        uint32_t v = tbl->refs_control;
+        if (v & 0xf0000000) {
+            while (protect_resize_help(tbl)) continue;
+        } else {
+            if (cas(&tbl->refs_control, v, v+1)) return;
+        }
+    }
+}
+
+static inline void
+protect_leave(refs_table_t *tbl)
+{
+    for (;;) {
+        uint32_t v = tbl->refs_control;
+        if (cas(&tbl->refs_control, v, v-1)) return;
+    }
+}
+
+void
+protect_up(refs_table_t *tbl, uint64_t a)
+{
+    volatile uint64_t *bucket;
+    volatile uint64_t *ts_bucket;
+    uint64_t v;
+    int i;
+
+    protect_enter(tbl);
+
+ref_retry:
+    bucket = tbl->refs_table + (fnv_hash(a) & (tbl->refs_size - 1));
+    ts_bucket = NULL; // tombstone
+    i = 128; // try 128 times linear probing
+
+    while (i--) {
+ref_restart:
+        v = *bucket;
+        if (v == refs_ts) {
+            if (ts_bucket == NULL) ts_bucket = bucket;
+        } else if (v == 0) {
+            // go go go
+            if (ts_bucket != NULL) {
+                if (cas(ts_bucket, refs_ts, a)) {
+                    protect_leave(tbl);
+                    return;
+                } else {
+                    goto ref_retry;
+                }
+            } else {
+                if (cas(bucket, 0, a)) {
+                    protect_leave(tbl);
+                    return;
+                } else {
+                    goto ref_restart;
+                }
+            }
+        }
+
+        if (++bucket == tbl->refs_table + tbl->refs_size) bucket = tbl->refs_table;
+    }
+
+    // not found after linear probing
+    if (ts_bucket != NULL) {
+        if (cas(ts_bucket, refs_ts, a)) {
+            protect_leave(tbl);
+            return;
+        } else {
+            goto ref_retry;
+        }
+    } else {
+        // hash table full
+        protect_leave(tbl);
+        protect_resize(tbl);
+        protect_enter(tbl);
+        goto ref_retry;
+    }
+}
+
+void
+protect_down(refs_table_t *tbl, uint64_t a)
+{
+    volatile uint64_t *bucket;
+    protect_enter(tbl);
+
+    bucket = tbl->refs_table + (fnv_hash(a) & (tbl->refs_size - 1));
+    int i = 128; // try 128 times linear probing
+
+    while (i--) {
+        if (*bucket == a) {
+            *bucket = refs_ts;
+            protect_leave(tbl);
+            return;
+        }
+        if (++bucket == tbl->refs_table + tbl->refs_size) bucket = tbl->refs_table;
+    }
+
+    // not found after linear probing
+    assert(0);
+}
+
+uint64_t*
+protect_iter(refs_table_t *tbl, size_t first, size_t end)
+{
+    // assert(first < tbl->refs_size);
+    // assert(end <= tbl->refs_size);
+
+    uint64_t *bucket = tbl->refs_table + first;
+    while (bucket != tbl->refs_table + end) {
+        if (*bucket != 0 && *bucket != refs_ts) return bucket;
+        bucket++;
+    }
+    return NULL;
+}
+
+uint64_t
+protect_next(refs_table_t *tbl, uint64_t **_bucket, size_t end)
+{
+    uint64_t *bucket = *_bucket;
+    // assert(bucket != NULL);
+    // assert(end <= tbl->refs_size);
+    uint64_t result = *bucket;
+    bucket++;
+    while (bucket != tbl->refs_table + end) {
+        if (*bucket != 0 && *bucket != refs_ts) {
+            *_bucket = bucket;
+            return result;
+        }
+        bucket++;
+    }
+    *_bucket = NULL;
+    return result;
+}
+
+void
+protect_create(refs_table_t *tbl, size_t _refs_size)
+{
+    if (__builtin_popcountll(_refs_size) != 1) {
+        fprintf(stderr, "refs: Table size must be a power of 2!\n");
+        exit(1);
+    }
+
+    tbl->refs_size = _refs_size;
+    tbl->refs_table = (uint64_t*)mmap(0, tbl->refs_size * sizeof(uint64_t), PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANON, -1, 0);
+    if (tbl->refs_table == (uint64_t*)-1) {
+        fprintf(stderr, "refs: Unable to allocate memory: %s!\n", strerror(errno));
+        exit(1);
+    }
+}
+
+void
+protect_free(refs_table_t *tbl)
+{
+    munmap(tbl->refs_table, tbl->refs_size * sizeof(uint64_t));
+    tbl->refs_table = 0;
+}
diff --git a/sylvan_refs.h b/sylvan_refs.h
new file mode 100755
index 0000000000000000000000000000000000000000..a0e20d90b7c1978a22140d114f47824954b2a96f
--- /dev/null
+++ b/sylvan_refs.h
@@ -0,0 +1,77 @@
+/*
+ * 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.
+ */
+
+/* Do not include this file directly. Instead, include sylvan.h */
+
+#ifndef REFS_INLINE_H
+#define REFS_INLINE_H
+
+#ifdef __cplusplus
+extern "C" {
+#endif /* __cplusplus */
+
+/**
+ * Implementation of external references
+ * Based on a hash table for 40-bit non-null values, linear probing
+ * Use tombstones for deleting, higher bits for reference count
+ */
+typedef struct
+{
+    uint64_t *refs_table;           // table itself
+    size_t refs_size;               // number of buckets
+
+    /* helpers during resize operation */
+    volatile uint32_t refs_control; // control field
+    uint64_t *refs_resize_table;    // previous table
+    size_t refs_resize_size;        // size of previous table
+    size_t refs_resize_part;        // which part is next
+    size_t refs_resize_done;        // how many parts are done
+} refs_table_t;
+
+// Count number of unique entries (not number of references)
+size_t refs_count(refs_table_t *tbl);
+
+// Increase or decrease reference to 40-bit value a
+// Will fail (assertion) if more down than up are called for a
+void refs_up(refs_table_t *tbl, uint64_t a);
+void refs_down(refs_table_t *tbl, uint64_t a);
+
+// Return a bucket or NULL to start iterating
+uint64_t *refs_iter(refs_table_t *tbl, size_t first, size_t end);
+
+// Continue iterating, set bucket to next bucket or NULL
+uint64_t refs_next(refs_table_t *tbl, uint64_t **bucket, size_t end);
+
+// User must supply a pointer, refs_create and refs_free handle initialization/destruction
+void refs_create(refs_table_t *tbl, size_t _refs_size);
+void refs_free(refs_table_t *tbl);
+
+// The same, but now for 64-bit values ("protect pointers")
+size_t protect_count(refs_table_t *tbl);
+void protect_up(refs_table_t *tbl, uint64_t a);
+void protect_down(refs_table_t *tbl, uint64_t a);
+uint64_t *protect_iter(refs_table_t *tbl, size_t first, size_t end);
+uint64_t protect_next(refs_table_t *tbl, uint64_t **bucket, size_t end);
+void protect_create(refs_table_t *tbl, size_t _refs_size);
+void protect_free(refs_table_t *tbl);
+
+#ifdef __cplusplus
+}
+#endif /* __cplusplus */
+
+
+#endif
diff --git a/sylvan_seq.c b/sylvan_seq.c
old mode 100644
new mode 100755
index bf7589cb09acb7649abd711f7b51a7d50523ed5b..ceaa9469a0a7c0deacc1d848867a2435653045cb
--- a/sylvan_seq.c
+++ b/sylvan_seq.c
@@ -14,7 +14,18 @@
  * limitations under the License.
  */
 
-#include <sylvan_config.h>
+#include <sylvan.h>
+#include <sylvan_int.h>
+
+#include <inttypes.h>
+#include <math.h>
+#include <string.h>
+#include <stdint.h>
+#include <avl.h>
+
+#include <sys/mman.h>
+
+#include <sha2.h>
 
 #include <assert.h>
 #include <inttypes.h>
@@ -25,31 +36,27 @@
 #include <stdlib.h>
 #include <string.h>
 
-#include <avl.h>
-#include <refs.h>
-#include <sha2.h>
-#include <sylvan.h>
-#include <sylvan_common.h>
+
 
 
 /**
  * MDD node structure
  */
-typedef struct __attribute__((packed)) mddnode
+/*typedef struct __attribute__((packed)) mddnode
 {
     uint64_t a, b;
-} * mddnode_t; // 16 bytes
+} * mddnode_t; // 16 bytes*/
 
 #define GETNODE(mdd) ((mddnode_t)llmsset_index_to_ptr(nodes, mdd))
 
 
-static inline uint64_t __attribute__((unused))
+/*static inline uint64_t __attribute__((unused))
 mddnode_getright(mddnode_t n)
 {
     return (n->a & 0x0000ffffffffffff) >> 1;
-}
+}*/
 
-static inline uint64_t __attribute__((unused))
+/*static inline uint64_t __attribute__((unused))
 mddnode_getdown(mddnode_t n)
 {
     return n->b >> 17;
@@ -59,10 +66,10 @@ static inline uint8_t __attribute__((unused))
 mddnode_getcopy(mddnode_t n)
 {
     return n->b & 0x10000 ? 1 : 0;
-}
+}*/
 
 
-static inline uint32_t __attribute__((unused))
+/*static inline uint32_t __attribute__((unused))
 mddnode_getvalue(mddnode_t n)
 {
     return *(uint32_t*)((uint8_t*)n+6);
@@ -81,7 +88,7 @@ mddnode_makecopy(mddnode_t n, uint64_t right, uint64_t down)
     n->a = right << 1;
     n->b = ((down << 1) | 1) << 16;
 }
-
+*/
 /**************************************************/
 /*************************************************/
 
@@ -100,17 +107,15 @@ MDD ldd_makenode(uint32_t value, MDD ifeq, MDD ifneq)
     uint64_t index = llmsset_lookup(nodes, n.a, n.b, &created);
     if (index == 0)
     {
-        //lddmc_refs_push(ifeq);
-        //lddmc_refs_push(ifneq);
+        /*LACE_ME;
+       sylvan_gc(); // --- MC*/
 
-        //  LACE_ME;
-        //  sylvan_gc(); // --- MC
-       // lddmc_refs_pop(1);
 
         index = llmsset_lookup(nodes, n.a, n.b, &created);
         if (index == 0)
         {
-            // fprintf(stderr, "MDD Unique table full, %zu of %zu buckets filled!\n", llmsset_count_marked(nodes), llmsset_get_size(nodes));
+            fprintf(stderr, "MDD Unique table full!\n");
+
             exit(1);
         }
     }
@@ -135,13 +140,13 @@ ldd_make_copynode(MDD ifeq, MDD ifneq)
         lddmc_refs_push(ifeq);
         lddmc_refs_push(ifneq);
         //  LACE_ME;
-        // sylvan_gc();
+        //sylvan_gc();
         lddmc_refs_pop(1);
 
         index = llmsset_lookup(nodes, n.a, n.b, &created);
         if (index == 0)
         {
-            //  fprintf(stderr, "MDD Unique table full, %zu of %zu buckets filled!\n", llmsset_count_marked(nodes), llmsset_get_size(nodes));
+            fprintf(stderr, "MDD Unique table full, make_copy_node!\n");
             exit(1);
         }
     }
@@ -282,15 +287,7 @@ MDD ldd_minus(MDD a, MDD b)
     return result;
 }
 
-
-
-MDD lddmc_firing_help_mono(uint32_t val, MDD mark, MDD minus, MDD plus)
-{
-    return ldd_makenode(val,lddmc_firing_mono(mark, minus, plus), lddmc_false);
-}
-
-
-MDD lddmc_firing_mono( MDD cmark, MDD minus, MDD plus)
+MDD lddmc_firing_mono(MDD cmark, MDD minus, MDD plus)
 {
     // for an empty set of source states, or an empty transition relation, return the empty set
     if (cmark == lddmc_true) return lddmc_true;
@@ -309,20 +306,7 @@ MDD lddmc_firing_mono( MDD cmark, MDD minus, MDD plus)
     mddnode_t n_cmark = GETNODE(_cmark);
     mddnode_t n_plus = GETNODE(_plus);
     mddnode_t n_minus = GETNODE(_minus);
-    // meta: -1 (end; rest not in rel), 0 (not in rel), 1 (read), 2 (write), 3 (only-read), 4 (only-write)
 
-    /* Recursive operations */
-
-    // write, only-write
-//        if (m_val == 4) {
-//            // only-write, so we need to include 'for all variables'
-//            // the reason is that we did not have a read phase, so we need to 'insert' a read phase here
-//
-//        }
-
-    // if we're here and we are only-write, then we read the current value
-
-    // spawn for every value to write (rel)
 
     result = lddmc_false;
 
@@ -347,6 +331,17 @@ MDD lddmc_firing_mono( MDD cmark, MDD minus, MDD plus)
     return result;
 }
 
+
+MDD lddmc_firing_help_mono(uint32_t val, MDD mark, MDD minus, MDD plus)
+{
+    return ldd_makenode(val,lddmc_firing_mono(mark, minus, plus), lddmc_false);
+}
+
+
+
+
+
+
 void convert_to_string(MDD mdd,char *chaine_mdd)
 {
     unsigned int indice=0;
@@ -428,8 +423,7 @@ lddmc_project_node(MDD mdd)
 int get_mddnbr(MDD mdd,int level)
 {
     mddnode_t node;
-    int j;
-    for (j=0; j<level; j++)
+    for (int j=0; j<level; j++)
     {
         node=GETNODE(mdd);
         mdd=mddnode_getdown(node);
@@ -447,11 +441,11 @@ int get_mddnbr(MDD mdd,int level)
     return i;
 }
 
-static inline void __attribute__((unused))
+/*static inline void __attribute__((unused))
 mddnode_setdown(mddnode_t n, uint64_t down)
 {
     n->b = (n->b & 0x000000000001ffff) | (down << 17);
-}
+}*/
 
 void ldd_divide(MDD mdd,const int level,MDD *mdd1,MDD *mdd2)
 {
@@ -459,8 +453,8 @@ void ldd_divide(MDD mdd,const int level,MDD *mdd1,MDD *mdd2)
 
     mddnode_t node=GETNODE(mdd);
     MDD pred=mdd;
-    int i;
-    for (i=0; i<=level; i++)
+
+    for (int i=0; i<=level; i++)
     {
         pred=mdd;
         uint32_t val = mddnode_getvalue(node);
@@ -475,14 +469,13 @@ void ldd_divide(MDD mdd,const int level,MDD *mdd1,MDD *mdd2)
 
 
     mddnode_t node_pred=GETNODE(pred);
-    //mddnode_t node_pred_right=GETNODE(mddnode_getright(node_pred));
+
 
     MDD copy_mdd1=mddnode_getdown(node_pred);//,
  mddnode_t node_mdd1,node_mdd2;
     MDD _mdd1=*mdd1;
     node_mdd1=GETNODE(_mdd1);
-    int j;
-    for (j=0; j<level; j++)
+    for (int j=0; j<level; j++)
     {
         _mdd1=mddnode_getdown(node_mdd1);
         node_mdd1=GETNODE(_mdd1);
@@ -493,7 +486,7 @@ void ldd_divide(MDD mdd,const int level,MDD *mdd1,MDD *mdd2)
 
     //mddnode_setdown
     //ldd_make_copynode
-    ;
+
     mddnode_setdown(node_mdd1,ldd_make_copynode(copy_mdd1,lddmc_false));
 
 /*     FILE *fp0=fopen("copy.dot","w");
@@ -508,8 +501,7 @@ void ldd_divide(MDD mdd,const int level,MDD *mdd1,MDD *mdd2)
         *mdd2=lddmc_cube(list_values,level);
         MDD _mdd2=*mdd2;
         node_mdd2=GETNODE(_mdd2);
-        int j;
-        for (j=0; j<level-1; j++)
+        for (int j=0; j<level-1; j++)
         {
             _mdd2=mddnode_getdown(node_mdd2);
             node_mdd2=GETNODE(_mdd2);
@@ -524,7 +516,8 @@ void ldd_divide(MDD mdd,const int level,MDD *mdd1,MDD *mdd2)
 int isSingleMDD(MDD mdd)
 {
     mddnode_t node;
-    while (mdd!=lddmc_false)
+
+    while (mdd>lddmc_true)
     {
         node=GETNODE(mdd);
         if (mddnode_getright(node)!=lddmc_false) return 0;
@@ -533,13 +526,6 @@ int isSingleMDD(MDD mdd)
     return 1;
 }
 
-
-
-MDD ldd_divide_rec(MDD a, int level)
-{
-    return ldd_divide_internal(a,0,level);
-}
-
 MDD ldd_divide_internal(MDD a,int current_level,int level) {
     /* Terminal cases */
 
@@ -548,12 +534,6 @@ MDD ldd_divide_internal(MDD a,int current_level,int level) {
 
 
     MDD result;
-    /*if (cache_get3(CACHE_MDD_UNION, a, b, 0, &result)) {
-        sylvan_stats_count(LDD_UNION_CACHED);
-        return result;
-    }*/
-
-    /* Get nodes */
     mddnode_t node_a = GETNODE(a);
     const uint32_t na_value = mddnode_getvalue(node_a);
 
@@ -578,7 +558,16 @@ MDD ldd_divide_internal(MDD a,int current_level,int level) {
 }
 
 
-//MDD createCopy()
-#ifndef NDEBUG
-#endif
+
+MDD ldd_divide_rec(MDD a, int level)
+{
+    return ldd_divide_internal(a,0,level);
+}
+
+
+
+
+
+
+
 
diff --git a/sylvan_seq.h b/sylvan_seq.h
old mode 100644
new mode 100755
index e60704c8949d555e0ec6a926aab0f2c313d0d28c..e355166e8fcdbea467ac8e364cc15fe5fdab521b
--- a/sylvan_seq.h
+++ b/sylvan_seq.h
@@ -24,9 +24,10 @@ extern "C" {
 #endif /* __cplusplus */
 
 MDD ldd_makenode(uint32_t value, MDD ifeq, MDD ifneq);
-MDD lddmc_firing_mono(MDD cmark, MDD minus, MDD plus);
+MDD lddmc_firing_mono(MDD cmark, MDD _minus, MDD _plus);
 MDD lddmc_union_mono(MDD a, MDD b);
 MDD ldd_minus(MDD a, MDD b);
+void convert_wholemdd_string(MDD cmark,char **result,unsigned int &length);
 void convert_to_string(MDD mdd,char *chaine_mdd);
 
 void ldd_divide(MDD mdd,const int level,MDD *mdd1,MDD *mdd2);
@@ -35,6 +36,8 @@ int get_mddnbr(MDD mdd,int level);
 int isSingleMDD(MDD mdd);
 MDD ldd_divide_rec(MDD a, int level);
 MDD ldd_divide_internal(MDD a,int current_level,int level);
+
+void sylvan_gc_seq();
 #ifdef __cplusplus
 }
 #endif /* __cplusplus */
diff --git a/sylvan_sl.c b/sylvan_sl.c
new file mode 100755
index 0000000000000000000000000000000000000000..04812539d899619843d233f3f0b8ab3bf99b5738
--- /dev/null
+++ b/sylvan_sl.c
@@ -0,0 +1,170 @@
+/*
+ * 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.h>
+#include <sylvan_sl.h>
+
+#include <sys/mman.h> // for mmap, munmap, etc
+
+/* A SL_DEPTH of 6 means 32 bytes per bucket, of 14 means 64 bytes per bucket.
+   However, there is a very large performance drop with only 6 levels. */
+#define SL_DEPTH 14
+
+typedef struct
+{
+    SyBDD dd;
+    uint32_t next[SL_DEPTH];
+} sl_bucket;
+
+struct sylvan_skiplist
+{
+    sl_bucket *buckets;
+    size_t size;
+    size_t next;
+};
+
+#ifndef cas
+#define cas(ptr, old, new) (__sync_bool_compare_and_swap((ptr),(old),(new)))
+#endif
+
+sylvan_skiplist_t
+sylvan_skiplist_alloc(size_t size)
+{
+    if (size >= 0x80000000) {
+        fprintf(stderr, "sylvan: Trying to allocate a skiplist >= 0x80000000 buckets!\n");
+        exit(1);
+    }
+    sylvan_skiplist_t l = malloc(sizeof(struct sylvan_skiplist));
+    l->buckets = (sl_bucket*)mmap(0, sizeof(sl_bucket)*size, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANON, 0, 0);
+    if (l->buckets == (sl_bucket*)-1) {
+        fprintf(stderr, "sylvan: Unable to allocate virtual memory (%'zu bytes) for the skiplist!\n", size*sizeof(sl_bucket));
+        exit(1);
+    }
+    l->size = size;
+    l->next = 1;
+    return l;
+}
+
+void
+sylvan_skiplist_free(sylvan_skiplist_t l)
+{
+    munmap(l->buckets, sizeof(sl_bucket)*l->size);
+    free(l);
+}
+
+/**
+ * Return the assigned number of the given dd,
+ * or 0 if not found.
+ */
+uint64_t
+sylvan_skiplist_get(sylvan_skiplist_t l, MTBDD dd)
+{
+    if (dd == mtbdd_false || dd == mtbdd_true) return 0;
+
+    uint32_t loc = 0, k = SL_DEPTH-1;
+    for (;;) {
+        /* invariant: [loc].dd < dd */
+        /* note: this is always true for loc==0 */
+        sl_bucket *e = l->buckets + loc;
+        uint32_t loc_next = (*(volatile uint32_t*)&e->next[k]) & 0x7fffffff;
+        if (loc_next != 0 && l->buckets[loc_next].dd == dd) {
+            /* found */
+            return loc_next;
+        } else if (loc_next != 0 && l->buckets[loc_next].dd < dd) {
+            /* go right */
+            loc = loc_next;
+        } else if (k > 0) {
+            /* go down */
+            k--;
+        } else {
+            return 0;
+        }
+    }
+}
+
+VOID_TASK_IMPL_2(sylvan_skiplist_assign_next, sylvan_skiplist_t, l, MTBDD, dd)
+{
+    if (dd == mtbdd_false || dd == mtbdd_true) return;
+
+    uint32_t trace[SL_DEPTH];
+    uint32_t loc = 0, loc_next = 0, k = SL_DEPTH-1;
+    for (;;) {
+        /* invariant: [loc].dd < dd */
+        /* note: this is always true for loc==0 */
+        sl_bucket *e = l->buckets + loc;
+        loc_next = (*(volatile uint32_t*)&e->next[k]) & 0x7fffffff;
+        if (loc_next != 0 && l->buckets[loc_next].dd == dd) {
+            /* found */
+            return;
+        } else if (loc_next != 0 && l->buckets[loc_next].dd < dd) {
+            /* go right */
+            loc = loc_next;
+        } else if (k > 0) {
+            /* go down */
+            trace[k] = loc;
+            k--;
+        } else if (!(e->next[0] & 0x80000000) && cas(&e->next[0], loc_next, loc_next|0x80000000)) {
+            /* locked */
+            break;
+        }
+    }
+
+    /* claim next item */
+    const uint64_t next = __sync_fetch_and_add(&l->next, 1);
+    if (next >= l->size) {
+        fprintf(stderr, "Out of cheese exception, no more blocks available\n");
+        exit(1);
+    }
+
+    /* fill next item */
+    sl_bucket *a = l->buckets + next;
+    a->dd = dd;
+    a->next[0] = loc_next;
+    compiler_barrier();
+    l->buckets[loc].next[0] = next;
+
+    /* determine height */
+    uint64_t h = 1 + __builtin_clz(LACE_TRNG) / 2;
+    if (h > SL_DEPTH) h = SL_DEPTH;
+
+    /* go up and create links */
+    for (k=1;k<h;k++) {
+        loc = trace[k];
+        for (;;) {
+            sl_bucket *e = l->buckets + loc;
+            /* note, at k>0, no locks on edges */
+            uint32_t loc_next = *(volatile uint32_t*)&e->next[k];
+            if (loc_next != 0 && l->buckets[loc_next].dd < dd) {
+                loc = loc_next;
+            } else {
+                a->next[k] = loc_next;
+                if (cas(&e->next[k], loc_next, next)) break;
+            }
+        }
+    }
+}
+
+size_t
+sylvan_skiplist_count(sylvan_skiplist_t l)
+{
+    return l->next - 1;
+}
+
+MTBDD
+sylvan_skiplist_getr(sylvan_skiplist_t l, uint64_t index)
+{
+    return l->buckets[index].dd;
+}
diff --git a/sylvan_sl.h b/sylvan_sl.h
new file mode 100755
index 0000000000000000000000000000000000000000..ad09c92a30ce849122c5860447d41071fb117a37
--- /dev/null
+++ b/sylvan_sl.h
@@ -0,0 +1,70 @@
+/*
+ * 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.
+ */
+
+#ifndef SYLVAN_SKIPLIST_H
+#define SYLVAN_SKIPLIST_H
+
+#ifdef __cplusplus
+extern "C" {
+#endif /* __cplusplus */
+
+/**
+ * Implementation of a simple limited-depth skiplist.
+ * The skiplist is used by the serialization mechanism in Sylvan.
+ * Each stored MTBDD is assigned a number starting with 1.
+ * Each bucket takes 32 bytes.
+ */
+
+typedef struct sylvan_skiplist *sylvan_skiplist_t;
+
+/**
+ * Allocate a new skiplist of maximum size <size>.
+ * Only supports at most 0x7fffffff (max int32) buckets
+ */
+sylvan_skiplist_t sylvan_skiplist_alloc(size_t size);
+
+/**
+ * Free the given skiplist.
+ */
+void sylvan_skiplist_free(sylvan_skiplist_t sl);
+
+/**
+ * Get the number assigned to the given node <dd>.
+ * Returns 0 if no number was assigned.
+ */
+uint64_t sylvan_skiplist_get(sylvan_skiplist_t sl, MTBDD dd);
+
+/**
+ * Assign the next number (starting at 1) to the given node <dd>.
+ */
+VOID_TASK_DECL_2(sylvan_skiplist_assign_next, sylvan_skiplist_t, MTBDD);
+#define sylvan_skiplist_assign_next(sl, dd) CALL(sylvan_skiplist_assign_next, sl, dd)
+
+/**
+ * Give the number of assigned nodes. (numbers 1,2,...,N)
+ */
+size_t sylvan_skiplist_count(sylvan_skiplist_t sl);
+
+/**
+ * Get the MTBDD assigned to the number <index>, with the index 1,...,count.
+ */
+MTBDD sylvan_skiplist_getr(sylvan_skiplist_t sl, uint64_t index);
+
+#ifdef __cplusplus
+}
+#endif /* __cplusplus */
+
+#endif
diff --git a/sylvan_stats.c b/sylvan_stats.c
new file mode 100755
index 0000000000000000000000000000000000000000..72383410fa0c772082e0af49745a1c8e5ea629df
--- /dev/null
+++ b/sylvan_stats.c
@@ -0,0 +1,287 @@
+/*
+ * 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>
+
+#include <errno.h>  // for errno
+#include <string.h> // memset
+#include <sys/mman.h>
+#include <inttypes.h>
+
+#if SYLVAN_STATS
+
+#ifdef __ELF__
+__thread sylvan_stats_t sylvan_stats;
+#else
+pthread_key_t sylvan_stats_key;
+#endif
+
+/**
+ * Instructions for sylvan_stats_report
+ */
+struct
+{
+    int type; /* 0 for print line, 1 for simple counter, 2 for operation with CACHED and CACHEDPUT */
+              /* 3 for timer, 4 for report table data */
+    int id;
+    const char *key;
+} sylvan_report_info[] =
+{
+    {0, 0, "Tables"},
+    {1, BDD_NODES_CREATED, "MTBDD nodes created"},
+    {1, BDD_NODES_REUSED, "MTBDD nodes reused"},
+    {1, LDD_NODES_CREATED, "LDD nodes created"},
+    {1, LDD_NODES_REUSED, "LDD nodes reused"},
+    {1, LLMSSET_LOOKUP, "Lookup iterations"},
+    {4, 0, NULL}, /* trigger to report unique nodes and operation cache */
+
+    {0, 0, "Operation            Count            Cache get        Cache put"},
+    {2, BDD_AND, "SyBDD and"},
+    {2, BDD_XOR, "SyBDD xor"},
+    {2, BDD_ITE, "SyBDD ite"},
+    {2, BDD_EXISTS, "SyBDD exists"},
+    {2, BDD_PROJECT, "SyBDD project"},
+    {2, BDD_AND_EXISTS, "SyBDD andexists"},
+    {2, BDD_AND_PROJECT, "SyBDD andproject"},
+    {2, BDD_RELNEXT, "SyBDD relnext"},
+    {2, BDD_RELPREV, "SyBDD relprev"},
+    {2, BDD_CLOSURE, "SyBDD closure"},
+    {2, BDD_COMPOSE, "SyBDD compose"},
+    {2, BDD_RESTRICT, "SyBDD restrict"},
+    {2, BDD_CONSTRAIN, "SyBDD constrain"},
+    {2, BDD_SUPPORT, "SyBDD support"},
+    {2, BDD_SATCOUNT, "SyBDD satcount"},
+    {2, BDD_PATHCOUNT, "SyBDD pathcount"},
+    {2, BDD_ISBDD, "SyBDD isbdd"},
+
+    {2, MTBDD_APPLY, "MTBDD binary apply"},
+    {2, MTBDD_UAPPLY, "MTBDD unary apply"},
+    {2, MTBDD_ABSTRACT, "MTBDD abstract"},
+    {2, MTBDD_ITE, "MTBDD ite"},
+    {2, MTBDD_EQUAL_NORM, "MTBDD eq norm"},
+    {2, MTBDD_EQUAL_NORM_REL, "MTBDD eq norm rel"},
+    {2, MTBDD_LEQ, "MTBDD leq"},
+    {2, MTBDD_LESS, "MTBDD less"},
+    {2, MTBDD_GEQ, "MTBDD geq"},
+    {2, MTBDD_GREATER, "MTBDD greater"},
+    {2, MTBDD_AND_ABSTRACT_PLUS, "MTBDD and_abs_plus"},
+    {2, MTBDD_AND_ABSTRACT_MAX, "MTBDD and_abs_max"},
+    {2, MTBDD_COMPOSE, "MTBDD compose"},
+    {2, MTBDD_MINIMUM, "MTBDD minimum"},
+    {2, MTBDD_MAXIMUM, "MTBDD maximum"},
+    {2, MTBDD_EVAL_COMPOSE, "MTBDD eval_compose"},
+
+    {2, LDD_UNION, "LDD union"},
+    {2, LDD_MINUS, "LDD minus"},
+    {2, LDD_INTERSECT, "LDD intersect"},
+    {2, LDD_RELPROD, "LDD relprod"},
+    {2, LDD_RELPREV, "LDD relprev"},
+    {2, LDD_PROJECT, "LDD project"},
+    {2, LDD_JOIN, "LDD join"},
+    {2, LDD_MATCH, "LDD match"},
+    {2, LDD_SATCOUNT, "LDD satcount"},
+    {2, LDD_SATCOUNTL, "LDD satcountl"},
+    {2, LDD_ZIP, "LDD zip"},
+    {2, LDD_RELPROD_UNION, "LDD relprod_union"},
+    {2, LDD_PROJECT_MINUS, "LDD project_minus"},
+
+    {0, 0, "Garbage collection"},
+    {1, SYLVAN_GC_COUNT, "GC executions"},
+    {3, SYLVAN_GC, "Total time spent"},
+
+    {-1, -1, NULL},
+};
+
+VOID_TASK_0(sylvan_stats_reset_perthread)
+{
+#ifdef __ELF__
+    for (int i=0; i<SYLVAN_COUNTER_COUNTER; i++) {
+        sylvan_stats.counters[i] = 0;
+    }
+    for (int i=0; i<SYLVAN_TIMER_COUNTER; i++) {
+        sylvan_stats.timers[i] = 0;
+    }
+#else
+    sylvan_stats_t *sylvan_stats = pthread_getspecific(sylvan_stats_key);
+    if (sylvan_stats == NULL) {
+        sylvan_stats = mmap(0, sizeof(sylvan_stats_t), PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANON, -1, 0);
+        if (sylvan_stats == (sylvan_stats_t *)-1) {
+            fprintf(stderr, "sylvan_stats: Unable to allocate memory: %s!\n", strerror(errno));
+            exit(1);
+        }
+    }
+    pthread_setspecific(sylvan_stats_key, sylvan_stats);
+    for (int i=0; i<SYLVAN_COUNTER_COUNTER; i++) {
+        sylvan_stats->counters[i] = 0;
+    }
+    for (int i=0; i<SYLVAN_TIMER_COUNTER; i++) {
+        sylvan_stats->timers[i] = 0;
+    }
+#endif
+}
+
+VOID_TASK_IMPL_0(sylvan_stats_init)
+{
+#ifndef __ELF__
+    pthread_key_create(&sylvan_stats_key, NULL);
+#endif
+    TOGETHER(sylvan_stats_reset_perthread);
+}
+
+/**
+ * Reset all counters (for statistics)
+ */
+VOID_TASK_IMPL_0(sylvan_stats_reset)
+{
+    TOGETHER(sylvan_stats_reset_perthread);
+}
+
+VOID_TASK_1(sylvan_stats_sum, sylvan_stats_t*, target)
+{
+#ifdef __ELF__
+    for (int i=0; i<SYLVAN_COUNTER_COUNTER; i++) {
+        __sync_fetch_and_add(&target->counters[i], sylvan_stats.counters[i]);
+    }
+    for (int i=0; i<SYLVAN_TIMER_COUNTER; i++) {
+        __sync_fetch_and_add(&target->timers[i], sylvan_stats.timers[i]);
+    }
+#else
+    sylvan_stats_t *sylvan_stats = pthread_getspecific(sylvan_stats_key);
+    if (sylvan_stats != NULL) {
+        for (int i=0; i<SYLVAN_COUNTER_COUNTER; i++) {
+            __sync_fetch_and_add(&target->counters[i], sylvan_stats->counters[i]);
+        }
+        for (int i=0; i<SYLVAN_TIMER_COUNTER; i++) {
+            __sync_fetch_and_add(&target->timers[i], sylvan_stats->timers[i]);
+        }
+    }
+#endif
+}
+
+VOID_TASK_IMPL_1(sylvan_stats_snapshot, sylvan_stats_t*, target)
+{
+    memset(target, 0, sizeof(sylvan_stats_t));
+    TOGETHER(sylvan_stats_sum, target);
+}
+
+#define BLACK "\33[22;30m"
+#define GRAY "\33[1;30m"
+#define RED "\33[22;31m"
+#define LRED "\33[1;31m"
+#define GREEN "\33[22;32m"
+#define LGREEN "\33[1;32m"
+#define BROWN "\33[22;33m"
+#define YELLOW "\33[1;33m"
+#define BLUE "\33[22;34m"
+#define LBLUE "\33[1;34m"
+#define MAGENTA "\33[22;35m"
+#define LMAGENTA "\33[1;35m"
+#define CYAN "\33[22;36m"
+#define LCYAN "\33[1;36m"
+#define LGRAY "\33[22;37m"
+#define WHITE "\33[1;37m"
+#define NC "\33[m"
+#define BOLD "\33[1m"
+#define ULINE "\33[4m"
+#define PINK "\33[38;5;200m"
+
+static char*
+to_h(double size, char *buf)
+{
+    const char* units[] = {"B", "KB", "MB", "GB", "TB", "PB", "EB", "ZB", "YB"};
+    int i = 0;
+    for (;size>1024;size/=1024) i++;
+    sprintf(buf, "%.*f %s", i, size, units[i]);
+    return buf;
+}
+
+void
+sylvan_stats_report(FILE *target)
+{
+    LACE_ME;
+    sylvan_stats_t totals;
+    sylvan_stats_snapshot(&totals);
+
+    // fix timers for MACH
+#ifdef __MACH__
+    mach_timebase_info_data_t timebase;
+    mach_timebase_info(&timebase);
+    uint64_t c = timebase.numer/timebase.denom;
+    for (int i=0;i<SYLVAN_TIMER_COUNTER;i++) totals.timers[i]*=c;
+#endif
+
+    int color = isatty(fileno(target)) ? 1 : 0;
+    if (color) fprintf(target, ULINE WHITE "Sylvan statistics\n" NC);
+    else fprintf(target, "Sylvan statistics\n");
+
+    int i=0;
+    for (;;) {
+        if (sylvan_report_info[i].id == -1) break;
+        int id = sylvan_report_info[i].id;
+        int type = sylvan_report_info[i].type;
+        if (type == 0) {
+            if (color) fprintf(target, WHITE "\n%s\n" NC, sylvan_report_info[i].key);
+            else fprintf(target, "\n%s\n", sylvan_report_info[i].key);
+        } else if (type == 1) {
+            if (totals.counters[id] > 0) {
+                fprintf(target, "%-20s %'-16"PRIu64"\n", sylvan_report_info[i].key, totals.counters[id]);
+            }
+        } else if (type == 2) {
+            if (totals.counters[id] > 0) {
+                fprintf(target, "%-20s %'-16"PRIu64 " %'-16"PRIu64" %'-16"PRIu64 "\n", sylvan_report_info[i].key, totals.counters[id], totals.counters[id+1], totals.counters[id+2]);
+            }
+        } else if (type == 3) {
+            if (totals.timers[id] > 0) {
+                fprintf(target, "%-20s %'.6Lf sec.\n", sylvan_report_info[i].key, (long double)totals.timers[id]/1000000000);
+            }
+        } else if (type == 4) {
+            fprintf(target, "%-20s %'zu of %'zu buckets filled.\n", "Unique nodes table", llmsset_count_marked(nodes), llmsset_get_size(nodes));
+            fprintf(target, "%-20s %'zu of %'zu buckets filled.\n", "Operation cache", cache_getused(), cache_getsize());
+            char buf[64], buf2[64];
+            to_h(24ULL * llmsset_get_size(nodes), buf);
+            to_h(24ULL * llmsset_get_max_size(nodes), buf2);
+            fprintf(target, "%-20s %s (max real) of %s (allocated virtual memory).\n", "Memory (nodes)", buf, buf2);
+            to_h(36ULL * cache_getsize(), buf);
+            to_h(36ULL * cache_getmaxsize(), buf2);
+            fprintf(target, "%-20s %s (max real) of %s (allocated virtual memory).\n", "Memory (cache)", buf, buf2);
+        }
+        i++;
+    }
+}
+
+#else
+
+VOID_TASK_IMPL_0(sylvan_stats_init)
+{
+}
+
+VOID_TASK_IMPL_0(sylvan_stats_reset)
+{
+}
+
+VOID_TASK_IMPL_1(sylvan_stats_snapshot, sylvan_stats_t*, target)
+{
+    memset(target, 0, sizeof(sylvan_stats_t));
+}
+
+void
+sylvan_stats_report(FILE* target)
+{
+    (void)target;
+}
+
+#endif
diff --git a/sylvan_stats.h b/sylvan_stats.h
new file mode 100755
index 0000000000000000000000000000000000000000..bac2ba0103e256cf28c36fa58e15c70563079de6
--- /dev/null
+++ b/sylvan_stats.h
@@ -0,0 +1,239 @@
+/*
+ * 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.
+ */
+
+/* Do not include this file directly. Instead, include sylvan.h */
+
+#ifndef SYLVAN_STATS_H
+#define SYLVAN_STATS_H
+
+#ifdef __cplusplus
+extern "C" {
+#endif /* __cplusplus */
+
+#define OPCOUNTER(NAME) NAME, NAME ## _CACHEDPUT, NAME ## _CACHED
+
+typedef enum {
+    /* Creating nodes */
+    BDD_NODES_CREATED,
+    BDD_NODES_REUSED,
+    LDD_NODES_CREATED,
+    LDD_NODES_REUSED,
+
+    /* SyBDD operations */
+    OPCOUNTER(BDD_ITE),
+    OPCOUNTER(BDD_AND),
+    OPCOUNTER(BDD_XOR),
+    OPCOUNTER(BDD_EXISTS),
+    OPCOUNTER(BDD_PROJECT),
+    OPCOUNTER(BDD_AND_EXISTS),
+    OPCOUNTER(BDD_AND_PROJECT),
+    OPCOUNTER(BDD_RELNEXT),
+    OPCOUNTER(BDD_RELPREV),
+    OPCOUNTER(BDD_SATCOUNT),
+    OPCOUNTER(BDD_COMPOSE),
+    OPCOUNTER(BDD_RESTRICT),
+    OPCOUNTER(BDD_CONSTRAIN),
+    OPCOUNTER(BDD_CLOSURE),
+    OPCOUNTER(BDD_ISBDD),
+    OPCOUNTER(BDD_SUPPORT),
+    OPCOUNTER(BDD_PATHCOUNT),
+
+    /* MTBDD operations */
+    OPCOUNTER(MTBDD_APPLY),
+    OPCOUNTER(MTBDD_UAPPLY),
+    OPCOUNTER(MTBDD_ABSTRACT),
+    OPCOUNTER(MTBDD_ITE),
+    OPCOUNTER(MTBDD_EQUAL_NORM),
+    OPCOUNTER(MTBDD_EQUAL_NORM_REL),
+    OPCOUNTER(MTBDD_LEQ),
+    OPCOUNTER(MTBDD_LESS),
+    OPCOUNTER(MTBDD_GEQ),
+    OPCOUNTER(MTBDD_GREATER),
+    OPCOUNTER(MTBDD_AND_ABSTRACT_PLUS),
+    OPCOUNTER(MTBDD_AND_ABSTRACT_MAX),
+    OPCOUNTER(MTBDD_COMPOSE),
+    OPCOUNTER(MTBDD_MINIMUM),
+    OPCOUNTER(MTBDD_MAXIMUM),
+    OPCOUNTER(MTBDD_EVAL_COMPOSE),
+
+    /* LDD operations */
+    OPCOUNTER(LDD_UNION),
+    OPCOUNTER(LDD_MINUS),
+    OPCOUNTER(LDD_INTERSECT),
+    OPCOUNTER(LDD_RELPROD),
+    OPCOUNTER(LDD_RELPREV),
+    OPCOUNTER(LDD_PROJECT),
+    OPCOUNTER(LDD_JOIN),
+    OPCOUNTER(LDD_MATCH),
+    OPCOUNTER(LDD_SATCOUNT),
+    OPCOUNTER(LDD_SATCOUNTL),
+    OPCOUNTER(LDD_ZIP),
+    OPCOUNTER(LDD_RELPROD_UNION),
+    OPCOUNTER(LDD_PROJECT_MINUS),
+
+    /* Other counters */
+    SYLVAN_GC_COUNT,
+    LLMSSET_LOOKUP,
+
+    SYLVAN_COUNTER_COUNTER
+} Sylvan_Counters;
+
+#undef OPCOUNTER
+
+typedef enum
+{
+    SYLVAN_GC,
+    SYLVAN_TIMER_COUNTER
+} Sylvan_Timers;
+
+typedef struct
+{
+    uint64_t counters[SYLVAN_COUNTER_COUNTER];
+    /* the timers are in ns */
+    uint64_t timers[SYLVAN_TIMER_COUNTER];
+    /* startstop is for internal use */
+    uint64_t timers_startstop[SYLVAN_TIMER_COUNTER];
+} sylvan_stats_t;
+
+/**
+ * Initialize stats system (done by sylvan_init_package)
+ */
+VOID_TASK_DECL_0(sylvan_stats_init);
+#define sylvan_stats_init() CALL(sylvan_stats_init)
+
+/**
+ * Reset all counters (for statistics)
+ */
+VOID_TASK_DECL_0(sylvan_stats_reset);
+#define sylvan_stats_reset() CALL(sylvan_stats_reset)
+
+/**
+ * Obtain current counts (this stops the world during counting)
+ */
+VOID_TASK_DECL_1(sylvan_stats_snapshot, sylvan_stats_t*);
+#define sylvan_stats_snapshot(target) CALL(sylvan_stats_snapshot, target)
+
+/**
+ * Write statistic report to file (stdout, stderr, etc)
+ */
+void sylvan_stats_report(FILE* target);
+
+#if SYLVAN_STATS
+
+#ifdef __MACH__
+#define getabstime() mach_absolute_time()
+#else
+static uint64_t
+getabstime(void)
+{
+    struct timespec ts;
+    clock_gettime(CLOCK_MONOTONIC, &ts);
+    uint64_t t = ts.tv_sec;
+    t *= 1000000000UL;
+    t += ts.tv_nsec;
+    return t;
+}
+#endif
+
+#ifdef __ELF__
+extern __thread sylvan_stats_t sylvan_stats;
+#else
+extern pthread_key_t sylvan_stats_key;
+#endif
+
+static inline void
+sylvan_stats_count(size_t counter)
+{
+#ifdef __ELF__
+    sylvan_stats.counters[counter]++;
+#else
+    sylvan_stats_t *sylvan_stats = (sylvan_stats_t*)pthread_getspecific(sylvan_stats_key);
+    sylvan_stats->counters[counter]++;
+#endif
+}
+
+static inline void
+sylvan_stats_add(size_t counter, size_t amount)
+{
+#ifdef __ELF__
+    sylvan_stats.counters[counter]+=amount;
+#else
+    sylvan_stats_t *sylvan_stats = (sylvan_stats_t*)pthread_getspecific(sylvan_stats_key);
+    sylvan_stats->counters[counter]+=amount;
+#endif
+}
+
+static inline void
+sylvan_timer_start(size_t timer)
+{
+    uint64_t t = getabstime();
+
+#ifdef __ELF__
+    sylvan_stats.timers_startstop[timer] = t;
+#else
+    sylvan_stats_t *sylvan_stats = (sylvan_stats_t*)pthread_getspecific(sylvan_stats_key);
+    sylvan_stats->timers_startstop[timer] = t;
+#endif
+}
+
+static inline void
+sylvan_timer_stop(size_t timer)
+{
+    uint64_t t = getabstime();
+
+#ifdef __ELF__
+    sylvan_stats.timers[timer] += (t - sylvan_stats.timers_startstop[timer]);
+#else
+    sylvan_stats_t *sylvan_stats = (sylvan_stats_t*)pthread_getspecific(sylvan_stats_key);
+    sylvan_stats->timers[timer] += (t - sylvan_stats->timers_startstop[timer]);
+#endif
+}
+
+#else
+
+static inline void
+sylvan_stats_count(size_t counter)
+{
+    (void)counter;
+}
+
+static inline void
+sylvan_stats_add(size_t counter, size_t amount)
+{
+    (void)counter;
+    (void)amount;
+}
+
+static inline void
+sylvan_timer_start(size_t timer)
+{
+    (void)timer;
+}
+
+static inline void
+sylvan_timer_stop(size_t timer)
+{
+    (void)timer;
+}
+
+#endif
+
+#ifdef __cplusplus
+}
+#endif /* __cplusplus */
+
+#endif
diff --git a/sylvan_table.c b/sylvan_table.c
new file mode 100755
index 0000000000000000000000000000000000000000..623dadce91374b49aa70477dba8eedff53436b31
--- /dev/null
+++ b/sylvan_table.c
@@ -0,0 +1,645 @@
+/*
+ * 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>
+
+#include <errno.h>  // for errno
+#include <string.h> // memset
+#include <sys/mman.h> // for mmap
+
+#ifndef MAP_ANONYMOUS
+#define MAP_ANONYMOUS MAP_ANON
+#endif
+
+#ifndef cas
+#define cas(ptr, old, new) (__sync_bool_compare_and_swap((ptr),(old),(new)))
+#endif
+
+DECLARE_THREAD_LOCAL(my_region, uint64_t);
+
+VOID_TASK_0(llmsset_reset_region)
+{
+    LOCALIZE_THREAD_LOCAL(my_region, uint64_t);
+    my_region = (uint64_t)-1; // no region
+    SET_THREAD_LOCAL(my_region, my_region);
+}
+
+static uint64_t
+claim_data_bucket(const llmsset_t dbs)
+{
+    LOCALIZE_THREAD_LOCAL(my_region, uint64_t);
+
+    for (;;) {
+        if (my_region != (uint64_t)-1) {
+            // find empty bucket in region <my_region>
+            uint64_t *ptr = dbs->bitmap2 + (my_region*8);
+            int i=0;
+            for (;i<8;) {
+                uint64_t v = *ptr;
+                if (v != 0xffffffffffffffffLL) {
+                    int j = __builtin_clzll(~v);
+                    *ptr |= (0x8000000000000000LL>>j);
+                    return (8 * my_region + i) * 64 + j;
+                }
+                i++;
+                ptr++;
+            }
+        } else {
+            // special case on startup or after garbage collection
+            my_region += (lace_get_worker()->worker*(dbs->table_size/(64*8)))/lace_workers();
+        }
+        uint64_t count = dbs->table_size/(64*8);
+        for (;;) {
+            // check if table maybe full
+            if (count-- == 0) return (uint64_t)-1;
+
+            my_region += 1;
+            if (my_region >= (dbs->table_size/(64*8))) my_region = 0;
+
+            // try to claim it
+            uint64_t *ptr = dbs->bitmap1 + (my_region/64);
+            uint64_t mask = 0x8000000000000000LL >> (my_region&63);
+            uint64_t v;
+restart:
+            v = *ptr;
+            if (v & mask) continue; // taken
+            if (cas(ptr, v, v|mask)) break;
+            else goto restart;
+        }
+        SET_THREAD_LOCAL(my_region, my_region);
+    }
+}
+
+static void
+release_data_bucket(const llmsset_t dbs, uint64_t index)
+{
+    uint64_t *ptr = dbs->bitmap2 + (index/64);
+    uint64_t mask = 0x8000000000000000LL >> (index&63);
+    *ptr &= ~mask;
+}
+
+static void
+set_custom_bucket(const llmsset_t dbs, uint64_t index, int on)
+{
+    uint64_t *ptr = dbs->bitmapc + (index/64);
+    uint64_t mask = 0x8000000000000000LL >> (index&63);
+    if (on) *ptr |= mask;
+    else *ptr &= ~mask;
+}
+
+static int
+is_custom_bucket(const llmsset_t dbs, uint64_t index)
+{
+    uint64_t *ptr = dbs->bitmapc + (index/64);
+    uint64_t mask = 0x8000000000000000LL >> (index&63);
+    return (*ptr & mask) ? 1 : 0;
+}
+
+uint64_t
+llmsset_hash(const uint64_t a, const uint64_t b, const uint64_t seed)
+{
+    // The FNV-1a hash for 64 bits
+    const uint64_t prime = 1099511628211;
+    uint64_t hash = seed;
+    hash = (hash ^ a) * prime;
+    hash = (hash ^ b) * prime;
+    return hash ^ (hash>>32);
+}
+
+/*
+ * CL_MASK and CL_MASK_R are for the probe sequence calculation.
+ * With 64 bytes per cacheline, there are 8 64-bit values per cacheline.
+ */
+// The LINE_SIZE is defined in lace.h
+static const uint64_t CL_MASK     = ~(((LINE_SIZE) / 8) - 1);
+static const uint64_t CL_MASK_R   = ((LINE_SIZE) / 8) - 1;
+
+/* 40 bits for the index, 24 bits for the hash */
+#define MASK_INDEX ((uint64_t)0x000000ffffffffff)
+#define MASK_HASH  ((uint64_t)0xffffff0000000000)
+
+static inline uint64_t
+llmsset_lookup2(const llmsset_t dbs, uint64_t a, uint64_t b, int* created, const int custom)
+{
+    uint64_t hash_rehash = 14695981039346656037LLU;
+    if (custom) hash_rehash = dbs->hash_cb(a, b, hash_rehash);
+    else hash_rehash = llmsset_hash(a, b, hash_rehash);
+
+    const uint64_t step = (((hash_rehash >> 20) | 1) << 3);
+    const uint64_t hash = hash_rehash & MASK_HASH;
+    uint64_t idx, last, cidx = 0;
+    int i=0;
+
+#if LLMSSET_MASK
+    last = idx = hash_rehash & dbs->mask;
+#else
+    last = idx = hash_rehash % dbs->table_size;
+#endif
+
+    for (;;) {
+        volatile uint64_t *bucket = dbs->table + idx;
+        uint64_t v = *bucket;
+
+        if (v == 0) {
+            if (cidx == 0) {
+                // Claim data bucket and write data
+                cidx = claim_data_bucket(dbs);
+                if (cidx == (uint64_t)-1) return 0; // failed to claim a data bucket
+                if (custom) dbs->create_cb(&a, &b);
+                uint64_t *d_ptr = ((uint64_t*)dbs->data) + 2*cidx;
+                d_ptr[0] = a;
+                d_ptr[1] = b;
+            }
+            if (cas(bucket, 0, hash | cidx)) {
+                if (custom) set_custom_bucket(dbs, cidx, custom);
+                *created = 1;
+                return cidx;
+            } else {
+                v = *bucket;
+            }
+        }
+
+        if (hash == (v & MASK_HASH)) {
+            uint64_t d_idx = v & MASK_INDEX;
+            uint64_t *d_ptr = ((uint64_t*)dbs->data) + 2*d_idx;
+            if (custom) {
+                if (dbs->equals_cb(a, b, d_ptr[0], d_ptr[1])) {
+                    if (cidx != 0) {
+                        dbs->destroy_cb(a, b);
+                        release_data_bucket(dbs, cidx);
+                    }
+                    *created = 0;
+                    return d_idx;
+                }
+            } else {
+                if (d_ptr[0] == a && d_ptr[1] == b) {
+                    if (cidx != 0) release_data_bucket(dbs, cidx);
+                    *created = 0;
+                    return d_idx;
+                }
+            }
+        }
+
+        sylvan_stats_count(LLMSSET_LOOKUP);
+
+        // find next idx on probe sequence
+        idx = (idx & CL_MASK) | ((idx+1) & CL_MASK_R);
+        if (idx == last) {
+            if (++i == dbs->threshold) return 0; // failed to find empty spot in probe sequence
+
+            // go to next cache line in probe sequence
+            hash_rehash += step;
+
+#if LLMSSET_MASK
+            last = idx = hash_rehash & dbs->mask;
+#else
+            last = idx = hash_rehash % dbs->table_size;
+#endif
+        }
+    }
+}
+
+uint64_t
+llmsset_lookup(const llmsset_t dbs, const uint64_t a, const uint64_t b, int* created)
+{
+    return llmsset_lookup2(dbs, a, b, created, 0);
+}
+
+uint64_t
+llmsset_lookupc(const llmsset_t dbs, const uint64_t a, const uint64_t b, int* created)
+{
+    return llmsset_lookup2(dbs, a, b, created, 1);
+}
+
+int
+llmsset_rehash_bucket(const llmsset_t dbs, uint64_t d_idx)
+{
+    const uint64_t * const d_ptr = ((uint64_t*)dbs->data) + 2*d_idx;
+    const uint64_t a = d_ptr[0];
+    const uint64_t b = d_ptr[1];
+
+    uint64_t hash_rehash = 14695981039346656037LLU;
+    const int custom = is_custom_bucket(dbs, d_idx) ? 1 : 0;
+    if (custom) hash_rehash = dbs->hash_cb(a, b, hash_rehash);
+    else hash_rehash = llmsset_hash(a, b, hash_rehash);
+    const uint64_t step = (((hash_rehash >> 20) | 1) << 3);
+    const uint64_t new_v = (hash_rehash & MASK_HASH) | d_idx;
+    int i=0;
+
+    uint64_t idx, last;
+#if LLMSSET_MASK
+    last = idx = hash_rehash & dbs->mask;
+#else
+    last = idx = hash_rehash % dbs->table_size;
+#endif
+
+    for (;;) {
+        volatile uint64_t *bucket = &dbs->table[idx];
+        if (*bucket == 0 && cas(bucket, 0, new_v)) return 1;
+
+        // find next idx on probe sequence
+        idx = (idx & CL_MASK) | ((idx+1) & CL_MASK_R);
+        if (idx == last) {
+            if (++i == *(volatile int16_t*)&dbs->threshold) {
+                // failed to find empty spot in probe sequence
+                // solution: increase probe sequence length...
+                __sync_fetch_and_add(&dbs->threshold, 1);
+            }
+
+            // go to next cache line in probe sequence
+            hash_rehash += step;
+
+#if LLMSSET_MASK
+            last = idx = hash_rehash & dbs->mask;
+#else
+            last = idx = hash_rehash % dbs->table_size;
+#endif
+        }
+    }
+}
+
+llmsset_t
+llmsset_create(size_t initial_size, size_t max_size)
+{
+    llmsset_t dbs = NULL;
+    if (posix_memalign((void**)&dbs, LINE_SIZE, sizeof(struct llmsset)) != 0) {
+        fprintf(stderr, "llmsset_create: Unable to allocate memory!\n");
+        exit(1);
+    }
+
+#if LLMSSET_MASK
+    /* Check if initial_size and max_size are powers of 2 */
+    if (__builtin_popcountll(initial_size) != 1) {
+        fprintf(stderr, "llmsset_create: initial_size is not a power of 2!\n");
+        exit(1);
+    }
+
+    if (__builtin_popcountll(max_size) != 1) {
+        fprintf(stderr, "llmsset_create: max_size is not a power of 2!\n");
+        exit(1);
+    }
+#endif
+
+    if (initial_size > max_size) {
+        fprintf(stderr, "llmsset_create: initial_size > max_size!\n");
+        exit(1);
+    }
+
+    // minimum size is now 512 buckets (region size, but of course, n_workers * 512 is suggested as minimum)
+
+    if (initial_size < 512) {
+        fprintf(stderr, "llmsset_create: initial_size too small!\n");
+        exit(1);
+    }
+
+    dbs->max_size = max_size;
+    llmsset_set_size(dbs, initial_size);
+
+    /* This implementation of "resizable hash table" allocates the max_size table in virtual memory,
+       but only uses the "actual size" part in real memory */
+
+    dbs->table = (uint64_t*)mmap(0, dbs->max_size * 8, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
+    dbs->data = (uint8_t*)mmap(0, dbs->max_size * 16, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
+
+    /* Also allocate bitmaps. Each region is 64*8 = 512 buckets.
+       Overhead of bitmap1: 1 bit per 4096 bucket.
+       Overhead of bitmap2: 1 bit per bucket.
+       Overhead of bitmapc: 1 bit per bucket. */
+
+    dbs->bitmap1 = (uint64_t*)mmap(0, dbs->max_size / (512*8), PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
+    dbs->bitmap2 = (uint64_t*)mmap(0, dbs->max_size / 8, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
+    dbs->bitmapc = (uint64_t*)mmap(0, dbs->max_size / 8, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
+
+    if (dbs->table == (uint64_t*)-1 || dbs->data == (uint8_t*)-1 || dbs->bitmap1 == (uint64_t*)-1 || dbs->bitmap2 == (uint64_t*)-1 || dbs->bitmapc == (uint64_t*)-1) {
+        fprintf(stderr, "llmsset_create: Unable to allocate memory: %s!\n", strerror(errno));
+        exit(1);
+    }
+
+#if defined(madvise) && defined(MADV_RANDOM)
+    madvise(dbs->table, dbs->max_size * 8, MADV_RANDOM);
+#endif
+
+    // forbid first two positions (index 0 and 1)
+    dbs->bitmap2[0] = 0xc000000000000000LL;
+
+    dbs->hash_cb = NULL;
+    dbs->equals_cb = NULL;
+    dbs->create_cb = NULL;
+    dbs->destroy_cb = NULL;
+
+    // yes, ugly. for now, we use a global thread-local value.
+    // that is a problem with multiple tables.
+    // so, for now, do NOT use multiple tables!!
+
+    LACE_ME;
+    INIT_THREAD_LOCAL(my_region);
+    TOGETHER(llmsset_reset_region);
+
+    return dbs;
+}
+
+void
+llmsset_free(llmsset_t dbs)
+{
+    munmap(dbs->table, dbs->max_size * 8);
+    munmap(dbs->data, dbs->max_size * 16);
+    munmap(dbs->bitmap1, dbs->max_size / (512*8));
+    munmap(dbs->bitmap2, dbs->max_size / 8);
+    munmap(dbs->bitmapc, dbs->max_size / 8);
+    free(dbs);
+}
+
+VOID_TASK_IMPL_1(llmsset_clear, llmsset_t, dbs)
+{
+    CALL(llmsset_clear_data, dbs);
+    CALL(llmsset_clear_hashes, dbs);
+}
+
+VOID_TASK_IMPL_1(llmsset_clear_data, llmsset_t, dbs)
+{
+    if (mmap(dbs->bitmap1, dbs->max_size / (512*8), PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS | MAP_FIXED, -1, 0) != (void*)-1) {
+    } else {
+        memset(dbs->bitmap1, 0, dbs->max_size / (512*8));
+    }
+
+    if (mmap(dbs->bitmap2, dbs->max_size / 8, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS | MAP_FIXED, -1, 0) != (void*)-1) {
+    } else {
+        memset(dbs->bitmap2, 0, dbs->max_size / 8);
+    }
+
+    // forbid first two positions (index 0 and 1)
+    dbs->bitmap2[0] = 0xc000000000000000LL;
+
+    TOGETHER(llmsset_reset_region);
+}
+
+VOID_TASK_IMPL_1(llmsset_clear_hashes, llmsset_t, dbs)
+{
+    // just reallocate...
+    if (mmap(dbs->table, dbs->max_size * 8, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS | MAP_FIXED, -1, 0) != (void*)-1) {
+#if defined(madvise) && defined(MADV_RANDOM)
+        madvise(dbs->table, sizeof(uint64_t[dbs->max_size]), MADV_RANDOM);
+#endif
+    } else {
+        // reallocate failed... expensive fallback
+        memset(dbs->table, 0, dbs->max_size * 8);
+    }
+}
+
+int
+llmsset_is_marked(const llmsset_t dbs, uint64_t index)
+{
+    volatile uint64_t *ptr = dbs->bitmap2 + (index/64);
+    uint64_t mask = 0x8000000000000000LL >> (index&63);
+    return (*ptr & mask) ? 1 : 0;
+}
+
+int
+llmsset_mark(const llmsset_t dbs, uint64_t index)
+{
+    volatile uint64_t *ptr = dbs->bitmap2 + (index/64);
+    uint64_t mask = 0x8000000000000000LL >> (index&63);
+    for (;;) {
+        uint64_t v = *ptr;
+        if (v & mask) return 0;
+        if (cas(ptr, v, v|mask)) return 1;
+    }
+}
+
+TASK_3(int, llmsset_rehash_par, llmsset_t, dbs, size_t, first, size_t, count)
+{
+    if (count > 512) {
+        SPAWN(llmsset_rehash_par, dbs, first, count/2);
+        int bad = CALL(llmsset_rehash_par, dbs, first + count/2, count - count/2);
+        return bad + SYNC(llmsset_rehash_par);
+    } else {
+        int bad = 0;
+        uint64_t *ptr = dbs->bitmap2 + (first / 64);
+        uint64_t mask = 0x8000000000000000LL >> (first & 63);
+        for (size_t k=0; k<count; k++) {
+            if (*ptr & mask) {
+                if (llmsset_rehash_bucket(dbs, first+k) == 0) bad++;
+            }
+            mask >>= 1;
+            if (mask == 0) {
+                ptr++;
+                mask = 0x8000000000000000LL;
+            }
+        }
+        return bad;
+    }
+}
+
+TASK_IMPL_1(int, llmsset_rehash, llmsset_t, dbs)
+{
+    return CALL(llmsset_rehash_par, dbs, 0, dbs->table_size);
+}
+
+TASK_3(size_t, llmsset_count_marked_par, llmsset_t, dbs, size_t, first, size_t, count)
+{
+    if (count > 512) {
+        size_t split = count/2;
+        SPAWN(llmsset_count_marked_par, dbs, first, split);
+        size_t right = CALL(llmsset_count_marked_par, dbs, first + split, count - split);
+        size_t left = SYNC(llmsset_count_marked_par);
+        return left + right;
+    } else {
+        size_t result = 0;
+        uint64_t *ptr = dbs->bitmap2 + (first / 64);
+        if (count == 512) {
+            result += __builtin_popcountll(ptr[0]);
+            result += __builtin_popcountll(ptr[1]);
+            result += __builtin_popcountll(ptr[2]);
+            result += __builtin_popcountll(ptr[3]);
+            result += __builtin_popcountll(ptr[4]);
+            result += __builtin_popcountll(ptr[5]);
+            result += __builtin_popcountll(ptr[6]);
+            result += __builtin_popcountll(ptr[7]);
+        } else {
+            uint64_t mask = 0x8000000000000000LL >> (first & 63);
+            for (size_t k=0; k<count; k++) {
+                if (*ptr & mask) result += 1;
+                mask >>= 1;
+                if (mask == 0) {
+                    ptr++;
+                    mask = 0x8000000000000000LL;
+                }
+            }
+        }
+        return result;
+    }
+}
+
+TASK_IMPL_1(size_t, llmsset_count_marked, llmsset_t, dbs)
+{
+    return CALL(llmsset_count_marked_par, dbs, 0, dbs->table_size);
+}
+
+VOID_TASK_3(llmsset_destroy_par, llmsset_t, dbs, size_t, first, size_t, count)
+{
+    if (count > 1024) {
+        size_t split = count/2;
+        SPAWN(llmsset_destroy_par, dbs, first, split);
+        CALL(llmsset_destroy_par, dbs, first + split, count - split);
+        SYNC(llmsset_destroy_par);
+    } else {
+        for (size_t k=first; k<first+count; k++) {
+            volatile uint64_t *ptr2 = dbs->bitmap2 + (k/64);
+            volatile uint64_t *ptrc = dbs->bitmapc + (k/64);
+            uint64_t mask = 0x8000000000000000LL >> (k&63);
+
+            // if not marked but is custom
+            if ((*ptr2 & mask) == 0 && (*ptrc & mask)) {
+                uint64_t *d_ptr = ((uint64_t*)dbs->data) + 2*k;
+                dbs->destroy_cb(d_ptr[0], d_ptr[1]);
+                *ptrc &= ~mask;
+            }
+        }
+    }
+}
+
+VOID_TASK_IMPL_1(llmsset_destroy_unmarked, llmsset_t, dbs)
+{
+    if (dbs->destroy_cb == NULL) return; // no custom function
+    CALL(llmsset_destroy_par, dbs, 0, dbs->table_size);
+}
+
+/**
+ * Set custom functions
+ */
+void llmsset_set_custom(const llmsset_t dbs, llmsset_hash_cb hash_cb, llmsset_equals_cb equals_cb, llmsset_create_cb create_cb, llmsset_destroy_cb destroy_cb)
+{
+    dbs->hash_cb = hash_cb;
+    dbs->equals_cb = equals_cb;
+    dbs->create_cb = create_cb;
+    dbs->destroy_cb = destroy_cb;
+}
+
+/***************************************************************/
+
+void llmsset_clear_data_seq(llmsset_t dbs)
+{
+    if (mmap(dbs->bitmap1, dbs->max_size / (512*8), PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS | MAP_FIXED, -1, 0) != (void*)-1) {
+
+    } else {
+
+        memset(dbs->bitmap1, 0, dbs->max_size / (512*8));
+    }
+
+    if (mmap(dbs->bitmap2, dbs->max_size / 8, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS | MAP_FIXED, -1, 0) != (void*)-1) {
+
+    } else {
+        memset(dbs->bitmap2, 0, dbs->max_size / 8);
+
+    }
+
+    // forbid first two positions (index 0 and 1)
+    dbs->bitmap2[0] = 0xc000000000000000LL;
+    /*LACE_ME;
+    TOGETHER(llmsset_reset_region);*/
+}
+
+
+
+void llmsset_clear_hashes_seq( llmsset_t dbs)
+{
+    // just reallocate...
+    if (mmap(dbs->table, dbs->max_size * 8, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS | MAP_FIXED, -1, 0) != (void*)-1) {
+#if defined(madvise) && defined(MADV_RANDOM)
+        madvise(dbs->table, sizeof(uint64_t[dbs->max_size]), MADV_RANDOM);
+#endif
+    } else {
+        // reallocate failed... expensive fallback
+        memset(dbs->table, 0, dbs->max_size * 8);
+    }
+}
+
+
+
+int llmsset_rehash_seq (llmsset_t dbs)
+{
+    return llmsset_rehash_par_seq( dbs, 0, dbs->table_size);
+}
+
+
+int llmsset_rehash_par_seq(llmsset_t  dbs, size_t  first, size_t  count)
+{
+    if (count > 512) {
+
+        int bad = llmsset_rehash_par_seq(dbs, first + count/2, count - count/2);
+        return bad + llmsset_rehash_par_seq(dbs, first, count/2);
+    } else {
+        int bad = 0;
+        uint64_t *ptr = dbs->bitmap2 + (first / 64);
+        uint64_t mask = 0x8000000000000000LL >> (first & 63);
+        for (size_t k=0; k<count; k++) {
+            if (*ptr & mask) {
+                if (llmsset_rehash_bucket(dbs, first+k) == 0) bad++;
+            }
+            mask >>= 1;
+            if (mask == 0) {
+                ptr++;
+                mask = 0x8000000000000000LL;
+            }
+        }
+        return bad;
+    }
+}
+
+
+
+/************** Sequential versions ************/
+size_t seq_llmsset_count_marked(llmsset_t dbs)
+{
+    return llmsset_count_marked_seq (dbs, 0, dbs->table_size);
+}
+
+//********************************************
+size_t llmsset_count_marked_seq(llmsset_t dbs, size_t first, size_t count)
+{
+    if (count > 512) {
+        size_t split = count/2;
+
+        size_t right = llmsset_count_marked_seq( dbs, first + split, count - split);
+        size_t left = llmsset_count_marked_seq( dbs, first, split);
+        return left + right;
+    } else {
+        size_t result = 0;
+        uint64_t *ptr = dbs->bitmap2 + (first / 64);
+        if (count == 512) {
+            result += __builtin_popcountll(ptr[0]);
+            result += __builtin_popcountll(ptr[1]);
+            result += __builtin_popcountll(ptr[2]);
+            result += __builtin_popcountll(ptr[3]);
+            result += __builtin_popcountll(ptr[4]);
+            result += __builtin_popcountll(ptr[5]);
+            result += __builtin_popcountll(ptr[6]);
+            result += __builtin_popcountll(ptr[7]);
+        } else {
+            uint64_t mask = 0x8000000000000000LL >> (first & 63);
+            for (size_t k=0; k<count; k++) {
+                if (*ptr & mask) result += 1;
+                mask >>= 1;
+                if (mask == 0) {
+                    ptr++;
+                    mask = 0x8000000000000000LL;
+                }
+            }
+        }
+        return result;
+    }
+}
diff --git a/sylvan_table.h b/sylvan_table.h
new file mode 100755
index 0000000000000000000000000000000000000000..c0495a4f34e5efa7385c1a653e8a37adb61d977d
--- /dev/null
+++ b/sylvan_table.h
@@ -0,0 +1,218 @@
+/*
+ * 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.
+ */
+
+/* Do not include this file directly. Instead, include sylvan_int.h */
+
+#ifndef SYLVAN_TABLE_H
+#define SYLVAN_TABLE_H
+
+#ifdef __cplusplus
+extern "C" {
+#endif /* __cplusplus */
+
+/**
+ * Lockless hash table (set) to store 16-byte keys.
+ * Each unique key is associated with a 42-bit number.
+ *
+ * The set has support for stop-the-world garbage collection.
+ * Methods llmsset_clear, llmsset_mark and llmsset_rehash implement garbage collection.
+ * During their execution, llmsset_lookup is not allowed.
+ *
+ * WARNING: Originally, this table is designed to allow multiple tables.
+ * However, this is not compatible with thread local storage for now.
+ * Do not use multiple tables.
+ */
+
+/**
+ * hash(a, b, seed)
+ * equals(lhs_a, lhs_b, rhs_a, rhs_b)
+ * create(a, b) -- with a,b pointers, allows changing pointers on create of node,
+ *                 but must keep hash/equals same!
+ * destroy(a, b)
+ */
+typedef uint64_t (*llmsset_hash_cb)(uint64_t, uint64_t, uint64_t);
+typedef int (*llmsset_equals_cb)(uint64_t, uint64_t, uint64_t, uint64_t);
+typedef void (*llmsset_create_cb)(uint64_t *, uint64_t *);
+typedef void (*llmsset_destroy_cb)(uint64_t, uint64_t);
+
+typedef struct llmsset
+{
+    uint64_t          *table;       // table with hashes
+    uint8_t           *data;        // table with values
+    uint64_t          *bitmap1;     // ownership bitmap (per 512 buckets)
+    uint64_t          *bitmap2;     // bitmap for "contains data"
+    uint64_t          *bitmapc;     // bitmap for "use custom functions"
+    size_t            max_size;     // maximum size of the hash table (for resizing)
+    size_t            table_size;   // size of the hash table (number of slots) --> power of 2!
+#if LLMSSET_MASK
+    size_t            mask;         // size-1
+#endif
+    size_t            f_size;
+    llmsset_hash_cb   hash_cb;      // custom hash function
+    llmsset_equals_cb equals_cb;    // custom equals function
+    llmsset_create_cb create_cb;    // custom create function
+    llmsset_destroy_cb destroy_cb;  // custom destroy function
+    int16_t           threshold;    // number of iterations for insertion until returning error
+} *llmsset_t;
+
+/**
+ * Retrieve a pointer to the data associated with the 42-bit value.
+ */
+static inline void*
+llmsset_index_to_ptr(const llmsset_t dbs, size_t index)
+{
+    return dbs->data + index * 16;
+}
+
+/**
+ * Create the set.
+ * This will allocate a set of <max_size> buckets in virtual memory.
+ * The actual space used is <initial_size> buckets.
+ */
+llmsset_t llmsset_create(size_t initial_size, size_t max_size);
+
+/**
+ * Free the set.
+ */
+void llmsset_free(llmsset_t dbs);
+
+/**
+ * Retrieve the maximum size of the set.
+ */
+static inline size_t
+llmsset_get_max_size(const llmsset_t dbs)
+{
+    return dbs->max_size;
+}
+
+/**
+ * Retrieve the current size of the lockless MS set.
+ */
+static inline size_t
+llmsset_get_size(const llmsset_t dbs)
+{
+    return dbs->table_size;
+}
+
+/**
+ * Set the table size of the set.
+ * Typically called during garbage collection, after clear and before rehash.
+ * Returns 0 if dbs->table_size > dbs->max_size!
+ */
+static inline void
+llmsset_set_size(llmsset_t dbs, size_t size)
+{
+    /* check bounds (don't be rediculous) */
+    if (size > 128 && size <= dbs->max_size) {
+        dbs->table_size = size;
+#if LLMSSET_MASK
+        /* Warning: if size is not a power of two, you will get interesting behavior */
+        dbs->mask = dbs->table_size - 1;
+#endif
+        /* Set threshold: number of cache lines to probe before giving up on node insertion */
+        dbs->threshold = 192 - 2 * __builtin_clzll(dbs->table_size);
+    }
+}
+
+/**
+ * Core function: find existing data or add new.
+ * Returns the unique 42-bit value associated with the data, or 0 when table is full.
+ * Also, this value will never equal 0 or 1.
+ * Note: garbage collection during lookup strictly forbidden
+ */
+uint64_t llmsset_lookup(const llmsset_t dbs, const uint64_t a, const uint64_t b, int *created);
+
+/**
+ * Same as lookup, but use the custom functions
+ */
+uint64_t llmsset_lookupc(const llmsset_t dbs, const uint64_t a, const uint64_t b, int *created);
+
+/**
+ * To perform garbage collection, the user is responsible that no lookups are performed during the process.
+ *
+ * 1) call llmsset_clear
+ * 2) call llmsset_mark for every bucket to rehash
+ * 3) call llmsset_rehash
+ */
+VOID_TASK_DECL_1(llmsset_clear, llmsset_t);
+#define llmsset_clear(dbs) CALL(llmsset_clear, dbs)
+
+VOID_TASK_DECL_1(llmsset_clear_data, llmsset_t);
+#define llmsset_clear_data(dbs) CALL(llmsset_clear_data, dbs)
+
+VOID_TASK_DECL_1(llmsset_clear_hashes, llmsset_t);
+#define llmsset_clear_hashes(dbs) CALL(llmsset_clear_hashes, dbs)
+
+/**
+ * Check if a certain data bucket is marked (in use).
+ */
+int llmsset_is_marked(const llmsset_t dbs, uint64_t index);
+
+/**
+ * During garbage collection, buckets are marked (for rehashing) with this function.
+ * Returns 0 if the node was already marked, or non-zero if it was not marked.
+ * May also return non-zero if multiple workers marked at the same time.
+ */
+int llmsset_mark(const llmsset_t dbs, uint64_t index);
+
+/**
+ * Rehash all marked buckets.
+ * Returns 0 if successful, or the number of buckets not rehashed if not.
+ */
+TASK_DECL_1(int, llmsset_rehash, llmsset_t);
+#define llmsset_rehash(dbs) CALL(llmsset_rehash, dbs)
+
+/**
+ * Rehash a single bucket.
+ * Returns 0 if successful, or 1 if not.
+ */
+int llmsset_rehash_bucket(const llmsset_t dbs, uint64_t d_idx);
+
+/**
+ * Retrieve number of marked buckets.
+ */
+TASK_DECL_1(size_t, llmsset_count_marked, llmsset_t);
+#define llmsset_count_marked(dbs) CALL(llmsset_count_marked, dbs)
+
+/**
+ * During garbage collection, this method calls the destroy callback
+ * for all 'custom' data that is not kept.
+ */
+VOID_TASK_DECL_1(llmsset_destroy_unmarked, llmsset_t);
+#define llmsset_destroy_unmarked(dbs) CALL(llmsset_destroy_unmarked, dbs)
+
+/**
+ * Set custom functions
+ */
+void llmsset_set_custom(const llmsset_t dbs, llmsset_hash_cb hash_cb, llmsset_equals_cb equals_cb, llmsset_create_cb create_cb, llmsset_destroy_cb destroy_cb);
+
+/**
+ * Default hashing function
+ */
+uint64_t llmsset_hash(const uint64_t a, const uint64_t b, const uint64_t seed);
+
+
+void llmsset_clear_data_seq(llmsset_t dbs);
+
+size_t seq_llmsset_count_marked(llmsset_t dbs);
+size_t llmsset_count_marked_seq(llmsset_t dbs, size_t first, size_t count);
+
+#ifdef __cplusplus
+}
+#endif /* __cplusplus */
+
+#endif
diff --git a/sylvan_tls.h b/sylvan_tls.h
new file mode 100755
index 0000000000000000000000000000000000000000..09958b2d32fc1f7ecd96e024d424caab7cfdc5cf
--- /dev/null
+++ b/sylvan_tls.h
@@ -0,0 +1,32 @@
+/*
+ * Written by Josh Dybnis and released to the public domain, as explained at
+ * http://creativecommons.org/licenses/publicdomain
+ *
+ * A platform independant wrapper around thread-local storage. On platforms that don't support
+ * __thread variables (e.g. Mac OS X), we have to use the pthreads library for thread-local storage
+ */
+
+#ifndef TLS_H
+#define TLS_H
+
+#ifdef __ELF__ // use gcc thread-local storage (i.e. __thread variables)
+#define DECLARE_THREAD_LOCAL(name, type) __thread type name
+#define INIT_THREAD_LOCAL(name)
+#define SET_THREAD_LOCAL(name, value) name = value
+#define LOCALIZE_THREAD_LOCAL(name, type)
+
+#else//!__ELF__
+
+#define DECLARE_THREAD_LOCAL(name, type) pthread_key_t name##_KEY
+
+#define INIT_THREAD_LOCAL(name) \
+    do { \
+        if (pthread_key_create(&name##_KEY, NULL) != 0) { assert(0); } \
+    } while (0)
+
+#define SET_THREAD_LOCAL(name, value) pthread_setspecific(name##_KEY, (void *)(size_t)value);
+
+#define LOCALIZE_THREAD_LOCAL(name, type) type name = (type)(size_t)pthread_getspecific(name##_KEY)
+
+#endif//__ELF__
+#endif//TLS_H
diff --git a/tls.h b/tls.h
old mode 100644
new mode 100755