I have an assignment to implement a very basic lock-free sorted vector (with only insert and indexing), and I have everything working properly, however, valgrind is saying that I have a conditional jump/move depending on an uninitialized value. I'm using --track-origins=yes, but I'm not finding it all that helpful.
This is the code for my indexing operator:
int operator[](int pos) {
Pair pdata_old = pdata.load();
Pair pdata_new = pdata_old;
// Increment ref count
do {
pdata_new = pdata_old;
++pdata_new.ref_count;
} while (!pdata.compare_exchange_weak(pdata_old, pdata_new));
// Get old data
int ret_val = (*pdata_new.pointer)[pos];
pdata_old = pdata.load();
// Decrement ref count
do {
pdata_new = pdata_old;
--pdata_new.ref_count;
// assert(pdata_new.ref_count >= 0);
} while (!pdata.compare_exchange_weak(pdata_old, pdata_new));
return ret_val;
}
Pair is just a struct that contains a vector* and an int, and the constructors initialize all of its values. I can't find anywhere I'm relying on uninitialized data, at least just by looking at my code.
Here's the relevant valgrind output (line 121 is the line the function is declared on, 130 and 142 are the compare_exchange_weak() lines):
==21299==
==21299== Thread 2:
==21299== Conditional jump or move depends on uninitialised value(s)
==21299== at 0x10A5C2: LFSV::operator[](int) (lfsv.h:130)
==21299== by 0x1099F4: read_position_0() (driver.cpp:27)
==21299== by 0x10FCC6: void std::__invoke_impl<void, void (*)()>(std::__invoke_other, void (*&&)()) (invoke.h:60)
==21299== by 0x10FC5C: std::__invoke_result<void (*)()>::type std::__invoke<void (*)()>(void (*&&)()) (invoke.h:95)
==21299== by 0x10FC34: _ZNSt6thread8_InvokerISt5tupleIJPFvvEEEE9_M_invokeIJLm0EEEEDTclsr3stdE8__invokespcl10_S_declvalIXT_EEEEESt12_Index_tupleIJXspT_EEE (thread:234)
==21299== by 0x10FC04: std::thread::_Invoker<std::tuple<void (*)()> >::operator()() (thread:243)
==21299== by 0x10FAE8: std::thread::_State_impl<std::thread::_Invoker<std::tuple<void (*)()> > >::_M_run() (thread:186)
==21299== by 0x50FAB9E: execute_native_thread_routine (thread.cc:83)
==21299== by 0x593208B: start_thread (in /usr/lib/libpthread-2.26.so)
==21299== by 0x5C3EE7E: clone (in /usr/lib/libc-2.26.so)
==21299== Uninitialised value was created by a stack allocation
==21299== at 0x10A520: LFSV::operator[](int) (lfsv.h:121)
==21299==
==21299== Conditional jump or move depends on uninitialised value(s)
==21299== at 0x10A654: LFSV::operator[](int) (lfsv.h:142)
==21299== by 0x1099F4: read_position_0() (driver.cpp:27)
==21299== by 0x10FCC6: void std::__invoke_impl<void, void (*)()>(std::__invoke_other, void (*&&)()) (invoke.h:60)
==21299== by 0x10FC5C: std::__invoke_result<void (*)()>::type std::__invoke<void (*)()>(void (*&&)()) (invoke.h:95)
==21299== by 0x10FC34: _ZNSt6thread8_InvokerISt5tupleIJPFvvEEEE9_M_invokeIJLm0EEEEDTclsr3stdE8__invokespcl10_S_declvalIXT_EEEEESt12_Index_tupleIJXspT_EEE (thread:234)
==21299== by 0x10FC04: std::thread::_Invoker<std::tuple<void (*)()> >::operator()() (thread:243)
==21299== by 0x10FAE8: std::thread::_State_impl<std::thread::_Invoker<std::tuple<void (*)()> > >::_M_run() (thread:186)
==21299== by 0x50FAB9E: execute_native_thread_routine (thread.cc:83)
==21299== by 0x593208B: start_thread (in /usr/lib/libpthread-2.26.so)
==21299== by 0x5C3EE7E: clone (in /usr/lib/libc-2.26.so)
==21299== Uninitialised value was created by a stack allocation
==21299== at 0x10A520: LFSV::operator[](int) (lfsv.h:121)
==21299==
This is normal and not a cause for concern when using compare_exchange_weak
on an object with padding. It can result in spurious CAS failures, so be worried if you ever use compare_exchange_strong
just once without a retry loop or something.
Pair is just a struct that contains a vector* and an int
And thus has padding on a normal 64-bit C++ implementation, where sizeof(vector*) == 8
and sizeof(int) == 4
, and alignof(vector*) == 8
.
To make each pointer member 8-byte aligned, the whole struct/class needs to be aligned by 8, and thus its size has to be padded up to a multiple of 8 so that an array of Pair foo[]
works properly, with each array element having 8-byte alignment.
But compare_exchange_weak
compares the bit-pattern of the entire object, including padding.
Presumably you compiled without optimization, and the compiler made code that stores locals to the stack with a 4-byte store for the int
member, but then loads it back the whole Pair
with two 8-byte loads for x86-64's cmpxchg16b
instruction, or (if atomic<Pair>
isn't lock-free) takes a lock and effectively does a memcmp
.