Change interface of atomic compare and swap

The old atomic compare and swap operations (`vtkm::AtomicCompareAndSwap`
and `vtkm::exec::AtomicArrayExecutionObject::CompareAndSwap`) had an
order of arguments that was confusing. The order of the arguments was
shared pointer (or index), desired value, expected value. Most people
probably assume expected value comes before desired value. And this
order conflicts with the order in the `std` methods, GCC atomics, and
Kokkos.

Change the interface of atomic operations to be patterned off the
`std::atomic_compare_exchange` and `std::atomic<T>::compare_exchange`
methods. First, these methods have a more intuitive order of parameters
(shared pointer, expected, desired). Second, rather than take a value
for the expected and return the actual old value, they take a pointer to
the expected value (or reference in `AtomicArrayExecutionObject`) and
modify this value in the case that it does not match the actual value.
This makes it harder to mix up the expected and desired parameters.
Also, because the methods return a bool indicating whether the value was
changed, there is an additional benefit that compare-exchange loops are
implemented easier.

For example, consider you want to apply the function `MyOp` on a
`sharedValue` atomically. With the old interface, you would have to do
something like this.

```cpp
T oldValue;
T newValue;
do
{
  oldValue = *sharedValue;
  newValue = MyOp(oldValue);
} while (vtkm::AtomicCompareAndSwap(sharedValue, newValue, oldValue) != oldValue);
```

With the new interface, this is simplfied to this.

```cpp
T oldValue = *sharedValue;
while (!vtkm::AtomicCompareExchange(sharedValue, &oldValue, MyOp(oldValue));
```
This commit is contained in:
Kenneth Moreland 2020-09-24 18:02:59 -06:00
parent 093d25f342
commit 28ecf3636d
14 changed files with 204 additions and 152 deletions

@ -260,7 +260,7 @@ VTKM_BENCHMARK_TEMPLATES_OPTS(
->ArgNames({ "Values", "Ops", "Stride" }),
vtkm::cont::AtomicArrayTypeList);
// Benchmarks AtomicArray::CompareAndSwap such that each work index writes to adjacent
// Benchmarks AtomicArray::CompareExchange such that each work index writes to adjacent
// indices.
struct CASSeqWorker : public vtkm::worklet::WorkletMapField
{
@ -273,12 +273,8 @@ struct CASSeqWorker : public vtkm::worklet::WorkletMapField
const vtkm::Id idx = i % portal.GetNumberOfValues();
const T val = static_cast<T>(i) + in;
T oldVal = portal.Get(idx);
T assumed = static_cast<T>(0);
do
{
assumed = oldVal;
oldVal = portal.CompareAndSwap(idx, assumed + val, assumed);
} while (assumed != oldVal);
while (!portal.CompareExchange(idx, &oldVal, oldVal + val))
;
}
};
@ -371,7 +367,7 @@ VTKM_BENCHMARK_TEMPLATES_OPTS(BenchCASSeqBaseline,
->ArgNames({ "Values", "Ops" }),
vtkm::cont::AtomicArrayTypeList);
// Benchmarks AtomicArray::CompareAndSwap such that each work index writes to
// Benchmarks AtomicArray::CompareExchange such that each work index writes to
// a strided index:
// ( floor(i / stride) + stride * (i % stride)
struct CASStrideWorker : public vtkm::worklet::WorkletMapField
@ -393,12 +389,8 @@ struct CASStrideWorker : public vtkm::worklet::WorkletMapField
const vtkm::Id idx = (i / this->Stride + this->Stride * (i % this->Stride)) % numVals;
const T val = static_cast<T>(i) + in;
T oldVal = portal.Get(idx);
T assumed = static_cast<T>(0);
do
{
assumed = oldVal;
oldVal = portal.CompareAndSwap(idx, assumed + val, assumed);
} while (assumed != oldVal);
while (!portal.CompareExchange(idx, &oldVal, oldVal + val))
;
}
};

