Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions parsec/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ set(BASE_SOURCES
class/parsec_value_array.c
class/parsec_hash_table.c
class/parsec_rwlock.c
class/parsec_heap.c
class/parsec_rbtree.c
class/parsec_future.c
class/parsec_datacopy_future.c
Expand Down
76 changes: 76 additions & 0 deletions parsec/class/lifo.h
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
* Copyright (c) 2009-2023 The University of Tennessee and The University
* of Tennessee Research Foundation. All rights
* reserved.
* Copyright (c) 2026 Stony Brook University. All rights reserved.
*/

#ifndef LIFO_H_HAS_BEEN_INCLUDED
Expand Down Expand Up @@ -232,6 +233,20 @@ LIFO_STATIC_INLINE int parsec_lifo_nolock_is_empty( parsec_lifo_t* lifo ) {
return (NULL == lifo->lifo_head.data.item);
}

/* Detach all elements in the chain */
#if defined(PARSEC_DEBUG_PARANOID)
#define PARSEC_CHAIN_DETACH(item) { \
parsec_list_item_t *_item = (item); \
while (_item != NULL) { \
parsec_list_item_t *next = (parsec_list_item_t *) _item->list_next; \
PARSEC_ITEM_DETACH(_item); \
_item = next; \
} \
}
#else
#define PARSEC_CHAIN_DETACH(item) do { (void)(item); } while(0)
#endif

#if defined(PARSEC_ATOMIC_HAS_ATOMIC_CAS_INT128)
/* Add one element to the FIFO. Returns true if successful, false otherwise.
*/
Expand Down Expand Up @@ -356,6 +371,29 @@ LIFO_STATIC_INLINE parsec_list_item_t* parsec_lifo_try_pop( parsec_lifo_t* lifo
return NULL;
}

LIFO_STATIC_INLINE parsec_list_item_t *
parsec_lifo_detach_chain(parsec_lifo_t *lifo)
{
parsec_counted_pointer_t old_head;
do {
old_head.data.guard.counter = lifo->lifo_head.data.guard.counter;
parsec_atomic_rmb();
old_head.data.item = lifo->lifo_head.data.item;
if (NULL == old_head.data.item) return NULL;
} while (!parsec_update_counted_pointer(&lifo->lifo_head, old_head, NULL));
parsec_atomic_wmb();
parsec_list_item_t *item = old_head.data.item;
#if defined(PARSEC_DEBUG_PARANOID)
while (item != NULL) {
parsec_list_item_t *next = (parsec_list_item_t *) item->list_next;
PARSEC_ITEM_DETACH(item);
item = next;
}
#endif
PARSEC_CHAIN_DETACH(item);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 — this breaks PARSEC_DEBUG_PARANOID chain detachment. In the CAS128 implementation, the preceding loop advances item to NULL, so this call receives NULL and the function returns NULL after atomically removing every queued task. In the LLSC and lock-backed implementations, PARSEC_CHAIN_DETACH calls PARSEC_ITEM_DETACH on each element, which poisons list_next/list_prev with 0xdeadbeef; parsec_heap_push_chain() then attempts to traverse those poisoned links.

Please preserve the chain links until heap insertion has saved each next pointer, while still performing the paranoid ownership/refcount bookkeeping. The duplicate CAS128 detach loop should also be removed. A paranoid-build test that detaches and inserts a multi-item chain would catch both failure modes.

return item;
}

#elif defined(PARSEC_ATOMIC_HAS_ATOMIC_LLSC_PTR)

LIFO_STATIC_INLINE void _parsec_lifo_release_cpu (void)
Expand Down Expand Up @@ -468,6 +506,23 @@ LIFO_STATIC_INLINE parsec_list_item_t* parsec_lifo_try_pop( parsec_lifo_t* lifo
return item;
}

LIFO_STATIC_INLINE parsec_list_item_t *
parsec_lifo_detach_chain(parsec_lifo_t *lifo)
{
parsec_list_item_t *item;
int attempt = 0;
do {
if (++attempt == 5) {
_parsec_lifo_release_cpu();
attempt = 0;
}
item = (parsec_list_item_t *)parsec_atomic_ll_ptr((long *)&lifo->lifo_head.data.item);
if (NULL == item) return NULL;
} while (!parsec_atomic_sc_ptr((long *)&lifo->lifo_head.data.item, (intptr_t)NULL));
parsec_atomic_wmb();
PARSEC_CHAIN_DETACH(item);
return item;
}

#else /* defined(PARSEC_ATOMIC_HAS_ATOMIC_CAS_INT128) || defined(PARSEC_ATOMIC_HAS_ATOMIC_LLSC_PTR) */

