diff --git a/include/ck_queue.h b/include/ck_queue.h index a9a502d..0393eef 100644 --- a/include/ck_queue.h +++ b/include/ck_queue.h @@ -88,24 +88,25 @@ * This facility is currently unsupported on architectures such as the Alpha * which require load-depend memory fences. * - * CK_SLIST CK_LIST - * _HEAD + + - * _HEAD_INITIALIZER + + - * _ENTRY + + - * _INIT + + - * _EMPTY + + - * _FIRST + + - * _NEXT + + - * _FOREACH + + - * _FOREACH_SAFE + + - * _INSERT_HEAD + + - * _INSERT_BEFORE - + - * _INSERT_AFTER + + - * _REMOVE_AFTER + - - * _REMOVE_HEAD + - - * _REMOVE + + - * _SWAP + + - * _MOVE + + + * CK_SLIST CK_LIST CK_STAILQ + * _HEAD + + + + * _HEAD_INITIALIZER + + + + * _ENTRY + + + + * _INIT + + + + * _EMPTY + + + + * _FIRST + + + + * _NEXT + + + + * _FOREACH + + + + * _FOREACH_SAFE + + + + * _INSERT_HEAD + + + + * _INSERT_BEFORE - + - + * _INSERT_AFTER + + + + * _INSERT_TAIL - - + + * _REMOVE_AFTER + - + + * _REMOVE_HEAD + - + + * _REMOVE + + + + * _SWAP + + + + * _MOVE + + + */ /* @@ -202,6 +203,130 @@ struct { \ (b)->slh_first = swap_first; \ } while (0) +/* + * Singly-linked Tail queue declarations. + */ +#define CK_STAILQ_HEAD(name, type) \ +struct name { \ + struct type *stqh_first;/* first element */ \ + struct type **stqh_last;/* addr of last next element */ \ +} + +#define CK_STAILQ_HEAD_INITIALIZER(head) \ + { NULL, &(head).stqh_first } + +#define CK_STAILQ_ENTRY(type) \ +struct { \ + struct type *stqe_next; /* next element */ \ +} + +/* + * Singly-linked Tail queue functions. + */ +#define CK_STAILQ_CONCAT(head1, head2) do { \ + if ((head2)->stqh_first == NULL) { \ + ck_pr_store_ptr((head1)->stqh_last, (head2)->stqh_first); \ + ck_pr_fence_store(); \ + (head1)->stqh_last = (head2)->stqh_last; \ + CK_STAILQ_INIT((head2)); \ + } \ +} while (0) + +#define CK_STAILQ_EMPTY(head) (ck_pr_load_ptr(&(head)->stqh_first) == NULL) + +#define CK_STAILQ_FIRST(head) (ck_pr_load_ptr(&(head)->stqh_first)) + +#define CK_STAILQ_FOREACH(var, head, field) \ + for((var) = CK_STAILQ_FIRST((head)); \ + (var) && (ck_pr_fence_load(), 1); \ + (var) = CK_STAILQ_NEXT((var), field)) + +#define CK_STAILQ_FOREACH_SAFE(var, head, field, tvar) \ + for ((var) = CK_STAILQ_FIRST((head)); \ + (var) && (ck_pr_fence_load(), (tvar) = \ + CK_STAILQ_NEXT((var), field), 1); \ + (var) = (tvar)) + +#define CK_STAILQ_INIT(head) do { \ + ck_pr_store_ptr(&(head)->stqh_first, NULL); \ + ck_pr_fence_store(); \ + (head)->stqh_last = &(head)->stqh_first; \ +} while (0) + +#define CK_STAILQ_INSERT_AFTER(head, tqelm, elm, field) do { \ + (elm)->field.stqe_next = (tqelm)->field.stqe_next; \ + ck_pr_fence_store(); \ + ck_pr_store_ptr(&(tqelm)->field.stqe_next, elm); \ + if ((elm)->field.stqe_next == NULL) \ + (head)->stqh_last = &(elm)->field.stqe_next; \ +} while (0) + +#define CK_STAILQ_INSERT_HEAD(head, elm, field) do { \ + (elm)->field.stqe_next = (head)->stqh_first; \ + ck_pr_fence_store(); \ + ck_pr_store_ptr(&(head)->stqh_first, elm); \ + if ((elm)->field.stqe_next == NULL) \ + (head)->stqh_last = &(elm)->field.stqe_next; \ +} while (0) + +#define CK_STAILQ_INSERT_TAIL(head, elm, field) do { \ + (elm)->field.stqe_next = NULL; \ + ck_pr_fence_store(); \ + ck_pr_store_ptr((head)->stqh_last, (elm)); \ + (head)->stqh_last = &(elm)->field.stqe_next; \ +} while (0) + +#define CK_STAILQ_NEXT(elm, field) \ + (ck_pr_load_ptr(&(elm)->field.stqe_next)) + +#define CK_STAILQ_REMOVE(head, elm, type, field) do { \ + if ((head)->stqh_first == (elm)) { \ + CK_STAILQ_REMOVE_HEAD((head), field); \ + } else { \ + struct type *curelm = (head)->stqh_first; \ + while (curelm->field.stqe_next != (elm)) \ + curelm = curelm->field.stqe_next; \ + CK_STAILQ_REMOVE_AFTER(head, curelm, field); \ + } \ +} while (0) + +#define CK_STAILQ_REMOVE_AFTER(head, elm, field) do { \ + ck_pr_store_ptr(&(elm)->field.stqe_next, \ + (elm)->field.stqe_next->field.stqe_next); \ + if ((elm)->field.stqe_next == NULL) \ + (head)->stqh_last = &(elm)->field.stqe_next; \ +} while (0) + +#define CK_STAILQ_REMOVE_HEAD(head, field) do { \ + ck_pr_store_ptr(&(head)->stqh_first, \ + (head)->stqh_first->field.stqe_next); \ + if ((head)->stqh_first == NULL) \ + (head)->stqh_last = &(head)->stqh_first; \ +} while (0) + +#define CK_STAILQ_MOVE(head1, head2, field) do { \ + ck_pr_store_ptr(&(head1)->stqh_first, (head2)->stqh_first); \ + (head1)->stqh_last = (head2)->stqh_last; \ + if ((head2)->stqh_last == &(head2)->stqh_first) \ + (head1)->stqh_last = &(head1)->stqh_first; \ +} while (0) + +/* + * This operation is not applied atomically. + */ +#define CK_STAILQ_SWAP(head1, head2, type) do { \ + struct type *swap_first = CK_STAILQ_FIRST(head1); \ + struct type **swap_last = (head1)->stqh_last; \ + CK_STAILQ_FIRST(head1) = CK_STAILQ_FIRST(head2); \ + (head1)->stqh_last = (head2)->stqh_last; \ + CK_STAILQ_FIRST(head2) = swap_first; \ + (head2)->stqh_last = swap_last; \ + if (CK_STAILQ_EMPTY(head1)) \ + (head1)->stqh_last = &(head1)->stqh_first; \ + if (CK_STAILQ_EMPTY(head2)) \ + (head2)->stqh_last = &(head2)->stqh_first; \ +} while (0) + /* * List declarations. */ diff --git a/regressions/ck_queue/validate/Makefile b/regressions/ck_queue/validate/Makefile index 07ce22b..7a690d3 100644 --- a/regressions/ck_queue/validate/Makefile +++ b/regressions/ck_queue/validate/Makefile @@ -1,19 +1,23 @@ .PHONY: check clean distribution HEADER=../../../include/ck_queue.h -OBJECTS=ck_slist ck_list +OBJECTS=ck_list ck_slist ck_stailq all: $(OBJECTS) check: all - ./ck_slist $(CORES) 100000 - ./ck_list $(CORES) 100000 + ./ck_list $(CORES) 5 + ./ck_slist $(CORES) 5 + ./ck_stailq $(CORES) 1000000 + +ck_list: $(HEADER) ck_list.c + $(CC) $(CFLAGS) -o ck_list ck_list.c ck_slist: $(HEADER) ck_slist.c $(CC) $(CFLAGS) -o ck_slist ck_slist.c -ck_list: $(HEADER) ck_list.c - $(CC) $(CFLAGS) -o ck_list ck_list.c +ck_stailq: $(HEADER) ck_stailq.c + $(CC) $(CFLAGS) -o ck_stailq ck_stailq.c clean: rm -rf *~ *.o $(OBJECTS) *.dSYM diff --git a/regressions/ck_queue/validate/ck_stailq.c b/regressions/ck_queue/validate/ck_stailq.c new file mode 100644 index 0000000..a129068 --- /dev/null +++ b/regressions/ck_queue/validate/ck_stailq.c @@ -0,0 +1,257 @@ +/* + * Copyright 2012-2013 Samy Al Bahra. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#include +#include +#include +#include +#include +#include +#include "../../common.h" + +struct test { + int value; + CK_STAILQ_ENTRY(test) list_entry; +}; +static CK_STAILQ_HEAD(test_list, test) head = CK_STAILQ_HEAD_INITIALIZER(head); + +static int goal; + +static void +test_foreach(void) +{ + struct test *n, *next, *safe; + int i, s = 0, j = 0, k = 0; + + for (i = goal; i != 0; i = goal) { + s = 0; + + CK_STAILQ_FOREACH(n, &head, list_entry) { + j++; + if (s == 0) + s = n->value; + else + s = s - 1; + + if (n->value != s) { + ck_error("\nExpected %d, but got %d.\n", + s, n->value); + } + + next = CK_STAILQ_NEXT(n, list_entry); + if (next != NULL && next->value != s - 1) { + ck_error("\nExpected %d, but got %d.\n", + s, next->value); + } + + i--; + } + + if (i == 0) + break; + + s = 0; + CK_STAILQ_FOREACH_SAFE(n, &head, list_entry, safe) { + k++; + + if (s == 0) + s = n->value; + else + s = s - 1; + + if (n->value != s) { + ck_error("\nExpected %d, but got %d.\n", + s, n->value); + } + + next = CK_STAILQ_NEXT(n, list_entry); + if (next != NULL && next->value != s - 1) { + ck_error("\nExpected %d, but got %d.\n", + s, next->value); + } + + i--; + } + + if (i == 0 || CK_STAILQ_EMPTY(&head) == true) + break; + } + + fprintf(stderr, "(%d, %d) ", j, k); + return; +} + +static void * +execute(void *c) +{ + + (void)c; + test_foreach(); + return NULL; +} + +int +main(int argc, char *argv[]) +{ + pthread_t *thread; + struct test *n, a, b; + struct test_list target; + int n_threads, i; + + if (argc != 3) { + ck_error("Usage: %s \n", argv[0]); + } + + n_threads = atoi(argv[1]); + if (n_threads < 1) { + ck_error("ERROR: Number of threads must be >= 1.\n"); + } + + thread = malloc(sizeof(pthread_t) * n_threads); + assert(thread != NULL); + + goal = atoi(argv[2]); + if (goal < 4) { + ck_error("ERROR: Number of entries must be >= 4.\n"); + } + + fprintf(stderr, "Beginning serial test..."); + CK_STAILQ_INIT(&head); + + for (i = 1; i <= goal; i++) { + n = malloc(sizeof *n); + assert(n != NULL); + n->value = i; + CK_STAILQ_INSERT_HEAD(&head, n, list_entry); + } + + test_foreach(); + + for (i = 1; i <= goal; i++) { + n = CK_STAILQ_FIRST(&head); + CK_STAILQ_REMOVE(&head, n, test, list_entry); + free(n); + } + + if (CK_STAILQ_EMPTY(&head) == false) { + ck_error("List is not empty after bulk removal.\n"); + } + + for (i = 1; i <= goal; i++) { + n = malloc(sizeof *n); + assert(n != NULL); + n->value = goal - i; + CK_STAILQ_INSERT_TAIL(&head, n, list_entry); + } + + test_foreach(); + + for (i = 1; i <= goal; i++) { + n = CK_STAILQ_FIRST(&head); + CK_STAILQ_REMOVE(&head, n, test, list_entry); + free(n); + } + + if (CK_STAILQ_EMPTY(&head) == false) { + ck_error("List is not empty after bulk removal.\n"); + } + + CK_STAILQ_INSERT_HEAD(&head, &a, list_entry); + CK_STAILQ_INSERT_HEAD(&head, &b, list_entry); + CK_STAILQ_REMOVE(&head, &a, test, list_entry); + if (CK_STAILQ_FIRST(&head) != &b) + ck_error("List is in invalid state.\n"); + CK_STAILQ_REMOVE(&head, &b, test, list_entry); + + if (CK_STAILQ_EMPTY(&head) == false) { + ck_error("List is not empty after bulk removal.\n"); + } + + CK_STAILQ_INSERT_HEAD(&head, &a, list_entry); + CK_STAILQ_INSERT_AFTER(&head, &a, &b, list_entry); + + if (CK_STAILQ_NEXT(&b, list_entry) != NULL) + ck_error("Inserted item after last, it should not have no next.\n"); + + CK_STAILQ_INIT(&head); + + CK_STAILQ_INSERT_HEAD(&head, &a, list_entry); + if (CK_STAILQ_NEXT(&a, list_entry) != NULL) + ck_error("Inserted item as last, but it contains next pointer.\n"); + + CK_STAILQ_INIT(&head); + fprintf(stderr, "done (success)\n"); + + fprintf(stderr, "Beginning parallel traversal..."); + + n = malloc(sizeof *n); + assert(n != NULL); + n->value = 1; + CK_STAILQ_INSERT_HEAD(&head, n, list_entry); + + for (i = 0; i < n_threads; i++) { + int r = pthread_create(&thread[i], NULL, execute, NULL); + assert(r == 0); + } + + for (i = 2; i <= goal; i++) { + volatile int j; + + n = malloc(sizeof *n); + assert(n != NULL); + n->value = i; + CK_STAILQ_INSERT_HEAD(&head, n, list_entry); + for (j = 0; j <= 1000; j++); + } + + for (i = 0; i < n_threads; i++) + pthread_join(thread[i], NULL); + + for (i = 0; i < n_threads; i++) { + int r = pthread_create(&thread[i], NULL, execute, NULL); + assert(r == 0); + } + + CK_STAILQ_MOVE(&target, &head, list_entry); + + for (i = 1; i <= goal; i++) { + volatile int j; + + if (CK_STAILQ_EMPTY(&target) == false) { + struct test *r = CK_STAILQ_FIRST(&target); + CK_STAILQ_REMOVE(&target, r, test, list_entry); + } + + for (j = 0; j <= 1000; j++); + } + + for (i = 0; i < n_threads; i++) + pthread_join(thread[i], NULL); + + fprintf(stderr, "done (success)\n"); + return (0); +} +