@ -207,15 +207,23 @@ VTKM_EXEC_CONT inline T AtomicNotImpl(T* addr, vtkm::MemoryOrder order)
}
template <typename T>
VTKM_EXEC_CONT inline T AtomicCompareAndSwapImpl(T* addr,
T desired,
T expected,
vtkm::MemoryOrder order)
VTKM_EXEC_CONT inline bool AtomicCompareExchangeImpl(T* addr,
T* expected,
T desired,
vtkm::MemoryOrder order)
{
AtomicStoreFence(order);
auto result = atomicCAS(addr, expected, desired);
auto result = atomicCAS(addr, *expected, desired);
AtomicLoadFence(order);
return result;
if (result == *expected)
{
return true;
}
else
{
*expected = result;
return false;
}
}
}
} // namespace vtkm::detail
@ -355,15 +363,23 @@ VTKM_EXEC_CONT inline T AtomicNotImpl(T* addr, vtkm::MemoryOrder order)
}
template <typename T>
VTKM_EXEC_CONT inline T AtomicCompareAndSwapImpl(T* addr,
T desired,
T expected,
vtkm::MemoryOrder order)
VTKM_EXEC_CONT inline bool AtomicCompareExchangeImpl(T* addr,
T* expected,
T desired,
vtkm::MemoryOrder order)
{
AtomicStoreFence(order);
T result = Kokkos::atomic_compare_exchange(addr, expected, desired);
T oldValue = Kokkos::atomic_compare_exchange(addr, *expected, desired);
AtomicLoadFence(order);
return result;
if (oldValue == *expected)
{
return true;
}
else
{
*expected = oldValue;
return false;
}
}
}
} // namespace vtkm::detail
@ -492,13 +508,22 @@ VTKM_EXEC_CONT inline void AtomicStoreImpl(vtkm::UInt64* addr,
{ \
return AtomicXorImpl(addr, static_cast<vtkmType>(~vtkmType{ 0u }), order); \
} \
VTKM_EXEC_CONT inline vtkmType AtomicCompareAndSwapImpl( \
vtkmType* addr, vtkmType desired, vtkmType expected, vtkm::MemoryOrder order) \
VTKM_EXEC_CONT inline bool AtomicCompareExchangeImpl( \
vtkmType* addr, vtkmType* expected, vtkmType desired, vtkm::MemoryOrder order) \
{ \
return BitCast<vtkmType>( \
vtkmType result = BitCast<vtkmType>( \
_InterlockedCompareExchange##suffix(reinterpret_cast<volatile winType*>(addr), \
BitCast<winType>(desired), \
BitCast<winType>(expected))); \
BitCast<winType>(*expected))); \
if (result == *expected) \
{ \
return true; \
} \
else \
{ \
*expected = result; \
return false; \
} \
}
VTKM_ATOMIC_OPS_FOR_TYPE(vtkm::UInt8, CHAR, 8)
@ -585,14 +610,13 @@ VTKM_EXEC_CONT inline T AtomicNotImpl(T* addr, vtkm::MemoryOrder order)
}
template <typename T>
VTKM_EXEC_CONT inline T AtomicCompareAndSwapImpl(T* addr,
T desired,
T expected,
vtkm::MemoryOrder order)
VTKM_EXEC_CONT inline bool AtomicCompareExchangeImpl(T* addr,
T* expected,
T desired,
vtkm::MemoryOrder order)
{
__atomic_compare_exchange_n(
addr, &expected, desired, false, GccAtomicMemOrder(order), GccAtomicMemOrder(order));
return expected;
return __atomic_compare_exchange_n(
addr, expected, desired, false, GccAtomicMemOrder(order), GccAtomicMemOrder(order));
}
}
} // namespace vtkm::detail
@ -801,24 +825,34 @@ VTKM_EXEC_CONT inline T AtomicNot(
/// \brief Atomic function that replaces a value given a condition.
///
/// Given a pointer, a new desired value, and an expected value, replaces the value at the
/// pointer if it is the same as the expected value with the new desired value. If the original
/// value in the pointer does not equal the expected value, then the memory at the pointer
/// remains unchanged. In either case, the function returns the _old_ original value that
/// was at the pointer.
/// Given a pointer to a `shared` value, a pointer holding the `expected` value at that shared
/// location, and a new `desired` value, `AtomicCompareExchange` compares the existing `shared`
/// value to the `expected` value, and then conditionally replaces the `shared` value with
/// the provided `desired` value. Otherwise, the `expected` value gets replaced with the
/// `shared` value. Note that in either case, the function returns with `expected` replaced
/// with the value _originally_ in `shared` at the start of the call.
///
/// If multiple threads call `AtomicCompareAndSwap` simultaneously, the result will be consistent
/// as if one was called before the other (although it is indeterminate which will be applied
/// first).
/// If the `shared` value and `expected` value are the same, then `shared` gets set to
/// `desired`, and `AtomicCompareAndExchange` returns `true`.
///
/// If the `shared` value and `expected` value are different, then `expected` gets set
/// to `shared`, and `AtomicCompareAndExchange` returns `false`. The value at `shared`
/// is _not_ changed in this case.
///
/// If multiple threads call `AtomicCompareExchange` simultaneously with the same `shared`
/// pointer, the result will be consistent as if one was called before the other (although
/// it is indeterminate which will be applied first). Note that the `expected` pointer should
/// _not_ be shared among threads. The `expected` pointer should be thread-local (often
/// pointing to an object on the stack).
///
template <typename T>
VTKM_EXEC_CONT inline T AtomicCompareAndSwap(
T* pointer,
VTKM_EXEC_CONT inline T AtomicCompareExchange(
T* shared,
T* expected,
T desired,
T expected,
vtkm::MemoryOrder order = vtkm::MemoryOrder::SequentiallyConsistent)
{
return detail::AtomicCompareAndSwapImpl(pointer, desired, expected, order);
return detail::AtomicCompareExchangeImpl(shared, expected, desired, order);
}
} // namespace vtkm

@ -430,56 +430,74 @@ public:
}
/// Perform an atomic compare-and-swap operation on the bit at @a bitIdx.
/// If the value in memory is equal to @a expectedBit, it is replaced with
/// the value of @a newBit and the original value of the bit is returned as a
/// boolean. This method implements a full memory barrier around the atomic
/// If the value in memory is equal to @a oldBit, it is replaced with
/// the value of @a newBit and true is returned. If the value in memory is
/// not equal to @oldBit, @oldBit is changed to that value and false is
/// returned. This method implements a full memory barrier around the atomic
/// operation.
VTKM_EXEC_CONT
bool CompareAndSwapBitAtomic(vtkm::Id bitIdx, bool newBit, bool expectedBit) const
bool CompareExchangeBitAtomic(vtkm::Id bitIdx, bool* oldBit, bool newBit) const
{
VTKM_STATIC_ASSERT_MSG(!IsConst, "Attempt to modify const BitField portal.");
using WordType = WordTypePreferred;
const auto coord = this->GetBitCoordinateFromIndex<WordType>(bitIdx);
const auto bitmask = WordType(1) << coord.BitOffset;
WordType oldWord;
WordType newWord;
WordType oldWord = this->GetWord<WordType>(coord.WordIndex);
do
{
oldWord = this->GetWord<WordType>(coord.WordIndex);
bool oldBitSet = (oldWord & bitmask) != WordType(0);
if (oldBitSet != expectedBit)
bool actualBit = (oldWord & bitmask) != WordType(0);
if (actualBit != *oldBit)
{ // The bit-of-interest does not match what we expected.
return oldBitSet;
*oldBit = actualBit;
return false;
}
else if (oldBitSet == newBit)
else if (actualBit == newBit)
{ // The bit hasn't changed, but also already matches newVal. We're done.
return expectedBit;
return true;
}
// Compute the new word
newWord = oldWord ^ bitmask;
} // CAS loop to resolve any conflicting changes to other bits in the word.
while (this->CompareAndSwapWordAtomic(coord.WordIndex, newWord, oldWord) != oldWord);
// Attempt to update the word with a compare-exchange in the loop condition.
// If the old word changed since last queried, oldWord will get updated and
// the loop will continue until it succeeds.
} while (!this->CompareExchangeWordAtomic(coord.WordIndex, &oldWord, oldWord ^ bitmask));
return true;
}
VTKM_DEPRECATED(1.6, "Use CompareExchangeBitAtomic. (Note the changed interface.)")
VTKM_EXEC_CONT bool CompareAndSwapBitAtomic(vtkm::Id bitIdx, bool newBit, bool expectedBit) const
{
this->CompareExchangeBitAtomic(bitIdx, &expectedBit, newBit);
return expectedBit;
}
/// Perform an atomic compare-and-swap operation on the word at @a wordIdx.
/// If the word in memory is equal to @a expectedWord, it is replaced with
/// the value of @a newWord and the original word is returned. This method
/// implements a full memory barrier around the atomic operation.
/// Perform an atomic compare-exchange operation on the word at @a wordIdx.
/// If the word in memory is equal to @a oldWord, it is replaced with
/// the value of @a newWord and true returned. If the word in memory is not
/// equal to @oldWord, @oldWord is set to the word in memory and false is
/// returned. This method implements a full memory barrier around the atomic
/// operation.
template <typename WordType = WordTypePreferred>
VTKM_EXEC_CONT WordType CompareAndSwapWordAtomic(vtkm::Id wordIdx,
WordType newWord,
WordType expected) const
VTKM_EXEC_CONT bool CompareExchangeWordAtomic(vtkm::Id wordIdx,
WordType* oldWord,
WordType newWord) const
{
VTKM_STATIC_ASSERT_MSG(!IsConst, "Attempt to modify const BitField portal.");
VTKM_STATIC_ASSERT_MSG(IsValidWordTypeAtomic<WordType>::value,
"Requested WordType does not support atomic"
" operations on target execution platform.");
WordType* addr = this->GetWordAddress<WordType>(wordIdx);
return vtkm::AtomicCompareAndSwap(addr, newWord, expected);
return vtkm::AtomicCompareExchange(addr, oldWord, newWord);
}
template <typename WordType = WordTypePreferred>
VTKM_DEPRECATED(1.6, "Use CompareExchangeWordAtomic. (Note the changed interface.)")
VTKM_EXEC_CONT WordType
CompareAndSwapWordAtomic(vtkm::Id wordIdx, WordType newWord, WordType expected) const
{
this->CompareExchangeWordAtomic(wordIdx, &expected, newWord);
return expected;
}
private:

