vtk-m/vtkm/testing/UnitTestAtomic.cxx
Kenneth Moreland 13056b3af5 Deprecate AtomicInterfaceControl and AtomicInterfaceExecution
Now that we have the functions in `vtkm/Atomic.h`, we can deprecate (and
eventually remove) the more cumbersome classes `AtomicInterfaceControl`
and `AtomicInterfaceExecution`.

Also reversed the order of the `expected` and `desired` parameters of
`vtkm::AtomicCompareAndSwap`. I think the former order makes more sense
and matches more other implementations (such as `std::atomic` and the
GCC `__atomic` built ins). However, there are still some non-deprecated
classes with similar methods that cannot easily be switched. Thus, it's
better to be inconsistent with most other libraries and consistent with
ourself than to be inconsitent with ourself.
2020-08-20 13:40:44 -06:00

360 lines
11 KiB
C++

//============================================================================
// Copyright (c) Kitware, Inc.
// All rights reserved.
// See LICENSE.txt for details.
//
// This software is distributed WITHOUT ANY WARRANTY; without even
// the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
// PURPOSE. See the above copyright notice for more information.
//============================================================================
#include <vtkm/Atomic.h>
#include <vtkm/cont/Algorithm.h>
#include <vtkm/cont/ArrayCopy.h>
#include <vtkm/cont/ArrayHandleBasic.h>
#include <vtkm/cont/ArrayHandleConstant.h>
#include <vtkm/cont/ArrayHandleIndex.h>
#include <vtkm/cont/DeviceAdapterTag.h>
#include <vtkm/cont/ExecutionObjectBase.h>
#include <vtkm/cont/Invoker.h>
#include <vtkm/worklet/WorkletMapField.h>
#include <vtkm/cont/testing/Testing.h>
namespace
{
constexpr vtkm::Id ARRAY_SIZE = 100;
template <typename T>
struct AtomicTests
{
vtkm::cont::Invoker Invoke;
static constexpr vtkm::Id OVERLAP = sizeof(T) * CHAR_BIT;
static constexpr vtkm::Id EXTENDED_SIZE = ARRAY_SIZE * OVERLAP;
VTKM_EXEC_CONT static T TestValue(vtkm::Id index) { return ::TestValue(index, T{}); }
struct ArrayToRawPointer : vtkm::cont::ExecutionObjectBase
{
vtkm::cont::ArrayHandleBasic<T> Array;
VTKM_CONT ArrayToRawPointer(const vtkm::cont::ArrayHandleBasic<T>& array)
: Array(array)
{
}
VTKM_CONT T* PrepareForExecution(vtkm::cont::DeviceAdapterId device,
vtkm::cont::Token& token) const
{
return reinterpret_cast<T*>(this->Array.GetBuffers()[0].WritePointerDevice(device, token));
}
};
struct LoadFunctor : vtkm::worklet::WorkletMapField
{
using ControlSignature = void(FieldIn ignored, ExecObject);
using ExecutionSignature = void(WorkIndex, _2);
VTKM_EXEC void operator()(vtkm::Id index, T* data) const
{
if (!test_equal(vtkm::AtomicLoad(data + index), TestValue(index)))
{
this->RaiseError("Bad AtomicLoad");
}
}
};
VTKM_CONT void TestLoad()
{
std::cout << "AtomicLoad" << std::endl;
vtkm::cont::ArrayHandleBasic<T> array;
array.Allocate(ARRAY_SIZE);
SetPortal(array.WritePortal());
this->Invoke(LoadFunctor{}, array, ArrayToRawPointer(array));
}
struct StoreFunctor : vtkm::worklet::WorkletMapField
{
using ControlSignature = void(FieldIn ignored, ExecObject);
using ExecutionSignature = void(WorkIndex, _2);
VTKM_EXEC void operator()(vtkm::Id index, T* data) const
{
vtkm::AtomicStore(data + (index % ARRAY_SIZE), TestValue(index));
}
};
VTKM_CONT void TestStore()
{
std::cout << "AtomicStore" << std::endl;
vtkm::cont::ArrayHandleBasic<T> array;
array.Allocate(ARRAY_SIZE);
this->Invoke(
StoreFunctor{}, vtkm::cont::ArrayHandleIndex(EXTENDED_SIZE), ArrayToRawPointer(array));
auto portal = array.ReadPortal();
for (vtkm::Id arrayIndex = 0; arrayIndex < ARRAY_SIZE; ++arrayIndex)
{
bool foundExpected = false;
T foundValue = portal.Get(arrayIndex);
for (vtkm::Id overlapIndex = 0; overlapIndex < OVERLAP; ++overlapIndex)
{
if (test_equal(foundValue, TestValue(arrayIndex + (overlapIndex * ARRAY_SIZE))))
{
foundExpected = true;
break;
}
}
VTKM_TEST_ASSERT(
foundExpected, "Wrong value (", foundValue, ") stored in index ", arrayIndex);
}
}
struct AddFunctor : vtkm::worklet::WorkletMapField
{
using ControlSignature = void(FieldIn ignored, ExecObject);
using ExecutionSignature = void(WorkIndex, _2);
VTKM_EXEC void operator()(vtkm::Id index, T* data) const
{
vtkm::AtomicAdd(data + (index % ARRAY_SIZE), 2);
vtkm::AtomicAdd(data + (index % ARRAY_SIZE), -1);
}
};
VTKM_CONT void TestAdd()
{
std::cout << "AtomicAdd" << std::endl;
vtkm::cont::ArrayHandleBasic<T> array;
vtkm::cont::ArrayCopy(vtkm::cont::make_ArrayHandleConstant<T>(0, ARRAY_SIZE), array);
array.Allocate(ARRAY_SIZE);
this->Invoke(
AddFunctor{}, vtkm::cont::ArrayHandleIndex(EXTENDED_SIZE), ArrayToRawPointer(array));
auto portal = array.ReadPortal();
T expectedValue = T(OVERLAP);
for (vtkm::Id arrayIndex = 0; arrayIndex < ARRAY_SIZE; ++arrayIndex)
{
T foundValue = portal.Get(arrayIndex);
VTKM_TEST_ASSERT(test_equal(foundValue, expectedValue), foundValue, " != ", expectedValue);
}
}
struct AndFunctor : vtkm::worklet::WorkletMapField
{
using ControlSignature = void(FieldIn ignored, ExecObject);
using ExecutionSignature = void(WorkIndex, _2);
VTKM_EXEC void operator()(vtkm::Id index, T* data) const
{
vtkm::Id arrayIndex = index % ARRAY_SIZE;
vtkm::Id offsetIndex = index / ARRAY_SIZE;
vtkm::AtomicAnd(data + arrayIndex, ~(0x1u << offsetIndex));
}
};
VTKM_CONT void TestAnd()
{
std::cout << "AtomicAnd" << std::endl;
vtkm::cont::ArrayHandleBasic<T> array;
vtkm::cont::ArrayCopy(vtkm::cont::make_ArrayHandleConstant<T>(T(-1), ARRAY_SIZE), array);
array.Allocate(ARRAY_SIZE);
this->Invoke(
AndFunctor{}, vtkm::cont::ArrayHandleIndex(EXTENDED_SIZE), ArrayToRawPointer(array));
auto portal = array.ReadPortal();
for (vtkm::Id arrayIndex = 0; arrayIndex < ARRAY_SIZE; ++arrayIndex)
{
T foundValue = portal.Get(arrayIndex);
VTKM_TEST_ASSERT(test_equal(foundValue, 0), foundValue, " != 0");
}
}
struct OrFunctor : vtkm::worklet::WorkletMapField
{
using ControlSignature = void(FieldIn ignored, ExecObject);
using ExecutionSignature = void(WorkIndex, _2);
VTKM_EXEC void operator()(vtkm::Id index, T* data) const
{
vtkm::Id arrayIndex = index % ARRAY_SIZE;
vtkm::Id offsetIndex = index / ARRAY_SIZE;
vtkm::AtomicOr(data + arrayIndex, 0x1u << offsetIndex);
}
};
VTKM_CONT void TestOr()
{
std::cout << "AtomicOr" << std::endl;
vtkm::cont::ArrayHandleBasic<T> array;
vtkm::cont::ArrayCopy(vtkm::cont::make_ArrayHandleConstant<T>(0, ARRAY_SIZE), array);
array.Allocate(ARRAY_SIZE);
this->Invoke(
AndFunctor{}, vtkm::cont::ArrayHandleIndex(EXTENDED_SIZE), ArrayToRawPointer(array));
auto portal = array.ReadPortal();
T expectedValue = T(-1);
for (vtkm::Id arrayIndex = 0; arrayIndex < ARRAY_SIZE; ++arrayIndex)
{
T foundValue = portal.Get(arrayIndex);
VTKM_TEST_ASSERT(test_equal(foundValue, 0), foundValue, " != ", expectedValue);
}
}
struct XorFunctor : vtkm::worklet::WorkletMapField
{
using ControlSignature = void(FieldIn ignored, ExecObject);
using ExecutionSignature = void(WorkIndex, _2);
VTKM_EXEC void operator()(vtkm::Id index, T* data) const
{
vtkm::Id arrayIndex = index % ARRAY_SIZE;
vtkm::Id offsetIndex = index / ARRAY_SIZE;
vtkm::AtomicXor(data + arrayIndex, 0x3u << offsetIndex);
}
};
VTKM_CONT void TestXor()
{
std::cout << "AtomicXor" << std::endl;
vtkm::cont::ArrayHandleBasic<T> array;
vtkm::cont::ArrayCopy(vtkm::cont::make_ArrayHandleConstant<T>(0, ARRAY_SIZE), array);
array.Allocate(ARRAY_SIZE);
this->Invoke(
AndFunctor{}, vtkm::cont::ArrayHandleIndex(EXTENDED_SIZE), ArrayToRawPointer(array));
auto portal = array.ReadPortal();
T expectedValue = T(1);
for (vtkm::Id arrayIndex = 0; arrayIndex < ARRAY_SIZE; ++arrayIndex)
{
T foundValue = portal.Get(arrayIndex);
VTKM_TEST_ASSERT(test_equal(foundValue, 0), foundValue, " != ", expectedValue);
}
}
struct NotFunctor : vtkm::worklet::WorkletMapField
{
using ControlSignature = void(FieldIn ignored, ExecObject);
using ExecutionSignature = void(WorkIndex, _2);
VTKM_EXEC void operator()(vtkm::Id index, T* data) const
{
vtkm::Id arrayIndex = index % ARRAY_SIZE;
vtkm::Id offsetIndex = index / ARRAY_SIZE;
if (offsetIndex < arrayIndex)
{
vtkm::AtomicNot(data + arrayIndex);
}
}
};
VTKM_CONT void TestNot()
{
std::cout << "AtomicNot" << std::endl;
vtkm::cont::ArrayHandleBasic<T> array;
vtkm::cont::ArrayCopy(vtkm::cont::make_ArrayHandleConstant<T>(0xA, ARRAY_SIZE), array);
array.Allocate(ARRAY_SIZE);
this->Invoke(
AndFunctor{}, vtkm::cont::ArrayHandleIndex(EXTENDED_SIZE), ArrayToRawPointer(array));
auto portal = array.ReadPortal();
T expectedValue = T(0xA);
for (vtkm::Id arrayIndex = 0; arrayIndex < ARRAY_SIZE; ++arrayIndex)
{
T foundValue = portal.Get(arrayIndex);
VTKM_TEST_ASSERT(test_equal(foundValue, 0), foundValue, " != ", expectedValue);
expectedValue = static_cast<T>(~expectedValue);
}
}
struct CompareAndSwapFunctor : vtkm::worklet::WorkletMapField
{
using ControlSignature = void(FieldIn ignored, ExecObject);
using ExecutionSignature = void(WorkIndex, _2);
VTKM_EXEC void operator()(vtkm::Id index, T* data) const
{
vtkm::Id arrayIndex = index % ARRAY_SIZE;
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)
{
success = true;
break;
}
}
if (!success)
{
this->RaiseError("No compare succeeded");
}
}
};
VTKM_CONT void TestCompareAndSwap()
{
std::cout << "AtomicCompareAndSwap" << std::endl;
vtkm::cont::ArrayHandleBasic<T> array;
vtkm::cont::ArrayCopy(vtkm::cont::make_ArrayHandleConstant<T>(0, ARRAY_SIZE), array);
array.Allocate(ARRAY_SIZE);
this->Invoke(
AddFunctor{}, vtkm::cont::ArrayHandleIndex(EXTENDED_SIZE), ArrayToRawPointer(array));
auto portal = array.ReadPortal();
T expectedValue = T(OVERLAP);
for (vtkm::Id arrayIndex = 0; arrayIndex < ARRAY_SIZE; ++arrayIndex)
{
T foundValue = portal.Get(arrayIndex);
VTKM_TEST_ASSERT(test_equal(foundValue, expectedValue), foundValue, " != ", expectedValue);
}
}
VTKM_CONT void TestAll()
{
TestLoad();
TestStore();
TestAdd();
TestAnd();
TestOr();
TestXor();
TestNot();
TestCompareAndSwap();
}
};
struct TestFunctor
{
template <typename T>
VTKM_CONT void operator()(T) const
{
AtomicTests<T>().TestAll();
}
};
void Run()
{
VTKM_TEST_ASSERT(vtkm::ListHas<vtkm::AtomicTypesSupported, vtkm::AtomicTypePreferred>::value);
vtkm::testing::Testing::TryTypes(TestFunctor{}, vtkm::AtomicTypesSupported{});
}
} // anonymous namespace
int UnitTestAtomic(int argc, char* argv[])
{
return vtkm::cont::testing::Testing::Run(Run, argc, argv);
}