/*
 * 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 llvm_module.cc
 * \brief LLVM runtime module for TVM
 */
#ifdef TVM_LLVM_VERSION

#include <llvm/ADT/SmallString.h>
#include <llvm/ADT/StringRef.h>
#include <llvm/Bitcode/BitcodeWriter.h>
#include <llvm/ExecutionEngine/ExecutionEngine.h>
#include <llvm/ExecutionEngine/MCJIT.h>
#include <llvm/ExecutionEngine/Orc/LLJIT.h>
#include <llvm/ExecutionEngine/Orc/ObjectLinkingLayer.h>
#include <tvm/ffi/reflection/registry.h>
#include <tvm/support/io.h>
#if _WIN32
#include <llvm/ExecutionEngine/Orc/RTDyldObjectLinkingLayer.h>
#include <llvm/ExecutionEngine/SectionMemoryManager.h>
#endif
#include <llvm/IR/DataLayout.h>
#include <llvm/IR/Function.h>
#include <llvm/IR/Intrinsics.h>
#include <llvm/IR/LLVMContext.h>
#include <llvm/IR/LegacyPassManager.h>
#include <llvm/IR/MDBuilder.h>
#include <llvm/IR/Metadata.h>
#include <llvm/IR/Module.h>
#include <llvm/IRReader/IRReader.h>
#include <llvm/Support/FileSystem.h>
#if TVM_LLVM_VERSION >= 180
#include <llvm/TargetParser/Host.h>
#else
#include <llvm/Support/Host.h>
#endif
#include <llvm/Support/SourceMgr.h>
#include <llvm/Support/raw_ostream.h>
#include <llvm/Target/TargetMachine.h>
#include <llvm/Target/TargetOptions.h>
#include <llvm/Transforms/Utils/Cloning.h>
#include <tvm/ffi/cast.h>
#include <tvm/ffi/container/array.h>
#include <tvm/ffi/extra/module.h>
#include <tvm/ffi/function.h>
#include <tvm/ffi/string.h>
#include <tvm/ir/module.h>
#include <tvm/ir/with_context.h>
#include <tvm/runtime/logging.h>
#include <tvm/target/codegen.h>
#include <tvm/target/target.h>

#include <algorithm>
#include <memory>
#include <mutex>
#include <sstream>
#include <string>
#include <system_error>
#include <utility>
#include <vector>

#include "../../runtime/file_utils.h"
#include "codegen_blob.h"
#include "codegen_cpu.h"
#include "codegen_llvm.h"
#include "llvm_instance.h"

namespace tvm {
namespace codegen {

using ffi::Any;
using ffi::Function;
using ffi::PackedArgs;

class LLVMModuleNode final : public ffi::ModuleObj {
 public:
  ~LLVMModuleNode();

  const char* kind() const final { return "llvm"; }

  ffi::Optional<ffi::Function> GetFunction(const ffi::String& name) final;

  /*! \brief Get the property of the runtime module .*/
  // TODO(tvm-team): Make it serializable
  int GetPropertyMask() const override {
    return ffi::Module::kRunnable | ffi::Module::kCompilationExportable;
  }

  void WriteToFile(const ffi::String& file_name, const ffi::String& format) const final;
  ffi::Bytes SaveToBytes() const final;
  ffi::String InspectSource(const ffi::String& format) const final;

  void Init(const IRModule& mod, const Target& target);
  void Init(std::unique_ptr<llvm::Module> module, std::unique_ptr<LLVMInstance> llvm_instance);
  void LoadIR(const std::string& file_name);

  bool ImplementsFunction(const ffi::String& name) final;

  void SetJITEngine(const std::string& jit_engine) { jit_engine_ = jit_engine; }

 private:
  void InitMCJIT();
  void InitORCJIT();
  bool IsCompatibleWithHost(const llvm::TargetMachine* tm) const;
  void* GetGlobalAddr(const std::string& name, const LLVMTarget& llvm_target) const;
  void* GetFunctionAddr(const std::string& name, const LLVMTarget& llvm_target) const;

