On Tue, 22 Nov 2016 13:36:11 -0800 Wan-Teh Chang <wtc-at-google....@ffmpeg.org> wrote:
> Make the one-time initialization in av_get_cpu_flags() thread-safe. The > static variables |flags| and |checked| in libavutil/cpu.c are read and > written using normal load and store operations. These are considered as > data races. The fix is to use atomic load and store operations. The fix > also eliminates the |checked| variable because the invalid value of -1 > for |flags| can be used to indicate the same condition. > > The fix requires a new atomic integer compare and swap function. Since > the fix does not need memory barriers, "relaxed" variants of > avpriv_atomic_int_get() and avpriv_atomic_int_set() are also added. > > Add a test program libavutil/tests/cpu_init.c to verify the one-time > initialization in av_get_cpu_flags() is thread-safe. > > Co-author: Dmitry Vyukov of Google, which suggested the data race fix. > > Signed-off-by: Wan-Teh Chang <w...@google.com> > --- > libavutil/Makefile | 1 + > libavutil/atomic.c | 40 ++++++++++++++++++++++++++ > libavutil/atomic.h | 34 ++++++++++++++++++++-- > libavutil/atomic_gcc.h | 33 +++++++++++++++++++++ > libavutil/atomic_suncc.h | 19 ++++++++++++ > libavutil/atomic_win32.h | 21 ++++++++++++++ > libavutil/cpu.c | 41 ++++++++++++++------------ > libavutil/cpu.h | 2 -- > libavutil/tests/atomic.c | 13 +++++++++ > libavutil/tests/cpu_init.c | 72 > ++++++++++++++++++++++++++++++++++++++++++++++ > 10 files changed, 253 insertions(+), 23 deletions(-) > create mode 100644 libavutil/tests/cpu_init.c > > diff --git a/libavutil/Makefile b/libavutil/Makefile > index 0fa90fe..732961a 100644 > --- a/libavutil/Makefile > +++ b/libavutil/Makefile > @@ -187,6 +187,7 @@ TESTPROGS = adler32 > \ > camellia \ > color_utils \ > cpu \ > + cpu_init \ > crc \ > des \ > dict \ > diff --git a/libavutil/atomic.c b/libavutil/atomic.c > index 64cff25..7bce013 100644 > --- a/libavutil/atomic.c > +++ b/libavutil/atomic.c > @@ -40,6 +40,11 @@ int avpriv_atomic_int_get(volatile int *ptr) > return res; > } > > +int avpriv_atomic_int_get_relaxed(volatile int *ptr) > +{ > + return avpriv_atomic_int_get(ptr); > +} > + > void avpriv_atomic_int_set(volatile int *ptr, int val) > { > pthread_mutex_lock(&atomic_lock); > @@ -47,6 +52,11 @@ void avpriv_atomic_int_set(volatile int *ptr, int val) > pthread_mutex_unlock(&atomic_lock); > } > > +void avpriv_atomic_int_set_relaxed(volatile int *ptr, int val) > +{ > + avpriv_atomic_int_set(ptr, val); > +} > + > int avpriv_atomic_int_add_and_fetch(volatile int *ptr, int inc) > { > int res; > @@ -59,6 +69,17 @@ int avpriv_atomic_int_add_and_fetch(volatile int *ptr, int > inc) > return res; > } > > +int avpriv_atomic_int_cas_relaxed(volatile int *ptr, int oldval, int newval) > +{ > + int ret; > + pthread_mutex_lock(&atomic_lock); > + ret = *ptr; > + if (ret == oldval) > + *ptr = newval; > + pthread_mutex_unlock(&atomic_lock); > + return ret; > +} > + > void *avpriv_atomic_ptr_cas(void * volatile *ptr, void *oldval, void *newval) > { > void *ret; > @@ -77,17 +98,36 @@ int avpriv_atomic_int_get(volatile int *ptr) > return *ptr; > } > > +int avpriv_atomic_int_get_relaxed(volatile int *ptr) > +{ > + return avpriv_atomic_int_get(ptr); > +} > + > void avpriv_atomic_int_set(volatile int *ptr, int val) > { > *ptr = val; > } > > +void avpriv_atomic_int_set_relaxed(volatile int *ptr, int val) > +{ > + avpriv_atomic_int_set(ptr, val); > +} > + > int avpriv_atomic_int_add_and_fetch(volatile int *ptr, int inc) > { > *ptr += inc; > return *ptr; > } > > +int avpriv_atomic_int_cas_relaxed(volatile int *ptr, int oldval, int newval) > +{ > + if (*ptr == oldval) { > + *ptr = newval; > + return oldval; > + } > + return *ptr; > +} > + > void *avpriv_atomic_ptr_cas(void * volatile *ptr, void *oldval, void *newval) > { > if (*ptr == oldval) { > diff --git a/libavutil/atomic.h b/libavutil/atomic.h > index 15906d2..c5aa1a4 100644 > --- a/libavutil/atomic.h > +++ b/libavutil/atomic.h > @@ -40,20 +40,38 @@ > * > * @param ptr atomic integer > * @return the current value of the atomic integer > - * @note This acts as a memory barrier. > + * @note This acts as a memory barrier with sequentially-consistent ordering. > */ > int avpriv_atomic_int_get(volatile int *ptr); > > /** > + * Load the current value stored in an atomic integer. > + * > + * @param ptr atomic integer > + * @return the current value of the atomic integer > + * @note This does NOT act as a memory barrier. > + */ > +int avpriv_atomic_int_get_relaxed(volatile int *ptr); > + > +/** > * Store a new value in an atomic integer. > * > * @param ptr atomic integer > * @param val the value to store in the atomic integer > - * @note This acts as a memory barrier. > + * @note This acts as a memory barrier with sequentially-consistent ordering. > */ > void avpriv_atomic_int_set(volatile int *ptr, int val); > > /** > + * Store a new value in an atomic integer. > + * > + * @param ptr atomic integer > + * @param val the value to store in the atomic integer > + * @note This does NOT act as a memory barrier. > + */ > +void avpriv_atomic_int_set_relaxed(volatile int *ptr, int val); > + > +/** > * Add a value to an atomic integer. > * > * @param ptr atomic integer > @@ -65,12 +83,24 @@ void avpriv_atomic_int_set(volatile int *ptr, int val); > int avpriv_atomic_int_add_and_fetch(volatile int *ptr, int inc); > > /** > + * Atomic integer compare and swap. > + * > + * @param ptr pointer to the integer to operate on > + * @param oldval do the swap if the current value of *ptr equals to oldval > + * @param newval value to replace *ptr with > + * @return the value of *ptr before comparison > + * @note This does NOT act as a memory barrier. > + */ > +int avpriv_atomic_int_cas_relaxed(volatile int *ptr, int oldval, int newval); > + > +/** > * Atomic pointer compare and swap. > * > * @param ptr pointer to the pointer to operate on > * @param oldval do the swap if the current value of *ptr equals to oldval > * @param newval value to replace *ptr with > * @return the value of *ptr before comparison > + * @note This acts as a memory barrier with sequentially-consistent ordering. > */ > void *avpriv_atomic_ptr_cas(void * volatile *ptr, void *oldval, void > *newval); > > diff --git a/libavutil/atomic_gcc.h b/libavutil/atomic_gcc.h > index 5f9fc49..15dd508 100644 > --- a/libavutil/atomic_gcc.h > +++ b/libavutil/atomic_gcc.h > @@ -36,6 +36,16 @@ static inline int atomic_int_get_gcc(volatile int *ptr) > #endif > } > > +#define avpriv_atomic_int_get_relaxed atomic_int_get_relaxed_gcc > +static inline int atomic_int_get_relaxed_gcc(volatile int *ptr) > +{ > +#if HAVE_ATOMIC_COMPARE_EXCHANGE > + return __atomic_load_n(ptr, __ATOMIC_RELAXED); > +#else > + return *ptr; > +#endif > +} > + > #define avpriv_atomic_int_set atomic_int_set_gcc > static inline void atomic_int_set_gcc(volatile int *ptr, int val) > { > @@ -47,6 +57,16 @@ static inline void atomic_int_set_gcc(volatile int *ptr, > int val) > #endif > } > > +#define avpriv_atomic_int_set_relaxed atomic_int_set_relaxed_gcc > +static inline void atomic_int_set_relaxed_gcc(volatile int *ptr, int val) > +{ > +#if HAVE_ATOMIC_COMPARE_EXCHANGE > + __atomic_store_n(ptr, val, __ATOMIC_RELAXED); > +#else > + *ptr = val; > +#endif > +} > + > #define avpriv_atomic_int_add_and_fetch atomic_int_add_and_fetch_gcc > static inline int atomic_int_add_and_fetch_gcc(volatile int *ptr, int inc) > { > @@ -57,6 +77,19 @@ static inline int atomic_int_add_and_fetch_gcc(volatile > int *ptr, int inc) > #endif > } > > +#define avpriv_atomic_int_cas_relaxed atomic_int_cas_relaxed_gcc > +static inline int atomic_int_cas_relaxed_gcc(volatile int *ptr, > + int oldval, int newval) > +{ > +#if HAVE_SYNC_VAL_COMPARE_AND_SWAP > + return __sync_val_compare_and_swap(ptr, oldval, newval); > +#else > + __atomic_compare_exchange_n(ptr, &oldval, newval, 0, __ATOMIC_RELAXED, > + __ATOMIC_RELAXED); > + return oldval; > +#endif > +} > + > #define avpriv_atomic_ptr_cas atomic_ptr_cas_gcc > static inline void *atomic_ptr_cas_gcc(void * volatile *ptr, > void *oldval, void *newval) > diff --git a/libavutil/atomic_suncc.h b/libavutil/atomic_suncc.h > index a75a37b..0997f23 100644 > --- a/libavutil/atomic_suncc.h > +++ b/libavutil/atomic_suncc.h > @@ -31,6 +31,12 @@ static inline int atomic_int_get_suncc(volatile int *ptr) > return *ptr; > } > > +#define avpriv_atomic_int_get_relaxed atomic_int_get_relaxed_suncc > +static inline int atomic_int_get_relaxed_suncc(volatile int *ptr) > +{ > + return *ptr; > +} > + > #define avpriv_atomic_int_set atomic_int_set_suncc > static inline void atomic_int_set_suncc(volatile int *ptr, int val) > { > @@ -38,12 +44,25 @@ static inline void atomic_int_set_suncc(volatile int > *ptr, int val) > __machine_rw_barrier(); > } > > +#define avpriv_atomic_int_set_relaxed atomic_int_set_relaxed_suncc > +static inline void atomic_int_set_relaxed_suncc(volatile int *ptr, int val) > +{ > + *ptr = val; > +} > + > #define avpriv_atomic_int_add_and_fetch atomic_int_add_and_fetch_suncc > static inline int atomic_int_add_and_fetch_suncc(volatile int *ptr, int inc) > { > return atomic_add_int_nv(ptr, inc); > } > > +#define avpriv_atomic_int_cas_relaxed atomic_int_cas_relaxed_suncc > +static inline int atomic_int_cas_relaxed_suncc(volatile int *ptr, > + int oldval, int newval) > +{ > + return atomic_cas_uint((volatile uint_t *)ptr, oldval, newval); > +} > + > #define avpriv_atomic_ptr_cas atomic_ptr_cas_suncc > static inline void *atomic_ptr_cas_suncc(void * volatile *ptr, > void *oldval, void *newval) > diff --git a/libavutil/atomic_win32.h b/libavutil/atomic_win32.h > index f729933..7ea0a56 100644 > --- a/libavutil/atomic_win32.h > +++ b/libavutil/atomic_win32.h > @@ -31,6 +31,12 @@ static inline int atomic_int_get_win32(volatile int *ptr) > return *ptr; > } > > +#define avpriv_atomic_int_get_relaxed atomic_int_get_relaxed_win32 > +static inline int atomic_int_get_relaxed_win32(volatile int *ptr) > +{ > + return *ptr; > +} > + > #define avpriv_atomic_int_set atomic_int_set_win32 > static inline void atomic_int_set_win32(volatile int *ptr, int val) > { > @@ -38,12 +44,27 @@ static inline void atomic_int_set_win32(volatile int > *ptr, int val) > MemoryBarrier(); > } > > +#define avpriv_atomic_int_set_relaxed atomic_int_set_relaxed_win32 > +static inline void atomic_int_set_relaxed_win32(volatile int *ptr, int val) > +{ > + *ptr = val; > +} > + > #define avpriv_atomic_int_add_and_fetch atomic_int_add_and_fetch_win32 > static inline int atomic_int_add_and_fetch_win32(volatile int *ptr, int inc) > { > return inc + InterlockedExchangeAdd(ptr, inc); > } > > +#define avpriv_atomic_int_cas_relaxed atomic_int_cas_relaxed_win32 > +static inline int atomic_int_cas_relaxed_win32(volatile int *ptr, > + int oldval, int newval) > +{ > + /* InterlockedCompareExchangeNoFence is available on Windows 8 or later > + * only. */ > + return InterlockedCompareExchange((volatile LONG *)ptr, newval, oldval); > +} > + > #define avpriv_atomic_ptr_cas atomic_ptr_cas_win32 > static inline void *atomic_ptr_cas_win32(void * volatile *ptr, > void *oldval, void *newval) > diff --git a/libavutil/cpu.c b/libavutil/cpu.c > index f5785fc..70b61de 100644 > --- a/libavutil/cpu.c > +++ b/libavutil/cpu.c > @@ -23,6 +23,7 @@ > #include "config.h" > #include "opt.h" > #include "common.h" > +#include "atomic.h" > > #if HAVE_SCHED_GETAFFINITY > #ifndef _GNU_SOURCE > @@ -44,7 +45,20 @@ > #include <unistd.h> > #endif > > -static int flags, checked; > +static int cpu_flags = -1; > + > +static int get_cpu_flags(void) > +{ > + if (ARCH_AARCH64) > + return ff_get_cpu_flags_aarch64(); > + if (ARCH_ARM) > + return ff_get_cpu_flags_arm(); > + if (ARCH_PPC) > + return ff_get_cpu_flags_ppc(); > + if (ARCH_X86) > + return ff_get_cpu_flags_x86(); > + return 0; > +} > > void av_force_cpu_flags(int arg){ > if ( (arg & ( AV_CPU_FLAG_3DNOW | > @@ -69,33 +83,22 @@ void av_force_cpu_flags(int arg){ > arg |= AV_CPU_FLAG_MMX; > } > > - flags = arg; > - checked = arg != -1; > + avpriv_atomic_int_set_relaxed(&cpu_flags, arg); > } > > int av_get_cpu_flags(void) > { > - if (checked) > - return flags; > - > - if (ARCH_AARCH64) > - flags = ff_get_cpu_flags_aarch64(); > - if (ARCH_ARM) > - flags = ff_get_cpu_flags_arm(); > - if (ARCH_PPC) > - flags = ff_get_cpu_flags_ppc(); > - if (ARCH_X86) > - flags = ff_get_cpu_flags_x86(); > - > - checked = 1; > + int flags = avpriv_atomic_int_get_relaxed(&cpu_flags); > + if (flags == -1) { > + flags = get_cpu_flags(); > + avpriv_atomic_int_cas_relaxed(&cpu_flags, -1, flags); > + } > return flags; > } > > void av_set_cpu_flags_mask(int mask) > { > - checked = 0; > - flags = av_get_cpu_flags() & mask; > - checked = 1; > + avpriv_atomic_int_set_relaxed(&cpu_flags, get_cpu_flags() & mask); > } > > int av_parse_cpu_flags(const char *s) > diff --git a/libavutil/cpu.h b/libavutil/cpu.h > index 4bff167..8499f0e 100644 > --- a/libavutil/cpu.h > +++ b/libavutil/cpu.h > @@ -85,8 +85,6 @@ void av_force_cpu_flags(int flags); > * Set a mask on flags returned by av_get_cpu_flags(). > * This function is mainly useful for testing. > * Please use av_force_cpu_flags() and av_get_cpu_flags() instead which are > more flexible > - * > - * @warning this function is not thread safe. > */ > attribute_deprecated void av_set_cpu_flags_mask(int mask); > > diff --git a/libavutil/tests/atomic.c b/libavutil/tests/atomic.c > index c92f220..5a12439 100644 > --- a/libavutil/tests/atomic.c > +++ b/libavutil/tests/atomic.c > @@ -26,9 +26,22 @@ int main(void) > > res = avpriv_atomic_int_add_and_fetch(&val, 1); > av_assert0(res == 2); > + res = avpriv_atomic_int_add_and_fetch(&val, -1); > + av_assert0(res == 1); > avpriv_atomic_int_set(&val, 3); > res = avpriv_atomic_int_get(&val); > av_assert0(res == 3); > + avpriv_atomic_int_set_relaxed(&val, 4); > + res = avpriv_atomic_int_get_relaxed(&val); > + av_assert0(res == 4); > + res = avpriv_atomic_int_cas_relaxed(&val, 3, 5); > + av_assert0(res == 4); > + res = avpriv_atomic_int_get_relaxed(&val); > + av_assert0(res == 4); > + res = avpriv_atomic_int_cas_relaxed(&val, 4, 5); > + av_assert0(res == 4); > + res = avpriv_atomic_int_get_relaxed(&val); > + av_assert0(res == 5); > > return 0; > } > diff --git a/libavutil/tests/cpu_init.c b/libavutil/tests/cpu_init.c > new file mode 100644 > index 0000000..402b6a8 > --- /dev/null > +++ b/libavutil/tests/cpu_init.c > @@ -0,0 +1,72 @@ > +/* > + * This file is part of FFmpeg. > + * > + * FFmpeg is free software; you can redistribute it and/or > + * modify it under the terms of the GNU Lesser General Public > + * License as published by the Free Software Foundation; either > + * version 2.1 of the License, or (at your option) any later version. > + * > + * FFmpeg is distributed in the hope that it will be useful, > + * but WITHOUT ANY WARRANTY; without even the implied warranty of > + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU > + * Lesser General Public License for more details. > + * > + * You should have received a copy of the GNU Lesser General Public > + * License along with FFmpeg; if not, write to the Free Software > + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 > USA > + */ > + > +/* > + * This test program verifies that the one-time initialization in > + * av_get_cpu_flags() is thread-safe. > + */ > + > +#include <stdio.h> > +#include <string.h> > + > +#include "config.h" > + > +#include "libavutil/cpu.h" > +#include "libavutil/thread.h" > + > +#if HAVE_PTHREADS > +static void *thread_main(void *arg) > +{ > + int *flags = arg; > + > + *flags = av_get_cpu_flags(); > + return NULL; > +} > +#endif > + > + > +int main(int argc, char **argv) > +{ > +#if HAVE_PTHREADS > + int cpu_flags1; > + int cpu_flags2; > + int ret; > + pthread_t thread1; > + pthread_t thread2; > + > + if ((ret = pthread_create(&thread1, NULL, thread_main, &cpu_flags1))) { > + fprintf(stderr, "pthread_create failed: %s.\n", strerror(ret)); > + return 1; > + } > + if ((ret = pthread_create(&thread2, NULL, thread_main, &cpu_flags2))) { > + fprintf(stderr, "pthread_create failed: %s.\n", strerror(ret)); > + return 1; > + } > + pthread_join(thread1, NULL); > + pthread_join(thread2, NULL); > + > + if (cpu_flags1 < 0) > + return 2; > + if (cpu_flags2 < 0) > + return 2; > + if (cpu_flags1 != cpu_flags2) > + return 3; > +#endif > + > + return 0; > +} Again, once the atomics changes in Libav are merged, the avpriv_atomic_ additions will have to be deleted, and code using it rewritten. _______________________________________________ ffmpeg-devel mailing list ffmpeg-devel@ffmpeg.org http://ffmpeg.org/mailman/listinfo/ffmpeg-devel