/*
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements.  See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership.  The ASF licenses this file
 * to you under the Apache License, Version 2.0 (the
 * "License"); you may not use this file except in compliance
 * with the License.  You may obtain a copy of the License at
 *
 *   http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing,
 * software distributed under the License is distributed on an
 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
 * KIND, either express or implied.  See the License for the
 * specific language governing permissions and limitations
 * under the License.
 */

#include <HexagonWrapper.h>
#include <tvm/ffi/function.h>
#include <tvm/ffi/reflection/registry.h>
#include <tvm/runtime/logging.h>
// POSIX includes
#include <dirent.h>
#include <unistd.h>

#include <algorithm>
#include <cstdlib>
#include <deque>
#include <iterator>
#include <map>
#include <memory>
#include <optional>
#include <sstream>
#include <string>
#include <utility>

#include "../../../../../runtime/rpc/rpc_channel.h"
#include "../../../../../runtime/rpc/rpc_endpoint.h"
#include "../../../../../runtime/rpc/rpc_session.h"
#include "hexagon_sim_proto.h"

#define CHECKED_CALL(func, ...)                                               \
  do {                                                                        \
    HEXAPI_Status s = sim_->func(__VA_ARGS__);                                \
    TVM_FFI_ICHECK_EQ(s, HEX_STAT_SUCCESS)                                    \
        << self_name_ << ": " #func " failed with code " << Status_{s}.str(); \
  } while (false)

namespace tvm {
namespace runtime {
namespace hexagon {

using string_list = std::deque<std::string>;

namespace detail {

// Replacement for llvm::StringSwitch.
template <typename T>
class StringSwitch {
 public:
  explicit StringSwitch(const std::string& key) : key(key) {}
  operator T() const {
    auto f = map.find(key);
    if (f != map.end()) {
      return f->second;
    }
    TVM_FFI_ICHECK(static_cast<bool>(def_val)) << "default value not set";
    return *def_val;
  }
  StringSwitch& Case(const std::string& key, T val) {
    map.insert(std::make_pair(key, val));
    return *this;
  }
  StringSwitch& Default(T val) {
    TVM_FFI_ICHECK(!static_cast<bool>(def_val)) << "default value already set";
    def_val = val;
    return *this;
  }

 private:
  const std::string key;
  std::map<std::string, T> map;
  std::optional<T> def_val;
};

using MaybeString = std::optional<std::string>;

MaybeString front(const string_list& deq) {
  return !deq.empty() ? MaybeString(deq.front()) : MaybeString();
}

MaybeString pop_front(string_list& deq) {  // NOLINT(*)
  if (deq.empty()) return MaybeString();
  std::string f = deq.front();
  deq.pop_front();
  return MaybeString(f);
}

// Functions used when parsing the argument string.

std::optional<int64_t> to_int(const MaybeString& str) {
  if (str.has_value()) {
    try {
      size_t pos;
      int64_t val = std::stoll(*str, &pos, 0);
      return pos == str->size() ? std::optional<int64_t>(val) : std::nullopt;
    } catch (std::invalid_argument&) {
    }
  }
  return std::nullopt;
}

std::optional<uint64_t> to_uint(const MaybeString& str) {
  if (str.has_value()) {
    try {
      size_t pos;
      uint64_t val = std::stoull(*str, &pos, 0);
      return pos == str->size() ? std::optional<uint64_t>(val) : std::nullopt;
    } catch (std::invalid_argument&) {
    }
  }
  return std::nullopt;
}

std::optional<float> to_float(const MaybeString& str) {
  if (str.has_value()) {
    try {
      size_t pos;
      float val = std::stof(*str, &pos);
      return pos == str->size() ? std::optional<float>(val) : std::nullopt;
    } catch (std::invalid_argument&) {
    }
  }
  return std::nullopt;
}

std::optional<bool> to_bool(const MaybeString& str) {
  if (auto num = to_int(str)) {
    if (*num == 0) return false;
    if (*num == 1) return true;
    return std::nullopt;
  }
  if (str) {
    if (*str == "true" || *str == "TRUE") return true;
    if (*str == "false" || *str == "FALSE") return false;
  }
  return std::nullopt;
}

template <typename T>
using MaybeRange = std::optional<std::pair<T, T>>;

template <typename T, std::optional<T> Parse(const MaybeString&)>
MaybeRange<T> to_range(const MaybeString& str) {
  if (str && !str->empty()) {
    auto n = str->find('-', 1);
    if (n != std::string::npos) {
      auto begin = Parse(str->substr(0, n));
      auto end = Parse(str->substr(n + 1, str->size() - n - 1));
      if (begin && end) {
        return std::make_pair(*begin, *end);
      }
    }
  }
  return std::nullopt;
}

}  // namespace detail

class SimulatorRPCChannel final : public RPCChannel {
 public:
  SimulatorRPCChannel(int stack_size, std::string args);
  ~SimulatorRPCChannel() final;
  size_t Send(const void* data, size_t size) final;
  size_t Recv(void* data, size_t size) final;

 private:
  struct Status_ {
    HEXAPI_Status s;
    std::string str() const;
  };
  struct Core_ {
    HEXAPI_CoreState s;
    std::string str() const;
  };
  struct Cpu_ {
    HEXAPI_Cpu c;
    std::string str() const;
  };
  struct SDKInfo_ {
    SDKInfo_(const std::string& sdk_root, const std::string& cpu);
    std::string root;
    std::string qurt_root;  // sdk_root/rtos/qurt/computevNN.
    std::string runelf;     // Path to runelf.pbn.
    std::string runmain;    // Path to run_main_on_hexagon.
  };

  struct Message_ {
    Message msg;
    std::string str() const;
  };

  Message SendMsg(Message msg);
  Message SendMsg(uint32_t code, uint32_t len, uint32_t va);
  void ReadFromProcess(void* host_dst, HEX_VA_t src, size_t len);
  void WriteToProcess(HEX_VA_t dst, const void* host_src, size_t len);

  static HEX_8u_t PassVirtAddrCallback(void* handle, int threadno, HEX_8u_t RssV, HEX_8u_t RttV,
                                       HEX_8u_t RxxV, HEX_1u_t imm);

  std::optional<HEXAPI_Cpu> GetCPU(const detail::MaybeString& cpu_str);

  // File name templates for mkstemps.
#define SUFFIX ".cfg"
  static constexpr int template_length_ = sizeof("temp-xxxx-XXXXXX" SUFFIX);  // == strlen() + 1
  char osam_file_[template_length_] = "temp-osam-XXXXXX" SUFFIX;
  char cosim_file_[template_length_] = "temp-q6ss-XXXXXX" SUFFIX;
  const int suffix_len_ = strlen(SUFFIX);
#undef SUFFIX

  static const constexpr char* self_name_ = "SimulatorRPCChannel";
  static const constexpr char* default_cpu_ = "v68";
  std::string cpu_;

  HEX_VA_t dispatch_v_, message_buffer_v_;
  std::unique_ptr<HexagonWrapper> sim_;

  // Sim configuration routines.
  bool Configure(string_list& opts);  // NOLINT(*)

