coroutine.hh (12800:7736882bdea5) coroutine.hh (14067:2c3667b32607)
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.
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.
164 *
165 * @param f task run by the coroutine
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
166 */
172 */
167 Coroutine(std::function f)
173 Coroutine(std::function<void(CallerType&)> f, bool run_coroutine = true)
168 : Fiber(), task(f), caller(*this)
169 {
174 : Fiber(), task(f), caller(*this)
175 {
170 // Create and Run the Coroutine
171 this->call();
176 // When desired, run the Coroutine after it is created
177 if (run_coroutine)
178 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__
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__