// Copyright 2019 The SwiftShader Authors. All Rights Reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. #include "Reactor.hpp" #include #ifndef rr_ReactorCoroutine_hpp # define rr_ReactorCoroutine_hpp namespace rr { // Base class for the template Stream class StreamBase { protected: StreamBase(const std::shared_ptr &routine, Nucleus::CoroutineHandle handle) : routine(routine) , handle(handle) {} ~StreamBase() { auto pfn = (Nucleus::CoroutineDestroy *)routine->getEntry(Nucleus::CoroutineEntryDestroy); pfn(handle); } bool await(void *out) { auto pfn = (Nucleus::CoroutineAwait *)routine->getEntry(Nucleus::CoroutineEntryAwait); return pfn(handle, out); } private: std::shared_ptr routine; Nucleus::CoroutineHandle handle; }; // Stream is the interface to a running Coroutine instance. // A Coroutine may Yield() values of type T, which can be retrieved with // await(). template class Stream : public StreamBase { public: inline Stream(const std::shared_ptr &routine, Nucleus::CoroutineHandle handle) : StreamBase(routine, handle) {} // await() retrieves the next yielded value from the coroutine. // Returns true if the coroutine yieled a value and out was assigned a // new value. If await() returns false, the coroutine has finished // execution and await() will return false for all future calls. inline bool await(T &out) { return StreamBase::await(&out); } }; template class Coroutine; // Coroutine constructs a reactor Coroutine function. // rr::Coroutine is similar to rr::Function in that it builds a new // executable function, but Coroutines have the following differences: // (1) Coroutines do not support Return() statements. // (2) Coroutines support Yield() statements to suspend execution of the // coroutine and pass a value up to the caller. Yield can be called // multiple times in a single execution of a coroutine. // (3) The template argument T to Coroutine is a C-style function // signature. // (4) Coroutine::operator() returns a rr::Stream instead of an // rr::Routine. // (5) operator() starts execution of the coroutine immediately. // (6) operator() uses the Coroutine's template function signature to // ensure the argument types match the generated function signature. // // Example usage: // // // Build the coroutine function // Coroutine coroutine; // { // Yield(Int(0)); // Yield(Int(1)); // Int current = 1; // Int next = 1; // While(true) { // Yield(next); // auto tmp = current + next; // current = next; // next = tmp; // } // } // // // Start the execution of the coroutine. // auto s = coroutine(); // // // Grab the first 20 yielded values and print them. // for(int i = 0; i < 20; i++) // { // int val = 0; // s->await(val); // printf("Fibonacci(%d): %d", i, val); // } // template class Coroutine { public: Coroutine(); template using CArgumentType = typename std::tuple_element>::type; template using RArgumentType = CToReactorT>; // Return the argument value with the given index. template Argument> Arg() const { Value *arg = Nucleus::getArgument(index); return Argument>(arg); } // Completes building of the coroutine and generates the coroutine's // executable code. After calling, no more reactor functions may be // called without building a new rr::Function or rr::Coroutine. // While automatically called by operator(), finalize() should be called // as early as possible to release the global Reactor mutex lock. // It must also be called explicitly on the same thread that instantiates // the Coroutine instance if operator() is invoked on separate threads. // This is because presently, Reactor backends use a global mutex scoped // to the generation of routines, and these must be locked/unlocked on the // same thread. inline void finalize(const Config::Edit &cfg = Config::Edit::None); // Starts execution of the coroutine and returns a unique_ptr to a // Stream<> that exposes the await() function for obtaining yielded // values. std::unique_ptr> operator()(Arguments...); protected: std::unique_ptr core; std::shared_ptr routine; std::vector arguments; }; template Coroutine::Coroutine() { core.reset(new Nucleus()); std::vector types = { CToReactorT::getType()... }; for(auto type : types) { if(type != Void::getType()) { arguments.push_back(type); } } Nucleus::createCoroutine(CToReactorT::getType(), arguments); } template void Coroutine::finalize(const Config::Edit &cfg /* = Config::Edit::None */) { if(core != nullptr) { routine = core->acquireCoroutine("coroutine", cfg); core.reset(nullptr); } } template std::unique_ptr> Coroutine::operator()(Arguments... args) { finalize(); // TODO(b/148400732): Go back to just calling the CoroutineEntryBegin function directly. std::function coroutineBegin = [=] { using Sig = Nucleus::CoroutineBegin; auto pfn = (Sig *)routine->getEntry(Nucleus::CoroutineEntryBegin); auto handle = pfn(args...); return handle; }; auto handle = Nucleus::invokeCoroutineBegin(*routine, coroutineBegin); return std::make_unique>(routine, handle); } # ifdef Yield // Defined in WinBase.h # undef Yield # endif // Suspends execution of the coroutine and yields val to the caller. // Execution of the coroutine will resume after val is retrieved. template inline void Yield(const T &val) { Nucleus::yield(ValueOf(val)); } } // namespace rr #endif // rr_ReactorCoroutine_hpp