blob: 119e999276f84318241b5a1f8bef567572b2aa9a [file] [log] [blame]
/*
* 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 <HAP_farf.h>
#include <dlfcn.h>
#include <algorithm>
#include <cassert>
#include <cstdlib>
#include <sstream>
#include <string>
#include "../../../library_module.h"
#include "../../../minrpc/minrpc_server.h"
#include "../../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<size_t>(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 occurred.
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<decltype(inp_buffer_)::char_type*>(buf);
return static_cast<ssize_t>(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<const decltype(out_buffer_)::char_type*>(buf);
return static_cast<ssize_t>(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 <typename TIOHandler>
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<int>(RPCServerStatus::kAllocError));
}
ArenaPageHeader* header = static_cast<ArenaPageHeader*>(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<SimulatorIOHandler, SimulatorPageAllocator> {
using Base = MinRPCServer<SimulatorIOHandler, SimulatorPageAllocator>;
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<uint32_t>(reinterpret_cast<uintptr_t>(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 << "<unknown>(" << 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<tvm::runtime::hexagon::SimulatorRPCServer*>(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(int argc, char* argv[]) {
// Load C++RT and ourselves as "global" to make all the symbols defined
// there be visible to any subsequent libraries loaded via dlopen.
void* cxx_abi = dlopen("libc++abi.so", RTLD_GLOBAL);
ICHECK(cxx_abi != nullptr);
void* cxx = dlopen("libc++.so", RTLD_GLOBAL);
ICHECK(cxx != nullptr);
void* self = dlopen(argv[0], RTLD_GLOBAL);
ICHECK(self != nullptr);
const auto* api = tvm::runtime::Registry::Get("device_api.hexagon");
ICHECK(api != nullptr);
tvm::runtime::Registry::Register("device_api.cpu", true).set_body(*api);
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
}
dlclose(self);
dlclose(cxx);
dlclose(cxx_abi);
return 0;
}
// Workaround for missing functions in 8.5.08
extern "C" {
__attribute__((weak)) void _Get_eh_data() {}
__attribute__((weak)) void _Parse_fde_instr() {}
}
TVM_REGISTER_GLOBAL("tvm.hexagon.load_module")
.set_body([](tvm::runtime::TVMArgs args, tvm::runtime::TVMRetValue* rv) {
std::string soname = args[0];
tvm::ObjectPtr<tvm::runtime::Library> n = tvm::runtime::CreateDSOLibraryObject(soname);
*rv = CreateModuleFromLibrary(n);
});