  bool HandleAHBBusPenalty(string_list& rest);      // NOLINT(*)
  bool HandleAHBBusRatio(string_list& rest);        // NOLINT(*)
  bool HandleAHBHighAddr(string_list& rest);        // NOLINT(*)
  bool HandleAHBLowAddr(string_list& rest);         // NOLINT(*)
  bool HandleAXI2BusPenalty(string_list& rest);     // NOLINT(*)
  bool HandleAXI2BusRatio(string_list& rest);       // NOLINT(*)
  bool HandleAXI2HighAddr(string_list& rest);       // NOLINT(*)
  bool HandleAXI2LowAddr(string_list& rest);        // NOLINT(*)
  bool HandleBuildTag(string_list& rest);           // NOLINT(*)
  bool HandleBusPenalty(string_list& rest);         // NOLINT(*)
  bool HandleBusRatio(string_list& rest);           // NOLINT(*)
  bool HandleBusTrace(string_list& rest);           // NOLINT(*)
  bool HandleBypassIdle(string_list& rest);         // NOLINT(*)
  bool HandleConnectionTimeout(string_list& rest);  // NOLINT(*)
  bool HandleCoprocTrace(string_list& rest);        // NOLINT(*)
  bool HandleCoreDump(string_list& rest);           // NOLINT(*)
  bool HandleCosimFile(string_list& rest);          // NOLINT(*)
  bool HandleDCacheTrace(string_list& rest);        // NOLINT(*)
  bool HandleDSPClock(string_list& rest);           // NOLINT(*)
  bool HandleETMCFGBase(string_list& rest);         // NOLINT(*)
  bool HandleGDBServ(string_list& rest);            // NOLINT(*)
  bool HandleHVXLength(string_list& rest);          // NOLINT(*)
  bool HandleICacheTrace(string_list& rest);        // NOLINT(*)
  bool HandleL2CacheTrace(string_list& rest);       // NOLINT(*)
  bool HandleL2CFGBase(string_list& rest);          // NOLINT(*)
  bool HandleL2TCMBase(string_list& rest);          // NOLINT(*)
  bool HandleMemFillRand(string_list& rest);        // NOLINT(*)
  bool HandleMemFill(string_list& rest);            // NOLINT(*)
  bool HandleMemTrace(string_list& rest);           // NOLINT(*)
  bool HandleNullPtr(string_list& rest);            // NOLINT(*)
  bool HandlePacketAnalyze(string_list& rest);      // NOLINT(*)
  bool HandlePCFilter(string_list& rest);           // NOLINT(*)
  bool HandlePCTraceMin(string_list& rest);         // NOLINT(*)
  bool HandlePCTraceNano(string_list& rest);        // NOLINT(*)
  bool HandlePCTrace(string_list& rest);            // NOLINT(*)
  bool HandlePMUStatsFile(string_list& rest);       // NOLINT(*)
  bool HandleProfile(string_list& rest);            // NOLINT(*)
  bool HandleProfileTimeZero(string_list& rest);    // NOLINT(*)
  bool HandleQuiet(string_list& rest);              // NOLINT(*)
  bool HandleReconnect(string_list& rest);          // NOLINT(*)
  bool HandleRTOS(string_list& rest);               // NOLINT(*)
  bool HandleSimErr(string_list& rest);             // NOLINT(*)
  bool HandleSimIn(string_list& rest);              // NOLINT(*)
  bool HandleSimOut(string_list& rest);             // NOLINT(*)
  bool HandleStackStart(string_list& rest);         // NOLINT(*)
  bool HandleStallTrace(string_list& rest);         // NOLINT(*)
  bool HandleStatsFile(string_list& rest);          // NOLINT(*)
  bool HandleSubsystemBase(string_list& rest);      // NOLINT(*)
  bool HandleSymFile(string_list& rest);            // NOLINT(*)
  bool HandleTCM(string_list& rest);                // NOLINT(*)
  bool HandleTCMHighAddr(string_list& rest);        // NOLINT(*)
  bool HandleTCMLowAddr(string_list& rest);         // NOLINT(*)
  bool HandleTimeFilterNS(string_list& rest);       // NOLINT(*)
  bool HandleTiming(string_list& rest);             // NOLINT(*)
  bool HandleUArchTrace(string_list& rest);         // NOLINT(*)
  bool HandleUseFS(string_list& rest);              // NOLINT(*)
  bool HandleV2PTranslation(string_list& rest);     // NOLINT(*)
  bool HandleVerbose(string_list& rest);            // NOLINT(*)

  using MaybeUInt64 = std::optional<uint64_t>;
  using MaybeUIntRange = std::pair<MaybeUInt64, MaybeUInt64>;

  bool should_parse_next(const string_list& rest);
  std::optional<HEXAPI_Interval> to_interval(const detail::MaybeString& str);
  std::optional<HEXAPI_TimingMode> to_timingmode(const detail::MaybeString& str);
  std::optional<HEXAPI_VerboseMode> to_verbosemode(const detail::MaybeString& str);
  std::optional<HEXAPI_Nullptr> to_nullptr(const detail::MaybeString& str);

  MaybeUIntRange ahb_, axi2_;
  std::optional<uint32_t> debug_port_;

