/*
Word-Based Portable Proxy Garbage Collector
Copyright (C) 2010 Christopher Michael Thomasson
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see .
*/
//#define RL_DEBUGBREAK_ON_ASSERT
//#define RL_MSVC_OUTPUT
//#define RL_FORCE_SEQ_CST
#include
#include
#include
#if ! defined (NDEBUG)
# define DBG_PRINTF(e) std::printf e
#else
# define DBG_PRINTF(e) ((void)0)
#endif
struct node
{
std::atomic m_next;
VAR_T(node*) m_defer_next;
};
template
class proxy
{
static void prv_destroy(node* n)
{
while (n)
{
node* next = VAR(n->m_defer_next);
delete n;
n = next;
}
}
public:
class collector
{
friend class proxy;
private:
std::atomic m_defer;
std::atomic m_defer_count;
std::atomic m_count;
public:
collector()
: m_defer(NULL),
m_defer_count(0),
m_count(0)
{
}
~collector()
{
prv_destroy(m_defer.load(std::memory_order_relaxed));
}
};
private:
std::atomic m_current;
std::atomic m_quiesce;
VAR_T(node*) m_defer;
collector m_collectors[2];
public:
proxy()
: m_current(0),
m_quiesce(false),
m_defer(NULL)
{
}
~proxy()
{
prv_destroy(VAR(m_defer));
}
private:
void prv_quiesce_begin()
{
// Try to begin the quiescence process.
if (! m_quiesce.exchange(true, std::memory_order_acquire))
{
// advance the current collector and grab the old one.
unsigned int old = m_current.load(std::memory_order_relaxed) & 0xFU;
old = m_current.exchange((old + 1) & 1, std::memory_order_acq_rel);
collector& c = m_collectors[old & 0xFU];
// decode reference count.
unsigned int refs = old & 0xFFFFFFF0U;
// verify reference count and previous collector index.
RL_ASSERT(! (refs & 0x10U) && (old & 0xFU) == (&c - m_collectors));
// increment and generate an odd reference count.
if (c.m_count.fetch_add(refs + 0x10U, std::memory_order_release) == -refs)
{
// odd reference count and drop-to-zero condition detected!
prv_quiesce_complete(c);
}
}
}
void prv_quiesce_complete(collector& c)
{
// the collector `c' is now in a quiescent state! :^)
std::atomic_thread_fence(std::memory_order_acquire);
// maintain the back link and obtain "fresh" objects from
// this collection.
node* n = VAR(m_defer);
VAR(m_defer) = c.m_defer.load(std::memory_order_relaxed);
c.m_defer.store(0, std::memory_order_relaxed);
// verify and reset the reference count.
RL_ASSERT(c.m_count.load(std::memory_order_relaxed) == 0x10U);
c.m_count.store(0, std::memory_order_relaxed);
c.m_defer_count.store(0, std::memory_order_relaxed);
// release the quiesce lock.
m_quiesce.store(false, std::memory_order_release);
// destroy nodes.
prv_destroy(n);
}
public:
collector& acquire()
{
// increment the master count _and_ obtain current collector.
unsigned int current =
m_current.fetch_add(0x20U, std::memory_order_acquire);
// decode the collector index.
return m_collectors[current & 0xFU];
}
void release(collector& c)
{
// decrement the collector.
unsigned int count =
c.m_count.fetch_sub(0x20U, std::memory_order_release);
// check for the completion of the quiescence process.
if ((count & 0xFFFFFFF0U) == 0x30U)
{
// odd reference count and drop-to-zero condition detected!
prv_quiesce_complete(c);
}
}
collector& sync(collector& c)
{
// check if the `c' is in the middle of a quiescence process.
if (c.m_count.load(std::memory_order_relaxed) & 0x10U)
{
// drop `c' and get the next collector.
release(c);
return acquire();
}
return c;
}
void collect()
{
prv_quiesce_begin();
}
void collect(collector& c, node* n)
{
if (! n) return;
// link node into the defer list.
node* prev = c.m_defer.exchange(n, std::memory_order_relaxed);
VAR(n->m_defer_next) = prev;
// bump the defer count and begin quiescence process if over
// the limit.
unsigned int count =
c.m_defer_count.fetch_add(1, std::memory_order_relaxed) + 1;
if (count >= (T_defer_limit / 2))
{
prv_quiesce_begin();
}
}
};
// you're basic lock-free stack...
// well, minus ABA counter and DWCAS of course! ;^)
class stack
{
std::atomic m_head;
public:
stack()
: m_head(NULL)
{
}
public:
void push(node* n)
{
node* head = m_head.load(std::memory_order_relaxed);
do
{
n->m_next.store(head, std::memory_order_relaxed);
}
while (! m_head.compare_exchange_weak(
head,
n,
std::memory_order_release));
}
node* flush()
{
return m_head.exchange(NULL, std::memory_order_acquire);
}
node* get_head()
{
return m_head.load(std::memory_order_acquire);
}
node* pop()
{
node* head = m_head.load(std::memory_order_acquire);
node* xchg;
do
{
if (! head) return NULL;
xchg = head->m_next.load(std::memory_order_relaxed);
}
while (! m_head.compare_exchange_weak(
head,
xchg,
std::memory_order_acquire));
return head;
}
};
#define ITERS 15
#define DEFER 6
#define WRITERS 2
#define READERS 3
#define REAPERS 1
#define THREADS (WRITERS + READERS + REAPERS)
struct proxy_test
: rl::test_suite
{
typedef proxy proxy_type;
proxy_type g_proxy;
stack g_stack;
unsigned g_writers; // for proper test termination only.
void before()
{
g_writers = WRITERS;
}
void thread(unsigned int tidx)
{
rl::backoff b;
if (tidx < READERS)
{
proxy_type::collector* c = &g_proxy.acquire();
// readers.
while (g_writers)
{
node* n = g_stack.get_head();
while (n)
{
node* next = n->m_next.load(std::memory_order_relaxed);
n = next;
}
c = &g_proxy.sync(*c);
b.yield($);
}
g_proxy.release(*c);
}
else if (tidx < WRITERS + READERS)
{
// writers.
unsigned count = 0;
for (unsigned int i = 0; i < ITERS; ++i)
{
g_stack.push(new node);
if (! (i % 2))
{
proxy_type::collector& c = g_proxy.acquire();
g_proxy.collect(c, g_stack.pop());
g_proxy.release(c);
b.yield($);
}
}
for (unsigned int i = count; i < ITERS; ++i)
{
proxy_type::collector& c = g_proxy.acquire();
g_proxy.collect(c, g_stack.pop());
g_proxy.release(c);
}
--g_writers;
}
else if (tidx < WRITERS + READERS + REAPERS)
{
// reapers.
while (g_writers)
{
g_proxy.collect();
b.yield($);
}
}
}
};
int main()
{
{
rl::test_params p;
p.iteration_count = 10000000;
p.execution_depth_limit = 10000;
//p.search_type = rl::sched_bound;
//p.search_type = rl::fair_full_search_scheduler_type;
//p.search_type = rl::fair_context_bound_scheduler_type;
rl::simulate(p);
}
return 0;
}