//===--- OHOS.cpp - OHOS ToolChain Implementations --------*- C++ -*-===//
//
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
// See https://llvm.org/LICENSE.txt for license information.
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
//
//===----------------------------------------------------------------------===//

#include "OHOS.h"
#include "Arch/ARM.h"
#include "CommonArgs.h"
#include "clang/Config/config.h"
#include "clang/Driver/Compilation.h"
#include "clang/Driver/Driver.h"
#include "clang/Driver/DriverDiagnostic.h"
#include "clang/Driver/Options.h"
#include "clang/Driver/SanitizerArgs.h"
#include "llvm/Option/ArgList.h"
#include "llvm/ProfileData/InstrProf.h"
#include "llvm/Support/FileSystem.h"
#include "llvm/Support/Path.h"
#include "llvm/Support/VirtualFileSystem.h"
#include "llvm/Support/ScopedPrinter.h"

using namespace clang::driver;
using namespace clang::driver::toolchains;
using namespace clang::driver::tools;
using namespace clang;
using namespace llvm::opt;
using namespace clang::driver::tools::arm;

using tools::addMultilibFlag;
using tools::addPathIfExists;

static bool findOHOSMuslMultilibs(const Multilib::flags_list &Flags,
                                  DetectedMultilibs &Result) {
  MultilibSet Multilibs;
  Multilibs.push_back(Multilib());
  // -mcpu=cortex-a7
  // -mfloat-abi=soft -mfloat-abi=softfp -mfloat-abi=hard
  // -mfpu=neon-vfpv4
  Multilibs.push_back(
      Multilib("/a7_soft", {}, {}, {"-mcpu=cortex-a7", "-mfloat-abi=soft"}));

  Multilibs.push_back(
      Multilib("/a7_softfp_neon-vfpv4", {}, {},
               {"-mcpu=cortex-a7", "-mfloat-abi=softfp", "-mfpu=neon-vfpv4"}));

  Multilibs.push_back(
      Multilib("/a7_hard_neon-vfpv4", {}, {},
               {"-mcpu=cortex-a7", "-mfloat-abi=hard", "-mfpu=neon-vfpv4"}));

  if (Multilibs.select(Flags, Result.SelectedMultilibs)) {
    Result.Multilibs = Multilibs;
    return true;
  }
  return false;
}

static bool findOHOSMultilibs(const Driver &D,
                                      const ToolChain &TC,
                                      const llvm::Triple &TargetTriple,
                                      StringRef Path, const ArgList &Args,
                                      DetectedMultilibs &Result) {
  Multilib::flags_list Flags;
  bool IsA7 = false;
  if (const Arg *A = Args.getLastArg(options::OPT_mcpu_EQ))
    IsA7 = A->getValue() == StringRef("cortex-a7");
  addMultilibFlag(IsA7, "-mcpu=cortex-a7", Flags);

  bool IsMFPU = false;
  if (const Arg *A = Args.getLastArg(options::OPT_mfpu_EQ))
    IsMFPU = A->getValue() == StringRef("neon-vfpv4");
  addMultilibFlag(IsMFPU, "-mfpu=neon-vfpv4", Flags);

  tools::arm::FloatABI ARMFloatABI = getARMFloatABI(D, TargetTriple, Args);
  addMultilibFlag((ARMFloatABI == tools::arm::FloatABI::Soft),
                  "-mfloat-abi=soft", Flags);
  addMultilibFlag((ARMFloatABI == tools::arm::FloatABI::SoftFP),
                  "-mfloat-abi=softfp", Flags);
  addMultilibFlag((ARMFloatABI == tools::arm::FloatABI::Hard),
                  "-mfloat-abi=hard", Flags);

  return findOHOSMuslMultilibs(Flags, Result);
}