@ -70,7 +70,8 @@ struct VTKM_DEPRECATED(1.6, "Use the functions in vtkm/Atomic.h.") AtomicInterfa
template <typename T>
VTKM_EXEC_CONT static T CompareAndSwap(T* addr, T newWord, T expected)
{
return vtkm::AtomicCompareAndSwap(addr, newWord, expected);
vtkm::AtomicCompareExchange(addr, &expected, newWord);
return expected;
}
};
}

@ -71,7 +71,8 @@ struct VTKM_DEPRECATED(1.6, "Use the functions in vtkm/Atomic.h.") AtomicInterfa
template <typename T>
VTKM_EXEC_CONT static T CompareAndSwap(T* addr, T newWord, T expected)
{
return vtkm::AtomicCompareAndSwap(addr, newWord, expected);
vtkm::AtomicCompareExchange(addr, &expected, newWord);
return expected;
}
};
}

@ -203,11 +203,19 @@ struct TestingBitField
DEVICE_ASSERT(testValues("XorBitAtomic"));
const auto notBit = !bit;
bool casResult = portal.CompareAndSwapBitAtomic(i, bit, notBit);
DEVICE_ASSERT(casResult == bit);
// A compare-exchange that should fail
auto expectedBit = notBit;
bool cxResult = portal.CompareExchangeBitAtomic(i, &expectedBit, bit);
DEVICE_ASSERT(!cxResult);
DEVICE_ASSERT(expectedBit != notBit);
DEVICE_ASSERT(portal.GetBit(i) == expectedBit);
DEVICE_ASSERT(portal.GetBit(i) == bit);
casResult = portal.CompareAndSwapBitAtomic(i, notBit, bit);
DEVICE_ASSERT(casResult == bit);
// A compare-exchange that should succeed.
expectedBit = bit;
cxResult = portal.CompareExchangeBitAtomic(i, &expectedBit, notBit);
DEVICE_ASSERT(cxResult);
DEVICE_ASSERT(expectedBit == bit);
DEVICE_ASSERT(portal.GetBit(i) == notBit);
return true;
@ -258,12 +266,20 @@ struct TestingBitField
portal.XorWordAtomic(i, mod);
DEVICE_ASSERT(testValues("XorWordAtomic"));
// Compare-exchange that should fail
const WordType notWord = static_cast<WordType>(~word);
auto casResult = portal.CompareAndSwapWordAtomic(i, word, notWord);
DEVICE_ASSERT(casResult == word);
WordType expectedWord = notWord;
bool cxResult = portal.CompareExchangeWordAtomic(i, &expectedWord, word);
DEVICE_ASSERT(!cxResult);
DEVICE_ASSERT(expectedWord != notWord);
DEVICE_ASSERT(portal.template GetWord<WordType>(i) == expectedWord);
DEVICE_ASSERT(portal.template GetWord<WordType>(i) == word);
casResult = portal.CompareAndSwapWordAtomic(i, notWord, word);
DEVICE_ASSERT(casResult == word);
// Compare-exchange that should succeed
expectedWord = word;
cxResult = portal.CompareExchangeWordAtomic(i, &expectedWord, notWord);
DEVICE_ASSERT(cxResult);
DEVICE_ASSERT(expectedWord == word);
DEVICE_ASSERT(portal.template GetWord<WordType>(i) == notWord);
return true;

