You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
concurrentqueue/tests/CDSChecker/corealgo.h

115 lines
5.0 KiB
C++

// ©2013 Cameron Desrochers
// Provides the core enqueue/dequeue algorithm of moodycamel::ConcurrentQueue
// for testing with CDSChecker (a C++11 memory model checking tool).
// See http://demsky.eecs.uci.edu/c11modelchecker.html for more info.
#pragma once
#include "model-checker/include/atomic"
#include "model-checker/include/librace.h"
#include "model-checker/include/model-assert.h"
#ifndef CHAR_BIT
#define CHAR_BIT 8
#endif
typedef unsigned int index_t;
static std::atomic<index_t> headIndex;
static std::atomic<index_t> tailIndex;
static std::atomic<index_t> dequeueOvercommit;
static std::atomic<index_t> dequeueOptimisticCount;
static const unsigned int BLOCK_SIZE = 256;
static int block[BLOCK_SIZE];
static void init()
{
headIndex.store(0, std::memory_order_relaxed);
tailIndex.store(0, std::memory_order_relaxed);
dequeueOvercommit.store(0, std::memory_order_relaxed);
dequeueOptimisticCount.store(0, std::memory_order_relaxed);
}
template<typename T>
static inline bool circular_less_than(T a, T b)
{
return static_cast<T>(a - b) > static_cast<T>(static_cast<T>(1) << static_cast<T>(sizeof(T) * CHAR_BIT - 1));
}
static void enqueue(int element)
{
index_t currentTailIndex = tailIndex.load(std::memory_order_relaxed);
index_t newTailIndex = 1 + currentTailIndex;
store_32(&block[currentTailIndex & (BLOCK_SIZE - 1)], element);
tailIndex.store(newTailIndex, std::memory_order_release);
}
static bool try_dequeue(int& element)
{
auto tail = tailIndex.load(std::memory_order_relaxed);
auto overcommit = dequeueOvercommit.load(std::memory_order_relaxed);
if (circular_less_than<index_t>(dequeueOptimisticCount.load(std::memory_order_relaxed) - overcommit, tail)) {
// Might be something to dequeue, let's give it a try
// Note that this if is purely for performance purposes in the common case when the queue is
// empty and the values are eventually consistent -- we may enter here spuriously.
// Note that whatever the values of overcommit and tail are, they are not going to change (unless we
// change them) and must be the same value at this point (inside the if) as when the if condition was
// evaluated.
// We insert an acquire fence here to synchronize-with the release upon incrementing dequeueOvercommit below.
// This ensures that whatever the value we got loaded into overcommit, the load of dequeueOptisticCount in
// the fetch_add below will result in a value at least as recent as that (and therefore at least as large).
// Note that I believe a compiler (signal) fence here would be sufficient due to the nature of fetch_add (all
// read-modify-write operations are guaranteed to work on the latest value in the modification order), but
// unfortunately that can't be shown to be correct using only the C++11 standard.
// See http://stackoverflow.com/questions/18223161/what-are-the-c11-memory-ordering-guarantees-in-this-corner-case
std::atomic_thread_fence(std::memory_order_acquire);
// Increment optimistic counter, then check if it went over the boundary
auto myDequeueCount = dequeueOptimisticCount.fetch_add(1, std::memory_order_relaxed);
// Note that since dequeueOvercommit must be <= dequeueOptimisticCount (because dequeueOvercommit is only ever
// incremented after dequeueOptimisticCount -- this is enforced in the `else` block below), and since we now
// have a version of dequeueOptimisticCount that is at least as recent as overcommit (due to the release upon
// incrementing dequeueOvercommit and the acquire above that synchronizes with it), overcommit <= myDequeueCount.
MODEL_ASSERT(overcommit <= myDequeueCount);
// Note that we reload tail here in case it changed; it will be the same value as before or greater, since
// this load is sequenced after (happens after) the earlier load above. This is supported by read-read
// coherance (as defined in the standard), explained here: http://en.cppreference.com/w/cpp/atomic/memory_order
auto newTail = tailIndex.load(std::memory_order_acquire);
MODEL_ASSERT(newTail >= tail);
tail = newTail;
if (circular_less_than<index_t>(myDequeueCount - overcommit, tail)) {
// Guaranteed to be at least one element to dequeue!
// Get the index. Note that since there's guaranteed to be at least one element, this
// will never exceed the true value of tail (but may exceed the value we read above).
auto index = headIndex.fetch_add(1, std::memory_order_acq_rel);
// Dequeue
element = load_32(&block[index & (BLOCK_SIZE - 1)]);
return true;
}
else {
// Wasn't anything to dequeue after all; make the effective dequeue count eventually consistent
dequeueOvercommit.fetch_add(1, std::memory_order_release); // Release so that the fetch_add on dequeueOptimisticCount is guaranteed to happen before this write
}
}
return false;
}
static int size_approx()
{
auto tail = tailIndex.load(std::memory_order_relaxed);
auto head = headIndex.load(std::memory_order_relaxed);
return circular_less_than(head, tail) ? static_cast<int>(tail - head) : 0;
}