/*
 * 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.
 */

#ifdef TVM_LLVM_VERSION

#include <llvm/ADT/ArrayRef.h>
#include <llvm/ADT/SmallString.h>
#include <llvm/ADT/StringRef.h>
#include <llvm/Bitcode/BitcodeWriter.h>
#include <llvm/IR/Constants.h>
#include <llvm/IR/DerivedTypes.h>
#include <llvm/IR/Function.h>
#include <llvm/IR/GlobalVariable.h>
#include <llvm/IR/Instructions.h>
#include <llvm/IR/Intrinsics.h>
#include <llvm/IR/IntrinsicsHexagon.h>
#include <llvm/IR/LLVMContext.h>
#include <llvm/IR/LegacyPassManager.h>
#include <llvm/IR/MDBuilder.h>
#include <llvm/IR/Module.h>
#include <llvm/Support/Alignment.h>
#include <llvm/Support/CodeGen.h>
#include <llvm/Support/CommandLine.h>
#include <llvm/Support/FileSystem.h>
#include <llvm/Support/raw_ostream.h>
#include <llvm/Target/TargetMachine.h>
#include <llvm/Transforms/Utils/Cloning.h>
#include <tvm/ffi/extra/module.h>
#include <tvm/ffi/reflection/registry.h>
#include <tvm/runtime/logging.h>
#include <tvm/target/codegen.h>
#include <tvm/tirx/analysis.h>

#include <cstdio>
#include <cstdlib>
#include <map>
#include <sstream>
#include <string>
#include <unordered_map>
#include <utility>
#include <vector>

#include "../../../../runtime/file_utils.h"
#include "../../../../runtime/metadata.h"
#include "../../../../target/build_common.h"
#include "../../../../target/llvm/codegen_cpu.h"
#include "../../../../target/llvm/llvm_instance.h"
#include "../hexagon_fallback_module.h"

namespace tvm {
namespace codegen {

TVM_FFI_INLINE int GetVectorBytes(const PrimType& dtype) {
  TVM_FFI_ICHECK(dtype.IsFixedLengthVector() || dtype.IsScalar());
  return static_cast<int>(dtype.StorageBytes());
}

// Hexagon code generation
class CodeGenHexagon final : public CodeGenCPU {
 public:
  void Init(const std::string& module_name, LLVMTarget* llvm_target,
            ffi::Optional<ffi::String> system_lib_prefix, bool dynamic_lookup,
            bool target_c_runtime) override;
  void InitTarget() final;

  using CodeGenCPU::VisitStmt_;
  llvm::Value* VisitExpr_(const BufferLoadNode* op) override;
  llvm::Value* CreateIntrinsic(const CallNode* op) override;

  llvm::Value* CreateCallExtern(Type ret_type, ffi::String global_symbol,
                                const ffi::Array<Expr>& args, bool skip_first_arg) override;
  llvm::Value* CreateCallExternQHL(Type ret_type, ffi::String global_symbol,
                                   const ffi::Array<PrimExpr>& args, bool skip_first_arg);

  llvm::Module* GetModulePtr() const { return module_.get(); }

  uint64_t GetTypeSizeInBits(llvm::Type* type) const {
#if TVM_LLVM_VERSION >= 160
    return data_layout_->getTypeSizeInBits(type).getFixedValue();
#else
    return data_layout_->getTypeSizeInBits(type).getFixedSize();
#endif
  }

 protected:
  void CreatePrintf(const std::string& format, llvm::ArrayRef<llvm::Value*> format_args) final;

 private:
  TypedPointer CreateBufferPtr(llvm::Value* buffer_ptr, Type buffer_element_type,
                               llvm::ArrayRef<llvm::Value*> indices, Type value_type) final;

  bool IsQHLFunction(const std::string& func);