@ -327,14 +327,9 @@ public:
T value = (T)index;
//Get the old value from the array
T oldValue = this->AArray.Get(0);
//This creates an atomic add using the CAS operatoin
T assumed = T(0);
do
{
assumed = oldValue;
oldValue = this->AArray.CompareAndSwap(0, (assumed + value), assumed);
} while (assumed != oldValue);
//Use atomic compare-exchange to atomically add value
while (!this->AArray.CompareExchange(0, &oldValue, oldValue + value))
;
}
VTKM_CONT void SetErrorMessageBuffer(const vtkm::exec::internal::ErrorMessageBuffer&) {}

@ -158,47 +158,42 @@ public:
vtkm::AtomicStore(reinterpret_cast<APIType*>(this->Data + index), static_cast<APIType>(value));
}
/// \brief Perform an atomic CAS operation with sequentially consistent
/// \brief Perform an atomic compare and exchange operation with sequentially consistent
/// memory ordering.
/// \param index The index of the array element that will be atomically
/// modified.
/// \param oldValue A pointer to the expected value of the indexed element.
/// \param newValue The value to replace the indexed element with.
/// \param oldValue The expected value of the indexed element.
/// \return If the operation is successful, \a oldValue is returned. Otherwise
/// the current value of the indexed element is returned, and the element is
/// not modified.
/// \return If the operation is successful, \a true is returned. Otherwise,
/// \a oldValue is replaced with the current value of the indexed element,
/// the element is not modified, and \a false is returned. In either case, \a oldValue
/// becomes the value that was originally in the indexed element.
///
/// This operation is typically used in a loop. For example usage,
/// an atomic multiplication may be implemented using CAS as follows:
/// an atomic multiplication may be implemented using compare-exchange as follows:
///
/// ```
/// AtomicArrayExecutionObject<vtkm::Int32, ...> arr = ...;
///
/// // CAS multiplication:
/// vtkm::Int32 cur = arr->Get(idx); // Load the current value at idx
/// vtkm::Int32 newVal; // will hold the result of the multiplication
/// vtkm::Int32 expect; // will hold the expected value before multiplication
/// // Compare-exchange multiplication:
/// vtkm::Int32 current = arr->Get(idx); // Load the current value at idx
/// do {
/// expect = cur; // Used to ensure the value hasn't changed since reading
/// newVal = cur * multFactor; // the actual multiplication
/// }
/// while ((cur = arr->CompareAndSwap(idx, newVal, expect)) == expect);
/// vtkm::Int32 newVal = current * multFactor; // the actual multiplication
/// } while (!arr->CompareExchange(idx, &current, newVal));
/// ```
///
/// The while condition here updates \a cur with the pre-CAS value of the
/// operation (the return from CompareAndSwap), and compares this to the
/// expected value. If the values match, the operation was successful and the
/// The while condition here updates \a newVal what the proper multiplication
/// is given the expected current value. It then compares this to the
/// value in the array. If the values match, the operation was successful and the
/// loop exits. If the values do not match, the value at \a idx was changed
/// by another thread since the initial Get, and the CAS operation failed --
/// the target element was not modified by the CAS call. If this happens, the
/// loop body re-executes using the new value of \a cur and tries again until
/// by another thread since the initial Get, and the compare-exchange operation failed --
/// the target element was not modified by the compare-exchange call. If this happens, the
/// loop body re-executes using the new value of \a current and tries again until
/// it succeeds.
///
VTKM_SUPPRESS_EXEC_WARNINGS
VTKM_EXEC
ValueType CompareAndSwap(vtkm::Id index,
const ValueType& newValue,
const ValueType& oldValue) const
bool CompareExchange(vtkm::Id index, ValueType* oldValue, const ValueType& newValue) const
{
// We only support 32/64 bit signed/unsigned ints, and vtkm::Atomic
// currently only provides API for unsigned types.
@ -207,9 +202,18 @@ public:
// is how overflow works, and signed overflow is already undefined.
using APIType = typename detail::MakeUnsigned<ValueType>::type;
return static_cast<T>(vtkm::AtomicCompareAndSwap(reinterpret_cast<APIType*>(this->Data + index),
static_cast<APIType>(newValue),
static_cast<APIType>(oldValue)));
return vtkm::AtomicCompareExchange(reinterpret_cast<APIType*>(this->Data + index),
reinterpret_cast<APIType*>(oldValue),
static_cast<APIType>(newValue));
}
VTKM_DEPRECATED(1.6, "Use CompareExchange. (Note the changed interface.)")
VTKM_EXEC ValueType CompareAndSwap(vtkm::Id index,
const ValueType& newValue,
ValueType oldValue) const
{
this->CompareExchange(index, &oldValue, newValue);
return oldValue;
}
private:

