/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ /* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ #ifndef WEBGLCOMMANDQUEUE_H_ #define WEBGLCOMMANDQUEUE_H_ #include #include "mozilla/FunctionTypeTraits.h" #include "mozilla/dom/ProducerConsumerQueue.h" #include "mozilla/ipc/IPDLParamTraits.h" #include "QueueParamTraits.h" #include "WebGLTypes.h" // Get around a bug in Clang related to __thiscall method pointers #if defined(_M_IX86) # define SINK_FCN_CC __thiscall #else # define SINK_FCN_CC #endif namespace mozilla { using webgl::QueueStatus; namespace webgl { class RangeConsumerView final : public webgl::ConsumerView { RangedPtr mSrcItr; const RangedPtr mSrcEnd; public: auto Remaining() const { return *MaybeAs(mSrcEnd - mSrcItr); } explicit RangeConsumerView(const Range range) : ConsumerView(this, nullptr, 0), mSrcItr(range.begin()), mSrcEnd(range.end()) { (void)Remaining(); // assert size non-negative } void AlignTo(const size_t alignment) { const auto offset = AlignmentOffset(alignment, mSrcItr.get()); if (offset > Remaining()) { mSrcItr = mSrcEnd; return; } mSrcItr += offset; } template Maybe> ReadRange(const size_t elemCount) { AlignTo(alignof(T)); constexpr auto elemSize = sizeof(T); const auto byteSizeChecked = CheckedInt(elemCount) * elemSize; MOZ_RELEASE_ASSERT(byteSizeChecked.isValid()); const auto& byteSize = byteSizeChecked.value(); const auto remaining = Remaining(); if (byteSize > remaining) return {}; const auto begin = reinterpret_cast(mSrcItr.get()); mSrcItr += byteSize; return Some(Range{begin, elemCount}); } }; // - namespace details { class SizeOnlyProducerView final : public webgl::ProducerView { size_t mRequiredSize = 0; public: SizeOnlyProducerView() : ProducerView(this, 0, nullptr) {} template void WriteFromRange(const Range& src) { constexpr auto alignment = alignof(T); const size_t byteSize = ByteSize(src); // printf_stderr("SizeOnlyProducerView: @%zu +%zu\n", alignment, byteSize); const auto offset = AlignmentOffset(alignment, mRequiredSize); mRequiredSize += offset; mRequiredSize += byteSize; } const auto& RequiredSize() const { return mRequiredSize; } }; // - class RangeProducerView final : public webgl::ProducerView { const RangedPtr mDestBegin; const RangedPtr mDestEnd; RangedPtr mDestItr; public: auto Remaining() const { return *MaybeAs(mDestEnd - mDestItr); } explicit RangeProducerView(const Range range) : ProducerView(this, 0, nullptr), mDestBegin(range.begin()), mDestEnd(range.end()), mDestItr(mDestBegin) { (void)Remaining(); // assert size non-negative } template void WriteFromRange(const Range& src) { constexpr auto alignment = alignof(T); const size_t byteSize = ByteSize(src); // printf_stderr("RangeProducerView: @%zu +%zu\n", alignment, byteSize); const auto offset = AlignmentOffset(alignment, mDestItr.get()); mDestItr += offset; MOZ_ASSERT(byteSize <= Remaining()); if (byteSize) { memcpy(mDestItr.get(), src.begin().get(), byteSize); } mDestItr += byteSize; } }; // - template inline void Serialize(ProducerViewT&) {} template inline void Serialize(ProducerViewT& view, const Arg& arg, const Args&... args) { MOZ_ALWAYS_TRUE(view.WriteParam(arg) == QueueStatus::kSuccess); Serialize(view, args...); } } // namespace details // - template size_t SerializedSize(const Args&... args) { webgl::details::SizeOnlyProducerView sizeView; webgl::details::Serialize(sizeView, args...); return sizeView.RequiredSize(); } template void Serialize(Range dest, const Args&... args) { webgl::details::RangeProducerView view(dest); webgl::details::Serialize(view, args...); } // - inline bool Deserialize(RangeConsumerView& view) { return true; } template inline bool Deserialize(RangeConsumerView& view, Arg& arg, Args&... args) { if (!webgl::QueueParamTraits::Read(view, &arg)) return false; return Deserialize(view, args...); } } // namespace webgl // - using mozilla::ipc::IPDLParamTraits; enum CommandResult { kSuccess, kTimeExpired, kQueueEmpty, kError }; enum CommandSyncType { ASYNC, SYNC }; /** * A CommandSource is obtained from a CommandQueue. Use it by inserting a * command (represented by type Command) using InsertCommand, which also * needs all parameters to the command. They are then serialized and sent * to the CommandSink, which must understand the Command+Args combination * to execute it. */ template class CommandSource { using Source = _Source; public: explicit CommandSource(UniquePtr&& aSource) : mSource(std::move(aSource)) { MOZ_ASSERT(mSource); } template QueueStatus InsertCommand(Command aCommand, Args&&... aArgs) { return this->mSource->TryWaitInsert(Nothing() /* wait forever */, aCommand, aArgs...); } QueueStatus InsertCommand(Command aCommand) { return this->mSource->TryWaitInsert(Nothing() /* wait forever */, aCommand); } template QueueStatus RunCommand(Command aCommand, Args&&... aArgs) { return InsertCommand(aCommand, std::forward(aArgs)...); } // For IPDL: CommandSource() = default; protected: friend struct IPDLParamTraits>; UniquePtr mSource; }; /** * A CommandSink is obtained from a CommandQueue. It executes commands that * originated in its CommandSource. Use this class by calling one of the * Process methods, which will autonomously deserialize, dispatch and * post-process the execution. This class handles deserialization -- dispatch * and processing are to be provided by a subclass in its implementation of the * pure-virtual DispatchCommand method. DispatchCommand implementations can * easily run functions and methods using arguments taken from the command * queue by calling the Dispatch methods in this class. */ template class CommandSink { using Sink = _Sink; public: explicit CommandSink(UniquePtr&& aSink) : mSink(std::move(aSink)) { MOZ_ASSERT(mSink); } /** * Attempts to process the next command in the queue, if one is available. */ CommandResult ProcessOne(const Maybe& aTimeout) { Command command; QueueStatus status = (aTimeout.isNothing() || aTimeout.value()) ? this->mSink->TryWaitRemove(aTimeout, command) : this->mSink->TryRemove(command); if (status == QueueStatus::kSuccess) { if (DispatchCommand(command)) { return CommandResult::kSuccess; } return CommandResult::kError; } if (status == QueueStatus::kNotReady) { return CommandResult::kQueueEmpty; } if (status == QueueStatus::kOOMError) { ReportOOM(); } return CommandResult::kError; } CommandResult ProcessOneNow() { return ProcessOne(Some(TimeDuration(0))); } /** * Drains the queue until the queue is empty or an error occurs, whichever * comes first. * Returns the result of the last attempt to process a command, which will * be either QueueEmpty or Error. */ CommandResult ProcessAll() { CommandResult result; do { result = ProcessOneNow(); } while (result == CommandResult::kSuccess); return result; } /** * Drains the queue until aDuration expires, the queue is empty, or an error * occurs, whichever comes first. * Returns the result of the last attempt to process a command. */ CommandResult ProcessUpToDuration(TimeDuration aDuration) { TimeStamp start = TimeStamp::Now(); TimeStamp now = start; CommandResult result; do { result = ProcessOne(Some(aDuration - (now - start))); now = TimeStamp::Now(); } while ((result == CommandResult::kSuccess) && ((now - start) < aDuration)); return result; } // For IPDL: CommandSink() = default; // non-void return value, non-const method variant template bool DispatchAsyncMethod(T& aObj, ReturnType (T::*aMethod)(Args...)) { std::tuple>...> args; if (!ReadArgs(args)) { return false; } CallMethod(aObj, aMethod, args, std::index_sequence_for{}); return true; } // non-void return value, const method variant template bool DispatchAsyncMethod(const T& aObj, ReturnType (T::*aMethod)(Args...) const) { std::tuple>...> args; if (!ReadArgs(args)) { return false; } CallMethod(aObj, aMethod, args, std::index_sequence_for{}); return true; } // void return value, non-const method variant template bool DispatchAsyncMethod(T* aObj, void (T::*aMethod)(Args...)) { std::tuple>...> args; if (!ReadArgs(args)) { return false; } CallVoidMethod(aObj, aMethod, args, std::index_sequence_for{}); return true; } // void return value, const method variant template bool DispatchAsyncMethod(const T* aObj, void (T::*aMethod)(Args...) const) { std::tuple>...> args; if (!ReadArgs(args)) { return false; } CallVoidMethod(aObj, aMethod, args, std::index_sequence_for{}); return true; } protected: friend struct IPDLParamTraits>; /** * Implementations will usually be something like a big switch statement * that calls one of the Dispatch methods in this class. */ virtual bool DispatchCommand(Command command) = 0; /** * Implementations can override this to detect out-of-memory during * deserialization. */ virtual void ReportOOM() {} template QueueStatus CallTryRemove(std::tuple& aArgs, std::index_sequence) { QueueStatus status = mSink->TryRemove(std::get(aArgs)...); // The CommandQueue inserts the command and the args together as an atomic // operation. We already read the command so the args must also be // available. MOZ_ASSERT(status != QueueStatus::kNotReady); return status; } QueueStatus CallTryRemove(std::tuple<>& aArgs, std::make_integer_sequence) { return QueueStatus::kSuccess; } template ::ReturnType> ReturnType CallMethod(T& aObj, MethodType aMethod, std::tuple& aArgs, std::index_sequence) { return (aObj.*aMethod)(std::forward(std::get(aArgs))...); } template void CallVoidMethod(T& aObj, MethodType aMethod, std::tuple& aArgs, std::index_sequence) { (aObj.*aMethod)(std::forward(std::get(aArgs))...); } template bool ReadArgs(std::tuple& aArgs) { QueueStatus status = CallTryRemove(aArgs, std::index_sequence_for{}); return IsSuccess(status); } UniquePtr mSink; }; enum SyncResponse : uint8_t { RESPONSE_NAK, RESPONSE_ACK }; /** * This is the Source for a SyncCommandSink. It takes an extra queue, * the ResponseQueue, and uses it to receive synchronous responses from * the sink. The ResponseQueue is a regular queue, not a CommandQueue. */ template class SyncCommandSource : public CommandSource { public: using BaseType = CommandSource; using Source = _Source; using ResponseQueue = _ResponseQueue; using ResponseSink = typename ResponseQueue::Consumer; SyncCommandSource(UniquePtr&& aSource, UniquePtr&& aResponseSink) : CommandSource(std::move(aSource)), mResponseSink(std::move(aResponseSink)) {} template QueueStatus RunAsyncCommand(Command aCommand, Args&&... aArgs) { return this->RunCommand(aCommand, std::forward(aArgs)...); } template QueueStatus RunVoidSyncCommand(Command aCommand, Args&&... aArgs) { QueueStatus status = RunAsyncCommand(aCommand, std::forward(aArgs)...); return IsSuccess(status) ? this->ReadSyncResponse() : status; } template QueueStatus RunSyncCommand(Command aCommand, ResultType& aReturn, Args&&... aArgs) { QueueStatus status = RunVoidSyncCommand(aCommand, std::forward(aArgs)...); return IsSuccess(status) ? this->ReadResult(aReturn) : status; } // for IPDL: SyncCommandSource() = default; friend struct mozilla::ipc::IPDLParamTraits< SyncCommandSource>; protected: QueueStatus ReadSyncResponse() { SyncResponse response; QueueStatus status = mResponseSink->TryWaitRemove(Nothing() /* wait forever */, response); MOZ_ASSERT(status != QueueStatus::kNotReady); if (IsSuccess(status) && response != RESPONSE_ACK) { return QueueStatus::kFatalError; } return status; } template QueueStatus ReadResult(T& aResult) { QueueStatus status = mResponseSink->TryRemove(aResult); // The Sink posts the response code and result as an atomic transaction. We // already read the response code so the result must be available. MOZ_ASSERT(status != QueueStatus::kNotReady); return status; } UniquePtr mResponseSink; }; /** * This is the Sink for a SyncCommandSource. It takes an extra queue, the * ResponseQueue, and uses it to issue synchronous responses to the client. * Subclasses can use the DispatchSync methods in this class in their * DispatchCommand implementations. * The ResponseQueue is not a CommandQueue. */ template class SyncCommandSink : public CommandSink { using BaseType = CommandSink; using ResponseQueue = _ResponseQueue; using Sink = _Sink; using ResponseSource = typename ResponseQueue::Producer; public: SyncCommandSink(UniquePtr&& aSink, UniquePtr&& aResponseSource) : CommandSink(std::move(aSink)), mResponseSource(std::move(aResponseSource)) { MOZ_ASSERT(mResponseSource); } // for IPDL: SyncCommandSink() = default; friend struct mozilla::ipc::IPDLParamTraits< SyncCommandSink>; // Places RESPONSE_ACK and the typed return value, or RESPONSE_NAK, in // the response queue, // __cdecl/__thiscall non-const method variant. template bool DispatchSyncMethod(T& aObj, ReturnType SINK_FCN_CC (T::*aMethod)(Args...)) { std::tuple>...> args; if (!BaseType::ReadArgs(args)) { WriteNAK(); return false; } ReturnType response = BaseType::CallMethod( aObj, aMethod, args, std::index_sequence_for{}); return WriteACK(response); } // __cdecl/__thiscall const method variant. template bool DispatchSyncMethod(const T& aObj, ReturnType SINK_FCN_CC (T::*aMethod)(Args...) const) { std::tuple>...> args; if (!BaseType::ReadArgs(args)) { WriteNAK(); return false; } ReturnType response = BaseType::CallMethod( aObj, aMethod, args, std::index_sequence_for{}); return WriteACK(response); } #if defined(_M_IX86) // __stdcall non-const method variant. template bool DispatchSyncMethod(T& aObj, ReturnType __stdcall (T::*aMethod)(Args...)) { std::tuple>...> args; if (!BaseType::ReadArgs(args)) { WriteNAK(); return false; } ReturnType response = BaseType::CallMethod( aObj, aMethod, args, std::index_sequence_for{}); return WriteACK(response); } // __stdcall const method variant. template bool DispatchSyncMethod(const T& aObj, ReturnType __stdcall (T::*aMethod)(Args...) const) { std::tuple>...> args; if (!BaseType::ReadArgs(args)) { WriteNAK(); return false; } ReturnType response = BaseType::CallMethod( aObj, aMethod, args, std::index_sequence_for{}); return WriteACK(response); } #endif // __cdecl/__thiscall non-const void method variant template bool DispatchSyncMethod(T& aObj, void SINK_FCN_CC (T::*aMethod)(Args...)) { std::tuple>...> args; if (!BaseType::ReadArgs(args)) { WriteNAK(); return false; } BaseType::CallVoidMethod(aObj, aMethod, args, std::index_sequence_for{}); return WriteACK(); } // __cdecl/__thiscall const void method variant template bool DispatchSyncMethod(const T& aObj, void SINK_FCN_CC (T::*aMethod)(Args...) const) { std::tuple>...> args; if (!BaseType::ReadArgs(args)) { WriteNAK(); return false; } BaseType::CallVoidMethod(aObj, aMethod, args, std::index_sequence_for{}); return WriteACK(); } #if defined(_M_IX86) // __stdcall non-const void method variant template bool DispatchSyncMethod(T& aObj, void __stdcall (T::*aMethod)(Args...)) { std::tuple>...> args; if (!BaseType::ReadArgs(args)) { WriteNAK(); return false; } BaseType::CallVoidMethod(aObj, aMethod, args, std::index_sequence_for{}); return WriteACK(); } // __stdcall const void method variant template bool DispatchSyncMethod(const T& aObj, void __stdcall (T::*aMethod)(Args...) const) { std::tuple>...> args; if (!BaseType::ReadArgs(args)) { WriteNAK(); return false; } BaseType::CallVoidMethod(aObj, aMethod, args, std::index_sequence_for{}); return WriteACK(); } #endif protected: template bool WriteArgs(const Args&... aArgs) { return IsSuccess(mResponseSource->TryInsert(aArgs...)); } template bool WriteACK(const Args&... aArgs) { SyncResponse ack = RESPONSE_ACK; return WriteArgs(ack, aArgs...); } bool WriteNAK() { SyncResponse nak = RESPONSE_NAK; return WriteArgs(nak); } UniquePtr mResponseSource; }; // The MethodDispatcher setup uses a CommandSink to read parameters, call the // given method using the given synchronization protocol, and provide // compile-time lookup of the ID by class method. // To use this system, first define a dispatcher subclass of // EmptyMethodDispatcher. This class must be parameterized by command ID. // // Example: // template class MyDispatcher // : public EmptyMethodDispatcher {}; // // Then, for each command handled, specialize this to subclass MethodDispatcher. // The subclass must define the Method. It may optionally define isSync for // synchronous methods. // // Example: // template <> // class MyDispatcher<0> // : public MethodDispatcher {}; // // The method may then be called from the source and run on the sink. // // Example: // int result = Run(param1, std::move(param2)); template