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