@ -106,21 +106,21 @@ public:
template <typename Atomic>
VTKM_EXEC void Max(Atomic& atom, const vtkm::Id& val, const vtkm::Id& index) const
{
vtkm::Id old = -1;
do
vtkm::Id old = atom.Get(index);
while (old < val)
{
old = atom.CompareAndSwap(index, val, old);
} while (old < val);
atom.CompareExchange(index, &old, val);
}
}
template <typename Atomic>
VTKM_EXEC void Min(Atomic& atom, const vtkm::Id& val, const vtkm::Id& index) const
{
vtkm::Id old = 1000000000;
do
vtkm::Id old = atom.Get(index);
while (old > val)
{
old = atom.CompareAndSwap(index, val, old);
} while (old > val);
atom.CompareExchange(index, &old, val);
}
}
template <typename T, typename AtomicType>

@ -362,7 +362,7 @@ private:
blendedColor[2] = color[2] * intensity + srcColor[2] * alpha;
blendedColor[3] = alpha + intensity;
next.Ints.Color = PackColor(blendedColor);
current.Raw = FrameBuffer.CompareAndSwap(index, next.Raw, current.Raw);
FrameBuffer.CompareExchange(index, &current.Raw, next.Raw);
} while (current.Floats.Depth > next.Floats.Depth);
}

