blob: dd2fd92fcdc9b68bf5c093acc603986a27841f08 [file] [log] [blame] [edit]
// 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 = CToReactorT<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 soon as possible once the coroutine has been fully built.
// finalize() *must* be called explicitly on the same thread that
// instantiates the Coroutine instance if operator() is to be invoked on
// different threads.
inline void finalize(const char *name = "coroutine", const Config::Edit *cfg = nullptr);
// 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(new Nucleus())
{
std::vector<Type *> types = { CToReactorT<Arguments>::type()... };
for(auto type : types)
{
if(type != Void::type())
{
arguments.push_back(type);
}
}
Nucleus::createCoroutine(CToReactorT<Return>::type(), arguments);
}
template<typename Return, typename... Arguments>
void Coroutine<Return(Arguments...)>::finalize(const char *name /*= "coroutine"*/, const Config::Edit *cfg /* = nullptr */)
{
if(core != nullptr)
{
routine = core->acquireCoroutine(name, cfg);
core.reset(nullptr);
}
}
template<typename Return, typename... Arguments>
std::unique_ptr<Stream<Return>>
Coroutine<Return(Arguments...)>::operator()(Arguments... args)
{
finalize();
// TODO(b/148400732): Go back to just calling the CoroutineEntryBegin function directly.
std::function<Nucleus::CoroutineHandle()> coroutineBegin = [=] {
using Sig = Nucleus::CoroutineBegin<Arguments...>;
auto pfn = (Sig *)routine->getEntry(Nucleus::CoroutineEntryBegin);
auto handle = pfn(args...);
return handle;
};
auto handle = Nucleus::invokeCoroutineBegin(*routine, coroutineBegin);
return std::make_unique<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