Skip to content

opal/asm: change ll/sc atomics to macros #5209

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 2 commits into from
Jun 1, 2018
Merged
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
7 changes: 2 additions & 5 deletions README
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ Copyright (c) 2010 Oak Ridge National Labs. All rights reserved.
Copyright (c) 2011 University of Houston. All rights reserved.
Copyright (c) 2013-2017 Intel, Inc. All rights reserved.
Copyright (c) 2015 NVIDIA Corporation. All rights reserved.
Copyright (c) 2017 Los Alamos National Security, LLC. All rights
Copyright (c) 2017-2018 Los Alamos National Security, LLC. All rights
reserved.
Copyright (c) 2017 Research Organization for Information Science
and Technology (RIST). All rights reserved.
Expand Down Expand Up @@ -143,10 +143,7 @@ General notes
Platform Notes
--------------

- ARM and POWER users may experience intermittent hangs when Open MPI
is compiled with low optimization settings, due to an issue with our
atomic list implementation. We recommend compiling with -O3
optimization, both for performance reasons and to avoid this hang.
- N/A

Compiler Notes
--------------
Expand Down
36 changes: 24 additions & 12 deletions opal/class/opal_fifo.h
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@
* All rights reserved.
* Copyright (c) 2007 Voltaire All rights reserved.
* Copyright (c) 2010 IBM Corporation. All rights reserved.
* Copyright (c) 2014-2017 Los Alamos National Security, LLC. All rights
* Copyright (c) 2014-2018 Los Alamos National Security, LLC. All rights
* reseved.
* $COPYRIGHT$
*
Expand Down Expand Up @@ -183,9 +183,10 @@ static inline opal_list_item_t *opal_fifo_pop_atomic (opal_fifo_t *fifo)
static inline opal_list_item_t *opal_fifo_push_atomic (opal_fifo_t *fifo,
opal_list_item_t *item)
{
const opal_list_item_t * const ghost = &fifo->opal_fifo_ghost;
opal_list_item_t *tail_item;

item->opal_list_next = &fifo->opal_fifo_ghost;
item->opal_list_next = (opal_list_item_t *) ghost;

opal_atomic_wmb ();

Expand All @@ -194,7 +195,7 @@ static inline opal_list_item_t *opal_fifo_push_atomic (opal_fifo_t *fifo,

opal_atomic_wmb ();
Copy link
Member

Choose a reason for hiding this comment

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

Why we need this wmb ? there is nothing to flush, except the atomic swap right above.

Copy link
Member

Choose a reason for hiding this comment

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

In fact I am not sure I understand how this code can be correct in the case where 2 threads are doing conflicting operations on a FIFO that contains only one element. Imagine a thread that pop while the second one push. The operation at line 202 (tail_item->opal_list_next = item) conflicts with the opal_atomic_sc_ptr in the pop, and can lead to a case where the head is NULL (it contains what the previous element assumed was his next) but the tail is not (it contains the result of the previous push).

Copy link
Member Author

Choose a reason for hiding this comment

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

This scenario is tested by test/class/opal_fifo.c. The exhaustive test causes the threads to empty the fifo.

The code that keeps things consistent is at line 276. If the pop detected that the fifo was empty but something was being pushed (next == ghost && tail != item) it waits until the next pointer has been updated then sets the head to the new item. This is the same strategy used by vader and nemesis.

Copy link
Member Author

Choose a reason for hiding this comment

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

Yeah, looking at it there is no way it can get to one of the following lines without the read and write completing anyway. That means the wmb does nothing. It shouldn't hurt though.

Copy link
Contributor

Choose a reason for hiding this comment

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

If this is not critical path, it is fine ...


if (&fifo->opal_fifo_ghost == tail_item) {
if (ghost == tail_item) {
/* update the head */
fifo->opal_fifo_head.data.item = item;
} else {
Expand All @@ -212,12 +213,22 @@ static inline opal_list_item_t *opal_fifo_push_atomic (opal_fifo_t *fifo,
*/
static inline opal_list_item_t *opal_fifo_pop_atomic (opal_fifo_t *fifo)
{
opal_list_item_t *item, *next, *ghost = &fifo->opal_fifo_ghost;
const opal_list_item_t * const ghost = &fifo->opal_fifo_ghost;

#if OPAL_HAVE_ATOMIC_LLSC_PTR
register opal_list_item_t *item, *next;
int attempt = 0, ret = 0;

/* use load-linked store-conditional to avoid ABA issues */
do {
item = opal_atomic_ll_ptr (&fifo->opal_fifo_head.data.item);
if (++attempt == 5) {
/* deliberatly suspend this thread to allow other threads to run. this should
* only occur during periods of contention on the lifo. */
_opal_lifo_release_cpu ();
attempt = 0;
}

opal_atomic_ll_ptr(&fifo->opal_fifo_head.data.item, item);
if (ghost == item) {
if (ghost == fifo->opal_fifo_tail.data.item) {
return NULL;
Expand All @@ -229,11 +240,12 @@ static inline opal_list_item_t *opal_fifo_pop_atomic (opal_fifo_t *fifo)
}

next = (opal_list_item_t *) item->opal_list_next;
if (opal_atomic_sc_ptr (&fifo->opal_fifo_head.data.item, next)) {
break;
}
} while (1);
opal_atomic_sc_ptr(&fifo->opal_fifo_head.data.item, next, ret);
} while (!ret);

#else
opal_list_item_t *item, *next;

/* protect against ABA issues by "locking" the head */
do {
if (!opal_atomic_swap_32 ((volatile int32_t *) &fifo->opal_fifo_head.data.counter, 1)) {
Expand All @@ -258,10 +270,10 @@ static inline opal_list_item_t *opal_fifo_pop_atomic (opal_fifo_t *fifo)
if (ghost == next) {
void *tmp = item;

if (!opal_atomic_compare_exchange_strong_ptr (&fifo->opal_fifo_tail.data.item, &tmp, ghost)) {
while (ghost == item->opal_list_next) {
if (!opal_atomic_compare_exchange_strong_ptr (&fifo->opal_fifo_tail.data.item, &tmp, (void *) ghost)) {
do {
opal_atomic_rmb ();
}
} while (ghost == item->opal_list_next);

fifo->opal_fifo_head.data.item = (opal_list_item_t *) item->opal_list_next;
}
Expand Down
11 changes: 6 additions & 5 deletions opal/class/opal_lifo.h
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@
* All rights reserved.
* Copyright (c) 2007 Voltaire All rights reserved.
* Copyright (c) 2010 IBM Corporation. All rights reserved.
* Copyright (c) 2014-2017 Los Alamos National Security, LLC. All rights
* Copyright (c) 2014-2018 Los Alamos National Security, LLC. All rights
* reseved.
* Copyright (c) 2016-2018 Research Organization for Information Science
* and Technology (RIST). All rights reserved.
Expand Down Expand Up @@ -206,8 +206,8 @@ static inline void _opal_lifo_release_cpu (void)
*/
static inline opal_list_item_t *opal_lifo_pop_atomic (opal_lifo_t* lifo)
{
opal_list_item_t *item, *next;
int attempt = 0;
register opal_list_item_t *item, *next;
int attempt = 0, ret;

do {
if (++attempt == 5) {
Expand All @@ -217,13 +217,14 @@ static inline opal_list_item_t *opal_lifo_pop_atomic (opal_lifo_t* lifo)
attempt = 0;
}

item = (opal_list_item_t *) opal_atomic_ll_ptr (&lifo->opal_lifo_head.data.item);
opal_atomic_ll_ptr(&lifo->opal_lifo_head.data.item, item);
if (&lifo->opal_lifo_ghost == item) {
return NULL;
}

next = (opal_list_item_t *) item->opal_list_next;
} while (!opal_atomic_sc_ptr (&lifo->opal_lifo_head.data.item, next));
opal_atomic_sc_ptr(&lifo->opal_lifo_head.data.item, next, ret);
} while (!ret);

opal_atomic_wmb ();

Expand Down
94 changes: 50 additions & 44 deletions opal/include/opal/sys/arm64/atomic.h
Original file line number Diff line number Diff line change
Expand Up @@ -162,28 +162,31 @@ static inline bool opal_atomic_compare_exchange_strong_rel_32 (volatile int32_t
return ret;
}

static inline int32_t opal_atomic_ll_32 (volatile int32_t *addr)
{
int32_t ret;

__asm__ __volatile__ ("ldaxr %w0, [%1] \n"
: "=&r" (ret)
: "r" (addr));

return ret;
}

static inline int opal_atomic_sc_32 (volatile int32_t *addr, int32_t newval)
{
int ret;

__asm__ __volatile__ ("stlxr %w0, %w2, [%1] \n"
: "=&r" (ret)
: "r" (addr), "r" (newval)
: "cc", "memory");

return ret == 0;
}
#define opal_atomic_ll_32(addr, ret) \
do { \
volatile int32_t *_addr = (addr); \
int32_t _ret; \
\
__asm__ __volatile__ ("ldaxr %w0, [%1] \n" \
: "=&r" (_ret) \
: "r" (_addr)); \
\
ret = (typeof(ret)) _ret; \
Copy link
Member

Choose a reason for hiding this comment

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

Does this conversion work for any signed type ?

} while (0)

#define opal_atomic_sc_32(addr, newval, ret) \
do { \
volatile int32_t *_addr = (addr); \
int32_t _newval = (int32_t) newval; \
int _ret; \
\
__asm__ __volatile__ ("stlxr %w0, %w2, [%1] \n" \
: "=&r" (_ret) \
: "r" (_addr), "r" (_newval) \
: "cc", "memory"); \
\
ret = (_ret == 0); \
} while (0)

static inline bool opal_atomic_compare_exchange_strong_64 (volatile int64_t *addr, int64_t *oldval, int64_t newval)
{
Expand Down Expand Up @@ -269,28 +272,31 @@ static inline bool opal_atomic_compare_exchange_strong_rel_64 (volatile int64_t
return ret;
}

static inline int64_t opal_atomic_ll_64 (volatile int64_t *addr)
{
int64_t ret;

__asm__ __volatile__ ("ldaxr %0, [%1] \n"
: "=&r" (ret)
: "r" (addr));

return ret;
}

static inline int opal_atomic_sc_64 (volatile int64_t *addr, int64_t newval)
{
int ret;

__asm__ __volatile__ ("stlxr %w0, %2, [%1] \n"
: "=&r" (ret)
: "r" (addr), "r" (newval)
: "cc", "memory");

return ret == 0;
}
#define opal_atomic_ll_64(addr, ret) \
do { \
volatile int64_t *_addr = (addr); \
int64_t _ret; \
\
__asm__ __volatile__ ("ldaxr %0, [%1] \n" \
: "=&r" (_ret) \
: "r" (_addr)); \
\
ret = (typeof(ret)) _ret; \
} while (0)

#define opal_atomic_sc_64(addr, newval, ret) \
do { \
volatile int64_t *_addr = (addr); \
int64_t _newval = (int64_t) newval; \
int _ret; \
\
__asm__ __volatile__ ("stlxr %w0, %2, [%1] \n" \
: "=&r" (_ret) \
: "r" (_addr), "r" (_newval) \
: "cc", "memory"); \
\
ret = (_ret == 0); \
} while (0)

#define OPAL_ASM_MAKE_ATOMIC(type, bits, name, inst, reg) \
static inline type opal_atomic_fetch_ ## name ## _ ## bits (volatile type *addr, type value) \
Expand Down
8 changes: 4 additions & 4 deletions opal/include/opal/sys/atomic_impl.h
Original file line number Diff line number Diff line change
Expand Up @@ -308,15 +308,15 @@ OPAL_ATOMIC_DEFINE_CMPXCG_PTR_XX(_rel_)

#if SIZEOF_VOID_P == 4 && OPAL_HAVE_ATOMIC_LLSC_32

#define opal_atomic_ll_ptr(addr) (void *) opal_atomic_ll_32((int32_t *) addr)
#define opal_atomic_sc_ptr(addr, newval) opal_atomic_sc_32((int32_t *) addr, (int32_t) newval)
#define opal_atomic_ll_ptr(addr, ret) opal_atomic_ll_32((volatile int32_t *) (addr), ret)
#define opal_atomic_sc_ptr(addr, value, ret) opal_atomic_sc_32((volatile int32_t *) (addr), (intptr_t) (value), ret)

#define OPAL_HAVE_ATOMIC_LLSC_PTR 1

#elif SIZEOF_VOID_P == 8 && OPAL_HAVE_ATOMIC_LLSC_64

#define opal_atomic_ll_ptr(addr) (void *) opal_atomic_ll_64((int64_t *) addr)
#define opal_atomic_sc_ptr(addr, newval) opal_atomic_sc_64((int64_t *) addr, (int64_t) newval)
#define opal_atomic_ll_ptr(addr, ret) opal_atomic_ll_64((volatile int64_t *) (addr), ret)
#define opal_atomic_sc_ptr(addr, value, ret) opal_atomic_sc_64((volatile int64_t *) (addr), (intptr_t) (value), ret)

#define OPAL_HAVE_ATOMIC_LLSC_PTR 1

Expand Down
107 changes: 56 additions & 51 deletions opal/include/opal/sys/powerpc/atomic.h
Original file line number Diff line number Diff line change
Expand Up @@ -165,31 +165,35 @@ static inline bool opal_atomic_compare_exchange_strong_32 (volatile int32_t *add
return ret;
}

static inline int32_t opal_atomic_ll_32 (volatile int32_t *addr)
{
int32_t ret;

__asm__ __volatile__ ("lwarx %0, 0, %1 \n\t"
: "=&r" (ret)
: "r" (addr)
);
return ret;
}

static inline int opal_atomic_sc_32 (volatile int32_t *addr, int32_t newval)
{
int32_t ret, foo;

__asm__ __volatile__ (" stwcx. %4, 0, %3 \n\t"
" li %0,0 \n\t"
" bne- 1f \n\t"
" ori %0,%0,1 \n\t"
"1:"
: "=r" (ret), "=m" (*addr), "=r" (foo)
: "r" (addr), "r" (newval)
: "cc", "memory");
return ret;
}
/* NTH: the LL/SC support is done through macros due to issues with non-optimized builds. The reason
* is that even with an always_inline attribute the compiler may still emit instructions to store then
* load the arguments to/from the stack. This sequence may cause the ll reservation to be cancelled. */
#define opal_atomic_ll_32(addr, ret) \
Copy link
Contributor

Choose a reason for hiding this comment

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

Makes sense. This is useful comment that should be present in some upper layer. It true for Power and Arm

Copy link
Member Author

Choose a reason for hiding this comment

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

Yeah. I plan to add some additional documentation detailing what live-lock is with LL/SC.

do { \
volatile int32_t *_addr = (addr); \
int32_t _ret; \
__asm__ __volatile__ ("lwarx %0, 0, %1 \n\t" \
: "=&r" (_ret) \
: "r" (_addr) \
); \
ret = (typeof(ret)) _ret; \
} while (0)

#define opal_atomic_sc_32(addr, value, ret) \
do { \
volatile int32_t *_addr = (addr); \
int32_t _ret, _foo, _newval = (int32_t) value; \
\
__asm__ __volatile__ (" stwcx. %4, 0, %3 \n\t" \
" li %0,0 \n\t" \
" bne- 1f \n\t" \
" ori %0,%0,1 \n\t" \
"1:" \
: "=r" (_ret), "=m" (*_addr), "=r" (_foo) \
: "r" (_addr), "r" (_newval) \
: "cc", "memory"); \
ret = _ret; \
} while (0)

/* these two functions aren't inlined in the non-gcc case because then
there would be two function calls (since neither cmpset_32 nor
Expand Down Expand Up @@ -278,32 +282,33 @@ static inline bool opal_atomic_compare_exchange_strong_64 (volatile int64_t *add
return ret;
}

static inline int64_t opal_atomic_ll_64(volatile int64_t *addr)
{
int64_t ret;

__asm__ __volatile__ ("ldarx %0, 0, %1 \n\t"
: "=&r" (ret)
: "r" (addr)
);
return ret;
}

static inline int opal_atomic_sc_64(volatile int64_t *addr, int64_t newval)
{
int32_t ret;

__asm__ __volatile__ (" stdcx. %2, 0, %1 \n\t"
" li %0,0 \n\t"
" bne- 1f \n\t"
" ori %0,%0,1 \n\t"
"1:"
: "=r" (ret)
: "r" (addr), "r" (OPAL_ASM_VALUE64(newval))
: "cc", "memory");
return ret;
}

#define opal_atomic_ll_64(addr, ret) \
do { \
volatile int64_t *_addr = (addr); \
int64_t _ret; \
__asm__ __volatile__ ("ldarx %0, 0, %1 \n\t" \
: "=&r" (_ret) \
: "r" (_addr) \
); \
ret = (typeof(ret)) _ret; \
} while (0)

#define opal_atomic_sc_64(addr, value, ret) \
do { \
volatile int64_t *_addr = (addr); \
int64_t _foo, _newval = (int64_t) value; \
int32_t _ret; \
\
__asm__ __volatile__ (" stdcx. %2, 0, %1 \n\t" \
" li %0,0 \n\t" \
" bne- 1f \n\t" \
" ori %0,%0,1 \n\t" \
"1:" \
: "=r" (_ret) \
: "r" (_addr), "r" (OPAL_ASM_VALUE64(_newval)) \
: "cc", "memory"); \
ret = _ret; \
} while (0)

static inline int64_t opal_atomic_swap_64(volatile int64_t *addr, int64_t newval)
{
Expand Down