1/*
2 * Copyright (c) 2018 ARM Limited
3 * All rights reserved
4 *
5 * The license below extends only to copyright in the software and shall
6 * not be construed as granting a license to any other intellectual
7 * property including but not limited to intellectual property relating
8 * to a hardware implementation of the functionality of the software
9 * licensed hereunder.  You may use the software subject to the license
10 * terms below provided that you ensure that this notice is replicated
11 * unmodified and in its entirety in all distributions of the software,
12 * modified or unmodified, in source code or in binary form.
13 *
14 * Redistribution and use in source and binary forms, with or without
15 * modification, are permitted provided that the following conditions are
16 * met: redistributions of source code must retain the above copyright
17 * notice, this list of conditions and the following disclaimer;
18 * redistributions in binary form must reproduce the above copyright
19 * notice, this list of conditions and the following disclaimer in the
20 * documentation and/or other materials provided with the distribution;
21 * neither the name of the copyright holders nor the names of its
22 * contributors may be used to endorse or promote products derived from
23 * this software without specific prior written permission.
24 *
25 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
26 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
27 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
28 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
29 * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
30 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
31 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
32 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
33 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
34 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
35 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
36 *
37 * Authors: Giacomo Travaglini
38 */
39
40#ifndef __BASE_COROUTINE_HH__
41#define __BASE_COROUTINE_HH__
42
43#include <functional>
44#include <stack>
45
46#include "base/fiber.hh"
47
48namespace m5
49{
50
51/**
52 * This template defines a Coroutine wrapper type with a Boost-like
53 * interface. It is built on top of the gem5 fiber class.
54 * The two template parameters (Arg and Ret) are the coroutine
55 * argument and coroutine return types which are passed between
56 * the coroutine and the caller via operator() and get() method.
57 * This implementation doesn't support passing multiple values,
58 * so a tuple must be used in that scenario.
59 *
60 * Most methods are templatized since it is relevant to distinguish
61 * the cases where one or both of the template parameters are void
62 */
63template <typename Arg, typename Ret>
64class Coroutine : public Fiber
65{
66
67    // This empty struct type is meant to replace coroutine channels
68    // in case the channel should be void (Coroutine template parameters
69    // are void. (See following ArgChannel, RetChannel typedef)
70    struct Empty {};
71    using ArgChannel = typename std::conditional<
72        std::is_same<Arg, void>::value, Empty, std::stack<Arg>>::type;
73
74    using RetChannel = typename std::conditional<
75        std::is_same<Ret, void>::value, Empty, std::stack<Ret>>::type;
76
77  public:
78    /**
79     * CallerType:
80     * A reference to an object of this class will be passed
81     * to the coroutine task. This is the way it is possible
82     * for the coroutine to interface (e.g. switch back)
83     * to the coroutine caller.
84     */
85    class CallerType
86    {
87        friend class Coroutine;
88      protected:
89        CallerType(Coroutine& _coro) : coro(_coro), callerFiber(nullptr) {}
90
91      public:
92        /**
93         * operator() is the way we can jump outside the coroutine
94         * and return a value to the caller.
95         *
96         * This method is generated only if the coroutine returns
97         * a value (Ret != void)
98         */
99        template <typename T = Ret>
100        CallerType&
101        operator()(typename std::enable_if<
102                   !std::is_same<T, void>::value, T>::type param)
103        {
104            retChannel.push(param);
105            callerFiber->run();
106            return *this;
107        }
108
109        /**
110         * operator() is the way we can jump outside the coroutine
111         *
112         * This method is generated only if the coroutine doesn't
113         * return a value (Ret = void)
114         */
115        template <typename T = Ret>
116        typename std::enable_if<std::is_same<T, void>::value,
117                                CallerType>::type&
118        operator()()
119        {
120            callerFiber->run();
121            return *this;
122        }
123
124        /**
125         * get() is the way we can extrapolate arguments from the
126         * coroutine caller.
127         * The coroutine blocks, waiting for the value, unless it is already
128         * available; otherwise caller execution is resumed,
129         * and coroutine won't execute until a value is pushed
130         * from the caller.
131         *
132         * @return arg coroutine argument
133         */
134        template <typename T = Arg>
135        typename std::enable_if<!std::is_same<T, void>::value, T>::type
136        get()
137        {
138            auto& args_channel = coro.argsChannel;
139            while (args_channel.empty()) {
140                callerFiber->run();
141            }
142
143            auto ret = args_channel.top();
144            args_channel.pop();
145            return ret;
146        }
147
148      private:
149        Coroutine& coro;
150        Fiber* callerFiber;
151        RetChannel retChannel;
152    };
153
154    Coroutine() = delete;
155    Coroutine(const Coroutine& rhs) = delete;
156    Coroutine& operator=(const Coroutine& rhs) = delete;
157
158    /**
159     * Coroutine constructor.
160     * The only way to construct a coroutine is to pass it the routine
161     * it needs to run. The first argument of the function should be a
162     * reference to the Coroutine<Arg,Ret>::caller_type which the
163     * routine will use as a way for yielding to the caller.
164     * The optional second boolean argument controls if the Coroutine
165     * should be run on creation, which mimics Boost's Coroutine
166     * semantics by default. This can be disabled as an optimization to
167     * avoid unnecessary context switches on Coroutine creation.
168     *
169     * @param f task run by the coroutine
170     * @param run_coroutine set to false to disable running the coroutine
171     *                      immediately after it is created
172     */
173    Coroutine(std::function<void(CallerType&)> f, bool run_coroutine = true)
174      : Fiber(), task(f), caller(*this)
175    {
176        // When desired, run the Coroutine after it is created
177        if (run_coroutine)
178            this->call();
179    }
180
181    virtual ~Coroutine() {}
182
183  public:
184    /** Coroutine interface */
185
186    /**
187     * operator() is the way we can jump inside the coroutine
188     * and passing arguments.
189     *
190     * This method is generated only if the coroutine takes
191     * arguments (Arg != void)
192     */
193    template <typename T = Arg>
194    Coroutine&
195    operator()(typename std::enable_if<
196               !std::is_same<T, void>::value, T>::type param)
197    {
198        argsChannel.push(param);
199        this->call();
200        return *this;
201    }
202
203    /**
204     * operator() is the way we can jump inside the coroutine.
205     *
206     * This method is generated only if the coroutine takes
207     * no arguments. (Arg = void)
208     */
209    template <typename T = Arg>
210    typename std::enable_if<std::is_same<T, void>::value, Coroutine>::type&
211    operator()()
212    {
213        this->call();
214        return *this;
215    }
216
217    /**
218     * get() is the way we can extrapolate return values
219     * (yielded) from the coroutine.
220     * The caller blocks, waiting for the value, unless it is already
221     * available; otherwise coroutine execution is resumed,
222     * and caller won't execute until a value is yielded back
223     * from the coroutine.
224     *
225     * @return ret yielded value
226     */
227    template <typename T = Ret>
228    typename std::enable_if<!std::is_same<T, void>::value, T>::type
229    get()
230    {
231        auto& ret_channel = caller.retChannel;
232        while (ret_channel.empty()) {
233            this->call();
234        }
235
236        auto ret = ret_channel.top();
237        ret_channel.pop();
238        return ret;
239    }
240
241    /** Check if coroutine is still running */
242    operator bool() const { return !this->finished(); }
243
244  private:
245    /**
246     * Overriding base (Fiber) main.
247     * This method will be automatically called by the Fiber
248     * running engine and it is a simple wrapper for the task
249     * that the coroutine is supposed to run.
250     */
251    void main() override { this->task(caller); }
252
253    void
254    call()
255    {
256        caller.callerFiber = currentFiber();
257        run();
258    }
259
260  private:
261    /** Arguments for the coroutine */
262    ArgChannel argsChannel;
263
264    /** Coroutine task */
265    std::function<void(CallerType&)> task;
266
267    /** Coroutine caller */
268    CallerType caller;
269};
270
271} //namespace m5
272
273#endif // __BASE_COROUTINE_HH__
274