coroutine.hh revision 12800:7736882bdea5
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     *
165     * @param f task run by the coroutine
166     */
167    Coroutine(std::function<void(CallerType&)> f)
168      : Fiber(), task(f), caller(*this)
169    {
170        // Create and Run the Coroutine
171        this->call();
172    }
173
174    virtual ~Coroutine() {}
175
176  public:
177    /** Coroutine interface */
178
179    /**
180     * operator() is the way we can jump inside the coroutine
181     * and passing arguments.
182     *
183     * This method is generated only if the coroutine takes
184     * arguments (Arg != void)
185     */
186    template <typename T = Arg>
187    Coroutine&
188    operator()(typename std::enable_if<
189               !std::is_same<T, void>::value, T>::type param)
190    {
191        argsChannel.push(param);
192        this->call();
193        return *this;
194    }
195
196    /**
197     * operator() is the way we can jump inside the coroutine.
198     *
199     * This method is generated only if the coroutine takes
200     * no arguments. (Arg = void)
201     */
202    template <typename T = Arg>
203    typename std::enable_if<std::is_same<T, void>::value, Coroutine>::type&
204    operator()()
205    {
206        this->call();
207        return *this;
208    }
209
210    /**
211     * get() is the way we can extrapolate return values
212     * (yielded) from the coroutine.
213     * The caller blocks, waiting for the value, unless it is already
214     * available; otherwise coroutine execution is resumed,
215     * and caller won't execute until a value is yielded back
216     * from the coroutine.
217     *
218     * @return ret yielded value
219     */
220    template <typename T = Ret>
221    typename std::enable_if<!std::is_same<T, void>::value, T>::type
222    get()
223    {
224        auto& ret_channel = caller.retChannel;
225        while (ret_channel.empty()) {
226            this->call();
227        }
228
229        auto ret = ret_channel.top();
230        ret_channel.pop();
231        return ret;
232    }
233
234    /** Check if coroutine is still running */
235    operator bool() const { return !this->finished(); }
236
237  private:
238    /**
239     * Overriding base (Fiber) main.
240     * This method will be automatically called by the Fiber
241     * running engine and it is a simple wrapper for the task
242     * that the coroutine is supposed to run.
243     */
244    void main() override { this->task(caller); }
245
246    void
247    call()
248    {
249        caller.callerFiber = currentFiber();
250        run();
251    }
252
253  private:
254    /** Arguments for the coroutine */
255    ArgChannel argsChannel;
256
257    /** Coroutine task */
258    std::function<void(CallerType&)> task;
259
260    /** Coroutine caller */
261    CallerType caller;
262};
263
264} //namespace m5
265
266#endif // __BASE_COROUTINE_HH__
267