@ -277,7 +277,7 @@ struct AtomicTests
}
}
struct CompareAndSwapFunctor : vtkm::worklet::WorkletMapField
struct CompareExchangeFunctor : vtkm::worklet::WorkletMapField
{
using ControlSignature = void(FieldIn ignored, ExecObject);
using ExecutionSignature = void(WorkIndex, _2);
@ -288,8 +288,8 @@ struct AtomicTests
bool success = false;
for (T overlapIndex = 0; overlapIndex < static_cast<T>(OVERLAP); ++overlapIndex)
{
T oldValue = vtkm::AtomicCompareAndSwap(data + arrayIndex, overlapIndex + 1, overlapIndex);
if (oldValue == overlapIndex)
T expectedValue = overlapIndex;
if (vtkm::AtomicCompareExchange(data + arrayIndex, &expectedValue, overlapIndex + 1))
{
success = true;
break;
@ -303,9 +303,9 @@ struct AtomicTests
}
};
VTKM_CONT void TestCompareAndSwap()
VTKM_CONT void TestCompareExchange()
{
std::cout << "AtomicCompareAndSwap" << std::endl;
std::cout << "AtomicCompareExchange" << std::endl;
vtkm::cont::ArrayHandleBasic<T> array;
vtkm::cont::ArrayCopy(vtkm::cont::make_ArrayHandleConstant<T>(0, ARRAY_SIZE), array);
array.Allocate(ARRAY_SIZE);
@ -331,7 +331,7 @@ struct AtomicTests
TestOr();
TestXor();
TestNot();
TestCompareAndSwap();
TestCompareExchange();
}
};

