5.4. Worklet Arguments
From the ControlSignature and ExecutionSignature defined in worklets,
Viskores uses template metaprogramming to build the code required to manage
data from the control to the execution environment.
These signatures contain tags that define the meaning of each argument and
control how the argument data are transferred from the control to execution
environments and broken up for each worklet instance.
Chapter 4.3 (Worklet Types) documents the many ControlSignature
and ExecutionSignature tags that come with the worklet types.
This chapter discusses the internals of these tags and how they control data
management.
Defining new worklet argument types can allow you to define new data structures
in Viskores.
New worklet arguments are also usually critical components for making new
worklet types, as described in the later chapter on creating new worklet types.
The management of data in worklet arguments is handled by three classes that
provide type checking, transportation, and fetching, respectively.
This chapter first describes these type-checking, transportation, and fetching
classes and then describes how ControlSignature and ExecutionSignature
tags specify these classes.
Throughout this chapter, we demonstrate the definition of worklet arguments
using an example of a worklet argument that represents line segments in 2D.
The input for such an argument expects an
viskores::cont::ArrayHandle containing floating-point
viskores::Vec objects of size 2 to represent coordinates in the plane.
The values in the array are paired to define the two endpoints of each segment,
and the worklet instance receives a Vec-2 of Vec-2 objects representing
the two endpoints.
In practice, it is generally easier to use a
viskores::cont::ArrayHandleGroupVec (see
Section 4.9.12 (Grouped Vector Arrays)), but this is a simple
example for demonstration purposes.
We also use this special worklet argument for our later example of a custom
worklet type.
5.4.1. Type Checks
Before attempting to move data from the control to the execution environment,
the Viskores invokers check the input types to ensure that they are compatible
with the associated ControlSignature concept.
This is done with the viskores::cont::arg::TypeCheck structure.
-
template<typename TypeCheckTag, typename Type>
struct TypeCheck Class for checking that a type matches the semantics for an argument.
The
TypeCheckclass is used in dispatchers to test whether an argument passed to theInvokecommand matches the corresponding argument in theControlSignature.This check happens after casting dynamic classes to static classes, so the check need not worry about querying dynamic types.
The generic implementation of
TypeCheckalways results in failure. When a new type check tag is defined, along with it should be partial specializations that find valid types.Public Static Attributes
-
static constexpr bool value = false
The static constant boolean
valueis set totrueif the type is valid for the given check tag andfalseotherwise.
-
static constexpr bool value = false
The viskores::cont::arg::TypeCheck structure is templated with two
parameters.
The first parameter is a tag that identifies which check to perform.
The second parameter is the type of the control argument (after any dynamic
casts).
The structure contains a static constant Boolean named value that is
true if the type in the second parameter is compatible with the tag in the
first, or false otherwise.
Type checks are implemented with a defined type-check tag, which by convention
is defined in the viskores::cont::arg namespace and starts with
TypeCheckTag, and a partial specialization of the
viskores::cont::arg::TypeCheck structure.
The following type checks, identified by their tags, are provided in
Viskores.
-
struct TypeCheckTagExecObject
The ExecObject type check passes for any object that inherits from
ExecutionObjectBaseand follows the conventions of that class.
-
struct TypeCheckTagArrayIn
The Array type check passes for any object that behaves like an
ArrayHandleclass and can be passed to the ArrayIn transport.
-
struct TypeCheckTagArrayOut
The Array type check passes for any object that behaves like an
ArrayHandleclass and can be passed to the ArrayOut transport.
-
struct TypeCheckTagArrayInOut
The Array type check passes for any object that behaves like an
ArrayHandleclass and can be passed to the ArrayInOut transport.
-
struct TypeCheckTagAtomicArray
The atomic array type check passes for an
ArrayHandleof a structure that is valid for atomic access.There are many restrictions on the type of data that can be used for an atomic array.
-
struct TypeCheckTagBitField
The Array type check passes for
viskores::cont::BitField.
-
struct TypeCheckTagCellSet
Check for a CellSet-like object.
-
struct TypeCheckTagCellSetStructured
Check for a Structured CellSet-like object.
-
struct TypeCheckTagKeys
Check for a
viskores::worklet::Keysobject.
Here are some trivial examples of using
viskores::cont::arg::TypeCheck.
Typically these checks are done internally in the base Viskores invoker code,
so these examples are for demonstration only.
1struct MyExecObject : viskores::cont::ExecutionObjectBase
2{
3 viskores::Id Value;
4};
5
6void DoTypeChecks()
7{
8 using viskores::cont::arg::TypeCheck;
9 using viskores::cont::arg::TypeCheckTagArrayIn;
10 using viskores::cont::arg::TypeCheckTagExecObject;
11
12 bool check1 = TypeCheck<TypeCheckTagExecObject, MyExecObject>::value; // true
13 bool check2 = TypeCheck<TypeCheckTagExecObject, viskores::Id>::value; // false
14
15 using ArrayType = viskores::cont::ArrayHandle<viskores::Float32>;
16
17 bool check3 = TypeCheck<TypeCheckTagArrayIn, ArrayType>::value; // true
18 bool check4 = TypeCheck<TypeCheckTagExecObject, ArrayType>::value; // false
19}
A type check is created by first defining a type-check tag object, which by
convention is placed in the viskores::cont::arg namespace and
whose name starts with TypeCheckTag.
Then, create a specialization of the
viskores::cont::arg::TypeCheck template class with the first template
argument matching the aforementioned tag.
As stated previously, the viskores::cont::arg::TypeCheck class must
contain a value static constant Boolean representing whether the type is
acceptable for the corresponding viskores::cont::Invoker argument.
This example of a viskores::cont::arg::TypeCheck returns true for
control objects that are viskores::cont::ArrayHandle objects with a
value type that is a floating-point viskores::Vec of size 2.
1namespace viskores
2{
3namespace cont
4{
5namespace arg
6{
7
8struct TypeCheckTag2DCoordinates
9{
10};
11
12template<typename ArrayType>
13struct TypeCheck<TypeCheckTag2DCoordinates, ArrayType>
14{
15 static constexpr bool value = false;
16};
17
18template<typename T, typename Storage>
19struct TypeCheck<TypeCheckTag2DCoordinates, viskores::cont::ArrayHandle<T, Storage>>
20{
21 static constexpr bool value = viskores::ListHas<viskores::TypeListFieldVec2, T>::value;
22};
23
24} // namespace arg
25} // namespace cont
26} // namespace viskores
5.4.2. Transport
After all the argument types are checked, the Viskores dispatch mechanism
must load the data into the execution environment before scheduling a job to
run there.
This is done with the viskores::cont::arg::Transport structure.
-
template<typename TransportTag, typename ContObjectType, typename DeviceAdapterTag>
struct Transport Class for transporting from the control to the execution environment.
The
Transportclass is used to transport data of a certain type from the control environment to the execution environment. It is used internally in Viskores’s dispatch mechanism.Transportis a templated class with three arguments. The first argument is a tag declaring the mechanism of transport. The second argument is the type of data to transport. The third argument is device adapter tag for the device to move the data to.There is no generic implementation of
Transport. There are partial specializations ofTransportfor each mechanism supported. If you get a compiler error about an incomplete type forTransport, it means you used an invalidTransportTagor it is an invalid combination of data type or device adapter.Public Types
-
using ExecObjectType = typename ContObjectType::ReadPortalType
The type used in the execution environment.
All
Transportspecializations are expected to declare a type namedExecObjectTypethat is the object type used in the execution environment. For example, for anArrayHandle, theExecObjectTypeis the portal used in the execution environment.
Public Functions
-
template<typename InputDomainType>
ExecObjectType operator()(ContObjectType &object, const InputDomainType &inputDomain, viskores::Id inputRange, viskores::Id outputRange, viskores::cont::Token &token) const Send data to the execution environment.
All
Transportspecializations are expected to have a constant parenthesis operator that takes the data in the control environment and returns an object that is accessible in the execution environment.- Parameters:
object – The control-side object that must be transferred to the device indicated by the
DeviceAdapterTagtemplate parameter of this struct.inputDomain – A reference to the input domain argument. This might have state necessary to establish the execution-side object. For some transports, this object can be ignored.
inputRange – The size of the input domain. This can be used for checking the size of the data to ensure it has values for each input.
outputRange – The size of the output domain. This can be used for checking the size of the data to ensure it has has a place for each output.
token – A reference to a token object that is used to generate execution-side objects. The token ensures that the execution object remains valid while it is still being used.
-
using ExecObjectType = typename ContObjectType::ReadPortalType
The viskores::cont::arg::Transport structure is templated with three
parameters.
The first parameter is a tag that identifies which transport to perform.
The second parameter is the type of the control parameter (after any dynamic
casts).
The third parameter is a device adapter tag for the device on which the data
will be loaded.
A viskores::cont::arg::Transport contains a type named
ExecObjectType that is the type used after data is moved to the execution
environment.
A viskores::cont::arg::Transport also has a const parenthesis
operator that takes five arguments: the control-side object to transport to the
execution environment, the control-side object that represents the input
domain, the size of the input domain, the size of the output domain, and a
reference to a viskores::cont::Token object that defines the scope of
any generated execution-environment objects.
The parenthesis operator returns an execution-side object.
This operator is called in the control environment, and it returns an object
that is ready to be used in the execution environment.
Transports are implemented with a defined transport tag, which by convention
is defined in the viskores::cont::arg namespace and starts with
TransportTag, and a partial specialization of the
viskores::cont::arg::Transport structure.
The following transports, identified by their tags, are provided in
Viskores.
-
struct TransportTagExecObject
Transporttag for execution objects.Calls
PrepareForInputon the providedviskores::cont::ExecutionObjectBaseobject. The returned execution object is what PrepareForInput provides.
-
struct TransportTagArrayIn
Transporttag for input arrays.Loads data from an
viskores::cont::ArrayHandleonto the specified device using the array handle’sviskores::cont::ArrayHandle::PrepareForInput()method. The size of the array must be the same as the input domain. The returned execution object is an array portal.
-
struct TransportTagArrayOut
Transporttag for output arrays.Allocates data on the specified device for an
viskores::cont::ArrayHandleusing the array handle’sviskores::cont::ArrayHandle::PrepareForOutput()method. The array is allocated to the size of the output domain. The returned execution object is an array portal.
-
struct TransportTagArrayInOut
Transporttag for in-place arrays.Loads data from an
viskores::cont::ArrayHandleonto the specified device using the array handle’sviskores::cont::ArrayHandle::PrepareForInPlace()method. The size of the array must be the same as the output domain, which is not necessarily the same size as the input domain. The returned execution object is an array portal.
-
struct TransportTagWholeArrayIn
Transporttag for in-place arrays with random access.Loads data from an
viskores::cont::ArrayHandleonto the specified device using the array handle’sviskores::cont::ArrayHandle::PrepareForInput()method. This transport is designed for random-access whole arrays, so unlikeviskores::cont::arg::TransportTagArrayIn, the array size can be unassociated with the input domain. The returned execution object is an array portal.
-
struct TransportTagWholeArrayOut
Transporttag for in-place arrays with random access.Readies data from an
viskores::cont::ArrayHandleon the specified device using the array handle’sviskores::cont::ArrayHandle::PrepareForOutput()method. This transport is designed for random-access whole arrays, so unlikeviskores::cont::arg::TransportTagArrayOut, the array size can be unassociated with the input domain. Thus, the array must be preallocated and its size is not changed. The returned execution object is an array portal.The worklet will have random access to the array through a portal interface, but care should be taken to not write a value in one instance that will be overridden by another entry.
-
struct TransportTagWholeArrayInOut
Transporttag for in-place arrays with random access.Loads data from an
viskores::cont::ArrayHandleonto the specified device using the array handle’sviskores::cont::ArrayHandle::PrepareForInPlace()method. This transport is designed for random-access whole arrays, so unlikeviskores::cont::arg::TransportTagArrayInOut, the array size can be unassociated with the input domain. The returned execution object is an array portal.The worklet will have random access to the array through a portal interface, but care should be taken to not write a value in one instance that will be read by or overridden by another entry.
-
struct TransportTagAtomicArray
Transporttag for in-place arrays with atomic operations.TransportTagAtomicArrayis a tag used with theTransportclass to transportArrayHandleobjects for data that is both input and output (that is, in place modification of array data). The array will be wrapped in a viskores::exec::AtomicArray class that provides atomic operations (like add and compare/swap).
-
struct TransportTagBitFieldIn
Loads data from a
viskores::cont::BitField.The worklet receives a bit portal, so the size of the field does not have to match the input domain.
-
struct TransportTagBitFieldOut
Readies a
viskores::cont::BitFieldfor writing from a worklet.The worklet receives a bit portal, so the array must be preallocated and its size does not have to match the output domain.
-
struct TransportTagBitFieldInOut
Readies a
viskores::cont::BitFieldfor reading and writing from a worklet.The worklet receives a bit portal, so its size does not have to match the output domain.
-
template<typename VisitTopology, typename IncidentTopology>
struct TransportTagCellSetIn Transporttag for input arrays.Loads data from a
viskores::cont::CellSetobject.TransportTagCellSetInis a templated class with two parameters: the “visit” topology and the “incident” topology. The returned execution object is a connectivity object.
-
template<typename TopologyElementTag>
struct TransportTagTopologyFieldIn Transporttag for input arrays in topology maps.Similar to viskores::cont::arg::TransportTagArrayIn, except that the size is checked against the topology of a cell set for the input domain. The input-domain object is assumed to be a viskores::cont::CellSet.
-
struct TransportTagKeysIn
Transporttag for keys in a reduce by key.Loads data from a
viskores::worklet::Keysobject. This transport is intended for the input domain of aviskores::worklet::WorkletReduceByKey. The returned execution object is of typeviskores::exec::internal::ReduceByKeyLookup.
-
struct TransportTagKeyedValuesIn
Transporttag for input values in a reduce by key.TransportTagKeyedValuesInis a tag used with theTransportclass to transportArrayHandleobjects for input values. The values are rearranged and grouped based on the keys they are associated with, which come from the input domain, which is expected to be aviskores::worklet::Keysobject.
-
struct TransportTagKeyedValuesOut
Transporttag for input values in a reduce by key.TransportTagKeyedValuesOutis a tag used with theTransportclass to transportArrayHandleobjects for output values. The values are rearranged and grouped based on the keys they are associated with, which come from the input domain, which is expected to be aviskores::worklet::Keysobject.
-
struct TransportTagKeyedValuesInOut
Transporttag for input values in a reduce by key.TransportTagKeyedValuesInOutis a tag used with theTransportclass to transportArrayHandleobjects for input/output values. The values are rearranged and grouped based on the keys they are associated with, which come from the input domain, which is expected to be aviskores::worklet::Keysobject.
Here are some trivial examples of using
viskores::cont::arg::Transport.
Typically this movement is done internally in the Viskores dispatching code,
so these examples are for demonstration only.
1template<typename ArrayType, typename Device>
2void DoTransport(ArrayType inArray, ArrayType outArray, Device)
3{
4 VISKORES_IS_ARRAY_HANDLE(ArrayType);
5 VISKORES_IS_DEVICE_ADAPTER_TAG(Device);
6
7 using viskores::cont::arg::Transport;
8 using viskores::cont::arg::TransportTagArrayIn;
9 using viskores::cont::arg::TransportTagArrayOut;
10 using viskores::cont::arg::TransportTagWholeArrayInOut;
11
12 viskores::cont::Token token;
13
14 // The array in transport returns a read-only array portal.
15 using ArrayInTransport = Transport<TransportTagArrayIn, ArrayType, Device>;
16 typename ArrayInTransport::ExecObjectType inPortal =
17 ArrayInTransport()(inArray, inArray, 10, 10, token);
18
19 // The array out transport returns an allocated array portal.
20 using ArrayOutTransport = Transport<TransportTagArrayOut, ArrayType, Device>;
21 typename ArrayOutTransport::ExecObjectType outPortal =
22 ArrayOutTransport()(outArray, inArray, 10, 10, token);
23
24 // The whole array in transport returns a read-only array portal wrapped in
25 // a viskores::exec::ExecutionWholeArrayConst.
26 using WholeArrayTransport = Transport<TransportTagWholeArrayInOut, ArrayType, Device>;
27 typename WholeArrayTransport::ExecObjectType wholeArray =
28 WholeArrayTransport()(inArray, inArray, 10, 10, token);
29}
A transport is created by first defining a transport-tag object, which by
convention is placed in the viskores::cont::arg namespace and
whose name starts with TransportTag.
Then, create a specialization of the
viskores::cont::arg::Transport template class with the first template
argument matching the aforementioned tag.
As stated previously, the viskores::cont::arg::Transport class must
contain an ExecObjectType type and a parenthesis operator that turns the
associated control argument into an execution-environment object.
This example internally uses a
viskores::cont::ArrayHandleGroupVec to take values from an input
viskores::cont::ArrayHandle and pair them to represent line segments.
The resulting execution object is an array portal containing Vec-2 values
of Vec-2 objects.
1namespace viskores
2{
3namespace cont
4{
5namespace arg
6{
7
8struct TransportTag2DLineSegmentsIn
9{
10};
11
12template<typename ContObjectType, typename Device>
13struct Transport<viskores::cont::arg::TransportTag2DLineSegmentsIn,
14 ContObjectType,
15 Device>
16{
17 VISKORES_IS_ARRAY_HANDLE(ContObjectType);
18
19 using GroupedArrayType = viskores::cont::ArrayHandleGroupVec<ContObjectType, 2>;
20
21 using ExecObjectType = typename GroupedArrayType::ReadPortalType;
22
23 template<typename InputDomainType>
24 VISKORES_CONT ExecObjectType operator()(const ContObjectType& object,
25 const InputDomainType&,
26 viskores::Id inputRange,
27 viskores::Id,
28 viskores::cont::Token& token) const
29 {
30 if (object.GetNumberOfValues() != inputRange * 2)
31 {
32 throw viskores::cont::ErrorBadValue(
33 "2D line segment array size does not agree with input size.");
34 }
35
36 GroupedArrayType groupedArray(object);
37 return groupedArray.PrepareForInput(Device{}, token);
38 }
39};
40
41} // namespace arg
42} // namespace cont
43} // namespace viskores
Common Errors
It is fair to assume that the viskores::cont::arg::Transport control-object type matches
whatever the associated viskores::cont::arg::TypeCheck allows.
However, it is good practice to provide a secondary compile-time check in the viskores::cont::arg::Transport class, like the one at Example 5.19, line 17, for debugging in case there is a problem with the viskores::cont::arg::TypeCheck or this viskores::cont::arg::Transport is used with an unexpected viskores::cont::arg::TypeCheck.
5.4.3. Fetch
Before a worklet function is invoked, the Viskores internals pull the
appropriate data out of the execution object and pass it to the worklet
function.
A class named viskores::exec::arg::Fetch is responsible for pulling
this data out of execution objects and putting computed data into them.
-
template<typename FetchTag, typename AspectTag, typename ExecObjectType>
struct Fetch Class for loading and storing values in thread instance.
The
Fetchclass is used within a thread in the execution environment to load a value from an execution object specific for the given thread instance and to store a resulting value back in the object. (Either load or store can be a no-op.)Fetchis a templated class with four arguments. The first argument is a tag declaring the type of fetch, which is usually tied to a particular type of execution object. The second argument is an aspect tag that declares what type of data to pull/push. Together, these two tags determine the mechanism for the fetch. The third argument is the type of thread indices used (one of the classes that starts withThreadIndicesin the viskores::exec::arg namespace), which defines the type of thread-local indices available during the fetch. The fourth argument is the type of execution object associated where the fetch (nominally) gets its data from. This execution object is the data provided by the transport.There is no generic implementation of
Fetch. There are partial specializations ofFetchfor each mechanism (fetch-aspect tag combination) supported. If you get a compiler error about an incomplete type forFetch, it means you used an invalidFetchTag-AspectTagcombination. Most likely this means that a parameter in an ExecutionSignature with a particular aspect is pointing to the wrong argument or an invalid argument in the ControlSignature.Public Types
-
using ValueType = typename ExecObjectType::ValueType
The type of value to load and store.
All
Fetchspecializations are expected to declare a type namedValueTypethat is the type of object returned fromLoadand passed toStore.
Public Functions
-
template<typename ThreadIndicesType>
ValueType Load(const ThreadIndicesType &indices, const ExecObjectType &execObject) const Load data for a work instance.
All
Fetchspecializations are expected to have a constant method namedLoadthat takes aThreadIndicesobject containing thread-local indices and an execution object and returns the value appropriate for the work instance. If there is no actual data to load (for example for a write-only fetch), this method can be a no-op and return any value.
-
template<typename ThreadIndicesType>
void Store(const ThreadIndicesType &indices, const ExecObjectType &execObject, const ValueType &value) const Store data from a work instance.
All
Fetchspecializations are expected to have a constant method namedStorethat takes aThreadIndicesobject containing thread-local indices, an execution object, and a value computed by the worklet call and stores that value into the execution object associated with this fetch. If the store is not applicable (for example for a read-only fetch), this method can be a no-op.
-
using ValueType = typename ExecObjectType::ValueType
The viskores::exec::arg::Fetch structure is templated with three
parameters.
The first parameter is a tag that identifies which type of fetch to perform.
The second parameter is a different tag that identifies the aspect of the data
to fetch.
The third template parameter to a viskores::exec::arg::Fetch is the
type of execution object created by the
viskores::cont::arg::Transport, as described in
Section 5.4.2 (Transport).
This is generally where the data are fetched from.
A viskores::exec::arg::Fetch also has a pair of methods named
Load() and Store() that get data from and add data to the execution object
at a given domain or thread index.
In both the Load() and Store() methods, the first parameter is a thread
indices object that manages the multiple indices relevant to the worklet
invocation, including the input index, output index, and visit index, all of
which can be different.
The specific type of the thread indices object depends on the type of worklet
being invoked, but all thread indices classes implement methods named
GetInputIndex, GetOutputIndex, and GetVisitIndex to get those
respective indices.
The thread indices object may also contain other methods to get information
pertinent to the associated worklet’s execution.
For example, a thread indices object associated with a topology map has methods
to get the shape identifier and incident-from indices of the current input
object.
Thread indices objects are discussed in more detail in the later section on
thread indices.
Fetches are specified with a pair of fetch and aspect tags.
Fetch tags are by convention defined in the
viskores::exec::arg namespace and start with FetchTag.
Likewise, aspect tags are also defined in the
viskores::exec::arg namespace and start with AspectTag.
The viskores::exec::arg::Fetch class is partially specialized on
these two tags.
The most common aspect tag is
viskores::exec::arg::AspectTagDefault, and all fetch tags should have
a specialization of viskores::exec::arg::Fetch with this tag.
The following list of fetch tags describes the execution objects they work
with and the data they pull for each aspect tag they support.