Add ExecutionAndControlObjectBase

This is a subclass of ExecutionObject and a superset of its
functionality. In addition to having a PrepareForExecution method, it
also has a PrepareForControl method that gets an object appropriate for
the control environment. This is helpful for situations where you need
code to work in both environments, such as the functor in an
ArrayHandleTransform.

Also added several runtime checks for execution objects and execution
and cotnrol objects.
This commit is contained in:
Kenneth Moreland 2018-09-08 11:07:06 -06:00
parent 62d3b9f4fb
commit 2b05487398
12 changed files with 273 additions and 70 deletions

@ -12,8 +12,9 @@ might be that you need some lookup tables. Another might be you want to
support a virtual object, which has to be initialized for a particular
device. The standard way to implement this in VTK-m is to create an
"executive object." This actually means that we create a wrapper around
executive objects that inherits from `vtkm::cont::ExecutionObjectBase` that
contains a `PrepareForExecution` method.
executive objects that inherits from
`vtkm::cont::ExecutionAndControlObjectBase` that contains a
`PrepareForExecution` method and a `PrepareForControl` method.
As an example, consider the use case of a special `ArrayHandle` that takes
the value in one array and returns the index of that value in another
@ -56,12 +57,14 @@ struct FindValueFunctor
Simple enough, except that the type of `ArrayPortalType` depends on what
device the functor runs on (not to mention its memory might need to be
moved to different hardware). We can now solve this problem by creating an
execution object to set this up for a device.
moved to different hardware). We can now solve this problem by creating a
functor objecgt set this up for a device. `ArrayHandle`s also need to be
able to provide portals that run in the control environment, and for that
we need a special version of the functor for the control environment.
``` cpp
template <typename ArrayHandleType>
struct FindValueExecutionObject : vtkm::cont::ExecutionObjectBase
struct FindValueExecutionObject : vtkm::cont::ExecutionAndControlObjectBase
{
VTKM_IS_ARRAY_HANDLE(ArrayHandleType);
@ -83,6 +86,16 @@ struct FindValueExecutionObject : vtkm::cont::ExecutionObjectBase
return FunctorType(this->SortedArray.PrepareForInput(device));
}
VTKM_CONT
FundValueFunctor<typename ArrayHandleType::PortalConstControl>
PrepareForControl()
{
using FunctorType =
FindValueFunctor<typename ArrayHandleType::PortalConstControl>
return FunctorType(this->SortedArray.GetPortalConstControl());
}
}
```

@ -0,0 +1,16 @@
# Add new execution and control objects
[Recent changes to execution objects](change-execution-object-creation.md)
now have execution objects behave as factories that create an object
specific for a particular device. Sometimes, you also need to be able to
get an object that behaves properly in the control environment. For these
cases, a sublcass to `vtkm::cont::ExecutionObjectBase` was created.
This subclass is called `vtkm::cont::ExecutionAndControlObjectBase`. In
addition to the `PrepareForExecution` method required by its superclass,
these objects also need to provide a `PrepareForControl` method to get an
equivalent object that works in the control environment.
See [the changelog for execution objects in
`ArrayHandleTransform`](array-handle-transform-exec-object.md) for an
example of using a `vtkm::cont::ExecutionAndControlObjectBase`.

@ -38,24 +38,25 @@ template <typename Device, typename T>
inline auto DoPrepareArgForExec(T&& object, std::true_type)
-> decltype(std::declval<T>().PrepareForExecution(Device()))
{
VTKM_IS_EXECUTION_OBJECT(T);
return object.PrepareForExecution(Device{});
}
template <typename Device, typename T>
inline T&& DoPrepareArgForExec(T&& object, std::false_type)
{
static_assert(!vtkm::cont::internal::IsExecutionObjectBase<T>::value,
"Internal error: failed to detect execution object.");
return std::forward<T>(object);
}
template <typename Device, typename T>
auto PrepareArgForExec(T&& object) -> decltype(DoPrepareArgForExec<Device>(
std::forward<T>(object),
typename std::is_base_of<vtkm::cont::ExecutionObjectBase, typename std::decay<T>::type>::type{}))
auto PrepareArgForExec(T&& object)
-> decltype(DoPrepareArgForExec<Device>(std::forward<T>(object),
vtkm::cont::internal::IsExecutionObjectBase<T>{}))
{
return DoPrepareArgForExec<Device>(
std::forward<T>(object),
typename std::is_base_of<vtkm::cont::ExecutionObjectBase,
typename std::decay<T>::type>::type{});
return DoPrepareArgForExec<Device>(std::forward<T>(object),
vtkm::cont::internal::IsExecutionObjectBase<T>{});
}
struct CopyFunctor

