text: /*
   * Copyright (c) 2018 ARM Limited
   * All rights reserved
   *
   * The license below extends only to copyright in the software and shall
   * not be construed as granting a license to any other intellectual
   * property including but not limited to intellectual property relating
   * to a hardware implementation of the functionality of the software
   * licensed hereunder.  You may use the software subject to the license
   * terms below provided that you ensure that this notice is replicated
   * unmodified and in its entirety in all distributions of the software,
   * modified or unmodified, in source code or in binary form.
   *
   * Redistribution and use in source and binary forms, with or without
   * modification, are permitted provided that the following conditions are
   * met: redistributions of source code must retain the above copyright
   * notice, this list of conditions and the following disclaimer;
   * redistributions in binary form must reproduce the above copyright
   * notice, this list of conditions and the following disclaimer in the
   * documentation and/or other materials provided with the distribution;
   * neither the name of the copyright holders nor the names of its
   * contributors may be used to endorse or promote products derived from
   * this software without specific prior written permission.
   *
   * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
   * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
   * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
   * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
   * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
   * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
   * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
   * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
   * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
   * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
   * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
   *
   * Authors: Giacomo Travaglini
   */
  
  #ifndef __BASE_COROUTINE_HH__
  #define __BASE_COROUTINE_HH__
  
  #include <functional>
  #include <stack>
  
  #include "base/fiber.hh"
  
  namespace m5
  {
  
  /**
   * This template defines a Coroutine wrapper type with a Boost-like
   * interface. It is built on top of the gem5 fiber class.
   * The two template parameters (Arg and Ret) are the coroutine
   * argument and coroutine return types which are passed between
   * the coroutine and the caller via operator() and get() method.
   * This implementation doesn't support passing multiple values,
   * so a tuple must be used in that scenario.
   *
   * Most methods are templatized since it is relevant to distinguish
   * the cases where one or both of the template parameters are void
   */
  template <typename Arg, typename Ret>
  class Coroutine : public Fiber
  {
  
      // This empty struct type is meant to replace coroutine channels
      // in case the channel should be void (Coroutine template parameters
      // are void. (See following ArgChannel, RetChannel typedef)
      struct Empty {};
      using ArgChannel = typename std::conditional<
          std::is_same<Arg, void>::value, Empty, std::stack<Arg>>::type;
  
      using RetChannel = typename std::conditional<
          std::is_same<Ret, void>::value, Empty, std::stack<Ret>>::type;
  
    public:
      /**
       * CallerType:
       * A reference to an object of this class will be passed
       * to the coroutine task. This is the way it is possible
       * for the coroutine to interface (e.g. switch back)
       * to the coroutine caller.
       */
      class CallerType
      {
          friend class Coroutine;
        protected:
          CallerType(Coroutine& _coro) : coro(_coro), callerFiber(nullptr) {}
  
        public:
          /**
           * operator() is the way we can jump outside the coroutine
           * and return a value to the caller.
           *
           * This method is generated only if the coroutine returns
           * a value (Ret != void)
           */
          template <typename T = Ret>
          CallerType&
          operator()(typename std::enable_if<
                     !std::is_same<T, void>::value, T>::type param)
          {
              retChannel.push(param);
              callerFiber->run();
              return *this;
          }
  
          /**
           * operator() is the way we can jump outside the coroutine
           *
           * This method is generated only if the coroutine doesn't
           * return a value (Ret = void)
           */
          template <typename T = Ret>
          typename std::enable_if<std::is_same<T, void>::value,
                                  CallerType>::type&
          operator()()
          {
              callerFiber->run();
              return *this;
          }
  
          /**
           * get() is the way we can extrapolate arguments from the
           * coroutine caller.
           * The coroutine blocks, waiting for the value, unless it is already
           * available; otherwise caller execution is resumed,
           * and coroutine won't execute until a value is pushed
           * from the caller.
           *
           * @return arg coroutine argument
           */
          template <typename T = Arg>
          typename std::enable_if<!std::is_same<T, void>::value, T>::type
          get()
          {
              auto& args_channel = coro.argsChannel;
              while (args_channel.empty()) {
                  callerFiber->run();
              }
  
              auto ret = args_channel.top();
              args_channel.pop();
              return ret;
          }
  
        private:
          Coroutine& coro;
          Fiber* callerFiber;
          RetChannel retChannel;
      };
  
      Coroutine() = delete;
      Coroutine(const Coroutine& rhs) = delete;
      Coroutine& operator=(const Coroutine& rhs) = delete;
  
      /**
       * Coroutine constructor.
       * The only way to construct a coroutine is to pass it the routine
       * it needs to run. The first argument of the function should be a
       * reference to the Coroutine<Arg,Ret>::caller_type which the
       * routine will use as a way for yielding to the caller.
       *
       * @param f task run by the coroutine
       */
      Coroutine(std::function<void(CallerType&)> f)
        : Fiber(), task(f), caller(*this)
      {
          // Create and Run the Coroutine
          this->call();
      }
  
      virtual ~Coroutine() {}
  
    public:
      /** Coroutine interface */
  
      /**
       * operator() is the way we can jump inside the coroutine
       * and passing arguments.
       *
       * This method is generated only if the coroutine takes
       * arguments (Arg != void)
       */
      template <typename T = Arg>
      Coroutine&
      operator()(typename std::enable_if<
                 !std::is_same<T, void>::value, T>::type param)
      {
          argsChannel.push(param);
          this->call();
          return *this;
      }
  
      /**
       * operator() is the way we can jump inside the coroutine.
       *
       * This method is generated only if the coroutine takes
       * no arguments. (Arg = void)
       */
      template <typename T = Arg>
      typename std::enable_if<std::is_same<T, void>::value, Coroutine>::type&
      operator()()
      {
          this->call();
          return *this;
      }
  
      /**
       * get() is the way we can extrapolate return values
       * (yielded) from the coroutine.
       * The caller blocks, waiting for the value, unless it is already
       * available; otherwise coroutine execution is resumed,
       * and caller won't execute until a value is yielded back
       * from the coroutine.
       *
       * @return ret yielded value
       */
      template <typename T = Ret>
      typename std::enable_if<!std::is_same<T, void>::value, T>::type
      get()
      {
          auto& ret_channel = caller.retChannel;
          while (ret_channel.empty()) {
              this->call();
          }
  
          auto ret = ret_channel.top();
          ret_channel.pop();
          return ret;
      }
  
      /** Check if coroutine is still running */
      operator bool() const { return !this->finished(); }
  
    private:
      /**
       * Overriding base (Fiber) main.
       * This method will be automatically called by the Fiber
       * running engine and it is a simple wrapper for the task
       * that the coroutine is supposed to run.
       */
      void main() override { this->task(caller); }
  
      void
      call()
      {
          caller.callerFiber = currentFiber();
          run();
      }
  
    private:
      /** Arguments for the coroutine */
      ArgChannel argsChannel;
  
      /** Coroutine task */
      std::function<void(CallerType&)> task;
  
      /** Coroutine caller */
      CallerType caller;
  };
  
  } //namespace m5
  
  #endif // __BASE_COROUTINE_HH__
  