Expand Down Expand Up @@ -547,6 +602,18 @@ LIFO_STATIC_INLINE parsec_list_item_t *parsec_lifo_try_pop(parsec_lifo_t* lifo)
return item;
}

LIFO_STATIC_INLINE parsec_list_item_t *
parsec_lifo_detach_chain(parsec_lifo_t *lifo)
{
parsec_list_item_t *item;
parsec_atomic_lock(&lifo->lifo_head.data.guard.lock);
item = lifo->lifo_head.data.item;
lifo->lifo_head.data.item = NULL;
parsec_atomic_unlock(&lifo->lifo_head.data.guard.lock);
PARSEC_CHAIN_DETACH(item);
return item;
}

#endif /* defined(PARSEC_ATOMIC_HAS_ATOMIC_CAS_INT128) || defined(PARSEC_ATOMIC_HAS_ATOMIC_LLSC_PTR) */

LIFO_STATIC_INLINE void parsec_lifo_nolock_push( parsec_lifo_t* lifo,
Expand Down Expand Up @@ -583,6 +650,15 @@ LIFO_STATIC_INLINE parsec_list_item_t* parsec_lifo_nolock_pop( parsec_lifo_t* li
return item;
}

LIFO_STATIC_INLINE parsec_list_item_t *
parsec_lifo_nolock_detach_chain(parsec_lifo_t *lifo)
{
parsec_list_item_t *item = lifo->lifo_head.data.item;
lifo->lifo_head.data.item = NULL;
PARSEC_CHAIN_DETACH(item);
return item;
}

/**
* @brief Allocate a lifo item.
*
Expand Down
204 changes: 204 additions & 0 deletions parsec/class/parsec_heap.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,204 @@
/*
* Copyright (c) 2026 Stony Brook University. All rights reserved.
*/

#include "parsec/parsec_config.h"
#include "parsec/class/parsec_heap.h"
#include "parsec/constants.h"

#include <assert.h>

/* COMPARISON_VAL(ptr, offset) — reads *(int*) at byte offset inside ptr.
* Defined in parsec_config_bottom.h, included transitively via parsec_config.h. */

/* left child = list_prev
* right child = list_next
* Same convention as parsec/maxheap.c and parsec_rbtree.c. */
#define HLEFT(item) ((parsec_list_item_t *)(item)->list_prev)
#define HRIGHT(item) ((parsec_list_item_t *)(item)->list_next)
#define HSET_LEFT(it, v) ((it)->list_prev = (volatile struct parsec_list_item_s *)(v))
#define HSET_RIGHT(it, v) ((it)->list_next = (volatile struct parsec_list_item_s *)(v))

static inline int heap_cmp(const parsec_heap_t *h,
const parsec_list_item_t *a,
const parsec_list_item_t *b)
{
int va = COMPARISON_VAL(a, h->comp_offset);
int vb = COMPARISON_VAL(b, h->comp_offset);
return (va > vb) - (va < vb);
}
Comment on lines +22 to +29

/* Maximum depth of the path array. 64 supports heaps of up to 2^64 elements. */
#define HEAP_MAX_DEPTH 64

int parsec_heap_push(parsec_heap_t *heap, parsec_list_item_t *item)
{
HSET_LEFT(item, NULL);
HSET_RIGHT(item, NULL);
heap->size++;

if (heap->size == 1) {
heap->top = item;
return PARSEC_SUCCESS;
}

/* Find the insertion point by following the bit path of 'size'.
* After the leading 1-bit, each subsequent bit chooses right (1) or left (0).
* Save ancestors for the sift-up pass.
* (Same bit-navigation used by heap_insert in parsec/maxheap.c.) */
parsec_list_item_t *path[HEAP_MAX_DEPTH];
int depth = 0;

size_t size = heap->size;
size_t bitmask = 1;
while (bitmask <= size) bitmask <<= 1;
bitmask >>= 2; /* position at bit just below the leading 1 */

parsec_list_item_t *node = heap->top;
path[depth++] = node;
while (bitmask > 1) {
node = (bitmask & size) ? HRIGHT(node) : HLEFT(node);
path[depth++] = node;
bitmask >>= 1;
}
/* Attach item as left (0) or right (1) child */
if (bitmask & size) HSET_RIGHT(node, item);
else HSET_LEFT(node, item);

/* Sift up: walk from immediate parent (path[depth-1]) toward root */
int level = depth - 1;
while (level >= 0) {
parsec_list_item_t *parent = path[level];
if (heap_cmp(heap, item, parent) <= 0) break;

/* Fix grandparent to point to item instead of parent */
if (level > 0) {
parsec_list_item_t *gp = path[level - 1];
if (HLEFT(gp) == parent) HSET_LEFT(gp, item);
else HSET_RIGHT(gp, item);
} else {
heap->top = item;
}

/* Swap item and parent: item takes parent's position, parent takes item's */
parsec_list_item_t *pl = HLEFT(parent);
parsec_list_item_t *pr = HRIGHT(parent);
HSET_LEFT(parent, HLEFT(item));
HSET_RIGHT(parent, HRIGHT(item));
if (pl == item) {
HSET_LEFT(item, parent);
HSET_RIGHT(item, pr);
} else {
HSET_LEFT(item, pl);
HSET_RIGHT(item, parent);
}
level--;
}
return PARSEC_SUCCESS;
}

parsec_list_item_t *parsec_heap_pop(parsec_heap_t *heap)
{
if (0 == heap->size) return NULL;

parsec_list_item_t *root = heap->top;

if (heap->size == 1) {
heap->top = NULL;
heap->size = 0;
PARSEC_LIST_ITEM_SINGLETON(root);
return root;
}

/* Navigate to the parent of the 'last' node (rightmost node in the
* bottom level), then detach it. Track which side it was on so we
* can correctly wire it into root's position even after clearing the
* pointer. */
size_t size = heap->size;
size_t bitmask = 1;
while (bitmask <= size) bitmask <<= 1;
bitmask >>= 2;

parsec_list_item_t *parent = heap->top;
while (bitmask > 1) {
parent = (bitmask & size) ? HRIGHT(parent) : HLEFT(parent);
bitmask >>= 1;
}

parsec_list_item_t *last;
int last_was_right = (int)(bitmask & size);
if (last_was_right) {
last = HRIGHT(parent);
HSET_RIGHT(parent, NULL);
} else {
last = HLEFT(parent);
HSET_LEFT(parent, NULL);
}
assert(last != NULL);

/* Wire 'last' into root's place, inheriting root's children. */
if (parent != root) {
HSET_LEFT(last, HLEFT(root));
HSET_RIGHT(last, HRIGHT(root));
} else {
/* last was a direct child of root; one pointer was already cleared above */
if (last_was_right) {
HSET_LEFT(last, HLEFT(root)); /* root's left is intact */
HSET_RIGHT(last, NULL); /* last is a leaf */
} else {
HSET_LEFT(last, NULL); /* last is a leaf */
HSET_RIGHT(last, HRIGHT(root)); /* root's right is intact */
}
}
heap->top = last;
heap->size--;

/* Sift down: swap last with the larger child until heap order is restored.
* Track parent and which side we came from (no extra allocation needed). */
parsec_list_item_t *bubbler = last;
parsec_list_item_t *par = NULL;
int from_right = 0;
while (1) {
parsec_list_item_t *left = HLEFT(bubbler);
parsec_list_item_t *right = HRIGHT(bubbler);
int go_left = (left && heap_cmp(heap, left, bubbler) > 0 &&

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 — equal-priority tasks can starve indefinitely. Equal elements neither sift up nor sift down. After a pop, the newest last leaf replaces the root and remains there because neither child is strictly greater. If one or more equal-priority tasks arrive before each subsequent pop, newly arrived work keeps cycling through the root while the older backlog remains below it indefinitely. Most device tasks use the same default priority, so this is a common arrival pattern, not just an edge case.

Please provide stable/FIFO ordering within a priority, for example with a monotonic enqueue sequence used as the secondary comparison key or per-priority FIFO queues. Add a test that continuously inserts and pops equal-priority elements while verifying that the initial backlog drains.

(!right || heap_cmp(heap, left, right) >= 0));
int go_right = (!go_left && right && heap_cmp(heap, right, bubbler) > 0);
if (!go_left && !go_right) break;

parsec_list_item_t *swap = go_left ? left : right;
if (par) {
if (from_right) HSET_RIGHT(par, swap);
else HSET_LEFT(par, swap);
} else {
heap->top = swap;
}
HSET_LEFT(bubbler, HLEFT(swap));
HSET_RIGHT(bubbler, HRIGHT(swap));
if (go_left) {
HSET_LEFT(swap, bubbler);
HSET_RIGHT(swap, right);
from_right = 0;
} else {
HSET_LEFT(swap, left);
HSET_RIGHT(swap, bubbler);
from_right = 1;
}
par = swap;
}

PARSEC_LIST_ITEM_SINGLETON(root);
return root;
}

int parsec_heap_push_chain(parsec_heap_t *heap, parsec_list_item_t *chain)
{
parsec_list_item_t *item = chain;
do {
/* Capture list_next before parsec_heap_push repurposes it as right-child */
parsec_list_item_t *next = (parsec_list_item_t *)item->list_next;
parsec_heap_push(heap, item);
item = next;
} while (item != chain && item != NULL);
return PARSEC_SUCCESS;
}
Loading
Loading