@ -25,9 +25,8 @@
#include <vtkm/cont/ArrayHandle.h>
#include <vtkm/cont/ErrorBadType.h>
#include <vtkm/cont/ErrorInternal.h>
#include <vtkm/cont/ExecutionObjectBase.h>
#include <vtkm/cont/ExecutionAndControlObjectBase.h>
#include <vtkm/cont/RuntimeDeviceTracker.h>
#include <vtkm/cont/serial/DeviceAdapterSerial.h>
namespace vtkm
{
@ -181,14 +180,17 @@ namespace cont
namespace internal
{
template <typename ProvidedFunctorType, typename Device, typename FunctorIsExecObject>
template <typename ProvidedFunctorType, typename FunctorIsExecContObject>
struct TransformFunctorManagerImpl;
template <typename ProvidedFunctorType, typename Device>
struct TransformFunctorManagerImpl<ProvidedFunctorType, Device, std::false_type>
template <typename ProvidedFunctorType>
struct TransformFunctorManagerImpl<ProvidedFunctorType, std::false_type>
{
VTKM_STATIC_ASSERT_MSG(!vtkm::cont::internal::IsExecutionObjectBase<ProvidedFunctorType>::value,
"Must use an ExecutionAndControlObject instead of an ExecutionObject.");
ProvidedFunctorType Functor;
using FunctorType = ProvidedFunctorType;
ProvidedFunctorType Functor;
TransformFunctorManagerImpl() = default;
@ -199,14 +201,23 @@ struct TransformFunctorManagerImpl<ProvidedFunctorType, Device, std::false_type>
}
VTKM_CONT
FunctorType GetFunctor() const { return this->Functor; }
ProvidedFunctorType PrepareForControl() const { return this->Functor; }
template <typename Device>
VTKM_CONT ProvidedFunctorType PrepareForExecution(Device) const
{
return this->Functor;
}
};
template <typename ProvidedFunctorType, typename Device>
struct TransformFunctorManagerImpl<ProvidedFunctorType, Device, std::true_type>
template <typename ProvidedFunctorType>
struct TransformFunctorManagerImpl<ProvidedFunctorType, std::true_type>
{
using FunctorType = decltype(std::declval<ProvidedFunctorType>().PrepareForExecution(Device()));
VTKM_IS_EXECUTION_AND_CONTROL_OBJECT(ProvidedFunctorType);
ProvidedFunctorType Functor;
// using FunctorType = decltype(std::declval<ProvidedFunctorType>().PrepareForControl());
using FunctorType = decltype(Functor.PrepareForControl());
TransformFunctorManagerImpl() = default;
@ -217,20 +228,28 @@ struct TransformFunctorManagerImpl<ProvidedFunctorType, Device, std::true_type>
}
VTKM_CONT
FunctorType GetFunctor() const { return this->Functor.PrepareForExecution(Device()); }
auto PrepareForControl() const -> decltype(this->Functor.PrepareForControl())
{
return this->Functor.PrepareForControl();
}
template <typename Device>
VTKM_CONT auto PrepareForExecution(Device device) const
-> decltype(this->Functor.PrepareForExecution(device))
{
return this->Functor.PrepareForExecution(device);
}
};
template <typename ProvidedFunctorType, typename Device = vtkm::cont::DeviceAdapterTagSerial>
template <typename ProvidedFunctorType>
struct TransformFunctorManager
: TransformFunctorManagerImpl<
ProvidedFunctorType,
Device,
typename std::is_base_of<vtkm::cont::ExecutionObjectBase, ProvidedFunctorType>::type>
typename vtkm::cont::internal::IsExecutionAndControlObjectBase<ProvidedFunctorType>::type>
{
using Superclass = TransformFunctorManagerImpl<
ProvidedFunctorType,
Device,
typename std::is_base_of<vtkm::cont::ExecutionObjectBase, ProvidedFunctorType>::type>;
typename vtkm::cont::internal::IsExecutionAndControlObjectBase<ProvidedFunctorType>::type>;
using FunctorType = typename Superclass::FunctorType;
VTKM_CONT TransformFunctorManager() = default;
@ -242,13 +261,6 @@ struct TransformFunctorManager
{
}
template <typename OtherDevice>
VTKM_CONT TransformFunctorManager(
const TransformFunctorManager<ProvidedFunctorType, OtherDevice>& other)
: Superclass(other.Functor)
{
}
template <typename ValueType>
using TransformedValueType = decltype(std::declval<FunctorType>()(ValueType{}));
};
@ -312,7 +324,7 @@ public:
VTKM_ASSERT(this->Valid);
vtkm::cont::ScopedGlobalRuntimeDeviceTracker trackerScope;
vtkm::cont::GetGlobalRuntimeDeviceTracker().ForceDevice(vtkm::cont::DeviceAdapterTagSerial());
return PortalConstType(this->Array.GetPortalConstControl(), this->Functor.GetFunctor());
return PortalConstType(this->Array.GetPortalConstControl(), this->Functor.PrepareForControl());
}
VTKM_CONT
@ -405,8 +417,8 @@ public:
vtkm::cont::ScopedGlobalRuntimeDeviceTracker trackerScope;
vtkm::cont::GetGlobalRuntimeDeviceTracker().ForceDevice(vtkm::cont::DeviceAdapterTagSerial());
return PortalType(this->Array.GetPortalControl(),
this->Functor.GetFunctor(),
this->InverseFunctor.GetFunctor());
this->Functor.PrepareForControl(),
this->InverseFunctor.PrepareForControl());
}
VTKM_CONT
@ -416,8 +428,8 @@ public:
vtkm::cont::ScopedGlobalRuntimeDeviceTracker trackerScope;
vtkm::cont::GetGlobalRuntimeDeviceTracker().ForceDevice(vtkm::cont::DeviceAdapterTagSerial());
return PortalConstType(this->Array.GetPortalConstControl(),
this->Functor.GetFunctor(),
this->InverseFunctor.GetFunctor());
this->Functor.PrepareForControl(),
this->InverseFunctor.PrepareForControl());
}
VTKM_CONT
@ -470,7 +482,7 @@ class ArrayTransfer<typename StorageTagTransform<ArrayHandleType, FunctorType>::
Device>
{
using StorageTag = StorageTagTransform<ArrayHandleType, FunctorType>;
using FunctorManager = TransformFunctorManager<FunctorType, Device>;
using FunctorManager = TransformFunctorManager<FunctorType>;
public:
using ValueType = typename StorageTagTransform<ArrayHandleType, FunctorType>::ValueType;
@ -499,7 +511,8 @@ public:
VTKM_CONT
PortalConstExecution PrepareForInput(bool vtkmNotUsed(updateData))
{
return PortalConstExecution(this->Array.PrepareForInput(Device()), this->Functor.GetFunctor());
return PortalConstExecution(this->Array.PrepareForInput(Device()),
this->Functor.PrepareForExecution(Device()));
}
VTKM_CONT
@ -548,8 +561,8 @@ class ArrayTransfer<
Device>
{
using StorageTag = StorageTagTransform<ArrayHandleType, FunctorType, InverseFunctorType>;
using FunctorManager = TransformFunctorManager<FunctorType, Device>;
using InverseFunctorManager = TransformFunctorManager<InverseFunctorType, Device>;
using FunctorManager = TransformFunctorManager<FunctorType>;
using InverseFunctorManager = TransformFunctorManager<InverseFunctorType>;
public:
using ValueType = typename StorageTagTransform<ArrayHandleType, FunctorType>::ValueType;
@ -584,24 +597,24 @@ public:
PortalConstExecution PrepareForInput(bool vtkmNotUsed(updateData))
{
return PortalConstExecution(this->Array.PrepareForInput(Device()),
this->Functor.GetFunctor(),
this->InverseFunctor.GetFunctor());
this->Functor.PrepareForExecution(Device()),
this->InverseFunctor.PrepareForExecution(Device()));
}
VTKM_CONT
PortalExecution PrepareForInPlace(bool& vtkmNotUsed(updateData))
{
return PortalExecution(this->Array.PrepareForInPlace(Device()),
this->Functor.GetFunctor(),
this->InverseFunctor.GetFunctor());
this->Functor.PrepareForExecution(Device()),
this->InverseFunctor.PrepareForExecution(Device()));
}
VTKM_CONT
PortalExecution PrepareForOutput(vtkm::Id numberOfValues)
{
return PortalExecution(this->Array.PrepareForOutput(numberOfValues, Device()),
this->Functor.GetFunctor(),
this->InverseFunctor.GetFunctor());
this->Functor.PrepareForExecution(Device()),
this->InverseFunctor.PrepareForExecution(Device()));
}
VTKM_CONT

