blob: f6b694cb7cb3da2a3597d23c83b93a1a37bc3429 [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.
*/
/*!
* \file codegen.cc
* \brief Common utilities to generated C style code.
*/
#include <dmlc/memory_io.h>
#include <tvm/ir/module.h>
#include <tvm/runtime/c_runtime_api.h>
#include <tvm/runtime/module.h>
#include <tvm/runtime/registry.h>
#include <tvm/target/codegen.h>
#include <tvm/target/target.h>
#include <tvm/tir/function.h>
#include <tvm/tir/transform.h>
#include <cstdint>
#include <cstring>
#include <sstream>
#include <unordered_set>
#include <vector>
namespace tvm {
namespace codegen {
runtime::Module Build(IRModule mod, Target target) {
if (transform::PassContext::Current()
->GetConfig<Bool>("tir.disable_assert", Bool(false))
.value()) {
mod = tir::transform::SkipAssert()(mod);
}
auto target_attr_map = tvm::TargetKind::GetAttrMap<FTVMTIRToRuntime>("TIRToRuntime");
if (target_attr_map.count(target->kind)) {
return target_attr_map[target->kind](mod, target);
}
// the build function.
std::string build_f_name = "target.build." + target->kind->name;
const PackedFunc* bf = runtime::Registry::Get(build_f_name);
ICHECK(bf != nullptr) << build_f_name << " is not enabled";
return (*bf)(mod, target);
}
/*! \brief Helper class to serialize module */
class ModuleSerializer {
public:
explicit ModuleSerializer(runtime::Module mod) : mod_(mod) { Init(); }
void SerializeModule(dmlc::Stream* stream) {
// Only have one DSO module and it is in the root, then
// we will not produce import_tree_.
bool has_import_tree = true;
if (mod_->IsDSOExportable() && mod_->imports().empty()) {
has_import_tree = false;
}
uint64_t sz = 0;
if (has_import_tree) {
// we will append one key for _import_tree
// The layout is the same as before: binary_size, key, logic, key, logic...
sz = mod_group_vec_.size() + 1;
} else {
// Keep the old behaviour
sz = mod_->imports().size();
}
stream->Write(sz);
for (const auto& group : mod_group_vec_) {
ICHECK_NE(group.size(), 0) << "Every allocated group must have at least one module";
if (!group[0]->IsDSOExportable()) {
ICHECK_EQ(group.size(), 1U) << "Non DSO module is never merged";
std::string mod_type_key = group[0]->type_key();
stream->Write(mod_type_key);
group[0]->SaveToBinary(stream);
} else {
// DSOExportable: do not need binary
if (has_import_tree) {
std::string mod_type_key = "_lib";
stream->Write(mod_type_key);
}
}
}
// Write _import_tree key if we have
if (has_import_tree) {
std::string import_key = "_import_tree";
stream->Write(import_key);
stream->Write(import_tree_row_ptr_);
stream->Write(import_tree_child_indices_);
}
}
private:
void Init() {
CreateModuleIndex();
CreateImportTree();
}
// invariance: root module is always at location 0.
// The module order is collected via DFS
// This function merges all the DSO exportable module into
// a single one as this is also what happens in the final hierachy
void CreateModuleIndex() {
std::unordered_set<const runtime::ModuleNode*> visited{mod_.operator->()};
std::vector<runtime::ModuleNode*> stack{mod_.operator->()};
uint64_t module_index = 0;
auto fpush_imports_to_stack = [&](runtime::ModuleNode* node) {
for (runtime::Module m : node->imports()) {
runtime::ModuleNode* next = m.operator->();
if (visited.count(next) == 0) {
visited.insert(next);
stack.push_back(next);
}
}
};
std::vector<runtime::ModuleNode*> dso_exportable_boundary;
// Create module index that merges all dso module into a single group.
//
// Do a two phase visit, to ensure dso module's index
// is always bigger than a parent of any dso module
// and smaller than children of any dso module.
//
// Error will be raised in CreateImportTree
// if merging dso module causes a cycle in the import tree
// Phase 0: only expand non-dso-module and record the boundary.
while (!stack.empty()) {
runtime::ModuleNode* n = stack.back();
stack.pop_back();
if (n->IsDSOExportable()) {
// do not recursively expand dso modules
// we will expand in phase 1
dso_exportable_boundary.emplace_back(n);
} else {
// expand the non-dso modules
mod2index_[n] = module_index++;
mod_group_vec_.emplace_back(std::vector<runtime::ModuleNode*>({n}));
fpush_imports_to_stack(n);
}
}
if (dso_exportable_boundary.size() == 0) return;
// create the slot for dso exportable modules
// This index is chosen so that all the DSO's parents are
// allocated before this index, and children will be allocated after
uint64_t dso_module_index = module_index++;
mod_group_vec_.emplace_back(std::vector<runtime::ModuleNode*>());
// restart visiting the stack using elements in dso exportable boundary
stack = std::move(dso_exportable_boundary);
// Phase 1: expand the children of dso modules.
while (!stack.empty()) {
runtime::ModuleNode* n = stack.back();
stack.pop_back();
if (n->IsDSOExportable()) {
mod_group_vec_[dso_module_index].emplace_back(n);
mod2index_[n] = dso_module_index;
} else {
mod2index_[n] = module_index++;
mod_group_vec_.emplace_back(std::vector<runtime::ModuleNode*>({n}));
}
fpush_imports_to_stack(n);
}
}
void CreateImportTree() {
std::vector<int64_t> child_indices;
for (size_t parent_index = 0; parent_index < mod_group_vec_.size(); ++parent_index) {
child_indices.clear();
for (const auto* m : mod_group_vec_[parent_index]) {
for (runtime::Module im : m->imports()) {
uint64_t mod_index = mod2index_.at(im.operator->());
// skip cycle when dso modules are merged together
if (mod_index != parent_index) {
child_indices.emplace_back(mod_index);
}
}
}
// sort and unique the merged indices
std::sort(child_indices.begin(), child_indices.end());
auto unique_end = std::unique(child_indices.begin(), child_indices.end());
// Check cycles due to merging dso exportable modules.
if (child_indices.size() != 0) {
// The index is supposed to follow the topological order.
CHECK_LT(parent_index, child_indices[0])
<< "RuntimeError: Cannot export due to multiple dso-exportables "
<< "that cannot be merged without creating a cycle in the import tree. "
<< "Related module keys: parent=" << mod_group_vec_[parent_index][0]->type_key()
<< ", child=" << mod_group_vec_[child_indices[0]][0]->type_key();
}
// insert the child indices
import_tree_child_indices_.insert(import_tree_child_indices_.end(), child_indices.begin(),
unique_end);
import_tree_row_ptr_.push_back(import_tree_child_indices_.size());
}
}
runtime::Module mod_;
// construct module to index
std::unordered_map<runtime::ModuleNode*, size_t> mod2index_;
// index -> module group
std::vector<std::vector<runtime::ModuleNode*>> mod_group_vec_;
std::vector<uint64_t> import_tree_row_ptr_{0};
std::vector<uint64_t> import_tree_child_indices_;
};
namespace {
std::string SerializeModule(const runtime::Module& mod) {
std::string bin;
dmlc::MemoryStringStream ms(&bin);
dmlc::Stream* stream = &ms;
ModuleSerializer module_serializer(mod);
module_serializer.SerializeModule(stream);
return bin;
}
} // namespace
std::string PackImportsToC(const runtime::Module& mod, bool system_lib) {
std::string bin = SerializeModule(mod);
// translate to C program
std::ostringstream os;
os << "#ifdef _WIN32\n"
<< "#define TVM_EXPORT __declspec(dllexport)\n"
<< "#else\n"
<< "#define TVM_EXPORT\n"
<< "#endif\n";
os << "#ifdef __cplusplus\n"
<< "extern \"C\" {\n"
<< "#endif\n";
os << "TVM_EXPORT extern const unsigned char " << runtime::symbol::tvm_dev_mblob << "[];\n";
uint64_t nbytes = bin.length();
os << "const unsigned char " << runtime::symbol::tvm_dev_mblob << "["
<< bin.length() + sizeof(nbytes) << "] = {\n ";
os << std::hex;
size_t nunit = 80 / 4;
for (size_t i = 0; i < sizeof(nbytes); ++i) {
// sperators
if (i != 0) {
os << ",";
}
os << "0x" << ((nbytes >> (i * 8)) & 0xffUL);
}
for (size_t i = 0; i < bin.length(); ++i) {
// sperators
if ((i + sizeof(nbytes)) % nunit == 0) {
os << ",\n ";
} else {
os << ",";
}
int c = bin[i];
os << "0x" << (c & 0xff);
}
os << "\n};\n";
if (system_lib) {
os << "extern int TVMBackendRegisterSystemLibSymbol(const char*, void*);\n";
os << "static int " << runtime::symbol::tvm_dev_mblob << "_reg_ = "
<< "TVMBackendRegisterSystemLibSymbol(\"" << runtime::symbol::tvm_dev_mblob << "\", (void*)"
<< runtime::symbol::tvm_dev_mblob << ");\n";
}
os << "#ifdef __cplusplus\n"
<< "}\n"
<< "#endif\n";
return os.str();
}
runtime::Module PackImportsToLLVM(const runtime::Module& mod, bool system_lib,
const std::string& llvm_target_string) {
std::string bin = SerializeModule(mod);
uint64_t nbytes = bin.length();
std::string header;
for (size_t i = 0; i < sizeof(nbytes); ++i) {
header.push_back(((nbytes >> (i * 8)) & 0xffUL));
}
std::string blob = header + bin;
TVMByteArray blob_byte_array;
blob_byte_array.size = blob.length();
blob_byte_array.data = blob.data();
// Call codegen_blob to generate LLVM module
std::string codegen_f_name = "codegen.codegen_blob";
// the codegen function.
const PackedFunc* codegen_f = runtime::Registry::Get(codegen_f_name);
ICHECK(codegen_f != nullptr) << "codegen.codegen_blob is not presented.";
return (*codegen_f)(blob_byte_array, system_lib, llvm_target_string);
}
TVM_REGISTER_GLOBAL("target.Build").set_body_typed(Build);
// Export two auxiliary function to the runtime namespace.
TVM_REGISTER_GLOBAL("runtime.ModulePackImportsToC").set_body_typed(PackImportsToC);
TVM_REGISTER_GLOBAL("runtime.ModulePackImportsToLLVM").set_body_typed(PackImportsToLLVM);
} // namespace codegen
} // namespace tvm