std::string OHOS::getMultiarchTriple(const llvm::Triple &T) const {
  // For most architectures, just use whatever we have rather than trying to be
  // clever.
  switch (T.getArch()) {
  default:
    break;

  // We use the existence of '/lib/<triple>' as a directory to detect some
  // common linux triples that don't quite match the Clang triple for both
  // 32-bit and 64-bit targets. Multiarch fixes its install triples to these
  // regardless of what the actual target triple is.
  case llvm::Triple::arm:
  case llvm::Triple::thumb:
    return T.isOSLiteOS() ? "arm-liteos-ohos" : "arm-linux-ohos";
  case llvm::Triple::riscv32:
    return "riscv32-linux-ohos";
  case llvm::Triple::riscv64:
    return "riscv64-linux-ohos";
  case llvm::Triple::mipsel:
    return "mipsel-linux-ohos";
  case llvm::Triple::x86:
    return "i686-linux-ohos";
  case llvm::Triple::x86_64:
    return "x86_64-linux-ohos";
  case llvm::Triple::aarch64:
    return "aarch64-linux-ohos";
  }
  return T.str();
}

std::string OHOS::getMultiarchTriple(const Driver &D,
                                     const llvm::Triple &TargetTriple,
                                     StringRef SysRoot) const {
  return getMultiarchTriple(TargetTriple);
}

static std::string makePath(const std::initializer_list<std::string> &IL) {
  SmallString<128> P;
  for (const auto &S : IL)
    llvm::sys::path::append(P, S);
  return static_cast<std::string>(P.str());
}

/// OHOS Toolchain
OHOS::OHOS(const Driver &D, const llvm::Triple &Triple, const ArgList &Args)
    : Generic_ELF(D, Triple, Args) {
  std::string SysRoot = computeSysRoot();

  // Select the correct multilib according to the given arguments.
  DetectedMultilibs Result;
  findOHOSMultilibs(D, *this, Triple, "", Args, Result);
  Multilibs = Result.Multilibs;
  SelectedMultilibs = Result.SelectedMultilibs;
  if (!SelectedMultilibs.empty()) {
    SelectedMultilib = SelectedMultilibs.back();
  }

  getFilePaths().clear();
  for (const auto &CandidateLibPath : getArchSpecificLibPaths())
    if (getVFS().exists(CandidateLibPath))
      getFilePaths().push_back(CandidateLibPath);

  getLibraryPaths().clear();
  for (auto &Path : getRuntimePaths())
    if (getVFS().exists(Path))
      getLibraryPaths().push_back(Path);

  // OHOS sysroots contain a library directory for each supported OS
  // version as well as some unversioned libraries in the usual multiarch
  // directory. Support --target=aarch64-linux-ohosX.Y.Z or
  // --target=aarch64-linux-ohosX.Y or --target=aarch64-linux-ohosX
  path_list &Paths = getFilePaths();
  std::string SysRootLibPath = makePath({SysRoot, "usr", "lib"});
  std::string MultiarchTriple = getMultiarchTriple(getTriple());
  addPathIfExists(D, makePath({SysRootLibPath, SelectedMultilib.gccSuffix()}),
                  Paths);
  addPathIfExists(D,
                  makePath({D.Dir, "..", "lib", MultiarchTriple,
                            SelectedMultilib.gccSuffix()}),
                  Paths);

  addPathIfExists(
      D,
      makePath({SysRootLibPath, MultiarchTriple, SelectedMultilib.gccSuffix()}),
      Paths);
}

ToolChain::RuntimeLibType OHOS::GetRuntimeLibType(
    const ArgList &Args) const {
  if (Arg *A = Args.getLastArg(clang::driver::options::OPT_rtlib_EQ)) {
    StringRef Value = A->getValue();
    if (Value != "compiler-rt")
      getDriver().Diag(clang::diag::err_drv_invalid_rtlib_name)
          << A->getAsString(Args);
  }

  return ToolChain::RLT_CompilerRT;
}

ToolChain::CXXStdlibType
OHOS::GetCXXStdlibType(const ArgList &Args) const {
  if (Arg *A = Args.getLastArg(options::OPT_stdlib_EQ)) {
    StringRef Value = A->getValue();
    if (Value != "libc++")
      getDriver().Diag(diag::err_drv_invalid_stdlib_name)
        << A->getAsString(Args);
  }

  return ToolChain::CST_Libcxx;
}

