-DIRS := barrier mcs-lock mpmc-queue spsc-queue spsc-bugfix linuxrwlocks \
- dekker-fences chase-lev-deque ms-queue chase-lev-deque-bugfix seqlock \
- treiber-stack cliffc-hashtable concurrent-hashmap
+DIRS := register-acqrel register-relaxed ms-queue linuxrwlocks mcs-lock \
+ chase-lev-deque-bugfix treiber-stack ticket-lock seqlock read-copy-update \
+ concurrent-hashmap spsc-bugfix mpmc-queue barrier \
+ chase-lev-deque-bugfix-loose ms-queue-loose blocking-mpmc-example
.PHONY: $(DIRS)
+++ /dev/null
-include ../benchmarks.mk
-
-TESTNAME = barrier
-
-all: $(TESTNAME)
-
-$(TESTNAME): $(TESTNAME).cc $(TESTNAME).h
- $(CXX) -o $@ $< $(CXXFLAGS) $(LDFLAGS)
-
-clean:
- rm -f $(TESTNAME) *.o
+++ /dev/null
-#include <stdio.h>
-#include <threads.h>
-
-#include "barrier.h"
-
-#include "librace.h"
-
-spinning_barrier *barr;
-int var = 0;
-
-void threadA(void *arg)
-{
- store_32(&var, 1);
- barr->wait();
-}
-
-void threadB(void *arg)
-{
- barr->wait();
- printf("var = %d\n", load_32(&var));
-}
-
-#define NUMREADERS 1
-int user_main(int argc, char **argv)
-{
- thrd_t A, B[NUMREADERS];
- int i;
-
- barr = new spinning_barrier(NUMREADERS + 1);
-
- thrd_create(&A, &threadA, NULL);
- for (i = 0; i < NUMREADERS; i++)
- thrd_create(&B[i], &threadB, NULL);
-
- for (i = 0; i < NUMREADERS; i++)
- thrd_join(B[i]);
- thrd_join(A);
-
- return 0;
-}
+++ /dev/null
-#include <atomic>
-
-class spinning_barrier {
- public:
- spinning_barrier (unsigned int n) : n_ (n) {
- nwait_ = 0;
- step_ = 0;
- }
-
- bool wait() {
- unsigned int step = step_.load ();
-
- if (nwait_.fetch_add (1) == n_ - 1) {
- /* OK, last thread to come. */
- nwait_.store (0); // XXX: maybe can use relaxed ordering here ??
- step_.fetch_add (1);
- return true;
- } else {
- /* Run in circles and scream like a little girl. */
- while (step_.load () == step)
- thrd_yield();
- return false;
- }
- }
-
- protected:
- /* Number of synchronized threads. */
- const unsigned int n_;
-
- /* Number of threads currently spinning. */
- std::atomic<unsigned int> nwait_;
-
- /* Number of barrier syncronizations completed so far,
- * * it's OK to wrap. */
- std::atomic<unsigned int> step_;
-};
INCLUDE = -I$(BASE)/include -I../include
# C preprocessor flags
-CPPFLAGS += $(INCLUDE) -g
+CPPFLAGS += $(INCLUDE)
# C++ compiler flags
CXXFLAGS += $(CPPFLAGS)
--- /dev/null
+include ../benchmarks.mk
+
+BENCH := queue
+
+BENCH_BINARY := $(BENCH).o
+
+TESTS := main testcase1 testcase2 testcase3 testcase4
+
+all: $(TESTS)
+ ../generate.sh $(notdir $(shell pwd))
+
+%.o : %.c
+ $(CXX) -c -fPIC -MMD -MF .$@.d -o $@ $< $(CXXFLAGS) $(LDFLAGS)
+
+$(TESTS): % : %.o $(BENCH_BINARY)
+ $(CXX) -o $@ $^ $(CXXFLAGS) $(LDFLAGS)
+
+-include .*.d
+
+clean:
+ rm -rf $(TESTS) *.o .*.d *.dSYM
+
+.PHONY: clean all
--- /dev/null
+class Queue {
+/** @DeclareState: IntList *q;
+@Commutativity:enq<->deq(true)
+@Commutativity:deq<->deq(!M1->RET||!M2->RET) */
+public: atomic<Node*> tail, head;
+Queue() { tail = head = new Node(); }
+/** @Transition: STATE(q)->push_back(val); */
+void Queue::enq(unsigned int val) {
+ Node *n = new Node(val);
+ while(true) {
+ Node *t = tail.load(acquire);
+ Node *next = t->next.load(relaxed);
+ if(next) continue;
+ if(t->next.CAS(next, n, relaxed)) {
+ /** @OPDefine: true */
+ tail.store(n, release);
+ return;
+ }
+ }
+}
+/**@PreCondition:
+return RET ? !STATE(q)->empty()
+ && STATE(q)->front() == RET : true;
+@Transition: if (RET) {
+ if (STATE(q)->empty()) return false;
+ STATE(q)->pop_front();
+} */
+unsigned int Queue::deq() {
+ while(true) {
+ Node *h = head.load(acquire);
+ Node *t = tail.load(acquire);
+ Node *next = h->next.load(relaxed);
+ /** @OPClearDefine: true */
+ if(h == t) return 0;
+ if(head.CAS(h, next, release))
+ return next->data;
+ }
+}
+};
--- /dev/null
+#include <stdlib.h>
+#include <stdio.h>
+#include <threads.h>
+#include "queue.h"
+
+static int procs = 3;
+Queue *q;
+
+int idx1, idx2;
+unsigned int a, b;
+
+
+atomic_int x[3];
+
+static void main_task(void *param)
+{
+ unsigned int val;
+ int pid = *((int *)param);
+ if (pid % 3 == 0) {
+ enq(q, 2);
+ } else if (pid % 3 == 1) {
+ deq(q);
+ } else if (pid % 3 == 2) {
+ deq(q);
+ }
+}
+
+int user_main(int argc, char **argv)
+{
+ int i;
+ int *param;
+
+ atomic_init(&x[1], 0);
+ atomic_init(&x[2], 0);
+
+ /** @Entry */
+ q = new Queue;
+
+ int num_threads = 3;
+
+ param = new int[num_threads];
+ thrd_t *threads = new thrd_t[num_threads];
+
+ for (i = 0; i < num_threads; i++) {
+ param[i] = i;
+ thrd_create(&threads[i], main_task, ¶m[i]);
+ }
+ for (i = 0; i < num_threads; i++)
+ thrd_join(threads[i]);
+
+ delete param;
+ delete threads;
+ delete q;
+
+ return 0;
+}
--- /dev/null
+#include "queue.h"
+
+// Make them C-callable interfaces
+
+/** @DeclareState: IntList *q;
+@Commutativity: enq <-> deq (true)
+@Commutativity: deq <-> deq (M1->C_RET!=-1||M2->C_RET!=-1) */
+
+void Queue::enq(int val) {
+ Node *n = new Node(val);
+ while(true) {
+ Node *t = tail.load(acquire);
+ Node *next = t->next.load(relaxed);
+ if(next) continue;
+ if(t->next.CAS(next, n, relaxed)) {
+ /** @OPDefine: true */
+ tail.store(n, release);
+ return;
+ }
+ }
+}
+int Queue::deq() {
+ while(true) {
+ Node *h = head.load(acquire);
+ Node *t = tail.load(acquire);
+ Node *next = h->next.load(relaxed);
+ /** @OPClearDefine: true */
+ if(h == t) return -1;
+ if(head.CAS(h, next, release))
+ return next->data;
+ }
+}
+
+/** @Transition: STATE(q)->push_back(val); */
+void enq(Queue *q, int val) {
+ q->enq(val);
+}
+
+/** @Transition:
+S_RET = STATE(q)->empty() ? -1 : STATE(q)->front();
+if (S_RET != -1)
+ STATE(q)->pop_front();
+@PostCondition:
+ return C_RET == -1 ? true : C_RET == S_RET;
+@JustifyingPostcondition: if (C_RET == -1)
+ return S_RET == -1; */
+int deq(Queue *q) {
+ return q->deq();
+}
--- /dev/null
+#ifndef _QUEUE_H
+#define _QUEUE_H
+#include <atomic>
+#include "unrelacy.h"
+
+#define CAS compare_exchange_strong
+using namespace std;
+
+typedef struct Node {
+ int data;
+ atomic<Node*> next;
+
+ Node() {
+ data = 0;
+ next.store(NULL, relaxed);
+ }
+
+ Node(int d) {
+ data = d;
+ next.store(NULL, relaxed);
+ }
+} Node;
+
+class Queue {
+public: atomic<Node*> tail, head;
+Queue() { tail = head = new Node(); }
+void enq(int val);
+int deq();
+};
+
+// Make them C-callable interfaces
+void enq(Queue *q, int val);
+
+int deq(Queue *s);
+
+#endif
--- /dev/null
+#include <stdlib.h>
+#include <stdio.h>
+#include <threads.h>
+
+#include "queue.h"
+#include "model-assert.h"
+
+static Queue *queue;
+static thrd_t *threads;
+static unsigned int *input;
+static unsigned int *output;
+static int num_threads;
+
+int get_thread_num()
+{
+ thrd_t curr = thrd_current();
+ int i;
+ for (i = 0; i < num_threads; i++)
+ if (curr.priv == threads[i].priv)
+ return i;
+ MODEL_ASSERT(0);
+ return -1;
+}
+
+bool succ1, succ2;
+atomic_int x[3];
+int idx1, idx2;
+unsigned int reclaimNode;
+
+static int procs = 2;
+static void main_task(void *param)
+{
+ unsigned int val;
+ int pid = *((int *)param);
+ if (pid % procs == 0) {
+ enq(queue, 1);
+ } else if (pid % procs == 1) {
+ succ1 = deq(queue);
+ }
+}
+
+int user_main(int argc, char **argv)
+{
+ int i;
+ int *param;
+ unsigned int in_sum = 0, out_sum = 0;
+
+ /** @Entry */
+ queue = new Queue;
+ MODEL_ASSERT(queue);
+
+ num_threads = procs;
+ threads = (thrd_t*) malloc(num_threads * sizeof(thrd_t));
+ param = (int*) malloc(num_threads * sizeof(*param));
+ input = (unsigned int *) calloc(num_threads, sizeof(*input));
+ output = (unsigned int *) calloc(num_threads, sizeof(*output));
+
+ atomic_init(&x[0], 0);
+ atomic_init(&x[1], 0);
+ atomic_init(&x[2], 0);
+ queue = new Queue;
+ for (i = 0; i < num_threads; i++) {
+ param[i] = i;
+ thrd_create(&threads[i], main_task, ¶m[i]);
+ }
+ for (i = 0; i < num_threads; i++)
+ thrd_join(threads[i]);
+/*
+ for (i = 0; i < num_threads; i++) {
+ in_sum += input[i];
+ out_sum += output[i];
+ }
+ for (i = 0; i < num_threads; i++)
+ printf("input[%d] = %u\n", i, input[i]);
+ for (i = 0; i < num_threads; i++)
+ printf("output[%d] = %u\n", i, output[i]);
+ if (succ1 && succ2)
+ MODEL_ASSERT(in_sum == out_sum);
+ else
+ MODEL_ASSERT (false);
+*/
+ free(param);
+ free(threads);
+
+ return 0;
+}
--- /dev/null
+#include <stdlib.h>
+#include <stdio.h>
+#include <threads.h>
+
+#include "queue.h"
+#include "model-assert.h"
+
+static Queue *queue;
+static thrd_t *threads;
+static unsigned int *input;
+static unsigned int *output;
+static int num_threads;
+
+int get_thread_num()
+{
+ thrd_t curr = thrd_current();
+ int i;
+ for (i = 0; i < num_threads; i++)
+ if (curr.priv == threads[i].priv)
+ return i;
+ MODEL_ASSERT(0);
+ return -1;
+}
+
+bool succ1, succ2;
+atomic_int x[3];
+int idx1, idx2;
+unsigned int reclaimNode;
+
+static int procs = 3;
+static void main_task(void *param)
+{
+ unsigned int val;
+ int pid = *((int *)param);
+ if (pid % procs == 0) {
+ enq(queue, 1);
+ } else if (pid % procs == 1) {
+ enq(queue, 2);
+ } else if (pid % procs == 2) {
+ succ1 = deq(queue);
+ }
+}
+
+int user_main(int argc, char **argv)
+{
+ int i;
+ int *param;
+ unsigned int in_sum = 0, out_sum = 0;
+
+ /** @Entry */
+ queue = new Queue;
+ MODEL_ASSERT(queue);
+
+ num_threads = procs;
+ threads = (thrd_t*) malloc(num_threads * sizeof(thrd_t));
+ param = (int*) malloc(num_threads * sizeof(*param));
+ input = (unsigned int *) calloc(num_threads, sizeof(*input));
+ output = (unsigned int *) calloc(num_threads, sizeof(*output));
+
+ atomic_init(&x[0], 0);
+ atomic_init(&x[1], 0);
+ atomic_init(&x[2], 0);
+ queue = new Queue;
+ for (i = 0; i < num_threads; i++) {
+ param[i] = i;
+ thrd_create(&threads[i], main_task, ¶m[i]);
+ }
+ for (i = 0; i < num_threads; i++)
+ thrd_join(threads[i]);
+/*
+ for (i = 0; i < num_threads; i++) {
+ in_sum += input[i];
+ out_sum += output[i];
+ }
+ for (i = 0; i < num_threads; i++)
+ printf("input[%d] = %u\n", i, input[i]);
+ for (i = 0; i < num_threads; i++)
+ printf("output[%d] = %u\n", i, output[i]);
+ if (succ1 && succ2)
+ MODEL_ASSERT(in_sum == out_sum);
+ else
+ MODEL_ASSERT (false);
+*/
+ free(param);
+ free(threads);
+
+ return 0;
+}
--- /dev/null
+#include <stdlib.h>
+#include <stdio.h>
+#include <threads.h>
+
+#include "queue.h"
+#include "model-assert.h"
+
+static Queue *queue;
+static thrd_t *threads;
+static unsigned int *input;
+static unsigned int *output;
+static int num_threads;
+
+int get_thread_num()
+{
+ thrd_t curr = thrd_current();
+ int i;
+ for (i = 0; i < num_threads; i++)
+ if (curr.priv == threads[i].priv)
+ return i;
+ MODEL_ASSERT(0);
+ return -1;
+}
+
+bool succ1, succ2;
+atomic_int x[3];
+int idx1, idx2;
+unsigned int reclaimNode;
+
+static int procs = 2;
+static void main_task(void *param)
+{
+ unsigned int val;
+ int pid = *((int *)param);
+ if (pid % procs == 0) {
+ enq(queue, 1);
+ succ1 = deq(queue);
+ } else if (pid % procs == 1) {
+ enq(queue, 2);
+ succ2 = deq(queue);
+ }
+}
+
+int user_main(int argc, char **argv)
+{
+ int i;
+ int *param;
+ unsigned int in_sum = 0, out_sum = 0;
+
+ /** @Entry */
+ queue = new Queue;
+ MODEL_ASSERT(queue);
+
+ num_threads = procs;
+ threads = (thrd_t*) malloc(num_threads * sizeof(thrd_t));
+ param = (int*) malloc(num_threads * sizeof(*param));
+ input = (unsigned int *) calloc(num_threads, sizeof(*input));
+ output = (unsigned int *) calloc(num_threads, sizeof(*output));
+
+ atomic_init(&x[0], 0);
+ atomic_init(&x[1], 0);
+ atomic_init(&x[2], 0);
+ queue = new Queue;
+ for (i = 0; i < num_threads; i++) {
+ param[i] = i;
+ thrd_create(&threads[i], main_task, ¶m[i]);
+ }
+ for (i = 0; i < num_threads; i++)
+ thrd_join(threads[i]);
+/*
+ for (i = 0; i < num_threads; i++) {
+ in_sum += input[i];
+ out_sum += output[i];
+ }
+ for (i = 0; i < num_threads; i++)
+ printf("input[%d] = %u\n", i, input[i]);
+ for (i = 0; i < num_threads; i++)
+ printf("output[%d] = %u\n", i, output[i]);
+ if (succ1 && succ2)
+ MODEL_ASSERT(in_sum == out_sum);
+ else
+ MODEL_ASSERT (false);
+*/
+ free(param);
+ free(threads);
+
+ return 0;
+}
--- /dev/null
+#include <stdlib.h>
+#include <stdio.h>
+#include <threads.h>
+
+#include "queue.h"
+#include "model-assert.h"
+
+static Queue *queue;
+static thrd_t *threads;
+static unsigned int *input;
+static unsigned int *output;
+static int num_threads;
+
+int get_thread_num()
+{
+ thrd_t curr = thrd_current();
+ int i;
+ for (i = 0; i < num_threads; i++)
+ if (curr.priv == threads[i].priv)
+ return i;
+ //MODEL_ASSERT(0);
+ return -1;
+}
+
+bool succ1, succ2;
+atomic_int x[3];
+int idx1, idx2, idx3;
+unsigned int reclaimNode;
+
+static int procs = 2;
+static void main_task(void *param)
+{
+ unsigned int val;
+ int pid = *((int *)param);
+ if (pid % procs == 0) {
+ enq(queue, 1);
+ succ1 = deq(queue);
+ enq(queue, 2);
+ } else if (pid % procs == 1) {
+ enq(queue, 2);
+ succ2 = deq(queue);
+ }
+}
+
+int user_main(int argc, char **argv)
+{
+ int i;
+ int *param;
+ unsigned int in_sum = 0, out_sum = 0;
+
+ /** @Entry */
+ queue = new Queue;
+ MODEL_ASSERT(queue);
+
+ num_threads = procs;
+ threads = (thrd_t*) malloc(num_threads * sizeof(thrd_t));
+ param = (int*) malloc(num_threads * sizeof(*param));
+ input = (unsigned int *) calloc(num_threads, sizeof(*input));
+ output = (unsigned int *) calloc(num_threads, sizeof(*output));
+
+ atomic_init(&x[0], 0);
+ atomic_init(&x[1], 0);
+ atomic_init(&x[2], 0);
+ queue = new Queue;
+ for (i = 0; i < num_threads; i++) {
+ param[i] = i;
+ thrd_create(&threads[i], main_task, ¶m[i]);
+ }
+ for (i = 0; i < num_threads; i++)
+ thrd_join(threads[i]);
+/*
+ for (i = 0; i < num_threads; i++) {
+ in_sum += input[i];
+ out_sum += output[i];
+ }
+ for (i = 0; i < num_threads; i++)
+ printf("input[%d] = %u\n", i, input[i]);
+ for (i = 0; i < num_threads; i++)
+ printf("output[%d] = %u\n", i, output[i]);
+ if (succ1 && succ2)
+ MODEL_ASSERT(in_sum == out_sum);
+ else
+ MODEL_ASSERT (false);
+*/
+ free(param);
+ free(threads);
+
+ return 0;
+}
include ../benchmarks.mk
-TESTNAME = main
+BENCH := deque
-HEADERS = deque.h
-OBJECTS = main.o deque.o
+BENCH_BINARY := $(BENCH).o
-all: $(TESTNAME)
+TESTS := main testcase1 testcase2 testcase3 testcase4 testcase5 testcase6
-$(TESTNAME): $(HEADERS) $(OBJECTS)
- $(CC) -o $@ $(OBJECTS) $(CPPFLAGS) $(LDFLAGS)
+all: $(TESTS)
+ ../generate.sh $(notdir $(shell pwd))
%.o: %.c
- $(CC) -c -o $@ $< $(CPPFLAGS)
+ $(CC) -c -fPIC -MMD -MF .$@.d -o $@ $< $(CFLAGS) $(LDFLAGS)
+
+$(TESTS): %: %.o $(BENCH_BINARY)
+ $(CC) -o $@ $^ $(CFLAGS) $(LDFLAGS)
+
+-include .*.d
clean:
- rm -f $(TESTNAME) *.o
+ rm -rf $(TESTS) *.o .*.d *.dSYM
+
+.PHONY: clean all
-#include <stdatomic.h>
-#include <inttypes.h>
#include "deque.h"
#include <stdlib.h>
#include <stdio.h>
+/** @Define:
+ bool succ(int res) { return res != EMPTY && res != ABORT; }
+ bool fail(int res) { return !succ(res); } */
+
+/** @DeclareState: IntList *deque;
+ @Initial: deque = new IntList;
+ @Commutativity: push <-> push (true)
+ @Commutativity: push <-> take (true)
+ @Commutativity: take <-> take (true) */
+
+Deque * create_size(int size) {
+ Deque * q = (Deque *) calloc(1, sizeof(Deque));
+ Array * a = (Array *) calloc(1, sizeof(Array)+size*sizeof(atomic_int));
+ atomic_store_explicit(&q->array, a, memory_order_relaxed);
+ atomic_store_explicit(&q->top, 0, memory_order_relaxed);
+ atomic_store_explicit(&q->bottom, 0, memory_order_relaxed);
+ atomic_store_explicit(&a->size, size, memory_order_relaxed);
+ return q;
+}
+
Deque * create() {
Deque * q = (Deque *) calloc(1, sizeof(Deque));
Array * a = (Array *) calloc(1, sizeof(Array)+2*sizeof(atomic_int));
return q;
}
+/**
+ Note:
+ 1. The expected way to use the deque is that we have a main thread where we
+ call push() and take(); and then we have other stealing threads that only
+ call steal().
+ 2. Bottom stores the index that push() is ready to update on; Top stores the
+ index that steal() is ready to read from.
+ 3. take() greedly decreases the bottom, and then later check whether it is
+ going to take the last element; If so, it will race with the corresponding
+ stealing threads.
+ XXX:
+ 4. In this implementation, there are two places where we update the Top: a)
+ take() racing the last element and steal() consumes an element. We need to
+ have seq_cst for all the updates because we need to have a total SC order
+ between them such that the SC fences in take() and steal() can prevent the
+ load of Top right after the fence in take() will read the update-to-date
+ value.
+ 5. Note that the steal() really can bail any time since it never retries!!!
+
+*/
+
+
+/** @PreCondition: return succ(C_RET) ? !STATE(deque)->empty() &&
+ STATE(deque)->back() == C_RET : true;
+ @JustifyingPrecondition: if (!succ(C_RET) && !STATE(deque)->empty()) {
+ // Make sure there are concurrent stealers who take those items
+ ForEach (item, STATE(deque))
+ if (!HasItem(CONCURRENT, Guard(EQ(NAME, "steal") && C_RET(steal) == item)))
+ return false;
+ }
+ @Transition: if (succ(C_RET)) {
+ if (STATE(deque)->empty()) return false;
+ STATE(deque)->pop_back();
+ } */
int take(Deque *q) {
+ // take() greedly decrease the Bottom, then check later
size_t b = atomic_load_explicit(&q->bottom, memory_order_relaxed) - 1;
Array *a = (Array *) atomic_load_explicit(&q->array, memory_order_relaxed);
atomic_store_explicit(&q->bottom, b, memory_order_relaxed);
+ /********** Detected Correctness (testcase2) **********/
atomic_thread_fence(memory_order_seq_cst);
size_t t = atomic_load_explicit(&q->top, memory_order_relaxed);
int x;
if (t <= b) {
/* Non-empty queue. */
- x = atomic_load_explicit(&a->buffer[b % atomic_load_explicit(&a->size,memory_order_relaxed)], memory_order_relaxed);
+ int sz = atomic_load_explicit(&a->size,memory_order_relaxed);
+ // Reads the buffer value before racing
+ x = atomic_load_explicit(&a->buffer[b % sz], memory_order_relaxed);
if (t == b) {
/* Single last element in queue. */
- if (!atomic_compare_exchange_strong_explicit(&q->top, &t, t + 1, memory_order_seq_cst, memory_order_relaxed))
+ // FIXME: This might not be necessary!!! We don't know yet
+ if (!atomic_compare_exchange_strong_explicit(&q->top, &t, t + 1,
+ memory_order_relaxed, memory_order_relaxed)) {
/* Failed race. */
x = EMPTY;
+ }
+ // Restore the Bottom
atomic_store_explicit(&q->bottom, b + 1, memory_order_relaxed);
}
} else { /* Empty queue. */
x = EMPTY;
+ // Restore the Bottom
atomic_store_explicit(&q->bottom, b + 1, memory_order_relaxed);
}
+ // Make sure we have one ordering point (push <-> take) when it's empty
+ /** @OPClearDefine: true */
return x;
}
size_t bottom=atomic_load_explicit(&q->bottom, memory_order_relaxed);
atomic_store_explicit(&new_a->size, new_size, memory_order_relaxed);
size_t i;
+
+ // XXX: Initialize the whole new array to turn off the CDSChecker UL error
+ // Check if CDSSpec checker can catch this bug
+ /*
+ for(i=0; i < new_size; i++) {
+ atomic_store_explicit(&new_a->buffer[i % new_size], atomic_load_explicit(&a->buffer[i % size], memory_order_relaxed), memory_order_relaxed);
+ }
+ */
+
for(i=top; i < bottom; i++) {
atomic_store_explicit(&new_a->buffer[i % new_size], atomic_load_explicit(&a->buffer[i % size], memory_order_relaxed), memory_order_relaxed);
}
+
+ /********** Detected UL **********/
atomic_store_explicit(&q->array, new_a, memory_order_release);
printf("resize\n");
}
+/** @Transition: STATE(deque)->push_back(x); */
void push(Deque *q, int x) {
size_t b = atomic_load_explicit(&q->bottom, memory_order_relaxed);
+ /********** Detected Correctness (testcase1 -x1000) **********/
size_t t = atomic_load_explicit(&q->top, memory_order_acquire);
Array *a = (Array *) atomic_load_explicit(&q->array, memory_order_relaxed);
if (b - t > atomic_load_explicit(&a->size, memory_order_relaxed) - 1) /* Full queue. */ {
resize(q);
+ /********** Also Detected (testcase1) **********/
//Bug in paper...should have next line...
a = (Array *) atomic_load_explicit(&q->array, memory_order_relaxed);
}
+ // Update the buffer (this is the ordering point)
atomic_store_explicit(&a->buffer[b % atomic_load_explicit(&a->size, memory_order_relaxed)], x, memory_order_relaxed);
+ /** @OPDefine: true */
+ /********** Detected UL (testcase1) **********/
atomic_thread_fence(memory_order_release);
atomic_store_explicit(&q->bottom, b + 1, memory_order_relaxed);
}
+/** @PreCondition: return succ(C_RET) ? !STATE(deque)->empty() &&
+ STATE(deque)->front() == C_RET : true;
+ @Transition: if (succ(C_RET)) {
+ if (STATE(deque)->empty()) return false;
+ STATE(deque)->pop_front();
+ } */
int steal(Deque *q) {
- size_t t = atomic_load_explicit(&q->top, memory_order_acquire);
+ // XXX: The following load should be just relaxed (cause it's followed by an
+ // SC fence (discussed in AutoMO)
+ size_t t = atomic_load_explicit(&q->top, memory_order_relaxed);
+ /********** Detected Correctness (testcase2) **********/
atomic_thread_fence(memory_order_seq_cst);
+ /********** Detected UL (testcase1 -x100) **********/
size_t b = atomic_load_explicit(&q->bottom, memory_order_acquire);
int x = EMPTY;
if (t < b) {
/* Non-empty queue. */
+ /********** Detected UL (testcase1 -x100) **********/
Array *a = (Array *) atomic_load_explicit(&q->array, memory_order_acquire);
- x = atomic_load_explicit(&a->buffer[t % atomic_load_explicit(&a->size, memory_order_relaxed)], memory_order_relaxed);
- if (!atomic_compare_exchange_strong_explicit(&q->top, &t, t + 1, memory_order_seq_cst, memory_order_relaxed))
+ int sz = atomic_load_explicit(&a->size, memory_order_relaxed);
+ x = atomic_load_explicit(&a->buffer[t % sz], memory_order_relaxed);
+ /********** Detected Correctness (testcase1 -x1000) **********/
+ bool succ = atomic_compare_exchange_strong_explicit(&q->top, &t, t + 1,
+ memory_order_seq_cst, memory_order_relaxed);
+ /** @OPDefine: true */
+ if (!succ) {
/* Failed race. */
return ABORT;
+ }
}
return x;
}
#ifndef DEQUE_H
#define DEQUE_H
+#include <stdatomic.h>
+#include <inttypes.h>
+
typedef struct {
atomic_size_t size;
atomic_int buffer[];
atomic_uintptr_t array; /* Atomic(Array *) */
} Deque;
+Deque * create_size(int size);
Deque * create();
int take(Deque *q);
void resize(Deque *q);
#define EMPTY 0xffffffff
#define ABORT 0xfffffffe
+/** @Define:
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+bool succ(int res);
+bool fail(int res);
+
+#ifdef __cplusplus
+};
+#endif
+*/
+
#endif
int user_main(int argc, char **argv)
{
+ /** @Entry */
thrd_t t;
q=create();
thrd_create(&t, task, 0);
push(q, 1);
push(q, 2);
- push(q, 4);
+ push(q, 3);
b=take(q);
c=take(q);
thrd_join(t);
-
+/*
bool correct=true;
if (a!=1 && a!=2 && a!=4 && a!= EMPTY)
correct=false;
correct=false;
if (!correct)
printf("a=%d b=%d c=%d\n",a,b,c);
- MODEL_ASSERT(correct);
+ */
+ //MODEL_ASSERT(correct);
return 0;
}
--- /dev/null
+#include <stdlib.h>
+#include <assert.h>
+#include <stdio.h>
+#include <threads.h>
+#include <stdatomic.h>
+
+#include "model-assert.h"
+
+#include "deque.h"
+
+Deque *q;
+int a;
+int b;
+int c;
+int x;
+
+static void task(void * param) {
+ a=steal(q);
+ if (a == ABORT) {
+ printf("Steal NULL\n");
+ } else {
+ printf("Steal %d\n", a);
+ }
+
+ x=steal(q);
+ if (x == ABORT) {
+ printf("Steal NULL\n");
+ } else {
+ printf("Steal %d\n", x);
+ }
+}
+
+int user_main(int argc, char **argv)
+{
+ /** @Entry */
+ thrd_t t;
+ q=create();
+ thrd_create(&t, task, 0);
+ push(q, 1);
+ printf("Push 1\n");
+ push(q, 2);
+ printf("Push 2\n");
+ push(q, 4);
+ printf("Push 4\n");
+ b=take(q);
+ if (b == EMPTY) {
+ printf("Take NULL\n");
+ } else {
+ printf("Take %d\n", b);
+ }
+ c=take(q);
+ if (c == EMPTY) {
+ printf("Take NULL\n");
+ } else {
+ printf("Take %d\n", c);
+ }
+ thrd_join(t);
+/*
+ bool correct=true;
+ if (a!=1 && a!=2 && a!=4 && a!= EMPTY)
+ correct=false;
+ if (b!=1 && b!=2 && b!=4 && b!= EMPTY)
+ correct=false;
+ if (c!=1 && c!=2 && c!=4 && a!= EMPTY)
+ correct=false;
+ if (a!=EMPTY && b!=EMPTY && c!=EMPTY && (a+b+c)!=7)
+ correct=false;
+ if (!correct)
+ printf("a=%d b=%d c=%d\n",a,b,c);
+ */
+ //MODEL_ASSERT(correct);
+
+ return 0;
+}
--- /dev/null
+#include <stdlib.h>
+#include <assert.h>
+#include <stdio.h>
+#include <threads.h>
+#include <stdatomic.h>
+
+#include "model-assert.h"
+
+#include "deque.h"
+
+Deque *q;
+int a;
+int b;
+int c;
+
+/** Making CAS in steal() (w39) SC */
+
+static void task(void * param) {
+ b=steal(q);
+ c=steal(q);
+ printf("steal: b=%d, c=%d\n", b, c);
+}
+
+int user_main(int argc, char **argv)
+{
+ thrd_t t1, t2;
+ q=create_size(16);
+ /** @Entry */
+
+ push(q, 1);
+ thrd_create(&t1, task, 0);
+ push(q, 2);
+ a=take(q);
+ printf("take: a=%d\n", a);
+ thrd_join(t1);
+
+ int d =take(q);
+ bool correct= b == 1 && c == 2 && a == 2 ;
+ //MODEL_ASSERT(!correct);
+/*
+ bool correct=true;
+ if (a!=1 && a!=2 && a!=4 && a!= EMPTY)
+ correct=false;
+ if (b!=1 && b!=2 && b!=4 && b!= EMPTY)
+ correct=false;
+ if (c!=1 && c!=2 && c!=4 && a!= EMPTY)
+ correct=false;
+ if (a!=EMPTY && b!=EMPTY && c!=EMPTY && (a+b+c)!=7)
+ correct=false;
+ //if (!correct)
+ printf("a=%d b=%d c=%d\n",a,b,c);
+ MODEL_ASSERT(correct);
+ */
+
+ return 0;
+}
--- /dev/null
+#include <stdlib.h>
+#include <assert.h>
+#include <stdio.h>
+#include <threads.h>
+#include <stdatomic.h>
+
+#include "model-assert.h"
+
+#include "deque.h"
+
+Deque *q;
+int a;
+int b;
+int c;
+
+/** Making CAS in steal() (w39) SC */
+
+static void task1(void * param) {
+ b=steal(q);
+ printf("steal: b=%d\n", b);
+}
+
+static void task2(void * param) {
+ c=steal(q);
+ printf("steal: c=%d\n", c);
+}
+
+int user_main(int argc, char **argv)
+{
+ thrd_t t1, t2;
+ q=create();
+ /** @Entry */
+
+ push(q, 1);
+ thrd_create(&t1, task1, 0);
+ thrd_create(&t2, task2, 0);
+ push(q, 2);
+ a=take(q);
+ printf("take: a=%d\n", a);
+ thrd_join(t1);
+ thrd_join(t2);
+
+ int d =take(q);
+ bool correct= b == 1 && c == 2 && a == 2 ;
+ MODEL_ASSERT(!correct);
+/*
+ bool correct=true;
+ if (a!=1 && a!=2 && a!=4 && a!= EMPTY)
+ correct=false;
+ if (b!=1 && b!=2 && b!=4 && b!= EMPTY)
+ correct=false;
+ if (c!=1 && c!=2 && c!=4 && a!= EMPTY)
+ correct=false;
+ if (a!=EMPTY && b!=EMPTY && c!=EMPTY && (a+b+c)!=7)
+ correct=false;
+ //if (!correct)
+ printf("a=%d b=%d c=%d\n",a,b,c);
+ MODEL_ASSERT(correct);
+ */
+
+ return 0;
+}
--- /dev/null
+#include <stdlib.h>
+#include <assert.h>
+#include <stdio.h>
+#include <threads.h>
+#include <stdatomic.h>
+
+#include "model-assert.h"
+
+#include "deque.h"
+
+Deque *q;
+int a;
+int b;
+int c;
+
+/** Making CAS in steal() (w39) SC */
+
+static void task(void * param) {
+ b=steal(q);
+ printf("steal: b=%d\n", b);
+}
+
+int user_main(int argc, char **argv)
+{
+ thrd_t t1, t2;
+ q=create();
+ /** @Entry */
+
+ push(q, 1);
+ thrd_create(&t1, task, 0);
+ a=take(q);
+ printf("take: a=%d\n", a);
+ thrd_join(t1);
+
+ int d =take(q);
+ bool correct= b == 1 && c == 2 && a == 2 ;
+ MODEL_ASSERT(!correct);
+/*
+ bool correct=true;
+ if (a!=1 && a!=2 && a!=4 && a!= EMPTY)
+ correct=false;
+ if (b!=1 && b!=2 && b!=4 && b!= EMPTY)
+ correct=false;
+ if (c!=1 && c!=2 && c!=4 && a!= EMPTY)
+ correct=false;
+ if (a!=EMPTY && b!=EMPTY && c!=EMPTY && (a+b+c)!=7)
+ correct=false;
+ //if (!correct)
+ printf("a=%d b=%d c=%d\n",a,b,c);
+ MODEL_ASSERT(correct);
+ */
+
+ return 0;
+}
--- /dev/null
+#include <stdlib.h>
+#include <assert.h>
+#include <stdio.h>
+#include <threads.h>
+#include <stdatomic.h>
+
+#include "model-assert.h"
+
+#include "deque.h"
+
+Deque *q;
+int a;
+int b;
+int c;
+
+/** Making CAS in steal() (w39) SC */
+
+static void task(void * param) {
+ steal(q);
+ steal(q);
+}
+
+int user_main(int argc, char **argv)
+{
+ thrd_t t1, t2;
+ q=create();
+ /** @Entry */
+
+ push(q, 1);
+ push(q, 2);
+ thrd_create(&t1, task, 0);
+ a=take(q);
+ printf("take: a=%d\n", a);
+ thrd_join(t1);
+
+ int d =take(q);
+ bool correct= b == 1 && c == 2 && a == 2 ;
+ MODEL_ASSERT(!correct);
+/*
+ bool correct=true;
+ if (a!=1 && a!=2 && a!=4 && a!= EMPTY)
+ correct=false;
+ if (b!=1 && b!=2 && b!=4 && b!= EMPTY)
+ correct=false;
+ if (c!=1 && c!=2 && c!=4 && a!= EMPTY)
+ correct=false;
+ if (a!=EMPTY && b!=EMPTY && c!=EMPTY && (a+b+c)!=7)
+ correct=false;
+ //if (!correct)
+ printf("a=%d b=%d c=%d\n",a,b,c);
+ MODEL_ASSERT(correct);
+ */
+
+ return 0;
+}
--- /dev/null
+#include <stdlib.h>
+#include <assert.h>
+#include <stdio.h>
+#include <threads.h>
+#include <stdatomic.h>
+
+#include "model-assert.h"
+
+#include "deque.h"
+
+Deque *q;
+int a;
+int b;
+int c;
+
+/** Making CAS in steal() (w39) SC */
+
+static void task(void * param) {
+ steal(q);
+}
+
+int user_main(int argc, char **argv)
+{
+ thrd_t t1, t2;
+ q=create();
+ /** @Entry */
+
+ push(q, 1);
+ push(q, 2);
+ thrd_create(&t1, task, 0);
+ take(q);
+ take(q);
+ printf("take: a=%d\n", a);
+ thrd_join(t1);
+
+ int d =take(q);
+ bool correct= b == 1 && c == 2 && a == 2 ;
+ MODEL_ASSERT(!correct);
+/*
+ bool correct=true;
+ if (a!=1 && a!=2 && a!=4 && a!= EMPTY)
+ correct=false;
+ if (b!=1 && b!=2 && b!=4 && b!= EMPTY)
+ correct=false;
+ if (c!=1 && c!=2 && c!=4 && a!= EMPTY)
+ correct=false;
+ if (a!=EMPTY && b!=EMPTY && c!=EMPTY && (a+b+c)!=7)
+ correct=false;
+ //if (!correct)
+ printf("a=%d b=%d c=%d\n",a,b,c);
+ MODEL_ASSERT(correct);
+ */
+
+ return 0;
+}
+++ /dev/null
-include ../benchmarks.mk
-
-TESTS := table
-
-all: $(TESTS)
-
-table: main.cc
- $(CXX) -o $@ $^ $(SPEC_OBJ) $(CXXFLAGS) -std=c++0x $(LDFLAGS)
-
-clean:
- rm -f *.o *.d $(TESTS)
+++ /dev/null
-/*\r
- * Written by Cliff Click and released to the public domain, as explained at\r
- * http://creativecommons.org/licenses/publicdomain\r
- */\r
-\r
-package org.cliffc.high_scale_lib;\r
-import java.io.IOException;\r
-import java.io.Serializable;\r
-import java.lang.reflect.Field;\r
-import java.util.*;\r
-import java.util.concurrent.ConcurrentMap;\r
-import java.util.concurrent.atomic.*;\r
-import sun.misc.Unsafe;\r
-\r
-/**\r
- * A lock-free alternate implementation of {@link java.util.concurrent.ConcurrentHashMap}\r
- * with better scaling properties and generally lower costs to mutate the Map.\r
- * It provides identical correctness properties as ConcurrentHashMap. All\r
- * operations are non-blocking and multi-thread safe, including all update\r
- * operations. {@link NonBlockingHashMap} scales substatially better than\r
- * {@link java.util.concurrent.ConcurrentHashMap} for high update rates, even with a\r
- * large concurrency factor. Scaling is linear up to 768 CPUs on a 768-CPU\r
- * Azul box, even with 100% updates or 100% reads or any fraction in-between.\r
- * Linear scaling up to all cpus has been observed on a 32-way Sun US2 box,\r
- * 32-way Sun Niagra box, 8-way Intel box and a 4-way Power box.\r
- *\r
- * This class obeys the same functional specification as {@link\r
- * java.util.Hashtable}, and includes versions of methods corresponding to\r
- * each method of <tt>Hashtable</tt>. However, even though all operations are\r
- * thread-safe, operations do <em>not</em> entail locking and there is\r
- * <em>not</em> any support for locking the entire table in a way that\r
- * prevents all access. This class is fully interoperable with\r
- * <tt>Hashtable</tt> in programs that rely on its thread safety but not on\r
- * its synchronization details.\r
- *\r
- * <p> Operations (including <tt>put</tt>) generally do not block, so may\r
- * overlap with other update operations (including other <tt>puts</tt> and\r
- * <tt>removes</tt>). Retrievals reflect the results of the most recently\r
- * <em>completed</em> update operations holding upon their onset. For\r
- * aggregate operations such as <tt>putAll</tt>, concurrent retrievals may\r
- * reflect insertion or removal of only some entries. Similarly, Iterators\r
- * and Enumerations return elements reflecting the state of the hash table at\r
- * some point at or since the creation of the iterator/enumeration. They do\r
- * <em>not</em> throw {@link ConcurrentModificationException}. However,\r
- * iterators are designed to be used by only one thread at a time.\r
- *\r
- * <p> Very full tables, or tables with high reprobe rates may trigger an\r
- * internal resize operation to move into a larger table. Resizing is not\r
- * terribly expensive, but it is not free either; during resize operations\r
- * table throughput may drop somewhat. All threads that visit the table\r
- * during a resize will 'help' the resizing but will still be allowed to\r
- * complete their operation before the resize is finished (i.e., a simple\r
- * 'get' operation on a million-entry table undergoing resizing will not need\r
- * to block until the entire million entries are copied).\r
- *\r
- * <p>This class and its views and iterators implement all of the\r
- * <em>optional</em> methods of the {@link Map} and {@link Iterator}\r
- * interfaces.\r
- *\r
- * <p> Like {@link Hashtable} but unlike {@link HashMap}, this class\r
- * does <em>not</em> allow <tt>null</tt> to be used as a key or value.\r
- *\r
- *\r
- * @since 1.5\r
- * @author Cliff Click\r
- * @param <TypeK> the type of keys maintained by this map\r
- * @param <TypeV> the type of mapped values\r
- *\r
- * @version 1.1.2\r
- * @author Prashant Deva - moved hash() function out of get_impl() so it is\r
- * not calculated multiple times.\r
- */\r
-\r
-public class NonBlockingHashMap<TypeK, TypeV>\r
- extends AbstractMap<TypeK, TypeV>\r
- implements ConcurrentMap<TypeK, TypeV>, Cloneable, Serializable {\r
-\r
- private static final long serialVersionUID = 1234123412341234123L;\r
-\r
- private static final int REPROBE_LIMIT=10; // Too many reprobes then force a table-resize\r
-\r
- // --- Bits to allow Unsafe access to arrays\r
- private static final Unsafe _unsafe = UtilUnsafe.getUnsafe();\r
- private static final int _Obase = _unsafe.arrayBaseOffset(Object[].class);\r
- private static final int _Oscale = _unsafe.arrayIndexScale(Object[].class);\r
- private static long rawIndex(final Object[] ary, final int idx) {\r
- assert idx >= 0 && idx < ary.length;\r
- return _Obase + idx * _Oscale;\r
- }\r
-\r
- // --- Setup to use Unsafe\r
- private static final long _kvs_offset;\r
- static { // <clinit>\r
- Field f = null;\r
- try { f = NonBlockingHashMap.class.getDeclaredField("_kvs"); }\r
- catch( java.lang.NoSuchFieldException e ) { throw new RuntimeException(e); }\r
- _kvs_offset = _unsafe.objectFieldOffset(f);\r
- }\r
- private final boolean CAS_kvs( final Object[] oldkvs, final Object[] newkvs ) {\r
- return _unsafe.compareAndSwapObject(this, _kvs_offset, oldkvs, newkvs );\r
- }\r
-\r
- // --- Adding a 'prime' bit onto Values via wrapping with a junk wrapper class\r
- private static final class Prime {\r
- final Object _V;\r
- Prime( Object V ) { _V = V; }\r
- static Object unbox( Object V ) { return V instanceof Prime ? ((Prime)V)._V : V; }\r
- }\r
-\r
- // --- hash ----------------------------------------------------------------\r
- // Helper function to spread lousy hashCodes\r
- private static final int hash(final Object key) {\r
- int h = key.hashCode(); // The real hashCode call\r
- // Spread bits to regularize both segment and index locations,\r
- // using variant of single-word Wang/Jenkins hash.\r
- h += (h << 15) ^ 0xffffcd7d;\r
- h ^= (h >>> 10);\r
- h += (h << 3);\r
- h ^= (h >>> 6);\r
- h += (h << 2) + (h << 14);\r
- return h ^ (h >>> 16);\r
- }\r
-\r
- // --- The Hash Table --------------------\r
- // Slot 0 is always used for a 'CHM' entry below to hold the interesting\r
- // bits of the hash table. Slot 1 holds full hashes as an array of ints.\r
- // Slots {2,3}, {4,5}, etc hold {Key,Value} pairs. The entire hash table\r
- // can be atomically replaced by CASing the _kvs field.\r
- //\r
- // Why is CHM buried inside the _kvs Object array, instead of the other way\r
- // around? The CHM info is used during resize events and updates, but not\r
- // during standard 'get' operations. I assume 'get' is much more frequent\r
- // than 'put'. 'get' can skip the extra indirection of skipping through the\r
- // CHM to reach the _kvs array.\r
- private transient Object[] _kvs;\r
- private static final CHM chm (Object[] kvs) { return (CHM )kvs[0]; }\r
- private static final int[] hashes(Object[] kvs) { return (int[])kvs[1]; }\r
- // Number of K,V pairs in the table\r
- private static final int len(Object[] kvs) { return (kvs.length-2)>>1; }\r
-\r
- // Time since last resize\r
- private transient long _last_resize_milli;\r
-\r
- // --- Minimum table size ----------------\r
- // Pick size 8 K/V pairs, which turns into (8*2+2)*4+12 = 84 bytes on a\r
- // standard 32-bit HotSpot, and (8*2+2)*8+12 = 156 bytes on 64-bit Azul.\r
- private static final int MIN_SIZE_LOG=3; //\r
- private static final int MIN_SIZE=(1<<MIN_SIZE_LOG); // Must be power of 2\r
-\r
- // --- Sentinels -------------------------\r
- // No-Match-Old - putIfMatch does updates only if it matches the old value,\r
- // and NO_MATCH_OLD basically counts as a wildcard match.\r
- private static final Object NO_MATCH_OLD = new Object(); // Sentinel\r
- // Match-Any-not-null - putIfMatch does updates only if it find a real old\r
- // value.\r
- private static final Object MATCH_ANY = new Object(); // Sentinel\r
- // This K/V pair has been deleted (but the Key slot is forever claimed).\r
- // The same Key can be reinserted with a new value later.\r
- private static final Object TOMBSTONE = new Object();\r
- // Prime'd or box'd version of TOMBSTONE. This K/V pair was deleted, then a\r
- // table resize started. The K/V pair has been marked so that no new\r
- // updates can happen to the old table (and since the K/V pair was deleted\r
- // nothing was copied to the new table).\r
- private static final Prime TOMBPRIME = new Prime(TOMBSTONE);\r
-\r
- // --- key,val -------------------------------------------------------------\r
- // Access K,V for a given idx\r
- //\r
- // Note that these are static, so that the caller is forced to read the _kvs\r
- // field only once, and share that read across all key/val calls - lest the\r
- // _kvs field move out from under us and back-to-back key & val calls refer\r
- // to different _kvs arrays.\r
- private static final Object key(Object[] kvs,int idx) { return kvs[(idx<<1)+2]; }\r
- private static final Object val(Object[] kvs,int idx) { return kvs[(idx<<1)+3]; }\r
- private static final boolean CAS_key( Object[] kvs, int idx, Object old, Object key ) {\r
- return _unsafe.compareAndSwapObject( kvs, rawIndex(kvs,(idx<<1)+2), old, key );\r
- }\r
- private static final boolean CAS_val( Object[] kvs, int idx, Object old, Object val ) {\r
- return _unsafe.compareAndSwapObject( kvs, rawIndex(kvs,(idx<<1)+3), old, val );\r
- }\r
-\r
-\r
- // --- dump ----------------------------------------------------------------\r
- /** Verbose printout of table internals, useful for debugging. */\r
- public final void print() {\r
- System.out.println("=========");\r
- print2(_kvs);\r
- System.out.println("=========");\r
- }\r
- // print the entire state of the table\r
- private final void print( Object[] kvs ) {\r
- for( int i=0; i<len(kvs); i++ ) {\r
- Object K = key(kvs,i);\r
- if( K != null ) {\r
- String KS = (K == TOMBSTONE) ? "XXX" : K.toString();\r
- Object V = val(kvs,i);\r
- Object U = Prime.unbox(V);\r
- String p = (V==U) ? "" : "prime_";\r
- String US = (U == TOMBSTONE) ? "tombstone" : U.toString();\r
- System.out.println(""+i+" ("+KS+","+p+US+")");\r
- }\r
- }\r
- Object[] newkvs = chm(kvs)._newkvs; // New table, if any\r
- if( newkvs != null ) {\r
- System.out.println("----");\r
- print(newkvs);\r
- }\r
- }\r
- // print only the live values, broken down by the table they are in\r
- private final void print2( Object[] kvs) {\r
- for( int i=0; i<len(kvs); i++ ) {\r
- Object key = key(kvs,i);\r
- Object val = val(kvs,i);\r
- Object U = Prime.unbox(val);\r
- if( key != null && key != TOMBSTONE && // key is sane\r
- val != null && U != TOMBSTONE ) { // val is sane\r
- String p = (val==U) ? "" : "prime_";\r
- System.out.println(""+i+" ("+key+","+p+val+")");\r
- }\r
- }\r
- Object[] newkvs = chm(kvs)._newkvs; // New table, if any\r
- if( newkvs != null ) {\r
- System.out.println("----");\r
- print2(newkvs);\r
- }\r
- }\r
-\r
- // Count of reprobes\r
- private transient Counter _reprobes = new Counter();\r
- /** Get and clear the current count of reprobes. Reprobes happen on key\r
- * collisions, and a high reprobe rate may indicate a poor hash function or\r
- * weaknesses in the table resizing function.\r
- * @return the count of reprobes since the last call to {@link #reprobes}\r
- * or since the table was created. */\r
- public long reprobes() { long r = _reprobes.get(); _reprobes = new Counter(); return r; }\r
-\r
-\r
- // --- reprobe_limit -----------------------------------------------------\r
- // Heuristic to decide if we have reprobed toooo many times. Running over\r
- // the reprobe limit on a 'get' call acts as a 'miss'; on a 'put' call it\r
- // can trigger a table resize. Several places must have exact agreement on\r
- // what the reprobe_limit is, so we share it here.\r
- private static final int reprobe_limit( int len ) {\r
- return REPROBE_LIMIT + (len>>2);\r
- }\r
-\r
- // --- NonBlockingHashMap --------------------------------------------------\r
- // Constructors\r
-\r
- /** Create a new NonBlockingHashMap with default minimum size (currently set\r
- * to 8 K/V pairs or roughly 84 bytes on a standard 32-bit JVM). */\r
- public NonBlockingHashMap( ) { this(MIN_SIZE); }\r
-\r
- /** Create a new NonBlockingHashMap with initial room for the given number of\r
- * elements, thus avoiding internal resizing operations to reach an\r
- * appropriate size. Large numbers here when used with a small count of\r
- * elements will sacrifice space for a small amount of time gained. The\r
- * initial size will be rounded up internally to the next larger power of 2. */\r
- public NonBlockingHashMap( final int initial_sz ) { initialize(initial_sz); }\r
- private final void initialize( int initial_sz ) {\r
- if( initial_sz < 0 ) throw new IllegalArgumentException();\r
- int i; // Convert to next largest power-of-2\r
- if( initial_sz > 1024*1024 ) initial_sz = 1024*1024;\r
- for( i=MIN_SIZE_LOG; (1<<i) < (initial_sz<<2); i++ ) ;\r
- // Double size for K,V pairs, add 1 for CHM and 1 for hashes\r
- _kvs = new Object[((1<<i)<<1)+2];\r
- _kvs[0] = new CHM(new Counter()); // CHM in slot 0\r
- _kvs[1] = new int[1<<i]; // Matching hash entries\r
- _last_resize_milli = System.currentTimeMillis();\r
- }\r
- // Version for subclassed readObject calls, to be called after the defaultReadObject\r
- protected final void initialize() { initialize(MIN_SIZE); }\r
-\r
- // --- wrappers ------------------------------------------------------------\r
-\r
- /** Returns the number of key-value mappings in this map.\r
- * @return the number of key-value mappings in this map */\r
- @Override \r
- public int size ( ) { return chm(_kvs).size(); }\r
- /** Returns <tt>size() == 0</tt>.\r
- * @return <tt>size() == 0</tt> */\r
- @Override \r
- public boolean isEmpty ( ) { return size() == 0; }\r
-\r
- /** Tests if the key in the table using the <tt>equals</tt> method.\r
- * @return <tt>true</tt> if the key is in the table using the <tt>equals</tt> method\r
- * @throws NullPointerException if the specified key is null */\r
- @Override \r
- public boolean containsKey( Object key ) { return get(key) != null; }\r
-\r
- /** Legacy method testing if some key maps into the specified value in this\r
- * table. This method is identical in functionality to {@link\r
- * #containsValue}, and exists solely to ensure full compatibility with\r
- * class {@link java.util.Hashtable}, which supported this method prior to\r
- * introduction of the Java Collections framework.\r
- * @param val a value to search for\r
- * @return <tt>true</tt> if this map maps one or more keys to the specified value\r
- * @throws NullPointerException if the specified value is null */\r
- public boolean contains ( Object val ) { return containsValue(val); }\r
-\r
- /** Maps the specified key to the specified value in the table. Neither key\r
- * nor value can be null.\r
- * <p> The value can be retrieved by calling {@link #get} with a key that is\r
- * equal to the original key.\r
- * @param key key with which the specified value is to be associated\r
- * @param val value to be associated with the specified key\r
- * @return the previous value associated with <tt>key</tt>, or\r
- * <tt>null</tt> if there was no mapping for <tt>key</tt>\r
- * @throws NullPointerException if the specified key or value is null */\r
- @Override\r
- public TypeV put ( TypeK key, TypeV val ) { return putIfMatch( key, val, NO_MATCH_OLD); }\r
-\r
- /** Atomically, do a {@link #put} if-and-only-if the key is not mapped.\r
- * Useful to ensure that only a single mapping for the key exists, even if\r
- * many threads are trying to create the mapping in parallel.\r
- * @return the previous value associated with the specified key,\r
- * or <tt>null</tt> if there was no mapping for the key\r
- * @throws NullPointerException if the specified key or value is null */\r
- public TypeV putIfAbsent( TypeK key, TypeV val ) { return putIfMatch( key, val, TOMBSTONE ); }\r
-\r
- /** Removes the key (and its corresponding value) from this map.\r
- * This method does nothing if the key is not in the map.\r
- * @return the previous value associated with <tt>key</tt>, or\r
- * <tt>null</tt> if there was no mapping for <tt>key</tt>\r
- * @throws NullPointerException if the specified key is null */\r
- @Override\r
- public TypeV remove ( Object key ) { return putIfMatch( key,TOMBSTONE, NO_MATCH_OLD); }\r
-\r
- /** Atomically do a {@link #remove(Object)} if-and-only-if the key is mapped\r
- * to a value which is <code>equals</code> to the given value.\r
- * @throws NullPointerException if the specified key or value is null */\r
- public boolean remove ( Object key,Object val ) { return putIfMatch( key,TOMBSTONE, val ) == val; }\r
-\r
- /** Atomically do a <code>put(key,val)</code> if-and-only-if the key is\r
- * mapped to some value already.\r
- * @throws NullPointerException if the specified key or value is null */\r
- public TypeV replace ( TypeK key, TypeV val ) { return putIfMatch( key, val,MATCH_ANY ); }\r
-\r
- /** Atomically do a <code>put(key,newValue)</code> if-and-only-if the key is\r
- * mapped a value which is <code>equals</code> to <code>oldValue</code>.\r
- * @throws NullPointerException if the specified key or value is null */\r
- public boolean replace ( TypeK key, TypeV oldValue, TypeV newValue ) {\r
- return putIfMatch( key, newValue, oldValue ) == oldValue;\r
- }\r
-\r
- private final TypeV putIfMatch( Object key, Object newVal, Object oldVal ) {\r
- if (oldVal == null || newVal == null) throw new NullPointerException();\r
- final Object res = putIfMatch( this, _kvs, key, newVal, oldVal );\r
- assert !(res instanceof Prime);\r
- assert res != null;\r
- return res == TOMBSTONE ? null : (TypeV)res;\r
- }\r
-\r
-\r
- /** Copies all of the mappings from the specified map to this one, replacing\r
- * any existing mappings.\r
- * @param m mappings to be stored in this map */\r
- @Override\r
- public void putAll(Map<? extends TypeK, ? extends TypeV> m) {\r
- for (Map.Entry<? extends TypeK, ? extends TypeV> e : m.entrySet())\r
- put(e.getKey(), e.getValue());\r
- }\r
-\r
- /** Removes all of the mappings from this map. */\r
- @Override\r
- public void clear() { // Smack a new empty table down\r
- Object[] newkvs = new NonBlockingHashMap(MIN_SIZE)._kvs;\r
- while( !CAS_kvs(_kvs,newkvs) ) // Spin until the clear works\r
- ;\r
- }\r
-\r
- /** Returns <tt>true</tt> if this Map maps one or more keys to the specified\r
- * value. <em>Note</em>: This method requires a full internal traversal of the\r
- * hash table and is much slower than {@link #containsKey}.\r
- * @param val value whose presence in this map is to be tested\r
- * @return <tt>true</tt> if this map maps one or more keys to the specified value\r
- * @throws NullPointerException if the specified value is null */\r
- @Override\r
- public boolean containsValue( final Object val ) {\r
- if( val == null ) throw new NullPointerException();\r
- for( TypeV V : values() )\r
- if( V == val || V.equals(val) )\r
- return true;\r
- return false;\r
- }\r
-\r
- // This function is supposed to do something for Hashtable, and the JCK\r
- // tests hang until it gets called... by somebody ... for some reason,\r
- // any reason....\r
- protected void rehash() {\r
- }\r
-\r
- /**\r
- * Creates a shallow copy of this hashtable. All the structure of the\r
- * hashtable itself is copied, but the keys and values are not cloned.\r
- * This is a relatively expensive operation.\r
- *\r
- * @return a clone of the hashtable.\r
- */\r
- @Override\r
- public Object clone() {\r
- try {\r
- // Must clone, to get the class right; NBHM might have been\r
- // extended so it would be wrong to just make a new NBHM.\r
- NonBlockingHashMap<TypeK,TypeV> t = (NonBlockingHashMap<TypeK,TypeV>) super.clone();\r
- // But I don't have an atomic clone operation - the underlying _kvs\r
- // structure is undergoing rapid change. If I just clone the _kvs\r
- // field, the CHM in _kvs[0] won't be in sync.\r
- //\r
- // Wipe out the cloned array (it was shallow anyways).\r
- t.clear();\r
- // Now copy sanely\r
- for( TypeK K : keySet() ) {\r
- final TypeV V = get(K); // Do an official 'get'\r
- t.put(K,V);\r
- }\r
- return t;\r
- } catch (CloneNotSupportedException e) {\r
- // this shouldn't happen, since we are Cloneable\r
- throw new InternalError();\r
- }\r
- }\r
-\r
- /**\r
- * Returns a string representation of this map. The string representation\r
- * consists of a list of key-value mappings in the order returned by the\r
- * map's <tt>entrySet</tt> view's iterator, enclosed in braces\r
- * (<tt>"{}"</tt>). Adjacent mappings are separated by the characters\r
- * <tt>", "</tt> (comma and space). Each key-value mapping is rendered as\r
- * the key followed by an equals sign (<tt>"="</tt>) followed by the\r
- * associated value. Keys and values are converted to strings as by\r
- * {@link String#valueOf(Object)}.\r
- *\r
- * @return a string representation of this map\r
- */\r
- @Override\r
- public String toString() {\r
- Iterator<Entry<TypeK,TypeV>> i = entrySet().iterator();\r
- if( !i.hasNext())\r
- return "{}";\r
-\r
- StringBuilder sb = new StringBuilder();\r
- sb.append('{');\r
- for (;;) {\r
- Entry<TypeK,TypeV> e = i.next();\r
- TypeK key = e.getKey();\r
- TypeV value = e.getValue();\r
- sb.append(key == this ? "(this Map)" : key);\r
- sb.append('=');\r
- sb.append(value == this ? "(this Map)" : value);\r
- if( !i.hasNext())\r
- return sb.append('}').toString();\r
- sb.append(", ");\r
- }\r
- }\r
-\r
- // --- keyeq ---------------------------------------------------------------\r
- // Check for key equality. Try direct pointer compare first, then see if\r
- // the hashes are unequal (fast negative test) and finally do the full-on\r
- // 'equals' v-call.\r
- private static boolean keyeq( Object K, Object key, int[] hashes, int hash, int fullhash ) {\r
- return\r
- K==key || // Either keys match exactly OR\r
- // hash exists and matches? hash can be zero during the install of a\r
- // new key/value pair.\r
- ((hashes[hash] == 0 || hashes[hash] == fullhash) &&\r
- // Do not call the users' "equals()" call with a Tombstone, as this can\r
- // surprise poorly written "equals()" calls that throw exceptions\r
- // instead of simply returning false.\r
- K != TOMBSTONE && // Do not call users' equals call with a Tombstone\r
- // Do the match the hard way - with the users' key being the loop-\r
- // invariant "this" pointer. I could have flipped the order of\r
- // operands (since equals is commutative), but I'm making mega-morphic\r
- // v-calls in a reprobing loop and nailing down the 'this' argument\r
- // gives both the JIT and the hardware a chance to prefetch the call target.\r
- key.equals(K)); // Finally do the hard match\r
- }\r
-\r
- // --- get -----------------------------------------------------------------\r
- /** Returns the value to which the specified key is mapped, or {@code null}\r
- * if this map contains no mapping for the key.\r
- * <p>More formally, if this map contains a mapping from a key {@code k} to\r
- * a value {@code v} such that {@code key.equals(k)}, then this method\r
- * returns {@code v}; otherwise it returns {@code null}. (There can be at\r
- * most one such mapping.)\r
- * @throws NullPointerException if the specified key is null */\r
- // Never returns a Prime nor a Tombstone.\r
- @Override\r
- public TypeV get( Object key ) {\r
- final int fullhash= hash (key); // throws NullPointerException if key is null\r
- final Object V = get_impl(this,_kvs,key,fullhash);\r
- assert !(V instanceof Prime); // Never return a Prime\r
- return (TypeV)V;\r
- }\r
-\r
- private static final Object get_impl( final NonBlockingHashMap topmap, final Object[] kvs, final Object key, final int fullhash ) {\r
- final int len = len (kvs); // Count of key/value pairs, reads kvs.length\r
- final CHM chm = chm (kvs); // The CHM, for a volatile read below; reads slot 0 of kvs\r
- final int[] hashes=hashes(kvs); // The memoized hashes; reads slot 1 of kvs\r
-\r
- int idx = fullhash & (len-1); // First key hash\r
-\r
- // Main spin/reprobe loop, looking for a Key hit\r
- int reprobe_cnt=0;\r
- while( true ) {\r
- // Probe table. Each read of 'val' probably misses in cache in a big\r
- // table; hopefully the read of 'key' then hits in cache.\r
- final Object K = key(kvs,idx); // Get key before volatile read, could be null\r
- final Object V = val(kvs,idx); // Get value before volatile read, could be null or Tombstone or Prime\r
- if( K == null ) return null; // A clear miss\r
-\r
- // We need a volatile-read here to preserve happens-before semantics on\r
- // newly inserted Keys. If the Key body was written just before inserting\r
- // into the table a Key-compare here might read the uninitalized Key body.\r
- // Annoyingly this means we have to volatile-read before EACH key compare.\r
- // .\r
- // We also need a volatile-read between reading a newly inserted Value\r
- // and returning the Value (so the user might end up reading the stale\r
- // Value contents). Same problem as with keys - and the one volatile\r
- // read covers both.\r
- final Object[] newkvs = chm._newkvs; // VOLATILE READ before key compare\r
-\r
- // Key-compare\r
- if( keyeq(K,key,hashes,idx,fullhash) ) {\r
- // Key hit! Check for no table-copy-in-progress\r
- if( !(V instanceof Prime) ) // No copy?\r
- return (V == TOMBSTONE) ? null : V; // Return the value\r
- // Key hit - but slot is (possibly partially) copied to the new table.\r
- // Finish the copy & retry in the new table.\r
- return get_impl(topmap,chm.copy_slot_and_check(topmap,kvs,idx,key),key,fullhash); // Retry in the new table\r
- }\r
- // get and put must have the same key lookup logic! But only 'put'\r
- // needs to force a table-resize for a too-long key-reprobe sequence.\r
- // Check for too-many-reprobes on get - and flip to the new table.\r
- // ???? Why a TOMBSTONE key means no more keys in this table\r
- // because a TOMBSTONE key should be null before\r
- if( ++reprobe_cnt >= reprobe_limit(len) || // too many probes\r
- key == TOMBSTONE ) // found a TOMBSTONE key, means no more keys in this table\r
- return newkvs == null ? null : get_impl(topmap,topmap.help_copy(newkvs),key,fullhash); // Retry in the new table\r
-\r
- idx = (idx+1)&(len-1); // Reprobe by 1! (could now prefetch)\r
- }\r
- }\r
-\r
- // --- putIfMatch ---------------------------------------------------------\r
- // Put, Remove, PutIfAbsent, etc. Return the old value. If the returned\r
- // value is equal to expVal (or expVal is NO_MATCH_OLD) then the put can be\r
- // assumed to work (although might have been immediately overwritten). Only\r
- // the path through copy_slot passes in an expected value of null, and\r
- // putIfMatch only returns a null if passed in an expected null.\r
- private static final Object putIfMatch( final NonBlockingHashMap topmap, final Object[] kvs, final Object key, final Object putval, final Object expVal ) {\r
- assert putval != null;\r
- assert !(putval instanceof Prime);\r
- assert !(expVal instanceof Prime);\r
- final int fullhash = hash (key); // throws NullPointerException if key null\r
- final int len = len (kvs); // Count of key/value pairs, reads kvs.length\r
- final CHM chm = chm (kvs); // Reads kvs[0]\r
- final int[] hashes = hashes(kvs); // Reads kvs[1], read before kvs[0]\r
- int idx = fullhash & (len-1);\r
-\r
- // ---\r
- // Key-Claim stanza: spin till we can claim a Key (or force a resizing).\r
- int reprobe_cnt=0;\r
- Object K=null, V=null;\r
- Object[] newkvs=null;\r
- while( true ) { // Spin till we get a Key slot\r
- V = val(kvs,idx); // Get old value (before volatile read below!)\r
- K = key(kvs,idx); // Get current key\r
- if( K == null ) { // Slot is free?\r
- // Found an empty Key slot - which means this Key has never been in\r
- // this table. No need to put a Tombstone - the Key is not here!\r
- if( putval == TOMBSTONE ) return putval; // Not-now & never-been in this table\r
- // Claim the null key-slot\r
- if( CAS_key(kvs,idx, null, key ) ) { // Claim slot for Key\r
- chm._slots.add(1); // Raise key-slots-used count\r
- hashes[idx] = fullhash; // Memoize fullhash\r
- break; // Got it!\r
- }\r
- // CAS to claim the key-slot failed.\r
- //\r
- // This re-read of the Key points out an annoying short-coming of Java\r
- // CAS. Most hardware CAS's report back the existing value - so that\r
- // if you fail you have a *witness* - the value which caused the CAS\r
- // to fail. The Java API turns this into a boolean destroying the\r
- // witness. Re-reading does not recover the witness because another\r
- // thread can write over the memory after the CAS. Hence we can be in\r
- // the unfortunate situation of having a CAS fail *for cause* but\r
- // having that cause removed by a later store. This turns a\r
- // non-spurious-failure CAS (such as Azul has) into one that can\r
- // apparently spuriously fail - and we avoid apparent spurious failure\r
- // by not allowing Keys to ever change.\r
- K = key(kvs,idx); // CAS failed, get updated value\r
- assert K != null; // If keys[idx] is null, CAS shoulda worked\r
- }\r
- // Key slot was not null, there exists a Key here\r
-\r
- // We need a volatile-read here to preserve happens-before semantics on\r
- // newly inserted Keys. If the Key body was written just before inserting\r
- // into the table a Key-compare here might read the uninitalized Key body.\r
- // Annoyingly this means we have to volatile-read before EACH key compare.\r
- newkvs = chm._newkvs; // VOLATILE READ before key compare\r
-\r
- if( keyeq(K,key,hashes,idx,fullhash) )\r
- break; // Got it!\r
-\r
- // get and put must have the same key lookup logic! Lest 'get' give\r
- // up looking too soon.\r
- //topmap._reprobes.add(1);\r
- if( ++reprobe_cnt >= reprobe_limit(len) || // too many probes or\r
- key == TOMBSTONE ) { // found a TOMBSTONE key, means no more keys\r
- // We simply must have a new table to do a 'put'. At this point a\r
- // 'get' will also go to the new table (if any). We do not need\r
- // to claim a key slot (indeed, we cannot find a free one to claim!).\r
- newkvs = chm.resize(topmap,kvs);\r
- if( expVal != null ) topmap.help_copy(newkvs); // help along an existing copy\r
- return putIfMatch(topmap,newkvs,key,putval,expVal);\r
- }\r
-\r
- idx = (idx+1)&(len-1); // Reprobe!\r
- } // End of spinning till we get a Key slot\r
-\r
- // ---\r
- // Found the proper Key slot, now update the matching Value slot. We\r
- // never put a null, so Value slots monotonically move from null to\r
- // not-null (deleted Values use Tombstone). Thus if 'V' is null we\r
- // fail this fast cutout and fall into the check for table-full.\r
- if( putval == V ) return V; // Fast cutout for no-change\r
-\r
- // See if we want to move to a new table (to avoid high average re-probe\r
- // counts). We only check on the initial set of a Value from null to\r
- // not-null (i.e., once per key-insert). Of course we got a 'free' check\r
- // of newkvs once per key-compare (not really free, but paid-for by the\r
- // time we get here).\r
- if( newkvs == null && // New table-copy already spotted?\r
- // Once per fresh key-insert check the hard way\r
- ((V == null && chm.tableFull(reprobe_cnt,len)) ||\r
- // Or we found a Prime, but the JMM allowed reordering such that we\r
- // did not spot the new table (very rare race here: the writing\r
- // thread did a CAS of _newkvs then a store of a Prime. This thread\r
- // reads the Prime, then reads _newkvs - but the read of Prime was so\r
- // delayed (or the read of _newkvs was so accelerated) that they\r
- // swapped and we still read a null _newkvs. The resize call below\r
- // will do a CAS on _newkvs forcing the read.\r
- V instanceof Prime) )\r
- newkvs = chm.resize(topmap,kvs); // Force the new table copy to start\r
- // See if we are moving to a new table.\r
- // If so, copy our slot and retry in the new table.\r
- if( newkvs != null )\r
- return putIfMatch(topmap,chm.copy_slot_and_check(topmap,kvs,idx,expVal),key,putval,expVal);\r
-\r
- // ---\r
- // We are finally prepared to update the existing table\r
- while( true ) {\r
- assert !(V instanceof Prime);\r
-\r
- // Must match old, and we do not? Then bail out now. Note that either V\r
- // or expVal might be TOMBSTONE. Also V can be null, if we've never\r
- // inserted a value before. expVal can be null if we are called from\r
- // copy_slot.\r
-\r
- if( expVal != NO_MATCH_OLD && // Do we care about expected-Value at all?\r
- V != expVal && // No instant match already?\r
- (expVal != MATCH_ANY || V == TOMBSTONE || V == null) &&\r
- !(V==null && expVal == TOMBSTONE) && // Match on null/TOMBSTONE combo\r
- (expVal == null || !expVal.equals(V)) ) // Expensive equals check at the last\r
- return V; // Do not update!\r
-\r
- // Actually change the Value in the Key,Value pair\r
- if( CAS_val(kvs, idx, V, putval ) ) {\r
- // CAS succeeded - we did the update!\r
- // Both normal put's and table-copy calls putIfMatch, but table-copy\r
- // does not (effectively) increase the number of live k/v pairs.\r
- if( expVal != null ) {\r
- // Adjust sizes - a striped counter\r
- if( (V == null || V == TOMBSTONE) && putval != TOMBSTONE ) chm._size.add( 1);\r
- if( !(V == null || V == TOMBSTONE) && putval == TOMBSTONE ) chm._size.add(-1);\r
- }\r
- return (V==null && expVal!=null) ? TOMBSTONE : V;\r
- } \r
- // Else CAS failed\r
- V = val(kvs,idx); // Get new value\r
- // If a Prime'd value got installed, we need to re-run the put on the\r
- // new table. Otherwise we lost the CAS to another racing put.\r
- // Simply retry from the start.\r
- if( V instanceof Prime )\r
- return putIfMatch(topmap,chm.copy_slot_and_check(topmap,kvs,idx,expVal),key,putval,expVal);\r
- }\r
- }\r
-\r
- // --- help_copy ---------------------------------------------------------\r
- // Help along an existing resize operation. This is just a fast cut-out\r
- // wrapper, to encourage inlining for the fast no-copy-in-progress case. We\r
- // always help the top-most table copy, even if there are nested table\r
- // copies in progress.\r
- private final Object[] help_copy( Object[] helper ) {\r
- // Read the top-level KVS only once. We'll try to help this copy along,\r
- // even if it gets promoted out from under us (i.e., the copy completes\r
- // and another KVS becomes the top-level copy).\r
- Object[] topkvs = _kvs;\r
- CHM topchm = chm(topkvs);\r
- if( topchm._newkvs == null ) return helper; // No copy in-progress\r
- topchm.help_copy_impl(this,topkvs,false);\r
- return helper;\r
- }\r
-\r
-\r
- // --- CHM -----------------------------------------------------------------\r
- // The control structure for the NonBlockingHashMap\r
- private static final class CHM<TypeK,TypeV> {\r
- // Size in active K,V pairs\r
- private final Counter _size;\r
- public int size () { return (int)_size.get(); }\r
-\r
- // ---\r
- // These next 2 fields are used in the resizing heuristics, to judge when\r
- // it is time to resize or copy the table. Slots is a count of used-up\r
- // key slots, and when it nears a large fraction of the table we probably\r
- // end up reprobing too much. Last-resize-milli is the time since the\r
- // last resize; if we are running back-to-back resizes without growing\r
- // (because there are only a few live keys but many slots full of dead\r
- // keys) then we need a larger table to cut down on the churn.\r
-\r
- // Count of used slots, to tell when table is full of dead unusable slots\r
- private final Counter _slots;\r
- public int slots() { return (int)_slots.get(); }\r
-\r
- // ---\r
- // New mappings, used during resizing.\r
- // The 'new KVs' array - created during a resize operation. This\r
- // represents the new table being copied from the old one. It's the\r
- // volatile variable that is read as we cross from one table to the next,\r
- // to get the required memory orderings. It monotonically transits from\r
- // null to set (once).\r
- volatile Object[] _newkvs;\r
- private final AtomicReferenceFieldUpdater<CHM,Object[]> _newkvsUpdater =\r
- AtomicReferenceFieldUpdater.newUpdater(CHM.class,Object[].class, "_newkvs");\r
- // Set the _next field if we can.\r
- boolean CAS_newkvs( Object[] newkvs ) {\r
- while( _newkvs == null )\r
- if( _newkvsUpdater.compareAndSet(this,null,newkvs) )\r
- return true;\r
- return false;\r
- }\r
- // Sometimes many threads race to create a new very large table. Only 1\r
- // wins the race, but the losers all allocate a junk large table with\r
- // hefty allocation costs. Attempt to control the overkill here by\r
- // throttling attempts to create a new table. I cannot really block here\r
- // (lest I lose the non-blocking property) but late-arriving threads can\r
- // give the initial resizing thread a little time to allocate the initial\r
- // new table. The Right Long Term Fix here is to use array-lets and\r
- // incrementally create the new very large array. In C I'd make the array\r
- // with malloc (which would mmap under the hood) which would only eat\r
- // virtual-address and not real memory - and after Somebody wins then we\r
- // could in parallel initialize the array. Java does not allow\r
- // un-initialized array creation (especially of ref arrays!).\r
- volatile long _resizers; // count of threads attempting an initial resize\r
- private static final AtomicLongFieldUpdater<CHM> _resizerUpdater =\r
- AtomicLongFieldUpdater.newUpdater(CHM.class, "_resizers");\r
-\r
- // ---\r
- // Simple constructor\r
- CHM( Counter size ) {\r
- _size = size;\r
- _slots= new Counter();\r
- }\r
-\r
- // --- tableFull ---------------------------------------------------------\r
- // Heuristic to decide if this table is too full, and we should start a\r
- // new table. Note that if a 'get' call has reprobed too many times and\r
- // decided the table must be full, then always the estimate_sum must be\r
- // high and we must report the table is full. If we do not, then we might\r
- // end up deciding that the table is not full and inserting into the\r
- // current table, while a 'get' has decided the same key cannot be in this\r
- // table because of too many reprobes. The invariant is:\r
- // slots.estimate_sum >= max_reprobe_cnt >= reprobe_limit(len)\r
- private final boolean tableFull( int reprobe_cnt, int len ) {\r
- return\r
- // Do the cheap check first: we allow some number of reprobes always\r
- reprobe_cnt >= REPROBE_LIMIT &&\r
- // More expensive check: see if the table is > 1/4 full.\r
- _slots.estimate_get() >= reprobe_limit(len);\r
- }\r
-\r
- // --- resize ------------------------------------------------------------\r
- // Resizing after too many probes. "How Big???" heuristics are here.\r
- // Callers will (not this routine) will 'help_copy' any in-progress copy.\r
- // Since this routine has a fast cutout for copy-already-started, callers\r
- // MUST 'help_copy' lest we have a path which forever runs through\r
- // 'resize' only to discover a copy-in-progress which never progresses.\r
- private final Object[] resize( NonBlockingHashMap topmap, Object[] kvs) {\r
- assert chm(kvs) == this;\r
-\r
- // Check for resize already in progress, probably triggered by another thread\r
- Object[] newkvs = _newkvs; // VOLATILE READ\r
- if( newkvs != null ) // See if resize is already in progress\r
- return newkvs; // Use the new table already\r
-\r
- // No copy in-progress, so start one. First up: compute new table size.\r
- int oldlen = len(kvs); // Old count of K,V pairs allowed\r
- int sz = size(); // Get current table count of active K,V pairs\r
- int newsz = sz; // First size estimate\r
-\r
- // Heuristic to determine new size. We expect plenty of dead-slots-with-keys\r
- // and we need some decent padding to avoid endless reprobing.\r
- if( sz >= (oldlen>>2) ) { // If we are >25% full of keys then...\r
- newsz = oldlen<<1; // Double size\r
- if( sz >= (oldlen>>1) ) // If we are >50% full of keys then...\r
- newsz = oldlen<<2; // Double double size\r
- }\r
- // This heuristic in the next 2 lines leads to a much denser table\r
- // with a higher reprobe rate\r
- //if( sz >= (oldlen>>1) ) // If we are >50% full of keys then...\r
- // newsz = oldlen<<1; // Double size\r
-\r
- // Last (re)size operation was very recent? Then double again; slows\r
- // down resize operations for tables subject to a high key churn rate.\r
- long tm = System.currentTimeMillis();\r
- long q=0;\r
- if( newsz <= oldlen && // New table would shrink or hold steady?\r
- tm <= topmap._last_resize_milli+10000 && // Recent resize (less than 1 sec ago)\r
- (q=_slots.estimate_get()) >= (sz<<1) ) // 1/2 of keys are dead?\r
- newsz = oldlen<<1; // Double the existing size\r
-\r
- // Do not shrink, ever\r
- if( newsz < oldlen ) newsz = oldlen;\r
-\r
- // Convert to power-of-2\r
- int log2;\r
- for( log2=MIN_SIZE_LOG; (1<<log2) < newsz; log2++ ) ; // Compute log2 of size\r
-\r
- // Now limit the number of threads actually allocating memory to a\r
- // handful - lest we have 750 threads all trying to allocate a giant\r
- // resized array.\r
- long r = _resizers;\r
- while( !_resizerUpdater.compareAndSet(this,r,r+1) )\r
- r = _resizers;\r
- // Size calculation: 2 words (K+V) per table entry, plus a handful. We\r
- // guess at 32-bit pointers; 64-bit pointers screws up the size calc by\r
- // 2x but does not screw up the heuristic very much.\r
- int megs = ((((1<<log2)<<1)+4)<<3/*word to bytes*/)>>20/*megs*/;\r
- if( r >= 2 && megs > 0 ) { // Already 2 guys trying; wait and see\r
- newkvs = _newkvs; // Between dorking around, another thread did it\r
- if( newkvs != null ) // See if resize is already in progress\r
- return newkvs; // Use the new table already\r
- // TODO - use a wait with timeout, so we'll wakeup as soon as the new table\r
- // is ready, or after the timeout in any case.\r
- //synchronized( this ) { wait(8*megs); } // Timeout - we always wakeup\r
- // For now, sleep a tad and see if the 2 guys already trying to make\r
- // the table actually get around to making it happen.\r
- try { Thread.sleep(8*megs); } catch( Exception e ) { }\r
- }\r
- // Last check, since the 'new' below is expensive and there is a chance\r
- // that another thread slipped in a new thread while we ran the heuristic.\r
- newkvs = _newkvs;\r
- if( newkvs != null ) // See if resize is already in progress\r
- return newkvs; // Use the new table already\r
-\r
- // Double size for K,V pairs, add 1 for CHM\r
- newkvs = new Object[((1<<log2)<<1)+2]; // This can get expensive for big arrays\r
- newkvs[0] = new CHM(_size); // CHM in slot 0\r
- newkvs[1] = new int[1<<log2]; // hashes in slot 1\r
-\r
- // Another check after the slow allocation\r
- if( _newkvs != null ) // See if resize is already in progress\r
- return _newkvs; // Use the new table already\r
-\r
- // The new table must be CAS'd in so only 1 winner amongst duplicate\r
- // racing resizing threads. Extra CHM's will be GC'd.\r
- if( CAS_newkvs( newkvs ) ) { // NOW a resize-is-in-progress!\r
- //notifyAll(); // Wake up any sleepers\r
- //long nano = System.nanoTime();\r
- //System.out.println(" "+nano+" Resize from "+oldlen+" to "+(1<<log2)+" and had "+(_resizers-1)+" extras" );\r
- //if( System.out != null ) System.out.print("["+log2);\r
- topmap.rehash(); // Call for Hashtable's benefit\r
- } else // CAS failed?\r
- newkvs = _newkvs; // Reread new table\r
- return newkvs;\r
- }\r
-\r
-\r
- // The next part of the table to copy. It monotonically transits from zero\r
- // to _kvs.length. Visitors to the table can claim 'work chunks' by\r
- // CAS'ing this field up, then copying the indicated indices from the old\r
- // table to the new table. Workers are not required to finish any chunk;\r
- // the counter simply wraps and work is copied duplicately until somebody\r
- // somewhere completes the count.\r
- volatile long _copyIdx = 0;\r
- static private final AtomicLongFieldUpdater<CHM> _copyIdxUpdater =\r
- AtomicLongFieldUpdater.newUpdater(CHM.class, "_copyIdx");\r
-\r
- // Work-done reporting. Used to efficiently signal when we can move to\r
- // the new table. From 0 to len(oldkvs) refers to copying from the old\r
- // table to the new.\r
- volatile long _copyDone= 0;\r
- static private final AtomicLongFieldUpdater<CHM> _copyDoneUpdater =\r
- AtomicLongFieldUpdater.newUpdater(CHM.class, "_copyDone");\r
-\r
- // --- help_copy_impl ----------------------------------------------------\r
- // Help along an existing resize operation. We hope its the top-level\r
- // copy (it was when we started) but this CHM might have been promoted out\r
- // of the top position.\r
- private final void help_copy_impl( NonBlockingHashMap topmap, Object[] oldkvs, boolean copy_all ) {\r
- assert chm(oldkvs) == this;\r
- Object[] newkvs = _newkvs;\r
- assert newkvs != null; // Already checked by caller\r
- int oldlen = len(oldkvs); // Total amount to copy\r
- final int MIN_COPY_WORK = Math.min(oldlen,1024); // Limit per-thread work\r
-\r
- // ---\r
- int panic_start = -1;\r
- int copyidx=-9999; // Fool javac to think it's initialized\r
- while( _copyDone < oldlen ) { // Still needing to copy?\r
- // Carve out a chunk of work. The counter wraps around so every\r
- // thread eventually tries to copy every slot repeatedly.\r
-\r
- // We "panic" if we have tried TWICE to copy every slot - and it still\r
- // has not happened. i.e., twice some thread somewhere claimed they\r
- // would copy 'slot X' (by bumping _copyIdx) but they never claimed to\r
- // have finished (by bumping _copyDone). Our choices become limited:\r
- // we can wait for the work-claimers to finish (and become a blocking\r
- // algorithm) or do the copy work ourselves. Tiny tables with huge\r
- // thread counts trying to copy the table often 'panic'.\r
- if( panic_start == -1 ) { // No panic?\r
- copyidx = (int)_copyIdx;\r
- while( copyidx < (oldlen<<1) && // 'panic' check\r
- !_copyIdxUpdater.compareAndSet(this,copyidx,copyidx+MIN_COPY_WORK) )\r
- copyidx = (int)_copyIdx; // Re-read\r
- if( !(copyidx < (oldlen<<1)) ) // Panic!\r
- panic_start = copyidx; // Record where we started to panic-copy\r
- }\r
-\r
- // We now know what to copy. Try to copy.\r
- int workdone = 0;\r
- for( int i=0; i<MIN_COPY_WORK; i++ )\r
- if( copy_slot(topmap,(copyidx+i)&(oldlen-1),oldkvs,newkvs) ) // Made an oldtable slot go dead?\r
- workdone++; // Yes!\r
- if( workdone > 0 ) // Report work-done occasionally\r
- copy_check_and_promote( topmap, oldkvs, workdone );// See if we can promote\r
- //for( int i=0; i<MIN_COPY_WORK; i++ )\r
- // if( copy_slot(topmap,(copyidx+i)&(oldlen-1),oldkvs,newkvs) ) // Made an oldtable slot go dead?\r
- // copy_check_and_promote( topmap, oldkvs, 1 );// See if we can promote\r
-\r
- copyidx += MIN_COPY_WORK;\r
- // Uncomment these next 2 lines to turn on incremental table-copy.\r
- // Otherwise this thread continues to copy until it is all done.\r
- if( !copy_all && panic_start == -1 ) // No panic?\r
- return; // Then done copying after doing MIN_COPY_WORK\r
- }\r
- // Extra promotion check, in case another thread finished all copying\r
- // then got stalled before promoting.\r
- copy_check_and_promote( topmap, oldkvs, 0 );// See if we can promote\r
- }\r
-\r
-\r
- // --- copy_slot_and_check -----------------------------------------------\r
- // Copy slot 'idx' from the old table to the new table. If this thread\r
- // confirmed the copy, update the counters and check for promotion.\r
- //\r
- // Returns the result of reading the volatile _newkvs, mostly as a\r
- // convenience to callers. We come here with 1-shot copy requests\r
- // typically because the caller has found a Prime, and has not yet read\r
- // the _newkvs volatile - which must have changed from null-to-not-null\r
- // before any Prime appears. So the caller needs to read the _newkvs\r
- // field to retry his operation in the new table, but probably has not\r
- // read it yet.\r
- private final Object[] copy_slot_and_check( NonBlockingHashMap topmap, Object[] oldkvs, int idx, Object should_help ) {\r
- assert chm(oldkvs) == this;\r
- Object[] newkvs = _newkvs; // VOLATILE READ\r
- // We're only here because the caller saw a Prime, which implies a\r
- // table-copy is in progress.\r
- assert newkvs != null;\r
- if( copy_slot(topmap,idx,oldkvs,_newkvs) ) // Copy the desired slot\r
- copy_check_and_promote(topmap, oldkvs, 1); // Record the slot copied\r
- // Generically help along any copy (except if called recursively from a helper)\r
- return (should_help == null) ? newkvs : topmap.help_copy(newkvs);\r
- }\r
-\r
- // --- copy_check_and_promote --------------------------------------------\r
- private final void copy_check_and_promote( NonBlockingHashMap topmap, Object[] oldkvs, int workdone ) {\r
- assert chm(oldkvs) == this;\r
- int oldlen = len(oldkvs);\r
- // We made a slot unusable and so did some of the needed copy work\r
- long copyDone = _copyDone;\r
- assert (copyDone+workdone) <= oldlen;\r
- if( workdone > 0 ) {\r
- while( !_copyDoneUpdater.compareAndSet(this,copyDone,copyDone+workdone) ) {\r
- copyDone = _copyDone; // Reload, retry\r
- assert (copyDone+workdone) <= oldlen;\r
- }\r
- //if( (10*copyDone/oldlen) != (10*(copyDone+workdone)/oldlen) )\r
- //System.out.print(" "+(copyDone+workdone)*100/oldlen+"%"+"_"+(_copyIdx*100/oldlen)+"%");\r
- }\r
-\r
- // Check for copy being ALL done, and promote. Note that we might have\r
- // nested in-progress copies and manage to finish a nested copy before\r
- // finishing the top-level copy. We only promote top-level copies.\r
- if( copyDone+workdone == oldlen && // Ready to promote this table?\r
- topmap._kvs == oldkvs && // Looking at the top-level table?\r
- // Attempt to promote\r
- topmap.CAS_kvs(oldkvs,_newkvs) ) {\r
- topmap._last_resize_milli = System.currentTimeMillis(); // Record resize time for next check\r
- //long nano = System.nanoTime();\r
- //System.out.println(" "+nano+" Promote table to "+len(_newkvs));\r
- //if( System.out != null ) System.out.print("]");\r
- }\r
- }\r
- // --- copy_slot ---------------------------------------------------------\r
- // Copy one K/V pair from oldkvs[i] to newkvs. Returns true if we can\r
- // confirm that the new table guaranteed has a value for this old-table\r
- // slot. We need an accurate confirmed-copy count so that we know when we\r
- // can promote (if we promote the new table too soon, other threads may\r
- // 'miss' on values not-yet-copied from the old table). We don't allow\r
- // any direct updates on the new table, unless they first happened to the\r
- // old table - so that any transition in the new table from null to\r
- // not-null must have been from a copy_slot (or other old-table overwrite)\r
- // and not from a thread directly writing in the new table. Thus we can\r
- // count null-to-not-null transitions in the new table.\r
- private boolean copy_slot( NonBlockingHashMap topmap, int idx, Object[] oldkvs, Object[] newkvs ) {\r
- // Blindly set the key slot from null to TOMBSTONE, to eagerly stop\r
- // fresh put's from inserting new values in the old table when the old\r
- // table is mid-resize. We don't need to act on the results here,\r
- // because our correctness stems from box'ing the Value field. Slamming\r
- // the Key field is a minor speed optimization.\r
- Object key;\r
- while( (key=key(oldkvs,idx)) == null )\r
- CAS_key(oldkvs,idx, null, TOMBSTONE);\r
-\r
- // ---\r
- // Prevent new values from appearing in the old table.\r
- // Box what we see in the old table, to prevent further updates.\r
- Object oldval = val(oldkvs,idx); // Read OLD table\r
- while( !(oldval instanceof Prime) ) {\r
- final Prime box = (oldval == null || oldval == TOMBSTONE) ? TOMBPRIME : new Prime(oldval);\r
- if( CAS_val(oldkvs,idx,oldval,box) ) { // CAS down a box'd version of oldval\r
- // If we made the Value slot hold a TOMBPRIME, then we both\r
- // prevented further updates here but also the (absent)\r
- // oldval is vaccuously available in the new table. We\r
- // return with true here: any thread looking for a value for\r
- // this key can correctly go straight to the new table and\r
- // skip looking in the old table.\r
- if( box == TOMBPRIME )\r
- return true;\r
- // Otherwise we boxed something, but it still needs to be\r
- // copied into the new table.\r
- oldval = box; // Record updated oldval\r
- break; // Break loop; oldval is now boxed by us\r
- }\r
- oldval = val(oldkvs,idx); // Else try, try again\r
- }\r
- if( oldval == TOMBPRIME ) return false; // Copy already complete here!\r
-\r
- // ---\r
- // Copy the value into the new table, but only if we overwrite a null.\r
- // If another value is already in the new table, then somebody else\r
- // wrote something there and that write is happens-after any value that\r
- // appears in the old table. If putIfMatch does not find a null in the\r
- // new table - somebody else should have recorded the null-not_null\r
- // transition in this copy.\r
- Object old_unboxed = ((Prime)oldval)._V;\r
- assert old_unboxed != TOMBSTONE;\r
- boolean copied_into_new = (putIfMatch(topmap, newkvs, key, old_unboxed, null) == null);\r
-\r
- // ---\r
- // Finally, now that any old value is exposed in the new table, we can\r
- // forever hide the old-table value by slapping a TOMBPRIME down. This\r
- // will stop other threads from uselessly attempting to copy this slot\r
- // (i.e., it's a speed optimization not a correctness issue).\r
- while( !CAS_val(oldkvs,idx,oldval,TOMBPRIME) )\r
- oldval = val(oldkvs,idx);\r
-\r
- return copied_into_new;\r
- } // end copy_slot\r
- } // End of CHM\r
-\r
-\r
- // --- Snapshot ------------------------------------------------------------\r
- // The main class for iterating over the NBHM. It "snapshots" a clean\r
- // view of the K/V array.\r
- private class SnapshotV implements Iterator<TypeV>, Enumeration<TypeV> {\r
- final Object[] _sskvs;\r
- public SnapshotV() {\r
- while( true ) { // Verify no table-copy-in-progress\r
- Object[] topkvs = _kvs;\r
- CHM topchm = chm(topkvs);\r
- if( topchm._newkvs == null ) { // No table-copy-in-progress\r
- // The "linearization point" for the iteration. Every key in this\r
- // table will be visited, but keys added later might be skipped or\r
- // even be added to a following table (also not iterated over).\r
- _sskvs = topkvs;\r
- break;\r
- }\r
- // Table copy in-progress - so we cannot get a clean iteration. We\r
- // must help finish the table copy before we can start iterating.\r
- topchm.help_copy_impl(NonBlockingHashMap.this,topkvs,true);\r
- }\r
- // Warm-up the iterator\r
- next();\r
- }\r
- int length() { return len(_sskvs); }\r
- Object key(int idx) { return NonBlockingHashMap.key(_sskvs,idx); }\r
- private int _idx; // Varies from 0-keys.length\r
- private Object _nextK, _prevK; // Last 2 keys found\r
- private TypeV _nextV, _prevV; // Last 2 values found\r
- public boolean hasNext() { return _nextV != null; }\r
- public TypeV next() {\r
- // 'next' actually knows what the next value will be - it had to\r
- // figure that out last go-around lest 'hasNext' report true and\r
- // some other thread deleted the last value. Instead, 'next'\r
- // spends all its effort finding the key that comes after the\r
- // 'next' key.\r
- if( _idx != 0 && _nextV == null ) throw new NoSuchElementException();\r
- _prevK = _nextK; // This will become the previous key\r
- _prevV = _nextV; // This will become the previous value\r
- _nextV = null; // We have no more next-key\r
- // Attempt to set <_nextK,_nextV> to the next K,V pair.\r
- // _nextV is the trigger: stop searching when it is != null\r
- while( _idx<length() ) { // Scan array\r
- _nextK = key(_idx++); // Get a key that definitely is in the set (for the moment!)\r
- if( _nextK != null && // Found something?\r
- _nextK != TOMBSTONE &&\r
- (_nextV=get(_nextK)) != null )\r
- break; // Got it! _nextK is a valid Key\r
- } // Else keep scanning\r
- return _prevV; // Return current value.\r
- }\r
- public void remove() {\r
- if( _prevV == null ) throw new IllegalStateException();\r
- putIfMatch( NonBlockingHashMap.this, _sskvs, _prevK, TOMBSTONE, _prevV );\r
- _prevV = null;\r
- }\r
-\r
- public TypeV nextElement() { return next(); }\r
- public boolean hasMoreElements() { return hasNext(); }\r
- }\r
-\r
- /** Returns an enumeration of the values in this table.\r
- * @return an enumeration of the values in this table\r
- * @see #values() */\r
- public Enumeration<TypeV> elements() { return new SnapshotV(); }\r
-\r
- // --- values --------------------------------------------------------------\r
- /** Returns a {@link Collection} view of the values contained in this map.\r
- * The collection is backed by the map, so changes to the map are reflected\r
- * in the collection, and vice-versa. The collection supports element\r
- * removal, which removes the corresponding mapping from this map, via the\r
- * <tt>Iterator.remove</tt>, <tt>Collection.remove</tt>,\r
- * <tt>removeAll</tt>, <tt>retainAll</tt>, and <tt>clear</tt> operations.\r
- * It does not support the <tt>add</tt> or <tt>addAll</tt> operations.\r
- *\r
- * <p>The view's <tt>iterator</tt> is a "weakly consistent" iterator that\r
- * will never throw {@link ConcurrentModificationException}, and guarantees\r
- * to traverse elements as they existed upon construction of the iterator,\r
- * and may (but is not guaranteed to) reflect any modifications subsequent\r
- * to construction. */\r
- @Override\r
- public Collection<TypeV> values() {\r
- return new AbstractCollection<TypeV>() {\r
- @Override public void clear ( ) { NonBlockingHashMap.this.clear ( ); }\r
- @Override public int size ( ) { return NonBlockingHashMap.this.size ( ); }\r
- @Override public boolean contains( Object v ) { return NonBlockingHashMap.this.containsValue(v); }\r
- @Override public Iterator<TypeV> iterator() { return new SnapshotV(); }\r
- };\r
- }\r
-\r
- // --- keySet --------------------------------------------------------------\r
- private class SnapshotK implements Iterator<TypeK>, Enumeration<TypeK> {\r
- final SnapshotV _ss;\r
- public SnapshotK() { _ss = new SnapshotV(); }\r
- public void remove() { _ss.remove(); }\r
- public TypeK next() { _ss.next(); return (TypeK)_ss._prevK; }\r
- public boolean hasNext() { return _ss.hasNext(); }\r
- public TypeK nextElement() { return next(); }\r
- public boolean hasMoreElements() { return hasNext(); }\r
- }\r
-\r
- /** Returns an enumeration of the keys in this table.\r
- * @return an enumeration of the keys in this table\r
- * @see #keySet() */\r
- public Enumeration<TypeK> keys() { return new SnapshotK(); }\r
-\r
- /** Returns a {@link Set} view of the keys contained in this map. The set\r
- * is backed by the map, so changes to the map are reflected in the set,\r
- * and vice-versa. The set supports element removal, which removes the\r
- * corresponding mapping from this map, via the <tt>Iterator.remove</tt>,\r
- * <tt>Set.remove</tt>, <tt>removeAll</tt>, <tt>retainAll</tt>, and\r
- * <tt>clear</tt> operations. It does not support the <tt>add</tt> or\r
- * <tt>addAll</tt> operations.\r
- *\r
- * <p>The view's <tt>iterator</tt> is a "weakly consistent" iterator that\r
- * will never throw {@link ConcurrentModificationException}, and guarantees\r
- * to traverse elements as they existed upon construction of the iterator,\r
- * and may (but is not guaranteed to) reflect any modifications subsequent\r
- * to construction. */\r
- @Override\r
- public Set<TypeK> keySet() {\r
- return new AbstractSet<TypeK> () {\r
- @Override public void clear ( ) { NonBlockingHashMap.this.clear ( ); }\r
- @Override public int size ( ) { return NonBlockingHashMap.this.size ( ); }\r
- @Override public boolean contains( Object k ) { return NonBlockingHashMap.this.containsKey(k); }\r
- @Override public boolean remove ( Object k ) { return NonBlockingHashMap.this.remove (k) != null; }\r
- @Override public Iterator<TypeK> iterator() { return new SnapshotK(); }\r
- };\r
- }\r
-\r
-\r
- // --- entrySet ------------------------------------------------------------\r
- // Warning: Each call to 'next' in this iterator constructs a new NBHMEntry.\r
- private class NBHMEntry extends AbstractEntry<TypeK,TypeV> {\r
- NBHMEntry( final TypeK k, final TypeV v ) { super(k,v); }\r
- public TypeV setValue(final TypeV val) {\r
- if( val == null ) throw new NullPointerException();\r
- _val = val;\r
- return put(_key, val);\r
- }\r
- }\r
-\r
- private class SnapshotE implements Iterator<Map.Entry<TypeK,TypeV>> {\r
- final SnapshotV _ss;\r
- public SnapshotE() { _ss = new SnapshotV(); }\r
- public void remove() { _ss.remove(); }\r
- public Map.Entry<TypeK,TypeV> next() { _ss.next(); return new NBHMEntry((TypeK)_ss._prevK,_ss._prevV); }\r
- public boolean hasNext() { return _ss.hasNext(); }\r
- }\r
-\r
- /** Returns a {@link Set} view of the mappings contained in this map. The\r
- * set is backed by the map, so changes to the map are reflected in the\r
- * set, and vice-versa. The set supports element removal, which removes\r
- * the corresponding mapping from the map, via the\r
- * <tt>Iterator.remove</tt>, <tt>Set.remove</tt>, <tt>removeAll</tt>,\r
- * <tt>retainAll</tt>, and <tt>clear</tt> operations. It does not support\r
- * the <tt>add</tt> or <tt>addAll</tt> operations.\r
- *\r
- * <p>The view's <tt>iterator</tt> is a "weakly consistent" iterator\r
- * that will never throw {@link ConcurrentModificationException},\r
- * and guarantees to traverse elements as they existed upon\r
- * construction of the iterator, and may (but is not guaranteed to)\r
- * reflect any modifications subsequent to construction.\r
- *\r
- * <p><strong>Warning:</strong> the iterator associated with this Set\r
- * requires the creation of {@link java.util.Map.Entry} objects with each\r
- * iteration. The {@link NonBlockingHashMap} does not normally create or\r
- * using {@link java.util.Map.Entry} objects so they will be created soley\r
- * to support this iteration. Iterating using {@link #keySet} or {@link\r
- * #values} will be more efficient.\r
- */\r
- @Override\r
- public Set<Map.Entry<TypeK,TypeV>> entrySet() {\r
- return new AbstractSet<Map.Entry<TypeK,TypeV>>() {\r
- @Override public void clear ( ) { NonBlockingHashMap.this.clear( ); }\r
- @Override public int size ( ) { return NonBlockingHashMap.this.size ( ); }\r
- @Override public boolean remove( final Object o ) {\r
- if( !(o instanceof Map.Entry)) return false;\r
- final Map.Entry<?,?> e = (Map.Entry<?,?>)o;\r
- return NonBlockingHashMap.this.remove(e.getKey(), e.getValue());\r
- }\r
- @Override public boolean contains(final Object o) {\r
- if( !(o instanceof Map.Entry)) return false;\r
- final Map.Entry<?,?> e = (Map.Entry<?,?>)o;\r
- TypeV v = get(e.getKey());\r
- return v.equals(e.getValue());\r
- }\r
- @Override public Iterator<Map.Entry<TypeK,TypeV>> iterator() { return new SnapshotE(); }\r
- };\r
- }\r
-\r
- // --- writeObject -------------------------------------------------------\r
- // Write a NBHM to a stream\r
- private void writeObject(java.io.ObjectOutputStream s) throws IOException {\r
- s.defaultWriteObject(); // Nothing to write\r
- for( Object K : keySet() ) {\r
- final Object V = get(K); // Do an official 'get'\r
- s.writeObject(K); // Write the <TypeK,TypeV> pair\r
- s.writeObject(V);\r
- }\r
- s.writeObject(null); // Sentinel to indicate end-of-data\r
- s.writeObject(null);\r
- }\r
-\r
- // --- readObject --------------------------------------------------------\r
- // Read a CHM from a stream\r
- private void readObject(java.io.ObjectInputStream s) throws IOException, ClassNotFoundException {\r
- s.defaultReadObject(); // Read nothing\r
- initialize(MIN_SIZE);\r
- for(;;) {\r
- final TypeK K = (TypeK) s.readObject();\r
- final TypeV V = (TypeV) s.readObject();\r
- if( K == null ) break;\r
- put(K,V); // Insert with an offical put\r
- }\r
- }\r
-\r
-} // End NonBlockingHashMap class\r
+++ /dev/null
-#ifndef CLIFFC_HASHTABLE_H
-#define CLIFFC_HASHTABLE_H
-
-#include <atomic>
-#include "stdio.h"
-//#include <common.h>
-#ifdef STANDALONE
-#include <assert.h>
-#define MODEL_ASSERT assert
-#else
-#include <model-assert.h>
-#endif
-#include <stdlib.h>
-
-using namespace std;
-
-/**
- This header file declares and defines a simplified version of Cliff Click's
- NonblockingHashMap. It contains all the necessary structrues and main
- functions. In simplified_cliffc_hashtable.cc file, it has the definition for
- the static fields.
-*/
-
-template<typename TypeK, typename TypeV>
-class cliffc_hashtable;
-
-/**
- Corresponding the the Object[] array in Cliff Click's Java implementation.
- It keeps the first two slots for CHM (Hashtable control unit) and the hash
- records (an array of hash used for fast negative key-equality check).
-*/
-struct kvs_data {
- int _size;
- atomic<void*> *_data;
-
- kvs_data(int sz) {
- _size = sz;
- int real_size = sz * 2 + 2;
- _data = new atomic<void*>[real_size];
- // The control block should be initialized in resize()
- // Init the hash record array
- int *hashes = new int[_size];
- int i;
- for (i = 0; i < _size; i++) {
- hashes[i] = 0;
- }
- // Init the data to Null slot
- for (i = 2; i < real_size; i++) {
- _data[i].store(NULL, memory_order_relaxed);
- }
- _data[1].store(hashes, memory_order_relaxed);
- }
-
- ~kvs_data() {
- int *hashes = (int*) _data[1].load(memory_order_relaxed);
- delete hashes;
- delete[] _data;
- }
-};
-
-struct slot {
- bool _prime;
- void *_ptr;
-
- slot(bool prime, void *ptr) {
- _prime = prime;
- _ptr = ptr;
- }
-};
-
-
-/**
- TypeK must have defined function "int hashCode()" which return the hash
- code for the its object, and "int equals(TypeK anotherKey)" which is
- used to judge equality.
- TypeK and TypeV should define their own copy constructor.
-*/
-/**
- @Begin
- @Class_begin
- @End
-*/
-template<typename TypeK, typename TypeV>
-class cliffc_hashtable {
- /**
- # The synchronization we have for the hashtable gives us the property of
- # serializability, so we should have a sequential hashtable when we check the
- # correctness. The key thing is to identify all the commit point.
-
- @Begin
- @Options:
- LANG = CPP;
- CLASS = cliffc_hashtable;
- @Global_define:
- @DeclareVar:
- spec_table *map;
- spec_table *id_map;
- id_tag_t *tag;
- @InitVar:
- map = new_spec_table_default(equals_key);
- id_map = new_spec_table_default(equals_id);
- tag = new_id_tag();
-
- @DefineFunc:
- bool equals_key(void *ptr1, void *ptr2) {
- TypeK *key1 = (TypeK*) ptr1,
- *key2 = (TypeK*) ptr2;
- if (key1 == NULL || key2 == NULL)
- return false;
- return key1->equals(key2);
- }
-
- @DefineFunc:
- bool equals_val(void *ptr1, void *ptr2) {
- if (ptr1 == ptr2)
- return true;
- TypeV *val1 = (TypeV*) ptr1,
- *val2 = (TypeV*) ptr2;
- if (val1 == NULL || val2 == NULL)
- return false;
- return val1->equals(val2);
- }
-
- @DefineFunc:
- bool equals_id(void *ptr1, void *ptr2) {
- id_tag_t *id1 = (id_tag_t*) ptr1,
- *id2 = (id_tag_t*) ptr2;
- if (id1 == NULL || id2 == NULL)
- return false;
- return (*id1).tag == (*id2).tag;
- }
-
- @DefineFunc:
- # Update the tag for the current key slot if the corresponding tag
- # is NULL, otherwise just return that tag. It will update the next
- # available tag too if it requires a new tag for that key slot.
- call_id_t getKeyTag(TypeK *key) {
- if (!spec_table_contains(id_map, key)) {
- call_id_t cur_id = current(tag);
- spec_table_put(id_map, key, (void*) cur_id);
- next(tag);
- return cur_id;
- } else {
- call_id_t res = (call_id_t) spec_table_get(id_map, key);
- return res;
- }
- }
- @Happens_before:
- Put->Get
- Put->Put
- @End
- */
-
-friend class CHM;
- /**
- The control structure for the hashtable
- */
- private:
- class CHM {
- friend class cliffc_hashtable;
- private:
- atomic<kvs_data*> _newkvs;
-
- // Size of active K,V pairs
- atomic_int _size;
-
- // Count of used slots
- atomic_int _slots;
-
- // The next part of the table to copy
- atomic_int _copy_idx;
-
- // Work-done reporting
- atomic_int _copy_done;
-
- public:
- CHM(int size) {
- _newkvs.store(NULL, memory_order_relaxed);
- _size.store(size, memory_order_relaxed);
- _slots.store(0, memory_order_relaxed);
-
- _copy_idx.store(0, memory_order_relaxed);
- _copy_done.store(0, memory_order_relaxed);
- }
-
- ~CHM() {}
-
- private:
-
- // Heuristic to decide if the table is too full
- bool table_full(int reprobe_cnt, int len) {
- return
- reprobe_cnt >= REPROBE_LIMIT &&
- _slots.load(memory_order_relaxed) >= reprobe_limit(len);
- }
-
- kvs_data* resize(cliffc_hashtable *topmap, kvs_data *kvs) {
- //model_print("resizing...\n");
- /**** FIXME: miss ****/
- kvs_data *newkvs = _newkvs.load(memory_order_acquire);
- if (newkvs != NULL)
- return newkvs;
-
- // No copy in-progress, start one; Only double the table size
- int oldlen = kvs->_size;
- int sz = _size.load(memory_order_relaxed);
- int newsz = sz;
-
- // Just follow Cliff Click's heuristic to decide the new size
- if (sz >= (oldlen >> 2)) { // If we are 25% full
- newsz = oldlen << 1; // Double size
- if (sz >= (oldlen >> 1))
- newsz = oldlen << 2; // Double double size
- }
-
- // We do not record the record timestamp
- if (newsz <= oldlen) newsz = oldlen << 1;
- // Do not shrink ever
- if (newsz < oldlen) newsz = oldlen;
-
- // Last check cause the 'new' below is expensive
- /**** FIXME: miss ****/
- newkvs = _newkvs.load(memory_order_acquire);
- //model_print("hey1\n");
- if (newkvs != NULL) return newkvs;
-
- newkvs = new kvs_data(newsz);
- void *chm = (void*) new CHM(sz);
- //model_print("hey2\n");
- newkvs->_data[0].store(chm, memory_order_relaxed);
-
- kvs_data *cur_newkvs;
- // Another check after the slow allocation
- /**** FIXME: miss ****/
- if ((cur_newkvs = _newkvs.load(memory_order_acquire)) != NULL)
- return cur_newkvs;
- // CAS the _newkvs to the allocated table
- kvs_data *desired = (kvs_data*) NULL;
- kvs_data *expected = (kvs_data*) newkvs;
- /**** FIXME: miss ****/
- //model_print("release in resize!\n");
- if (!_newkvs.compare_exchange_strong(desired, expected, memory_order_release,
- memory_order_relaxed)) {
- // Should clean the allocated area
- delete newkvs;
- /**** FIXME: miss ****/
- newkvs = _newkvs.load(memory_order_acquire);
- }
- return newkvs;
- }
-
- void help_copy_impl(cliffc_hashtable *topmap, kvs_data *oldkvs,
- bool copy_all) {
- MODEL_ASSERT (get_chm(oldkvs) == this);
- /**** FIXME: miss ****/
- kvs_data *newkvs = _newkvs.load(memory_order_acquire);
- int oldlen = oldkvs->_size;
- int min_copy_work = oldlen > 1024 ? 1024 : oldlen;
-
- // Just follow Cliff Click's code here
- int panic_start = -1;
- int copyidx;
- while (_copy_done.load(memory_order_relaxed) < oldlen) {
- copyidx = _copy_idx.load(memory_order_relaxed);
- if (panic_start == -1) { // No painc
- copyidx = _copy_idx.load(memory_order_relaxed);
- while (copyidx < (oldlen << 1) &&
- !_copy_idx.compare_exchange_strong(copyidx, copyidx +
- min_copy_work, memory_order_relaxed, memory_order_relaxed))
- copyidx = _copy_idx.load(memory_order_relaxed);
- if (!(copyidx < (oldlen << 1)))
- panic_start = copyidx;
- }
-
- // Now copy the chunk of work we claimed
- int workdone = 0;
- for (int i = 0; i < min_copy_work; i++)
- if (copy_slot(topmap, (copyidx + i) & (oldlen - 1), oldkvs,
- newkvs))
- workdone++;
- if (workdone > 0)
- copy_check_and_promote(topmap, oldkvs, workdone);
-
- copyidx += min_copy_work;
- if (!copy_all && panic_start == -1)
- return; // We are done with the work we claim
- }
- copy_check_and_promote(topmap, oldkvs, 0); // See if we can promote
- }
-
- kvs_data* copy_slot_and_check(cliffc_hashtable *topmap, kvs_data
- *oldkvs, int idx, void *should_help) {
- /**** FIXME: miss ****/
- kvs_data *newkvs = _newkvs.load(memory_order_acquire);
- // We're only here cause the caller saw a Prime
- if (copy_slot(topmap, idx, oldkvs, newkvs))
- copy_check_and_promote(topmap, oldkvs, 1); // Record the slot copied
- return (should_help == NULL) ? newkvs : topmap->help_copy(newkvs);
- }
-
- void copy_check_and_promote(cliffc_hashtable *topmap, kvs_data*
- oldkvs, int workdone) {
- int oldlen = oldkvs->_size;
- int copyDone = _copy_done.load(memory_order_relaxed);
- if (workdone > 0) {
- while (true) {
- copyDone = _copy_done.load(memory_order_relaxed);
- if (_copy_done.compare_exchange_weak(copyDone, copyDone +
- workdone, memory_order_relaxed, memory_order_relaxed))
- break;
- }
- }
-
- // Promote the new table to the current table
- if (copyDone + workdone == oldlen &&
- topmap->_kvs.load(memory_order_relaxed) == oldkvs) {
- /**** FIXME: miss ****/
- kvs_data *newkvs = _newkvs.load(memory_order_acquire);
- /**** CDSChecker error ****/
- topmap->_kvs.compare_exchange_strong(oldkvs, newkvs, memory_order_release,
- memory_order_relaxed);
- }
- }
-
- bool copy_slot(cliffc_hashtable *topmap, int idx, kvs_data *oldkvs,
- kvs_data *newkvs) {
- slot *key_slot;
- while ((key_slot = key(oldkvs, idx)) == NULL)
- CAS_key(oldkvs, idx, NULL, TOMBSTONE);
-
- // First CAS old to Prime
- slot *oldval = val(oldkvs, idx);
- while (!is_prime(oldval)) {
- slot *box = (oldval == NULL || oldval == TOMBSTONE)
- ? TOMBPRIME : new slot(true, oldval->_ptr);
- if (CAS_val(oldkvs, idx, oldval, box)) {
- if (box == TOMBPRIME)
- return 1; // Copy done
- // Otherwise we CAS'd the box
- oldval = box; // Record updated oldval
- break;
- }
- oldval = val(oldkvs, idx); // Else re-try
- }
-
- if (oldval == TOMBPRIME) return false; // Copy already completed here
-
- slot *old_unboxed = new slot(false, oldval->_ptr);
- int copied_into_new = (putIfMatch(topmap, newkvs, key_slot, old_unboxed,
- NULL) == NULL);
-
- // Old value is exposed in the new table
- while (!CAS_val(oldkvs, idx, oldval, TOMBPRIME))
- oldval = val(oldkvs, idx);
-
- return copied_into_new;
- }
- };
-
-
-
- private:
- static const int Default_Init_Size = 4; // Intial table size
-
- static slot* const MATCH_ANY;
- static slot* const NO_MATCH_OLD;
-
- static slot* const TOMBPRIME;
- static slot* const TOMBSTONE;
-
- static const int REPROBE_LIMIT = 10; // Forces a table-resize
-
- atomic<kvs_data*> _kvs;
-
- public:
- cliffc_hashtable() {
- // Should initialize the CHM for the construction of the table
- // For other CHM in kvs_data, they should be initialzed in resize()
- // because the size is determined dynamically
- /**
- @Begin
- @Entry_point
- @End
- */
- kvs_data *kvs = new kvs_data(Default_Init_Size);
- void *chm = (void*) new CHM(0);
- kvs->_data[0].store(chm, memory_order_relaxed);
- _kvs.store(kvs, memory_order_relaxed);
- }
-
- cliffc_hashtable(int init_size) {
- // Should initialize the CHM for the construction of the table
- // For other CHM in kvs_data, they should be initialzed in resize()
- // because the size is determined dynamically
-
- /**
- @Begin
- @Entry_point
- @End
- */
- kvs_data *kvs = new kvs_data(init_size);
- void *chm = (void*) new CHM(0);
- kvs->_data[0].store(chm, memory_order_relaxed);
- _kvs.store(kvs, memory_order_relaxed);
- }
-
- /**
- @Begin
- @Interface: Get
- //@Commit_point_set: Get_Point1 | Get_Point2 | Get_ReadKVS | Get_ReadNewKVS | Get_Clear
- @Commit_point_set: Get_Point1 | Get_Point2 | Get_Clear
- //@Commit_point_set: Get_Point1 | Get_Point2 | Get_Point3
- @ID: getKeyTag(key)
- @Action:
- TypeV *_Old_Val = (TypeV*) spec_table_get(map, key);
- //bool passed = equals_val(_Old_Val, __RET__);
- bool passed = false;
- if (!passed) {
- int old = _Old_Val == NULL ? 0 : _Old_Val->_val;
- int ret = __RET__ == NULL ? 0 : __RET__->_val;
- //model_print("Get: key: %d, _Old_Val: %d, RET: %d\n",
- //key->_val, old, ret);
- }
- @Post_check:
- //__RET__ == NULL ? true : equals_val(_Old_Val, __RET__)
- equals_val(_Old_Val, __RET__)
- @End
- */
- TypeV* get(TypeK *key) {
- slot *key_slot = new slot(false, key);
- int fullhash = hash(key_slot);
- /**** CDSChecker error ****/
- kvs_data *kvs = _kvs.load(memory_order_acquire);
- /**
- //@Begin
- @Commit_point_define_check: true
- @Label: Get_ReadKVS
- @End
- */
- slot *V = get_impl(this, kvs, key_slot, fullhash);
- if (V == NULL) return NULL;
- MODEL_ASSERT (!is_prime(V));
- return (TypeV*) V->_ptr;
- }
-
- /**
- @Begin
- @Interface: Put
- //@Commit_point_set: Put_Point | Put_ReadKVS | Put_ReadNewKVS | Put_WriteKey
- @Commit_point_set: Put_Point | Put_WriteKey
- @ID: getKeyTag(key)
- @Action:
- # Remember this old value at checking point
- TypeV *_Old_Val = (TypeV*) spec_table_get(map, key);
- spec_table_put(map, key, val);
- //bool passed = equals_val(__RET__, _Old_Val);
- bool passed = false;
- if (!passed) {
- int old = _Old_Val == NULL ? 0 : _Old_Val->_val;
- int ret = __RET__ == NULL ? 0 : __RET__->_val;
- //model_print("Put: key: %d, val: %d, _Old_Val: %d, RET: %d\n",
- // key->_val, val->_val, old, ret);
- }
- @Post_check:
- equals_val(__RET__, _Old_Val)
- @End
- */
- TypeV* put(TypeK *key, TypeV *val) {
- return putIfMatch(key, val, NO_MATCH_OLD);
- }
-
- /**
-// @Begin
- @Interface: PutIfAbsent
- @Commit_point_set:
- Write_Success_Point | PutIfAbsent_Fail_Point
- @Condition: !spec_table_contains(map, key)
- @HB_condition:
- COND_PutIfAbsentSucc :: __RET__ == NULL
- @ID: getKeyTag(key)
- @Action:
- void *_Old_Val = spec_table_get(map, key);
- if (__COND_SAT__)
- spec_table_put(map, key, value);
- @Post_check:
- __COND_SAT__ ? __RET__ == NULL : equals_val(_Old_Val, __RET__)
- @End
- */
- TypeV* putIfAbsent(TypeK *key, TypeV *value) {
- return putIfMatch(key, val, TOMBSTONE);
- }
-
- /**
-// @Begin
- @Interface: RemoveAny
- @Commit_point_set: Write_Success_Point
- @ID: getKeyTag(key)
- @Action:
- void *_Old_Val = spec_table_get(map, key);
- spec_table_put(map, key, NULL);
- @Post_check:
- equals_val(__RET__, _Old_Val)
- @End
- */
- TypeV* remove(TypeK *key) {
- return putIfMatch(key, TOMBSTONE, NO_MATCH_OLD);
- }
-
- /**
-// @Begin
- @Interface: RemoveIfMatch
- @Commit_point_set:
- Write_Success_Point | RemoveIfMatch_Fail_Point
- @Condition:
- equals_val(spec_table_get(map, key), val)
- @HB_condition:
- COND_RemoveIfMatchSucc :: __RET__ == true
- @ID: getKeyTag(key)
- @Action:
- if (__COND_SAT__)
- spec_table_put(map, key, NULL);
- @Post_check:
- __COND_SAT__ ? __RET__ : !__RET__
- @End
- */
- bool remove(TypeK *key, TypeV *val) {
- slot *val_slot = val == NULL ? NULL : new slot(false, val);
- return putIfMatch(key, TOMBSTONE, val) == val;
-
- }
-
- /**
-// @Begin
- @Interface: ReplaceAny
- @Commit_point_set:
- Write_Success_Point
- @ID: getKeyTag(key)
- @Action:
- void *_Old_Val = spec_table_get(map, key);
- @Post_check:
- equals_val(__RET__, _Old_Val)
- @End
- */
- TypeV* replace(TypeK *key, TypeV *val) {
- return putIfMatch(key, val, MATCH_ANY);
- }
-
- /**
-// @Begin
- @Interface: ReplaceIfMatch
- @Commit_point_set:
- Write_Success_Point | ReplaceIfMatch_Fail_Point
- @Condition:
- equals_val(spec_table_get(map, key), oldval)
- @HB_condition:
- COND_ReplaceIfMatchSucc :: __RET__ == true
- @ID: getKeyTag(key)
- @Action:
- if (__COND_SAT__)
- spec_table_put(map, key, newval);
- @Post_check:
- __COND_SAT__ ? __RET__ : !__RET__
- @End
- */
- bool replace(TypeK *key, TypeV *oldval, TypeV *newval) {
- return putIfMatch(key, newval, oldval) == oldval;
- }
-
- private:
- static CHM* get_chm(kvs_data* kvs) {
- CHM *res = (CHM*) kvs->_data[0].load(memory_order_relaxed);
- return res;
- }
-
- static int* get_hashes(kvs_data *kvs) {
- return (int *) kvs->_data[1].load(memory_order_relaxed);
- }
-
- // Preserve happens-before semantics on newly inserted keys
- static inline slot* key(kvs_data *kvs, int idx) {
- MODEL_ASSERT (idx >= 0 && idx < kvs->_size);
- // Corresponding to the volatile read in get_impl() and putIfMatch in
- // Cliff Click's Java implementation
- slot *res = (slot*) kvs->_data[idx * 2 + 2].load(memory_order_relaxed);
- /**
- @Begin
- # This is a complicated potential commit point since many many functions are
- # calling val().
- @Potential_commit_point_define: true
- @Label: Read_Key_Point
- @End
- */
- return res;
- }
-
- /**
- The atomic operation in val() function is a "potential" commit point,
- which means in some case it is a real commit point while it is not for
- some other cases. This so happens because the val() function is such a
- fundamental function that many internal operation will call. Our
- strategy is that we label any potential commit points and check if they
- really are the commit points later.
- */
- // Preserve happens-before semantics on newly inserted values
- static inline slot* val(kvs_data *kvs, int idx) {
- MODEL_ASSERT (idx >= 0 && idx < kvs->_size);
- // Corresponding to the volatile read in get_impl() and putIfMatch in
- // Cliff Click's Java implementation
- /**** CDSChecker error & hb violation ****/
- slot *res = (slot*) kvs->_data[idx * 2 + 3].load(memory_order_acquire);
- /**
- @Begin
- # This is a complicated potential commit point since many many functions are
- # calling val().
- @Potential_commit_point_define: true
- @Label: Read_Val_Point
- @End
- */
- return res;
-
-
- }
-
- static int hash(slot *key_slot) {
- MODEL_ASSERT(key_slot != NULL && key_slot->_ptr != NULL);
- TypeK* key = (TypeK*) key_slot->_ptr;
- int h = key->hashCode();
- // Spread bits according to Cliff Click's code
- h += (h << 15) ^ 0xffffcd7d;
- h ^= (h >> 10);
- h += (h << 3);
- h ^= (h >> 6);
- h += (h << 2) + (h << 14);
- return h ^ (h >> 16);
- }
-
- // Heuristic to decide if reprobed too many times.
- // Be careful here: Running over the limit on a 'get' acts as a 'miss'; on a
- // put it triggers a table resize. Several places MUST have exact agreement.
- static int reprobe_limit(int len) {
- return REPROBE_LIMIT + (len >> 2);
- }
-
- static inline bool is_prime(slot *val) {
- return (val != NULL) && val->_prime;
- }
-
- // Check for key equality. Try direct pointer comparison first (fast
- // negative teset) and then the full 'equals' call
- static bool keyeq(slot *K, slot *key_slot, int *hashes, int hash,
- int fullhash) {
- // Caller should've checked this.
- MODEL_ASSERT (K != NULL);
- TypeK* key_ptr = (TypeK*) key_slot->_ptr;
- return
- K == key_slot ||
- ((hashes[hash] == 0 || hashes[hash] == fullhash) &&
- K != TOMBSTONE &&
- key_ptr->equals(K->_ptr));
- }
-
- static bool valeq(slot *val_slot1, slot *val_slot2) {
- MODEL_ASSERT (val_slot1 != NULL);
- TypeK* ptr1 = (TypeV*) val_slot1->_ptr;
- if (val_slot2 == NULL || ptr1 == NULL) return false;
- return ptr1->equals(val_slot2->_ptr);
- }
-
- // Together with key() preserve the happens-before relationship on newly
- // inserted keys
- static inline bool CAS_key(kvs_data *kvs, int idx, void *expected, void *desired) {
- bool res = kvs->_data[2 * idx + 2].compare_exchange_strong(expected,
- desired, memory_order_relaxed, memory_order_relaxed);
- /**
- # If it is a successful put instead of a copy or any other internal
- # operantions, expected != NULL
- @Begin
- @Potential_commit_point_define: res
- @Label: Write_Key_Point
- @End
- */
- return res;
- }
-
- /**
- Same as the val() function, we only label the CAS operation as the
- potential commit point.
- */
- // Together with val() preserve the happens-before relationship on newly
- // inserted values
- static inline bool CAS_val(kvs_data *kvs, int idx, void *expected, void
- *desired) {
- /**** CDSChecker error & HB violation ****/
- bool res = kvs->_data[2 * idx + 3].compare_exchange_strong(expected,
- desired, memory_order_acq_rel, memory_order_relaxed);
- /**
- # If it is a successful put instead of a copy or any other internal
- # operantions, expected != NULL
- @Begin
- @Potential_commit_point_define: res
- @Label: Write_Val_Point
- @End
- */
- return res;
- }
-
- slot* get_impl(cliffc_hashtable *topmap, kvs_data *kvs, slot* key_slot, int
- fullhash) {
- int len = kvs->_size;
- CHM *chm = get_chm(kvs);
- int *hashes = get_hashes(kvs);
-
- int idx = fullhash & (len - 1);
- int reprobe_cnt = 0;
- while (true) {
- slot *K = key(kvs, idx);
- /**
- @Begin
- @Commit_point_define: K == NULL
- @Potential_commit_point_label: Read_Key_Point
- @Label: Get_Point1
- @End
- */
- slot *V = val(kvs, idx);
-
- if (K == NULL) {
- //model_print("Key is null\n");
- return NULL; // A miss
- }
-
- if (keyeq(K, key_slot, hashes, idx, fullhash)) {
- // Key hit! Check if table-resize in progress
- if (!is_prime(V)) {
- /**
- @Begin
- @Commit_point_clear: true
- @Label: Get_Clear
- @End
- */
-
- /**
- @Begin
- @Commit_point_define: true
- @Potential_commit_point_label: Read_Val_Point
- @Label: Get_Point2
- @End
- */
- return (V == TOMBSTONE) ? NULL : V; // Return this value
- }
- // Otherwise, finish the copy & retry in the new table
- return get_impl(topmap, chm->copy_slot_and_check(topmap, kvs,
- idx, key_slot), key_slot, fullhash);
- }
-
- if (++reprobe_cnt >= REPROBE_LIMIT ||
- key_slot == TOMBSTONE) {
- // Retry in new table
- // Atomic read can be here
- /**** FIXME: miss ****/
- kvs_data *newkvs = chm->_newkvs.load(memory_order_acquire);
- /**
- //@Begin
- @Commit_point_define_check: true
- @Label: Get_ReadNewKVS
- @End
- */
- return newkvs == NULL ? NULL : get_impl(topmap,
- topmap->help_copy(newkvs), key_slot, fullhash);
- }
-
- idx = (idx + 1) & (len - 1); // Reprobe by 1
- }
- }
-
- // A wrapper of the essential function putIfMatch()
- TypeV* putIfMatch(TypeK *key, TypeV *value, slot *old_val) {
- // TODO: Should throw an exception rather return NULL
- if (old_val == NULL) {
- return NULL;
- }
- slot *key_slot = new slot(false, key);
-
- slot *value_slot = new slot(false, value);
- /**** FIXME: miss ****/
- kvs_data *kvs = _kvs.load(memory_order_acquire);
- /**
- //@Begin
- @Commit_point_define_check: true
- @Label: Put_ReadKVS
- @End
- */
- slot *res = putIfMatch(this, kvs, key_slot, value_slot, old_val);
- // Only when copy_slot() call putIfMatch() will it return NULL
- MODEL_ASSERT (res != NULL);
- MODEL_ASSERT (!is_prime(res));
- return res == TOMBSTONE ? NULL : (TypeV*) res->_ptr;
- }
-
- /**
- Put, Remove, PutIfAbsent, etc will call this function. Return the old
- value. If the returned value is equals to the expVal (or expVal is
- NO_MATCH_OLD), then this function puts the val_slot to the table 'kvs'.
- Only copy_slot will pass a NULL expVal, and putIfMatch only returns a
- NULL if passed a NULL expVal.
- */
- static slot* putIfMatch(cliffc_hashtable *topmap, kvs_data *kvs, slot
- *key_slot, slot *val_slot, slot *expVal) {
- MODEL_ASSERT (val_slot != NULL);
- MODEL_ASSERT (!is_prime(val_slot));
- MODEL_ASSERT (!is_prime(expVal));
-
- int fullhash = hash(key_slot);
- int len = kvs->_size;
- CHM *chm = get_chm(kvs);
- int *hashes = get_hashes(kvs);
- int idx = fullhash & (len - 1);
-
- // Claim a key slot
- int reprobe_cnt = 0;
- slot *K;
- slot *V;
- kvs_data *newkvs;
-
- while (true) { // Spin till we get a key slot
- K = key(kvs, idx);
- V = val(kvs, idx);
- if (K == NULL) { // Get a free slot
- if (val_slot == TOMBSTONE) return val_slot;
- // Claim the null key-slot
- if (CAS_key(kvs, idx, NULL, key_slot)) {
- /**
- @Begin
- @Commit_point_define: true
- @Potential_commit_point_label: Write_Key_Point
- @Label: Put_WriteKey
- @End
- */
- chm->_slots.fetch_add(1, memory_order_relaxed); // Inc key-slots-used count
- hashes[idx] = fullhash; // Memorize full hash
- break;
- }
- K = key(kvs, idx); // CAS failed, get updated value
- MODEL_ASSERT (K != NULL);
- }
-
- // Key slot not null, there exists a Key here
- if (keyeq(K, key_slot, hashes, idx, fullhash))
- break; // Got it
-
- // Notice that the logic here should be consistent with that of get.
- // The first predicate means too many reprobes means nothing in the
- // old table.
- if (++reprobe_cnt >= reprobe_limit(len) ||
- K == TOMBSTONE) { // Found a Tombstone key, no more keys
- newkvs = chm->resize(topmap, kvs);
- //model_print("resize1\n");
- // Help along an existing copy
- if (expVal != NULL) topmap->help_copy(newkvs);
- return putIfMatch(topmap, newkvs, key_slot, val_slot, expVal);
- }
-
- idx = (idx + 1) & (len - 1); // Reprobe
- } // End of spinning till we get a Key slot
-
- if (val_slot == V) return V; // Fast cutout for no-change
-
- // Here it tries to resize cause it doesn't want other threads to stop
- // its progress (eagerly try to resize soon)
- /**** FIXME: miss ****/
- newkvs = chm->_newkvs.load(memory_order_acquire);
- /**
- //@Begin
- @Commit_point_define_check: true
- @Label: Put_ReadNewKVS
- @End
- */
- if (newkvs == NULL &&
- ((V == NULL && chm->table_full(reprobe_cnt, len)) || is_prime(V))) {
- //model_print("resize2\n");
- newkvs = chm->resize(topmap, kvs); // Force the copy to start
- }
-
- // Finish the copy and then put it in the new table
- if (newkvs != NULL)
- return putIfMatch(topmap, chm->copy_slot_and_check(topmap, kvs, idx,
- expVal), key_slot, val_slot, expVal);
-
- // Decided to update the existing table
- while (true) {
- MODEL_ASSERT (!is_prime(V));
-
- if (expVal != NO_MATCH_OLD &&
- V != expVal &&
- (expVal != MATCH_ANY || V == TOMBSTONE || V == NULL) &&
- !(V == NULL && expVal == TOMBSTONE) &&
- (expVal == NULL || !valeq(expVal, V))) {
- /**
- //@Begin
- @Commit_point_define: expVal == TOMBSTONE
- @Potential_commit_point_label: Read_Val_Point
- @Label: PutIfAbsent_Fail_Point
- # This is a check for the PutIfAbsent() when the value
- # is not absent
- @End
- */
- /**
- //@Begin
- @Commit_point_define: expVal != NULL && val_slot == TOMBSTONE
- @Potential_commit_point_label: Read_Val_Point
- @Label: RemoveIfMatch_Fail_Point
- @End
- */
- /**
- //@Begin
- @Commit_point_define: expVal != NULL && !valeq(expVal, V)
- @Potential_commit_point_label: Read_Val_Point
- @Label: ReplaceIfMatch_Fail_Point
- @End
- */
- return V; // Do not update!
- }
-
- if (CAS_val(kvs, idx, V, val_slot)) {
- /**
- @Begin
- # The only point where a successful put happens
- @Commit_point_define: true
- @Potential_commit_point_label: Write_Val_Point
- @Label: Put_Point
- @End
- */
- if (expVal != NULL) { // Not called by a table-copy
- // CAS succeeded, should adjust size
- // Both normal put's and table-copy calls putIfMatch, but
- // table-copy does not increase the number of live K/V pairs
- if ((V == NULL || V == TOMBSTONE) &&
- val_slot != TOMBSTONE)
- chm->_size.fetch_add(1, memory_order_relaxed);
- if (!(V == NULL || V == TOMBSTONE) &&
- val_slot == TOMBSTONE)
- chm->_size.fetch_add(-1, memory_order_relaxed);
- }
- return (V == NULL && expVal != NULL) ? TOMBSTONE : V;
- }
- // Else CAS failed
- V = val(kvs, idx);
- if (is_prime(V))
- return putIfMatch(topmap, chm->copy_slot_and_check(topmap, kvs,
- idx, expVal), key_slot, val_slot, expVal);
- }
- }
-
- // Help along an existing table-resize. This is a fast cut-out wrapper.
- kvs_data* help_copy(kvs_data *helper) {
- /**** FIXME: miss ****/
- kvs_data *topkvs = _kvs.load(memory_order_acquire);
- CHM *topchm = get_chm(topkvs);
- // No cpy in progress
- if (topchm->_newkvs.load(memory_order_relaxed) == NULL) return helper;
- topchm->help_copy_impl(this, topkvs, false);
- return helper;
- }
-};
-/**
- @Begin
- @Class_end
- @End
-*/
-
-#endif
+++ /dev/null
-#include <threads.h>
-#include "cliffc_hashtable.h"
-
-using namespace std;
-
-template<typename TypeK, typename TypeV>
-slot* const cliffc_hashtable<TypeK, TypeV>::MATCH_ANY = new slot(false, NULL);
-
-template<typename TypeK, typename TypeV>
-slot* const cliffc_hashtable<TypeK, TypeV>::NO_MATCH_OLD = new slot(false, NULL);
-
-template<typename TypeK, typename TypeV>
-slot* const cliffc_hashtable<TypeK, TypeV>::TOMBPRIME = new slot(true, NULL);
-
-template<typename TypeK, typename TypeV>
-slot* const cliffc_hashtable<TypeK, TypeV>::TOMBSTONE = new slot(false, NULL);
-
-
-class IntWrapper {
- private:
- public:
- int _val;
-
- IntWrapper(int val) : _val(val) {}
-
- IntWrapper() : _val(0) {}
-
- IntWrapper(IntWrapper& copy) : _val(copy._val) {}
-
- int get() {
- return _val;
- }
-
- int hashCode() {
- return _val;
- }
-
- bool operator==(const IntWrapper& rhs) {
- return false;
- }
-
- bool equals(const void *another) {
- if (another == NULL)
- return false;
- IntWrapper *ptr =
- (IntWrapper*) another;
- return ptr->_val == _val;
- }
-};
-
-cliffc_hashtable<IntWrapper, IntWrapper> *table;
-IntWrapper *val1, *val2;
-IntWrapper *k0, *k1, *k2, *k3, *k4, *k5;
-IntWrapper *v0, *v1, *v2, *v3, *v4, *v5;
-
-void threadA(void *arg) {
- IntWrapper *Res;
- int res;
- Res = table->put(k3, v3);
- res = Res == NULL ? 0 : Res->_val;
- printf("Put1: key_%d, val_%d, res_%d\n", k3->_val, v3->_val, res);
-
- Res = table->get(k2);
- res = Res == NULL ? 0 : Res->_val;
- printf("Get2: key_%d, res_%d\n", k2->_val, res);
-}
-
-void threadB(void *arg) {
- IntWrapper *Res;
- int res;
- Res = table->put(k2, v2);
- res = Res == NULL ? 0 : Res->_val;
- printf("Put3: key_%d, val_%d, res_%d\n", k2->_val, v2->_val, res);
-
- Res = table->get(k3);
- res = Res == NULL ? 0 : Res->_val;
- printf("Get4: key_%d, res_%d\n", k3->_val, res);
-}
-
-int user_main(int argc, char *argv[]) {
- thrd_t t1, t2;
- table = new cliffc_hashtable<IntWrapper, IntWrapper>(32);
- k1 = new IntWrapper(3);
- k2 = new IntWrapper(5);
- k3 = new IntWrapper(11);
- k4 = new IntWrapper(7);
- k5 = new IntWrapper(13);
-
- v0 = new IntWrapper(2048);
- v1 = new IntWrapper(1024);
- v2 = new IntWrapper(47);
- v3 = new IntWrapper(73);
- v4 = new IntWrapper(81);
- v5 = new IntWrapper(99);
-
- thrd_create(&t1, threadA, NULL);
- thrd_create(&t2, threadB, NULL);
- thrd_join(t1);
- thrd_join(t2);
-
- return 0;
-}
-
-
include ../benchmarks.mk
BENCH := hashmap
-NORMAL_TESTS := testcase1 testcase2 testcase3
-WILDCARD_TESTS := $(patsubst %, %_wildcard, $(NORMAL_TESTS))
+BENCH_BINARY := $(BENCH).o
-#TESTS := $(NORMAL_TESTS) $(WILDCARD_TESTS)
-TESTS := $(NORMAL_TESTS)
+TESTS := main testcase1 testcase2
all: $(TESTS)
+ ../generate.sh $(notdir $(shell pwd))
-$(WILDCARD_TESTS): CXXFLAGS += -DWILDCARD
+%.o : %.cc
+ $(CXX) -c -fPIC -MMD -MF .$@.d -o $@ $< $(CXXFLAGS) $(LDFLAGS)
-$(BENCH).o : $(BENCH).h
- $(CXX) -o $@ $< $(CXXFLAGS) -c $(LDFLAGS)
+$(TESTS): % : %.o $(BENCH_BINARY)
+ $(CXX) -o $@ $^ $(CXXFLAGS) $(LDFLAGS)
-$(BENCH)_wildcard.o : $(BENCH)_wildcard.h
- $(CXX) -o $@ $< $(CXXFLAGS) -c $(LDFLAGS)
-
-$(WILDCARD_TESTS): %_wildcard : %.cc $(BENCH)_wildcard.o
- $(CXX) -o $@ $< $(CXXFLAGS) $(LDFLAGS)
-
-$(NORMAL_TESTS): % : %.cc $(BENCH).o
- $(CXX) -o $@ $< $(CXXFLAGS) $(LDFLAGS)
+-include .*.d
clean:
- rm -f *.o *.d $(TESTS)
+ rm -rf $(TESTS) *.o .*.d *.dSYM
+
+.PHONY: clean all
--- /dev/null
+#include "unrelacy.h"
+#include "hashmap.h"
+
+/** @DeclareState: IntMap *map; */
+
+int HashMap::get(int key) {
+ MODEL_ASSERT (key);
+ int hash = hashKey(key);
+
+ // Try first without locking...
+ atomic<Entry*> *tab = table;
+ int index = hash & (capacity - 1);
+ atomic<Entry*> *first = &tab[index];
+ Entry *e;
+ int res = 0;
+
+ // Should be a load acquire
+ // This load action here makes it problematic for the SC analysis, what
+ // we need to do is as follows: if the get() method ever acquires the
+ // lock, we ignore this operation for the SC analysis, and otherwise we
+ // take it into consideration
+
+ /********** Detected UL (testcase2) **********/
+ Entry *firstPtr = first->load(mo_acquire);
+
+ e = firstPtr;
+ while (e != NULL) {
+ if (e->hash == hash && eq(key, e->key)) {
+ /********** Detected Correctness (testcase1) **********/
+ res = e->value.load(mo_seqcst);
+ /** @OPClearDefine: res != 0 */
+ if (res != 0)
+ return res;
+ else
+ break;
+ }
+ // Loading the next entry, this can be relaxed because the
+ // synchronization has been established by the load of head
+ e = e->next.load(mo_relaxed);
+ }
+
+ // Recheck under synch if key apparently not there or interference
+ Segment *seg = segments[hash & SEGMENT_MASK];
+ seg->lock(); // Critical region begins
+ /** @OPClearDefine: true */
+ // Not considering resize now, so ignore the reload of table...
+
+ // Synchronized by locking, no need to be load acquire
+ Entry *newFirstPtr = first->load(mo_relaxed);
+ if (e != NULL || firstPtr != newFirstPtr) {
+ e = newFirstPtr;
+ while (e != NULL) {
+ if (e->hash == hash && eq(key, e->key)) {
+ // Protected by lock, no need to be SC
+ res = e->value.load(mo_relaxed);
+ seg->unlock(); // Critical region ends
+ return res;
+ }
+ // Synchronized by locking
+ e = e->next.load(mo_relaxed);
+ }
+ }
+ seg->unlock(); // Critical region ends
+ return 0;
+}
+
+int HashMap::put(int key, int value) {
+ // Don't allow NULL key or value
+ MODEL_ASSERT (key && value);
+
+ int hash = hashKey(key);
+ Segment *seg = segments[hash & SEGMENT_MASK];
+ atomic<Entry*> *tab;
+
+ seg->lock(); // Critical region begins
+ tab = table;
+ int index = hash & (capacity - 1);
+
+ atomic<Entry*> *first = &tab[index];
+ Entry *e;
+ int oldValue = 0;
+
+ // The written of the entry is synchronized by locking
+ Entry *firstPtr = first->load(mo_relaxed);
+ e = firstPtr;
+ while (e != NULL) {
+ if (e->hash == hash && eq(key, e->key)) {
+ // FIXME: This could be a relaxed (because locking synchronize
+ // with the previous put())?? no need to be acquire
+ oldValue = e->value.load(relaxed);
+ /********** Detected Correctness (testcase1) **********/
+ e->value.store(value, mo_seqcst);
+ /** @OPClearDefine: true */
+ seg->unlock(); // Don't forget to unlock before return
+ return oldValue;
+ }
+ // Synchronized by locking
+ e = e->next.load(mo_relaxed);
+ }
+
+ // Add to front of list
+ Entry *newEntry = new Entry();
+ newEntry->hash = hash;
+ newEntry->key = key;
+ newEntry->value.store(value, relaxed);
+ /** @OPClearDefine: true */
+ newEntry->next.store(firstPtr, relaxed);
+ /********** Detected UL (testcase2) **********/
+ // Publish the newEntry to others
+ first->store(newEntry, mo_release);
+ seg->unlock(); // Critical region ends
+ return 0;
+}
+
+/** @PreCondition: return STATE(map)->get(key) == C_RET; */
+int get(HashMap *map, int key) {
+ return map->get(key);
+}
+
+/** @PreCondition: return STATE(map)->get(key) == C_RET;
+ @Transition: STATE(map)->put(key, value); */
+int put(HashMap *map, int key, int value) {
+ return map->put(key, value);
+}
+
#define _HASHMAP_H
#include <atomic>
-#include "stdio.h"
-//#include <common.h>
-#include <stdlib.h>
#include <mutex>
+#include "unrelacy.h"
-//#include "sc_annotation.h"
-
-#define relaxed memory_order_relaxed
-#define release memory_order_release
-#define acquire memory_order_acquire
-#define acq_rel memory_order_acq_rel
-#define seq_cst memory_order_seq_cst
-
-using namespace std;
-
-/**
- For the sake of simplicity, we do not use template but some toy structs to
- represent the Key and Value.
-*/
-struct Key {
- // Probably represent the coordinate (x, y, z)
- int x;
- int y;
- int z;
-
- int hashCode() {
- return x + 31 * y + 31 * 31 * z;
- }
-
- bool equals(Key *other) {
- if (!other)
- return false;
- return x == other->x && y == other->y && z == other->z;
- }
-
- Key(int x, int y, int z) :
- x(x),
- y(y),
- z(z)
- {
-
- }
-};
-
-struct Value {
- // Probably represent the speed vector (vX, vY, vZ)
- int vX;
- int vY;
- int vZ;
-
- Value(int vX, int vY, int vZ) :
- vX(vX),
- vY(vY),
- vZ(vZ)
- {
-
- }
-
- bool equals(Value *other) {
- if (!other)
- return false;
- return vX == other->vX && vY == other->vY && vZ == other->vZ;
- }
-};
+using std::atomic;
+using std::atomic_int;
+using std::mutex;
class Entry {
public:
- Key *key;
- atomic<Value*> value;
+ int key;
+ atomic_int value;
int hash;
atomic<Entry*> next;
- Entry(int h, Key *k, Value *v, Entry *n) {
+ Entry() { }
+
+ Entry(int h, int k, int v, Entry *n) {
this->hash = h;
this->key = k;
this->value.store(v, relaxed);
+ /** OPClearDefine: true */
this->next.store(n, relaxed);
}
};
}
}
- int hashKey(Key *x) {
- int h = x->hashCode();
+ int hashKey(int x) {
+ //int h = x->hashCode();
+ int h = x;
// Use logical right shift
unsigned int tmp = (unsigned int) h;
- return ((h << 7) - h + (tmp >> 9) + (tmp >> 17));
+ //return ((h << 7) - h + (tmp >> 9) + (tmp >> 17));
+ return x;
}
- bool eq(Key *x, Key *y) {
- return x == y || x->equals(y);
+ bool eq(int x, int y) {
+ //return x == y || x->equals(y);
+ return x == y;
}
- Value* get(Key *key) {
- //MODEL_ASSERT (key);
- int hash = hashKey(key);
-
- // Try first without locking...
- atomic<Entry*> *tab = table;
- int index = hash & (capacity - 1);
- atomic<Entry*> *first = &tab[index];
- Entry *e;
- Value *res = NULL;
-
- // Should be a load acquire
- // This load action here makes it problematic for the SC analysis, what
- // we need to do is as follows: if the get() method ever acquires the
- // lock, we ignore this operation for the SC analysis, and otherwise we
- // take it into consideration
-
- //SC_BEGIN();
- Entry *firstPtr = first->load(acquire);
- //SC_END();
+ int get(int key);
- e = firstPtr;
- while (e != NULL) {
- if (e->hash == hash && eq(key, e->key)) {
- res = e->value.load(seq_cst);
- if (res != NULL)
- return res;
- else
- break;
- }
- // Loading the next entry, this can be relaxed because the
- // synchronization has been established by the load of head
- e = e->next.load(relaxed);
- }
-
- // Recheck under synch if key apparently not there or interference
- Segment *seg = segments[hash & SEGMENT_MASK];
- seg->lock(); // Critical region begins
- // Not considering resize now, so ignore the reload of table...
+ int put(int key, int value);
- // Synchronized by locking, no need to be load acquire
- Entry *newFirstPtr = first->load(relaxed);
- if (e != NULL || firstPtr != newFirstPtr) {
- e = newFirstPtr;
- while (e != NULL) {
- if (e->hash == hash && eq(key, e->key)) {
- // Protected by lock, no need to be SC
- res = e->value.load(relaxed);
- seg->unlock(); // Critical region ends
- return res;
- }
- // Synchronized by locking
- e = e->next.load(relaxed);
- }
- }
- seg->unlock(); // Critical region ends
- return NULL;
- }
-
- Value* put(Key *key, Value *value) {
- // Don't allow NULL key or value
- //MODEL_ASSERT (key && value);
-
- int hash = hashKey(key);
- Segment *seg = segments[hash & SEGMENT_MASK];
- atomic<Entry*> *tab;
-
- seg->lock(); // Critical region begins
- tab = table;
- int index = hash & (capacity - 1);
-
- atomic<Entry*> *first = &tab[index];
- Entry *e;
- Value *oldValue = NULL;
-
- // The written of the entry is synchronized by locking
- Entry *firstPtr = first->load(relaxed);
- e = firstPtr;
- while (e != NULL) {
- if (e->hash == hash && eq(key, e->key)) {
- // FIXME: This could be a relaxed (because locking synchronize
- // with the previous put())?? no need to be acquire
- oldValue = e->value.load(relaxed);
- e->value.store(value, seq_cst);
- seg->unlock(); // Don't forget to unlock before return
- return oldValue;
- }
- // Synchronized by locking
- e = e->next.load(relaxed);
- }
-
- // Add to front of list
- Entry *newEntry = new Entry(hash, key, value, firstPtr);
- // Publish the newEntry to others
- first->store(newEntry, release);
- seg->unlock(); // Critical region ends
- return NULL;
- }
-
- Value* remove(Key *key, Value *value) {
- //MODEL_ASSERT (key);
- int hash = hashKey(key);
- Segment *seg = segments[hash & SEGMENT_MASK];
- atomic<Entry*> *tab;
-
- seg->lock(); // Critical region begins
- tab = table;
- int index = hash & (capacity - 1);
-
- atomic<Entry*> *first = &tab[index];
- Entry *e;
- Value *oldValue = NULL;
-
- // The written of the entry is synchronized by locking
- Entry *firstPtr = first->load(relaxed);
- e = firstPtr;
-
- while (true) {
- if (e != NULL) {
- seg->unlock(); // Don't forget to unlock
- return NULL;
- }
- if (e->hash == hash && eq(key, e->key))
- break;
- // Synchronized by locking
- e = e->next.load(relaxed);
- }
-
- // FIXME: This could be a relaxed (because locking synchronize
- // with the previous put())?? No need to be acquire
- oldValue = e->value.load(relaxed);
- // If the value parameter is NULL, we will remove the entry anyway
- if (value != NULL && value->equals(oldValue)) {
- seg->unlock();
- return NULL;
- }
-
- // Force the get() to grab the lock and retry
- e->value.store(NULL, relaxed);
-
- // The strategy to remove the entry is to keep the entries after the
- // removed one and copy the ones before it
- Entry *head = e->next.load(relaxed);
- Entry *p;
- p = first->load(relaxed);
- while (p != e) {
- head = new Entry(p->hash, p->key, p->value.load(relaxed), head);
- p = p->next.load(relaxed);
- }
-
- // Publish the new head to readers
- first->store(head, release);
- seg->unlock(); // Critical region ends
- return oldValue;
- }
};
+/** C Interface */
+int get(HashMap *map, int key);
+int put(HashMap *map, int key, int value);
+
#endif
-#include <iostream>
#include <threads.h>
#include "hashmap.h"
HashMap *table;
+/** Making w4 & w11 seq_cst */
-void printKey(Key *key) {
- if (key)
- printf("pos = (%d, %d, %d)\n", key->x, key->y, key->z);
- else
- printf("pos = NULL\n");
-}
-
-void printValue(Value *value) {
- if (value)
- printf("velocity = (%d, %d, %d)\n", value->vX, value->vY, value->vZ);
- else
- printf("velocity = NULL\n");
-}
-
-// Key(3, 2, 6) & Key(1, 3, 3) are hashed to the same slot -> 4
-// Key(1, 1, 1) & Key(3, 2, 2) are hashed to the same slot -> 0
-// Key(2, 4, 1) & Key(3, 4, 2) are hashed to the same slot -> 3
-// Key(3, 4, 5) & Key(1, 4, 3) are hashed to the same slot -> 5
-
+int k1 = 1;
+int k2 = 3;
+int v1 = 10;
+int v2 = 30;
void threadA(void *arg) {
- Key *k1 = new Key(3, 2, 6);
- Key *k2 = new Key(1, 1, 1);
- Value *v1 = new Value(10, 10, 10);
- Value *r1 = table->put(k1, v1);
- //printValue(r1);
- Value *r2 = table->get(k2);
- //printf("Thrd A:\n");
- printValue(r2);
+ int r1 = put(table, k1, v1);
+ int r2 = get(table, k2);
}
void threadB(void *arg) {
- Key *k1 = new Key(3, 2, 6);
- Key *k2 = new Key(1, 1, 1);
- Value *v2 = new Value(30, 40, 50);
- Value *r3 = table->put(k2, v2);
- //printValue(r3);
- Value *r4 = table->get(k1);
- printf("Thrd B:\n");
- printValue(r4);
+ int r3 = put(table, k2, v2);
+ int r4 = get(table, k1);
}
int user_main(int argc, char *argv[]) {
+
thrd_t t1, t2;
-
- Key *k1 = new Key(1, 3, 3);
- Key *k1_prime = new Key(3, 2, 6);
- Key *k2 = new Key(3, 2, 2);
- Key *k2_prime = new Key(1, 1, 1);
- Value *v1 = new Value(111, 111, 111);
- Value *v2 = new Value(222, 222, 222);
-
+ /** @Entry */
table = new HashMap;
- printf("Key1: %d\n", table->hashKey(k1) % table->capacity);
- printf("Key1': %d\n", table->hashKey(k1_prime) % table->capacity);
- printf("Key2: %d\n", table->hashKey(k2) % table->capacity);
- printf("Key2': %d\n", table->hashKey(k2_prime) % table->capacity);
-
thrd_create(&t1, threadA, NULL);
thrd_create(&t2, threadB, NULL);
thrd_join(t1);
When b and d both read the old head of the list (and they later grab the lock,
making it the interface SC), it's non-SC because neither reads the updated
value.
+
+Run testcase1 to make the store and load of value slot to be seq_cst.
+
+Then run testcase2 with "-o annotation" to get store and load of key slot to be
+release/acquire.
+
+0m0.015s + 0m0.000 = 0m0.015s
+++ /dev/null
-Result 0:
-wildcard 1 -> memory_order_relaxed
-wildcard 2 -> memory_order_relaxed
-wildcard 3 -> memory_order_acquire
-wildcard 4 -> memory_order_relaxed
-wildcard 6 -> memory_order_relaxed
-wildcard 7 -> memory_order_relaxed
-wildcard 9 -> memory_order_relaxed
-wildcard 13 -> memory_order_release
+++ /dev/null
-Result 0:
-wildcard 1 -> memory_order_relaxed
-wildcard 2 -> memory_order_relaxed
-wildcard 3 -> memory_order_acquire
-wildcard 4 -> memory_order_seq_cst
-wildcard 6 -> memory_order_relaxed
-wildcard 7 -> memory_order_relaxed
-wildcard 9 -> memory_order_relaxed
-wildcard 10 -> memory_order_relaxed
-wildcard 11 -> memory_order_seq_cst
-wildcard 13 -> memory_order_release
#include <threads.h>
-#ifdef WILDCARD
-#include "hashmap_wildcard.h"
-#else
#include "hashmap.h"
-#endif
HashMap *table;
-void printKey(Key *key) {
- if (key)
- printf("pos = (%d, %d, %d)\n", key->x, key->y, key->z);
- else
- printf("pos = NULL\n");
-}
-
-void printValue(Value *value) {
- if (value)
- printf("velocity = (%d, %d, %d)\n", value->vX, value->vY, value->vZ);
- else
- printf("velocity = NULL\n");
-}
-
-// Key(3, 2, 6) & Key(1, 3, 3) are hashed to the same slot -> 4
-// Key(1, 1, 1) & Key(3, 2, 2) are hashed to the same slot -> 0
-// Key(2, 4, 1) & Key(3, 4, 2) are hashed to the same slot -> 3
-// Key(3, 4, 5) & Key(1, 4, 3) are hashed to the same slot -> 5
-
+int k1 = 1;
+int k2 = 3;
+int v1 = 10;
+int v2 = 30;
void threadA(void *arg) {
- Key *k1 = new Key(3, 2, 6);
- Key *k2 = new Key(1, 1, 1);
- Value *v1 = new Value(10, 10, 10);
- Value *r1 = table->put(k1, v1);
- //printValue(r1);
- Value *r2 = table->get(k2);
- //printf("Thrd A:\n");
- printValue(r2);
+ int r1 = put(table, k1, v1);
+ int r2 = get(table, k2);
+ printf("r2=%d\n", r2);
}
void threadB(void *arg) {
- Key *k1 = new Key(3, 2, 6);
- Key *k2 = new Key(1, 1, 1);
- Value *v2 = new Value(30, 40, 50);
- Value *r3 = table->put(k2, v2);
- //printValue(r3);
- Value *r4 = table->get(k1);
- printf("Thrd B:\n");
- printValue(r4);
+ int r3 = put(table, k2, v2);
+ int r4 = get(table, k1);
+ printf("r4=%d\n", r4);
}
int user_main(int argc, char *argv[]) {
+
thrd_t t1, t2;
+ /** @Entry */
table = new HashMap;
+ put(table, k1, 1);
+ put(table, k2, 1);
thrd_create(&t1, threadA, NULL);
thrd_create(&t2, threadB, NULL);
#include <threads.h>
-
-#ifdef WILDCARD
-#include "hashmap_wildcard.h"
-#else
#include "hashmap.h"
-#endif
HashMap *table;
-void printKey(Key *key) {
- if (key)
- printf("pos = (%d, %d, %d)\n", key->x, key->y, key->z);
- else
- printf("pos = NULL\n");
-}
-
-void printValue(Value *value) {
- if (value)
- printf("velocity = (%d, %d, %d)\n", value->vX, value->vY, value->vZ);
- else
- printf("velocity = NULL\n");
-}
-
-// Key(3, 2, 6) & Key(1, 3, 3) are hashed to the same slot -> 4
-// Key(1, 1, 1) & Key(3, 2, 2) are hashed to the same slot -> 0
-// Key(2, 4, 1) & Key(3, 4, 2) are hashed to the same slot -> 3
-// Key(3, 4, 5) & Key(1, 4, 3) are hashed to the same slot -> 5
+/** Making w4 & w11 seq_cst */
+int k1 = 1;
+int k2 = 3;
+int v1 = 10;
+int v2 = 30;
void threadA(void *arg) {
- Key *k1 = new Key(3, 2, 6);
- Key *k2 = new Key(1, 1, 1);
- Value *v1 = new Value(10, 10, 10);
- Value *r1 = table->put(k1, v1);
- //printValue(r1);
- Value *r2 = table->get(k2);
- //printf("Thrd A:\n");
- printValue(r2);
+ int r1 = put(table, k1, v1);
+ int r2 = get(table, k2);
+ printf("r2=%d\n", r2);
}
void threadB(void *arg) {
- Key *k1 = new Key(3, 2, 6);
- Key *k2 = new Key(1, 1, 1);
- Value *v2 = new Value(30, 40, 50);
- Value *r3 = table->put(k2, v2);
- //printValue(r3);
- Value *r4 = table->get(k1);
- printf("Thrd B:\n");
- printValue(r4);
+ int r3 = put(table, k2, v2);
+ int r4 = get(table, k1);
+ printf("r4=%d\n", r4);
}
int user_main(int argc, char *argv[]) {
- Key *k1 = new Key(3, 2, 6);
- Key *k2 = new Key(1, 1, 1);
- Value *v1 = new Value(111, 111, 111);
- Value *v2 = new Value(222, 222, 222);
thrd_t t1, t2;
+ /** @Entry */
table = new HashMap;
- table->put(k1, v1);
- table->put(k2, v2);
thrd_create(&t1, threadA, NULL);
thrd_create(&t2, threadB, NULL);
+++ /dev/null
-#include <threads.h>
-
-#ifdef WILDCARD
-#include "hashmap_wildcard.h"
-#else
-#include "hashmap.h"
-#endif
-
-HashMap *table;
-
-void printKey(Key *key) {
- if (key)
- printf("pos = (%d, %d, %d)\n", key->x, key->y, key->z);
- else
- printf("pos = NULL\n");
-}
-
-void printValue(Value *value) {
- if (value)
- printf("velocity = (%d, %d, %d)\n", value->vX, value->vY, value->vZ);
- else
- printf("velocity = NULL\n");
-}
-
-// Key(3, 2, 6) & Key(1, 3, 3) are hashed to the same slot -> 4
-// Key(1, 1, 1) & Key(3, 2, 2) are hashed to the same slot -> 0
-// Key(2, 4, 1) & Key(3, 4, 2) are hashed to the same slot -> 3
-// Key(3, 4, 5) & Key(1, 4, 3) & Key(1, 1, 6) are hashed to the same slot -> 5
-// Key(2, 4, 8) & Key(1, 3, 8) -> 9
-// Key(1, 4, 8) -> 10
-// Key(1, 3, 7) -> 8
-// Key(1, 2, 7) -> 7
-// Key(1, 2, 6) -> 6
-
-void threadA(void *arg) {
- Key *k1 = new Key(3, 4, 5);
- Key *k2 = new Key(1, 4, 3);
- Key *k3 = new Key(1, 1, 6);
- Value *v2 = new Value(10, 10, 10);
- Value *r1 = table->put(k2, v2);
- //printValue(r1);
- Value *r2 = table->get(k3);
- printf("k1 -> %d:\n", table->hashKey(k1) % table->capacity);
- printf("k2 -> %d:\n", table->hashKey(k2) % table->capacity);
- printf("k3 -> %d:\n", table->hashKey(k3) % table->capacity);
- //printValue(r2);
-}
-
-void threadB(void *arg) {
- Key *k1 = new Key(3, 4, 5);
- Key *k2 = new Key(1, 4, 3);
- Key *k3 = new Key(1, 1, 6);
- Value *v3 = new Value(30, 40, 50);
- Value *r3 = table->put(k3, v3);
- //printValue(r3);
- //Value *r4 = table->get(k1);
- //printf("Thrd B:\n");
- //printValue(r4);
-}
-
-int user_main(int argc, char *argv[]) {
- Key *k1 = new Key(3, 4, 5);
- Key *k2 = new Key(1, 4, 3);
- Key *k3 = new Key(1, 1, 6);
- //Key *k2 = new Key(1, 3, 3);
- Value *v1 = new Value(111, 111, 111);
- //Value *v2 = new Value(222, 222, 222);
- thrd_t t1, t2;
- table = new HashMap;
- table->put(k1, v1);
- //table->put(k2, v2);
-
- thrd_create(&t1, threadA, NULL);
- thrd_create(&t2, threadB, NULL);
- thrd_join(t1);
- thrd_join(t2);
-
- return 0;
-}
-
-
--- /dev/null
+#!/bin/bash
+#
+
+Files=(
+ mcs-lock/mcs-lock.h mcs-lock/mcs-lock.cc
+ linuxrwlocks/linuxrwlocks.h linuxrwlocks/linuxrwlocks.c
+ concurrent-hashmap/hashmap.h concurrent-hashmap/hashmap.cc
+ read-copy-update/rcu.h read-copy-update/rcu.cc
+ seqlock/seqlock.h seqlock/seqlock.cc
+ ticket-lock/lock.h ticket-lock/lock.c
+ spsc-bugfix/eventcount.h spsc-bugfix/queue.h spsc-bugfix/queue.cc
+ mpmc-queue/mpmc-queue.h mpmc-queue/mpmc-queue.cc
+ chase-lev-deque-bugfix/deque.h chase-lev-deque-bugfix/deque.c
+ ms-queue/queue.h ms-queue/queue.c
+)
+
+MainFiles=(
+ linuxrwlocks/main.c
+ concurrent-hashmap/main.cc
+ read-copy-update/main.cc
+ seqlock/main.cc
+ ticket-lock/main.cc
+ spsc-bugfix/main.cc
+ mcs-lock/main.cc
+ mpmc-queue/main.cc
+ chase-lev-deque-bugfix/main.c
+ ms-queue/main.c
+)
+
+echo "cloc ${Files[*]}"
+
+cloc ${Files[*]} ${MainFiles[*]}
+++ /dev/null
-/dekker-fences
+++ /dev/null
-include ../benchmarks.mk
-
-TESTNAME = dekker-fences
-
-all: $(TESTNAME)
-
-$(TESTNAME): $(TESTNAME).cc
- $(CXX) -o $@ $< $(CXXFLAGS) $(LDFLAGS)
-
-clean:
- rm -f $(TESTNAME) *.o
+++ /dev/null
-/*
- * Dekker's critical section algorithm, implemented with fences.
- *
- * URL:
- * http://www.justsoftwaresolutions.co.uk/threading/
- */
-
-#include <atomic>
-#include <threads.h>
-
-#include "librace.h"
-
-std::atomic<bool> flag0, flag1;
-std::atomic<int> turn;
-
-uint32_t var = 0;
-
-void p0(void *arg)
-{
- flag0.store(true,std::memory_order_relaxed);
- std::atomic_thread_fence(std::memory_order_seq_cst);
-
- while (flag1.load(std::memory_order_relaxed))
- {
- if (turn.load(std::memory_order_relaxed) != 0)
- {
- flag0.store(false,std::memory_order_relaxed);
- while (turn.load(std::memory_order_relaxed) != 0)
- {
- thrd_yield();
- }
- flag0.store(true,std::memory_order_relaxed);
- std::atomic_thread_fence(std::memory_order_seq_cst);
- } else
- thrd_yield();
- }
- std::atomic_thread_fence(std::memory_order_acquire);
-
- // critical section
- store_32(&var, 1);
-
- turn.store(1,std::memory_order_relaxed);
- std::atomic_thread_fence(std::memory_order_release);
- flag0.store(false,std::memory_order_relaxed);
-}
-
-void p1(void *arg)
-{
- flag1.store(true,std::memory_order_relaxed);
- std::atomic_thread_fence(std::memory_order_seq_cst);
-
- while (flag0.load(std::memory_order_relaxed))
- {
- if (turn.load(std::memory_order_relaxed) != 1)
- {
- flag1.store(false,std::memory_order_relaxed);
- while (turn.load(std::memory_order_relaxed) != 1)
- {
- thrd_yield();
- }
- flag1.store(true,std::memory_order_relaxed);
- std::atomic_thread_fence(std::memory_order_seq_cst);
- } else
- thrd_yield();
- }
- std::atomic_thread_fence(std::memory_order_acquire);
-
- // critical section
- store_32(&var, 2);
-
- turn.store(0,std::memory_order_relaxed);
- std::atomic_thread_fence(std::memory_order_release);
- flag1.store(false,std::memory_order_relaxed);
-}
-
-int user_main(int argc, char **argv)
-{
- thrd_t a, b;
-
- flag0 = false;
- flag1 = false;
- turn = 0;
-
- thrd_create(&a, p0, NULL);
- thrd_create(&b, p1, NULL);
-
- thrd_join(a);
- thrd_join(b);
-
- return 0;
-}
+++ /dev/null
-#include "stack.h"
-
-Simple_Stack S;
-void **location;
-int *collision;
-
-void StackOp (ThreadInfo * pInfo) {
- if (TryPerformStackOp (p) == FALSE)
- LesOP (p);
- return;
-}
-
-void LesOP (ThreadInfo * p) {
- while (1) {
- location[mypid] = p;
- pos = GetPosition (p);
- him = collision[pos];
- while (!CAS (&collision[pos], him, mypid))
- him = collision[pos];
- if (him != EMPTY) {
- q = location[him];
- if (q != NULL && q->id == him && q->op != p->op) {
- if (CAS (&location[mypid], p, NULL)) {
- if (TryCollision (p, q) == TRUE)
- return;
- else
- goto stack;
- } else {
- FinishCollision (p);
- return;
- }
- }
- delay (p->spin);
- if (!CAS (&location[mypid], p, NULL)) {
- FinishCollision (p);
- return;
- }
- stack:
- if (TryPerformStackOp (p) == TRUE)
- return;
- }
- }
-}
-
-bool TryPerformStackOp (ThreadInfo * p) {
- Cell *phead, *pnext;
- if (p->op == PUSH) {
- phead = S.ptop;
- p->cell.pnext = phead;
- if (CAS (&S.ptop, phead, &p->cell))
- return TRUE;
- else
- return FALSE;
- }
- if (p->op == POP) {
- phead = S.ptop;
- if (phead == NULL) {
- p->cell = EMPTY;
- return TRUE;
- }
- pnext = phead->pnext;
- if (CAS (&S.ptop, phead, pnext)) {
- p->cell = *phead;
- return TRUE;
- } else {
- p->cell = EMPTY;
- return FALSE;
- }
- }
-}
-
-void FinishCollision (ProcessInfo * p) {
- if (p->op == POP) {
- p->pcell = location[mypid]->pcell;
- location[mypid] = NULL;
- }
-}
-
-void TryCollision (ThreadInfo * p, ThreadInfo * q) {
- if (p->op == PUSH) {
- if (CAS (&location[him], q, p))
- return TRUE;
- else
- return FALSE;
- }
- if (p->op == POP) {
- if (CAS (&location[him], q, NULL)) {
- p->cell = q->cell;
- location[mypid] = NULL;
- return TRUE;
- }
- else
- return FALSE;
- }
-}
+++ /dev/null
-struct Cell {
- Cell *pnext;
- void *pdata;
-};
-
-struct ThreadInfo {
- unsigned int id;
- char op;
- Cell cell;
- int spin;
-};
-
-struct Simple_Stack {
- Cell *ptop;
-};
-
--- /dev/null
+#!/bin/bash
+
+SpecCompiler=$HOME/spec-checker-compiler
+
+ClassPath=$SpecCompiler/classes
+
+Class=edu/uci/eecs/codeGenerator/CodeGenerator
+
+java -cp $ClassPath $Class $1
#include <librace.h>
#define $
-
+#ifndef ASSRT
#define ASSERT(expr) MODEL_ASSERT(expr)
+#endif
#define RL_ASSERT(expr) MODEL_ASSERT(expr)
#define RL_NEW new
#define RL_DELETE(expr) delete expr
-#define mo_seqcst memory_order_relaxed
+#define mo_seqcst memory_order_seq_cst
#define mo_release memory_order_release
#define mo_acquire memory_order_acquire
#define mo_acq_rel memory_order_acq_rel
#define mo_relaxed memory_order_relaxed
+#define seq_cst memory_order_seq_cst
+#define release memory_order_release
+#define acquire memory_order_acquire
+#define acq_rel memory_order_acq_rel
+#define relaxed memory_order_relaxed
+
namespace rl {
/* This 'useless' struct is declared just so we can use partial template
include ../benchmarks.mk
-TESTNAME = linuxrwlocks
+BENCH := linuxrwlocks
-all: $(TESTNAME)
+BENCH_BINARY := $(BENCH).o
-$(TESTNAME): $(TESTNAME).c
- $(CC) -o $@ $< $(CFLAGS) $(LDFLAGS)
+TESTS := main testcase1 testcase2 testcase3
+
+all: $(TESTS)
+ ../generate.sh $(notdir $(shell pwd))
+
+%.o : %.c
+ $(CC) -c -fPIC -MMD -MF .$@.d -o $@ $< $(CFLAGS) $(LDFLAGS)
+
+$(TESTS): % : %.o $(BENCH_BINARY)
+ $(CC) -o $@ $^ $(CFLAGS) $(LDFLAGS)
+
+-include .*.d
clean:
- rm -f $(TESTNAME) *.o
+ rm -rf $(TESTS) *.o .*.d *.dSYM
+
+.PHONY: clean all
#include <stdatomic.h>
#include "librace.h"
-
-#define RW_LOCK_BIAS 0x00100000
-#define WRITE_LOCK_CMP RW_LOCK_BIAS
+#include "linuxrwlocks.h"
/** Example implementation of linux rw lock along with 2 thread test
* driver... */
-typedef union {
- atomic_int lock;
-} rwlock_t;
-
-static inline int read_can_lock(rwlock_t *lock)
+/**
+ Properties to check:
+ 1. At most 1 thread can acquire the write lock, and at the same time, no
+ other threads can acquire any lock (including read/write lock).
+ 2. At most RW_LOCK_BIAS threads can successfully acquire the read lock.
+ 3. A read_unlock release 1 read lock, and a write_unlock release the write
+ lock. They can not release a lock that they don't acquire.
+ ###
+ 4. Read_lock and write_lock can not be grabbed at the same time.
+ 5. Happpens-before relationship should be checked and guaranteed, which
+ should be as the following:
+ a. read_unlock hb-> write_lock
+ b. write_unlock hb-> write_lock
+ c. write_unlock hb-> read_lock
+*/
+
+/**
+ Interesting point for locks:
+ a. If the users unlock() before any lock(), then the model checker will fail.
+ For this case, we can not say that the data structure is buggy, how can we
+ tell them from a real data structure bug???
+ b. We should specify that for a specific thread, successful locks and
+ unlocks should always come in pairs. We could make this check as an
+ auxiliary check since it is an extra rule for how the interfaces should called.
+*/
+
+/** @DeclareState: bool writerLockAcquired;
+ int readerLockCnt; */
+
+int read_can_lock(rwlock_t *lock)
{
return atomic_load_explicit(&lock->lock, memory_order_relaxed) > 0;
}
-static inline int write_can_lock(rwlock_t *lock)
+int write_can_lock(rwlock_t *lock)
{
return atomic_load_explicit(&lock->lock, memory_order_relaxed) == RW_LOCK_BIAS;
}
-static inline void read_lock(rwlock_t *rw)
+
+/** @PreCondition: return !STATE(writerLockAcquired);
+@Transition: STATE(readerLockCnt)++; */
+void read_lock(rwlock_t *rw)
{
+
+ /********** Detected Correctness (testcase1) **********/
int priorvalue = atomic_fetch_sub_explicit(&rw->lock, 1, memory_order_acquire);
+ /** @OPDefine: priorvalue > 0 */
while (priorvalue <= 0) {
atomic_fetch_add_explicit(&rw->lock, 1, memory_order_relaxed);
while (atomic_load_explicit(&rw->lock, memory_order_relaxed) <= 0) {
thrd_yield();
}
+ /********** Detected Correctness (testcase1) **********/
priorvalue = atomic_fetch_sub_explicit(&rw->lock, 1, memory_order_acquire);
+ /** @OPDefine: priorvalue > 0 */
}
}
-static inline void write_lock(rwlock_t *rw)
+
+/** @PreCondition: return !STATE(writerLockAcquired) && STATE(readerLockCnt) == 0;
+@Transition: STATE(writerLockAcquired) = true; */
+void write_lock(rwlock_t *rw)
{
+ /********** Detected Correctness (testcase1) **********/
int priorvalue = atomic_fetch_sub_explicit(&rw->lock, RW_LOCK_BIAS, memory_order_acquire);
+ /** @OPDefine: priorvalue == RW_LOCK_BIAS */
while (priorvalue != RW_LOCK_BIAS) {
atomic_fetch_add_explicit(&rw->lock, RW_LOCK_BIAS, memory_order_relaxed);
while (atomic_load_explicit(&rw->lock, memory_order_relaxed) != RW_LOCK_BIAS) {
thrd_yield();
}
+ /********** Detected Correctness (testcase1) **********/
priorvalue = atomic_fetch_sub_explicit(&rw->lock, RW_LOCK_BIAS, memory_order_acquire);
+ /** @OPDefine: priorvalue == RW_LOCK_BIAS */
}
}
-static inline int read_trylock(rwlock_t *rw)
+/** @PreCondition: return !C_RET || !STATE(writerLockAcquired);
+@Transition: if (C_RET) STATE(readerLockCnt)++; */
+int read_trylock(rwlock_t *rw)
{
+ /********** Detected Correctness (testcase2) **********/
int priorvalue = atomic_fetch_sub_explicit(&rw->lock, 1, memory_order_acquire);
- if (priorvalue > 0)
+ /** @OPDefine: true */
+ if (priorvalue > 0) {
return 1;
-
+ }
atomic_fetch_add_explicit(&rw->lock, 1, memory_order_relaxed);
return 0;
}
-static inline int write_trylock(rwlock_t *rw)
+/** @PreCondition: return !C_RET || !STATE(writerLockAcquired) && STATE(readerLockCnt) == 0;
+@Transition: if (C_RET) STATE(writerLockAcquired) = true; */
+int write_trylock(rwlock_t *rw)
{
+ /********** Detected Correctness (testcase2) **********/
int priorvalue = atomic_fetch_sub_explicit(&rw->lock, RW_LOCK_BIAS, memory_order_acquire);
- if (priorvalue == RW_LOCK_BIAS)
+ /** @OPDefine: true */
+ if (priorvalue == RW_LOCK_BIAS) {
return 1;
-
+ }
atomic_fetch_add_explicit(&rw->lock, RW_LOCK_BIAS, memory_order_relaxed);
return 0;
}
-static inline void read_unlock(rwlock_t *rw)
+/** @PreCondition: return STATE(readerLockCnt) > 0 && !STATE(writerLockAcquired);
+@Transition: STATE(readerLockCnt)--; */
+void read_unlock(rwlock_t *rw)
{
+ /********** Detected Correctness (testcase1) **********/
atomic_fetch_add_explicit(&rw->lock, 1, memory_order_release);
+ /** @OPDefine: true */
}
-static inline void write_unlock(rwlock_t *rw)
+/** @PreCondition: return STATE(readerLockCnt) == 0 && STATE(writerLockAcquired);
+@Transition: STATE(writerLockAcquired) = false; */
+void write_unlock(rwlock_t *rw)
{
+ /********** Detected Correctness (testcase1) **********/
atomic_fetch_add_explicit(&rw->lock, RW_LOCK_BIAS, memory_order_release);
-}
-
-rwlock_t mylock;
-int shareddata;
-
-static void a(void *obj)
-{
- int i;
- for(i = 0; i < 2; i++) {
- if ((i % 2) == 0) {
- read_lock(&mylock);
- load_32(&shareddata);
- read_unlock(&mylock);
- } else {
- write_lock(&mylock);
- store_32(&shareddata,(unsigned int)i);
- write_unlock(&mylock);
- }
- }
-}
-
-int user_main(int argc, char **argv)
-{
- thrd_t t1, t2;
- atomic_init(&mylock.lock, RW_LOCK_BIAS);
-
- thrd_create(&t1, (thrd_start_t)&a, NULL);
- thrd_create(&t2, (thrd_start_t)&a, NULL);
-
- thrd_join(t1);
- thrd_join(t2);
-
- return 0;
+ /** @OPDefine: true */
}
--- /dev/null
+#ifndef _LINUXRWLOCKS_H
+#define _LINUXRWLOCKS_H
+
+#include <stdio.h>
+#include <threads.h>
+#include <stdatomic.h>
+
+#include "librace.h"
+
+#define RW_LOCK_BIAS 0x00100000
+#define WRITE_LOCK_CMP RW_LOCK_BIAS
+
+typedef union {
+ atomic_int lock;
+} rwlock_t;
+
+
+int read_can_lock(rwlock_t *lock);
+
+int write_can_lock(rwlock_t *lock);
+
+void read_lock(rwlock_t *rw);
+
+void write_lock(rwlock_t *rw);
+
+int read_trylock(rwlock_t *rw);
+
+int write_trylock(rwlock_t *rw);
+
+void read_unlock(rwlock_t *rw);
+
+void write_unlock(rwlock_t *rw);
+
+#endif
--- /dev/null
+#include <stdio.h>
+#include <threads.h>
+#include <stdatomic.h>
+
+#include "librace.h"
+#include "linuxrwlocks.h"
+
+rwlock_t mylock;
+int shareddata;
+
+atomic_int x, y;
+
+static void a(void *obj)
+{
+ int i;
+ for(i = 0; i < 2; i++) {
+ if ((i % 2) == 0) {
+ read_lock(&mylock);
+ load_32(&shareddata);
+ read_unlock(&mylock);
+ } else {
+ write_lock(&mylock);
+ store_32(&shareddata,(unsigned int)i);
+ write_unlock(&mylock);
+ }
+ }
+}
+
+
+int user_main(int argc, char **argv)
+{
+ thrd_t t1, t2;
+ atomic_init(&mylock.lock, RW_LOCK_BIAS);
+ atomic_init(&x, 0);
+ atomic_init(&y, 0);
+
+ /** @Entry */
+ thrd_create(&t1, (thrd_start_t)&a, NULL);
+ thrd_create(&t2, (thrd_start_t)&a, NULL);
+
+ thrd_join(t1);
+ thrd_join(t2);
+
+ return 0;
+}
--- /dev/null
+#include <stdio.h>
+#include <threads.h>
+#include <stdatomic.h>
+
+#include "librace.h"
+#include "linuxrwlocks.h"
+
+rwlock_t mylock;
+int shareddata;
+
+atomic_int x, y;
+
+static void a(void *obj)
+{
+ write_lock(&mylock);
+ //atomic_store_explicit(&x, 17, memory_order_relaxed);
+ write_unlock(&mylock);
+
+/*
+
+ if (!write_can_lock(&mylock))
+ return;
+
+ if (write_trylock(&mylock)) {
+ atomic_store_explicit(&x, 17, memory_order_relaxed);
+ write_unlock(&mylock);
+ }
+*/
+}
+
+static void b(void *obj)
+{
+ //if (write_trylock(&mylock)) {
+ //atomic_store_explicit(&x, 16, memory_order_relaxed);
+ // write_unlock(&mylock);
+ //}
+
+ read_lock(&mylock);
+ //atomic_load_explicit(&x, memory_order_relaxed);
+ read_unlock(&mylock);
+
+/*
+ if (write_trylock(&mylock)) {
+ atomic_store_explicit(&x, 16, memory_order_relaxed);
+ write_unlock(&mylock);
+ }
+
+ if (!read_can_lock(&mylock))
+ return;
+ if (read_trylock(&mylock)) {
+ atomic_load_explicit(&x, memory_order_relaxed);
+ read_unlock(&mylock);
+ }
+*/
+}
+
+int user_main(int argc, char **argv)
+{
+ thrd_t t1, t2;
+ atomic_init(&mylock.lock, RW_LOCK_BIAS);
+ atomic_init(&x, 0);
+ atomic_init(&y, 0);
+
+ /** @Entry */
+ thrd_create(&t1, (thrd_start_t)&a, NULL);
+ thrd_create(&t2, (thrd_start_t)&b, NULL);
+
+ thrd_join(t1);
+ thrd_join(t2);
+
+ return 0;
+}
--- /dev/null
+#include <stdio.h>
+#include <threads.h>
+#include <stdatomic.h>
+
+#include "librace.h"
+#include "linuxrwlocks.h"
+
+rwlock_t mylock;
+int shareddata;
+
+atomic_int x, y;
+
+static void a(void *obj)
+{
+ if (!write_can_lock(&mylock))
+ return;
+
+ if (write_trylock(&mylock)) {
+ // atomic_store_explicit(&x, 17, memory_order_relaxed);
+ write_unlock(&mylock);
+ }
+}
+
+static void b(void *obj)
+{
+ if (write_trylock(&mylock)) {
+ //atomic_store_explicit(&x, 16, memory_order_relaxed);
+ write_unlock(&mylock);
+ }
+
+ if (!read_can_lock(&mylock))
+ return;
+ if (read_trylock(&mylock)) {
+ // atomic_load_explicit(&x, memory_order_relaxed);
+ read_unlock(&mylock);
+ }
+}
+
+int user_main(int argc, char **argv)
+{
+ thrd_t t1, t2;
+ atomic_init(&mylock.lock, RW_LOCK_BIAS);
+ atomic_init(&x, 0);
+ atomic_init(&y, 0);
+
+ /** @Entry */
+ thrd_create(&t1, (thrd_start_t)&a, NULL);
+ thrd_create(&t2, (thrd_start_t)&b, NULL);
+
+ thrd_join(t1);
+ thrd_join(t2);
+
+ return 0;
+}
--- /dev/null
+#include <stdio.h>
+#include <threads.h>
+#include <stdatomic.h>
+
+#include "librace.h"
+#include "linuxrwlocks.h"
+
+rwlock_t mylock;
+int shareddata;
+
+atomic_int x, y;
+
+static void a(void *obj)
+{
+ write_lock(&mylock);
+ atomic_store_explicit(&x, 17, memory_order_relaxed);
+ write_unlock(&mylock);
+
+ if (!read_can_lock(&mylock))
+ return;
+ if (read_trylock(&mylock)) {
+ atomic_load_explicit(&x, memory_order_relaxed);
+ read_unlock(&mylock);
+ }
+}
+
+static void b(void *obj)
+{
+
+ if (write_trylock(&mylock)) {
+ atomic_store_explicit(&x, 16, memory_order_relaxed);
+ write_unlock(&mylock);
+ }
+
+ read_lock(&mylock);
+ atomic_load_explicit(&x, memory_order_relaxed);
+ read_unlock(&mylock);
+}
+
+int user_main(int argc, char **argv)
+{
+ thrd_t t1, t2;
+ /** @Entry */
+ atomic_init(&mylock.lock, RW_LOCK_BIAS);
+ atomic_init(&x, 0);
+ atomic_init(&y, 0);
+
+ thrd_create(&t1, (thrd_start_t)&a, NULL);
+ thrd_create(&t2, (thrd_start_t)&b, NULL);
+
+ thrd_join(t1);
+ thrd_join(t2);
+
+ return 0;
+}
include ../benchmarks.mk
-TESTNAME = mcs-lock
+BENCH := mcs-lock
-all: $(TESTNAME)
+BENCH_BINARY := $(BENCH).o
-$(TESTNAME): $(TESTNAME).cc $(TESTNAME).h
- $(CXX) -o $@ $< $(CXXFLAGS) $(LDFLAGS)
+TESTS := main testcase
+
+all: $(TESTS)
+ ../generate.sh $(notdir $(shell pwd))
+
+%.o : %.cc
+ $(CXX) -c -fPIC -MMD -MF .$@.d -o $@ $< $(CXXFLAGS) $(LDFLAGS)
+
+$(TESTS): % : %.o $(BENCH_BINARY)
+ $(CXX) -o $@ $^ $(CXXFLAGS) $(LDFLAGS)
+
+-include .*.d
clean:
- rm -f $(TESTNAME) *.o
+ rm -rf $(TESTS) *.o .*.d *.dSYM
+
+.PHONY: clean all
--- /dev/null
+#include <stdio.h>
+#include <threads.h>
+
+#include "mcs-lock.h"
+
+/* For data race instrumentation */
+#include "librace.h"
+
+struct mcs_mutex *mutex;
+static uint32_t shared;
+
+void threadA(void *arg)
+{
+ mcs_mutex::guard g(mutex);
+ //printf("store: %d\n", 17);
+ //store_32(&shared, 17);
+ mcs_unlock(mutex, &g);
+ //mutex->unlock(&g);
+ mcs_lock(mutex, &g);
+ //mutex->lock(&g);
+ //printf("load: %u\n", load_32(&shared));
+}
+
+void threadB(void *arg)
+{
+ mcs_mutex::guard g(mutex);
+ //printf("load: %u\n", load_32(&shared));
+ mcs_unlock(mutex, &g);
+ //mutex->unlock(&g);
+ mcs_lock(mutex, &g);
+ //mutex->lock(&g);
+ //printf("store: %d\n", 17);
+ //store_32(&shared, 17);
+}
+
+int user_main(int argc, char **argv)
+{
+ thrd_t A, B;
+
+ mutex = new mcs_mutex();
+ /** @Entry */
+ thrd_create(&A, &threadA, NULL);
+ thrd_create(&B, &threadB, NULL);
+ thrd_join(A);
+ thrd_join(B);
+ return 0;
+}
-#include <stdio.h>
-#include <threads.h>
-
#include "mcs-lock.h"
-/* For data race instrumentation */
-#include "librace.h"
+void mcs_mutex::lock(guard * I) {
+ mcs_node * me = &(I->m_node);
+
+ // set up my node :
+ // not published yet so relaxed :
+ me->next.store(NULL, std::mo_relaxed );
+ me->gate.store(1, std::mo_relaxed );
+
+ /********** Detected Correctness **********/
+ /** Run this in the -Y mode to expose the HB bug */
+ // publish my node as the new tail :
+ mcs_node * pred = m_tail.exchange(me, std::mo_acq_rel);
+ /** @OPDefine: pred == NULL */
+ if ( pred != NULL ) {
+ // (*1) race here
+ // unlock of pred can see me in the tail before I fill next
-struct mcs_mutex *mutex;
-static uint32_t shared;
+ // FIXME: detection miss, execution never ends
+ // If this is relaxed, the store 0 to gate will be read before and
+ // that lock will never ends.
+ // publish me to previous lock-holder :
+ pred->next.store(me, std::mo_release );
-void threadA(void *arg)
-{
- mcs_mutex::guard g(mutex);
- printf("store: %d\n", 17);
- store_32(&shared, 17);
- mutex->unlock(&g);
- mutex->lock(&g);
- printf("load: %u\n", load_32(&shared));
+ // (*2) pred not touched any more
+
+ // now this is the spin -
+ // wait on predecessor setting my flag -
+ rl::linear_backoff bo;
+ /********** Detected Correctness *********/
+ /** Run this in the -Y mode to expose the HB bug */
+ while ( me->gate.load(std::mo_acquire) ) {
+ thrd_yield();
+ }
+ /** @OPDefine: true */
+ }
}
-void threadB(void *arg)
-{
- mcs_mutex::guard g(mutex);
- printf("load: %u\n", load_32(&shared));
- mutex->unlock(&g);
- mutex->lock(&g);
- printf("store: %d\n", 17);
- store_32(&shared, 17);
+void mcs_mutex::unlock(guard * I) {
+ mcs_node * me = &(I->m_node);
+
+ // FIXME: detection miss, execution never ends
+ mcs_node * next = me->next.load(std::mo_acquire);
+ if ( next == NULL )
+ {
+ mcs_node * tail_was_me = me;
+ /********** Detected Correctness **********/
+ /** Run this in the -Y mode to expose the HB bug */
+ if ( m_tail.compare_exchange_strong(
+ tail_was_me,NULL,std::mo_acq_rel) ) {
+ // got null in tail, mutex is unlocked
+ /** @OPDefine: true */
+ return;
+ }
+
+ // (*1) catch the race :
+ rl::linear_backoff bo;
+ for(;;) {
+ // FIXME: detection miss, execution never ends
+ next = me->next.load(std::mo_acquire);
+ if ( next != NULL )
+ break;
+ thrd_yield();
+ }
+ }
+
+ // (*2) - store to next must be done,
+ // so no locker can be viewing my node any more
+
+ /********** Detected Correctness **********/
+ /** Run this in the -Y mode to expose the HB bug */
+ // let next guy in :
+ next->gate.store( 0, std::mo_release);
+ /** @OPDefine: true */
}
-int user_main(int argc, char **argv)
-{
- thrd_t A, B;
+// C-callable interface
+
+/** @DeclareState: bool lock;
+ @Initial: lock = false; */
- mutex = new mcs_mutex();
- thrd_create(&A, &threadA, NULL);
- thrd_create(&B, &threadB, NULL);
- thrd_join(A);
- thrd_join(B);
- return 0;
+/** @PreCondition: return STATE(lock) == false;
+@Transition: STATE(lock) = true; */
+void mcs_lock(mcs_mutex *mutex, CGuard guard) {
+ mcs_mutex::guard *myGuard = (mcs_mutex::guard*) guard;
+ mutex->lock(myGuard);
+}
+
+/** @PreCondition: return STATE(lock) == true;
+@Transition: STATE(lock) = false; */
+void mcs_unlock(mcs_mutex *mutex, CGuard guard) {
+ mcs_mutex::guard *myGuard = (mcs_mutex::guard*) guard;
+ mutex->unlock(myGuard);
}
// mcs on stack
+#ifndef _MCS_LOCK_H
+#define _MCS_LOCK_H
+
#include <stdatomic.h>
+#include <threads.h>
#include <unrelacy.h>
+// Forward declaration
+struct mcs_node;
+struct mcs_mutex;
+
struct mcs_node {
std::atomic<mcs_node *> next;
std::atomic<int> gate;
}
};
+// C-callable interface
+typedef void *CGuard;
+void mcs_lock(mcs_mutex *mutex, CGuard guard);
+
+void mcs_unlock(mcs_mutex *mutex, CGuard guard);
+
struct mcs_mutex {
public:
// tail is null when lock is not held
m_tail.store( NULL );
}
~mcs_mutex() {
- ASSERT( m_tail.load() == NULL );
+ RL_ASSERT( m_tail.load() == NULL );
}
class guard {
public:
mcs_mutex * m_t;
mcs_node m_node; // node held on the stack
-
- guard(mcs_mutex * t) : m_t(t) { t->lock(this); }
- ~guard() { m_t->unlock(this); }
+
+ // Call the wrapper (instrument every lock/unlock)
+ guard(mcs_mutex * t) : m_t(t) { mcs_lock(t, this); }
+ ~guard() { mcs_unlock(m_t, this); }
};
- void lock(guard * I) {
- mcs_node * me = &(I->m_node);
-
- // set up my node :
- // not published yet so relaxed :
- me->next.store(NULL, std::mo_relaxed );
- me->gate.store(1, std::mo_relaxed );
-
- // publish my node as the new tail :
- mcs_node * pred = m_tail.exchange(me, std::mo_acq_rel);
- if ( pred != NULL ) {
- // (*1) race here
- // unlock of pred can see me in the tail before I fill next
-
- // publish me to previous lock-holder :
- pred->next.store(me, std::mo_release );
-
- // (*2) pred not touched any more
+ void lock(guard * I);
- // now this is the spin -
- // wait on predecessor setting my flag -
- rl::linear_backoff bo;
- while ( me->gate.load(std::mo_acquire) ) {
- thrd_yield();
- }
- }
- }
-
- void unlock(guard * I) {
- mcs_node * me = &(I->m_node);
-
- mcs_node * next = me->next.load(std::mo_acquire);
- if ( next == NULL )
- {
- mcs_node * tail_was_me = me;
- if ( m_tail.compare_exchange_strong( tail_was_me,NULL,std::mo_acq_rel) ) {
- // got null in tail, mutex is unlocked
- return;
- }
-
- // (*1) catch the race :
- rl::linear_backoff bo;
- for(;;) {
- next = me->next.load(std::mo_acquire);
- if ( next != NULL )
- break;
- thrd_yield();
- }
- }
-
- // (*2) - store to next must be done,
- // so no locker can be viewing my node any more
-
- // let next guy in :
- next->gate.store( 0, std::mo_release );
- }
+ void unlock(guard * I);
};
+
+#endif
--- /dev/null
+#include <stdio.h>
+#include <threads.h>
+
+#include "mcs-lock.h"
+
+/* For data race instrumentation */
+#include "librace.h"
+
+struct mcs_mutex *mutex;
+static uint32_t shared;
+
+void threadA(void *arg)
+{
+ mcs_mutex::guard g(mutex);
+ //printf("store: %d\n", 17);
+ //store_32(&shared, 17);
+ mcs_unlock(mutex, &g);
+ //mutex->unlock(&g);
+ mcs_lock(mutex, &g);
+ //mutex->lock(&g);
+ //printf("load: %u\n", load_32(&shared));
+}
+
+void threadB(void *arg)
+{
+ mcs_mutex::guard g(mutex);
+ //printf("load: %u\n", load_32(&shared));
+ mcs_unlock(mutex, &g);
+ //mutex->unlock(&g);
+ mcs_lock(mutex, &g);
+ //mutex->lock(&g);
+ //printf("store: %d\n", 17);
+ //store_32(&shared, 17);
+}
+
+int user_main(int argc, char **argv)
+{
+ thrd_t A, B;
+
+ mutex = new mcs_mutex();
+ /** @Entry */
+ thrd_create(&A, &threadA, NULL);
+ thrd_create(&B, &threadB, NULL);
+ thrd_join(A);
+ thrd_join(B);
+ return 0;
+}
include ../benchmarks.mk
-TESTNAME = mpmc-queue
-TESTS = mpmc-queue mpmc-1r2w mpmc-2r1w mpmc-queue-rdwr
-TESTS += mpmc-queue-noinit mpmc-1r2w-noinit mpmc-2r1w-noinit mpmc-rdwr-noinit
+BENCH := mpmc-queue
+
+BENCH_BINARY := $(BENCH).o
+
+TESTS := main testcase1 testcase2
all: $(TESTS)
+ ../generate.sh $(notdir $(shell pwd))
-mpmc-queue: CPPFLAGS += -DCONFIG_MPMC_READERS=2 -DCONFIG_MPMC_WRITERS=2
-mpmc-queue-rdwr: CPPFLAGS += -DCONFIG_MPMC_READERS=0 -DCONFIG_MPMC_WRITERS=0 -DCONFIG_MPMC_RDWR=2
-mpmc-1r2w: CPPFLAGS += -DCONFIG_MPMC_READERS=1 -DCONFIG_MPMC_WRITERS=2
-mpmc-2r1w: CPPFLAGS += -DCONFIG_MPMC_READERS=2 -DCONFIG_MPMC_WRITERS=1
-mpmc-queue-noinit: CPPFLAGS += -DCONFIG_MPMC_READERS=2 -DCONFIG_MPMC_WRITERS=2 -DCONFIG_MPMC_NO_INITIAL_ELEMENT
-mpmc-1r2w-noinit: CPPFLAGS += -DCONFIG_MPMC_READERS=1 -DCONFIG_MPMC_WRITERS=2 -DCONFIG_MPMC_NO_INITIAL_ELEMENT
-mpmc-2r1w-noinit: CPPFLAGS += -DCONFIG_MPMC_READERS=2 -DCONFIG_MPMC_WRITERS=1 -DCONFIG_MPMC_NO_INITIAL_ELEMENT
-mpmc-rdwr-noinit: CPPFLAGS += -DCONFIG_MPMC_READERS=0 -DCONFIG_MPMC_WRITERS=0 -DCONFIG_MPMC_RDWR=2 -DCONFIG_MPMC_NO_INITIAL_ELEMENT
+%.o : %.cc
+ $(CXX) -c -fPIC -MMD -MF .$@.d -o $@ $< $(CXXFLAGS) $(LDFLAGS)
-$(TESTS): $(TESTNAME).cc $(TESTNAME).h
- $(CXX) -o $@ $< $(CXXFLAGS) $(LDFLAGS)
+$(TESTS): % : %.o $(BENCH_BINARY)
+ $(CXX) -o $@ $^ $(CXXFLAGS) $(LDFLAGS)
+
+-include .*.d
clean:
- rm -f $(TESTS) *.o
+ rm -rf $(TESTS) *.o .*.d *.dSYM
+
+.PHONY: clean all
--- /dev/null
+#include <threads.h>
+#include <unistd.h>
+#include <librace.h>
+
+#include "mpmc-queue.h"
+
+atomic_int x;
+
+void threadA(struct mpmc_boundq_1_alt<int32_t> *queue)
+{
+ int32_t *bin = write_prepare(queue);
+ //store_32(bin, 1);
+ write_publish(queue, bin);
+}
+
+void threadB(struct mpmc_boundq_1_alt<int32_t> *queue)
+{
+ int32_t *bin;
+ if ((bin = read_fetch(queue)) != NULL) {
+ //printf("Read: %d\n", load_32(bin));
+ read_consume(queue, bin);
+ }
+}
+
+void threadC(struct mpmc_boundq_1_alt<int32_t> *queue)
+{
+ int32_t *bin = write_prepare(queue);
+ //store_32(bin, 1);
+ write_publish(queue, bin);
+
+ while ((bin = read_fetch(queue)) != NULL) {
+ //printf("Read: %d\n", load_32(bin));
+ read_consume(queue, bin);
+ }
+}
+
+
+int user_main(int argc, char **argv)
+{
+ mpmc_boundq_1_alt<int32_t> *queue = createMPMC(16);
+ thrd_t t1, t2;
+ /** @Entry */
+ /*
+ int32_t *bin = write_prepare(queue);
+ store_32(bin, 1);
+ write_publish(queue, bin);
+ */
+ printf("Start threads\n");
+
+ thrd_create(&t1, (thrd_start_t)&threadC, queue);
+ thrd_create(&t2, (thrd_start_t)&threadC, queue);
+
+ thrd_join(t1);
+ thrd_join(t2);
+
+ printf("Threads complete\n");
+
+ //destroyMPMC(queue);
+ return 0;
+}
-#include <inttypes.h>
-#include <threads.h>
-#include <stdio.h>
-#include <unistd.h>
-#include <stdlib.h>
+#include <threads.h>
+#include "mpmc-queue.h"
-#include <librace.h>
+template <typename t_element>
+t_element * mpmc_boundq_1_alt<t_element>::read_fetch() {
+ // FIXME: We can have a relaxed for sure here since the next CAS
+ // will fix the problem
+ unsigned int rdwr = m_rdwr.load(mo_acquire);
+ unsigned int rd,wr;
+ for(;;) {
+ rd = (rdwr>>16) & 0xFFFF;
+ wr = rdwr & 0xFFFF;
-#include "mpmc-queue.h"
+ if ( wr == rd ) // empty
+ return NULL;
-void threadA(struct mpmc_boundq_1_alt<int32_t, sizeof(int32_t)> *queue)
-{
- int32_t *bin = queue->write_prepare();
- store_32(bin, 1);
- queue->write_publish();
-}
+ if ( m_rdwr.compare_exchange_weak(rdwr,rdwr+(1<<16),mo_acq_rel) )
+ break;
+ else
+ thrd_yield();
+ }
-void threadB(struct mpmc_boundq_1_alt<int32_t, sizeof(int32_t)> *queue)
-{
- int32_t *bin;
- while ((bin = queue->read_fetch()) != NULL) {
- printf("Read: %d\n", load_32(bin));
- queue->read_consume();
+ // (*1)
+ rl::backoff bo;
+ /********** Detected Admissibility (testcase1) **********/
+ while ( (m_written.load(mo_acquire) & 0xFFFF) != wr ) {
+ thrd_yield();
}
-}
+ /** @OPDefine: true */
-void threadC(struct mpmc_boundq_1_alt<int32_t, sizeof(int32_t)> *queue)
-{
- int32_t *bin = queue->write_prepare();
- store_32(bin, 1);
- queue->write_publish();
+ t_element * p = & ( m_array[ rd % t_size ] );
- while ((bin = queue->read_fetch()) != NULL) {
- printf("Read: %d\n", load_32(bin));
- queue->read_consume();
- }
+ return p;
}
-#define MAXREADERS 3
-#define MAXWRITERS 3
-#define MAXRDWR 3
-#ifdef CONFIG_MPMC_READERS
-#define DEFAULT_READERS (CONFIG_MPMC_READERS)
-#else
-#define DEFAULT_READERS 2
-#endif
-
-#ifdef CONFIG_MPMC_WRITERS
-#define DEFAULT_WRITERS (CONFIG_MPMC_WRITERS)
-#else
-#define DEFAULT_WRITERS 2
-#endif
+template <typename t_element>
+void mpmc_boundq_1_alt<t_element>::read_consume(t_element *bin) {
+ /********** Detected Admissibility (testcase2) **********/
+ // run with -Y
+ m_read.fetch_add(1,mo_release);
+ /** @OPDefine: true */
+}
-#ifdef CONFIG_MPMC_RDWR
-#define DEFAULT_RDWR (CONFIG_MPMC_RDWR)
-#else
-#define DEFAULT_RDWR 0
-#endif
-int readers = DEFAULT_READERS, writers = DEFAULT_WRITERS, rdwr = DEFAULT_RDWR;
+template <typename t_element>
+t_element * mpmc_boundq_1_alt<t_element>::write_prepare() {
+ // FIXME: We can have a relaxed for sure here since the next CAS
+ // will fix the problem
+ unsigned int rdwr = m_rdwr.load(mo_acquire);
+ unsigned int rd,wr;
+ for(;;) {
+ rd = (rdwr>>16) & 0xFFFF;
+ wr = rdwr & 0xFFFF;
-void print_usage()
-{
- printf("Error: use the following options\n"
- " -r <num> Choose number of reader threads\n"
- " -w <num> Choose number of writer threads\n");
- exit(EXIT_FAILURE);
-}
+ if ( wr == ((rd + t_size)&0xFFFF) ) // full
+ return NULL;
-void process_params(int argc, char **argv)
-{
- const char *shortopts = "hr:w:";
- int opt;
- bool error = false;
-
- while (!error && (opt = getopt(argc, argv, shortopts)) != -1) {
- switch (opt) {
- case 'h':
- print_usage();
- break;
- case 'r':
- readers = atoi(optarg);
- break;
- case 'w':
- writers = atoi(optarg);
+ if ( m_rdwr.compare_exchange_weak(rdwr,(rd<<16) | ((wr+1)&0xFFFF),mo_acq_rel) )
break;
- default: /* '?' */
- error = true;
- break;
- }
+ else
+ thrd_yield();
+ }
+
+ // (*1)
+ rl::backoff bo;
+ /********** Detected Admissibility (testcase2) **********/
+ // run with -Y
+ while ( (m_read.load(mo_acquire) & 0xFFFF) != rd ) {
+ thrd_yield();
}
+ /** @OPDefine: true */
- if (writers < 1 || writers > MAXWRITERS)
- error = true;
- if (readers < 1 || readers > MAXREADERS)
- error = true;
+ t_element * p = & ( m_array[ wr % t_size ] );
- if (error)
- print_usage();
+ return p;
}
-int user_main(int argc, char **argv)
+template <typename t_element>
+void mpmc_boundq_1_alt<t_element>::write_publish(t_element *bin)
{
- struct mpmc_boundq_1_alt<int32_t, sizeof(int32_t)> queue;
- thrd_t A[MAXWRITERS], B[MAXREADERS], C[MAXRDWR];
+ /********** Detected Admissibility (testcase1) **********/
+ m_written.fetch_add(1,mo_release);
+ /** @OPDefine: true */
+}
- /* Note: optarg() / optind is broken in model-checker - workaround is
- * to just copy&paste this test a few times */
- //process_params(argc, argv);
- printf("%d reader(s), %d writer(s)\n", readers, writers);
+/** @DeclareState:
+ @Commutativity: write_prepare <-> write_publish (M1->C_RET == M2->bin)
+ @Commutativity: write_publish <-> read_fetch (M1->bin == M2->C_RET)
+ @Commutativity: read_fetch <-> read_consume (M1->C_RET == M2->bin)
+ @Commutativity: read_consume <-> write_prepare (M1->bin == M2->C_RET) */
-#ifndef CONFIG_MPMC_NO_INITIAL_ELEMENT
- printf("Adding initial element\n");
- int32_t *bin = queue.write_prepare();
- store_32(bin, 17);
- queue.write_publish();
-#endif
- printf("Start threads\n");
+mpmc_boundq_1_alt<int32_t>* createMPMC(int size) {
+ return new mpmc_boundq_1_alt<int32_t>(size);
+}
- for (int i = 0; i < writers; i++)
- thrd_create(&A[i], (thrd_start_t)&threadA, &queue);
- for (int i = 0; i < readers; i++)
- thrd_create(&B[i], (thrd_start_t)&threadB, &queue);
+void destroyMPMC(mpmc_boundq_1_alt<int32_t> *q) {
+ delete q;
+}
- for (int i = 0; i < rdwr; i++)
- thrd_create(&C[i], (thrd_start_t)&threadC, &queue);
+/** @PreCondition: */
+int32_t * read_fetch(mpmc_boundq_1_alt<int32_t> *q) {
+ return q->read_fetch();
+}
- for (int i = 0; i < writers; i++)
- thrd_join(A[i]);
- for (int i = 0; i < readers; i++)
- thrd_join(B[i]);
- for (int i = 0; i < rdwr; i++)
- thrd_join(C[i]);
+/** @PreCondition: */
+void read_consume(mpmc_boundq_1_alt<int32_t> *q, int32_t *bin) {
+ q->read_consume(bin);
+}
- printf("Threads complete\n");
+/** @PreCondition: */
+int32_t * write_prepare(mpmc_boundq_1_alt<int32_t> *q) {
+ return q->write_prepare();
+}
- return 0;
+/** @PreCondition: */
+void write_publish(mpmc_boundq_1_alt<int32_t> *q, int32_t *bin) {
+ q->write_publish(bin);
}
+#ifndef _MPMC_QUEUE_H
+#define _MPMC_QUEUE_H
+
#include <stdatomic.h>
#include <unrelacy.h>
-template <typename t_element, size_t t_size>
+template <typename t_element>
struct mpmc_boundq_1_alt
{
private:
-
+ // buffer capacity
+ size_t t_size;
// elements should generally be cache-line-size padded :
- t_element m_array[t_size];
+ t_element *m_array;
// rdwr counts the reads & writes that have started
atomic<unsigned int> m_rdwr;
public:
- mpmc_boundq_1_alt()
+ mpmc_boundq_1_alt(int size)
{
+ t_size = size;
+ m_array = new t_element[size];
m_rdwr = 0;
m_read = 0;
m_written = 0;
//-----------------------------------------------------
- t_element * read_fetch() {
- unsigned int rdwr = m_rdwr.load(mo_acquire);
- unsigned int rd,wr;
- for(;;) {
- rd = (rdwr>>16) & 0xFFFF;
- wr = rdwr & 0xFFFF;
-
- if ( wr == rd ) // empty
- return NULL;
-
- if ( m_rdwr.compare_exchange_weak(rdwr,rdwr+(1<<16),mo_acq_rel) )
- break;
- else
- thrd_yield();
- }
-
- // (*1)
- rl::backoff bo;
- while ( (m_written.load(mo_acquire) & 0xFFFF) != wr ) {
- thrd_yield();
- }
-
- t_element * p = & ( m_array[ rd % t_size ] );
-
- return p;
- }
-
- void read_consume() {
- m_read.fetch_add(1,mo_release);
- }
+ t_element * read_fetch();
- //-----------------------------------------------------
+ void read_consume(t_element *bin);
- t_element * write_prepare() {
- unsigned int rdwr = m_rdwr.load(mo_acquire);
- unsigned int rd,wr;
- for(;;) {
- rd = (rdwr>>16) & 0xFFFF;
- wr = rdwr & 0xFFFF;
+ t_element * write_prepare();
- if ( wr == ((rd + t_size)&0xFFFF) ) // full
- return NULL;
+ void write_publish(t_element *bin);
+};
- if ( m_rdwr.compare_exchange_weak(rdwr,(rd<<16) | ((wr+1)&0xFFFF),mo_acq_rel) )
- break;
- else
- thrd_yield();
- }
+mpmc_boundq_1_alt<int32_t>* createMPMC(int size);
- // (*1)
- rl::backoff bo;
- while ( (m_read.load(mo_acquire) & 0xFFFF) != rd ) {
- thrd_yield();
- }
+void destroyMPMC(mpmc_boundq_1_alt<int32_t> *q);
- t_element * p = & ( m_array[ wr % t_size ] );
+int32_t * read_fetch(mpmc_boundq_1_alt<int32_t> *q);
- return p;
- }
+void read_consume(mpmc_boundq_1_alt<int32_t> *q, int32_t *bin);
- void write_publish()
- {
- m_written.fetch_add(1,mo_release);
- }
-
- //-----------------------------------------------------
+int32_t * write_prepare(mpmc_boundq_1_alt<int32_t> *q);
+void write_publish(mpmc_boundq_1_alt<int32_t> *q, int32_t *bin);
-};
+#endif
--- /dev/null
+#include <threads.h>
+#include <unistd.h>
+#include <librace.h>
+
+#include "mpmc-queue.h"
+
+atomic_int x;
+
+void threadA(struct mpmc_boundq_1_alt<int32_t> *queue)
+{
+ int32_t *bin = write_prepare(queue);
+ //store_32(bin, 1);
+ write_publish(queue, bin);
+}
+
+void threadB(struct mpmc_boundq_1_alt<int32_t> *queue)
+{
+ int32_t *bin;
+ if ((bin = read_fetch(queue)) != NULL) {
+ //printf("Read: %d\n", load_32(bin));
+ read_consume(queue, bin);
+ }
+}
+
+void threadC(struct mpmc_boundq_1_alt<int32_t> *queue)
+{
+ int32_t *bin = write_prepare(queue);
+ //store_32(bin, 1);
+ write_publish(queue, bin);
+
+ while ((bin = read_fetch(queue)) != NULL) {
+ //printf("Read: %d\n", load_32(bin));
+ read_consume(queue, bin);
+ }
+}
+
+
+int user_main(int argc, char **argv)
+{
+ mpmc_boundq_1_alt<int32_t> *queue = createMPMC(2);
+ thrd_t A, B, C;
+ /** @Entry */
+ x.store(0);
+ printf("Start threads\n");
+
+ thrd_create(&A, (thrd_start_t)&threadA, queue);
+ thrd_create(&B, (thrd_start_t)&threadB, queue);
+ //thrd_create(&C, (thrd_start_t)&threadC, queue);
+
+ thrd_join(A);
+ thrd_join(B);
+ //thrd_join(C);
+
+ printf("Threads complete\n");
+
+ destroyMPMC(queue);
+ return 0;
+}
--- /dev/null
+#include <threads.h>
+#include <unistd.h>
+#include <librace.h>
+
+#include "mpmc-queue.h"
+
+atomic_int x;
+
+void threadA(struct mpmc_boundq_1_alt<int32_t> *queue)
+{
+ int32_t *bin = write_prepare(queue);
+ if (bin) {
+ //printf("Thread A: bin=%d", bin);
+ //store_32(bin, 1);
+ write_publish(queue, bin);
+ }
+}
+
+void threadB(struct mpmc_boundq_1_alt<int32_t> *queue)
+{
+ int32_t *bin;
+ if ((bin = read_fetch(queue)) != NULL) {
+ //printf("Read: %d\n", load_32(bin));
+ read_consume(queue, bin);
+ }
+}
+
+void threadC(struct mpmc_boundq_1_alt<int32_t> *queue)
+{
+ int32_t *bin = write_prepare(queue);
+ if (bin) {
+ //store_32(bin, 1);
+ write_publish(queue, bin);
+ }
+
+ while ((bin = read_fetch(queue)) != NULL) {
+ //printf("Read: %d\n", load_32(bin));
+ read_consume(queue, bin);
+ }
+}
+
+
+int user_main(int argc, char **argv)
+{
+ mpmc_boundq_1_alt<int32_t> *queue = createMPMC(2);
+ thrd_t A, B, C;
+ /** @Entry */
+
+ // A queue of capacity 2, 2 writes happened very early
+ int32_t *bin = write_prepare(queue);
+ write_publish(queue, bin);
+ bin = write_prepare(queue);
+ write_publish(queue, bin);
+
+ printf("Start threads\n");
+
+ thrd_create(&A, (thrd_start_t)&threadA, queue);
+ thrd_create(&B, (thrd_start_t)&threadB, queue);
+ //thrd_create(&C, (thrd_start_t)&threadC, queue);
+
+ thrd_join(A);
+ thrd_join(B);
+ //thrd_join(C);
+
+ printf("Threads complete\n");
+
+ destroyMPMC(queue);
+ return 0;
+}
--- /dev/null
+include ../benchmarks.mk
+
+BENCH := queue
+
+BENCH_BINARY := $(BENCH).o
+
+TESTS := main testcase1 testcase2 testcase3 testcase4
+
+all: $(TESTS)
+ ../generate.sh $(notdir $(shell pwd))
+
+%.o : %.c
+ $(CC) -c -fPIC -MMD -MF .$@.d -o $@ $< $(CFLAGS) $(LDFLAGS)
+
+$(TESTS): % : %.o $(BENCH_BINARY)
+ $(CC) -o $@ $^ $(CFLAGS) $(LDFLAGS)
+
+-include .*.d
+
+clean:
+ rm -rf $(TESTS) *.o .*.d *.dSYM
+
+.PHONY: clean all
--- /dev/null
+#include <stdlib.h>
+#include <stdio.h>
+#include <threads.h>
+
+#include "queue.h"
+#include "model-assert.h"
+
+static queue_t *queue;
+static thrd_t *threads;
+static unsigned int *input;
+static unsigned int *output;
+static int num_threads;
+
+int get_thread_num()
+{
+ thrd_t curr = thrd_current();
+ int i;
+ for (i = 0; i < num_threads; i++)
+ if (curr.priv == threads[i].priv)
+ return i;
+ MODEL_ASSERT(0);
+ return -1;
+}
+
+bool succ1, succ2;
+atomic_int x[3];
+unsigned int idx1, idx2;
+unsigned int reclaimNode;
+
+static int procs = 2;
+static void main_task(void *param)
+{
+ unsigned int val;
+ int pid = *((int *)param);
+ if (pid % 2 == 0) {
+ enqueue(queue, 0, 0);
+ succ1 = dequeue(queue, &idx1, &reclaimNode);
+ } else {
+ enqueue(queue, 1, 0);
+ succ2 = dequeue(queue, &idx2, &reclaimNode);
+ }
+}
+
+int user_main(int argc, char **argv)
+{
+ int i;
+ int *param;
+ unsigned int in_sum = 0, out_sum = 0;
+
+ /** @Entry */
+ queue = (queue_t*) calloc(1, sizeof(*queue));
+ MODEL_ASSERT(queue);
+
+ num_threads = procs;
+ threads = (thrd_t*) malloc(num_threads * sizeof(thrd_t));
+ param = (int*) malloc(num_threads * sizeof(*param));
+ input = (unsigned int *) calloc(num_threads, sizeof(*input));
+ output = (unsigned int *) calloc(num_threads, sizeof(*output));
+
+ init_queue(queue, num_threads);
+ for (i = 0; i < num_threads; i++) {
+ param[i] = i;
+ thrd_create(&threads[i], main_task, ¶m[i]);
+ }
+ for (i = 0; i < num_threads; i++)
+ thrd_join(threads[i]);
+/*
+ for (i = 0; i < num_threads; i++) {
+ in_sum += input[i];
+ out_sum += output[i];
+ }
+ for (i = 0; i < num_threads; i++)
+ printf("input[%d] = %u\n", i, input[i]);
+ for (i = 0; i < num_threads; i++)
+ printf("output[%d] = %u\n", i, output[i]);
+ if (succ1 && succ2)
+ MODEL_ASSERT(in_sum == out_sum);
+ else
+ MODEL_ASSERT (false);
+*/
+ free(param);
+ free(threads);
+ free(queue);
+
+ return 0;
+}
--- /dev/null
+#include <threads.h>
+#include <stdlib.h>
+#include "librace.h"
+#include "model-assert.h"
+
+#include "queue.h"
+
+#define relaxed memory_order_relaxed
+#define release memory_order_release
+#define acquire memory_order_acquire
+
+#define MAX_FREELIST 4 /* Each thread can own up to MAX_FREELIST free nodes */
+#define INITIAL_FREE 2 /* Each thread starts with INITIAL_FREE free nodes */
+
+#define POISON_IDX 0x666
+
+static unsigned int (*free_lists)[MAX_FREELIST];
+
+/* Search this thread's free list for a "new" node */
+static unsigned int new_node()
+{
+ int i;
+ int t = get_thread_num();
+ for (i = 0; i < MAX_FREELIST; i++) {
+ //unsigned int node = load_32(&free_lists[t][i]);
+ unsigned int node = free_lists[t][i];
+ if (node) {
+ //store_32(&free_lists[t][i], 0);
+ free_lists[t][i] = 0;
+ return node;
+ }
+ }
+ /* free_list is empty? */
+ MODEL_ASSERT(0);
+ return 0;
+}
+
+/* Simulate the fact that when a node got recycled, it will get assigned to the
+ * same queue or for other usage */
+void simulateRecycledNodeUpdate(queue_t *q, unsigned int node) {
+ atomic_store_explicit(&q->nodes[node].next, -1, memory_order_release);
+}
+
+
+/* Place this node index back on this thread's free list */
+static void reclaim(unsigned int node)
+{
+ int i;
+ int t = get_thread_num();
+
+ /* Don't reclaim NULL node */
+ //MODEL_ASSERT(node);
+
+ for (i = 0; i < MAX_FREELIST; i++) {
+ /* Should never race with our own thread here */
+ //unsigned int idx = load_32(&free_lists[t][i]);
+ unsigned int idx = free_lists[t][i];
+
+ /* Found empty spot in free list */
+ if (idx == 0) {
+ //store_32(&free_lists[t][i], node);
+ free_lists[t][i] = node;
+ return;
+ }
+ }
+ /* free list is full? */
+ //MODEL_ASSERT(0);
+}
+
+void init_queue(queue_t *q, int num_threads)
+{
+ int i, j;
+
+ /* Initialize each thread's free list with INITIAL_FREE pointers */
+ /* The actual nodes are initialized with poison indexes */
+ free_lists = ( unsigned int (*)[MAX_FREELIST] ) malloc(num_threads * sizeof(*free_lists));
+ for (i = 0; i < num_threads; i++) {
+ for (j = 0; j < INITIAL_FREE; j++) {
+ free_lists[i][j] = 2 + i * MAX_FREELIST + j;
+ atomic_init(&q->nodes[free_lists[i][j]].next, MAKE_POINTER(POISON_IDX, 0));
+ }
+ }
+
+ /* initialize queue */
+ atomic_init(&q->head, MAKE_POINTER(1, 0));
+ atomic_init(&q->tail, MAKE_POINTER(1, 0));
+ atomic_init(&q->nodes[1].next, MAKE_POINTER(0, 0));
+}
+
+/** @DeclareState: IntList *q;
+@Commutativity: enqueue <-> dequeue (true)
+@Commutativity: dequeue <-> dequeue (!M1->RET || !M2->RET) */
+
+/** @Transition: STATE(q)->push_back(val); */
+void enqueue(queue_t *q, unsigned int val, int n)
+{
+ int success = 0;
+ unsigned int node;
+ pointer tail;
+ pointer next;
+ pointer tmp;
+
+ node = new_node();
+ //store_32(&q->nodes[node].value, val);
+ q->nodes[node].value = val;
+ tmp = atomic_load_explicit(&q->nodes[node].next, relaxed);
+ set_ptr(&tmp, 0); // NULL
+ // This is a found bug in AutoMO, and testcase4 can reveal this known bug
+ atomic_store_explicit(&q->nodes[node].next, tmp, release);
+
+ while (!success) {
+ /********** Detected UL **********/
+ tail = atomic_load_explicit(&q->tail, acquire);
+ /********** Detected Admissibility (testcase4) **********/
+ next = atomic_load_explicit(&q->nodes[get_ptr(tail)].next, acquire);
+ if (tail == atomic_load_explicit(&q->tail, relaxed)) {
+
+ /* Check for uninitialized 'next' */
+ //MODEL_ASSERT(get_ptr(next) != POISON_IDX);
+
+ if (get_ptr(next) == 0) { // == NULL
+ pointer value = MAKE_POINTER(node, get_count(next) + 1);
+ /********** Detected Correctness (testcase1) **********/
+ success = atomic_compare_exchange_strong_explicit(&q->nodes[get_ptr(tail)].next,
+ &next, value, release, release);
+ /** @OPClearDefine: success */
+ }
+ if (!success) {
+ /********** Detected UL **********/
+ unsigned int ptr = get_ptr(atomic_load_explicit(&q->nodes[get_ptr(tail)].next, acquire));
+ pointer value = MAKE_POINTER(ptr,
+ get_count(tail) + 1);
+ /********** Detected Correctness (testcase2) **********/
+ atomic_compare_exchange_strong_explicit(&q->tail,
+ &tail, value,
+ release, release);
+ thrd_yield();
+ }
+ }
+ }
+ /********** Detected Corrctness (testcase1) **********/
+ atomic_compare_exchange_strong_explicit(&q->tail,
+ &tail,
+ MAKE_POINTER(node, get_count(tail) + 1),
+ release, release);
+}
+
+/** @Transition: if (RET) {
+ if (STATE(q)->empty()) return false;
+ STATE(q)->pop_front();
+}
+@PreCondition: return RET ? !STATE(q)->empty() && STATE(q)->front() == *retVal : true; */
+bool dequeue(queue_t *q, unsigned int *retVal, unsigned int *reclaimNode)
+{
+ int success = 0;
+ pointer head;
+ pointer tail;
+ pointer next;
+
+ while (!success) {
+ /********** Dectected Admissibility (testcase3) **********/
+ head = atomic_load_explicit(&q->head, acquire);
+ /********** Detected KNOWN BUG **********/
+ tail = atomic_load_explicit(&q->tail, acquire);
+ /********** Detected Correctness (testcase1) **********/
+ next = atomic_load_explicit(&q->nodes[get_ptr(head)].next, acquire);
+ /** @OPClearDefine: true */
+ if (atomic_load_explicit(&q->head, relaxed) == head) {
+ if (get_ptr(head) == get_ptr(tail)) {
+
+ /* Check for uninitialized 'next' */
+ MODEL_ASSERT(get_ptr(next) != POISON_IDX);
+
+ if (get_ptr(next) == 0) { // NULL
+ return false; // NULL
+ }
+ /********** Detected UL **********/
+ atomic_compare_exchange_strong_explicit(&q->tail,
+ &tail,
+ MAKE_POINTER(get_ptr(next), get_count(tail) + 1),
+ release, release);
+ thrd_yield();
+ } else {
+ //*retVal = load_32(&q->nodes[get_ptr(next)].value);
+ *retVal = q->nodes[get_ptr(next)].value;
+ /********** Detected Admissibility (testcase3) **********/
+ success = atomic_compare_exchange_strong_explicit(&q->head,
+ &head,
+ MAKE_POINTER(get_ptr(next), get_count(head) + 1),
+ release, release);
+ if (!success)
+ thrd_yield();
+ }
+ }
+ }
+ *reclaimNode = get_ptr(head);
+ reclaim(get_ptr(head));
+ return true;
+}
--- /dev/null
+#ifndef _QUEUE_H
+#define _QUEUE_H
+
+#include <stdatomic.h>
+
+#define MAX_NODES 0xf
+
+typedef unsigned long long pointer;
+typedef atomic_ullong pointer_t;
+
+#define MAKE_POINTER(ptr, count) ((((pointer)count) << 32) | ptr)
+#define PTR_MASK 0xffffffffLL
+#define COUNT_MASK (0xffffffffLL << 32)
+
+static inline void set_count(pointer *p, unsigned int val) { *p = (*p & ~COUNT_MASK) | ((pointer)val << 32); }
+static inline void set_ptr(pointer *p, unsigned int val) { *p = (*p & ~PTR_MASK) | val; }
+static inline unsigned int get_count(pointer p) { return (p & COUNT_MASK) >> 32; }
+static inline unsigned int get_ptr(pointer p) { return p & PTR_MASK; }
+
+typedef struct node {
+ unsigned int value;
+ pointer_t next;
+} node_t;
+
+typedef struct {
+ pointer_t head;
+ pointer_t tail;
+ node_t nodes[MAX_NODES + 2];
+} queue_t;
+
+void init_queue(queue_t *q, int num_threads);
+void enqueue(queue_t *q, unsigned int val, int n);
+bool dequeue(queue_t *q, unsigned int *retVal, unsigned int *reclaimedNode);
+
+int get_thread_num();
+
+#endif
--- /dev/null
+#include <stdlib.h>
+#include <stdio.h>
+#include <threads.h>
+
+#include "queue.h"
+#include "model-assert.h"
+
+static queue_t *queue;
+static thrd_t *threads;
+static unsigned int *input;
+static unsigned int *output;
+static int num_threads;
+
+int get_thread_num()
+{
+ thrd_t curr = thrd_current();
+ int i;
+ for (i = 0; i < num_threads; i++)
+ if (curr.priv == threads[i].priv)
+ return i;
+ MODEL_ASSERT(0);
+ return -1;
+}
+
+bool succ1, succ2;
+atomic_int x[3];
+unsigned int idx1, idx2;
+unsigned int reclaimNode;
+
+static int procs = 2;
+static void main_task(void *param)
+{
+ unsigned int val;
+ int pid = *((int *)param);
+ if (pid % procs == 0) {
+ //atomic_store_explicit(&x[0], 1, memory_order_relaxed);
+ enqueue(queue, 0, 0);
+ } else if (pid % procs == 1) {
+ //atomic_store_explicit(&x[1], 1, memory_order_relaxed);
+ succ1 = dequeue(queue, &idx1, &reclaimNode);
+ if (succ1) {
+ //atomic_load_explicit(&x[idx1], memory_order_relaxed);
+ }
+ }
+}
+
+int user_main(int argc, char **argv)
+{
+ int i;
+ int *param;
+ unsigned int in_sum = 0, out_sum = 0;
+
+ /** @Entry */
+ queue = (queue_t*) calloc(1, sizeof(*queue));
+ MODEL_ASSERT(queue);
+
+ num_threads = procs;
+ threads = (thrd_t*) malloc(num_threads * sizeof(thrd_t));
+ param = (int*) malloc(num_threads * sizeof(*param));
+ input = (unsigned int *) calloc(num_threads, sizeof(*input));
+ output = (unsigned int *) calloc(num_threads, sizeof(*output));
+
+ atomic_init(&x[0], 0);
+ atomic_init(&x[1], 0);
+ atomic_init(&x[2], 0);
+ init_queue(queue, num_threads);
+ for (i = 0; i < num_threads; i++) {
+ param[i] = i;
+ thrd_create(&threads[i], main_task, ¶m[i]);
+ }
+ for (i = 0; i < num_threads; i++)
+ thrd_join(threads[i]);
+/*
+ for (i = 0; i < num_threads; i++) {
+ in_sum += input[i];
+ out_sum += output[i];
+ }
+ for (i = 0; i < num_threads; i++)
+ printf("input[%d] = %u\n", i, input[i]);
+ for (i = 0; i < num_threads; i++)
+ printf("output[%d] = %u\n", i, output[i]);
+ if (succ1 && succ2)
+ MODEL_ASSERT(in_sum == out_sum);
+ else
+ MODEL_ASSERT (false);
+*/
+ free(param);
+ free(threads);
+ free(queue);
+
+ return 0;
+}
--- /dev/null
+#include <stdlib.h>
+#include <stdio.h>
+#include <threads.h>
+
+#include "queue.h"
+#include "model-assert.h"
+
+static queue_t *queue;
+static thrd_t *threads;
+static unsigned int *input;
+static unsigned int *output;
+static int num_threads;
+
+int get_thread_num()
+{
+ thrd_t curr = thrd_current();
+ int i;
+ for (i = 0; i < num_threads; i++)
+ if (curr.priv == threads[i].priv)
+ return i;
+ MODEL_ASSERT(0);
+ return -1;
+}
+
+bool succ1, succ2;
+atomic_int x[3];
+unsigned int idx1, idx2;
+unsigned int reclaimNode;
+
+static int procs = 4;
+static void main_task(void *param)
+{
+ unsigned int val;
+ int pid = *((int *)param);
+ if (pid % 4 == 0) {
+ //atomic_store_explicit(&x[0], 1, memory_order_relaxed);
+ enqueue(queue, 0, 0);
+ } else if (pid % 4 == 1) {
+ //atomic_store_explicit(&x[1], 1, memory_order_relaxed);
+ enqueue(queue, 1, 0);
+ } else if (pid % 4 == 2) {
+ succ1 = dequeue(queue, &idx1, &reclaimNode);
+ if (succ1) {
+ //atomic_load_explicit(&x[idx1], memory_order_relaxed);
+ }
+ } else if (pid % 4 == 3) {
+ /*
+ succ2 = dequeue(queue, &idx2, &reclaimNode);
+ if (succ2) {
+ atomic_load_explicit(&x[idx2], memory_order_relaxed);
+ }
+ */
+ }
+}
+
+int user_main(int argc, char **argv)
+{
+ int i;
+ int *param;
+ unsigned int in_sum = 0, out_sum = 0;
+
+ /** @Entry */
+ queue = (queue_t*) calloc(1, sizeof(*queue));
+ MODEL_ASSERT(queue);
+
+ num_threads = procs;
+ threads = (thrd_t*) malloc(num_threads * sizeof(thrd_t));
+ param = (int*) malloc(num_threads * sizeof(*param));
+ input = (unsigned int *) calloc(num_threads, sizeof(*input));
+ output = (unsigned int *) calloc(num_threads, sizeof(*output));
+
+ atomic_init(&x[0], 0);
+ atomic_init(&x[1], 0);
+ atomic_init(&x[2], 0);
+ init_queue(queue, num_threads);
+ for (i = 0; i < num_threads; i++) {
+ param[i] = i;
+ thrd_create(&threads[i], main_task, ¶m[i]);
+ }
+ for (i = 0; i < num_threads; i++)
+ thrd_join(threads[i]);
+/*
+ for (i = 0; i < num_threads; i++) {
+ in_sum += input[i];
+ out_sum += output[i];
+ }
+ for (i = 0; i < num_threads; i++)
+ printf("input[%d] = %u\n", i, input[i]);
+ for (i = 0; i < num_threads; i++)
+ printf("output[%d] = %u\n", i, output[i]);
+ if (succ1 && succ2)
+ MODEL_ASSERT(in_sum == out_sum);
+ else
+ MODEL_ASSERT (false);
+*/
+ free(param);
+ free(threads);
+ free(queue);
+
+ return 0;
+}
--- /dev/null
+#include <stdlib.h>
+#include <stdio.h>
+#include <threads.h>
+
+#include "queue.h"
+#include "model-assert.h"
+
+static queue_t *queue;
+static thrd_t *threads;
+static unsigned int *input;
+static unsigned int *output;
+static int num_threads;
+
+int get_thread_num()
+{
+ thrd_t curr = thrd_current();
+ int i;
+ for (i = 0; i < num_threads; i++)
+ if (curr.priv == threads[i].priv)
+ return i;
+ MODEL_ASSERT(0);
+ return -1;
+}
+
+bool succ1, succ2;
+atomic_int x[3];
+unsigned int idx1, idx2;
+unsigned int reclaimNode;
+
+static int procs = 2;
+static void main_task(void *param)
+{
+ unsigned int val;
+ int pid = *((int *)param);
+ if (pid % procs == 0) {
+ //atomic_store_explicit(&x[0], 1, memory_order_relaxed);
+ enqueue(queue, 1, 0);
+ printf("T2 enqueue %d\n", 1);
+ succ1 = dequeue(queue, &idx1, &reclaimNode);
+ if (succ1)
+ printf("T2 dequeue %d\n", idx1);
+ else
+ printf("T2 dequeue NULL\n");
+ } else if (pid % procs == 1) {
+ enqueue(queue, 2, 0);
+ printf("T3 enqueue %d\n", 2);
+ succ2 = dequeue(queue, &idx2, &reclaimNode);
+ if (succ2)
+ printf("T3 dequeue %d\n", idx2);
+ else
+ printf("T2 dequeue NULL\n");
+ }
+}
+
+int user_main(int argc, char **argv)
+{
+ int i;
+ int *param;
+ unsigned int in_sum = 0, out_sum = 0;
+
+ /** @Entry */
+ queue = (queue_t*) calloc(1, sizeof(*queue));
+ MODEL_ASSERT(queue);
+
+ num_threads = procs;
+ threads = (thrd_t*) malloc(num_threads * sizeof(thrd_t));
+ param = (int*) malloc(num_threads * sizeof(*param));
+ input = (unsigned int *) calloc(num_threads, sizeof(*input));
+ output = (unsigned int *) calloc(num_threads, sizeof(*output));
+
+ atomic_init(&x[0], 0);
+ atomic_init(&x[1], 0);
+ atomic_init(&x[2], 0);
+ init_queue(queue, num_threads);
+ for (i = 0; i < num_threads; i++) {
+ param[i] = i;
+ thrd_create(&threads[i], main_task, ¶m[i]);
+ }
+ for (i = 0; i < num_threads; i++)
+ thrd_join(threads[i]);
+/*
+ for (i = 0; i < num_threads; i++) {
+ in_sum += input[i];
+ out_sum += output[i];
+ }
+ for (i = 0; i < num_threads; i++)
+ printf("input[%d] = %u\n", i, input[i]);
+ for (i = 0; i < num_threads; i++)
+ printf("output[%d] = %u\n", i, output[i]);
+ if (succ1 && succ2)
+ MODEL_ASSERT(in_sum == out_sum);
+ else
+ MODEL_ASSERT (false);
+*/
+ free(param);
+ free(threads);
+ free(queue);
+
+ return 0;
+}
--- /dev/null
+#include <stdlib.h>
+#include <stdio.h>
+#include <threads.h>
+
+#include "queue.h"
+#include "model-assert.h"
+
+static queue_t *queue;
+static thrd_t *threads;
+static unsigned int *input;
+static unsigned int *output;
+static int num_threads;
+
+int get_thread_num()
+{
+ thrd_t curr = thrd_current();
+ int i;
+ for (i = 0; i < num_threads; i++)
+ if (curr.priv == threads[i].priv)
+ return i;
+ //MODEL_ASSERT(0);
+ return -1;
+}
+
+bool succ1, succ2;
+atomic_int x[3];
+unsigned int idx1, idx2, idx3;
+unsigned int reclaimNode;
+
+static int procs = 2;
+static void main_task(void *param)
+{
+ unsigned int val;
+ int pid = *((int *)param);
+ if (pid % procs == 0) {
+ enqueue(queue, 1, 0);
+ succ1 = dequeue(queue, &idx1, &reclaimNode);
+ enqueue(queue, 2, 0);
+ } else if (pid % procs == 1) {
+ enqueue(queue, 2, 2);
+ succ2 = dequeue(queue, &idx2, &reclaimNode);
+ }
+}
+
+int user_main(int argc, char **argv)
+{
+ int i;
+ int *param;
+ unsigned int in_sum = 0, out_sum = 0;
+
+ /** @Entry */
+ queue = (queue_t*) calloc(1, sizeof(*queue));
+ MODEL_ASSERT(queue);
+
+ num_threads = procs;
+ threads = (thrd_t*) malloc(num_threads * sizeof(thrd_t));
+ param = (int*) malloc(num_threads * sizeof(*param));
+ input = (unsigned int *) calloc(num_threads, sizeof(*input));
+ output = (unsigned int *) calloc(num_threads, sizeof(*output));
+
+ atomic_init(&x[0], 0);
+ atomic_init(&x[1], 0);
+ atomic_init(&x[2], 0);
+ init_queue(queue, num_threads);
+ for (i = 0; i < num_threads; i++) {
+ param[i] = i;
+ thrd_create(&threads[i], main_task, ¶m[i]);
+ }
+ for (i = 0; i < num_threads; i++)
+ thrd_join(threads[i]);
+/*
+ for (i = 0; i < num_threads; i++) {
+ in_sum += input[i];
+ out_sum += output[i];
+ }
+ for (i = 0; i < num_threads; i++)
+ printf("input[%d] = %u\n", i, input[i]);
+ for (i = 0; i < num_threads; i++)
+ printf("output[%d] = %u\n", i, output[i]);
+ if (succ1 && succ2)
+ MODEL_ASSERT(in_sum == out_sum);
+ else
+ MODEL_ASSERT (false);
+*/
+ free(param);
+ free(threads);
+ free(queue);
+
+ return 0;
+}
-/main
+main
+testcase1
+testcase2
+testcase3
+testcase4
+*.dSYM/
+*.o
+.*.d
include ../benchmarks.mk
-TESTNAME = main
+BENCH := queue
-HEADERS = my_queue.h
-OBJECTS = main.o my_queue.o
+BENCH_BINARY := $(BENCH).o
-all: $(TESTNAME)
+TESTS := main testcase1 testcase2 testcase3 testcase4
-$(TESTNAME): $(HEADERS) $(OBJECTS)
- $(CC) -o $@ $(OBJECTS) $(CFLAGS) $(LDFLAGS)
+all: $(TESTS)
+ ../generate.sh $(notdir $(shell pwd))
-%.o: %.c
- $(CC) -c -o $@ $< $(CFLAGS)
+%.o : %.c
+ $(CC) -c -fPIC -MMD -MF .$@.d -o $@ $< $(CFLAGS) $(LDFLAGS)
+
+$(TESTS): % : %.o $(BENCH_BINARY)
+ $(CC) -o $@ $^ $(CFLAGS) $(LDFLAGS)
+
+-include .*.d
clean:
- rm -f $(TESTNAME) *.o
+ rm -rf $(TESTS) *.o .*.d *.dSYM
+
+.PHONY: clean all
#include <stdio.h>
#include <threads.h>
-#include "my_queue.h"
+#include "queue.h"
#include "model-assert.h"
-static int procs = 2;
static queue_t *queue;
static thrd_t *threads;
static unsigned int *input;
}
bool succ1, succ2;
+atomic_int x[3];
+unsigned int idx1, idx2;
+unsigned int reclaimNode;
+static int procs = 2;
static void main_task(void *param)
{
unsigned int val;
int pid = *((int *)param);
- if (!pid) {
- input[0] = 17;
- enqueue(queue, input[0]);
- succ1 = dequeue(queue, &output[0]);
- //printf("Dequeue: %d\n", output[0]);
+ if (pid % 2 == 0) {
+ enqueue(queue, 0, 0);
+ succ1 = dequeue(queue, &idx1, &reclaimNode);
} else {
- input[1] = 37;
- enqueue(queue, input[1]);
- succ2 = dequeue(queue, &output[1]);
+ enqueue(queue, 1, 0);
+ succ2 = dequeue(queue, &idx2, &reclaimNode);
}
}
int *param;
unsigned int in_sum = 0, out_sum = 0;
- queue = calloc(1, sizeof(*queue));
+ /** @Entry */
+ queue = (queue_t*) calloc(1, sizeof(*queue));
MODEL_ASSERT(queue);
num_threads = procs;
- threads = malloc(num_threads * sizeof(thrd_t));
- param = malloc(num_threads * sizeof(*param));
- input = calloc(num_threads, sizeof(*input));
- output = calloc(num_threads, sizeof(*output));
+ threads = (thrd_t*) malloc(num_threads * sizeof(thrd_t));
+ param = (int*) malloc(num_threads * sizeof(*param));
+ input = (unsigned int *) calloc(num_threads, sizeof(*input));
+ output = (unsigned int *) calloc(num_threads, sizeof(*output));
init_queue(queue, num_threads);
for (i = 0; i < num_threads; i++) {
}
for (i = 0; i < num_threads; i++)
thrd_join(threads[i]);
-
+/*
for (i = 0; i < num_threads; i++) {
in_sum += input[i];
out_sum += output[i];
MODEL_ASSERT(in_sum == out_sum);
else
MODEL_ASSERT (false);
-
+*/
free(param);
free(threads);
free(queue);
+++ /dev/null
-#include <threads.h>
-#include <stdlib.h>
-#include "librace.h"
-#include "model-assert.h"
-
-#include "my_queue.h"
-
-#define relaxed memory_order_relaxed
-#define release memory_order_release
-#define acquire memory_order_acquire
-
-#define MAX_FREELIST 4 /* Each thread can own up to MAX_FREELIST free nodes */
-#define INITIAL_FREE 2 /* Each thread starts with INITIAL_FREE free nodes */
-
-#define POISON_IDX 0x666
-
-static unsigned int (*free_lists)[MAX_FREELIST];
-
-/* Search this thread's free list for a "new" node */
-static unsigned int new_node()
-{
- int i;
- int t = get_thread_num();
- for (i = 0; i < MAX_FREELIST; i++) {
- unsigned int node = load_32(&free_lists[t][i]);
- if (node) {
- store_32(&free_lists[t][i], 0);
- return node;
- }
- }
- /* free_list is empty? */
- MODEL_ASSERT(0);
- return 0;
-}
-
-/* Place this node index back on this thread's free list */
-static void reclaim(unsigned int node)
-{
- int i;
- int t = get_thread_num();
-
- /* Don't reclaim NULL node */
- MODEL_ASSERT(node);
-
- for (i = 0; i < MAX_FREELIST; i++) {
- /* Should never race with our own thread here */
- unsigned int idx = load_32(&free_lists[t][i]);
-
- /* Found empty spot in free list */
- if (idx == 0) {
- store_32(&free_lists[t][i], node);
- return;
- }
- }
- /* free list is full? */
- MODEL_ASSERT(0);
-}
-
-void init_queue(queue_t *q, int num_threads)
-{
- int i, j;
-
- /* Initialize each thread's free list with INITIAL_FREE pointers */
- /* The actual nodes are initialized with poison indexes */
- free_lists = malloc(num_threads * sizeof(*free_lists));
- for (i = 0; i < num_threads; i++) {
- for (j = 0; j < INITIAL_FREE; j++) {
- free_lists[i][j] = 2 + i * MAX_FREELIST + j;
- atomic_init(&q->nodes[free_lists[i][j]].next, MAKE_POINTER(POISON_IDX, 0));
- }
- }
-
- /* initialize queue */
- atomic_init(&q->head, MAKE_POINTER(1, 0));
- atomic_init(&q->tail, MAKE_POINTER(1, 0));
- atomic_init(&q->nodes[1].next, MAKE_POINTER(0, 0));
-}
-
-void enqueue(queue_t *q, unsigned int val)
-{
- int success = 0;
- unsigned int node;
- pointer tail;
- pointer next;
- pointer tmp;
-
- node = new_node();
- store_32(&q->nodes[node].value, val);
- tmp = atomic_load_explicit(&q->nodes[node].next, relaxed);
- set_ptr(&tmp, 0); // NULL
- atomic_store_explicit(&q->nodes[node].next, tmp, relaxed);
-
- while (!success) {
- tail = atomic_load_explicit(&q->tail, acquire);
- next = atomic_load_explicit(&q->nodes[get_ptr(tail)].next, acquire);
- if (tail == atomic_load_explicit(&q->tail, relaxed)) {
-
- /* Check for uninitialized 'next' */
- MODEL_ASSERT(get_ptr(next) != POISON_IDX);
-
- if (get_ptr(next) == 0) { // == NULL
- pointer value = MAKE_POINTER(node, get_count(next) + 1);
- success = atomic_compare_exchange_strong_explicit(&q->nodes[get_ptr(tail)].next,
- &next, value, release, release);
- }
- if (!success) {
- unsigned int ptr = get_ptr(atomic_load_explicit(&q->nodes[get_ptr(tail)].next, acquire));
- pointer value = MAKE_POINTER(ptr,
- get_count(tail) + 1);
- atomic_compare_exchange_strong_explicit(&q->tail,
- &tail, value,
- release, release);
- thrd_yield();
- }
- }
- }
- atomic_compare_exchange_strong_explicit(&q->tail,
- &tail,
- MAKE_POINTER(node, get_count(tail) + 1),
- release, release);
-}
-
-bool dequeue(queue_t *q, unsigned int *retVal)
-{
- int success = 0;
- pointer head;
- pointer tail;
- pointer next;
-
- while (!success) {
- head = atomic_load_explicit(&q->head, acquire);
- tail = atomic_load_explicit(&q->tail, relaxed);
- next = atomic_load_explicit(&q->nodes[get_ptr(head)].next, acquire);
- if (atomic_load_explicit(&q->head, relaxed) == head) {
- if (get_ptr(head) == get_ptr(tail)) {
-
- /* Check for uninitialized 'next' */
- MODEL_ASSERT(get_ptr(next) != POISON_IDX);
-
- if (get_ptr(next) == 0) { // NULL
- return false; // NULL
- }
- atomic_compare_exchange_strong_explicit(&q->tail,
- &tail,
- MAKE_POINTER(get_ptr(next), get_count(tail) + 1),
- release, release);
- thrd_yield();
- } else {
- *retVal = load_32(&q->nodes[get_ptr(next)].value);
- success = atomic_compare_exchange_strong_explicit(&q->head,
- &head,
- MAKE_POINTER(get_ptr(next), get_count(head) + 1),
- release, release);
- if (!success)
- thrd_yield();
- }
- }
- }
- reclaim(get_ptr(head));
- return true;
-}
+++ /dev/null
-#include <stdatomic.h>
-
-#define MAX_NODES 0xf
-
-typedef unsigned long long pointer;
-typedef atomic_ullong pointer_t;
-
-#define MAKE_POINTER(ptr, count) ((((pointer)count) << 32) | ptr)
-#define PTR_MASK 0xffffffffLL
-#define COUNT_MASK (0xffffffffLL << 32)
-
-static inline void set_count(pointer *p, unsigned int val) { *p = (*p & ~COUNT_MASK) | ((pointer)val << 32); }
-static inline void set_ptr(pointer *p, unsigned int val) { *p = (*p & ~PTR_MASK) | val; }
-static inline unsigned int get_count(pointer p) { return (p & COUNT_MASK) >> 32; }
-static inline unsigned int get_ptr(pointer p) { return p & PTR_MASK; }
-
-typedef struct node {
- unsigned int value;
- pointer_t next;
-} node_t;
-
-typedef struct {
- pointer_t head;
- pointer_t tail;
- node_t nodes[MAX_NODES + 1];
-} queue_t;
-
-void init_queue(queue_t *q, int num_threads);
-void enqueue(queue_t *q, unsigned int val);
-bool dequeue(queue_t *q, unsigned int *retVal);
-int get_thread_num();
--- /dev/null
+#include <threads.h>
+#include <stdlib.h>
+#include "librace.h"
+#include "model-assert.h"
+
+#include "queue.h"
+
+#define relaxed memory_order_relaxed
+#define release memory_order_release
+#define acquire memory_order_acquire
+
+#define MAX_FREELIST 4 /* Each thread can own up to MAX_FREELIST free nodes */
+#define INITIAL_FREE 2 /* Each thread starts with INITIAL_FREE free nodes */
+
+#define POISON_IDX 0x666
+
+static unsigned int (*free_lists)[MAX_FREELIST];
+
+/* Search this thread's free list for a "new" node */
+static unsigned int new_node()
+{
+ int i;
+ int t = get_thread_num();
+ for (i = 0; i < MAX_FREELIST; i++) {
+ //unsigned int node = load_32(&free_lists[t][i]);
+ unsigned int node = free_lists[t][i];
+ if (node) {
+ //store_32(&free_lists[t][i], 0);
+ free_lists[t][i] = 0;
+ return node;
+ }
+ }
+ /* free_list is empty? */
+ MODEL_ASSERT(0);
+ return 0;
+}
+
+/* Simulate the fact that when a node got recycled, it will get assigned to the
+ * same queue or for other usage */
+void simulateRecycledNodeUpdate(queue_t *q, unsigned int node) {
+ atomic_store_explicit(&q->nodes[node].next, -1, memory_order_release);
+}
+
+
+/* Place this node index back on this thread's free list */
+static void reclaim(unsigned int node)
+{
+ int i;
+ int t = get_thread_num();
+
+ /* Don't reclaim NULL node */
+ //MODEL_ASSERT(node);
+
+ for (i = 0; i < MAX_FREELIST; i++) {
+ /* Should never race with our own thread here */
+ //unsigned int idx = load_32(&free_lists[t][i]);
+ unsigned int idx = free_lists[t][i];
+
+ /* Found empty spot in free list */
+ if (idx == 0) {
+ //store_32(&free_lists[t][i], node);
+ free_lists[t][i] = node;
+ return;
+ }
+ }
+ /* free list is full? */
+ //MODEL_ASSERT(0);
+}
+
+void init_queue(queue_t *q, int num_threads)
+{
+ int i, j;
+
+ /* Initialize each thread's free list with INITIAL_FREE pointers */
+ /* The actual nodes are initialized with poison indexes */
+ free_lists = ( unsigned int (*)[MAX_FREELIST] ) malloc(num_threads * sizeof(*free_lists));
+ for (i = 0; i < num_threads; i++) {
+ for (j = 0; j < INITIAL_FREE; j++) {
+ free_lists[i][j] = 2 + i * MAX_FREELIST + j;
+ atomic_init(&q->nodes[free_lists[i][j]].next, MAKE_POINTER(POISON_IDX, 0));
+ }
+ }
+
+ /* initialize queue */
+ atomic_init(&q->head, MAKE_POINTER(1, 0));
+ atomic_init(&q->tail, MAKE_POINTER(1, 0));
+ atomic_init(&q->nodes[1].next, MAKE_POINTER(0, 0));
+}
+
+/** @DeclareState: IntList *q;
+@Initial: q = new IntList;
+@Print:
+ model_print("\tSTATE(q): ");
+ printContainer(q);
+ model_print("\n"); */
+
+/** @Transition: STATE(q)->push_back(val);
+@Print: model_print("\tENQ #%d: val=%d\n", ID, val); */
+void enqueue(queue_t *q, unsigned int val, int n)
+{
+ int success = 0;
+ unsigned int node;
+ pointer tail;
+ pointer next;
+ pointer tmp;
+
+ node = new_node();
+ //store_32(&q->nodes[node].value, val);
+ q->nodes[node].value = val;
+ tmp = atomic_load_explicit(&q->nodes[node].next, relaxed);
+ set_ptr(&tmp, 0); // NULL
+ // This is a found bug in AutoMO, and testcase4 can reveal this known bug
+ /********** Detected KNOWN BUG (testcase4) **********/
+ atomic_store_explicit(&q->nodes[node].next, tmp, release);
+
+ while (!success) {
+ /********** Detected UL **********/
+ tail = atomic_load_explicit(&q->tail, acquire);
+ /********** Detected Correctness (testcase4) **********/
+ next = atomic_load_explicit(&q->nodes[get_ptr(tail)].next, acquire);
+ if (tail == atomic_load_explicit(&q->tail, relaxed)) {
+
+ /* Check for uninitialized 'next' */
+ //MODEL_ASSERT(get_ptr(next) != POISON_IDX);
+
+ if (get_ptr(next) == 0) { // == NULL
+ pointer value = MAKE_POINTER(node, get_count(next) + 1);
+ /********** Detected Correctness (testcase1) **********/
+ success = atomic_compare_exchange_strong_explicit(&q->nodes[get_ptr(tail)].next,
+ &next, value, release, release);
+ /** @OPClearDefine: success */
+ }
+ if (!success) {
+ /********** Detected UL **********/
+ unsigned int ptr = get_ptr(atomic_load_explicit(&q->nodes[get_ptr(tail)].next, acquire));
+ pointer value = MAKE_POINTER(ptr,
+ get_count(tail) + 1);
+ /********** Detected Correctness (testcase2) **********/
+ atomic_compare_exchange_strong_explicit(&q->tail,
+ &tail, value,
+ release, release);
+ thrd_yield();
+ }
+ }
+ }
+ /********** Detected Corrctness (testcase1) **********/
+ atomic_compare_exchange_strong_explicit(&q->tail,
+ &tail,
+ MAKE_POINTER(node, get_count(tail) + 1),
+ release, release);
+}
+
+/** @Transition: S_RET = STATE(q)->empty() ? 0 : STATE(q)->front();
+if (S_RET) STATE(q)->pop_front();
+@JustifyingPostcondition: if (!C_RET)
+ return S_RET == C_RET;
+@PostCondition: return C_RET ? *retVal == S_RET : true;
+@Print: model_print("\tDEQ #%d: C_RET=%d && *retVal=%d && S_RET=%d\n", ID,
+ C_RET, *retVal, S_RET);
+*/
+int dequeue(queue_t *q, unsigned int *retVal, unsigned int *reclaimNode)
+{
+ int success = 0;
+ pointer head;
+ pointer tail;
+ pointer next;
+
+ while (!success) {
+ /********** Dectected Correctness (testcase3) **********/
+ head = atomic_load_explicit(&q->head, acquire);
+ /********** Detected KNOWN BUG (testcase2) **********/
+ tail = atomic_load_explicit(&q->tail, acquire);
+ /********** Detected Correctness (testcase1) **********/
+ next = atomic_load_explicit(&q->nodes[get_ptr(head)].next, acquire);
+ /** @OPClearDefine: true */
+ if (atomic_load_explicit(&q->head, relaxed) == head) {
+ if (get_ptr(head) == get_ptr(tail)) {
+
+ /* Check for uninitialized 'next' */
+ MODEL_ASSERT(get_ptr(next) != POISON_IDX);
+
+ if (get_ptr(next) == 0) { // NULL
+ return false; // NULL
+ }
+ /********** Detected UL **********/
+ atomic_compare_exchange_strong_explicit(&q->tail,
+ &tail,
+ MAKE_POINTER(get_ptr(next), get_count(tail) + 1),
+ release, release);
+ thrd_yield();
+ } else {
+ //*retVal = load_32(&q->nodes[get_ptr(next)].value);
+ *retVal = q->nodes[get_ptr(next)].value;
+ /********** Detected Correctness (testcase3) **********/
+ success = atomic_compare_exchange_strong_explicit(&q->head,
+ &head,
+ MAKE_POINTER(get_ptr(next), get_count(head) + 1),
+ release, release);
+ if (!success)
+ thrd_yield();
+ }
+ }
+ }
+ *reclaimNode = get_ptr(head);
+ reclaim(get_ptr(head));
+ return true;
+}
--- /dev/null
+#ifndef _QUEUE_H
+#define _QUEUE_H
+
+#include <stdatomic.h>
+
+#define MAX_NODES 0xf
+
+typedef unsigned long long pointer;
+typedef atomic_ullong pointer_t;
+
+#define MAKE_POINTER(ptr, count) ((((pointer)count) << 32) | ptr)
+#define PTR_MASK 0xffffffffLL
+#define COUNT_MASK (0xffffffffLL << 32)
+
+static inline void set_count(pointer *p, unsigned int val) { *p = (*p & ~COUNT_MASK) | ((pointer)val << 32); }
+static inline void set_ptr(pointer *p, unsigned int val) { *p = (*p & ~PTR_MASK) | val; }
+static inline unsigned int get_count(pointer p) { return (p & COUNT_MASK) >> 32; }
+static inline unsigned int get_ptr(pointer p) { return p & PTR_MASK; }
+
+typedef struct node {
+ unsigned int value;
+ pointer_t next;
+} node_t;
+
+typedef struct {
+ pointer_t head;
+ pointer_t tail;
+ node_t nodes[MAX_NODES + 2];
+} queue_t;
+
+void init_queue(queue_t *q, int num_threads);
+void enqueue(queue_t *q, unsigned int val, int n);
+int dequeue(queue_t *q, unsigned int *retVal, unsigned int *reclaimedNode);
+
+int get_thread_num();
+
+#endif
--- /dev/null
+#include <stdlib.h>
+#include <stdio.h>
+#include <threads.h>
+
+#include "queue.h"
+#include "model-assert.h"
+
+static queue_t *queue;
+static thrd_t *threads;
+static unsigned int *input;
+static unsigned int *output;
+static int num_threads;
+
+int get_thread_num()
+{
+ thrd_t curr = thrd_current();
+ int i;
+ for (i = 0; i < num_threads; i++)
+ if (curr.priv == threads[i].priv)
+ return i;
+ MODEL_ASSERT(0);
+ return -1;
+}
+
+bool succ1, succ2;
+atomic_int x[3];
+unsigned int idx1, idx2;
+unsigned int reclaimNode;
+
+static int procs = 2;
+static void main_task(void *param)
+{
+ unsigned int val;
+ int pid = *((int *)param);
+ if (pid % procs == 0) {
+ //atomic_store_explicit(&x[0], 1, memory_order_relaxed);
+ enqueue(queue, 0, 0);
+ } else if (pid % procs == 1) {
+ //atomic_store_explicit(&x[1], 1, memory_order_relaxed);
+ succ1 = dequeue(queue, &idx1, &reclaimNode);
+ if (succ1) {
+ //atomic_load_explicit(&x[idx1], memory_order_relaxed);
+ }
+ }
+}
+
+int user_main(int argc, char **argv)
+{
+ int i;
+ int *param;
+ unsigned int in_sum = 0, out_sum = 0;
+
+ /** @Entry */
+ queue = (queue_t*) calloc(1, sizeof(*queue));
+ MODEL_ASSERT(queue);
+
+ num_threads = procs;
+ threads = (thrd_t*) malloc(num_threads * sizeof(thrd_t));
+ param = (int*) malloc(num_threads * sizeof(*param));
+ input = (unsigned int *) calloc(num_threads, sizeof(*input));
+ output = (unsigned int *) calloc(num_threads, sizeof(*output));
+
+ atomic_init(&x[0], 0);
+ atomic_init(&x[1], 0);
+ atomic_init(&x[2], 0);
+ init_queue(queue, num_threads);
+ for (i = 0; i < num_threads; i++) {
+ param[i] = i;
+ thrd_create(&threads[i], main_task, ¶m[i]);
+ }
+ for (i = 0; i < num_threads; i++)
+ thrd_join(threads[i]);
+/*
+ for (i = 0; i < num_threads; i++) {
+ in_sum += input[i];
+ out_sum += output[i];
+ }
+ for (i = 0; i < num_threads; i++)
+ printf("input[%d] = %u\n", i, input[i]);
+ for (i = 0; i < num_threads; i++)
+ printf("output[%d] = %u\n", i, output[i]);
+ if (succ1 && succ2)
+ MODEL_ASSERT(in_sum == out_sum);
+ else
+ MODEL_ASSERT (false);
+*/
+ free(param);
+ free(threads);
+ free(queue);
+
+ return 0;
+}
--- /dev/null
+#include <stdlib.h>
+#include <stdio.h>
+#include <threads.h>
+
+#include "queue.h"
+#include "model-assert.h"
+
+static queue_t *queue;
+static thrd_t *threads;
+static unsigned int *input;
+static unsigned int *output;
+static int num_threads;
+
+int get_thread_num()
+{
+ thrd_t curr = thrd_current();
+ int i;
+ for (i = 0; i < num_threads; i++)
+ if (curr.priv == threads[i].priv)
+ return i;
+ MODEL_ASSERT(0);
+ return -1;
+}
+
+bool succ1, succ2;
+atomic_int x[3];
+unsigned int idx1, idx2;
+unsigned int reclaimNode;
+
+static int procs = 4;
+static void main_task(void *param)
+{
+ unsigned int val;
+ int pid = *((int *)param);
+ if (pid % 4 == 0) {
+ //atomic_store_explicit(&x[0], 1, memory_order_relaxed);
+ enqueue(queue, 0, 0);
+ } else if (pid % 4 == 1) {
+ //atomic_store_explicit(&x[1], 1, memory_order_relaxed);
+ enqueue(queue, 1, 0);
+ } else if (pid % 4 == 2) {
+ succ1 = dequeue(queue, &idx1, &reclaimNode);
+ if (succ1) {
+ //atomic_load_explicit(&x[idx1], memory_order_relaxed);
+ }
+ } else if (pid % 4 == 3) {
+ /*
+ succ2 = dequeue(queue, &idx2, &reclaimNode);
+ if (succ2) {
+ atomic_load_explicit(&x[idx2], memory_order_relaxed);
+ }
+ */
+ }
+}
+
+int user_main(int argc, char **argv)
+{
+ int i;
+ int *param;
+ unsigned int in_sum = 0, out_sum = 0;
+
+ /** @Entry */
+ queue = (queue_t*) calloc(1, sizeof(*queue));
+ MODEL_ASSERT(queue);
+
+ num_threads = procs;
+ threads = (thrd_t*) malloc(num_threads * sizeof(thrd_t));
+ param = (int*) malloc(num_threads * sizeof(*param));
+ input = (unsigned int *) calloc(num_threads, sizeof(*input));
+ output = (unsigned int *) calloc(num_threads, sizeof(*output));
+
+ atomic_init(&x[0], 0);
+ atomic_init(&x[1], 0);
+ atomic_init(&x[2], 0);
+ init_queue(queue, num_threads);
+ for (i = 0; i < num_threads; i++) {
+ param[i] = i;
+ thrd_create(&threads[i], main_task, ¶m[i]);
+ }
+ for (i = 0; i < num_threads; i++)
+ thrd_join(threads[i]);
+/*
+ for (i = 0; i < num_threads; i++) {
+ in_sum += input[i];
+ out_sum += output[i];
+ }
+ for (i = 0; i < num_threads; i++)
+ printf("input[%d] = %u\n", i, input[i]);
+ for (i = 0; i < num_threads; i++)
+ printf("output[%d] = %u\n", i, output[i]);
+ if (succ1 && succ2)
+ MODEL_ASSERT(in_sum == out_sum);
+ else
+ MODEL_ASSERT (false);
+*/
+ free(param);
+ free(threads);
+ free(queue);
+
+ return 0;
+}
--- /dev/null
+#include <stdlib.h>
+#include <stdio.h>
+#include <threads.h>
+
+#include "queue.h"
+#include "model-assert.h"
+
+static queue_t *queue;
+static thrd_t *threads;
+static unsigned int *input;
+static unsigned int *output;
+static int num_threads;
+
+int get_thread_num()
+{
+ thrd_t curr = thrd_current();
+ int i;
+ for (i = 0; i < num_threads; i++)
+ if (curr.priv == threads[i].priv)
+ return i;
+ MODEL_ASSERT(0);
+ return -1;
+}
+
+bool succ1, succ2;
+atomic_int x[3];
+unsigned int idx1, idx2;
+unsigned int reclaimNode;
+
+static int procs = 2;
+static void main_task(void *param)
+{
+ unsigned int val;
+ int pid = *((int *)param);
+ if (pid % procs == 0) {
+ //atomic_store_explicit(&x[0], 1, memory_order_relaxed);
+ enqueue(queue, 1, 0);
+ printf("T2 enqueue %d\n", 1);
+ succ1 = dequeue(queue, &idx1, &reclaimNode);
+ if (succ1)
+ printf("T2 dequeue %d\n", idx1);
+ else
+ printf("T2 dequeue NULL\n");
+ } else if (pid % procs == 1) {
+ enqueue(queue, 2, 0);
+ printf("T3 enqueue %d\n", 2);
+ succ2 = dequeue(queue, &idx2, &reclaimNode);
+ if (succ2)
+ printf("T3 dequeue %d\n", idx2);
+ else
+ printf("T2 dequeue NULL\n");
+ }
+}
+
+int user_main(int argc, char **argv)
+{
+ int i;
+ int *param;
+ unsigned int in_sum = 0, out_sum = 0;
+
+ /** @Entry */
+ queue = (queue_t*) calloc(1, sizeof(*queue));
+ MODEL_ASSERT(queue);
+
+ num_threads = procs;
+ threads = (thrd_t*) malloc(num_threads * sizeof(thrd_t));
+ param = (int*) malloc(num_threads * sizeof(*param));
+ input = (unsigned int *) calloc(num_threads, sizeof(*input));
+ output = (unsigned int *) calloc(num_threads, sizeof(*output));
+
+ atomic_init(&x[0], 0);
+ atomic_init(&x[1], 0);
+ atomic_init(&x[2], 0);
+ init_queue(queue, num_threads);
+ for (i = 0; i < num_threads; i++) {
+ param[i] = i;
+ thrd_create(&threads[i], main_task, ¶m[i]);
+ }
+ for (i = 0; i < num_threads; i++)
+ thrd_join(threads[i]);
+/*
+ for (i = 0; i < num_threads; i++) {
+ in_sum += input[i];
+ out_sum += output[i];
+ }
+ for (i = 0; i < num_threads; i++)
+ printf("input[%d] = %u\n", i, input[i]);
+ for (i = 0; i < num_threads; i++)
+ printf("output[%d] = %u\n", i, output[i]);
+ if (succ1 && succ2)
+ MODEL_ASSERT(in_sum == out_sum);
+ else
+ MODEL_ASSERT (false);
+*/
+ free(param);
+ free(threads);
+ free(queue);
+
+ return 0;
+}
--- /dev/null
+#include <stdlib.h>
+#include <stdio.h>
+#include <threads.h>
+
+#include "queue.h"
+#include "model-assert.h"
+
+static queue_t *queue;
+static thrd_t *threads;
+static unsigned int *input;
+static unsigned int *output;
+static int num_threads;
+
+int get_thread_num()
+{
+ thrd_t curr = thrd_current();
+ int i;
+ for (i = 0; i < num_threads; i++)
+ if (curr.priv == threads[i].priv)
+ return i;
+ //MODEL_ASSERT(0);
+ return -1;
+}
+
+bool succ1, succ2;
+atomic_int x[3];
+unsigned int idx1, idx2, idx3;
+unsigned int reclaimNode;
+
+static int procs = 2;
+static void main_task(void *param)
+{
+ unsigned int val;
+ int pid = *((int *)param);
+ if (pid % procs == 0) {
+ enqueue(queue, 1, 0);
+ succ1 = dequeue(queue, &idx1, &reclaimNode);
+ enqueue(queue, 2, 0);
+ } else if (pid % procs == 1) {
+ enqueue(queue, 2, 2);
+ succ2 = dequeue(queue, &idx2, &reclaimNode);
+ }
+}
+
+int user_main(int argc, char **argv)
+{
+ int i;
+ int *param;
+ unsigned int in_sum = 0, out_sum = 0;
+
+ /** @Entry */
+ queue = (queue_t*) calloc(1, sizeof(*queue));
+ MODEL_ASSERT(queue);
+
+ num_threads = procs;
+ threads = (thrd_t*) malloc(num_threads * sizeof(thrd_t));
+ param = (int*) malloc(num_threads * sizeof(*param));
+ input = (unsigned int *) calloc(num_threads, sizeof(*input));
+ output = (unsigned int *) calloc(num_threads, sizeof(*output));
+
+ atomic_init(&x[0], 0);
+ atomic_init(&x[1], 0);
+ atomic_init(&x[2], 0);
+ init_queue(queue, num_threads);
+ for (i = 0; i < num_threads; i++) {
+ param[i] = i;
+ thrd_create(&threads[i], main_task, ¶m[i]);
+ }
+ for (i = 0; i < num_threads; i++)
+ thrd_join(threads[i]);
+/*
+ for (i = 0; i < num_threads; i++) {
+ in_sum += input[i];
+ out_sum += output[i];
+ }
+ for (i = 0; i < num_threads; i++)
+ printf("input[%d] = %u\n", i, input[i]);
+ for (i = 0; i < num_threads; i++)
+ printf("output[%d] = %u\n", i, output[i]);
+ if (succ1 && succ2)
+ MODEL_ASSERT(in_sum == out_sum);
+ else
+ MODEL_ASSERT (false);
+*/
+ free(param);
+ free(threads);
+ free(queue);
+
+ return 0;
+}
--- /dev/null
+include ../benchmarks.mk
+
+BENCH := rcu
+
+BENCH_BINARY := $(BENCH).o
+
+TESTS := main testcase
+
+all: $(TESTS)
+ ../generate.sh $(notdir $(shell pwd))
+
+%.o : %.cc
+ $(CXX) -c -fPIC -MMD -MF .$@.d -o $@ $< $(CXXFLAGS) $(LDFLAGS)
+
+$(TESTS): % : %.o $(BENCH_BINARY)
+ $(CXX) -o $@ $^ $(CXXFLAGS) $(LDFLAGS)
+
+-include .*.d
+
+clean:
+ rm -rf $(TESTS) *.o .*.d *.dSYM
+
+.PHONY: clean all
--- /dev/null
+#include <threads.h>
+#include "rcu.h"
+
+void threadA(void *arg) {
+ write(1, 0);
+}
+
+void threadB(void *arg) {
+ write(0, 2);
+}
+
+void threadC(void *arg) {
+ write(2, 2);
+}
+
+void threadD(void *arg) {
+ int *d1 = new int;
+ int *d2 = new int;
+ read(d1, d2);
+ printf("ThreadD: d1=%d, d2=%d\n", *d1, *d2);
+}
+
+int user_main(int argc, char **argv) {
+ thrd_t t1, t2, t3, t4;
+ /** @Entry */
+ Data *dataInit = new Data;
+ dataInit->data1 = 0;
+ dataInit->data2 = 0;
+ atomic_init(&dataPtr, dataInit);
+
+ thrd_create(&t1, threadA, NULL);
+ thrd_create(&t2, threadB, NULL);
+ //thrd_create(&t3, threadC, NULL);
+ thrd_create(&t4, threadD, NULL);
+
+ thrd_join(t1);
+ thrd_join(t2);
+ //thrd_join(t3);
+ thrd_join(t4);
+
+ return 0;
+}
--- /dev/null
+#include "rcu.h"
+
+/**
+ This is an example about how to specify the correctness of the
+ read-copy-update synchronization mechanism.
+*/
+
+atomic<Data*> dataPtr;
+
+/** @DeclareState: int data1;
+ int data2; */
+
+/** @JustifyingPrecondition: return STATE(data1) == *data1 && STATE(data2) == *data2; */
+void read(int *data1, int *data2) {
+ /********** Detected Correctness **********/
+ Data *res = dataPtr.load(memory_order_acquire);
+ /** @OPDefine: true */
+ *data1 = res->data1;
+ *data2 = res->data2;
+ //load_32(&res->data1);
+}
+
+static void inc(Data *newPtr, Data *prev, int d1, int d2) {
+ newPtr->data1 = prev->data1 + d1;
+ newPtr->data2 = prev->data2 + d2;
+}
+
+/** @Transition: STATE(data1) += data1;
+ STATE(data2) += data2; */
+void write(int data1, int data2) {
+ bool succ = false;
+ Data *tmp = new Data;
+ do {
+ /********** Detected Correctness **********/
+ Data *prev = dataPtr.load(memory_order_acquire);
+ inc(tmp, prev, data1, data2);
+ /********** Detected Correctness **********/
+ succ = dataPtr.compare_exchange_strong(prev, tmp,
+ memory_order_release, memory_order_relaxed);
+ /** @OPClearDefine: succ */
+ } while (!succ);
+}
--- /dev/null
+#ifndef _RCU_H
+#define _RCU_H
+
+#include <atomic>
+#include <threads.h>
+#include <stdatomic.h>
+#include <stdlib.h>
+#include <stdio.h>
+
+#include "librace.h"
+
+struct Data {
+ /** Declare atomic just to expose them to CDSChecker */
+ int data1;
+ int data2;
+};
+
+
+extern atomic<Data*> dataPtr;
+
+void read(int *data1, int *data2);
+
+void write(int data1, int data2);
+
+#endif
--- /dev/null
+#include <threads.h>
+#include "rcu.h"
+
+void threadA(void *arg) {
+ write(1, 0);
+}
+
+void threadB(void *arg) {
+ write(0, 2);
+}
+
+void threadC(void *arg) {
+ write(2, 2);
+}
+
+void threadD(void *arg) {
+ int *d1 = new int;
+ int *d2 = new int;
+ read(d1, d2);
+ printf("ThreadD: d1=%d, d2=%d\n", *d1, *d2);
+}
+
+int user_main(int argc, char **argv) {
+ thrd_t t1, t2, t3, t4;
+ /** @Entry */
+ Data *dataInit = new Data;
+ dataInit->data1 = 0;
+ dataInit->data2 = 0;
+ atomic_init(&dataPtr, dataInit);
+
+ thrd_create(&t1, threadA, NULL);
+ thrd_create(&t2, threadB, NULL);
+ //thrd_create(&t3, threadC, NULL);
+ thrd_create(&t4, threadD, NULL);
+
+ thrd_join(t1);
+ thrd_join(t2);
+ //thrd_join(t3);
+ thrd_join(t4);
+
+ return 0;
+}
include ../benchmarks.mk
-all: seqlock
+BENCH := seqlock
+BENCH_BINARY := $(BENCH).o
-seqlock: seqlock.c
- $(CXX) -o $@ $< $(CXXFLAGS) $(LDFLAGS)
+TESTS := main testcase1 testcase2
+
+all: $(TESTS)
+ ../generate.sh $(notdir $(shell pwd))
+
+%.o : %.cc
+ $(CXX) -c -fPIC -MMD -MF .$@.d -o $@ $< $(CXXFLAGS) $(LDFLAGS)
+
+$(TESTS): % : %.o $(BENCH_BINARY)
+ $(CXX) -o $@ $^ $(CXXFLAGS) $(LDFLAGS)
+
+-include .*.d
clean:
- rm -f seqlock
+ rm -rf $(TESTS) *.o .*.d *.dSYM
+
+.PHONY: clean all
--- /dev/null
+#include <threads.h>
+#include "seqlock.h"
+
+seqlock *lock;
+
+static void a(void *obj) {
+ write(lock, 1, 2);
+}
+
+static void b(void *obj) {
+ write(lock, 3, 4);
+}
+
+static void c(void *obj) {
+ int *data1 = new int;
+ int *data2 = new int;
+ read(lock, data1, data2);
+}
+
+int user_main(int argc, char **argv) {
+ thrd_t t1, t2, t3;
+ /** @Entry */
+ lock = new seqlock;
+
+ thrd_create(&t1, (thrd_start_t)&a, NULL);
+ //thrd_create(&t2, (thrd_start_t)&b, NULL);
+ thrd_create(&t3, (thrd_start_t)&c, NULL);
+
+ thrd_join(t1);
+ //thrd_join(t2);
+ thrd_join(t3);
+ return 0;
+}
+++ /dev/null
-#include <stdatomic.h>
-#include <threads.h>
-
-typedef struct seqlock {
- // Sequence for reader consistency check
- atomic_int _seq;
- // It needs to be atomic to avoid data races
- atomic_int _data;
-
- seqlock() {
- atomic_init(&_seq, 0);
- atomic_init(&_data, 0);
- }
-
- int read() {
- while (true) {
- int old_seq = _seq.load(memory_order_acquire); // acquire
- if (old_seq % 2 == 1) continue;
-
- int res = _data.load(memory_order_acquire); // acquire
- if (_seq.load(memory_order_relaxed) == old_seq) { // relaxed
- return res;
- }
- }
- }
-
- void write(int new_data) {
- while (true) {
- // This might be a relaxed too
- int old_seq = _seq.load(memory_order_acquire); // acquire
- if (old_seq % 2 == 1)
- continue; // Retry
-
- // Should be relaxed!!!
- if (_seq.compare_exchange_strong(old_seq, old_seq + 1,
- memory_order_relaxed, memory_order_relaxed)) // relaxed
- break;
- }
-
- // Update the data
- _data.store(new_data, memory_order_release); // release
-
- _seq.fetch_add(1, memory_order_release); // release
- }
-
-} seqlock_t;
-
-
-seqlock_t *lock;
-
-static void a(void *obj) {
- lock->write(3);
-}
-
-static void b(void *obj) {
- lock->write(2);
-}
-
-static void c(void *obj) {
- int r1 = lock->read();
-}
-
-int user_main(int argc, char **argv) {
- thrd_t t1, t2, t3, t4;
- lock = new seqlock_t();
-
- thrd_create(&t1, (thrd_start_t)&a, NULL);
- thrd_create(&t2, (thrd_start_t)&b, NULL);
- thrd_create(&t3, (thrd_start_t)&c, NULL);
-
- thrd_join(t1);
- thrd_join(t2);
- thrd_join(t3);
- return 0;
-}
--- /dev/null
+#include <stdatomic.h>
+#include <threads.h>
+#include "seqlock.h"
+
+seqlock::seqlock() {
+ atomic_init(&_seq, 0);
+ atomic_init(&_data1, 0);
+ atomic_init(&_data2, 0);
+}
+
+/** @DeclareState: int data1;
+ int data2; */
+
+void seqlock::read(int *data1, int *data2) {
+ while (true) {
+ // XXX: This cannot be detected unless when we only have a single data
+ // varaible since that becomes a plain load/store. However, when we have
+ // multiple data pieces, we will detect the inconsitency of the data.
+ /********** Detected Correctness (testcase1) **********/
+ int old_seq = _seq.load(memory_order_acquire); // acquire
+ if (old_seq % 2 == 1) {
+ thrd_yield();
+ continue;
+ }
+
+ // Acquire ensures that the second _seq reads an update-to-date value
+ /********** Detected Correctness (testcase1) **********/
+ *data1 = _data1.load(memory_order_relaxed);
+ *data2 = _data2.load(memory_order_relaxed);
+ /** @OPClearDefine: true */
+ /********** Detected Correctness (testcase1) **********/
+ atomic_thread_fence(memory_order_acquire);
+ if (_seq.load(memory_order_relaxed) == old_seq) { // relaxed
+ thrd_yield();
+ return;
+ }
+ }
+}
+
+
+void seqlock::write(int data1, int data2) {
+ while (true) {
+ // #1: either here or #2 must be acquire
+ /********** Detected Correctness (testcase2 with -y -x1000) **********/
+ int old_seq = _seq.load(memory_order_acquire); // acquire
+ if (old_seq % 2 == 1) {
+ thrd_yield();
+ continue; // Retry
+ }
+
+ // #2
+ if (_seq.compare_exchange_strong(old_seq, old_seq + 1,
+ memory_order_relaxed, memory_order_relaxed)) {
+ thrd_yield();
+ break;
+ }
+ }
+
+ // XXX: Update the data. It needs to be release since this version use
+ // relaxed CAS in write(). When the first load of _seq reads the realaxed
+ // CAS update, it does not form synchronization, thus requiring the data to
+ // be acq/rel. The data in practice can be pointers to objects.
+ /********** Detected Correctness (testcase1) **********/
+ atomic_thread_fence(memory_order_release);
+ _data1.store(data1, memory_order_relaxed);
+ _data2.store(data2, memory_order_relaxed);
+ /** @OPDefine: true */
+
+ /********** Detected Admissibility (testcase1) **********/
+ _seq.fetch_add(1, memory_order_release); // release
+}
+
+/** C Interface */
+
+/** @Transition: STATE(data1) = data1;
+ STATE(data2) = data2; */
+void write(seqlock *lock, int data1, int data2) {
+ lock->write(data1, data2);
+}
+
+
+/** @JustifyingPrecondition: return STATE(data1) == *data1 && STATE(data2) == *data2; */
+void read(seqlock *lock, int *data1, int *data2) {
+ lock->read(data1, data2);
+}
--- /dev/null
+#ifndef _SEQLOCK_H
+#define _SEQLOCK_H
+
+#include <stdatomic.h>
+
+class seqlock {
+ public:
+ // Sequence for reader consistency check
+ atomic_int _seq;
+ // It needs to be atomic to avoid data races
+ atomic_int _data1;
+ atomic_int _data2;
+
+ seqlock();
+
+ void read(int *data1, int *data2);
+
+ void write(int data1, int data2);
+};
+
+/** C Interface */
+void write(seqlock *lock, int data1, int data2);
+void read(seqlock *lock, int *data1, int *data2);
+
+#endif
--- /dev/null
+#include <threads.h>
+#include "seqlock.h"
+
+seqlock *lock;
+
+static void a(void *obj) {
+ write(lock, 1, 2);
+}
+
+static void b(void *obj) {
+ write(lock, 3, 4);
+}
+
+static void c(void *obj) {
+ int *data1 = new int;
+ int *data2 = new int;
+ read(lock, data1, data2);
+}
+
+int user_main(int argc, char **argv) {
+ thrd_t t1, t2, t3;
+ /** @Entry */
+ lock = new seqlock;
+
+ thrd_create(&t1, (thrd_start_t)&a, NULL);
+ //thrd_create(&t2, (thrd_start_t)&b, NULL);
+ thrd_create(&t3, (thrd_start_t)&c, NULL);
+
+ thrd_join(t1);
+ //thrd_join(t2);
+ thrd_join(t3);
+ return 0;
+}
--- /dev/null
+#include <threads.h>
+#include "seqlock.h"
+
+seqlock *lock;
+
+static void a(void *obj) {
+ write(lock, 1, 2);
+}
+
+static void b(void *obj) {
+ write(lock, 3, 4);
+}
+
+static void c(void *obj) {
+ int *data1 = new int;
+ int *data2 = new int;
+ read(lock, data1, data2);
+}
+
+int user_main(int argc, char **argv) {
+ thrd_t t1, t2, t3;
+ /** @Entry */
+ lock = new seqlock;
+
+ thrd_create(&t1, (thrd_start_t)&a, NULL);
+ thrd_create(&t2, (thrd_start_t)&b, NULL);
+ thrd_create(&t3, (thrd_start_t)&c, NULL);
+
+ thrd_join(t1);
+ thrd_join(t2);
+ thrd_join(t3);
+ return 0;
+}
include ../benchmarks.mk
-TESTNAME = spsc-queue
-RELACYNAME = spsc-relacy
+BENCH := queue
-all: $(TESTNAME)
+BENCH_BINARY := $(BENCH).o
-$(TESTNAME): $(TESTNAME).cc queue.h eventcount.h
- $(CXX) -o $@ $< $(CXXFLAGS) $(LDFLAGS)
+TESTS := main testcase1
-relacy: $(RELACYNAME)
+all: $(TESTS)
+ ../generate.sh $(notdir $(shell pwd))
-$(RELACYNAME): spsc-relacy.cc queue-relacy.h eventcount-relacy.h
-ifdef RELACYPATH
- $(CXX) -o $(RELACYNAME) spsc-relacy.cc -I$(RELACYPATH) -Wno-deprecated
-else
- @echo "Please define RELACYPATH"
- @echo " e.g., make RELACYPATH=/path-to-relacy"
- @exit 1
-endif
+%.o : %.cc
+ $(CXX) -c -fPIC -MMD -MF .$@.d -o $@ $< $(CXXFLAGS) $(LDFLAGS)
+
+$(TESTS): % : %.o $(BENCH_BINARY)
+ $(CXX) -o $@ $^ $(CXXFLAGS) $(LDFLAGS)
+
+-include .*.d
clean:
- rm -f $(TESTNAME) $(RELACYNAME) *.o
+ rm -rf $(TESTS) *.o .*.d *.dSYM
+
+.PHONY: clean all
+++ /dev/null
-class eventcount
-{
-public:
- eventcount() : waiters(0)
- {
- count($) = 0;
- }
-
- void signal_relaxed()
- {
- unsigned cmp = count.load(std::memory_order_relaxed);
- signal_impl(cmp);
- }
-
- void signal()
- {
- unsigned cmp = count.fetch_add(0, std::memory_order_seq_cst);
- signal_impl(cmp);
- }
-
- unsigned get()
- {
- unsigned cmp = count.fetch_or(0x80000000,
-std::memory_order_seq_cst);
- return cmp & 0x7FFFFFFF;
- }
-
- void wait(unsigned cmp)
- {
- unsigned ec = count.load(std::memory_order_seq_cst);
- if (cmp == (ec & 0x7FFFFFFF))
- {
- guard.lock($);
- ec = count.load(std::memory_order_seq_cst);
- if (cmp == (ec & 0x7FFFFFFF))
- {
- waiters($) += 1;
- cv.wait(guard, $);
- }
- guard.unlock($);
- }
- }
-
-private:
- std::atomic<unsigned> count;
- rl::var<unsigned> waiters;
- std::mutex guard;
- std::condition_variable cv;
-
- void signal_impl(unsigned cmp)
- {
- if (cmp & 0x80000000)
- {
- guard.lock($);
- while (false == count.compare_exchange_weak(cmp,
- (cmp + 1) & 0x7FFFFFFF, std::memory_order_relaxed));
- unsigned w = waiters($);
- waiters($) = 0;
- guard.unlock($);
- if (w)
- cv.notify_all($);
- }
- }
-};
+#ifndef _EVENTCOUNT_H_
+#define _EVENTCOUNT_H_
+
#include <unrelacy.h>
#include <atomic>
#include <mutex>
}
}
};
+
+#endif
--- /dev/null
+#include <threads.h>
+
+#include "queue.h"
+
+spsc_queue<int> *q;
+
+void thread(unsigned thread_index)
+{
+ if (0 == thread_index)
+ {
+ enqueue(q, 11);
+ }
+ else
+ {
+ int d = dequeue(q);
+ //RL_ASSERT(11 == d);
+ }
+}
+
+int user_main(int argc, char **argv)
+{
+ thrd_t A, B;
+ /** @Entry */
+ q = new spsc_queue<int>();
+
+ thrd_create(&A, (thrd_start_t)&thread, (void *)0);
+ thrd_create(&B, (thrd_start_t)&thread, (void *)1);
+ thrd_join(A);
+ thrd_join(B);
+
+ delete q;
+
+ return 0;
+}
+++ /dev/null
-#include "eventcount-relacy.h"
-
-template<typename T>
-class spsc_queue
-{
-public:
- spsc_queue()
- {
- node* n = new node ();
- head($) = n;
- tail($) = n;
- }
-
- ~spsc_queue()
- {
- RL_ASSERT(head($) == tail($));
- delete ((node*)head($));
- }
-
- void enqueue(T data)
- {
- node* n = new node (data);
- head($)->next.store(n, std::memory_order_release);
- head($) = n;
- ec.signal();
- }
-
- T dequeue()
- {
- T data = try_dequeue();
- while (0 == data)
- {
- int cmp = ec.get();
- data = try_dequeue();
- if (data)
- break;
- ec.wait(cmp);
- data = try_dequeue();
- if (data)
- break;
- }
- return data;
- }
-
-private:
- struct node
- {
- std::atomic<node*> next;
- rl::var<T> data;
-
- node(T data = T())
- : data(data)
- {
- next($) = 0;
- }
- };
-
- rl::var<node*> head;
- rl::var<node*> tail;
-
- eventcount ec;
-
- T try_dequeue()
- {
- node* t = tail($);
- node* n = t->next.load(std::memory_order_acquire);
- if (0 == n)
- return 0;
- T data = n->data($);
- delete (t);
- tail($) = n;
- return data;
- }
-};
--- /dev/null
+#include "queue.h"
+
+template<typename T>
+void spsc_queue<T>::enqueue(T data)
+{
+ node* n = new node (data);
+ /********** Detected Correctness **********/
+ //head($)->next.store(n, std::memory_order_release);
+ head->next.store(n, std::memory_order_release);
+ /** @OPDefine: true */
+ head = n;
+ ec.signal();
+}
+
+
+template<typename T>
+T spsc_queue<T>::dequeue()
+{
+ T data = try_dequeue();
+ while (0 == data)
+ {
+ int cmp = ec.get();
+ data = try_dequeue();
+ if (data)
+ break;
+ ec.wait(cmp);
+ data = try_dequeue();
+ if (data)
+ break;
+ }
+ return data;
+}
+
+template<typename T>
+T spsc_queue<T>::try_dequeue()
+{
+ //node* t = tail($);
+ node* t = tail;
+ /********** Detected Correctness **********/
+ node* n = t->next.load(std::memory_order_acquire);
+ /** @OPClearDefine: true */
+ if (0 == n)
+ return 0;
+ //T data = n->data($);
+ T data = n->data;
+ delete (t);
+ tail = n;
+ return data;
+}
+
+/** @DeclareState: IntList *q; */
+
+/** @Transition: STATE(q)->push_back(data); */
+void enqueue(spsc_queue<int> *q, int data) {
+ q->enqueue(data);
+}
+
+/** @PreCondition: return STATE(q)->empty() ? !C_RET : STATE(q)->front() == C_RET;
+ @Transition: if (!C_RET) STATE(q)->pop_front(); */
+int dequeue(spsc_queue<int> *q) {
+ return q->dequeue();
+}
+#ifndef _SPSC_QUEUE_H
+#define _SPSC_QUEUE_H
+
#include <unrelacy.h>
#include <atomic>
~spsc_queue()
{
RL_ASSERT(head == tail);
- delete ((node*)head($));
+ //delete ((node*)head($));
+ delete ((node*)head);
}
- void enqueue(T data)
- {
- node* n = new node (data);
- head($)->next.store(n, std::memory_order_release);
- head = n;
- ec.signal();
- }
+ void enqueue(T data);
- T dequeue()
- {
- T data = try_dequeue();
- while (0 == data)
- {
- int cmp = ec.get();
- data = try_dequeue();
- if (data)
- break;
- ec.wait(cmp);
- data = try_dequeue();
- if (data)
- break;
- }
- return data;
- }
+ T dequeue();
private:
struct node
{
std::atomic<node*> next;
- rl::var<T> data;
+ //rl::var<T> data;
+ T data;
node(T data = T())
: data(data)
}
};
+ /* Use normal memory access
rl::var<node*> head;
rl::var<node*> tail;
+ */
+ node *head;
+ node *tail;
eventcount ec;
- T try_dequeue()
- {
- node* t = tail($);
- node* n = t->next.load(std::memory_order_acquire);
- if (0 == n)
- return 0;
- T data = n->data($);
- delete (t);
- tail = n;
- return data;
- }
+ T try_dequeue();
};
+
+// C Interface
+void enqueue(spsc_queue<int> *q, int data);
+int dequeue(spsc_queue<int> *q);
+
+#endif
--- /dev/null
+class eventcount
+{
+public:
+ eventcount() : waiters(0)
+ {
+ count($) = 0;
+ }
+
+ void signal_relaxed()
+ {
+ unsigned cmp = count.load(std::memory_order_relaxed);
+ signal_impl(cmp);
+ }
+
+ void signal()
+ {
+ unsigned cmp = count.fetch_add(0, std::memory_order_seq_cst);
+ signal_impl(cmp);
+ }
+
+ unsigned get()
+ {
+ unsigned cmp = count.fetch_or(0x80000000,
+std::memory_order_seq_cst);
+ return cmp & 0x7FFFFFFF;
+ }
+
+ void wait(unsigned cmp)
+ {
+ unsigned ec = count.load(std::memory_order_seq_cst);
+ if (cmp == (ec & 0x7FFFFFFF))
+ {
+ guard.lock($);
+ ec = count.load(std::memory_order_seq_cst);
+ if (cmp == (ec & 0x7FFFFFFF))
+ {
+ waiters($) += 1;
+ cv.wait(guard, $);
+ }
+ guard.unlock($);
+ }
+ }
+
+private:
+ std::atomic<unsigned> count;
+ rl::var<unsigned> waiters;
+ std::mutex guard;
+ std::condition_variable cv;
+
+ void signal_impl(unsigned cmp)
+ {
+ if (cmp & 0x80000000)
+ {
+ guard.lock($);
+ while (false == count.compare_exchange_weak(cmp,
+ (cmp + 1) & 0x7FFFFFFF, std::memory_order_relaxed));
+ unsigned w = waiters($);
+ waiters($) = 0;
+ guard.unlock($);
+ if (w)
+ cv.notify_all($);
+ }
+ }
+};
--- /dev/null
+#include "eventcount-relacy.h"
+
+template<typename T>
+class spsc_queue
+{
+public:
+ spsc_queue()
+ {
+ node* n = new node ();
+ head($) = n;
+ tail($) = n;
+ }
+
+ ~spsc_queue()
+ {
+ RL_ASSERT(head($) == tail($));
+ delete ((node*)head($));
+ }
+
+ void enqueue(T data)
+ {
+ node* n = new node (data);
+ head($)->next.store(n, std::memory_order_release);
+ head($) = n;
+ ec.signal();
+ }
+
+ T dequeue()
+ {
+ T data = try_dequeue();
+ while (0 == data)
+ {
+ int cmp = ec.get();
+ data = try_dequeue();
+ if (data)
+ break;
+ ec.wait(cmp);
+ data = try_dequeue();
+ if (data)
+ break;
+ }
+ return data;
+ }
+
+private:
+ struct node
+ {
+ std::atomic<node*> next;
+ rl::var<T> data;
+
+ node(T data = T())
+ : data(data)
+ {
+ next($) = 0;
+ }
+ };
+
+ rl::var<node*> head;
+ rl::var<node*> tail;
+
+ eventcount ec;
+
+ T try_dequeue()
+ {
+ node* t = tail($);
+ node* n = t->next.load(std::memory_order_acquire);
+ if (0 == n)
+ return 0;
+ T data = n->data($);
+ delete (t);
+ tail($) = n;
+ return data;
+ }
+};
--- /dev/null
+#include <relacy/relacy_std.hpp>
+
+#include "queue-relacy.h"
+
+struct spsc_queue_test : rl::test_suite<spsc_queue_test, 2>
+{
+ spsc_queue<int> q;
+
+ void thread(unsigned thread_index)
+ {
+ if (0 == thread_index)
+ {
+ q.enqueue(11);
+ }
+ else
+ {
+ int d = q.dequeue();
+ RL_ASSERT(11 == d);
+ }
+ }
+};
+
+
+int main()
+{
+ rl::test_params params;
+ params.search_type = rl::fair_full_search_scheduler_type;
+ rl::simulate<spsc_queue_test>(params);
+}
+++ /dev/null
-#include <threads.h>
-
-#include "queue.h"
-
-spsc_queue<int> *q;
-
- void thread(unsigned thread_index)
- {
- if (0 == thread_index)
- {
- q->enqueue(11);
- }
- else
- {
- int d = q->dequeue();
- RL_ASSERT(11 == d);
- }
- }
-
-int user_main(int argc, char **argv)
-{
- thrd_t A, B;
-
- q = new spsc_queue<int>();
-
- thrd_create(&A, (thrd_start_t)&thread, (void *)0);
- thrd_create(&B, (thrd_start_t)&thread, (void *)1);
- thrd_join(A);
- thrd_join(B);
-
- delete q;
-
- return 0;
-}
+++ /dev/null
-#include <relacy/relacy_std.hpp>
-
-#include "queue-relacy.h"
-
-struct spsc_queue_test : rl::test_suite<spsc_queue_test, 2>
-{
- spsc_queue<int> q;
-
- void thread(unsigned thread_index)
- {
- if (0 == thread_index)
- {
- q.enqueue(11);
- }
- else
- {
- int d = q.dequeue();
- RL_ASSERT(11 == d);
- }
- }
-};
-
-
-int main()
-{
- rl::test_params params;
- params.search_type = rl::fair_full_search_scheduler_type;
- rl::simulate<spsc_queue_test>(params);
-}
--- /dev/null
+#include <threads.h>
+
+#include "queue.h"
+
+spsc_queue<int> *q;
+
+void thread(unsigned thread_index)
+{
+ if (0 == thread_index)
+ {
+ enqueue(q, 11);
+ }
+ else
+ {
+ int d = dequeue(q);
+ //RL_ASSERT(11 == d);
+ }
+}
+
+int user_main(int argc, char **argv)
+{
+ thrd_t A, B;
+ /** @Entry */
+ q = new spsc_queue<int>();
+
+ thrd_create(&A, (thrd_start_t)&thread, (void *)0);
+ thrd_create(&B, (thrd_start_t)&thread, (void *)1);
+ thrd_join(A);
+ thrd_join(B);
+
+ delete q;
+
+ return 0;
+}
+++ /dev/null
-/spsc-queue
-/spsc-relacy
+++ /dev/null
-include ../benchmarks.mk
-
-TESTNAME = spsc-queue
-RELACYNAME = spsc-relacy
-
-all: $(TESTNAME)
-
-$(TESTNAME): $(TESTNAME).cc queue.h eventcount.h
- $(CXX) -o $@ $< $(CXXFLAGS) $(LDFLAGS)
-
-relacy: $(RELACYNAME)
-
-$(RELACYNAME): spsc-relacy.cc queue-relacy.h eventcount-relacy.h
-ifdef RELACYPATH
- $(CXX) -o $(RELACYNAME) spsc-relacy.cc -I$(RELACYPATH) -Wno-deprecated
-else
- @echo "Please define RELACYPATH"
- @echo " e.g., make RELACYPATH=/path-to-relacy"
- @exit 1
-endif
-
-clean:
- rm -f $(TESTNAME) $(RELACYNAME) *.o
+++ /dev/null
-class eventcount
-{
-public:
- eventcount() : waiters(0)
- {
- count($) = 0;
- }
-
- void signal_relaxed()
- {
- unsigned cmp = count.load(std::memory_order_relaxed);
- signal_impl(cmp);
- }
-
- void signal()
- {
- unsigned cmp = count.fetch_add(0, std::memory_order_seq_cst);
- signal_impl(cmp);
- }
-
- unsigned get()
- {
- unsigned cmp = count.fetch_or(0x80000000,
-std::memory_order_seq_cst);
- return cmp & 0x7FFFFFFF;
- }
-
- void wait(unsigned cmp)
- {
- unsigned ec = count.load(std::memory_order_seq_cst);
- if (cmp == (ec & 0x7FFFFFFF))
- {
- guard.lock($);
- ec = count.load(std::memory_order_seq_cst);
- if (cmp == (ec & 0x7FFFFFFF))
- {
- waiters($) += 1;
- cv.wait(guard, $);
- }
- guard.unlock($);
- }
- }
-
-private:
- std::atomic<unsigned> count;
- rl::var<unsigned> waiters;
- std::mutex guard;
- std::condition_variable cv;
-
- void signal_impl(unsigned cmp)
- {
- if (cmp & 0x80000000)
- {
- guard.lock($);
- while (false == count.compare_exchange_weak(cmp,
- (cmp + 1) & 0x7FFFFFFF, std::memory_order_relaxed));
- unsigned w = waiters($);
- waiters($) = 0;
- guard.unlock($);
- if (w)
- cv.notify_all($);
- }
- }
-};
+++ /dev/null
-#include <unrelacy.h>
-#include <atomic>
-#include <mutex>
-#include <condition_variable>
-
-class eventcount
-{
-public:
- eventcount() : waiters(0)
- {
- count = 0;
- }
-
- void signal_relaxed()
- {
- unsigned cmp = count.load(std::memory_order_relaxed);
- signal_impl(cmp);
- }
-
- void signal()
- {
- unsigned cmp = count.fetch_add(0, std::memory_order_seq_cst);
- signal_impl(cmp);
- }
-
- unsigned get()
- {
- unsigned cmp = count.fetch_or(0x80000000,
-std::memory_order_seq_cst);
- return cmp & 0x7FFFFFFF;
- }
-
- void wait(unsigned cmp)
- {
- unsigned ec = count.load(std::memory_order_seq_cst);
- if (cmp == (ec & 0x7FFFFFFF))
- {
- guard.lock($);
- ec = count.load(std::memory_order_seq_cst);
- if (cmp == (ec & 0x7FFFFFFF))
- {
- waiters += 1;
- cv.wait(guard);
- }
- guard.unlock($);
- }
- }
-
-private:
- std::atomic<unsigned> count;
- rl::var<unsigned> waiters;
- std::mutex guard;
- std::condition_variable cv;
-
- void signal_impl(unsigned cmp)
- {
- if (cmp & 0x80000000)
- {
- guard.lock($);
- while (false == count.compare_exchange_weak(cmp,
- (cmp + 1) & 0x7FFFFFFF, std::memory_order_relaxed));
- unsigned w = waiters($);
- waiters = 0;
- guard.unlock($);
- if (w)
- cv.notify_all($);
- }
- }
-};
+++ /dev/null
-#include "eventcount-relacy.h"
-
-template<typename T>
-class spsc_queue
-{
-public:
- spsc_queue()
- {
- node* n = new node ();
- head($) = n;
- tail($) = n;
- }
-
- ~spsc_queue()
- {
- RL_ASSERT(head($) == tail($));
- delete ((node*)head($));
- }
-
- void enqueue(T data)
- {
- node* n = new node (data);
- head($)->next.store(n, std::memory_order_release);
- head($) = n;
- ec.signal_relaxed();
- }
-
- T dequeue()
- {
- T data = try_dequeue();
- while (0 == data)
- {
- int cmp = ec.get();
- data = try_dequeue();
- if (data)
- break;
- ec.wait(cmp);
- data = try_dequeue();
- if (data)
- break;
- }
- return data;
- }
-
-private:
- struct node
- {
- std::atomic<node*> next;
- rl::var<T> data;
-
- node(T data = T())
- : data(data)
- {
- next($) = 0;
- }
- };
-
- rl::var<node*> head;
- rl::var<node*> tail;
-
- eventcount ec;
-
- T try_dequeue()
- {
- node* t = tail($);
- node* n = t->next.load(std::memory_order_acquire);
- if (0 == n)
- return 0;
- T data = n->data($);
- delete (t);
- tail($) = n;
- return data;
- }
-};
+++ /dev/null
-#include <unrelacy.h>
-#include <atomic>
-
-#include "eventcount.h"
-
-template<typename T>
-class spsc_queue
-{
-public:
- spsc_queue()
- {
- node* n = new node ();
- head = n;
- tail = n;
- }
-
- ~spsc_queue()
- {
- RL_ASSERT(head == tail);
- delete ((node*)head($));
- }
-
- void enqueue(T data)
- {
- node* n = new node (data);
- head($)->next.store(n, std::memory_order_release);
- head = n;
- ec.signal_relaxed();
- }
-
- T dequeue()
- {
- T data = try_dequeue();
- while (0 == data)
- {
- int cmp = ec.get();
- data = try_dequeue();
- if (data)
- break;
- ec.wait(cmp);
- data = try_dequeue();
- if (data)
- break;
- }
- return data;
- }
-
-private:
- struct node
- {
- std::atomic<node*> next;
- rl::var<T> data;
-
- node(T data = T())
- : data(data)
- {
- next = 0;
- }
- };
-
- rl::var<node*> head;
- rl::var<node*> tail;
-
- eventcount ec;
-
- T try_dequeue()
- {
- node* t = tail($);
- node* n = t->next.load(std::memory_order_acquire);
- if (0 == n)
- return 0;
- T data = n->data($);
- delete (t);
- tail = n;
- return data;
- }
-};
+++ /dev/null
-#include <threads.h>
-
-#include "queue.h"
-
-spsc_queue<int> *q;
-
- void thread(unsigned thread_index)
- {
- if (0 == thread_index)
- {
- q->enqueue(11);
- }
- else
- {
- int d = q->dequeue();
- RL_ASSERT(11 == d);
- }
- }
-
-int user_main(int argc, char **argv)
-{
- thrd_t A, B;
-
- q = new spsc_queue<int>();
-
- thrd_create(&A, (thrd_start_t)&thread, (void *)0);
- thrd_create(&B, (thrd_start_t)&thread, (void *)1);
- thrd_join(A);
- thrd_join(B);
-
- delete q;
-
- return 0;
-}
+++ /dev/null
-#include <relacy/relacy_std.hpp>
-
-#include "queue-relacy.h"
-
-struct spsc_queue_test : rl::test_suite<spsc_queue_test, 2>
-{
- spsc_queue<int> q;
-
- void thread(unsigned thread_index)
- {
- if (0 == thread_index)
- {
- q.enqueue(11);
- }
- else
- {
- int d = q.dequeue();
- RL_ASSERT(11 == d);
- }
- }
-};
-
-
-int main()
-{
- rl::simulate<spsc_queue_test>();
-}
--- /dev/null
+include ../benchmarks.mk
+
+BENCH := lock
+
+BENCH_BINARY := $(BENCH).o
+
+TESTS := main testcase1
+
+all: $(TESTS)
+ ../generate.sh $(notdir $(shell pwd))
+
+%.o : %.c
+ $(CC) -c -fPIC -MMD -MF .$@.d -o $@ $< $(CFLAGS) $(LDFLAGS)
+
+$(TESTS): % : %.o $(BENCH_BINARY)
+ $(CC) -o $@ $^ $(CFLAGS) $(LDFLAGS)
+
+-include .*.d
+
+clean:
+ rm -rf $(TESTS) *.o .*.d *.dSYM
+
+.PHONY: clean all
--- /dev/null
+#include <stdatomic.h>
+
+#include "librace.h"
+#include "lock.h"
+
+/**
+ This ticket lock implementation is derived from the original Mellor-Crummey
+ & Scott paper <Algorithms for Scalable Synchronization on
+ SharedMemory Multiprocessors> in 1991.
+ It assumes that the ticket and turn counter are large enough to accommodate
+ the maximum number of simultaneous requests for the lock.
+*/
+
+/** @DeclareState: bool lock; */
+
+
+void initTicketLock(TicketLock *l) {
+ atomic_init(&l->ticket, 0);
+ atomic_init(&l->turn, 0);
+}
+
+/** @PreCondition: return STATE(lock) == false;
+@Transition: STATE(lock) = true; */
+void lock(struct TicketLock *l) {
+ // First grab a ticket
+ unsigned ticket = atomic_fetch_add_explicit(&l->ticket, 1,
+ memory_order_relaxed);
+ // Spinning for my turn
+ while (true) {
+ /********** Detected Correctness (testcase1 with -Y) **********/
+ unsigned turn = atomic_load_explicit(&l->turn, memory_order_acquire);
+ /** @OPDefine: turn == ticket */
+ if (turn == ticket) { // Now it's my turn
+ return;
+ } else {
+ thrd_yield(); // Added for CDSChecker
+ }
+ }
+}
+
+/** @PreCondition: return STATE(lock) == true;
+@Transition: STATE(lock) = false; */
+void unlock(struct TicketLock *l) {
+ unsigned turn = atomic_load_explicit(&l->turn, memory_order_relaxed);
+ /********** Detected Correctness (testcase1 with -Y) **********/
+ atomic_store_explicit(&l->turn, turn + 1, memory_order_release);
+ /** @OPDefine: true */
+}
--- /dev/null
+#ifndef _LOCK_H
+#define _LOCK_H
+
+#include <stdio.h>
+#include <threads.h>
+#include <stdatomic.h>
+
+typedef struct TicketLock {
+ atomic_uint ticket;
+ atomic_uint turn;
+} TicketLock;
+
+void initTicketLock(TicketLock* l);
+
+void lock(TicketLock *l);
+
+void unlock(TicketLock *l);
+
+#endif
--- /dev/null
+#include <threads.h>
+#include <stdatomic.h>
+
+#include "librace.h"
+#include "lock.h"
+
+TicketLock mylock;
+int shareddata;
+
+static void a(void *obj)
+{
+ int i;
+ for(i = 0; i < 2; i++) {
+ if ((i % 2) == 0) {
+ lock(&mylock);
+ //load_32(&shareddata);
+ unlock(&mylock);
+ } else {
+ lock(&mylock);
+ //store_32(&shareddata,(unsigned int)i);
+ unlock(&mylock);
+ }
+ }
+}
+
+int user_main(int argc, char **argv)
+{
+ thrd_t t1, t2;
+ /** @Entry */
+ initTicketLock(&mylock);
+
+ thrd_create(&t1, (thrd_start_t)&a, NULL);
+ thrd_create(&t2, (thrd_start_t)&a, NULL);
+
+ thrd_join(t1);
+ thrd_join(t2);
+
+ return 0;
+}
--- /dev/null
+#include <threads.h>
+#include <stdatomic.h>
+
+#include "librace.h"
+#include "lock.h"
+
+TicketLock mylock;
+int shareddata;
+
+static void a(void *obj)
+{
+ int i;
+ for(i = 0; i < 2; i++) {
+ if ((i % 2) == 0) {
+ lock(&mylock);
+ //load_32(&shareddata);
+ unlock(&mylock);
+ } else {
+ lock(&mylock);
+ //store_32(&shareddata,(unsigned int)i);
+ unlock(&mylock);
+ }
+ }
+}
+
+int user_main(int argc, char **argv)
+{
+ thrd_t t1, t2;
+ /** @Entry */
+ initTicketLock(&mylock);
+
+ thrd_create(&t1, (thrd_start_t)&a, NULL);
+ thrd_create(&t2, (thrd_start_t)&a, NULL);
+
+ thrd_join(t1);
+ thrd_join(t2);
+
+ return 0;
+}
+++ /dev/null
-include ../benchmarks.mk
-
-main: my_stack.o main.c
- $(CC) -o $@ $^ $(CFLAGS) $(LDFLAGS)
-
-%.o: %.c
- $(CC) -c -o $@ $^ $(CFLAGS)
-
-clean:
- rm -f *.o
+++ /dev/null
-#include <stdlib.h>
-#include <stdio.h>
-#include <threads.h>
-
-#include "my_stack.h"
-#include "model-assert.h"
-
-static int procs = 4;
-static mystack_t *stack;
-static thrd_t *threads;
-static int num_threads;
-
-unsigned int idx1, idx2;
-unsigned int a, b;
-
-
-atomic_int x[3];
-
-int get_thread_num()
-{
- thrd_t curr = thrd_current();
- int i;
- for (i = 0; i < num_threads; i++)
- if (curr.priv == threads[i].priv)
- return i;
- MODEL_ASSERT(0);
- return -1;
-}
-
-static void main_task(void *param)
-{
- unsigned int val;
- int pid = *((int *)param);
-
- if (pid % 4 == 0) {
- atomic_store_explicit(&x[1], 17, relaxed);
- push(stack, 1);
- } else if (pid % 4 == 1) {
- atomic_store_explicit(&x[2], 37, relaxed);
- push(stack, 2);
- } else if (pid % 4 == 2) {/*
- idx1 = pop(stack);
- if (idx1 != 0) {
- a = atomic_load_explicit(&x[idx1], relaxed);
- printf("a: %d\n", a);
- }*/
- } else {
- idx2 = pop(stack);
- if (idx2 != 0) {
- b = atomic_load_explicit(&x[idx2], relaxed);
- printf("b: %d\n", b);
- }
- }
-}
-
-int user_main(int argc, char **argv)
-{
- int i;
- int *param;
- unsigned int in_sum = 0, out_sum = 0;
-
- atomic_init(&x[1], 0);
- atomic_init(&x[2], 0);
-
- stack = calloc(1, sizeof(*stack));
-
- num_threads = procs;
- threads = malloc(num_threads * sizeof(thrd_t));
- param = malloc(num_threads * sizeof(*param));
-
- init_stack(stack, num_threads);
-
- for (i = 0; i < num_threads; i++) {
- param[i] = i;
- thrd_create(&threads[i], main_task, ¶m[i]);
- }
- for (i = 0; i < num_threads; i++)
- thrd_join(threads[i]);
-
- bool correct = false;
- //correct |= (a == 17 || a == 37 || a == 0);
- //MODEL_ASSERT(correct);
-
- free(param);
- free(threads);
- free(stack);
-
- return 0;
-}
+++ /dev/null
-#include <threads.h>
-#include <stdlib.h>
-#include "librace.h"
-#include "model-assert.h"
-
-#include "my_stack.h"
-
-#define MAX_FREELIST 4 /* Each thread can own up to MAX_FREELIST free nodes */
-#define INITIAL_FREE 2 /* Each thread starts with INITIAL_FREE free nodes */
-
-#define POISON_IDX 0x666
-
-static unsigned int (*free_lists)[MAX_FREELIST];
-
-/* Search this thread's free list for a "new" node */
-static unsigned int new_node()
-{
- int i;
- int t = get_thread_num();
- for (i = 0; i < MAX_FREELIST; i++) {
- //unsigned int node = load_32(&free_lists[t][i]);
- unsigned int node = free_lists[t][i];
- if (node) {
- //store_32(&free_lists[t][i], 0);
- free_lists[t][i] = 0;
- return node;
- }
- }
- /* free_list is empty? */
- MODEL_ASSERT(0);
- return 0;
-}
-
-/* Place this node index back on this thread's free list */
-static void reclaim(unsigned int node)
-{
- int i;
- int t = get_thread_num();
-
- /* Don't reclaim NULL node */
- //MODEL_ASSERT(node);
-
- for (i = 0; i < MAX_FREELIST; i++) {
- /* Should never race with our own thread here */
- //unsigned int idx = load_32(&free_lists[t][i]);
- unsigned int idx = free_lists[t][i];
-
- /* Found empty spot in free list */
- if (idx == 0) {
- //store_32(&free_lists[t][i], node);
- free_lists[t][i] = node;
- return;
- }
- }
- /* free list is full? */
- MODEL_ASSERT(0);
-}
-
-void init_stack(mystack_t *s, int num_threads)
-{
- int i, j;
-
- /* Initialize each thread's free list with INITIAL_FREE pointers */
- /* The actual nodes are initialized with poison indexes */
- free_lists = malloc(num_threads * sizeof(*free_lists));
- for (i = 0; i < num_threads; i++) {
- for (j = 0; j < INITIAL_FREE; j++) {
- free_lists[i][j] = 1 + i * MAX_FREELIST + j;
- atomic_init(&s->nodes[free_lists[i][j]].next, MAKE_POINTER(POISON_IDX, 0));
- }
- }
-
- /* initialize stack */
- atomic_init(&s->top, MAKE_POINTER(0, 0));
-}
-
-void push(mystack_t *s, unsigned int val) {
- unsigned int nodeIdx = new_node();
- node_t *node = &s->nodes[nodeIdx];
- node->value = val;
- pointer oldTop, newTop;
- bool success;
- while (true) {
- // acquire
- oldTop = atomic_load_explicit(&s->top, acquire);
- newTop = MAKE_POINTER(nodeIdx, get_count(oldTop) + 1);
- // relaxed
- atomic_store_explicit(&node->next, oldTop, relaxed);
-
- // release & relaxed
- success = atomic_compare_exchange_strong_explicit(&s->top, &oldTop,
- newTop, release, relaxed);
- if (success)
- break;
- }
-}
-
-unsigned int pop(mystack_t *s)
-{
- pointer oldTop, newTop, next;
- node_t *node;
- bool success;
- int val;
- while (true) {
- // acquire
- oldTop = atomic_load_explicit(&s->top, acquire);
- if (get_ptr(oldTop) == 0)
- return 0;
- node = &s->nodes[get_ptr(oldTop)];
- // relaxed
- next = atomic_load_explicit(&node->next, relaxed);
- newTop = MAKE_POINTER(get_ptr(next), get_count(oldTop) + 1);
- // release & relaxed
- success = atomic_compare_exchange_strong_explicit(&s->top, &oldTop,
- newTop, release, relaxed);
- if (success)
- break;
- }
- val = node->value;
- /* Reclaim the used slot */
- reclaim(get_ptr(oldTop));
- return val;
-}
+++ /dev/null
-#include <stdatomic.h>
-
-#define release memory_order_release
-#define acquire memory_order_acquire
-#define relaxed memory_order_relaxed
-
-#define MAX_NODES 0xf
-
-typedef unsigned long long pointer;
-typedef atomic_ullong pointer_t;
-
-#define MAKE_POINTER(ptr, count) ((((pointer)count) << 32) | ptr)
-#define PTR_MASK 0xffffffffLL
-#define COUNT_MASK (0xffffffffLL << 32)
-
-static inline void set_count(pointer *p, unsigned int val) { *p = (*p & ~COUNT_MASK) | ((pointer)val << 32); }
-static inline void set_ptr(pointer *p, unsigned int val) { *p = (*p & ~PTR_MASK) | val; }
-static inline unsigned int get_count(pointer p) { return (p & COUNT_MASK) >> 32; }
-static inline unsigned int get_ptr(pointer p) { return p & PTR_MASK; }
-
-typedef struct node {
- unsigned int value;
- pointer_t next;
-
-} node_t;
-
-typedef struct {
- pointer_t top;
- node_t nodes[MAX_NODES + 1];
-} mystack_t;
-
-void init_stack(mystack_t *s, int num_threads);
-void push(mystack_t *s, unsigned int val);
-unsigned int pop(mystack_t *s);
-int get_thread_num();