  llvm::Value* VectorLookupLoad(Buffer buffer, PrimType buffer_type, ffi::Array<PrimExpr> indices);
  llvm::Value* Intrinsic(llvm::Intrinsic::ID, llvm::ArrayRef<llvm::Value*> args);
  std::vector<std::string> fqhl_list_ = {
      "tvm_vect_qhmath_hvx_cos_ahf",     "tvm_vect_qhmath_hvx_tanh_ahf",
      "tvm_vect_qhmath_hvx_sigmoid_ahf", "tvm_vect_qhmath_hvx_sin_ahf",
      "tvm_vect_qhmath_hvx_sqrt_ahf",    "tvm_vect_qhmath_hvx_exp_ahf",
      "tvm_vect_qhmath_hvx_tan_ahf",     "tvm_vect_qhmath_hvx_floor_ahf",
      "tvm_vect_qhmath_hvx_ceil_ahf",    "tvm_vect_qhmath_hvx_pow_ahf"};
};

void CodeGenHexagon::Init(const std::string& module_name, LLVMTarget* llvm_target,
                          ffi::Optional<ffi::String> system_lib_prefix, bool dynamic_lookup,
                          bool target_c_runtime) {
  CodeGenCPU::Init(module_name, llvm_target, system_lib_prefix, dynamic_lookup, target_c_runtime);
}

void CodeGenHexagon::InitTarget() {
  native_vector_bits_ = 64;                       // Assume "scalar" vectors at first.
  const auto hvx_length_feature = "+hvx-length";  // +hvx-length{64|128}b
  for (const std::string& f : llvm_target_->GetTargetFeatures()) {
    llvm::StringRef fs(f);
#if TVM_LLVM_VERSION >= 180
    if (!fs.starts_with(hvx_length_feature)) continue;

    TVM_FFI_ICHECK(fs.ends_with("b")) << "malformed target feature: " << f;
#else
    if (!fs.startswith(hvx_length_feature)) continue;

    TVM_FFI_ICHECK(fs.endswith("b")) << "malformed target feature: " << f;
#endif

    int hvx_bytes = 0;
    size_t len_begin = std::strlen(hvx_length_feature);
    TVM_FFI_ICHECK(!fs.substr(len_begin, fs.size() - len_begin - 1).getAsInteger(10, hvx_bytes))
        << "invalid HVX length in feature string: " << f;
    TVM_FFI_ICHECK(hvx_bytes == 64 || hvx_bytes == 128)
        << "invalid HVX vector length: " << hvx_bytes << ", should be 64 or 128";
    native_vector_bits_ = hvx_bytes * 8;
    // There should only be one hvx-length...
    break;
  }
  CodeGenCPU::InitTarget();
}

llvm::Value* CodeGenHexagon::CreateCallExternQHL(Type ret_type, ffi::String global_symbol,
                                                 const ffi::Array<PrimExpr>& args,
                                                 bool skip_first_arg) {
  PrimType arg_ty = args[1].ty();
  int num_lanes = arg_ty.lanes();
  int vector_length = native_vector_bits_ / arg_ty.bits();
  num_lanes = ((num_lanes + vector_length - 1) / vector_length) * vector_length;
  std::vector<llvm::Value*> vect_split;
  for (int i = 0; i < num_lanes / vector_length; ++i) {
    std::vector<llvm::Value*> sub_vect_val;
    std::vector<llvm::Type*> arg_types;
    for (size_t k = skip_first_arg; k < args.size(); ++k)
      sub_vect_val.push_back(
          CodeGenCPU::CreateVecSlice(MakeValue(args[k]), i * vector_length, vector_length));
    for (llvm::Value* v : sub_vect_val) {
      arg_types.push_back(v->getType());
    }
    llvm::FunctionType* ftype = llvm::FunctionType::get(arg_types[0], arg_types, false);
    llvm::Function* f = module_->getFunction(MakeStringRef(global_symbol));
    if (f == nullptr) {
      f = llvm::Function::Create(ftype, llvm::Function::ExternalLinkage,
                                 MakeStringRef(global_symbol), module_.get());
    }
    auto ext_callee = llvm::FunctionCallee(f);
    vect_split.push_back(builder_->CreateCall(ext_callee, sub_vect_val));
  }
  return CodeGenCPU::CreateVecConcat(vect_split);
}

bool CodeGenHexagon::IsQHLFunction(const std::string& func) {
  return std::find(fqhl_list_.begin(), fqhl_list_.end(), func) != fqhl_list_.end();
}

llvm::Value* CodeGenHexagon::CreateCallExtern(Type ret_type, ffi::String global_symbol,
                                              const ffi::Array<Expr>& args, bool skip_first_arg) {
  if (IsQHLFunction(global_symbol)) {
    PrimType arg_ty = args[1].as_or_throw<PrimExpr>().ty();
    int num_lanes = arg_ty.lanes();
    int vector_length = native_vector_bits_ / arg_ty.bits();
    if (num_lanes > vector_length) {
      return CreateCallExternQHL(ret_type, global_symbol, args.as_or_throw<ffi::Array<PrimExpr>>(),
                                 skip_first_arg);
    }
  }
  return CodeGenCPU::CreateCallExtern(ret_type, global_symbol, args, skip_first_arg);
}

llvm::Value* CodeGenHexagon::VisitExpr_(const BufferLoadNode* op) {
  if (!op->buffer.same_as(op->buffer->data)) {
    // Check if we can generate a vector lookup.
    if (!op->indices[0].as<RampNode>()) {
      if (auto* vlut = VectorLookupLoad(op->buffer, op->ty.as_or_throw<PrimType>(), op->indices)) {
        return vlut;
      }
    }
  }
  return CodeGenCPU::VisitExpr_(op);
}

llvm::Value* CodeGenHexagon::CreateIntrinsic(const CallNode* op) {
  if (op->op.same_as(builtin::start_profile_intrinsic()) ||
      op->op.same_as(builtin::end_profile_intrinsic())) {
    llvm::Value* id = MakeValue(op->args[0].as_or_throw<PrimExpr>());
    auto instrprof_id = llvm::Intrinsic::hexagon_instrprof_custom;
#if TVM_LLVM_VERSION >= 200
    llvm::Function* func = llvm::cast<llvm::Function>(
        llvm::Intrinsic::getOrInsertDeclaration(module_.get(), instrprof_id, {}));
#else
    llvm::Function* func = llvm::Intrinsic::getDeclaration(module_.get(), instrprof_id);
#endif
    llvm::GlobalVariable* name_var = module_->getGlobalVariable("handler_name");
    if (!name_var) {
      llvm::StringRef init_str = "lwp_handler";
      llvm::Constant* init = llvm::ConstantDataArray::getString(module_->getContext(), init_str);

      name_var = new llvm::GlobalVariable(*module_, init->getType(), true,
                                          llvm::GlobalValue::InternalLinkage, init, "handler_name");
    }
    llvm::Type* t_int8_p_ = llvmGetPointerTo(t_int8_, 0);
    return builder_->CreateCall(func, {llvm::ConstantExpr::getBitCast(name_var, t_int8_p_), id});
  }
  return CodeGenCPU::CreateIntrinsic(op);
}

void CodeGenHexagon::CreatePrintf(const std::string& format,
                                  llvm::ArrayRef<llvm::Value*> format_args) {
  // This function generates LLVM instructions to call HAP_debug_v2,
  // as if the FARF macro in `HAP_farf.h` were called as
  // FARF(ALWAYS, format, format_args[0], format_args[1], ...)
  std::string func_name = "HAP_debug_v2";

  llvm::Function* func = module_->getFunction(func_name);
  if (func == nullptr) {
    llvm::FunctionType* ftype = llvm::FunctionType::get(
        t_void_, {t_int32_, llvmGetPointerTo(t_char_, 0), t_int32_, llvmGetPointerTo(t_char_, 0)},
        true);
    func = llvm::Function::Create(ftype, llvm::Function::ExternalLinkage, func_name, module_.get());
  }

  // There is no such filename/line number for this print statement
#if TVM_LLVM_VERSION >= 200
  llvm::Value* filename = builder_->CreateGlobalString("generated-LLVM-code", "dummy_filename");
  llvm::Value* format_str = builder_->CreateGlobalString(format, "printf_format_str");
#else
  llvm::Value* filename = builder_->CreateGlobalStringPtr("generated-LLVM-code", "dummy_filename");
  llvm::Value* format_str = builder_->CreateGlobalStringPtr(format, "printf_format_str");
#endif

  // The value of FARF_ALWAYS_LEVEL, defined as HAP_LEVEL_HIGH
  llvm::Value* level = ConstInt32(2);

  llvm::Value* line_number = ConstInt32(1);

  std::vector<llvm::Value*> func_args = {level, filename, line_number, format_str};
  func_args.insert(func_args.end(), format_args.begin(), format_args.end());

  builder_->CreateCall(func, func_args);
}

CodeGenLLVM::TypedPointer CodeGenHexagon::CreateBufferPtr(llvm::Value* buffer_ptr,
                                                          Type buffer_element_type,
                                                          llvm::ArrayRef<llvm::Value*> indices,
                                                          Type value_type) {
  // Flat indices get delegated to the LLVM codegen.
  if (indices.size() == 1) {
    return CodeGenCPU::CreateBufferPtr(buffer_ptr, buffer_element_type, indices, value_type);
  }

  TVM_FFI_ICHECK_EQ(indices.size(), 2)
      << "CodegenHexagon supports 1-d and 2-d physical buffers, received " << indices.size()
      << "-d buffer indices";

  // Use the first index to identify the pointer.
  Type type_void_ptr = PointerType::VoidPointerTy();
  CodeGenLLVM::TypedPointer buffer_chunk_ptr_ptr =
      CodeGenCPU::CreateBufferPtr(buffer_ptr, type_void_ptr, {indices[0]}, type_void_ptr);
  llvm::Value* buffer_chunk_ptr =
      builder_->CreateLoad(buffer_chunk_ptr_ptr.type, buffer_chunk_ptr_ptr.addr);

  // Then delegate the CodeGenLLVM to find the value from the second
  // index.
  return CodeGenCPU::CreateBufferPtr(buffer_chunk_ptr, buffer_element_type, {indices[1]},
                                     value_type);
}

llvm::Value* CodeGenHexagon::Intrinsic(llvm::Intrinsic::ID IntID,
                                       llvm::ArrayRef<llvm::Value*> args) {
#if TVM_LLVM_VERSION >= 200
  llvm::Function* intf =
      llvm::cast<llvm::Function>(llvm::Intrinsic::getOrInsertDeclaration(module_.get(), IntID, {}));
#else
  llvm::Function* intf = llvm::Intrinsic::getDeclaration(module_.get(), IntID);
#endif
  auto intf_callee = llvm::FunctionCallee(intf);
  std::vector<llvm::Value*> conv_args;
  llvm::FunctionType* intf_type = intf->getFunctionType();
  TVM_FFI_ICHECK(args.size() == intf_type->getNumParams());

  for (int i = 0, e = args.size(); i != e; ++i) {
    llvm::Value* arg = args[i];
    auto* need_type = llvm::dyn_cast<llvm::VectorType>(intf_type->getParamType(i));
    auto* have_type = llvm::dyn_cast<llvm::VectorType>(arg->getType());
    if (need_type != nullptr && have_type != nullptr && need_type != have_type) {
      int need_width = GetTypeSizeInBits(need_type);
      int have_width = GetTypeSizeInBits(have_type);
      if (need_width == have_width) {
        if (need_width == native_vector_bits_ || need_width == 2 * native_vector_bits_) {
          arg = builder_->CreateBitCast(arg, need_type);
        }
      }  // TODO(joshherr-quic): add handling of v128i1 <-> v1024i1
    }
    conv_args.push_back(arg);
  }
  return builder_->CreateCall(intf_callee, conv_args);
}

llvm::Value* CodeGenHexagon::VectorLookupLoad(Buffer buffer, PrimType buffer_type,
                                              ffi::Array<PrimExpr> indices) {
  PrimExpr index = indices[0];
  PrimType index_ty = index.ty();
  if (!index_ty.IsFixedLengthVector()) {
    return nullptr;
  }

  if (buffer_type.bits() != 8) return nullptr;

  int table_elem_count = arith::Analyzer()->Simplify(buffer->shape[0]).as<IntImmNode>()->value;
  if (table_elem_count <= 0 || table_elem_count > 256) return nullptr;

  auto int32 = PrimType::Int(32);
  auto native_vector_bytes = native_vector_bits_ / 8;

  // Indexes
  llvm::Value* trunc = MakeValue(Cast(index_ty.WithBits(8), index));
  llvm::Value* index_pad = CreateVecPad(trunc, native_vector_bytes);

  // Values
  std::vector<llvm::Value*> vloads;
  PrimType table_type = buffer_type.WithLanes(table_elem_count);

  auto table_all =
      MakeValue(BufferLoad(buffer, {
                                       Ramp(IntImm(int32, 0), IntImm(int32, 1), table_elem_count),
                                   }));

  // The number of value vectors should be a power of 2.
  int table_vec_count = llvm::PowerOf2Ceil(GetVectorBytes(table_type) / native_vector_bytes);
  int table_vec_length = native_vector_bytes / GetVectorBytes(buffer_type);
  for (int i = 0; i != table_vec_count; ++i) {
    // CreateVecSlice will generate undefs for elements outside the source vector.
    vloads.push_back(CreateVecSlice(table_all, i * table_vec_length, table_vec_length));
  }

#define VLO(x) Intrinsic(llvm::Intrinsic::hexagon_V6_lo_128B, {x})
#define VHI(x) Intrinsic(llvm::Intrinsic::hexagon_V6_hi_128B, {x})
#define VXOR(x, y) Intrinsic(llvm::Intrinsic::hexagon_V6_vxor_128B, {x, y})
#define VSHUFF(x) Intrinsic(llvm::Intrinsic::hexagon_V6_vshuffb_128B, {x})
#define VSPLATB(x) Intrinsic(llvm::Intrinsic::hexagon_V6_lvsplatb_128B, {x})
#define VLUT32(x, y, z) Intrinsic(llvm::Intrinsic::hexagon_V6_vlutvvbi_128B, {x, y, z})
#define VLUT32_OR(v, x, y, z) \
  Intrinsic(llvm::Intrinsic::hexagon_V6_vlutvvb_oracci_128B, {v, x, y, z})

  // Shuffle table bytes:
  // 127, 63,  126, 62,........68, 4,  67, 3,  66, 2,  65, 1,  64, 0
  std::vector<llvm::Value*> table;
  for (int i = 0; i != table_vec_count; ++i) table.push_back(VSHUFF(vloads[i]));

  // Get each 32 byte sub-table's output
  std::vector<llvm::Value*> results;
  int table_iters = table_elem_count / 32;
  for (int i = 0; i < table_iters; ++i)
    results.push_back(VLUT32(index_pad, table[i / 4], ConstInt32(i % 8)));

  // Combine outputs
  llvm::Value* result = results[0];
  for (int i = 1; i < table_iters; ++i) result = VXOR(result, results[i]);

  llvm::Type* res_type = result->getType();
  llvm::Type* ret_type = DTypeToLLVMType(buffer_type);
  if (res_type == ret_type) {
    return result;
  }

  int res_bits = GetTypeSizeInBits(res_type);
  int ret_bits = GetTypeSizeInBits(ret_type);
  TVM_FFI_ICHECK_GE(res_bits, ret_bits);
  if (ret_bits < res_bits) {
    llvm::Type* res_byte_type = llvm::VectorType::get(t_int8_, res_bits / 8, /*Scalable*/ false);
    result = CreateVecSlice(builder_->CreateBitCast(result, res_byte_type), 0, ret_bits / 8);
  }
  if (result->getType() != ret_type) {
    return builder_->CreateBitCast(result, ret_type);
  }
  return result;

#undef VLUT32_OR
#undef VLUT32
#undef VSPLATB
#undef VSHUFF
#undef VXOR
#undef VHI
#undef VLO
}

namespace {
[[maybe_unused]] std::ostream& operator<<(std::ostream& os, const llvm::Module& m) {
  std::string ms;
  llvm::raw_string_ostream sos(ms);
  sos << m;
  os << sos.str();
  return os;
}

void ProcessLLVMOptions(const std::vector<std::string>& llvm_vec) {
  if (llvm_vec.empty()) return;

  // LLVM options.
  std::vector<const char*> starts;
  std::transform(llvm_vec.begin(), llvm_vec.end(), std::back_inserter(starts),
                 std::mem_fn(&std::string::c_str));
  const char** args = &starts.front();

  llvm::cl::ParseCommandLineOptions(llvm_vec.size(), args);
}
}  // namespace

ffi::Module BuildHexagon(IRModule mod, Target target) {
  LLVMInstance llvm_instance;
  With<LLVMTarget> llvm_target(llvm_instance, target);

  auto split = [](const std::string& str, char delim = ' ') {
    std::vector<std::string> vec;
    std::string tmp;
    for (std::istringstream iss(str); std::getline(iss, tmp, delim);) {
      vec.push_back(tmp);
    }
    return vec;
  };
  std::string llvm_options_str = "llvm";
  if (const auto& llvm_options = target->GetAttr<ffi::Array<ffi::String>>("llvm-options")) {
    for (const ffi::String& s : llvm_options.value()) llvm_options_str += "," + s;
  }
  // Postprocess the LLVM options string: replace '@' with '=', and ',' with ' '.
  for (int i = 0, e = llvm_options_str.size(); i != e; ++i) {
    switch (llvm_options_str[i]) {
      case '@':
        llvm_options_str[i] = '=';
        break;
      case ',':
        llvm_options_str[i] = ' ';
        break;
    }
  }

  // The vector of LLVM options is treated at "argv" from "main(argc, argv)". The entry at
  // position 0 is the name of the executable, and is ignored by the LLVM cl::option parser.
  // Make sure it's set to "llvm" (tvm.target.hexagon does that).
  std::vector<std::string> llvm_options_vec = split(llvm_options_str);
  assert(llvm_options_vec.size() >= 1 && llvm_options_vec[0] == "llvm");
  llvm_options_vec.insert(std::next(llvm_options_vec.begin()),
                          {"-hexagon-small-data-threshold=0",
                           "-force-target-max-vector-interleave=1", "-hexagon-autohvx=1"});

  // Process extra command line options for LLVM. Make sure it's only
  // done once.
  static bool CallOnce = (ProcessLLVMOptions(llvm_options_vec), true);
  (void)CallOnce;

  auto cg = std::make_unique<CodeGenHexagon>();

  std::string entry_func;

  for (auto kv : mod->functions) {
    if (!kv.second->IsInstance<PrimFuncNode>()) {
      // (@jroesch): we relax constraints here, relax functions will just be ignored.
      DLOG(INFO) << "Can only lower IR Module with PrimFuncs, but got " << kv.second->GetTypeKey();
      continue;
    }
    auto f = kv.second.as_or_throw<PrimFunc>();
    if (f->HasNonzeroAttr(tirx::attr::kIsEntryFunc)) {
      auto global_symbol = f->GetAttr<ffi::String>(tvm::attr::kGlobalSymbol);
      TVM_FFI_ICHECK(global_symbol.has_value());
      entry_func = global_symbol.value();
    }
  }

  cg->Init("TVMHexagonModule", llvm_target.get(), std::nullopt, false, false);
  cg->AddFunctionsOrdered(mod->functions.begin(), mod->functions.end());
  if (entry_func.length() != 0) {
    cg->AddMainFunction(entry_func);
  }

  // Uncomment to get the LLVM module right out of codegen, before optimizations.
  // std::cerr << "HexagonModule.0 {\n" << *cg->GetModulePtr() << "}\n";
  std::unique_ptr<llvm::Module> module = cg->Finish();

  enum CodeGenFileType { Asm, Obj, IR, BC };

  auto EmitToString = [&llvm_target](const llvm::Module& m, CodeGenFileType cgft) {
    std::string out;

    if (cgft == IR || cgft == BC) {
      llvm::raw_string_ostream os(out);
      if (cgft == IR)
        m.print(os, nullptr);
      else
        llvm::WriteBitcodeToFile(m, os);
    } else if (cgft == Asm || cgft == Obj) {
#if TVM_LLVM_VERSION <= 170
      auto ft = cgft == Asm ? llvm::CGFT_AssemblyFile : llvm::CGFT_ObjectFile;
#else
      auto ft =
          cgft == Asm ? llvm::CodeGenFileType::AssemblyFile : llvm::CodeGenFileType::ObjectFile;
#endif

      llvm::SmallString<16384> ss;  // Will grow on demand.
      llvm::raw_svector_ostream os(ss);
      std::unique_ptr<llvm::Module> cm = llvm::CloneModule(m);
      llvm::legacy::PassManager pass;
      llvm::TargetMachine* tm = llvm_target->GetOrCreateTargetMachine();
      TVM_FFI_ICHECK(tm->addPassesToEmitFile(pass, os, nullptr, ft) == 0)
          << "Cannot emit target code";
      pass.run(*cm.get());
      out.assign(ss.c_str(), ss.size());
    }

    return out;
  };

  auto SaveToFile = [](const std::string& data, const std::string& suffix) {
    llvm::SmallString<64> file_name;
    int fd;
    std::error_code ec = llvm::sys::fs::createTemporaryFile("tvm", suffix, fd, file_name);
    TVM_FFI_ICHECK_EQ(static_cast<bool>(ec), false) << ec.message();
    llvm::raw_fd_ostream file(fd, true);
    file << data;
    TVM_FFI_ICHECK(!file.has_error()) << file.error().message();
    // If there is an error, execution will never get here, but return
    // {ec, name} anyway to allow caller to handle error conditions.
    // This way the "TVM_FFI_ICHECK" above can be removed with minimal effort.
    return std::make_pair(file.error(), std::string(file_name.c_str()));
  };

  std::string asm_str = EmitToString(*module.get(), Asm);
  std::string obj_str = EmitToString(*module.get(), Obj);
  std::string ir_str = EmitToString(*module.get(), IR);
  std::string bc_str = EmitToString(*module.get(), BC);

  std::string o_name = SaveToFile(obj_str, "o").second;
  std::string so_name(o_name, 0, o_name.size() - 1);
  so_name += "so";

  const auto f = tvm::ffi::Function::GetGlobal("tvm.contrib.hexagon.link_shared");
  TVM_FFI_ICHECK(f.has_value()) << "tvm.contrib.hexagon.link_shared does not to exist, "
                                   "do import tvm.contrib.hexagon";

  ffi::Array<PrimExpr> o_names = {StringImm(o_name)};
  ffi::Map<ffi::String, ffi::String> extra_args;
  if (target->attrs.count("mcpu")) {
    std::string mcpu = target->attrs.at("mcpu").as_or_throw<ffi::String>();
#if TVM_LLVM_VERSION >= 180
    TVM_FFI_ICHECK(llvm::StringRef(mcpu).starts_with("hexagon"))
#else
    TVM_FFI_ICHECK(llvm::StringRef(mcpu).startswith("hexagon"))
#endif
        << "unexpected -mcpu value in target:" << mcpu;
    extra_args.Set("hex_arch", llvm::StringRef(mcpu).drop_front(strlen("hexagon")).str());
  }
  int rc = (*f)(so_name, o_names, extra_args).cast<int>();
  TVM_FFI_ICHECK(rc == 0) << "Failed to link " << so_name;

  // The new design takes Bytes (not a filename); slurp the linked .so into
  // memory.  Receiver materializes the bytes back to a tempfile internally
  // before dlopen.
  std::string so_data;
  runtime::LoadBinaryFromFile(so_name, &so_data);

  // Pack auxiliaries into the unified source map.  Variant lets text blobs
  // (asm/ll) and binary blobs (obj/bc) coexist in their natural form.
  target::HexagonSourceMap source;
  source.Set("s", ffi::Variant<ffi::String, ffi::Bytes>(ffi::String(asm_str)));
  source.Set("ll", ffi::Variant<ffi::String, ffi::Bytes>(ffi::String(ir_str)));
  source.Set("obj", ffi::Variant<ffi::String, ffi::Bytes>(ffi::Bytes(obj_str)));
  source.Set("bc", ffi::Variant<ffi::String, ffi::Bytes>(ffi::Bytes(bc_str)));

  return target::HexagonModuleCreateWithFallback(ffi::Bytes(std::move(so_data)), "so",
                                                 ExtractFuncInfo(mod), std::move(source));
}

void RegisterHexagonCodegen() {
  static bool registered = false;
  if (registered) return;
  registered = true;

  namespace refl = tvm::ffi::reflection;
  refl::GlobalDef()
      .def("target.build.hexagon", BuildHexagon)
      .def_packed("tvm.codegen.llvm.target_hexagon",
                  [](const ffi::PackedArgs& targs, ffi::Any* rv) {
                    *rv = static_cast<void*>(new CodeGenHexagon());
                  });
}

}  // namespace codegen
}  // namespace tvm

#endif  // TVM_LLVM_VERSION
