//============================================================================ // 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 2014 National Technology & Engineering Solutions of Sandia, LLC (NTESS). // Copyright 2014 UT-Battelle, LLC. // Copyright 2014 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_worklet_internal_DispatcherBase_h #define vtk_m_worklet_internal_DispatcherBase_h #include #include #include #include #include #include #include #include #include #include #include #include #include #include namespace vtkm { namespace cont { // Forward declaration. template class DynamicCellSetBase; } } namespace vtkm { namespace worklet { namespace internal { template inline auto scheduling_range(const Domain& inputDomain) -> decltype(inputDomain.GetNumberOfValues()) { return inputDomain.GetNumberOfValues(); } template inline auto scheduling_range(const Domain* const inputDomain) -> decltype(inputDomain->GetNumberOfValues()) { return inputDomain->GetNumberOfValues(); } template inline auto scheduling_range(const Domain& inputDomain, SchedulingRangeType type) -> decltype(inputDomain.GetSchedulingRange(type)) { return inputDomain.GetSchedulingRange(type); } template inline auto scheduling_range(const Domain* const inputDomain, SchedulingRangeType type) -> decltype(inputDomain->GetSchedulingRange(type)) { return inputDomain->GetSchedulingRange(type); } namespace detail { // This code is actually taking an error found at compile-time and not // reporting it until run-time. This seems strange at first, but this // behavior is actually important. With dynamic arrays and similar dynamic // classes, there may be types that are technically possible (such as using a // vector where a scalar is expected) but in reality never happen. Thus, for // these unsupported combinations we just silently halt the compiler from // attempting to create code for these errant conditions and throw a run-time // error if one every tries to create one. inline void PrintFailureMessage(int index) { std::stringstream message; message << "Encountered bad type for parameter " << index << " when calling Invoke on a dispatcher."; throw vtkm::cont::ErrorBadType(message.str()); } inline void PrintNullPtrMessage(int index, int mode) { std::stringstream message; if (mode == 0) { message << "Encountered nullptr for parameter " << index; } else { message << "Encountered nullptr for " << index << " from last parameter "; } message << " when calling Invoke on a dispatcher."; throw vtkm::cont::ErrorBadValue(message.str()); } template inline void not_nullptr(T* ptr, int index, int mode = 0) { if (!ptr) { PrintNullPtrMessage(index, mode); } } template inline void not_nullptr(T&&, int, int mode = 0) { (void)mode; } template inline T& as_ref(T* ptr) { return *ptr; } template inline T&& as_ref(T&& t) { return std::forward(t); } template struct ReportTypeOnError; template struct ReportTypeOnError : std::true_type { }; template struct ReportValueOnError; template struct ReportValueOnError : std::true_type { }; template struct remove_pointer_and_decay : std::remove_pointer::type> { }; // Is designed as a brigand fold operation. template struct DetermineIfHasDynamicParameter { using T = typename std::remove_pointer::type; using DynamicTag = typename vtkm::cont::internal::DynamicTransformTraits::DynamicTag; using isDynamic = typename std::is_same::type; using type = std::integral_constant; }; // Is designed as a brigand fold operation. template struct DetermineHasCorrectParameters { template struct Functor { //T is the type of the Param at the current index //State if the index to use to fetch the control signature tag using ControlSignatureTag = typename brigand::at_c; using TypeCheckTag = typename ControlSignatureTag::TypeCheckTag; using T = typename std::remove_pointer::type; static constexpr bool isCorrect = vtkm::cont::arg::TypeCheck::value; // If you get an error on the line below, that means that your code has called the // Invoke method on a dispatcher, and one of the arguments of the Invoke is the wrong // type. Each argument of Invoke corresponds to a tag in the arguments of the // ControlSignature of the worklet. If there is a mismatch, then you get an error here // (instead of where you called the dispatcher). For example, if the worklet has a // control signature as ControlSignature(CellSetIn, ...) and the first argument passed // to Invoke is an ArrayHandle, you will get an error here because you cannot use an // ArrayHandle in place of a CellSetIn argument. (You need to use a CellSet.) See a few // lines later for some diagnostics to help you trace where the error occurred. VTKM_READ_THE_SOURCE_CODE_FOR_HELP(isCorrect); // If you are getting the error described above, the following lines will give you some // diagnostics (in the form of compile errors). Each one will result in a compile error // reporting an undefined type for ReportTypeOnError (or ReportValueOnError). What we are // really reporting is the first template argument, which is one of the types or values that // should help pinpoint where the error is. The comment for static_assert provides the // type/value being reported. (Note that some compilers report better types than others. If // your compiler is giving unhelpful types like "T" or "WorkletType", you may need to try a // different compiler.) static_assert(ReportTypeOnError::value, "Type passed to Invoke"); static_assert(ReportTypeOnError::value, "Worklet being invoked."); static_assert(ReportValueOnError::value, "Index of Invoke parameter"); static_assert(ReportTypeOnError::value, "Type check tag used"); // This final static_assert gives a human-readable error message. Ideally, this would be // placed first, but some compilers will suppress further errors when a static_assert // fails, so you would not see the other diagnostic error messages. static_assert(isCorrect, "The type of one of the arguments to the dispatcher's Invoke method is " "incompatible with the corresponding tag in the worklet's ControlSignature."); using type = std::integral_constant; }; }; // Checks that an argument in a ControlSignature is a valid control signature // tag. Causes a compile error otherwise. struct DispatcherBaseControlSignatureTagCheck { template struct ReturnType { // If you get a compile error here, it means there is something that is // not a valid control signature tag in a worklet's ControlSignature. VTKM_IS_CONTROL_SIGNATURE_TAG(ControlSignatureTag); using type = ControlSignatureTag; }; }; // Checks that an argument in a ExecutionSignature is a valid execution // signature tag. Causes a compile error otherwise. struct DispatcherBaseExecutionSignatureTagCheck { template struct ReturnType { // If you get a compile error here, it means there is something that is not // a valid execution signature tag in a worklet's ExecutionSignature. VTKM_IS_EXECUTION_SIGNATURE_TAG(ExecutionSignatureTag); using type = ExecutionSignatureTag; }; }; struct DispatcherBaseTryExecuteFunctor { template VTKM_CONT bool operator()(Device device, const DispatcherBaseType* self, Invocation& invocation, const RangeType& dimensions) { self->InvokeTransportParameters( invocation, dimensions, self->Scatter.GetOutputRange(dimensions), device); return true; } }; // A look up helper used by DispatcherBaseTransportFunctor to determine //the types independent of the device we are templated on. template struct DispatcherBaseTransportInvokeTypes { //Moved out of DispatcherBaseTransportFunctor to reduce code generation using ControlSignatureTag = typename ControlInterface::template ParameterType::type; using TransportTag = typename ControlSignatureTag::TransportTag; }; VTKM_CONT inline vtkm::Id FlatRange(vtkm::Id range) { return range; } VTKM_CONT inline vtkm::Id FlatRange(const vtkm::Id3& range) { return range[0] * range[1] * range[2]; } // A functor used in a StaticCast of a FunctionInterface to transport arguments // from the control environment to the execution environment. template struct DispatcherBaseTransportFunctor { const InputDomainType& InputDomain; // Warning: this is a reference vtkm::Id InputRange; vtkm::Id OutputRange; // TODO: We need to think harder about how scheduling on 3D arrays works. // Chances are we need to allow the transport for each argument to manage // 3D indices (for example, allocate a 3D array instead of a 1D array). // But for now, just treat all transports as 1D arrays. template VTKM_CONT DispatcherBaseTransportFunctor(const InputDomainType& inputDomain, const InputRangeType& inputRange, const OutputRangeType& outputRange) : InputDomain(inputDomain) , InputRange(FlatRange(inputRange)) , OutputRange(FlatRange(outputRange)) { } template struct ReturnType { using TransportTag = typename DispatcherBaseTransportInvokeTypes::TransportTag; using T = typename remove_pointer_and_decay::type; using TransportType = typename vtkm::cont::arg::Transport; using type = typename TransportType::ExecObjectType; }; // template // VTKM_CONT typename ReturnType::type operator()( // ControlParameter const& invokeData, // vtkm::internal::IndexTag) const // { // using TransportTag = // typename DispatcherBaseTransportInvokeTypes::TransportTag; // using T = typename remove_pointer_and_decay::type; // vtkm::cont::arg::Transport transport; // return transport(invokeData, as_ref(this->InputDomain), this->InputRange, this->OutputRange); // } template VTKM_CONT typename ReturnType::type operator()( ControlParameter&& invokeData, vtkm::internal::IndexTag) const { using TransportTag = typename DispatcherBaseTransportInvokeTypes::TransportTag; using T = typename remove_pointer_and_decay::type; vtkm::cont::arg::Transport transport; not_nullptr(invokeData, Index); return transport( as_ref(invokeData), as_ref(this->InputDomain), this->InputRange, this->OutputRange); } private: void operator=(const DispatcherBaseTransportFunctor&) = delete; }; //forward declares template struct for_each_dynamic_arg; template struct convert_arg_wrapper { template void operator()(T&& t, Args&&... args) const { using Type = typename std::decay::type; using valid = std::integral_constant::value>; this->WillContinue(valid(), std::forward(t), std::forward(args)...); } template void WillContinue(std::true_type, T&& t, Args&&... args) const { for_each_dynamic_arg()(std::forward(args)..., std::forward(t)); } template void WillContinue(std::false_type, Args&&...) const { vtkm::worklet::internal::detail::PrintFailureMessage(LeftToProcess); } }; template inline void convert_arg(vtkm::cont::internal::DynamicTransformTagStatic, T&& t, const ContParams&, const Trampoline& trampoline, Args&&... args) { //This is a static array, so just push it to the back using popped_sig = brigand::pop_front; for_each_dynamic_arg()( trampoline, popped_sig(), std::forward(args)..., std::forward(t)); } template inline void convert_arg(vtkm::cont::internal::DynamicTransformTagCastAndCall, T&& t, const ContParams&, const Trampoline& trampoline, Args&&... args) { //This is something dynamic so cast and call using tag_check = typename brigand::at_c::TypeCheckTag; using popped_sig = brigand::pop_front; not_nullptr(t, LeftToProcess, 1); vtkm::cont::CastAndCall(as_ref(t), convert_arg_wrapper(), trampoline, popped_sig(), std::forward(args)...); } template struct for_each_dynamic_arg { template void operator()(const Trampoline& trampoline, ContParams&& sig, T&& t, Args&&... args) const { //Determine that state of T when it is either a `cons&` or a `* const&` using Type = typename std::remove_pointer::type>::type; using tag = typename vtkm::cont::internal::DynamicTransformTraits::DynamicTag; //convert the first item to a known type convert_arg( tag(), std::forward(t), sig, trampoline, std::forward(args)...); } }; template <> struct for_each_dynamic_arg<0> { template void operator()(const Trampoline& trampoline, ContParams&&, Args&&... args) const { trampoline.StartInvokeDynamic(std::false_type(), std::forward(args)...); } }; template inline void deduce(Trampoline&& trampoline, ContParams&& sig, Args&&... args) { for_each_dynamic_arg()(std::forward(trampoline), sig, args...); } #if defined(VTKM_MSVC) #pragma warning(push) #pragma warning(disable : 4068) //unknown pragma #endif #if defined(__NVCC__) && defined(__CUDACC_VER_MAJOR__) // Disable warning "calling a __host__ function from a __host__ __device__" // In some cases nv_exec_check_disable doesn't work and therefore you need // to use the following suppressions #pragma push #if (__CUDACC_VER_MAJOR__ < 8) #pragma diag_suppress 2670 #pragma diag_suppress 2668 #endif #if (__CUDACC_VER_MAJOR__ >= 8) #pragma diag_suppress 2735 #pragma diag_suppress 2737 #pragma diag_suppress 2739 #endif #if (__CUDACC_VER_MAJOR__ >= 9) #pragma diag_suppress 2828 #pragma diag_suppress 2864 #pragma diag_suppress 2867 #pragma diag_suppress 2885 #endif #if (__CUDACC_VER_MAJOR__ >= 10) #pragma diag_suppress 2905 #endif #endif //This is a separate function as the pragma guards can cause nvcc //to have an internal compiler error (codegen #3028) template inline auto make_funcIFace(Args&&... args) -> decltype( vtkm::internal::make_FunctionInterface::type...>(args...)) { return vtkm::internal::make_FunctionInterface::type...>(args...); } #if defined(__NVCC__) && defined(__CUDACC_VER_MAJOR__) #pragma pop #endif #if defined(VTKM_MSVC) #pragma warning(pop) #endif } // namespace detail /// This is a help struct to detect out of bound placeholders defined in the /// execution signature at compile time template struct PlaceholderValidator { PlaceholderValidator() {} // An overload operator to detect possible out of bound placeholder template void operator()(brigand::type_>) const { static_assert(N <= MaxIndexAllowed, "An argument in the execution signature" " (usually _2, _3, _4, etc.) refers to a control signature argument that" " does not exist. For example, you will get this error if you have _3 (or" " _4 or _5 or so on) as one of the execution signature arguments, but you" " have fewer than 3 (or 4 or 5 or so on) arguments in the control signature."); } template void operator()(brigand::type_) const { } }; /// Base class for all dispatcher classes. Every worklet type should have its /// own dispatcher. /// template class DispatcherBase { private: using MyType = DispatcherBase; friend struct detail::for_each_dynamic_arg<0>; protected: using ControlInterface = vtkm::internal::FunctionInterface; using ExecutionInterface = vtkm::internal::FunctionInterface; static constexpr vtkm::IdComponent NUM_INVOKE_PARAMS = ControlInterface::ARITY; private: // We don't really need these types, but declaring them checks the arguments // of the control and execution signatures. using ControlSignatureCheck = typename ControlInterface::template StaticTransformType< detail::DispatcherBaseControlSignatureTagCheck>::type; using ExecutionSignatureCheck = typename ExecutionInterface::template StaticTransformType< detail::DispatcherBaseExecutionSignatureTagCheck>::type; template VTKM_CONT void StartInvoke(Args&&... args) const { using ParameterInterface = vtkm::internal::FunctionInterface::type...)>; VTKM_STATIC_ASSERT_MSG(ParameterInterface::ARITY == NUM_INVOKE_PARAMS, "Dispatcher Invoke called with wrong number of arguments."); static_assert( std::is_base_of::value, "The worklet being scheduled by this dispatcher doesn't match the type of the dispatcher"); // Check if the placeholders defined in the execution environment exceed the max bound // defined in the control environment by throwing a nice compile error. using ComponentSig = typename ExecutionInterface::ComponentSig; brigand::for_each(PlaceholderValidator{}); //We need to determine if we have the need to do any dynamic //transforms. This is fairly simple of a query. We just need to check //everything in the FunctionInterface and see if any of them have the //proper dynamic trait. Doing this, allows us to generate zero dynamic //check & convert code when we already know all the types. This results //in smaller executables and libraries. using ParamTypes = typename ParameterInterface::ParameterSig; using HasDynamicTypes = brigand::fold>; this->StartInvokeDynamic(HasDynamicTypes(), std::forward(args)...); } template VTKM_CONT void StartInvokeDynamic(std::true_type, Args&&... args) const { // As we do the dynamic transform, we are also going to check the static // type against the TypeCheckTag in the ControlSignature tags. To do this, // the check needs access to both the parameter (in the parameters // argument) and the ControlSignature tags (in the ControlInterface type). using ContParamsInfo = vtkm::internal::detail::FunctionSigInfo; typename ContParamsInfo::Parameters parameters; detail::deduce(*this, parameters, std::forward(args)...); } template VTKM_CONT void StartInvokeDynamic(std::false_type, Args&&... args) const { using ParameterInterface = vtkm::internal::FunctionInterface::type...)>; //Nothing requires a conversion from dynamic to static types, so //next we need to verify that each argument's type is correct. If not //we need to throw a nice compile time error using ParamTypes = typename ParameterInterface::ParameterSig; using ContSigTypes = typename vtkm::internal::detail::FunctionSigInfo< typename WorkletType::ControlSignature>::Parameters; //isAllValid will throw a compile error if everything doesn't match using isAllValid = brigand::fold< ParamTypes, std::integral_constant, typename detail::DetermineHasCorrectParameters:: template Functor>>; //this warning exists so that we don't get a warning from not using isAllValid using expectedLen = std::integral_constant; static_assert(isAllValid::value == expectedLen::value, "All arguments failed the TypeCheck pass"); //This is a separate function as the pragma guards can cause nvcc //to have an internal compiler error (codegen #3028) auto fi = detail::make_funcIFace(std::forward(args)...); auto ivc = vtkm::internal::Invocation( fi, vtkm::internal::NullType{}, vtkm::internal::NullType{}); static_cast(this)->DoInvoke(ivc); } public: //@{ /// Setting the device ID will force the execute to happen on a particular device. If no device /// is specified (or the device ID is set to any), then a device will automatically be chosen /// based on the runtime device tracker. /// VTKM_CONT void SetDevice(vtkm::cont::DeviceAdapterId device) { this->Device = device; } VTKM_CONT vtkm::cont::DeviceAdapterId GetDevice() const { return this->Device; } //@} using ScatterType = typename WorkletType::ScatterType; template VTKM_CONT void Invoke(Args&&... args) const { this->StartInvoke(std::forward(args)...); } protected: VTKM_CONT DispatcherBase(const WorkletType& worklet, const ScatterType& scatter) : Worklet(worklet) , Scatter(scatter) , Device(vtkm::cont::DeviceAdapterTagAny()) { } friend struct internal::detail::DispatcherBaseTryExecuteFunctor; template VTKM_CONT void BasicInvoke(Invocation& invocation, vtkm::Id numInstances) const { bool success = vtkm::cont::TryExecuteOnDevice(this->Device, internal::detail::DispatcherBaseTryExecuteFunctor(), this, invocation, numInstances); if (!success) { throw vtkm::cont::ErrorExecution("Failed to execute worklet on any device."); } } template VTKM_CONT void BasicInvoke(Invocation& invocation, vtkm::Id2 dimensions) const { this->BasicInvoke(invocation, vtkm::Id3(dimensions[0], dimensions[1], 1)); } template VTKM_CONT void BasicInvoke(Invocation& invocation, vtkm::Id3 dimensions) const { bool success = vtkm::cont::TryExecuteOnDevice(this->Device, internal::detail::DispatcherBaseTryExecuteFunctor(), this, invocation, dimensions); if (!success) { throw vtkm::cont::ErrorExecution("Failed to execute worklet on any device."); } } WorkletType Worklet; ScatterType Scatter; private: // Dispatchers cannot be copied DispatcherBase(const MyType&) = delete; void operator=(const MyType&) = delete; vtkm::cont::DeviceAdapterId Device; template VTKM_CONT void InvokeTransportParameters(Invocation& invocation, const InputRangeType& inputRange, OutputRangeType&& outputRange, DeviceAdapter device) const { // The first step in invoking a worklet is to transport the arguments to // the execution environment. The invocation object passed to this function // contains the parameters passed to Invoke in the control environment. We // will use the template magic in the FunctionInterface class to invoke the // appropriate Transport class on each parameter and get a list of // execution objects (corresponding to the arguments of the Invoke in the // control environment) in a FunctionInterface. Specifically, we use a // static transform of the FunctionInterface to call the transport on each // argument and return the corresponding execution environment object. using ParameterInterfaceType = typename Invocation::ParameterInterface; ParameterInterfaceType& parameters = invocation.Parameters; using TransportFunctorType = detail::DispatcherBaseTransportFunctor; using ExecObjectParameters = typename ParameterInterfaceType::template StaticTransformType::type; ExecObjectParameters execObjectParameters = parameters.StaticTransformCont( TransportFunctorType(invocation.GetInputDomain(), inputRange, outputRange)); // Get the arrays used for scattering input to output. typename WorkletType::ScatterType::OutputToInputMapType outputToInputMap = this->Scatter.GetOutputToInputMap(inputRange); typename WorkletType::ScatterType::VisitArrayType visitArray = this->Scatter.GetVisitArray(inputRange); // Replace the parameters in the invocation with the execution object and // pass to next step of Invoke. Also add the scatter information. this->InvokeSchedule(invocation.ChangeParameters(execObjectParameters) .ChangeOutputToInputMap(outputToInputMap.PrepareForInput(device)) .ChangeVisitArray(visitArray.PrepareForInput(device)), outputRange, device); } template VTKM_CONT void InvokeSchedule(const Invocation& invocation, RangeType range, DeviceAdapter) const { using Algorithm = vtkm::cont::DeviceAdapterAlgorithm; using TaskTypes = typename vtkm::cont::DeviceTaskTypes; // The TaskType class handles the magic of fetching values // for each instance and calling the worklet's function. // The TaskType will evaluate to one of the following classes: // // vtkm::exec::internal::TaskSingular // vtkm::exec::internal::TaskTiling1D // vtkm::exec::internal::TaskTiling3D auto task = TaskTypes::MakeTask(this->Worklet, invocation, range); Algorithm::ScheduleTask(task, range); } }; } } } // namespace vtkm::worklet::internal #endif //vtk_m_worklet_internal_DispatcherBase_h