/* * 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. */ /*! * \file rpc_env.cc * \brief Server environment of the RPC. */ #include #include #ifndef _WIN32 #include #include #include #else #include #include namespace { int mkdir(const char* path, int /* ignored */) { return _mkdir(path); } } // namespace #endif #include #include #include #include #include #include "../../src/support/utils.h" #include "rpc_env.h" namespace { std::string GenerateUntarCommand(const std::string& tar_file, const std::string& output_dir) { std::string untar_cmd; untar_cmd.reserve(512); #if defined(__linux__) || defined(__ANDROID__) || defined(__APPLE__) untar_cmd += "tar -C "; untar_cmd += output_dir; untar_cmd += " -zxf "; untar_cmd += tar_file; #elif defined(_WIN32) untar_cmd += "python -m tarfile -e "; untar_cmd += tar_file; untar_cmd += " "; untar_cmd += output_dir; #endif return untar_cmd; } } // Anonymous namespace namespace tvm { namespace runtime { /*! * \brief CleanDir Removes the files from the directory * \param dirname THe name of the directory */ void CleanDir(const std::string& dirname); /*! * \brief buld a shared library if necessary * * This function will automatically call * cc.create_shared if the path is in format .o or .tar * High level handling for .o and .tar file. * We support this to be consistent with RPC module load. * \param file_in The input file path. * * \return The name of the shared library. */ std::string BuildSharedLibrary(std::string file_in); RPCEnv::RPCEnv(const std::string& wd) { if (wd != "") { base_ = wd + "/.cache"; mkdir(wd.c_str(), 0777); mkdir(base_.c_str(), 0777); } else { #if defined(ANDROID) || defined(__ANDROID__) char cwd[PATH_MAX]; auto cmdline = fopen("/proc/self/cmdline", "r"); fread(cwd, 1, sizeof(cwd), cmdline); fclose(cmdline); std::string android_base_ = "/data/data/" + std::string(cwd) + "/cache"; struct stat statbuf; // Check if application data directory exist. If not exist, usually means we run tvm_rpc from // adb shell terminal. if (stat(android_base_.data(), &statbuf) == -1 || !S_ISDIR(statbuf.st_mode)) { // Tmp directory is always writable for 'shell' user. android_base_ = "/data/local/tmp"; } base_ = android_base_ + "/rpc"; #elif !defined(_WIN32) char cwd[PATH_MAX]; if (getcwd(cwd, sizeof(cwd))) { base_ = std::string(cwd) + "/rpc"; } else { base_ = "./rpc"; } #else base_ = "./rpc"; #endif mkdir(base_.c_str(), 0777); } TVM_REGISTER_GLOBAL("tvm.rpc.server.workpath").set_body([this](TVMArgs args, TVMRetValue* rv) { *rv = this->GetPath(args[0]); }); TVM_REGISTER_GLOBAL("tvm.rpc.server.load_module").set_body([this](TVMArgs args, TVMRetValue* rv) { std::string file_name = this->GetPath(args[0]); file_name = BuildSharedLibrary(file_name); *rv = Module::LoadFromFile(file_name, ""); LOG(INFO) << "Load module from " << file_name << " ..."; }); TVM_REGISTER_GLOBAL("tvm.rpc.server.download_linked_module") .set_body([this](TVMArgs args, TVMRetValue* rv) { std::string file_name = this->GetPath(args[0]); file_name = BuildSharedLibrary(file_name); std::string bin; std::ifstream fs(file_name, std::ios::in | std::ios::binary); ICHECK(!fs.fail()) << "Cannot open " << file_name; fs.seekg(0, std::ios::end); size_t size = static_cast(fs.tellg()); fs.seekg(0, std::ios::beg); bin.resize(size); fs.read(dmlc::BeginPtr(bin), size); TVMByteArray binarr; binarr.data = bin.data(); binarr.size = bin.length(); *rv = binarr; LOG(INFO) << "Send linked module " << file_name << " to client"; }); } /*! * \brief GetPath To get the work path from packed function * \param file_name The file name * \return The full path of file. */ std::string RPCEnv::GetPath(const std::string& file_name) const { // we assume file_name has "/" means file_name is the exact path // and does not create /.rpc/ return file_name.find('/') != std::string::npos ? file_name : base_ + "/" + file_name; } /*! * \brief Remove The RPC Environment cleanup function */ void RPCEnv::CleanUp() const { CleanDir(base_); const int ret = rmdir(base_.c_str()); if (ret != 0) { LOG(WARNING) << "Remove directory " << base_ << " failed"; } } /*! * \brief ListDir get the list of files in a directory * \param dirname The root directory name * \return vector Files in directory. */ std::vector ListDir(const std::string& dirname) { std::vector vec; #ifndef _WIN32 DIR* dp = opendir(dirname.c_str()); if (dp == nullptr) { int errsv = errno; LOG(FATAL) << "ListDir " << dirname << " error: " << strerror(errsv); } dirent* d; while ((d = readdir(dp)) != nullptr) { std::string filename = d->d_name; if (filename != "." && filename != "..") { std::string f = dirname; if (f[f.length() - 1] != '/') { f += '/'; } f += d->d_name; vec.push_back(f); } } closedir(dp); #elif defined(_WIN32) WIN32_FIND_DATAA fd; const std::string pattern = dirname + "/*"; HANDLE handle = FindFirstFileA(pattern.c_str(), &fd); if (handle == INVALID_HANDLE_VALUE) { const int errsv = GetLastError(); LOG(FATAL) << "ListDir " << dirname << " error: " << strerror(errsv); } do { std::string filename = fd.cFileName; if (filename != "." && filename != "..") { std::string f = dirname; if (f[f.length() - 1] != '/') { f += '/'; } f += filename; vec.push_back(f); } } while (FindNextFileA(handle, &fd)); FindClose(handle); #else LOG(FATAL) << "Operating system not supported"; #endif return vec; } #if defined(__linux__) || defined(__ANDROID__) || defined(__APPLE__) /*! * \brief LinuxShared Creates a linux shared library * \param output The output file name * \param files The files for building * \param options The compiler options * \param cc The compiler */ void LinuxShared(const std::string output, const std::vector& files, std::string options = "", std::string cc = "g++") { std::string cmd = cc; cmd += " -shared -fPIC "; cmd += " -o " + output; for (auto f = files.begin(); f != files.end(); ++f) { cmd += " " + *f; } cmd += " " + options; std::string err_msg; auto executed_status = support::Execute(cmd, &err_msg); if (executed_status) { LOG(FATAL) << err_msg; } } #endif #ifdef _WIN32 /*! * \brief WindowsShared Creates a Windows shared library * \param output The output file name * \param files The files for building * \param options The compiler options * \param cc The compiler */ void WindowsShared(const std::string& output, const std::vector& files, const std::string& options = "", const std::string& cc = "clang") { std::string cmd = cc; cmd += " -O2 -flto=full -fuse-ld=lld-link -shared "; cmd += " -o " + output; for (const auto& file : files) { cmd += " " + file; } cmd += " " + options; std::string err_msg; const auto executed_status = support::Execute(cmd, &err_msg); if (executed_status) { LOG(FATAL) << err_msg; } } #endif /*! * \brief CreateShared Creates a shared library * \param output The output file name * \param files The files for building */ void CreateShared(const std::string& output, const std::vector& files) { #if defined(__linux__) || defined(__ANDROID__) || defined(__APPLE__) LinuxShared(output, files); #elif defined(_WIN32) WindowsShared(output, files); #else LOG(FATAL) << "Operating system not supported"; #endif } std::string BuildSharedLibrary(std::string file) { if (support::EndsWith(file, ".so") || support::EndsWith(file, ".dll") || support::EndsWith(file, ".dylib")) { return file; } std::string file_name = file + ".so"; if (support::EndsWith(file, ".o")) { CreateShared(file_name, {file}); } else if (support::EndsWith(file, ".tar")) { const std::string tmp_dir = "./rpc/tmp/"; mkdir(tmp_dir.c_str(), 0777); const std::string cmd = GenerateUntarCommand(file, tmp_dir); std::string err_msg; const int executed_status = support::Execute(cmd, &err_msg); if (executed_status) { LOG(FATAL) << err_msg; } CreateShared(file_name, ListDir(tmp_dir)); CleanDir(tmp_dir); (void)rmdir(tmp_dir.c_str()); } else { file_name = file; } return file_name; } /*! * \brief CleanDir Removes the files from the directory * \param dirname The name of the directory */ void CleanDir(const std::string& dirname) { auto files = ListDir(dirname); for (const auto& filename : files) { std::string file_path = dirname + "/"; file_path += filename; const int ret = std::remove(filename.c_str()); if (ret != 0) { LOG(WARNING) << "Remove file " << filename << " failed"; } } } } // namespace runtime } // namespace tvm