diff --git a/apps/hexagon_api/CMakeLists.txt b/apps/hexagon_api/CMakeLists.txt index 557dcfb850454..052fd00020cba 100644 --- a/apps/hexagon_api/CMakeLists.txt +++ b/apps/hexagon_api/CMakeLists.txt @@ -23,6 +23,31 @@ else() endif() file(MAKE_DIRECTORY ${HEXAGON_API_BINARY_DIR}) +# Build X86 binaries: +# - tvm_rpc_x86 + +ExternalProject_Add(x86_tvm_runtime_rpc + SOURCE_DIR "${TVM_SOURCE_DIR}" + BUILD_COMMAND $(MAKE) runtime tvm_rpc + CMAKE_ARGS + "-DUSE_HEXAGON_TOOLCHAIN=${USE_HEXAGON_TOOLCHAIN}" + "-DCMAKE_CXX_STANDARD=14" + "-DUSE_LIBBACKTRACE=OFF" + "-DUSE_RPC=ON" + "-DUSE_CPP_RPC=ON" + "-DUSE_HEXAGON_RPC=ON" + "-DBUILD_STATIC_RUNTIME=ON" + INSTALL_COMMAND "" + BUILD_ALWAYS ON +) +ExternalProject_Get_Property(x86_tvm_runtime_rpc BINARY_DIR) +ExternalProject_Add_Step(x86_tvm_runtime_rpc copy_rpc_server + COMMAND ${CMAKE_COMMAND} -E copy_if_different + ${BINARY_DIR}/tvm_rpc + ${HEXAGON_API_BINARY_DIR}/tvm_rpc_x86 + DEPENDEES install +) + # Build Android binaries: # - libtvm_runtime.so # - tvm_rpc_android @@ -38,7 +63,6 @@ ExternalProject_Add(android_tvm_runtime_rpc "-DUSE_HEXAGON_ARCH=${USE_HEXAGON_ARCH}" "-DCMAKE_CXX_STANDARD=14" "-DUSE_LIBBACKTRACE=OFF" - "-DUSE_LLVM=OFF" "-DUSE_RPC=ON" "-DUSE_CPP_RPC=ON" "-DUSE_HEXAGON_RPC=ON" @@ -66,7 +90,7 @@ ExternalProject_Add_Step(android_tvm_runtime_rpc copy_rpc_server ExternalProject_Add(hexagon_tvm_runtime_rpc SOURCE_DIR "${TVM_SOURCE_DIR}" - BUILD_COMMAND $(MAKE) runtime + BUILD_COMMAND $(MAKE) runtime hexagon_rpc_sim CMAKE_ARGS "-DCMAKE_C_COMPILER=${USE_HEXAGON_TOOLCHAIN}/bin/hexagon-clang" "-DCMAKE_CXX_COMPILER=${USE_HEXAGON_TOOLCHAIN}/bin/hexagon-clang++" @@ -84,6 +108,7 @@ ExternalProject_Add_Step(hexagon_tvm_runtime_rpc copy_binaries COMMAND ${CMAKE_COMMAND} -E copy_if_different ${BINARY_DIR}/libtvm_runtime.a ${BINARY_DIR}/libhexagon_rpc_skel.so + ${BINARY_DIR}/libhexagon_rpc_sim.so ${HEXAGON_API_BINARY_DIR} DEPENDEES install ) diff --git a/cmake/modules/Hexagon.cmake b/cmake/modules/Hexagon.cmake index d1e62358a2d9e..a8844a22e164e 100644 --- a/cmake/modules/Hexagon.cmake +++ b/cmake/modules/Hexagon.cmake @@ -78,10 +78,13 @@ endif() # # USE_HEXAGON_RPC: # - When building for Hexagon, this will build the Hexagon endpoint of the -# RPC server: the FastRPC skel library (with TVM runtime built into it). +# RPC server: the FastRPC skel library (with TVM runtime built into it), +# and the standalone RPC server for simulator. # - When building for Android, this will build the (intermediary) RPC server, # including the "stub" code for the FastRPC implementation of the RPC # channel. +# - When building for x86, this will build the host-side code that instan- +# tiates the simulator. if(NOT BUILD_FOR_HEXAGON AND NOT BUILD_FOR_ANDROID) set(BUILD_FOR_HOST TRUE) @@ -116,6 +119,15 @@ function(add_android_paths) link_directories(${HEXAGON_REMOTE_ROOT}) endfunction() +function(add_hexagon_wrapper_paths) + if(NOT DEFINED HEXAGON_TOOLCHAIN) + message(FATAL_ERROR "This function must be called after find_hexagon_toolchain") + endif() + include_directories(SYSTEM + "${HEXAGON_TOOLCHAIN}/include/iss" + ) + link_directories("${HEXAGON_TOOLCHAIN}/lib/iss") +endfunction() # Common sources for TVM runtime with Hexagon support file_glob_append(RUNTIME_HEXAGON_COMMON_SRCS @@ -148,12 +160,11 @@ if(USE_HEXAGON_DEVICE) invalid_device_value_for("host") endif() find_hexagon_toolchain() + add_hexagon_wrapper_paths() file_glob_append(RUNTIME_HEXAGON_SRCS "${TVMRT_SOURCE_DIR}/hexagon/android/*.cc" "${TVMRT_SOURCE_DIR}/hexagon/android/sim/*.cc" ) - include_directories(SYSTEM "${HEXAGON_TOOLCHAIN}/include/iss") - link_directories("${HEXAGON_TOOLCHAIN}/lib/iss") list(APPEND TVM_RUNTIME_LINKER_LIBS "-lwrapper") ExternalProject_Add(sim_dev @@ -245,6 +256,23 @@ if(USE_HEXAGON_RPC) target_include_directories(hexagon_rpc_skel SYSTEM PRIVATE "${TVMRT_SOURCE_DIR}/hexagon/rpc" ) + # Add the simulator-specific RPC code into a shared library to be + # executed via run_main_on_sim. + add_library(hexagon_rpc_sim SHARED + "${TVMRT_SOURCE_DIR}/hexagon/rpc/simulator/rpc_server.cc" + ) + target_link_libraries(hexagon_rpc_sim + -Wl,--whole-archive tvm_runtime -Wl,--no-whole-archive + ) + + elseif(BUILD_FOR_HOST) + find_hexagon_toolchain() + add_hexagon_wrapper_paths() + file_glob_append(RUNTIME_HEXAGON_SRCS + "${TVMRT_SOURCE_DIR}/hexagon/host/*.cc" + "${TVMRT_SOURCE_DIR}/hexagon/rpc/simulator/session.cc" + ) + list(APPEND TVM_RUNTIME_LINKER_LIBS "-lwrapper") endif() endif() # USE_HEXAGON_RPC diff --git a/python/tvm/contrib/hexagon/session.py b/python/tvm/contrib/hexagon/session.py index c413c60ce5c5f..caaec6cf7b9ab 100644 --- a/python/tvm/contrib/hexagon/session.py +++ b/python/tvm/contrib/hexagon/session.py @@ -17,6 +17,7 @@ """Defines a Session class for Hexagon devices.""" +import os from tvm import rpc as _rpc @@ -59,6 +60,7 @@ def __enter__(self): "tvm.contrib.hexagon.create_hexagon_session", self._session_name, self._remote_stack_size_bytes, + os.environ.get("HEXAGON_SIM_ARGS", ""), ], ) self.device = self._rpc.hexagon(0) diff --git a/src/runtime/hexagon/rpc/simulator/hexagon_sim_proto.h b/src/runtime/hexagon/rpc/simulator/hexagon_sim_proto.h new file mode 100644 index 0000000000000..fec9d7329dbed --- /dev/null +++ b/src/runtime/hexagon/rpc/simulator/hexagon_sim_proto.h @@ -0,0 +1,75 @@ +/* + * 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. + */ + +#ifndef TVM_RUNTIME_HEXAGON_RPC_SIMULATOR_HEXAGON_SIM_PROTO_H_ +#define TVM_RUNTIME_HEXAGON_RPC_SIMULATOR_HEXAGON_SIM_PROTO_H_ + +struct Message { + enum : uint32_t { + kNone = 0, + kAck, + kTerminate, + kReceiveStart, + kReceiveEnd, + kSendStart, + kSendEnd, + }; + enum : uint32_t { + null_va = 0, + }; + + uint32_t code; + uint32_t len; + uint32_t va; +} __attribute__((packed)); + +// Protocol: +// +// Copying data from host to remote: +// +// Host >-- [ kReceiveStart, len, null_va ] --> Remote +// * Remote client prepares a buffer with at least `len` bytes. +// Host <-- [ kAck, buf_size, buf_ptr ] <-- Remote +// * Host writes `nbytes` into buffer, `nbytes` <= `len`. +// Host >-- [ kReceiveEnd, nbytes, buf_ptr ] --> Remote +// * Remote client processes the data. +// Host <-- [ kAck, ___, ___ ] <-- Remote +// +// Copying data from remote to host: +// +// Host >-- [ kSendStart, len, null_va ] --> Remote +// * Remote client returns pointer to the buffer with the data to be read. +// * There should be at least `len` bytes ready in the buffer. +// Host <-- [ kAck, buf_size, buf_ptr ] <-- Remote +// * Host reads `nbytes` from buffer, `nbytes` <= `buf_size`. +// Host >-- [ kSendEnd , nbytes, buf_ptr ] --> Remote +// * Remote client processes the data. +// Host <-- [ kAck, ___, ___ ] <-- Remote +// +// Teminating server: +// +// Host >-- [ kTerminate, ___, ___ ] --> Remote +// Host <-- [ kAck, ___, ___ ] <-- Remote +// * Host continues execution of the client. +// * Client terminates. + +#define DISPATCH_FUNCTION_NAME dispatch_875b2e3a28186123 +#define MESSAGE_BUFFER_NAME message_buffer_71d6a7b93c318d7e + +#endif // TVM_RUNTIME_HEXAGON_RPC_SIMULATOR_HEXAGON_SIM_PROTO_H_ diff --git a/src/runtime/hexagon/rpc/simulator/rpc_server.cc b/src/runtime/hexagon/rpc/simulator/rpc_server.cc new file mode 100644 index 0000000000000..715d30f81e373 --- /dev/null +++ b/src/runtime/hexagon/rpc/simulator/rpc_server.cc @@ -0,0 +1,319 @@ +/* + * 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 + +#include +#include +#include +#include +#include + +#include "../../../library_module.h" +#include "../../../minrpc/minrpc_server.h" +#include "../../hexagon/hexagon_common.h" +#include "hexagon_sim_proto.h" +#include "tvm/runtime/packed_func.h" +#include "tvm/runtime/registry.h" + +namespace tvm { +namespace runtime { +namespace hexagon { + +class stringbuf_with_remote_access : public std::stringbuf { + constexpr static size_t zero_count = 1024; + constexpr static char zeros[zero_count] = {}; + + public: + char* reserve_for_remote_write(size_t size) { + // Reserve memory in the put area by adding zeros to it. The put + // area will automatically resize itself as needed. This is needed + // for the simulator to be able to write to program's memory. + size_t remaining = size; + while (remaining >= zero_count) { + sputn(zeros, zero_count); + remaining -= zero_count; + } + sputn(zeros, remaining); + return pptr() - size; + } + + // Get area is the storage area with the data that will be read from + // the buffer. From the buffer's point of view, this is the area with + // the outgoing data. + char* get_area() { return gptr(); } + + // Adjust the buffer state after the data has been read by the remote + // end. This means discarding given amount of bytes from the get area. + size_t acknowledge_remote_read(size_t size) { + size_t remaining = size; + do { + auto bump = std::min(in_avail(), remaining); + setg(eback(), gptr() + bump, egptr()); + remaining -= bump; + // The setg will cause in_avail to become 0. At this point calling + // bumpc will either recaulate the get area pointers (if there are + // more characters left), or it will return eof(). + if (remaining == 0 || sbumpc() == traits_type::eof()) { + break; + } + remaining--; + } while (remaining > 0); + + // This will return 0 on success, non-zero if underflow occured. + return size - remaining; + } +}; + +const char stringbuf_with_remote_access::zeros[zero_count]; + +class SimulatorIOHandler { + public: + SimulatorIOHandler() = default; + + void MessageStart(size_t message_size_bytes) {} + void MessageDone() {} + + // Store data from the input buffer into 'buf'. + ssize_t PosixRead(uint8_t* buf, size_t read_len_bytes) { + auto char_buf = reinterpret_cast(buf); + return static_cast(inp_buffer_.sgetn(char_buf, read_len_bytes)); + } + + // Append 'write_len_bytes' starting at 'buf' to the output buffer. + ssize_t PosixWrite(const uint8_t* buf, size_t write_len_bytes) { + auto char_buf = reinterpret_cast(buf); + return static_cast(out_buffer_.sputn(char_buf, write_len_bytes)); + } + + void Close() {} + void Exit(int code) { exit(code); } + + char* PrepareToReceiveFromRemote(size_t nbytes) { + // Reserve space in the incoming buffer. + return inp_buffer_.reserve_for_remote_write(nbytes); + } + bool CompleteReceiveFromRemote(size_t nbytes) { + // Nothing to do. + return true; + } + char* PrepareToSendToRemote(size_t nbytes) { + // Return the pointer to the data coming out of the buffer. + return out_buffer_.get_area(); + } + bool CompleteSendToRemote(size_t nbytes) { + // Flush the read data from the outgoing buffer. + return out_buffer_.acknowledge_remote_read(nbytes) == 0; + } + + private: + stringbuf_with_remote_access inp_buffer_; // Data received from remote. + stringbuf_with_remote_access out_buffer_; // Data to be sent to remote. +}; + +// Internal allocator that redirects alloc to TVM's C API. +template +class SimulatorPageAllocator { + public: + using ArenaPageHeader = tvm::support::ArenaPageHeader; + + explicit SimulatorPageAllocator(TIOHandler* io) : io_(io) {} + + ArenaPageHeader* allocate(size_t min_size) { + size_t npages = ((min_size + kPageSize - 1) / kPageSize); + void* data; + + if (posix_memalign(&data, kPageAlign, npages * kPageSize) != 0) { + io_->Exit(static_cast(RPCServerStatus::kAllocError)); + } + + ArenaPageHeader* header = static_cast(data); + header->size = npages * kPageSize; + header->offset = sizeof(ArenaPageHeader); + return header; + } + + void deallocate(ArenaPageHeader* page) { free(page); } + + static const constexpr int kPageSize = 2 << 10; + static const constexpr int kPageAlign = kPageSize; + + private: + TIOHandler* io_; +}; + +class SimulatorRPCServer : public MinRPCServer { + using Base = MinRPCServer; + + public: + SimulatorRPCServer() : Base(&io_) {} + + char* PrepareToReceive(size_t nbytes) { + // Reserve receiving area. + return io_.PrepareToReceiveFromRemote(nbytes); + } + bool CompleteReceive(size_t nbytes) { + // Interpret the received message. + return io_.CompleteReceiveFromRemote(nbytes) && ProcessOnePacket(); + } + char* PrepareToSend(size_t nbytes) { + // Identify the beginning of the outgoing data. + return io_.PrepareToSendToRemote(nbytes); + } + bool CompleteSend(size_t nbytes) { + // Flush the read data. + return io_.CompleteSendToRemote(nbytes); + } + + private: + SimulatorIOHandler io_; +}; + +} // namespace hexagon +} // namespace runtime +} // namespace tvm + +// Handling communication with the simulator. +// +// Simulator can read and write the memory of the process, but the process has +// no way to find out that it's running in a simulation, nor can it actively +// communicate with the simulator. The RPC server must be entirely passive, +// allowing the simulator to perform data transfers. + +extern "C" { +// The names of these symbols will "pollute" the global namespace in the final +// binary, so make them unique (i.e. "more likely to be unique"). These names +// will be referenced in the host's code (the code controlling the simulator) +// as well, so to avoid repetition use human-friendly macros. +int DISPATCH_FUNCTION_NAME(void*) __attribute__((noinline)); +alignas(8) volatile Message MESSAGE_BUFFER_NAME; +} + +inline uint32_t va(const volatile void* p) { + static_assert(sizeof(p) == sizeof(uint32_t), "Pointers must be 32-bit long"); + return static_cast(reinterpret_cast(p)); +} + +// NOLINTNEXTLINE(runtime/references) +__attribute__((__unused__)) static std::string to_string(const volatile Message& m) { + std::stringstream out; + out << "{code="; + switch (m.code) { + case Message::kNone: + out << "kNone"; + break; + case Message::kAck: + out << "kAck"; + break; + case Message::kTerminate: + out << "kTerminate"; + break; + case Message::kReceiveStart: + out << "kReceiveStart"; + break; + case Message::kReceiveEnd: + out << "kReceiveEnd"; + break; + case Message::kSendStart: + out << "kSendStart"; + break; + case Message::kSendEnd: + out << "kSendEnd"; + break; + default: + out << "(" << m.code << ")"; + break; + } + out << ", len:" << m.len << ", va:" << std::hex << m.va << std::dec << '}'; + return out.str(); +} + +static inline void setmsg(volatile Message* m, uint32_t code, uint32_t len, uint32_t va) { + m->code = code; + m->len = len; + m->va = va; +} + +int DISPATCH_FUNCTION_NAME(void* serverp) { + static bool terminate = false; + if (terminate) { + return 1; + } + + Message msg; + setmsg(&msg, MESSAGE_BUFFER_NAME.code, MESSAGE_BUFFER_NAME.len, MESSAGE_BUFFER_NAME.va); + + auto& server = *reinterpret_cast(serverp); + + switch (msg.code) { + case Message::kReceiveStart: + assert(msg.va == Message::null_va); + setmsg(&MESSAGE_BUFFER_NAME, Message::kAck, msg.len, va(server.PrepareToReceive(msg.len))); + break; + case Message::kReceiveEnd: + server.CompleteReceive(msg.len); + setmsg(&MESSAGE_BUFFER_NAME, Message::kAck, 0u, Message::null_va); + break; + case Message::kSendStart: + assert(msg.va == Message::null_va); + setmsg(&MESSAGE_BUFFER_NAME, Message::kAck, msg.len, va(server.PrepareToSend(msg.len))); + break; + case Message::kSendEnd: + server.CompleteSend(msg.len); + setmsg(&MESSAGE_BUFFER_NAME, Message::kAck, 0u, Message::null_va); + break; + case Message::kTerminate: + // Don't exit immediately, send response to simulator first. + terminate = true; + setmsg(&MESSAGE_BUFFER_NAME, Message::kAck, 0u, Message::null_va); + break; + } + + return 0; +} + +int main() { + const auto* api_v2 = tvm::runtime::Registry::Get("device_api.hexagon.v2"); + ICHECK(api_v2 != nullptr); + tvm::runtime::Registry::Register("device_api.hexagon", true).set_body(*api_v2); + + tvm::runtime::hexagon::SimulatorRPCServer server; + + // Hand-encode user-instruction: + // r17:16 = userinsn(r17:16, r17:16, #0) + // 1100 1111 00010000 11010000 00010000 : cf10d010 + + asm volatile("r17:16 = combine(%0,%1); .long 0xcf10d010" + : // No outputs + : "r"(&MESSAGE_BUFFER_NAME), "r"(&DISPATCH_FUNCTION_NAME) + : "r16", "r17"); + + while (!DISPATCH_FUNCTION_NAME(&server)) { + // nothing + } + + return 0; +} + +TVM_REGISTER_GLOBAL("tvm.hexagon.load_module") + .set_body([](tvm::runtime::TVMArgs args, tvm::runtime::TVMRetValue* rv) { + std::string soname = args[0]; + tvm::ObjectPtr n = tvm::runtime::CreateDSOLibraryObject(soname); + *rv = CreateModuleFromLibrary(n, tvm::runtime::hexagon::WrapPackedFunc); + }); diff --git a/src/runtime/hexagon/rpc/simulator/session.cc b/src/runtime/hexagon/rpc/simulator/session.cc new file mode 100644 index 0000000000000..af5903330ceac --- /dev/null +++ b/src/runtime/hexagon/rpc/simulator/session.cc @@ -0,0 +1,1322 @@ +/* + * 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 +#include +#include +#include +// POSIX includes +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "../../../rpc/rpc_channel.h" +#include "../../../rpc/rpc_endpoint.h" +#include "../../../rpc/rpc_session.h" +#include "hexagon_sim_proto.h" + +#define CHECKED_CALL(func, ...) \ + do { \ + HEXAPI_Status s = sim_->func(__VA_ARGS__); \ + 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; + +namespace detail { + +// An "Optional" class, originally a replacement for llvm::Optional, then an +// extension of dmlc::optional to make it compatible with C++17's std::optional. +template +struct Optional : public dmlc::optional { + using dmlc::optional::optional; + using dmlc::optional::operator=; + Optional(const T& val) : dmlc::optional(val) {} // NOLINT(*) + + T* operator->() { return &this->operator*(); } + const T* operator->() const { return &this->operator*(); } +}; + +// Replacement for llvm::StringSwitch. +template +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; + } + ICHECK(static_cast(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) { + ICHECK(!static_cast(def_val)) << "default value already set"; + def_val = val; + return *this; + } + + private: + const std::string key; + std::map map; + Optional def_val; +}; + +using MaybeString = Optional; + +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. + +Optional to_int(const MaybeString& str) { + auto none = Optional(); + if (str.has_value()) { + try { + size_t pos; + int64_t val = std::stoll(*str, &pos, 0); + return pos == str->size() ? Optional(val) : none; + } catch (std::invalid_argument&) { + } + } + return none; +} + +Optional to_uint(const MaybeString& str) { + auto none = Optional(); + if (str.has_value()) { + try { + size_t pos; + uint64_t val = std::stoull(*str, &pos, 0); + return pos == str->size() ? Optional(val) : none; + } catch (std::invalid_argument&) { + } + } + return none; +} + +Optional to_float(const MaybeString& str) { + auto none = Optional(); + if (str.has_value()) { + try { + size_t pos; + float val = std::stof(*str, &pos); + return pos == str->size() ? Optional(val) : none; + } catch (std::invalid_argument&) { + } + } + return none; +} + +Optional to_bool(const MaybeString& str) { + auto none = Optional(); + if (auto num = to_int(str)) { + if (*num == 0) return false; + if (*num == 1) return true; + return none; + } + if (str) { + if (*str == "true" || *str == "TRUE") return true; + if (*str == "false" || *str == "FALSE") return false; + } + return none; +} + +template +using MaybeRange = Optional>; + +template Parse(const MaybeString&)> +MaybeRange to_range(const MaybeString& str) { + auto none = MaybeRange(); + 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 none; +} + +} // namespace detail + +class SimulatorRPCChannel final : public RPCChannel { + public: + SimulatorRPCChannel(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. + }; + + 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); + + detail::Optional GetCPU(const detail::MaybeString& cpu_str); + + // File name templates for mkstemps. +#define SUFFIX ".cfg" + static constexpr int template_length_ = strlen("temp-xxxx-XXXXXX" SUFFIX) + 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 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 = detail::Optional; + using MaybeUIntRange = std::pair; + + bool should_parse_next(const string_list& rest); + detail::Optional to_interval(const detail::MaybeString& str); + detail::Optional to_timingmode(const detail::MaybeString& str); + detail::Optional to_verbosemode(const detail::MaybeString& str); + detail::Optional to_nullptr(const detail::MaybeString& str); + + MaybeUIntRange ahb_, axi2_; + detail::Optional debug_port_; + + using OptionHandler = bool (SimulatorRPCChannel::*)(string_list&); + static std::map 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(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(s)); +} + +std::string SimulatorRPCChannel::Cpu_::str() const { + switch (c) { + case HEX_CPU_V65: + return "v65"; + case HEX_CPU_V66: + return "v66"; + case HEX_CPU_V68: + return "v68"; + case HEX_CPU_V69: + return "v69"; + default: + break; + } + return default_cpu_; +} + +SimulatorRPCChannel::SDKInfo_::SDKInfo_(const std::string& sdk_root, const std::string& cpu) + : root(sdk_root) { + qurt_root = root + "/rtos/qurt/compute" + 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 dir_names; + + DIR* dir = opendir((root + "/libs/run_main_on_hexagon/ship").c_str()); + 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) == cpu) { + dir_names.push_back(name); + } + } + closedir(dir); + 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(handle); + rpc->dispatch_v_ = RssV & ~0u; // ~0u is uint32_t + rpc->message_buffer_v_ = RssV >> 32; + + LOG(INFO) << "dispatch:" << reinterpret_cast(rpc->dispatch_v_) + << ", message buffer:" << reinterpret_cast(rpc->message_buffer_v_); + HEXAPI_Status s = rpc->sim_->SetBreakpoint(rpc->dispatch_v_); + ICHECK_EQ(s, HEX_STAT_SUCCESS) << self_name_ << ": SetBreakpoint failed with code " + << Status_{s}.str(); + return RssV; +} + +detail::Optional SimulatorRPCChannel::GetCPU(const detail::MaybeString& cpu_str) { + auto none = detail::Optional(); + if (!cpu_str) return none; + return detail::StringSwitch>(*cpu_str) + .Case("v65", HEX_CPU_V65) + .Case("v66", HEX_CPU_V66) + .Case("v68", HEX_CPU_V68) + .Case("v69", HEX_CPU_V69) + .Default(none); +} + +SimulatorRPCChannel::SimulatorRPCChannel(std::string args) { + const auto* api_v2 = tvm::runtime::Registry::Get("device_api.hexagon.v2"); + ICHECK(api_v2 != nullptr); + tvm::runtime::Registry::Register("device_api.hexagon", true).set_body(*api_v2); + + const char* sdk_root_env = std::getenv("HEXAGON_SDK_ROOT"); + ICHECK(sdk_root_env != nullptr) << "Please set HEXAGON_SDK_ROOT"; + const char* toolchain_env = std::getenv("HEXAGON_TOOLCHAIN"); + 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; + 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 { + LOG(FATAL) << "Invalid CPU name " << *target_str; + } + } + cpu_ = Cpu_{*maybe_cpu}.str(); + sim_ = std::make_unique(*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_); + ICHECK_GE(fd_osam, 0); + std::string osam_str = sdk.qurt_root + "/debugger/lnx64/qurt_model.so"; + 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_); + 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"; + 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 cmdline = sdk.runelf + " " + sdk.runmain + " -- 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) { + LOG(FATAL) << 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); + ICHECK_EQ(core, HEX_CORE_FINISHED); + + unlink(osam_file_); + unlink(cosim_file_); +} + +size_t SimulatorRPCChannel::Send(const void* data, size_t size) { + ICHECK(size <= std::numeric_limits::max()); + + Message reply_start = + SendMsg(Message::kReceiveStart, static_cast(size), Message::null_va); + ICHECK_EQ(reply_start.code, Message::kAck); + ICHECK_GE(reply_start.len, size); + ICHECK_NE(reply_start.va, Message::null_va); + + WriteToProcess(reply_start.va, data, size); + + Message reply_end = SendMsg(Message::kReceiveEnd, static_cast(size), reply_start.va); + ICHECK_EQ(reply_end.code, Message::kAck); + return size; +} + +size_t SimulatorRPCChannel::Recv(void* data, size_t size) { + ICHECK(size <= std::numeric_limits::max()); + + Message reply_start = SendMsg(Message::kSendStart, static_cast(size), Message::null_va); + ICHECK_EQ(reply_start.code, Message::kAck); + ICHECK_GE(reply_start.len, size); + ICHECK_NE(reply_start.va, Message::null_va); + + ReadFromProcess(data, reply_start.va, size); + + Message reply_end = SendMsg(Message::kSendEnd, static_cast(size), reply_start.va); + 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); + ICHECK_EQ(core, HEX_CORE_BREAKPOINT); + }; + + 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(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({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(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({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()) { + LOG(FATAL) << "Unrecognized simulator option: " << key; + // unreachable + } + 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 { + 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 { + 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(penalty) && static_cast(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(ratio); +} + +bool SimulatorRPCChannel::HandleAHBHighAddr(string_list& rest) { + auto addr = detail::to_uint(detail::pop_front(rest)); + ICHECK(addr) << self_name_ << ": invalid value for AHB high adddress"; + if (addr) { + ahb_.second = *addr; + } + return static_cast(addr); +} + +bool SimulatorRPCChannel::HandleAHBLowAddr(string_list& rest) { + auto addr = detail::to_uint(detail::pop_front(rest)); + ICHECK(addr) << self_name_ << ": invalid value for AHB low adddress"; + if (addr) { + ahb_.first = *addr; + } + return static_cast(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(penalty) && static_cast(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(ratio); +} + +bool SimulatorRPCChannel::HandleAXI2HighAddr(string_list& rest) { + auto addr = detail::to_uint(detail::pop_front(rest)); + ICHECK(addr) << self_name_ << ": invalid value for AXI2 high adddress"; + if (addr) { + axi2_.second = *addr; + } + return static_cast(addr); +} + +bool SimulatorRPCChannel::HandleAXI2LowAddr(string_list& rest) { + auto addr = detail::to_uint(detail::pop_front(rest)); + ICHECK(addr) << self_name_ << ": invalid value for AXI2 low adddress"; + if (addr) { + axi2_.first = *addr; + } + return static_cast(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(penalty) && static_cast(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(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(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(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(file); +} + +bool SimulatorRPCChannel::HandleCoreDump(string_list& rest) { + auto file = detail::pop_front(rest); + if (file) { + CHECKED_CALL(ConfigureCoreDump, file->c_str()); + } + return static_cast(file); +} + +bool SimulatorRPCChannel::HandleCosimFile(string_list& rest) { + auto file = detail::pop_front(rest); + if (file) { + CHECKED_CALL(ConfigureCosim, file->c_str()); + } + return static_cast(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(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(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(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(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(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(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(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(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(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(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(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(file); +} + +bool SimulatorRPCChannel::HandleNullPtr(string_list& rest) { + auto behavior = to_nullptr(detail::pop_front(rest)); + if (behavior) { + CHECKED_CALL(ConfigureNULLPointerBehavior, *behavior); + } + return static_cast(behavior); +} + +bool SimulatorRPCChannel::HandlePacketAnalyze(string_list& rest) { + auto file = detail::pop_front(rest); + if (file) { + CHECKED_CALL(ConfigurePacketAnalysis, file->c_str()); + } + return static_cast(file); +} + +bool SimulatorRPCChannel::HandlePCFilter(string_list& rest) { + auto range = detail::to_range(detail::pop_front(rest)); + if (range) { + CHECKED_CALL(ConfigurePCRangeFilter, range->first, range->second); + } + return static_cast(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(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(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(file); +} + +bool SimulatorRPCChannel::HandlePMUStatsFile(string_list& rest) { + auto file = detail::pop_front(rest); + if (file) { + CHECKED_CALL(ConfigurePmuStatisticsFile, file->c_str()); + } + return static_cast(file); +} + +bool SimulatorRPCChannel::HandleProfile(string_list& rest) { + auto path = detail::pop_front(rest); + if (path) { + CHECKED_CALL(ConfigureGProf, path->c_str()); + } + return static_cast(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(timezero); +} + +bool SimulatorRPCChannel::HandleQuiet(string_list& rest) { + sim_->VerboseMode(HEX_QUIET); + return true; +} + +bool SimulatorRPCChannel::HandleReconnect(string_list& rest) { + if (!debug_port_) { + LOG(FATAL) << "Reconnect error: --reconnect must be specified " + "AFTER --gdbserv "; + } + 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(file); +} + +bool SimulatorRPCChannel::HandleSimErr(string_list& rest) { + auto file = detail::pop_front(rest); + if (file) { + CHECKED_CALL(ConfigureSimStderr, file->c_str()); + } + return static_cast(file); +} + +bool SimulatorRPCChannel::HandleSimIn(string_list& rest) { + auto file = detail::pop_front(rest); + if (file) { + CHECKED_CALL(ConfigureSimStdin, file->c_str()); + } + return static_cast(file); +} + +bool SimulatorRPCChannel::HandleSimOut(string_list& rest) { + auto file = detail::pop_front(rest); + if (file) { + CHECKED_CALL(ConfigureSimStdout, file->c_str()); + } + return static_cast(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(base) && static_cast(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(file); +} + +bool SimulatorRPCChannel::HandleStatsFile(string_list& rest) { + auto file = detail::pop_front(rest); + if (file) { + CHECKED_CALL(ConfigureStatisticsFile, file->c_str()); + } + return static_cast(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(base); +} + +bool SimulatorRPCChannel::HandleSymFile(string_list& rest) { + auto file = detail::pop_front(rest); + if (file) { + CHECKED_CALL(AddSymbolFile, file->c_str()); + } + return static_cast(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(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(addr); +} + +bool SimulatorRPCChannel::HandleTimeFilterNS(string_list& rest) { + auto range = detail::to_range(detail::pop_front(rest)); + if (range) { + CHECKED_CALL(ConfigureTimeRangeFilter, range->first, HEX_NANOSEC, range->second, HEX_NANOSEC); + } + return static_cast(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(file); +} + +bool SimulatorRPCChannel::HandleUseFS(string_list& rest) { + auto file = detail::pop_front(rest); + if (file) { + CHECKED_CALL(ConfigureARFilesystem, &(*file)[0]); + } + return static_cast(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(enable); +} + +bool SimulatorRPCChannel::HandleVerbose(string_list& rest) { + auto mode = to_verbosemode(detail::pop_front(rest)); + if (mode) { + sim_->VerboseMode(*mode); + } + return static_cast(mode); +} + +bool SimulatorRPCChannel::should_parse_next(const string_list& rest) { + if (auto str = detail::front(rest)) { + return str->empty() || str->front() != '-'; + } + return false; +} + +detail::Optional SimulatorRPCChannel::to_interval(const detail::MaybeString& str) { + auto none = detail::Optional(); + if (!str) return none; + + 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(*val); + } + } + + return detail::StringSwitch>(*str) + .Case("MILLISEC", HEX_MILLISEC) + .Case("MICROSEC", HEX_MICROSEC) + .Case("NANOSEC", HEX_NANOSEC) + .Case("PICOSEC", HEX_PICOSEC) + .Case("PCYCLE", HEX_PCYCLE) + .Default(none); +} + +detail::Optional SimulatorRPCChannel::to_timingmode( + const detail::MaybeString& str) { + auto none = detail::Optional(); + if (!str) return none; + + 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(*val); + } + } + + return detail::StringSwitch>(*str) + .Case("NOTIMING", HEX_NOTIMING) + .Case("TIMING_NODBC", HEX_TIMING_NODBC) + .Case("TIMING", HEX_TIMING) + .Case("TIMING_COHERENCY", HEX_TIMING_COHERENCY) + .Default(none); +} + +detail::Optional SimulatorRPCChannel::to_verbosemode( + const detail::MaybeString& str) { + auto none = detail::Optional(); + if (!str) return none; + + 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(*val); + } + } + + return detail::StringSwitch>(*str) + .Case("SILENT", HEX_SILENT) + .Case("QUIET", HEX_QUIET) + .Case("NORMAL", HEX_NORMAL) + .Case("VERBOSE", HEX_VERBOSE) + .Case("REALLY_VERBOSE", HEX_REALLY_VERBOSE) + .Default(none); +} + +detail::Optional SimulatorRPCChannel::to_nullptr(const detail::MaybeString& str) { + auto none = detail::Optional(); + if (!str) return none; + + 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(*val); + } + } + + return detail::StringSwitch>(*str) + .Case("IGNORE", HEX_NULLPTR_IGNORE) + .Case("WARN", HEX_NULLPTR_WARN) + .Case("FATAL", HEX_NULLPTR_FATAL) + .Case("PCZERO", HEX_NULLPTR_PCZERO) + .Default(none); +} + +TVM_REGISTER_GLOBAL("tvm.contrib.hexagon.create_hexagon_session") + .set_body([](TVMArgs args, TVMRetValue* rv) { + std::string session_name = args[0]; + // For target, the second parameter is remote_stack_size_bytes, ignore it. + std::string sim_args = args[2]; + auto channel = std::make_unique(sim_args); + std::shared_ptr endpoint = + RPCEndpoint::Create(std::move(channel), session_name, "", nullptr); + std::shared_ptr session = CreateClientSession(endpoint); + *rv = CreateRPCSessionModule(session); + }); + +} // namespace hexagon +} // namespace runtime +} // namespace tvm