|
|
|
/**
|
|
|
|
* Redistribution of this file is permitted under the BSD two clause license.
|
|
|
|
*
|
|
|
|
* Copyright 2019, The George Washington University
|
|
|
|
* Author: Phani Gadepalli, phanikishoreg@gwu.edu
|
|
|
|
*/
|
|
|
|
#ifndef DEQUE_H
|
|
|
|
#define DEQUE_H
|
|
|
|
|
|
|
|
/*
|
|
|
|
* This was implemented by referring to:
|
|
|
|
* https://github.com/cpp-taskflow/cpp-taskflow/blob/9c28ccec910346a9937c40db7bdb542262053f9c/taskflow/executor/workstealing.hpp
|
|
|
|
*
|
|
|
|
* which is based on the following papers:
|
|
|
|
*
|
|
|
|
* The work stealing queue described in the paper, "Dynamic Circular Work-stealing Deque," SPAA, 2015.
|
|
|
|
* Only the queue owner can perform pop and push operations, while others can steal data from the queue.
|
|
|
|
*
|
|
|
|
* PPoPP implementation paper, "Correct and Efficient Work-Stealing for Weak Memory Models"
|
|
|
|
* https://www.di.ens.fr/~zappa/readings/ppopp13.pdf
|
|
|
|
*/
|
|
|
|
|
|
|
|
/* TODO: Implement the ability to dynamically resize! Issue #89 */
|
|
|
|
#define DEQUE_MAX_SZ (1 << 23)
|
|
|
|
|
|
|
|
#define DEQUE_PROTOTYPE(name, type) \
|
|
|
|
struct deque_##name { \
|
|
|
|
type wrk[DEQUE_MAX_SZ]; \
|
|
|
|
long size; \
|
|
|
|
\
|
|
|
|
volatile long top; \
|
|
|
|
volatile long bottom; \
|
|
|
|
}; \
|
|
|
|
\
|
|
|
|
static inline void deque_init_##name(struct deque_##name *q, size_t sz) \
|
|
|
|
{ \
|
|
|
|
memset(q, 0, sizeof(struct deque_##name)); \
|
|
|
|
\
|
|
|
|
if (sz) { \
|
|
|
|
/* only for size with pow of 2 */ \
|
|
|
|
/* assert((sz & (sz - 1)) == 0); */ \
|
|
|
|
assert(sz <= DEQUE_MAX_SZ); \
|
|
|
|
} else { \
|
|
|
|
sz = DEQUE_MAX_SZ; \
|
|
|
|
} \
|
|
|
|
\
|
|
|
|
q->size = sz; \
|
|
|
|
} \
|
|
|
|
\
|
|
|
|
/* Use mutual exclusion locks around push/pop if multi-threaded. */ \
|
|
|
|
static inline int deque_push_##name(struct deque_##name *q, type *w) \
|
|
|
|
{ \
|
|
|
|
long ct, cb; \
|
|
|
|
\
|
|
|
|
ct = q->top; \
|
|
|
|
cb = q->bottom; \
|
|
|
|
\
|
|
|
|
/* nope, fixed size only */ \
|
|
|
|
if (q->size - 1 < (cb - ct)) return -ENOSPC; \
|
|
|
|
\
|
|
|
|
q->wrk[cb] = *w; \
|
|
|
|
__sync_synchronize(); \
|
|
|
|
if (__sync_bool_compare_and_swap(&q->bottom, cb, cb + 1) == false) assert(0); \
|
|
|
|
\
|
|
|
|
return 0; \
|
|
|
|
} \
|
|
|
|
\
|
|
|
|
/* Use mutual exclusion locks around push/pop if multi-threaded. */ \
|
|
|
|
static inline int deque_pop_##name(struct deque_##name *q, type *w) \
|
|
|
|
{ \
|
|
|
|
long ct = 0, sz = 0; \
|
|
|
|
long cb = q->bottom - 1; \
|
|
|
|
int ret = 0; \
|
|
|
|
\
|
|
|
|
if (__sync_bool_compare_and_swap(&q->bottom, cb + 1, cb) == false) assert(0); \
|
|
|
|
\
|
|
|
|
ct = q->top; \
|
|
|
|
sz = cb - ct; \
|
|
|
|
if (sz < 0) { \
|
|
|
|
if (__sync_bool_compare_and_swap(&q->bottom, cb, ct) == false) assert(0); \
|
|
|
|
\
|
|
|
|
return -ENOENT; \
|
|
|
|
} \
|
|
|
|
\
|
|
|
|
*w = q->wrk[cb]; \
|
|
|
|
if (sz > 0) return 0; \
|
|
|
|
\
|
|
|
|
ret = __sync_bool_compare_and_swap(&q->top, ct, ct + 1); \
|
|
|
|
if (__sync_bool_compare_and_swap(&q->bottom, cb, ct + 1) == false) assert(0); \
|
|
|
|
if (ret == false) { \
|
|
|
|
*w = NULL; \
|
|
|
|
return -ENOENT; \
|
|
|
|
} \
|
|
|
|
\
|
|
|
|
return 0; \
|
|
|
|
} \
|
|
|
|
/** \
|
|
|
|
* deque_steal \
|
|
|
|
* @param deque \
|
|
|
|
* @param w pointer to location to copy stolen type to \
|
|
|
|
* @returns 0 if successful, -2 if empty, -11 if unable to perform atomic operation \
|
|
|
|
*/ \
|
|
|
|
static inline int deque_steal_##name(struct deque_##name *deque, type *w) \
|
|
|
|
{ \
|
|
|
|
long ct, cb; \
|
|
|
|
\
|
|
|
|
ct = deque->top; \
|
|
|
|
cb = deque->bottom; \
|
|
|
|
\
|
|
|
|
/* Empty */ \
|
|
|
|
if (ct >= cb) return -ENOENT; \
|
|
|
|
\
|
|
|
|
*w = deque->wrk[ct]; \
|
|
|
|
if (__sync_bool_compare_and_swap(&deque->top, ct, ct + 1) == false) return -EAGAIN; \
|
|
|
|
\
|
|
|
|
return 0; \
|
|
|
|
}
|
|
|
|
|
|
|
|
#endif /* DEQUE_H */
|