void OHOS::AddClangSystemIncludeArgs(const ArgList &DriverArgs,
                                        ArgStringList &CC1Args) const {
  const Driver &D = getDriver();
  const llvm::Triple &Triple = getTriple();
  std::string SysRoot = computeSysRoot();

  if (DriverArgs.hasArg(options::OPT_nostdinc))
    return;

  if (!DriverArgs.hasArg(options::OPT_nobuiltininc)) {
    SmallString<128> P(D.ResourceDir);
    llvm::sys::path::append(P, "include");
    addSystemInclude(DriverArgs, CC1Args, P);
  }

  if (DriverArgs.hasArg(options::OPT_nostdlibinc))
    return;

  // Check for configure-time C include directories.
  StringRef CIncludeDirs(C_INCLUDE_DIRS);
  if (CIncludeDirs != "") {
    SmallVector<StringRef, 5> dirs;
    CIncludeDirs.split(dirs, ":");
    for (StringRef dir : dirs) {
      StringRef Prefix =
          llvm::sys::path::is_absolute(dir) ? StringRef(SysRoot) : "";
      addExternCSystemInclude(DriverArgs, CC1Args, Prefix + dir);
    }
    return;
  }

  addExternCSystemInclude(DriverArgs, CC1Args,
                          SysRoot + "/usr/include/" +
                              getMultiarchTriple(Triple));
  addExternCSystemInclude(DriverArgs, CC1Args, SysRoot + "/include");
  addExternCSystemInclude(DriverArgs, CC1Args, SysRoot + "/usr/include");
}

void OHOS::AddClangCXXStdlibIncludeArgs(const ArgList &DriverArgs,
                                        ArgStringList &CC1Args) const {
  if (DriverArgs.hasArg(options::OPT_nostdlibinc) ||
      DriverArgs.hasArg(options::OPT_nostdincxx))
    return;

  switch (GetCXXStdlibType(DriverArgs)) {
  case ToolChain::CST_Libcxx: {
    std::string IncPath = makePath({getDriver().Dir, "..", "include"});
    std::string IncTargetPath =
        makePath({IncPath, getMultiarchTriple(getTriple()), "c++", "v1"});
    if (getVFS().exists(IncTargetPath)) {
      addSystemInclude(DriverArgs, CC1Args, makePath({IncPath, "c++", "v1"}));
      addSystemInclude(DriverArgs, CC1Args, IncTargetPath);
    }
    break;
  }

  default:
    llvm_unreachable("invalid stdlib name");
  }
}

void OHOS::AddCXXStdlibLibArgs(const ArgList &Args,
                                  ArgStringList &CmdArgs) const {
  switch (GetCXXStdlibType(Args)) {
  case ToolChain::CST_Libcxx:
    CmdArgs.push_back("-lc++");
    CmdArgs.push_back("-lc++abi");
    CmdArgs.push_back("-lunwind");
    break;

  case ToolChain::CST_Libstdcxx:
    llvm_unreachable("invalid stdlib name");
  }
}

std::string OHOS::computeSysRoot() const {
  std::string SysRoot =
      !getDriver().SysRoot.empty()
          ? getDriver().SysRoot
          : makePath({getDriver().getInstalledDir(), "..", "..", "sysroot"});
  if (!llvm::sys::fs::exists(SysRoot))
    return std::string();

  std::string ArchRoot = makePath({SysRoot, getMultiarchTriple(getTriple())});
  return llvm::sys::fs::exists(ArchRoot) ? ArchRoot : SysRoot;
}

ToolChain::path_list OHOS::getRuntimePaths() const {
  SmallString<128> P;
  path_list Paths;
  const Driver &D = getDriver();
  const llvm::Triple &Triple = getTriple();

  // First try the triple passed to driver as --target=<triple>.
  P.assign(D.ResourceDir);
  llvm::sys::path::append(P, "lib", D.getTargetTriple(), SelectedMultilib.gccSuffix());
  Paths.push_back(P.c_str());

  // Second try the normalized triple.
  P.assign(D.ResourceDir);
  llvm::sys::path::append(P, "lib", Triple.str(), SelectedMultilib.gccSuffix());
  Paths.push_back(P.c_str());

  // Third try the effective triple.
  P.assign(D.ResourceDir);
  std::string SysRoot = computeSysRoot();
  llvm::sys::path::append(P, "lib", getMultiarchTriple(Triple),
                          SelectedMultilib.gccSuffix());
  Paths.push_back(P.c_str());

  return Paths;
}