  using OptionHandler = bool (SimulatorRPCChannel::*)(string_list&);
  static std::map<std::string, OptionHandler> opt_map_;
};

const constexpr char* SimulatorRPCChannel::self_name_;
const constexpr char* SimulatorRPCChannel::default_cpu_;

decltype(SimulatorRPCChannel::opt_map_) SimulatorRPCChannel::opt_map_ = {
    {"--ahbbuspenalty", &SimulatorRPCChannel::HandleAHBBusPenalty},
    {"--ahbbusratio", &SimulatorRPCChannel::HandleAHBBusRatio},
    {"--ahb:highaddr", &SimulatorRPCChannel::HandleAHBHighAddr},
    {"--ahb:lowaddr", &SimulatorRPCChannel::HandleAHBLowAddr},
    {"--axi2buspenalty", &SimulatorRPCChannel::HandleAXI2BusPenalty},
    {"--axi2busratio", &SimulatorRPCChannel::HandleAXI2BusRatio},
    {"--axi2:highaddr", &SimulatorRPCChannel::HandleAXI2HighAddr},
    {"--axi2:lowaddr", &SimulatorRPCChannel::HandleAXI2LowAddr},
    {"-b", &SimulatorRPCChannel::HandleBusTrace},
    {"--build_tag", &SimulatorRPCChannel::HandleBuildTag},
    {"--buspenalty", &SimulatorRPCChannel::HandleBusPenalty},
    {"--busratio", &SimulatorRPCChannel::HandleBusRatio},
    {"--bustrace", &SimulatorRPCChannel::HandleBusTrace},
    {"--bypass_idle", &SimulatorRPCChannel::HandleBypassIdle},
    {"--connection_timeout", &SimulatorRPCChannel::HandleConnectionTimeout},
    {"--coproctrace", &SimulatorRPCChannel::HandleCoprocTrace},
    {"--coredump", &SimulatorRPCChannel::HandleCoreDump},
    {"--cosim_file", &SimulatorRPCChannel::HandleCosimFile},
    {"--dcachetrace", &SimulatorRPCChannel::HandleDCacheTrace},
    {"--dsp_clock", &SimulatorRPCChannel::HandleDSPClock},
    {"-E", &SimulatorRPCChannel::HandleSimErr},
    {"--etm_base", &SimulatorRPCChannel::HandleETMCFGBase},
    {"--etmcfg_base", &SimulatorRPCChannel::HandleETMCFGBase},
    {"--gdbserv", &SimulatorRPCChannel::HandleGDBServ},
    {"-G", &SimulatorRPCChannel::HandleGDBServ},
    {"--hvx_length", &SimulatorRPCChannel::HandleHVXLength},
    {"--icachetrace", &SimulatorRPCChannel::HandleICacheTrace},
    {"-I", &SimulatorRPCChannel::HandleSimIn},
    {"--l2cachetrace", &SimulatorRPCChannel::HandleL2CacheTrace},
    {"--l2cfg_base", &SimulatorRPCChannel::HandleL2CFGBase},
    {"--l2tcm_base", &SimulatorRPCChannel::HandleL2TCMBase},
    {"--memfill", &SimulatorRPCChannel::HandleMemFill},
    {"--memfill_rand", &SimulatorRPCChannel::HandleMemFillRand},
    {"--memtrace", &SimulatorRPCChannel::HandleMemTrace},
    {"-m", &SimulatorRPCChannel::HandleMemTrace},
    {"--nullptr", &SimulatorRPCChannel::HandleNullPtr},
    {"-O", &SimulatorRPCChannel::HandleSimOut},
    {"--packet_analyze", &SimulatorRPCChannel::HandlePacketAnalyze},
    {"--pcfilter", &SimulatorRPCChannel::HandlePCFilter},
    {"--pctrace", &SimulatorRPCChannel::HandlePCTrace},
    {"--pctrace_min", &SimulatorRPCChannel::HandlePCTraceMin},
    {"--pctrace_nano", &SimulatorRPCChannel::HandlePCTraceNano},
    {"-p", &SimulatorRPCChannel::HandleProfile},
    {"--pmu_statsfile", &SimulatorRPCChannel::HandlePMUStatsFile},
    {"--profile", &SimulatorRPCChannel::HandleProfile},
    {"--profile_timezero", &SimulatorRPCChannel::HandleProfileTimeZero},
    {"-q", &SimulatorRPCChannel::HandleQuiet},
    {"--quiet", &SimulatorRPCChannel::HandleQuiet},
    {"--reconnect", &SimulatorRPCChannel::HandleReconnect},
    {"--rtos", &SimulatorRPCChannel::HandleRTOS},
    {"-S", &SimulatorRPCChannel::HandleStatsFile},
    {"--sim_err", &SimulatorRPCChannel::HandleSimErr},
    {"--sim_in", &SimulatorRPCChannel::HandleSimIn},
    {"--sim_out", &SimulatorRPCChannel::HandleSimOut},
    {"--stackstart", &SimulatorRPCChannel::HandleStackStart},
    {"--stalltrace", &SimulatorRPCChannel::HandleStallTrace},
    {"--statsfile", &SimulatorRPCChannel::HandleStatsFile},
    {"--subsystem_base", &SimulatorRPCChannel::HandleSubsystemBase},
    {"--symfile", &SimulatorRPCChannel::HandleSymFile},
    {"--tcm", &SimulatorRPCChannel::HandleTCM},
    {"--tcm:highaddr", &SimulatorRPCChannel::HandleTCMHighAddr},
    {"--tcm:lowaddr", &SimulatorRPCChannel::HandleTCMLowAddr},
    {"-t", &SimulatorRPCChannel::HandlePCTrace},
    {"--timefilter_ns", &SimulatorRPCChannel::HandleTimeFilterNS},
    {"--timing", &SimulatorRPCChannel::HandleTiming},
    {"--uarchtrace", &SimulatorRPCChannel::HandleUArchTrace},
    {"-u", &SimulatorRPCChannel::HandlePCTraceMin},
    {"--usefs", &SimulatorRPCChannel::HandleUseFS},
    {"--v2p_translation", &SimulatorRPCChannel::HandleV2PTranslation},
    {"--verbose", &SimulatorRPCChannel::HandleVerbose},
};

std::string SimulatorRPCChannel::Status_::str() const {
  switch (s) {
    case HEX_STAT_ERROR:
      return "HEX_STAT_ERROR";
    case HEX_STAT_SUCCESS:
      return "HEX_STAT_SUCCESS";
    case HEX_STAT_CANNOT_CONFIG:
      return "HEX_STAT_CANNOT_CONFIG";
    case HEX_STAT_INVALID_ARGS:
      return "HEX_STAT_INVALID_ARGS";
    case HEX_STAT_RANGE_ERROR:
      return "HEX_STAT_RANGE_ERROR";
    case HEX_STAT_FILE_ACCESS_ERROR:
      return "HEX_STAT_FILE_ACCESS_ERROR";
    case HEX_STAT_DEVICE_NOT_FOUND:
      return "HEX_STAT_DEVICE_NOT_FOUND";
    case HEX_STAT_MEM_ACCESS_ERROR:
      return "HEX_STAT_MEM_ACCESS_ERROR";
    case HEX_STAT_CANNOT_TRANSLATE:
      return "HEX_STAT_CANNOT_TRANSLATE";
    case HEX_STAT_NO_ACTIVE_THREADS:
      return "HEX_STAT_NO_ACTIVE_THREADS";
    case HEX_STAT_LOAD_ELF_ERROR:
      return "HEX_STAT_LOAD_ELF_ERROR";
    case HEX_STAT_CORE_RESET:
      return "HEX_STAT_CORE_RESET";
    default:
      break;
  }
  return std::to_string(static_cast<int>(s));
}

std::string SimulatorRPCChannel::Core_::str() const {
  switch (s) {
    case HEX_CORE_SUCCESS:
      return "HEX_CORE_SUCCESS";
    case HEX_CORE_FINISHED:
      return "HEX_CORE_FINISHED";
    case HEX_CORE_RESET:
      return "HEX_CORE_RESET";
    case HEX_CORE_BREAKPOINT:
      return "HEX_CORE_BREAKPOINT";
    case HEX_CORE_ASYNCHRONOUS_BREAK:
      return "HEX_CORE_ASYNCHRONOUS_BREAK";
    case HEX_CORE_ERROR:
      return "HEX_CORE_ERROR";
    default:
      break;
  }
  return std::to_string(static_cast<int>(s));
}

std::string SimulatorRPCChannel::Cpu_::str() const {
  switch (c) {
#ifdef HEX_CPU_ID_V65A_512
    case HEX_CPU_V65:
      return "v65";
#endif
#ifdef HEX_CPU_ID_V66A_512
    case HEX_CPU_V66:
      return "v66";
#endif
#ifdef HEX_CPU_ID_V68N_1024
    case HEX_CPU_V68:
      return "v68";
#endif
#ifdef HEX_CPU_ID_V69NA
    case HEX_CPU_V69:
      return "v69";
#endif
#ifdef HEX_CPU_ID_V71H_MDM_SCA
    case HEX_CPU_V71:
      return "v71";
#endif
#ifdef HEX_CPU_ID_V73NA_1
    case HEX_CPU_V73:
      return "v73";
#endif
#ifdef HEX_CPU_ID_V75NA_1
    case HEX_CPU_V75:
      return "v75";
#endif
    default:
      break;
  }
  return default_cpu_;
}

// TVM_FFI_THROW(InternalError) always throws an exception or terminates the
// process, but the compiler doesn't know that.
#if (__GNUC__)
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wreturn-type"
#endif

#if (__clang__)
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wreturn-type"
#endif

std::string SimulatorRPCChannel::Message_::str() const {
  switch (msg.code) {
    case Message::kNone:
      return "kNone";
    case Message::kAck:
      return "kAck";
    case Message::kTerminate:
      return "kTerminate";
    case Message::kReceiveStart:
      return "kReceiveStart";
    case Message::kReceiveEnd:
      return "kReceiveEnd";
    case Message::kSendStart:
      return "kSendStart";
    case Message::kSendEnd:
      return "kSendEnd";
    default:
      TVM_FFI_THROW(InternalError) << "Internal error: Unrecognized code value: " << msg.code;
      break;
  }
}

#if (__GNUC__)
#pragma GCC diagnostic pop
#endif

#if (__clang__)
#pragma GCC diagnostic pop
#endif

SimulatorRPCChannel::SDKInfo_::SDKInfo_(const std::string& sdk_root, const std::string& cpu)
    : root(sdk_root) {
  // For v69 chips, still look for v68 in the directory names.
  std::string check_cpu = cpu == "v69" ? "v68" : cpu;

  qurt_root = root + "/rtos/qurt/compute" + check_cpu;
  runelf = qurt_root + "/sdksim_bin/runelf.pbn";

  // The "run_main_on_hexagon_sim" binary lives in a subdirectory that looks
  // like "[...]on_hexagon/ship/hexagon_toolv84_v68/run_main_on_hexagon_sim".
  // We need to get the right "hexagon_toolv..." component, based mostly on
  // the cpu version.
  std::vector<std::string> dir_names;

  DIR* dir = opendir((root + "/libs/run_main_on_hexagon/ship").c_str());
  TVM_FFI_ICHECK(dir != nullptr) << "Cannot read directory "
                                 << root + "/libs/run_main_on_hexagon/ship";
  while (dirent* d = readdir(dir)) {
    if (d->d_type != DT_DIR) continue;

    std::string name = d->d_name;
    // Note: The first substr is always safe, and the second only executes
    // when "name" is at least 13 characters long.
    if (name.substr(0, 13) == "hexagon_toolv" && name.substr(name.size() - 3, 3) == check_cpu) {
      dir_names.push_back(name);
    }
  }
  closedir(dir);
  TVM_FFI_ICHECK(!dir_names.empty());

  auto max_it = std::max_element(dir_names.begin(), dir_names.end());
  runmain = root + "/libs/run_main_on_hexagon/ship/" + *max_it + "/run_main_on_hexagon_sim";
}

HEX_8u_t SimulatorRPCChannel::PassVirtAddrCallback(void* handle, int threadno, HEX_8u_t RssV,
                                                   HEX_8u_t RttV, HEX_8u_t RxxV, HEX_1u_t imm) {
  // Rssv = combine(&message_buffer, &dispatch)
  auto* rpc = reinterpret_cast<SimulatorRPCChannel*>(handle);
  rpc->dispatch_v_ = RssV & ~0u;  // ~0u is uint32_t
  rpc->message_buffer_v_ = RssV >> 32;

  LOG(INFO) << "dispatch:" << reinterpret_cast<void*>(rpc->dispatch_v_)
            << ", message buffer:" << reinterpret_cast<void*>(rpc->message_buffer_v_);
  HEXAPI_Status s = rpc->sim_->SetBreakpoint(rpc->dispatch_v_);
  TVM_FFI_ICHECK_EQ(s, HEX_STAT_SUCCESS)
      << self_name_ << ": SetBreakpoint failed with code " << Status_{s}.str();
  return RssV;
}

std::optional<HEXAPI_Cpu> SimulatorRPCChannel::GetCPU(const detail::MaybeString& cpu_str) {
  if (!cpu_str) return std::nullopt;
  return detail::StringSwitch<std::optional<HEXAPI_Cpu>>(*cpu_str)
#ifdef HEX_CPU_ID_V65A_512
      .Case("v65", HEX_CPU_V65)
#endif
#ifdef HEX_CPU_ID_V66A_512
      .Case("v66", HEX_CPU_V66)
#endif
#ifdef HEX_CPU_ID_V68N_1024
      .Case("v68", HEX_CPU_V68)
#endif
#ifdef HEX_CPU_ID_V69NA
      .Case("v69", HEX_CPU_V69)
#endif
#ifdef HEX_CPU_ID_V71H_MDM_SCA
      .Case("v71", HEX_CPU_V71)
#endif
#ifdef HEX_CPU_ID_V73NA_1
      .Case("v73", HEX_CPU_V73)
#endif
#ifdef HEX_CPU_ID_V75NA_1
      .Case("v75", HEX_CPU_V75)
#endif
      .Default(std::nullopt);
}

SimulatorRPCChannel::SimulatorRPCChannel(int stack_size, std::string args) {
  const char* sdk_root_env = std::getenv("HEXAGON_SDK_ROOT");
  TVM_FFI_ICHECK(sdk_root_env != nullptr) << "Please set HEXAGON_SDK_ROOT";
  const char* toolchain_env = std::getenv("HEXAGON_TOOLCHAIN");
  TVM_FFI_ICHECK(toolchain_env != nullptr) << "Please set HEXAGON_TOOLCHAIN";

  std::string sdk_root(sdk_root_env);
  std::string toolchain(toolchain_env);

  auto sim_args_iss = std::istringstream(args);
  using iterator = std::istream_iterator<std::string>;
  auto sim_args = string_list(iterator(sim_args_iss), iterator());

  detail::MaybeString target_str = detail::pop_front(sim_args);
  auto maybe_cpu = GetCPU(target_str);
  if (!maybe_cpu) {
    if (!target_str || target_str->empty()) {
      LOG(INFO) << "CPU not given, defaulting to " << default_cpu_;
      maybe_cpu = GetCPU(std::string(default_cpu_));
    } else {
      TVM_FFI_THROW(InternalError) << "Invalid CPU name " << *target_str;
    }
  }
  cpu_ = Cpu_{*maybe_cpu}.str();
  sim_ = std::make_unique<HexagonWrapper>(*maybe_cpu, (toolchain + "/lib/iss").c_str());
  SDKInfo_ sdk(sdk_root, cpu_);

  // Prepare the osam.cfg file.
  int fd_osam = mkstemps(osam_file_, suffix_len_);
  TVM_FFI_ICHECK_GE(fd_osam, 0);
  std::string osam_str = sdk.qurt_root + "/debugger/lnx64/qurt_model.so";
  TVM_FFI_ICHECK_EQ(write(fd_osam, osam_str.c_str(), osam_str.size()), osam_str.size());
  close(fd_osam);
  // Prepare the q6ss.cfg file.
  int fd_cosim = mkstemps(cosim_file_, suffix_len_);
  TVM_FFI_ICHECK_GE(fd_cosim, 0);
  std::string cosim_str =
      toolchain +
      "/lib/iss/qtimer.so --csr_base=0xFC900000 --irq_p=1 --freq=19200000 --cnttid=1\n" +
      toolchain + "/lib/iss/l2vic.so 32 0xFC910000";
  TVM_FFI_ICHECK_EQ(write(fd_cosim, cosim_str.c_str(), cosim_str.size()), cosim_str.size());
  close(fd_cosim);

  CHECKED_CALL(ConfigureL2tcmBase, 0xD800);
  CHECKED_CALL(ConfigureARFilesystem, &std::string(".")[0]);
  CHECKED_CALL(ConfigureOSAwareness, osam_file_);
  CHECKED_CALL(ConfigureCosim, cosim_file_);
  CHECKED_CALL(ConfigureExecutableBinary, sdk.runelf.c_str());

  std::string stack_arg =
      stack_size > 0 ? std::string(" -stack_size=") + std::to_string(stack_size) : "";
  std::string cmdline = sdk.runelf + " " + sdk.runmain + stack_arg + " -- libhexagon_rpc_sim.so";
  char* parg = &cmdline[0];
  CHECKED_CALL(ConfigureAppCommandLine, 1, &parg);

  // Configure the simulator.
  Configure(sim_args);

  CHECKED_CALL(EndOfConfiguration);
  CHECKED_CALL(AddUserDefinedInstCallback, this, &PassVirtAddrCallback);

  // Start the initial run, until the callback is executed.
  HEX_4u_t result;
  HEXAPI_CoreState core = sim_->Run(&result);
  if (core != HEX_CORE_BREAKPOINT) {
    TVM_FFI_THROW(InternalError) << self_name_
                                 << ": Run not stopped on breakpoint, code=" << Core_{core}.str();
  }

  // At this point the simulator has executed the executable's initialization
  // code that could have written to the SSR register.
  // Enable UPCYCLE register.
  HEX_4u_t thread_num;
  CHECKED_CALL(GetCurrentHWThreadNum, &thread_num);
  HEX_4u_t thread_ssr;
  CHECKED_CALL(ReadThreadRegister, thread_num, TH_REG_SSR, &thread_ssr);
  thread_ssr |= (1 << 23);
  CHECKED_CALL(WriteThreadRegister, thread_num, TH_REG_SSR, thread_ssr);
}

SimulatorRPCChannel::~SimulatorRPCChannel() {
  SendMsg(Message::kTerminate, 0, Message::null_va);

  HEX_4u_t result;
  HEXAPI_CoreState core = sim_->Run(&result);
  TVM_FFI_ICHECK_EQ(core, HEX_CORE_FINISHED);

  unlink(osam_file_);
  unlink(cosim_file_);
}

size_t SimulatorRPCChannel::Send(const void* data, size_t size) {
  TVM_FFI_ICHECK(size <= std::numeric_limits<uint32_t>::max());

  Message reply_start =
      SendMsg(Message::kReceiveStart, static_cast<uint32_t>(size), Message::null_va);
  TVM_FFI_ICHECK_EQ(reply_start.code, Message::kAck);
  TVM_FFI_ICHECK_GE(reply_start.len, size);
  TVM_FFI_ICHECK_NE(reply_start.va, Message::null_va);

  WriteToProcess(reply_start.va, data, size);

  Message reply_end = SendMsg(Message::kReceiveEnd, static_cast<uint32_t>(size), reply_start.va);
  TVM_FFI_ICHECK_EQ(reply_end.code, Message::kAck);
  return size;
}

size_t SimulatorRPCChannel::Recv(void* data, size_t size) {
  TVM_FFI_ICHECK(size <= std::numeric_limits<uint32_t>::max());

  Message reply_start = SendMsg(Message::kSendStart, static_cast<uint32_t>(size), Message::null_va);
  TVM_FFI_ICHECK_EQ(reply_start.code, Message::kAck);
  TVM_FFI_ICHECK_GE(reply_start.len, size);
  TVM_FFI_ICHECK_NE(reply_start.va, Message::null_va);

  ReadFromProcess(data, reply_start.va, size);

  Message reply_end = SendMsg(Message::kSendEnd, static_cast<uint32_t>(size), reply_start.va);
  TVM_FFI_ICHECK_EQ(reply_end.code, Message::kAck);
  return size;
}

Message SimulatorRPCChannel::SendMsg(Message msg) {
  auto run = [this]() {
    HEXAPI_CoreState core = HEX_CORE_RESET;
    HEX_4u_t result;

    core = sim_->Run(&result);
    Core_ core_ = {core};
    TVM_FFI_ICHECK_EQ(core, HEX_CORE_BREAKPOINT)
        << "Expecting HEX_CORE_BREAKPOINT, received: " << core_.str();
  };

  WriteToProcess(message_buffer_v_, &msg, sizeof msg);
  run();

  Message ret = {0};
  ReadFromProcess(&ret, message_buffer_v_, sizeof ret);
  return ret;
}

Message SimulatorRPCChannel::SendMsg(uint32_t code, uint32_t len, uint32_t va) {
  Message m;
  m.code = code;
  m.len = len;
  m.va = va;
  return SendMsg(m);
}

void SimulatorRPCChannel::ReadFromProcess(void* host_dst, HEX_VA_t src, size_t len) {
  if (len == 0) return;

  auto* dst = reinterpret_cast<char*>(host_dst);

  while (len > 0) {
    uint32_t src_align = 1u << __builtin_ctz(src);  // Max pow-of-2 dividing src.
    uint32_t len_align = 1u << __builtin_ctz(len);  // Max pow-of-2 dividing len.
    // The transfer behaves as a bus transaction, so it has to be of a size
    // that is a power of 2 not exceeding 8, and the remote address has to
    // be aligned to at least the transfer size.
    auto read_size = std::min<size_t>({src_align, len_align, 8});
    CHECKED_CALL(ReadVirtual, src, /*asid*/ -1u, read_size, dst);

    src += read_size;
    dst += read_size;
    len -= read_size;
  }
}

void SimulatorRPCChannel::WriteToProcess(HEX_VA_t dst, const void* host_src, size_t len) {
  if (len == 0) return;

  auto* src = reinterpret_cast<const char*>(host_src);

  while (len > 0) {
    uint32_t dst_align = 1u << __builtin_ctz(dst);  // Max pow-of-2 dividing dst.
    uint32_t len_align = 1u << __builtin_ctz(len);  // Max pow-of-2 dividing len.
    // The transfer behaves as a bus transaction, so it has to be of a size
    // that is a power of 2 not exceeding 8, and the remote address has to
    // be aligned to at least the transfer size.
    auto write_size = std::min<size_t>({dst_align, len_align, 8});
    HEX_8u_t val = 0;
    memcpy(&val, src, write_size);
    CHECKED_CALL(WriteVirtual, dst, /*asid*/ -1u, write_size, val);

    src += write_size;
    dst += write_size;
    len -= write_size;
  }
}

// Configuration functions

bool SimulatorRPCChannel::Configure(string_list& opts) {
  while (!opts.empty()) {
    std::string key = *detail::pop_front(opts);
    auto f = opt_map_.find(key);
    if (f == opt_map_.end()) {
      TVM_FFI_THROW(InternalError) << "Unrecognized simulator option: " << key;
      // unreachable
    }
    TVM_FFI_ICHECK((this->*f->second)(opts)) << "error handling option: " << key;
  }

  // Check AHB.
  if (ahb_.first.has_value() && ahb_.second.has_value()) {
    CHECKED_CALL(ConfigureAHB, *ahb_.first, *ahb_.second);
  } else {
    TVM_FFI_ICHECK(!ahb_.first.has_value() && !ahb_.second.has_value())
        << self_name_ << ": please specify both low and high addresses for AHB";
  }

  // Check AXI2.
  if (axi2_.first.has_value() && axi2_.second.has_value()) {
    CHECKED_CALL(ConfigureAXI2, *axi2_.first, *axi2_.second);
  } else {
    TVM_FFI_ICHECK(!axi2_.first.has_value() && !axi2_.second.has_value())
        << self_name_ << ": please specify both low and high addresses for AXI2";
  }

  return true;
}

bool SimulatorRPCChannel::HandleAHBBusPenalty(string_list& rest) {
  auto penalty = detail::to_uint(detail::pop_front(rest));
  auto interval = to_interval(detail::pop_front(rest));
  if (penalty && interval) {
    CHECKED_CALL(ConfigureAHBBusPenalty, *penalty, *interval);
  }
  return static_cast<bool>(penalty) && static_cast<bool>(interval);
}

bool SimulatorRPCChannel::HandleAHBBusRatio(string_list& rest) {
  auto ratio = detail::to_float(detail::pop_front(rest));
  if (ratio) {
    CHECKED_CALL(ConfigureAHBBusRatio, *ratio);
  }
  return static_cast<bool>(ratio);
}

bool SimulatorRPCChannel::HandleAHBHighAddr(string_list& rest) {
  auto addr = detail::to_uint(detail::pop_front(rest));
  TVM_FFI_ICHECK(addr) << self_name_ << ": invalid value for AHB high adddress";
  if (addr) {
    ahb_.second = *addr;
  }
  return static_cast<bool>(addr);
}

bool SimulatorRPCChannel::HandleAHBLowAddr(string_list& rest) {
  auto addr = detail::to_uint(detail::pop_front(rest));
  TVM_FFI_ICHECK(addr) << self_name_ << ": invalid value for AHB low adddress";
  if (addr) {
    ahb_.first = *addr;
  }
  return static_cast<bool>(addr);
}

bool SimulatorRPCChannel::HandleAXI2BusPenalty(string_list& rest) {
  auto penalty = detail::to_uint(detail::pop_front(rest));
  auto interval = to_interval(detail::pop_front(rest));
  if (penalty && interval) {
    CHECKED_CALL(ConfigureAXI2BusPenalty, *penalty, *interval);
  }
  return static_cast<bool>(penalty) && static_cast<bool>(interval);
}

bool SimulatorRPCChannel::HandleAXI2BusRatio(string_list& rest) {
  auto ratio = detail::to_float(detail::pop_front(rest));
  if (ratio) {
    CHECKED_CALL(ConfigureAXI2BusRatio, *ratio);
  }
  return static_cast<bool>(ratio);
}

bool SimulatorRPCChannel::HandleAXI2HighAddr(string_list& rest) {
  auto addr = detail::to_uint(detail::pop_front(rest));
  TVM_FFI_ICHECK(addr) << self_name_ << ": invalid value for AXI2 high adddress";
  if (addr) {
    axi2_.second = *addr;
  }
  return static_cast<bool>(addr);
}

bool SimulatorRPCChannel::HandleAXI2LowAddr(string_list& rest) {
  auto addr = detail::to_uint(detail::pop_front(rest));
  TVM_FFI_ICHECK(addr) << self_name_ << ": invalid value for AXI2 low adddress";
  if (addr) {
    axi2_.first = *addr;
  }
  return static_cast<bool>(addr);
}

bool SimulatorRPCChannel::HandleBuildTag(string_list& rest) {
  sim_->PrintBuildTag();
  return true;
}

bool SimulatorRPCChannel::HandleBusPenalty(string_list& rest) {
  auto penalty = detail::to_uint(detail::pop_front(rest));
  auto interval = to_interval(detail::pop_front(rest));
  if (penalty && interval) {
    CHECKED_CALL(ConfigureBusPenalty, *penalty, *interval);
  }
  return static_cast<bool>(penalty) && static_cast<bool>(interval);
}

bool SimulatorRPCChannel::HandleBusRatio(string_list& rest) {
  auto ratio = detail::to_float(detail::pop_front(rest));
  if (ratio) {
    CHECKED_CALL(ConfigureBusRatio, *ratio);
  }
  return static_cast<bool>(ratio);
}

bool SimulatorRPCChannel::HandleBusTrace(string_list& rest) {
  auto file = detail::pop_front(rest);
  if (file) {
    CHECKED_CALL(SetTracing, HEX_TRACE_BUS, file->c_str());
  }
  return static_cast<bool>(file);
}

bool SimulatorRPCChannel::HandleBypassIdle(string_list& rest) {
  CHECKED_CALL(ConfigureBypassIdle, true);
  return true;
}

bool SimulatorRPCChannel::HandleConnectionTimeout(string_list& rest) {
  auto time = detail::to_int(detail::pop_front(rest));
  if (time) {
    CHECKED_CALL(ConfigureConnectionTimeout, *time);
  }
  return static_cast<bool>(time);
}

bool SimulatorRPCChannel::HandleCoprocTrace(string_list& rest) {
  auto file = detail::pop_front(rest);
  if (file) {
    CHECKED_CALL(SetTracing, HEX_TRACE_COPROC, file->c_str());
  }
  return static_cast<bool>(file);
}

bool SimulatorRPCChannel::HandleCoreDump(string_list& rest) {
  auto file = detail::pop_front(rest);
  if (file) {
    CHECKED_CALL(ConfigureCoreDump, file->c_str());
  }
  return static_cast<bool>(file);
}

bool SimulatorRPCChannel::HandleCosimFile(string_list& rest) {
  auto file = detail::pop_front(rest);
  if (file) {
    CHECKED_CALL(ConfigureCosim, file->c_str());
  }
  return static_cast<bool>(file);
}

bool SimulatorRPCChannel::HandleDCacheTrace(string_list& rest) {
  auto file = detail::pop_front(rest);
  if (file) {
    CHECKED_CALL(SetTracing, HEX_TRACE_DCACHE, file->c_str());
  }
  return static_cast<bool>(file);
}

bool SimulatorRPCChannel::HandleDSPClock(string_list& rest) {
  auto freq = detail::to_uint(detail::pop_front(rest));
  if (freq) {
    CHECKED_CALL(ConfigureCoreFrequency, *freq);
  }
  return static_cast<bool>(freq);
}

bool SimulatorRPCChannel::HandleETMCFGBase(string_list& rest) {
  auto base = detail::to_uint(detail::pop_front(rest));
  if (base) {
    CHECKED_CALL(ConfigureEtmcfgBase, *base);
  }
  return static_cast<bool>(base);
}

bool SimulatorRPCChannel::HandleGDBServ(string_list& rest) {
  auto port = detail::to_uint(detail::pop_front(rest));
  if (port) {
    CHECKED_CALL(ConfigureRemoteDebug, *port);
    debug_port_ = *port;
  }
  return static_cast<bool>(port);
}

bool SimulatorRPCChannel::HandleHVXLength(string_list& rest) {
  auto len = detail::to_int(detail::pop_front(rest));
  if (len) {
    CHECKED_CALL(ConfigureHVXLength, *len);
  }
  return static_cast<bool>(len);
}

bool SimulatorRPCChannel::HandleICacheTrace(string_list& rest) {
  auto file = detail::pop_front(rest);
  if (file) {
    CHECKED_CALL(SetTracing, HEX_TRACE_ICACHE, file->c_str());
  }
  return static_cast<bool>(file);
}

bool SimulatorRPCChannel::HandleL2CacheTrace(string_list& rest) {
  auto file = detail::pop_front(rest);
  if (file) {
    CHECKED_CALL(SetTracing, HEX_TRACE_L2CACHE, file->c_str());
  }
  return static_cast<bool>(file);
}

bool SimulatorRPCChannel::HandleL2CFGBase(string_list& rest) {
  auto base = detail::to_uint(detail::pop_front(rest));
  if (base) {
    CHECKED_CALL(ConfigureL2cfgBase, *base);
  }
  return static_cast<bool>(base);
}

bool SimulatorRPCChannel::HandleL2TCMBase(string_list& rest) {
  auto base = detail::to_uint(detail::pop_front(rest));
  if (base) {
    CHECKED_CALL(ConfigureL2tcmBase, *base);
  }
  return static_cast<bool>(base);
}

bool SimulatorRPCChannel::HandleMemFillRand(string_list& rest) {
  auto seed = detail::to_uint(detail::pop_front(rest));
  if (seed) {
    CHECKED_CALL(ConfigureMemFillRandom, *seed);
  }
  return static_cast<bool>(seed);
}

bool SimulatorRPCChannel::HandleMemFill(string_list& rest) {
  auto val = detail::to_uint(detail::pop_front(rest));
  if (val) {
    CHECKED_CALL(ConfigureMemFill, *val);
  }
  return static_cast<bool>(val);
}

bool SimulatorRPCChannel::HandleMemTrace(string_list& rest) {
  auto file = detail::pop_front(rest);
  if (file) {
    CHECKED_CALL(SetTracing, HEX_TRACE_MEM, file->c_str());
  }
  return static_cast<bool>(file);
}

bool SimulatorRPCChannel::HandleNullPtr(string_list& rest) {
  auto behavior = to_nullptr(detail::pop_front(rest));
  if (behavior) {
    CHECKED_CALL(ConfigureNULLPointerBehavior, *behavior);
  }
  return static_cast<bool>(behavior);
}

bool SimulatorRPCChannel::HandlePacketAnalyze(string_list& rest) {
  auto file = detail::pop_front(rest);
  if (file) {
    CHECKED_CALL(ConfigurePacketAnalysis, file->c_str());
  }
  return static_cast<bool>(file);
}

bool SimulatorRPCChannel::HandlePCFilter(string_list& rest) {
  auto range = detail::to_range<uint64_t, detail::to_uint>(detail::pop_front(rest));
  if (range) {
    CHECKED_CALL(ConfigurePCRangeFilter, range->first, range->second);
  }
  return static_cast<bool>(range);
}

bool SimulatorRPCChannel::HandlePCTraceMin(string_list& rest) {
  auto file = detail::pop_front(rest);
  if (file) {
    CHECKED_CALL(SetTracing, HEX_TRACE_PC_MIN, file->c_str());
  }
  return static_cast<bool>(file);
}

bool SimulatorRPCChannel::HandlePCTraceNano(string_list& rest) {
  auto file = detail::pop_front(rest);
  if (file) {
    CHECKED_CALL(SetTracing, HEX_TRACE_PC_NANO, file->c_str());
  }
  return static_cast<bool>(file);
}

bool SimulatorRPCChannel::HandlePCTrace(string_list& rest) {
  auto file = detail::pop_front(rest);
  if (file) {
    CHECKED_CALL(SetTracing, HEX_TRACE_PC, file->c_str());
  }
  return static_cast<bool>(file);
}

bool SimulatorRPCChannel::HandlePMUStatsFile(string_list& rest) {
  auto file = detail::pop_front(rest);
  if (file) {
    CHECKED_CALL(ConfigurePmuStatisticsFile, file->c_str());
  }
  return static_cast<bool>(file);
}

bool SimulatorRPCChannel::HandleProfile(string_list& rest) {
  auto path = detail::pop_front(rest);
  if (path) {
    CHECKED_CALL(ConfigureGProf, path->c_str());
  }
  return static_cast<bool>(path);
}

bool SimulatorRPCChannel::HandleProfileTimeZero(string_list& rest) {
  auto timezero = detail::to_bool(detail::pop_front(rest));
  if (timezero) {
    CHECKED_CALL(ConfigureProfileMode, *timezero);
  }
  return static_cast<bool>(timezero);
}

bool SimulatorRPCChannel::HandleQuiet(string_list& rest) {
  sim_->VerboseMode(HEX_QUIET);
  return true;
}

bool SimulatorRPCChannel::HandleReconnect(string_list& rest) {
  if (!debug_port_) {
    TVM_FFI_THROW(InternalError) << "Reconnect error: --reconnect must be specified "
                                    "AFTER --gdbserv <port_num>";
  }
  CHECKED_CALL(ConfigureRemoteDebug, *debug_port_, true);
  return true;
}

bool SimulatorRPCChannel::HandleRTOS(string_list& rest) {
  auto file = detail::pop_front(rest);
  if (file) {
    CHECKED_CALL(ConfigureOSAwareness, file->c_str());
  }
  return static_cast<bool>(file);
}

bool SimulatorRPCChannel::HandleSimErr(string_list& rest) {
  auto file = detail::pop_front(rest);
  if (file) {
    CHECKED_CALL(ConfigureSimStderr, file->c_str());
  }
  return static_cast<bool>(file);
}

bool SimulatorRPCChannel::HandleSimIn(string_list& rest) {
  auto file = detail::pop_front(rest);
  if (file) {
    CHECKED_CALL(ConfigureSimStdin, file->c_str());
  }
  return static_cast<bool>(file);
}

bool SimulatorRPCChannel::HandleSimOut(string_list& rest) {
  auto file = detail::pop_front(rest);
  if (file) {
    CHECKED_CALL(ConfigureSimStdout, file->c_str());
  }
  return static_cast<bool>(file);
}

bool SimulatorRPCChannel::HandleStackStart(string_list& rest) {
  auto base = detail::to_uint(detail::pop_front(rest));
  auto size = detail::to_uint(detail::pop_front(rest));
  if (base && size) {
    CHECKED_CALL(ConfigureStackInfo, *base, *size);
  }
  return static_cast<bool>(base) && static_cast<bool>(size);
}

bool SimulatorRPCChannel::HandleStallTrace(string_list& rest) {
  auto file = detail::pop_front(rest);
  if (file) {
    CHECKED_CALL(SetTracing, HEX_TRACE_STALL, file->c_str());
  }
  return static_cast<bool>(file);
}

bool SimulatorRPCChannel::HandleStatsFile(string_list& rest) {
  auto file = detail::pop_front(rest);
  if (file) {
    CHECKED_CALL(ConfigureStatisticsFile, file->c_str());
  }
  return static_cast<bool>(file);
}

bool SimulatorRPCChannel::HandleSubsystemBase(string_list& rest) {
  auto base = detail::to_uint(detail::pop_front(rest));
  if (base) {
    CHECKED_CALL(ConfigureSubsystemBase, *base);
  }
  return static_cast<bool>(base);
}

bool SimulatorRPCChannel::HandleSymFile(string_list& rest) {
  auto file = detail::pop_front(rest);
  if (file) {
    CHECKED_CALL(AddSymbolFile, file->c_str());
  }
  return static_cast<bool>(file);
}

bool SimulatorRPCChannel::HandleTCM(string_list& rest) {
  CHECKED_CALL(ConfigureTimingMode, HEX_TIMING);
  return true;
}

bool SimulatorRPCChannel::HandleTCMHighAddr(string_list& rest) {
  // This option takes an argument, but (the option) is ignored.
  auto addr = detail::to_uint(detail::pop_front(rest));
  return static_cast<bool>(addr);
}

bool SimulatorRPCChannel::HandleTCMLowAddr(string_list& rest) {
  auto addr = detail::to_uint(detail::pop_front(rest));
  if (addr) {
    CHECKED_CALL(ConfigureTCM, *addr);
  }
  return static_cast<bool>(addr);
}

bool SimulatorRPCChannel::HandleTimeFilterNS(string_list& rest) {
  auto range = detail::to_range<uint64_t, detail::to_uint>(detail::pop_front(rest));
  if (range) {
    CHECKED_CALL(ConfigureTimeRangeFilter, range->first, HEX_NANOSEC, range->second, HEX_NANOSEC);
  }
  return static_cast<bool>(range);
}

bool SimulatorRPCChannel::HandleTiming(string_list& rest) {
  HEXAPI_TimingMode timing_mode = HEX_TIMING;
  // The argument to --timing is optional.
  if (should_parse_next(rest)) {
    if (auto mode = to_timingmode(detail::pop_front(rest))) {
      timing_mode = *mode;
    } else {
      return false;
    }
  }
  CHECKED_CALL(ConfigureTimingMode, timing_mode);
  return true;
}

bool SimulatorRPCChannel::HandleUArchTrace(string_list& rest) {
  auto file = detail::pop_front(rest);
  if (file) {
    CHECKED_CALL(SetTracing, HEX_TRACE_UARCH, file->c_str());
  }
  return static_cast<bool>(file);
}

bool SimulatorRPCChannel::HandleUseFS(string_list& rest) {
  auto file = detail::pop_front(rest);
  if (file) {
    CHECKED_CALL(ConfigureARFilesystem, &(*file)[0]);
  }
  return static_cast<bool>(file);
}

bool SimulatorRPCChannel::HandleV2PTranslation(string_list& rest) {
  auto enable = detail::to_bool(detail::pop_front(rest));
  if (enable) {
    CHECKED_CALL(EnableVirtualToPhysicalTranslation, *enable);
  }
  return static_cast<bool>(enable);
}

bool SimulatorRPCChannel::HandleVerbose(string_list& rest) {
  auto mode = to_verbosemode(detail::pop_front(rest));
  if (mode) {
    sim_->VerboseMode(*mode);
  }
  return static_cast<bool>(mode);
}

bool SimulatorRPCChannel::should_parse_next(const string_list& rest) {
  if (auto str = detail::front(rest)) {
    return str->empty() || str->front() != '-';
  }
  return false;
}

std::optional<HEXAPI_Interval> SimulatorRPCChannel::to_interval(const detail::MaybeString& str) {
  if (!str) return std::nullopt;

  if (auto val = detail::to_int(*str)) {
    switch (*val) {
      case HEX_MILLISEC:
      case HEX_MICROSEC:
      case HEX_NANOSEC:
      case HEX_PICOSEC:
      case HEX_PCYCLE:
        return static_cast<HEXAPI_Interval>(*val);
    }
  }

  return detail::StringSwitch<std::optional<HEXAPI_Interval>>(*str)
      .Case("MILLISEC", HEX_MILLISEC)
      .Case("MICROSEC", HEX_MICROSEC)
      .Case("NANOSEC", HEX_NANOSEC)
      .Case("PICOSEC", HEX_PICOSEC)
      .Case("PCYCLE", HEX_PCYCLE)
      .Default(std::nullopt);
}

std::optional<HEXAPI_TimingMode> SimulatorRPCChannel::to_timingmode(
    const detail::MaybeString& str) {
  if (!str) return std::nullopt;

  if (auto val = detail::to_int(*str)) {
    switch (*val) {
      case HEX_NOTIMING:
      case HEX_TIMING_NODBC:
      case HEX_TIMING:
      case HEX_TIMING_COHERENCY:
        return static_cast<HEXAPI_TimingMode>(*val);
    }
  }

  return detail::StringSwitch<std::optional<HEXAPI_TimingMode>>(*str)
      .Case("NOTIMING", HEX_NOTIMING)
      .Case("TIMING_NODBC", HEX_TIMING_NODBC)
      .Case("TIMING", HEX_TIMING)
      .Case("TIMING_COHERENCY", HEX_TIMING_COHERENCY)
      .Default(std::nullopt);
}

std::optional<HEXAPI_VerboseMode> SimulatorRPCChannel::to_verbosemode(
    const detail::MaybeString& str) {
  if (!str) return std::nullopt;

  if (auto val = detail::to_int(*str)) {
    switch (*val) {
      case HEX_SILENT:
      case HEX_QUIET:
      case HEX_NORMAL:
      case HEX_VERBOSE:
      case HEX_REALLY_VERBOSE:
        return static_cast<HEXAPI_VerboseMode>(*val);
    }
  }

  return detail::StringSwitch<std::optional<HEXAPI_VerboseMode>>(*str)
      .Case("SILENT", HEX_SILENT)
      .Case("QUIET", HEX_QUIET)
      .Case("NORMAL", HEX_NORMAL)
      .Case("VERBOSE", HEX_VERBOSE)
      .Case("REALLY_VERBOSE", HEX_REALLY_VERBOSE)
      .Default(std::nullopt);
}

std::optional<HEXAPI_Nullptr> SimulatorRPCChannel::to_nullptr(const detail::MaybeString& str) {
  if (!str) return std::nullopt;

  if (auto val = detail::to_int(*str)) {
    switch (*val) {
      case HEX_NULLPTR_IGNORE:
      case HEX_NULLPTR_WARN:
      case HEX_NULLPTR_FATAL:
      case HEX_NULLPTR_PCZERO:
        return static_cast<HEXAPI_Nullptr>(*val);
    }
  }

  return detail::StringSwitch<std::optional<HEXAPI_Nullptr>>(*str)
      .Case("IGNORE", HEX_NULLPTR_IGNORE)
      .Case("WARN", HEX_NULLPTR_WARN)
      .Case("FATAL", HEX_NULLPTR_FATAL)
      .Case("PCZERO", HEX_NULLPTR_PCZERO)
      .Default(std::nullopt);
}

TVM_FFI_STATIC_INIT_BLOCK() {
  namespace refl = tvm::ffi::reflection;
  refl::GlobalDef().def_packed(
      "tvm.contrib.hexagon.create_hexagon_session", [](ffi::PackedArgs args, ffi::Any* rv) {
        TVM_FFI_ICHECK(args.size() >= 4) << args.size() << " is less than 4";

        auto session_name = args[0].cast<std::string>();
        int stack_size = args[1].cast<int>();
        auto sim_args = args[2].cast<std::string>();
        auto channel = std::make_unique<SimulatorRPCChannel>(stack_size, sim_args);
        std::shared_ptr<RPCEndpoint> endpoint =
            RPCEndpoint::Create(std::move(channel), session_name, "", nullptr);
        std::shared_ptr<RPCSession> session = CreateClientSession(endpoint);
        *rv = CreateRPCSessionModule(session);
      });
}

}  // namespace hexagon
}  // namespace runtime
}  // namespace tvm