@ -151,9 +151,8 @@ public:
for (vtkm::IdComponent c = 0; c < cells.GetNumberOfComponents(); ++c)
{
const vtkm::Id cellId = cells[c];
const bool alreadyVisited = visitedCells.CompareAndSwapBitAtomic(cellId, true, false);
if (!alreadyVisited)
bool checkNotVisited = false;
if (visitedCells.CompareExchangeBitAtomic(cellId, &checkNotVisited, true))
{ // This thread is first to visit cell
activeCells.SetBitAtomic(cellId, true);
}
@ -216,8 +215,8 @@ public:
const bool alreadyVisited = visitedPoints.GetBit(pointId);
if (!alreadyVisited)
{
const bool alreadyActive = activePoints.CompareAndSwapBitAtomic(pointId, true, false);
if (!alreadyActive)
bool checkNotActive = false;
if (activePoints.CompareExchangeBitAtomic(pointId, &checkNotActive, true))
{ // If we're the first thread to mark point active, set ref point:
refPoints.Set(pointId, refPtId);
}

@ -106,19 +106,16 @@ public:
// We can use this return "new root" as is without calling findRoot() to
// find the "new root". The while loop terminates when both u and v have
// the same root (thus united).
auto root_u = UnionFind::findRoot(parents, u);
auto root_v = UnionFind::findRoot(parents, v);
vtkm::Id root_u = UnionFind::findRoot(parents, u);
vtkm::Id root_v = UnionFind::findRoot(parents, v);
while (root_u != root_v)
{
// FIXME: we might be executing the loop one extra time than necessary.
// Nota Bene: VTKm's CompareAndSwap has a different order of parameters
// than common practice, it is (index, new, expected) rather than
// (index, expected, new).
if (root_u < root_v)
root_v = parents.CompareAndSwap(root_v, root_u, root_v);
parents.CompareExchange(root_v, &root_v, root_u);
else if (root_u > root_v)
root_u = parents.CompareAndSwap(root_u, root_v, root_u);
parents.CompareExchange(root_u, &root_u, root_v);
}
}

@ -264,14 +264,9 @@ public:
//Id writeValue = op(vertexValue, parentValue);
auto cur = minMaxIndexPortal.Get(parent); // Load the current value at idx
vtkm::Id newVal; // will hold the result of the multiplication
vtkm::Id expect; // will hold the expected value before multiplication
do
{
expect = cur; // Used to ensure the value hasn't changed since reading
newVal = this->Op(cur, vertexValue); // the actual multiplication
} while ((cur = minMaxIndexPortal.CompareAndSwap(parent, newVal, expect)) != expect);
// Use a compare-exchange loop to ensure the operation gets applied atomically
while (!minMaxIndexPortal.CompareExchange(parent, &cur, this->Op(cur, vertexValue)))
;
//minMaxIndexPortal.Set(parent, writeValue);
}