  // The LLVM scope object.
  std::unique_ptr<LLVMInstance> llvm_instance_;
  // JIT lock
  std::mutex mutex_;
  // jit execution engines
  llvm::ExecutionEngine* mcjit_ee_{nullptr};
  std::unique_ptr<llvm::orc::LLJIT> orcjit_ee_{nullptr};
  // The raw pointer to the module.
  llvm::Module* module_{nullptr};
  // The unique_ptr owning the module. This becomes empty once JIT has been initialized
  // (EngineBuilder takes ownership of the module).
  std::unique_ptr<llvm::Module> module_owning_ptr_;
  /* \brief names of the external functions declared in this module */
  ffi::Array<ffi::String> function_names_;
  std::string jit_engine_;
};

LLVMModuleNode::~LLVMModuleNode() {
  if (mcjit_ee_ != nullptr) {
    mcjit_ee_->runStaticConstructorsDestructors(true);
    delete mcjit_ee_;
  }
  if (orcjit_ee_ != nullptr) {
    auto dtors = llvm::orc::getDestructors(*module_);
    auto dtorRunner = std::make_unique<llvm::orc::CtorDtorRunner>(orcjit_ee_->getMainJITDylib());
    dtorRunner->add(dtors);
    auto err = dtorRunner->run();
    TVM_FFI_ICHECK(!err) << llvm::toString(std::move(err));
    orcjit_ee_.reset();
  }
  module_owning_ptr_.reset();
}

ffi::Optional<ffi::Function> LLVMModuleNode::GetFunction(const ffi::String& name) {
  ffi::ObjectPtr<ffi::Object> sptr_to_self = ffi::GetObjectPtr<ffi::Object>(this);
  if (name == "__tvm_is_system_module") {
    bool flag = (module_->getFunction("__tvm_module_startup") != nullptr);
    return ffi::Function([flag](ffi::PackedArgs args, ffi::Any* rv) { *rv = flag; });
  } else if (name == "__tvm_get_system_lib_prefix") {
    return ffi::Function([this](ffi::PackedArgs args, ffi::Any* rv) {
      auto* md = module_->getModuleFlag("tvm_system_lib_prefix");
      if (md != nullptr) {
        *rv = llvm::cast<llvm::MDString>(md)->getString().str();
      } else {
        *rv = nullptr;
      }
    });
  } else if (name == "get_func_names") {
    return ffi::Function(
        [sptr_to_self, this](ffi::PackedArgs args, ffi::Any* rv) { *rv = this->function_names_; });
  } else if (name == "get_symbol") {
    return std::nullopt;
  } else if (name == "get_const_vars") {
    return std::nullopt;
  } else if (name == "_get_target_string") {
    std::string target_string = LLVMTarget::GetTargetMetadata(*module_);
    return ffi::Function(
        [target_string](ffi::PackedArgs args, ffi::Any* rv) { *rv = target_string; });
  }
  TVM_FFI_ICHECK(jit_engine_.size()) << "JIT engine type is missing";
  if ((jit_engine_ == "mcjit") && (mcjit_ee_ == nullptr)) InitMCJIT();
  if ((jit_engine_ == "orcjit") && (orcjit_ee_ == nullptr)) InitORCJIT();

  std::lock_guard<std::mutex> lock(mutex_);

  TVMFFISafeCallType faddr;
  With<LLVMTarget> llvm_target(*llvm_instance_, LLVMTarget::GetTargetMetadata(*module_));
  ffi::String name_with_prefix = ffi::symbol::tvm_ffi_symbol_prefix + name;
  faddr = reinterpret_cast<TVMFFISafeCallType>(GetFunctionAddr(name_with_prefix, *llvm_target));
  if (faddr == nullptr) return std::nullopt;
  ffi::Module self_strong_ref = ffi::GetRef<ffi::Module>(this);
  return ffi::Function::FromPacked([faddr, self_strong_ref](ffi::PackedArgs args, ffi::Any* rv) {
    TVM_FFI_ICHECK_LT(rv->type_index(), ffi::TypeIndex::kTVMFFIStaticObjectBegin);
    TVM_FFI_CHECK_SAFE_CALL((*faddr)(nullptr, reinterpret_cast<const TVMFFIAny*>(args.data()),
                                     args.size(), reinterpret_cast<TVMFFIAny*>(rv)));
  });
}

namespace {
constexpr auto llvm_open_output_flag = llvm::sys::fs::OF_None;

std::unique_ptr<llvm::Module> CloneLLVMModule(llvm::Module* mod) { return llvm::CloneModule(*mod); }

#if TVM_LLVM_VERSION <= 170
constexpr auto llvm_object_file_target = llvm::CGFT_ObjectFile;
constexpr auto llvm_assembly_file_target = llvm::CGFT_AssemblyFile;
#else
constexpr auto llvm_object_file_target = llvm::CodeGenFileType::ObjectFile;
constexpr auto llvm_assembly_file_target = llvm::CodeGenFileType::AssemblyFile;
#endif

bool LLVMAddPassesToEmitFile(llvm::TargetMachine* tm, llvm::legacy::PassManager* pm,
                             llvm::raw_fd_ostream* dest,
                             decltype(llvm_object_file_target) llvm_file_target) {
  return tm->addPassesToEmitFile(*pm, *dest, nullptr, llvm_file_target);
}

}  // namespace

void LLVMModuleNode::WriteToFile(const ffi::String& file_name_str,
                                 const ffi::String& format) const {
  // TVM_FFI_ICHECK(imports_.empty()) << "SaveToFile does not handle imported modules";
  std::string file_name = file_name_str;
  std::string fmt = runtime::GetFileFormat(file_name, format);
  std::error_code ecode;
  llvm::raw_fd_ostream dest(file_name, ecode, llvm_open_output_flag);
  TVM_FFI_ICHECK_EQ(ecode.value(), 0)
      << "Cannot open file: " << file_name << " " << ecode.message();
  bool is_obj_file = fmt == "o" || fmt == "obj";
  bool is_asm_file = fmt == "s" || fmt == "asm";
  if (is_obj_file || is_asm_file) {
    auto llvm_file_target = is_obj_file ? llvm_object_file_target : llvm_assembly_file_target;

    With<LLVMTarget> llvm_target(*llvm_instance_, LLVMTarget::GetTargetMetadata(*module_));
    llvm::legacy::PassManager pass;
    llvm::TargetMachine* tm = llvm_target->GetOrCreateTargetMachine();

    auto err = LLVMAddPassesToEmitFile(tm, &pass, &dest, llvm_file_target);
    TVM_FFI_ICHECK(!err) << "Cannot emit target CGFT_ObjectFile";

    pass.run(*CloneLLVMModule(module_));
  } else if (fmt == "ll") {
    module_->print(dest, nullptr);
  } else if (fmt == "bc") {
    llvm::WriteBitcodeToFile(*module_, dest);
  } else {
    TVM_FFI_THROW(InternalError) << "Do not know how to save file " << file_name
                                 << " with format=\'" << format << "\'";
  }
  dest.close();
}

ffi::Bytes LLVMModuleNode::SaveToBytes() const {
  TVM_FFI_THROW(InternalError) << "LLVMModule: SaveToBytes not supported";
}

ffi::String LLVMModuleNode::InspectSource(const ffi::String& format) const {
  std::string fmt = runtime::GetFileFormat("", format);
  std::string type_str;
  llvm::SmallString<256> str;
  llvm::raw_svector_ostream rso(str);

  if (fmt == "s" || fmt == "asm") {
    With<LLVMTarget> llvm_target(*llvm_instance_, LLVMTarget::GetTargetMetadata(*module_));
    std::unique_ptr<llvm::Module> m = llvm::CloneModule(*module_);
    llvm::legacy::PassManager pass;
    llvm::TargetMachine* tm = llvm_target->GetOrCreateTargetMachine();
#if TVM_LLVM_VERSION <= 170
    TVM_FFI_ICHECK(tm->addPassesToEmitFile(pass, rso, nullptr, llvm::CGFT_AssemblyFile) == 0)
        << "Cannot emit target CGFT_AssemblyFile";
#else
    TVM_FFI_ICHECK(
        tm->addPassesToEmitFile(pass, rso, nullptr, llvm::CodeGenFileType::AssemblyFile) == 0)
        << "Cannot emit target CodeGenFileType::AssemblyFile";
#endif
    pass.run(*m);
    return rso.str().str();
  } else if (fmt == "" || fmt == "ll") {
    std::string type_str;
    llvm::raw_string_ostream rso(type_str);
    TVM_FFI_ICHECK(module_ != nullptr);
    module_->print(rso, nullptr);
    return rso.str();
  } else {
    TVM_FFI_THROW(InternalError) << "Do not know how to get source code with format: " << format
                                 << "\'";
  }
  return "";
}

void LLVMModuleNode::Init(const IRModule& mod, const Target& target) {
  llvm_instance_ = std::make_unique<LLVMInstance>();
  With<LLVMTarget> llvm_target(*llvm_instance_, target);
  llvm::TargetMachine* tm = llvm_target->GetOrCreateTargetMachine();
  std::unique_ptr<CodeGenLLVM> cg = CodeGenLLVM::Create(llvm_target.get());

  std::string entry_func;

  ffi::Optional<ffi::String> system_lib_prefix =
      mod->GetAttr<ffi::String>(tvm::attr::kSystemLibPrefix);

  for (auto kv : mod->functions) {
    if (!kv.second->IsInstance<PrimFuncNode>()) {
      DLOG(INFO) << "Can only lower IR Module with PrimFuncs, but got " << kv.second->GetTypeKey();
      continue;
    }
    auto f = kv.second.as_or_throw<PrimFunc>();
    auto global_symbol = f->GetAttr<ffi::String>(tvm::attr::kGlobalSymbol);
    bool is_entry_func = f->HasNonzeroAttr(tirx::attr::kIsEntryFunc);

    TVM_FFI_ICHECK(global_symbol || !is_entry_func) << "The entry func must be exposed externally.";

    if (global_symbol) {
      function_names_.push_back(global_symbol.value());
      if (is_entry_func) {
        entry_func = global_symbol.value();
      }
    }
  }
  // TODO(@jroesch): follow up on this condition.
  // TVM_FFI_ICHECK(funcs.size() > 0);
  // TODO(tqchen): remove the entry function behavior as it does not
  // makes sense when we start to use multiple modules.
  cg->Init("TVMMod", llvm_target.get(), system_lib_prefix, system_lib_prefix.has_value(), false);
  cg->SetFastMathFlags(llvm_target->GetFastMathFlags());
  cg->AddFunctionsOrdered(mod->functions.begin(), mod->functions.end());
  if (entry_func.length() != 0) {
    cg->AddMainFunction(entry_func);
  }

  module_owning_ptr_ = cg->Finish();
  module_ = module_owning_ptr_.get();
  jit_engine_ = llvm_target->GetJITEngine();
  llvm_target->SetTargetMetadata(module_);
  module_->addModuleFlag(llvm::Module::Override, "Debug Info Version",
                         llvm::DEBUG_METADATA_VERSION);

  if (system_lib_prefix) {
    std::string str_val = system_lib_prefix.value();
    module_->addModuleFlag(llvm::Module::Warning, "tvm_system_lib_prefix",
                           llvm::MDString::get(*(llvm_target->GetContext()), str_val));
  }

  module_->addModuleFlag(llvm::Module::Override, "Dwarf Version",
                         tm->getTargetTriple().isOSDarwin() ? 2 : 4);
}

void LLVMModuleNode::Init(std::unique_ptr<llvm::Module> module,
                          std::unique_ptr<LLVMInstance> llvm_instance) {
  module_owning_ptr_ = std::move(module);
  module_ = module_owning_ptr_.get();
  llvm_instance_ = std::move(llvm_instance);
}

void LLVMModuleNode::LoadIR(const std::string& file_name) {
  auto llvm_instance = std::make_unique<LLVMInstance>();
  std::unique_ptr<llvm::Module> module = llvm_instance->LoadIR(file_name);
  Init(std::move(module), std::move(llvm_instance));
}

bool LLVMModuleNode::ImplementsFunction(const ffi::String& name) {
  return std::find(function_names_.begin(), function_names_.end(),
                   ffi::symbol::tvm_ffi_symbol_prefix + name) != function_names_.end();
}

void LLVMModuleNode::InitMCJIT() {
  std::lock_guard<std::mutex> lock(mutex_);
  if (mcjit_ee_) {
    return;
  }
  // MCJIT builder
  With<LLVMTarget> llvm_target(*llvm_instance_, LLVMTarget::GetTargetMetadata(*module_));
  llvm::EngineBuilder builder(std::move(module_owning_ptr_));

  // set options
  builder.setEngineKind(llvm::EngineKind::JIT);
#if TVM_LLVM_VERSION <= 170
  builder.setOptLevel(llvm::CodeGenOpt::Aggressive);
#else
  builder.setOptLevel(llvm::CodeGenOptLevel::Aggressive);
#endif
  builder.setMCPU(llvm_target->GetCPU());
  builder.setMAttrs(llvm_target->GetTargetFeatures());
  builder.setTargetOptions(llvm_target->GetTargetOptions());

  // create the taget machine
  auto tm = std::unique_ptr<llvm::TargetMachine>(builder.selectTarget());
  if (!IsCompatibleWithHost(tm.get())) {
    TVM_FFI_THROW(InternalError) << "Cannot run module, architecture mismatch";
  }

  // data layout
  llvm::DataLayout layout(tm->createDataLayout());
  TVM_FFI_ICHECK(layout == module_->getDataLayout())
      << "Data layout mismatch between module("
      << module_->getDataLayout().getStringRepresentation() << ")"
      << " and ExecutionEngine (" << layout.getStringRepresentation() << ")";

  // create MCJIT
  mcjit_ee_ = builder.create(tm.release());
  TVM_FFI_ICHECK(mcjit_ee_ != nullptr) << "Failed to initialize LLVM MCJIT engine for "
#if TVM_LLVM_VERSION >= 210
                                       << module_->getTargetTriple().str();
#else
                                       << module_->getTargetTriple();
#endif

  VLOG(2) << "LLVM MCJIT execute " << module_->getModuleIdentifier() << " for triple `"
          << llvm_target->GetTargetTriple() << "`"
          << " on cpu `" << llvm_target->GetCPU() << "`";

  // run ctors
  mcjit_ee_->runStaticConstructorsDestructors(false);

  if (void** ctx_addr =
          reinterpret_cast<void**>(GetGlobalAddr(ffi::symbol::tvm_ffi_library_ctx, *llvm_target))) {
    *ctx_addr = this;
  }

  ffi::Module::VisitContextSymbols([this, &llvm_target](const ffi::String& name, void* symbol) {
    if (void** ctx_addr = reinterpret_cast<void**>(GetGlobalAddr(name, *llvm_target))) {
      *ctx_addr = symbol;
    }
  });
  // There is a problem when a JITed function contains a call to a runtime function.
  // The runtime function (e.g. __truncsfhf2) may not be resolved, and calling it will
  // lead to a runtime crash.
  // Do name lookup on a symbol that doesn't exist. This will force MCJIT to finalize
  // all loaded objects, which will resolve symbols in JITed code.
  mcjit_ee_->getFunctionAddress(
      "__some_name_that_hopefully_doesnt_exist__b49f8aaade5877eaba7583b91");
}

void LLVMModuleNode::InitORCJIT() {
  std::lock_guard<std::mutex> lock(mutex_);
  if (orcjit_ee_) {
    return;
  }
  // ORCJIT builder
  With<LLVMTarget> llvm_target(*llvm_instance_, LLVMTarget::GetTargetMetadata(*module_));
  llvm::orc::JITTargetMachineBuilder tm_builder(llvm::Triple(llvm_target->GetTargetTriple()));

  // set options
  tm_builder.setCPU(llvm_target->GetCPU());
  tm_builder.setFeatures(llvm_target->GetTargetFeatureString());
  tm_builder.setOptions(llvm_target->GetTargetOptions());
#if TVM_LLVM_VERSION <= 170
  tm_builder.setCodeGenOptLevel(llvm::CodeGenOpt::Aggressive);
#else
  tm_builder.setCodeGenOptLevel(llvm::CodeGenOptLevel::Aggressive);
#endif

  // Default is no explicit JIT code & reloc model
  // Propagate instance code & reloc for RISCV case.
  auto arch = tm_builder.getTargetTriple().getArch();
  if (arch == llvm::Triple::riscv32 || arch == llvm::Triple::riscv64) {
    tm_builder.setRelocationModel(llvm_target->GetTargetRelocModel());
    tm_builder.setCodeModel(llvm_target->GetTargetCodeModel());
  }

  // create the taget machine
  std::unique_ptr<llvm::TargetMachine> tm = llvm::cantFail(tm_builder.createTargetMachine());
  if (!IsCompatibleWithHost(tm.get())) {
    TVM_FFI_THROW(InternalError) << "Cannot run module, architecture mismatch";
  }

  // data layout
  ffi::String module_name = module_->getModuleIdentifier();
  llvm::DataLayout layout(tm->createDataLayout());
  TVM_FFI_ICHECK(layout == module_->getDataLayout())
      << "Data layout mismatch between module("
      << module_->getDataLayout().getStringRepresentation() << ")"
      << " and ExecutionEngine (" << layout.getStringRepresentation() << ")";

  // compiler
  const auto compilerBuilder = [&](const llvm::orc::JITTargetMachineBuilder&)
      -> llvm::Expected<std::unique_ptr<llvm::orc::IRCompileLayer::IRCompiler>> {
    return std::make_unique<llvm::orc::TMOwningSimpleCompiler>(std::move(tm));
  };

  // linker
  const auto linkerBuilder =
#if TVM_LLVM_VERSION >= 210
      [&](llvm::orc::ExecutionSession& session)
      -> llvm::Expected<std::unique_ptr<llvm::orc::ObjectLayer>> {
#else
      [&](llvm::orc::ExecutionSession& session,
          const llvm::Triple& triple) -> std::unique_ptr<llvm::orc::ObjectLayer> {
#endif
#if _WIN32
#if TVM_LLVM_VERSION >= 210
    auto GetMemMgr = [](const llvm::MemoryBuffer&) {
      return std::make_unique<llvm::SectionMemoryManager>();
    };
#else
    auto GetMemMgr = []() { return std::make_unique<llvm::SectionMemoryManager>(); };
#endif
    auto ObjLinkingLayer =
        std::make_unique<llvm::orc::RTDyldObjectLinkingLayer>(session, std::move(GetMemMgr));
#else
    auto ObjLinkingLayer = std::make_unique<llvm::orc::ObjectLinkingLayer>(session);
#endif
#if TVM_LLVM_VERSION >= 210
    if (tm_builder.getTargetTriple().isOSBinFormatCOFF()) {
#else
    if (triple.isOSBinFormatCOFF()) {
#endif
      ObjLinkingLayer->setOverrideObjectFlagsWithResponsibilityFlags(true);
      ObjLinkingLayer->setAutoClaimResponsibilityForObjectSymbols(true);
    }
#if TVM_LLVM_VERSION >= 210
    return llvm::Expected<std::unique_ptr<llvm::orc::ObjectLayer>>(std::move(ObjLinkingLayer));
#else
    return ObjLinkingLayer;
#endif
  };  // NOLINT(readability/braces)

  // create LLJIT
  orcjit_ee_ = llvm::cantFail(llvm::orc::LLJITBuilder()
                                  .setDataLayout(layout)
                                  .setCompileFunctionCreator(compilerBuilder)
                                  .setObjectLinkingLayerCreator(linkerBuilder)
                                  .create());

  TVM_FFI_ICHECK(orcjit_ee_ != nullptr) << "Failed to initialize LLVM ORCJIT engine for "
#if TVM_LLVM_VERSION >= 210
                                        << module_->getTargetTriple().str();
#else
                                        << module_->getTargetTriple();
#endif

  // store ctors
  auto ctors = llvm::orc::getConstructors(*module_);
  llvm::orc::CtorDtorRunner ctorRunner(orcjit_ee_->getMainJITDylib());
  ctorRunner.add(ctors);

  // resolve system symbols (like pthread, dl, m, etc.)
  auto gen =
      llvm::orc::DynamicLibrarySearchGenerator::GetForCurrentProcess(layout.getGlobalPrefix());
  TVM_FFI_ICHECK(gen) << llvm::toString(gen.takeError()) << "\n";
  orcjit_ee_->getMainJITDylib().addGenerator(std::move(gen.get()));

  // transfer module to a clone
  auto uctx = std::make_unique<llvm::LLVMContext>();
  auto umod = llvm::CloneModule(*(std::move(module_owning_ptr_)));

  // add the llvm module to run
  llvm::orc::ThreadSafeModule tsm(std::move(umod), std::move(uctx));
  auto err = orcjit_ee_->addIRModule(std::move(tsm));
  TVM_FFI_ICHECK(!err) << llvm::toString(std::move(err));

  VLOG(2) << "LLVM ORCJIT execute " << module_->getModuleIdentifier() << " for triple `"
          << llvm_target->GetTargetTriple() << "`"
          << " on cpu `" << llvm_target->GetCPU() << "`";

  // run ctors
  err = ctorRunner.run();
  TVM_FFI_ICHECK(!err) << llvm::toString(std::move(err));

  if (void** ctx_addr =
          reinterpret_cast<void**>(GetGlobalAddr(ffi::symbol::tvm_ffi_library_ctx, *llvm_target))) {
    *ctx_addr = this;
  }
  ffi::Module::VisitContextSymbols([this, &llvm_target](const ffi::String& name, void* symbol) {
    if (void** ctx_addr = reinterpret_cast<void**>(GetGlobalAddr(name, *llvm_target))) {
      *ctx_addr = symbol;
    }
  });
}

bool LLVMModuleNode::IsCompatibleWithHost(const llvm::TargetMachine* tm) const {
  LLVMTargetInfo host_target(*llvm_instance_, "llvm");
  auto tm_host = host_target.GetOrCreateTargetMachine();
  if (tm_host->getTargetTriple().getArch() != tm->getTargetTriple().getArch()) {
    LOG(INFO) << "Architecture mismatch: module=" << tm->getTargetTriple().str()
              << " host=" << tm_host->getTargetTriple().str();
    return false;
  }
  return true;
}

// Get global address from execution engine.
void* LLVMModuleNode::GetGlobalAddr(const std::string& name, const LLVMTarget& llvm_target) const {
  // first verifies if GV exists.
  if (module_->getGlobalVariable(name) != nullptr) {
    if (jit_engine_ == "mcjit") {
      return reinterpret_cast<void*>(mcjit_ee_->getGlobalValueAddress(name));
    } else if (jit_engine_ == "orcjit") {
      auto addr = llvm::cantFail(orcjit_ee_->lookup(name)).getValue();
      return reinterpret_cast<void*>(addr);
    } else {
      TVM_FFI_THROW(InternalError) << "Either `mcjit` or `orcjit` are not initialized.";
    }
  }
  return nullptr;
}

void* LLVMModuleNode::GetFunctionAddr(const std::string& name,
                                      const LLVMTarget& llvm_target) const {
  // first verifies if GV exists.
  if (module_->getFunction(name) != nullptr) {
    if (jit_engine_ == "mcjit") {
      return reinterpret_cast<void*>(mcjit_ee_->getFunctionAddress(name));
    } else if (jit_engine_ == "orcjit") {
      auto addr = llvm::cantFail(orcjit_ee_->lookup(name)).getValue();
      return reinterpret_cast<void*>(addr);
    } else {
      TVM_FFI_THROW(InternalError) << "Either `mcjit` or `orcjit` are not initialized.";
    }
  }
  return nullptr;
}

static void LLVMReflectionRegister() {
  namespace refl = tvm::ffi::reflection;
  refl::GlobalDef()
      .def("target.build.llvm",
           [](IRModule mod, Target target) -> ffi::Module {
             auto n = ffi::make_object<LLVMModuleNode>();
             n->Init(mod, target);
             return ffi::Module(n);
           })
      .def("codegen.LLVMModuleCreate",
           [](std::string target_str, std::string module_name) -> ffi::Module {
             auto llvm_instance = std::make_unique<LLVMInstance>();
             With<LLVMTarget> llvm_target(*llvm_instance, target_str);
             auto n = ffi::make_object<LLVMModuleNode>();
             // Generate a LLVM module from an input target string
             auto module = std::make_unique<llvm::Module>(module_name, *llvm_target->GetContext());
             llvm_target->SetTargetMetadata(module.get());
#if TVM_LLVM_VERSION >= 210
             module->setTargetTriple(llvm::Triple(llvm_target->GetTargetTriple()));
#else
             module->setTargetTriple(llvm_target->GetTargetTriple());
#endif
             module->setDataLayout(llvm_target->GetOrCreateTargetMachine()->createDataLayout());
             n->Init(std::move(module), std::move(llvm_instance));
             n->SetJITEngine(llvm_target->GetJITEngine());
             return ffi::Module(n);
           })
      .def("target.llvm_lookup_intrinsic_id",
           [](std::string name) -> int64_t {
#if TVM_LLVM_VERSION >= 200
             return static_cast<int64_t>(llvm::Intrinsic::lookupIntrinsicID(name));
#else
      return static_cast<int64_t>(llvm::Function::lookupIntrinsicID(name));
#endif
           })
      .def("target.llvm_get_intrinsic_name",
           [](int64_t id) -> ffi::String { return llvmGetIntrinName(id); })
      .def("target.llvm_get_system_x86_vendor",
           []() -> ffi::String {
#if defined(__i386__) || defined(_M_IX86) || defined(__x86_64__) || defined(_M_X64)
             using namespace llvm::sys::detail::x86;
             const auto x86_sign = getVendorSignature();
             if (x86_sign == VendorSignatures::GENUINE_INTEL)
               return "intel";
             else if (x86_sign == VendorSignatures::AUTHENTIC_AMD)
               return "amd";
             else if (x86_sign == VendorSignatures::UNKNOWN)
               return "unknown";
#endif
             return "unimplemented";
           })
      .def("target.llvm_get_vector_width",
           [](const Target& target) -> int {
             auto use_target = target.defined() ? target : Target::Current(false);
             // ignore non "llvm" target
             if (target.defined()) {
               if (target->kind->name != "llvm") {
                 return -1;
               }
             }
             auto llvm_instance = std::make_unique<LLVMInstance>();
             LLVMTargetInfo llvm_backend(*llvm_instance, use_target);
             return llvm_backend.GetVectorWidth();
           })
      .def("target.llvm_get_system_triple",
           []() -> ffi::String { return llvm::sys::getDefaultTargetTriple(); })
      .def("target.llvm_get_system_cpu",
           []() -> ffi::String { return llvm::sys::getHostCPUName().str(); })
      .def("target.llvm_get_targets",
           []() -> ffi::Array<ffi::String> {
             auto llvm_instance = std::make_unique<LLVMInstance>();
             LLVMTargetInfo llvm_backend(*llvm_instance, "llvm");
             return llvm_backend.GetAllLLVMTargets();
           })
      .def("target.llvm_get_cpu_archlist",
           [](const Target& target) -> ffi::Array<ffi::String> {
             auto use_target = target.defined() ? target : Target::Current(false);
             // ignore non "llvm" target
             if (target.defined()) {
               if (target->kind->name != "llvm") {
                 return ffi::Array<ffi::String>{};
               }
             }
             auto llvm_instance = std::make_unique<LLVMInstance>();
             LLVMTargetInfo llvm_backend(*llvm_instance, use_target);
             return llvm_backend.GetAllLLVMTargetArches();
           })
      .def("target.llvm_get_cpu_features",
           [](const Target& target) -> ffi::Map<ffi::String, ffi::String> {
             auto use_target = target.defined() ? target : Target::Current(false);
             // ignore non "llvm" target
             if (target.defined()) {
               if (target->kind->name != "llvm") {
                 return {};
               }
             }
             auto llvm_instance = std::make_unique<LLVMInstance>();
             LLVMTargetInfo llvm_backend(*llvm_instance, use_target);
             return llvm_backend.GetAllLLVMCpuFeatures();
           })
      .def("target.llvm_cpu_has_feature",
           [](const ffi::String feature, const Target& target) -> bool {
             auto use_target = target.defined() ? target : Target::Current(false);
             // ignore non "llvm" target
             if (target.defined()) {
               if (target->kind->name != "llvm") {
                 return false;
               }
             }
             auto llvm_instance = std::make_unique<LLVMInstance>();
             LLVMTargetInfo llvm_backend(*llvm_instance, use_target);
             auto cpu_features = llvm_backend.GetAllLLVMCpuFeatures();
             bool has_feature = cpu_features.find(feature) != cpu_features.end();
             return has_feature;
           })
      .def("target.target_has_feature",
           [](const ffi::String feature, const Target& target) -> bool {
             auto use_target = target.defined() ? target : Target::Current(false);
             // ignore non "llvm" target
             if (target.defined()) {
               if (target->kind->name != "llvm") {
                 return false;
               }
             }
             auto llvm_instance = std::make_unique<LLVMInstance>();
             LLVMTargetInfo llvm_target(*llvm_instance, use_target);
             return llvm_target.TargetHasCPUFeature(feature);
           })
      .def("target.llvm_is_valid_cpu",
           [](ffi::String cpu, ffi::String triple) -> bool {
             auto llvm_instance = std::make_unique<LLVMInstance>();
             ffi::Map<ffi::String, ffi::Any> target_map;
             target_map.Set("kind", ffi::String("llvm"));
             target_map.Set("mtriple", triple);
             LLVMTargetInfo llvm_backend(*llvm_instance, Target(target_map));
             return llvm_backend.IsValidCPU(std::string(cpu));
           })
      .def("target.llvm_version_major", []() -> int { return TVM_LLVM_VERSION / 10; })
      .def("ffi.Module.load_from_file.ll",
           [](std::string filename, std::string fmt) -> ffi::Module {
             auto n = ffi::make_object<LLVMModuleNode>();
             n->SetJITEngine("orcjit");
             n->LoadIR(filename);
             return ffi::Module(n);
           })
      .def("codegen.llvm_target_enabled",
           [](std::string target_str) -> bool {
             LLVMInstance llvm_instance;
             auto* tm = With<LLVMTarget>(llvm_instance, target_str)
                            ->GetOrCreateTargetMachine(/*allow_missing=*/true);
             return tm != nullptr;
           })
      .def("codegen.codegen_blob",
           [](std::string data, bool system_lib, std::string llvm_target_string,
              std::string c_symbol_prefix) -> ffi::Module {
             auto n = ffi::make_object<LLVMModuleNode>();
             auto llvm_instance = std::make_unique<LLVMInstance>();
             With<LLVMTarget> llvm_target(*llvm_instance, llvm_target_string);
             std::unique_ptr<llvm::Module> blob =
                 CodeGenBlob(data, system_lib, llvm_target.get(), c_symbol_prefix);
             n->Init(std::move(blob), std::move(llvm_instance));
             n->SetJITEngine(llvm_target->GetJITEngine());
             return ffi::Module(n);
           });
}

TVM_FFI_STATIC_INIT_BLOCK() { LLVMReflectionRegister(); }

}  // namespace codegen
}  // namespace tvm

#endif  // TVM_LLVM_VERSION