@ -83,6 +83,7 @@ set(headers
ErrorFilterExecution.h
ErrorExecution.h
ErrorInternal.h
ExecutionAndControlObjectBase.h
ExecutionObjectBase.h
Field.h
FieldRangeCompute.h

@ -0,0 +1,82 @@
//============================================================================
// 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.
//
// Copyright 2018 National Technology & Engineering Solutions of Sandia, LLC (NTESS).
// Copyright 2018 UT-Battelle, LLC.
// Copyright 2018 Los Alamos National Security.
//
// Under the terms of Contract DE-NA0003525 with NTESS,
// the U.S. Government retains certain rights in this software.
//
// Under the terms of Contract DE-AC52-06NA25396 with Los Alamos National
// Laboratory (LANL), the U.S. Government retains certain rights in
// this software.
//============================================================================
#ifndef vtk_m_cont_ExecutionAndControlObjectBase_h
#define vtk_m_cont_ExecutionAndControlObjectBase_h
#include <vtkm/cont/ExecutionObjectBase.h>
namespace vtkm
{
namespace cont
{
/// Base \c ExecutionAndControlObjectBase class. These are objects that behave
/// as execution objects but can also be use din the control environment.
/// Any subclass of \c ExecutionAndControlObjectBase must implement a
/// \c PrepareForExecution method that takes a device adapter tag and returns
/// an object for that device as well as a \c PrepareForControl that simply
/// returns an object that works in the control environment.
///
struct ExecutionAndControlObjectBase : vtkm::cont::ExecutionObjectBase
{
};
namespace internal
{
namespace detail
{
struct CheckPrepareForControl
{
template <typename T>
static auto check(T* p) -> decltype(p->PrepareForControl(), std::true_type());
template <typename T>
static auto check(...) -> std::false_type;
};
} // namespace detail
template <typename T>
using IsExecutionAndControlObjectBase =
std::is_base_of<vtkm::cont::ExecutionAndControlObjectBase, typename std::decay<T>::type>;
template <typename T>
struct HasPrepareForControl
: decltype(detail::CheckPrepareForControl::check<typename std::decay<T>::type>(nullptr))
{
};
} // namespace internal
}
} // namespace vtkm::cont
/// Checks that the argument is a proper execution object.
///
#define VTKM_IS_EXECUTION_AND_CONTROL_OBJECT(execObject) \
static_assert(::vtkm::cont::internal::IsExecutionAndControlObjectBase<execObject>::value, \
"Provided type is not a subclass of vtkm::cont::ExecutionAndControlObjectBase."); \
static_assert(::vtkm::cont::internal::HasPrepareForExecution<execObject>::value, \
"Provided type does not have requisite PrepareForExecution method."); \
static_assert(::vtkm::cont::internal::HasPrepareForControl<execObject>::value, \
"Provided type does not have requisite PrepareForControl method.")
#endif //vtk_m_cont_ExecutionAndControlObjectBase_h

@ -19,21 +19,75 @@
//============================================================================
#ifndef vtk_m_cont_ExecutionObjectBase_h
#define vtk_m_cont_ExecutionObjectBase_h
#include <vtkm/Types.h>
#include <vtkm/cont/DeviceAdapter.h>
#if ((VTKM_DEVICE_ADAPTER > 0) && (VTKM_DEVICE_ADAPTER < VTKM_MAX_DEVICE_ADAPTER_ID))
// Use the default device adapter tag for testing whether execution objects are valid.
#define VTK_M_DEVICE_ADAPTER_TO_TEST_EXEC_OBJECT VTKM_DEFAULT_DEVICE_ADAPTER_TAG
#else
// The default device adapter is invalid. Perhaps the error device adapter is being used.
// In this case, try the serial device adapter instead. It should always be valid.
#include <vtkm/cont/serial/DeviceAdapterSerial.h>
#define VTK_M_DEVICE_ADAPTER_TO_TEST_EXEC_OBJECT ::vtkm::cont::DeviceAdapterTagSerial
#endif
namespace vtkm
{
namespace cont
{
/// Base \c ExecutionObjectBase for execution objects to inherit from so that
/// you can use an arbitrary object as a parameter in an execution environment
/// function. Any method you want to use on the execution side must have the
/// VTKM_EXEC modifier.
/// \tparam Device
class ExecutionObjectBase
/// function. Any subclass of \c ExecutionObjectBase must implement a
/// \c PrepareForExecution method that takes a device adapter tag and returns
/// an object for that device.
///
struct ExecutionObjectBase
{
};
namespace internal
{
namespace detail
{
struct CheckPrepareForExecution
{
template <typename T>
static auto check(T* p)
-> decltype(p->PrepareForExecution(VTK_M_DEVICE_ADAPTER_TO_TEST_EXEC_OBJECT()),
std::true_type());
template <typename T>
static auto check(...) -> std::false_type;
};
} // namespace detail
template <typename T>
using IsExecutionObjectBase =
std::is_base_of<vtkm::cont::ExecutionObjectBase, typename std::decay<T>::type>;
template <typename T>
struct HasPrepareForExecution
: decltype(detail::CheckPrepareForExecution::check<typename std::decay<T>::type>(nullptr))
{
};
} // namespace internal
}
} // namespace vtkm::cont
/// Checks that the argument is a proper execution object.
///
#define VTKM_IS_EXECUTION_OBJECT(execObject) \
static_assert(::vtkm::cont::internal::IsExecutionObjectBase<execObject>::value, \
"Provided type is not a subclass of vtkm::cont::ExecutionObjectBase."); \
static_assert(::vtkm::cont::internal::HasPrepareForExecution<execObject>::value, \
"Provided type does not have requisite PrepareForExecution method.")
#endif //vtk_m_cont_ExecutionObjectBase_h

@ -48,10 +48,8 @@ struct Transport<vtkm::cont::arg::TransportTagExecObject, ContObjectType, Device
{
// If you get a compile error here, it means you tried to use an object that is not an execution
// object as an argument that is expected to be one. All execution objects are expected to
// inherit from vtkm::cont::ExecutionObjectBase.
VTKM_STATIC_ASSERT_MSG(
(std::is_base_of<vtkm::cont::ExecutionObjectBase, ContObjectType>::value),
"All execution objects are expected to inherit from vtkm::cont::ExecutionObjectBase");
// inherit from vtkm::cont::ExecutionObjectBase and have a PrepareForExecution method.
VTKM_IS_EXECUTION_OBJECT(ContObjectType);
using ExecObjectType = decltype(std::declval<ContObjectType>().PrepareForExecution(Device()));
template <typename InputDomainType>

@ -46,7 +46,7 @@ struct TypeCheckTagExecObject
template <typename Type>
struct TypeCheck<TypeCheckTagExecObject, Type>
{
static constexpr bool value = std::is_base_of<vtkm::cont::ExecutionObjectBase, Type>::value;
static constexpr bool value = vtkm::cont::internal::IsExecutionObjectBase<Type>::value;
};
}
}

