//===-- mangling.cpp ------------------------------------------------------===//
//
//                         LDC – the LLVM D compiler
//
// This file is distributed under the BSD-style LDC license. See the LICENSE
// file for details.
//
//===----------------------------------------------------------------------===//
//
// Tries to centralize functionality for mangling of symbols.
//
//===----------------------------------------------------------------------===//

#include "gen/mangling.h"

#include "dmd/declaration.h"
#include "dmd/dsymbol.h"
#include "dmd/identifier.h"
#include "dmd/mangle.h"
#include "dmd/module.h"
#include "gen/abi/abi.h"
#include "gen/irstate.h"
#include "gen/to_string.h"
#include "llvm/Support/MD5.h"

using namespace dmd;

namespace {

// TODO: Disable hashing of symbols that are defined in libdruntime and
// libphobos. This would enable hashing thresholds below the largest symbol in
// libdruntime/phobos.

bool shouldHashAggrName(llvm::StringRef name) {
  /// Add extra chars to the length of aggregate names to account for
  /// the additional D mangling suffix and prefix
  return (global.params.hashThreshold != 0) &&
         ((name.size() + 11) > global.params.hashThreshold);
}

llvm::SmallString<32> hashName(llvm::StringRef name) {
  llvm::MD5 hasher;
  hasher.update(name);
  llvm::MD5::MD5Result result;
  hasher.final(result);
  llvm::SmallString<32> hashStr;
  llvm::MD5::stringifyResult(result, hashStr);

  return hashStr;
}

/// Hashes the symbol name and prefixes the hash with some recognizable parts of
/// the full symbol name. The prefixing means that the hashed name may be larger
/// than the input when identifiers are very long and the hash threshold is low.
/// Demangled hashed name is:
/// module.L<line_no>.<hash>.<top aggregate>.<identifier>
std::string hashSymbolName(llvm::StringRef name, Dsymbol *symb) {
  std::string ret;

  // module
  {
    auto moddecl = symb->getModule()->md;
    assert(moddecl);
    for (size_t i = 0; i < moddecl->packages.length; ++i) {
      llvm::StringRef str = moddecl->packages.ptr[i]->toChars();
      ret += ldc::to_string(str.size());
      ret += str;
    }
    llvm::StringRef str = moddecl->id->toChars();
    ret += ldc::to_string(str.size());
    ret += str;
  }

  // source line number
  auto lineNo = ldc::to_string(symb->loc.linnum());
  ret += ldc::to_string(lineNo.size()+1);
  ret += 'L';
  ret += lineNo;

  // MD5 hash
  auto hashedName = hashName(name);
  ret += "33_"; // add underscore to delimit the 33 character count
  ret += hashedName;

  // top aggregate
  if (auto agg = symb->isMember()) {
    llvm::StringRef topaggr = agg->ident->toChars();
    ret += ldc::to_string(topaggr.size());
    ret += topaggr;
  }

  // identifier
  llvm::StringRef identifier = symb->toChars();
  ret += ldc::to_string(identifier.size());
  ret += identifier;

  return ret;
}
}

std::string getIRMangledName(FuncDeclaration *fdecl, LINK link) {
  std::string mangledName = mangleExact(fdecl);
  if (fdecl->adFlags & 4) { // nounderscore
    mangledName.insert(0, "\1");
  }

  // Hash the name if necessary
  if (((link == LINK::d) || (link == LINK::default_)) &&
      (global.params.hashThreshold != 0) &&
      (mangledName.length() > global.params.hashThreshold)) {

    auto hashedName = hashSymbolName(mangledName, fdecl);
    mangledName = "_D" + hashedName + "Z";
  }

  // TODO: Cache the result?

  return getIRMangledFuncName(std::move(mangledName), link);
}

std::string getIRMangledName(VarDeclaration *vd) {
  OutBuffer mangleBuf;
  mangleToBuffer(vd, mangleBuf);

  // TODO: is hashing of variable names necessary?

  // TODO: Cache the result?

  return getIRMangledVarName(mangleBuf.peekChars(), vd->resolvedLinkage());
}

std::string getIRMangledFuncName(std::string baseMangle, LINK link) {
  return baseMangle[0] == '\1'
             ? std::move(baseMangle)
             : gABI->mangleFunctionForLLVM(std::move(baseMangle), link);
}

std::string getIRMangledVarName(std::string baseMangle, LINK link) {
  return baseMangle[0] == '\1'
             ? std::move(baseMangle)
             : gABI->mangleVariableForLLVM(std::move(baseMangle), link);
}

std::string getIRMangledAggregateName(AggregateDeclaration *ad,
                                      const char *suffix) {
  std::string ret = "_D";

  OutBuffer mangleBuf;
  mangleToBuffer(ad, mangleBuf);
  llvm::StringRef mangledAggrName = mangleBuf.peekChars();

  if (shouldHashAggrName(mangledAggrName)) {
    ret += hashSymbolName(mangledAggrName, ad);
  } else {
    ret += mangledAggrName;
  }

  if (suffix)
    ret += suffix;

  return getIRMangledVarName(std::move(ret), LINK::d);
}

std::string getIRMangledInitSymbolName(AggregateDeclaration *aggrdecl) {
  return getIRMangledAggregateName(aggrdecl, "6__initZ");
}

std::string getIRMangledVTableSymbolName(AggregateDeclaration *aggrdecl) {
  return getIRMangledAggregateName(aggrdecl, "6__vtblZ");
}

std::string getIRMangledClassInfoSymbolName(AggregateDeclaration *aggrdecl) {
  const char *suffix =
      aggrdecl->isInterfaceDeclaration() ? "11__InterfaceZ" : "7__ClassZ";
  return getIRMangledAggregateName(aggrdecl, suffix);
}

std::string getIRMangledInterfaceInfosSymbolName(ClassDeclaration *cd) {
  OutBuffer mangledName;
  mangledName.writestring("_D");
  mangleToBuffer(cd, mangledName);
  mangledName.writestring("16__interfaceInfosZ");
  return getIRMangledVarName(mangledName.peekChars(), LINK::d);
}

std::string getIRMangledModuleInfoSymbolName(Module *module) {
  OutBuffer mangledName;
  mangledName.writestring("_D");
  mangleToBuffer(module, mangledName);
  mangledName.writestring("12__ModuleInfoZ");
  return getIRMangledVarName(mangledName.peekChars(), LINK::d);
}

std::string getIRMangledModuleRefSymbolName(const char *moduleMangle) {
  return getIRMangledVarName(
      (llvm::Twine("_D") + moduleMangle + "11__moduleRefZ").str(), LINK::d);
}