std::string OHOS::getDynamicLinker(const ArgList &Args) const {
  const llvm::Triple &Triple = getTriple();
  const llvm::Triple::ArchType Arch = getArch();

  assert(Triple.isMusl());
  std::string ArchName;
  bool IsArm = false;

  switch (Arch) {
  case llvm::Triple::arm:
  case llvm::Triple::thumb:
    ArchName = "arm";
    IsArm = true;
    break;
  case llvm::Triple::armeb:
  case llvm::Triple::thumbeb:
    ArchName = "armeb";
    IsArm = true;
    break;
  default:
    ArchName = Triple.getArchName().str();
  }
  if (IsArm &&
      (tools::arm::getARMFloatABI(*this, Args) == tools::arm::FloatABI::Hard))
    ArchName += "hf";

  return "/lib/ld-musl-" + ArchName + ".so.1";
}

std::string OHOS::getCompilerRT(const ArgList &Args, StringRef Component,
                                FileType Type) const {
  SmallString<128> Path(getDriver().ResourceDir);
  llvm::sys::path::append(Path, "lib", getMultiarchTriple(getTriple()),
                          SelectedMultilib.gccSuffix());
  const char *Prefix =
      Type == ToolChain::FT_Object ? "" : "lib";
  const char *Suffix;
  switch (Type) {
  case ToolChain::FT_Object:
    Suffix = ".o";
    break;
  case ToolChain::FT_Static:
    Suffix = ".a";
    break;
  case ToolChain::FT_Shared:
    Suffix = ".so";
    break;
  }
  llvm::sys::path::append(
      Path, Prefix + Twine("clang_rt.") + Component + Suffix);
  return static_cast<std::string>(Path.str());
}

void OHOS::addExtraOpts(llvm::opt::ArgStringList &CmdArgs) const {
  CmdArgs.push_back("-z");
  CmdArgs.push_back("now");
  CmdArgs.push_back("-z");
  CmdArgs.push_back("relro");
  CmdArgs.push_back("-z");
  CmdArgs.push_back("max-page-size=4096");
  // .gnu.hash section is not compatible with the MIPS target
  if (getArch() != llvm::Triple::mipsel)
    CmdArgs.push_back("--hash-style=both");
#ifdef ENABLE_LINKER_BUILD_ID
  CmdArgs.push_back("--build-id");
#endif
  CmdArgs.push_back("--enable-new-dtags");
}

SanitizerMask OHOS::getSupportedSanitizers() const {
  SanitizerMask Res = ToolChain::getSupportedSanitizers();
  Res |= SanitizerKind::Address;
  Res |= SanitizerKind::PointerCompare;
  Res |= SanitizerKind::PointerSubtract;
  Res |= SanitizerKind::Fuzzer;
  Res |= SanitizerKind::FuzzerNoLink;
  Res |= SanitizerKind::Memory;
  Res |= SanitizerKind::Vptr;
  Res |= SanitizerKind::SafeStack;
  Res |= SanitizerKind::Scudo;
  // TODO: kASAN for liteos ??
  // TODO: Support TSAN and HWASAN and update mask.
  return Res;
}

// TODO: Make a base class for Linux and OHOS and move this there.
void OHOS::addProfileRTLibs(const llvm::opt::ArgList &Args,
                             llvm::opt::ArgStringList &CmdArgs) const {
  // Add linker option -u__llvm_profile_runtime to cause runtime
  // initialization module to be linked in.
  if (needsProfileRT(Args))
    CmdArgs.push_back(Args.MakeArgString(
        Twine("-u", llvm::getInstrProfRuntimeHookVarName())));
  ToolChain::addProfileRTLibs(Args, CmdArgs);
}

ToolChain::path_list OHOS::getArchSpecificLibPaths() const {
  ToolChain::path_list Paths;
  llvm::Triple Triple = getTriple();
  Paths.push_back(
      makePath({getDriver().ResourceDir, "lib", getMultiarchTriple(Triple)}));
  return Paths;
}

ToolChain::UnwindLibType OHOS::GetUnwindLibType(const llvm::opt::ArgList &Args) const {
  if (Args.getLastArg(options::OPT_unwindlib_EQ))
    return Generic_ELF::GetUnwindLibType(Args);
  return GetDefaultUnwindLibType();
}