@ -33,6 +33,13 @@
namespace
{
struct NotAnExecutionObject
{
};
struct InvalidExecutionObject : vtkm::cont::ExecutionObjectBase
{
};
template <typename Device>
struct ExecutionObject
{
@ -51,6 +58,7 @@ struct TestExecutionObject : public vtkm::cont::ExecutionObjectBase
return object;
}
};
template <typename Device>
struct TestKernel : public vtkm::exec::FunctorBase
{
@ -83,6 +91,21 @@ void TryExecObjectTransport(Device)
void TestExecObjectTransport()
{
std::cout << "Checking ExecObject queries." << std::endl;
VTKM_TEST_ASSERT(!vtkm::cont::internal::IsExecutionObjectBase<NotAnExecutionObject>::value,
"Bad query");
VTKM_TEST_ASSERT(vtkm::cont::internal::IsExecutionObjectBase<InvalidExecutionObject>::value,
"Bad query");
VTKM_TEST_ASSERT(vtkm::cont::internal::IsExecutionObjectBase<TestExecutionObject>::value,
"Bad query");
VTKM_TEST_ASSERT(!vtkm::cont::internal::HasPrepareForExecution<NotAnExecutionObject>::value,
"Bad query");
VTKM_TEST_ASSERT(!vtkm::cont::internal::HasPrepareForExecution<InvalidExecutionObject>::value,
"Bad query");
VTKM_TEST_ASSERT(vtkm::cont::internal::HasPrepareForExecution<TestExecutionObject>::value,
"Bad query");
std::cout << "Trying ExecObject transport with serial device." << std::endl;
TryExecObjectTransport(vtkm::cont::DeviceAdapterTagSerial());
}

@ -162,7 +162,7 @@ struct VirtualTransformFunctor : VirtualTransformFunctorBase<ValueType>
};
template <typename ValueType>
struct TransformExecObject : public vtkm::cont::ExecutionObjectBase
struct TransformExecObject : public vtkm::cont::ExecutionAndControlObjectBase
{
vtkm::cont::VirtualObjectHandle<VirtualTransformFunctorBase<ValueType>> VirtualFunctor;
@ -178,15 +178,14 @@ struct TransformExecObject : public vtkm::cont::ExecutionObjectBase
this->VirtualFunctor.Reset(new VirtualTransformFunctor<ValueType, FunctorType>(functor));
}
template <typename DeviceAdapterTag>
struct ExecutionObject
struct FunctorWrapper
{
const VirtualTransformFunctorBase<ValueType>* FunctorPointer;
ExecutionObject() = default;
FunctorWrapper() = default;
VTKM_CONT
ExecutionObject(const VirtualTransformFunctorBase<ValueType>* functorPointer)
FunctorWrapper(const VirtualTransformFunctorBase<ValueType>* functorPointer)
: FunctorPointer(functorPointer)
{
}
@ -199,9 +198,14 @@ struct TransformExecObject : public vtkm::cont::ExecutionObjectBase
};
template <typename DeviceAdapterTag>
VTKM_CONT ExecutionObject<DeviceAdapterTag> PrepareForExecution(DeviceAdapterTag device) const
VTKM_CONT FunctorWrapper PrepareForExecution(DeviceAdapterTag device) const
{
return ExecutionObject<DeviceAdapterTag>(this->VirtualFunctor.PrepareForExecution(device));
return FunctorWrapper(this->VirtualFunctor.PrepareForExecution(device));
}
VTKM_CONT FunctorWrapper PrepareForControl() const
{
return FunctorWrapper(this->VirtualFunctor.Get());
}
};
}

@ -22,8 +22,6 @@
#include <vtkm/exec/arg/testing/ThreadIndicesTesting.h>
#include <vtkm/cont/ExecutionObjectBase.h>
#include <vtkm/testing/Testing.h>
#define EXPECTED_NUMBER 67
@ -31,7 +29,7 @@
namespace
{
struct TestExecutionObject : public vtkm::cont::ExecutionObjectBase
struct TestExecutionObject
{
TestExecutionObject()
: Number(static_cast<vtkm::Int32>(0xDEADDEAD))