blob: aaf08ae2111f1c7eeedb896ae9c88f7a6aba115b [file] [log] [blame]
// 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 <memory>
#ifndef rr_ReactorCoroutine_hpp
#define rr_ReactorCoroutine_hpp
namespace rr
{
// Base class for the template Stream<T>
class StreamBase
{
protected:
StreamBase(const std::shared_ptr<Routine> &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> 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<typename T>
class Stream : public StreamBase
{
public:
inline Stream(const std::shared_ptr<Routine> &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<typename FunctionType>
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<T> is a C-style function
// signature.
// (4) Coroutine::operator() returns a rr::Stream<T> 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<int()> 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<typename Return, typename... Arguments>
class Coroutine<Return(Arguments...)>
{
public:
Coroutine();
template<int index>
using CArgumentType = typename std::tuple_element<index, std::tuple<Arguments...>>::type;
template<int index>
using RArgumentType = CToReactor<CArgumentType<index>>;
// Return the argument value with the given index.
template<int index>
Argument<RArgumentType<index>> Arg() const
{
Value *arg = Nucleus::getArgument(index);
return Argument<RArgumentType<index>>(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.
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<Stream<Return>> operator()(Arguments...);
protected:
std::unique_ptr<Nucleus> core;
std::shared_ptr<Routine> routine;
std::vector<Type*> arguments;
};
template<typename Return, typename... Arguments>
Coroutine<Return(Arguments...)>::Coroutine()
{
core.reset(new Nucleus());
std::vector<Type*> types = {CToReactor<Arguments>::getType()...};
for(auto type : types)
{
if(type != Void::getType())
{
arguments.push_back(type);
}
}
Nucleus::createCoroutine(CToReactor<Return>::getType(), arguments);
}
template<typename Return, typename... Arguments>
void Coroutine<Return(Arguments...)>::finalize(const Config::Edit &cfg /* = Config::Edit::None */)
{
if(core != nullptr)
{
routine = core->acquireCoroutine("coroutine", cfg);
core.reset(nullptr);
}
}
template<typename Return, typename... Arguments>
std::unique_ptr<Stream<Return>>
Coroutine<Return(Arguments...)>::operator()(Arguments... args)
{
finalize();
using Sig = Nucleus::CoroutineBegin<Arguments...>;
auto pfn = (Sig*)routine->getEntry(Nucleus::CoroutineEntryBegin);
auto handle = pfn(args...);
return std::unique_ptr<Stream<Return>>(new Stream<Return>(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<typename T>
inline void Yield(const T &val) { Nucleus::yield(ValueOf(val)); }
} // namespace rr
#endif // rr_ReactorCoroutine_hpp