//===-- MSVC.cpp - MSVC ToolChain Implementations -------------------------===//
//
// 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 "MSVC.h"
#include "Darwin.h"
#include "clang/Config/config.h"
#include "clang/Driver/CommonArgs.h"
#include "clang/Driver/Compilation.h"
#include "clang/Driver/Driver.h"
#include "clang/Driver/SanitizerArgs.h"
#include "clang/Options/Options.h"
#include "llvm/Option/Arg.h"
#include "llvm/Option/ArgList.h"
#include "llvm/Support/ConvertUTF.h"
#include "llvm/Support/ErrorHandling.h"
#include "llvm/Support/FileSystem.h"
#include "llvm/Support/Path.h"
#include "llvm/Support/Process.h"
#include "llvm/Support/VirtualFileSystem.h"
#include "llvm/TargetParser/Host.h"
#include <cstdio>

#ifdef _WIN32
  #define WIN32_LEAN_AND_MEAN
  #define NOGDI
  #ifndef NOMINMAX
    #define NOMINMAX
  #endif
  #include <windows.h>
#endif

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

static bool canExecute(llvm::vfs::FileSystem &VFS, StringRef Path) {
  auto Status = VFS.status(Path);
  if (!Status)
    return false;
  return (Status->getPermissions() & llvm::sys::fs::perms::all_exe) != 0;
}

// Try to find Exe from a Visual Studio distribution.  This first tries to find
// an installed copy of Visual Studio and, failing that, looks in the PATH,
// making sure that whatever executable that's found is not a same-named exe
// from clang itself to prevent clang from falling back to itself.
static std::string FindVisualStudioExecutable(const ToolChain &TC,
                                              const char *Exe) {
  const auto &MSVC = static_cast<const toolchains::MSVCToolChain &>(TC);
  SmallString<128> FilePath(
      MSVC.getSubDirectoryPath(llvm::SubDirectoryType::Bin));
  llvm::sys::path::append(FilePath, Exe);
  return std::string(canExecute(TC.getVFS(), FilePath) ? FilePath.str() : Exe);
}

void visualstudio::Linker::ConstructJob(Compilation &C, const JobAction &JA,
                                        const InputInfo &Output,
                                        const InputInfoList &Inputs,
                                        const ArgList &Args,
                                        const char *LinkingOutput) const {
  ArgStringList CmdArgs;

  auto &TC = static_cast<const toolchains::MSVCToolChain &>(getToolChain());

  assert((Output.isFilename() || Output.isNothing()) && "invalid output");
  if (Output.isFilename())
    CmdArgs.push_back(
        Args.MakeArgString(std::string("-out:") + Output.getFilename()));

  if (Args.hasArg(options::OPT_marm64x))
    CmdArgs.push_back("-machine:arm64x");
  else if (TC.getTriple().isWindowsArm64EC())
    CmdArgs.push_back("-machine:arm64ec");

  if (!Args.hasArg(options::OPT_nostdlib, options::OPT_nostartfiles) &&
      !C.getDriver().IsCLMode() && !C.getDriver().IsFlangMode()) {
    CmdArgs.push_back("-defaultlib:libcmt");
    CmdArgs.push_back("-defaultlib:oldnames");
  }

  // If the VC environment hasn't been configured (perhaps because the user
  // did not run vcvarsall), try to build a consistent link environment.  If
  // the environment variable is set however, assume the user knows what
  // they're doing. If the user passes /vctoolsdir or /winsdkdir, trust that
  // over env vars.
  if (const Arg *A = Args.getLastArg(options::OPT__SLASH_diasdkdir,
                                     options::OPT__SLASH_winsysroot)) {
    // cl.exe doesn't find the DIA SDK automatically, so this too requires
    // explicit flags and doesn't automatically look in "DIA SDK" relative
    // to the path we found for VCToolChainPath.
    llvm::SmallString<128> DIAPath(A->getValue());
    if (A->getOption().getID() == options::OPT__SLASH_winsysroot)
      llvm::sys::path::append(DIAPath, "DIA SDK");

    // The DIA SDK always uses the legacy vc arch, even in new MSVC versions.
    llvm::sys::path::append(DIAPath, "lib",
                            llvm::archToLegacyVCArch(TC.getArch()));
    CmdArgs.push_back(Args.MakeArgString(Twine("-libpath:") + DIAPath));
  }
  if (!llvm::sys::Process::GetEnv("LIB") ||
      Args.hasArg(options::OPT__SLASH_vctoolsdir,
                  options::OPT__SLASH_vctoolsversion,
                  options::OPT__SLASH_winsysroot)) {
    CmdArgs.push_back(Args.MakeArgString(
        Twine("-libpath:") +
        TC.getSubDirectoryPath(llvm::SubDirectoryType::Lib)));
    CmdArgs.push_back(Args.MakeArgString(
        Twine("-libpath:") +
        TC.getSubDirectoryPath(llvm::SubDirectoryType::Lib, "atlmfc")));
  }
  if (!llvm::sys::Process::GetEnv("LIB") ||
      Args.hasArg(options::OPT__SLASH_winsdkdir,
                  options::OPT__SLASH_winsdkversion,
                  options::OPT__SLASH_winsysroot)) {
    if (TC.useUniversalCRT()) {
      std::string UniversalCRTLibPath;
      if (TC.getUniversalCRTLibraryPath(Args, UniversalCRTLibPath))
        CmdArgs.push_back(
            Args.MakeArgString(Twine("-libpath:") + UniversalCRTLibPath));
    }
    std::string WindowsSdkLibPath;
    if (TC.getWindowsSDKLibraryPath(Args, WindowsSdkLibPath))
      CmdArgs.push_back(
          Args.MakeArgString(std::string("-libpath:") + WindowsSdkLibPath));
  }

  if (!C.getDriver().IsCLMode() && Args.hasArg(options::OPT_L))
    for (const auto &LibPath : Args.getAllArgValues(options::OPT_L))
      CmdArgs.push_back(Args.MakeArgString("-libpath:" + LibPath));

  if (C.getDriver().IsFlangMode() &&
      !Args.hasArg(options::OPT_nostdlib, options::OPT_nodefaultlibs)) {
    TC.addFortranRuntimeLibraryPath(Args, CmdArgs);
    TC.addFortranRuntimeLibs(Args, CmdArgs);

    // Inform the MSVC linker that we're generating a console application, i.e.
    // one with `main` as the "user-defined" entry point. The `main` function is
    // defined in flang's runtime libraries.
    CmdArgs.push_back("/subsystem:console");
  }

  // Add the compiler-rt library directories to libpath if they exist to help
  // the linker find the various sanitizer, builtin, and profiling runtimes.
  for (const auto &LibPath : TC.getLibraryPaths()) {
    if (TC.getVFS().exists(LibPath))
      CmdArgs.push_back(Args.MakeArgString("-libpath:" + LibPath));
  }
  auto CRTPath = TC.getCompilerRTPath();
  if (TC.getVFS().exists(CRTPath))
    CmdArgs.push_back(Args.MakeArgString("-libpath:" + CRTPath));

  CmdArgs.push_back("-nologo");

  if (Args.hasArg(options::OPT_g_Group, options::OPT__SLASH_Z7))
    CmdArgs.push_back("-debug");

  // If we specify /hotpatch, let the linker add padding in front of each
  // function, like MSVC does.
  if (Args.hasArg(options::OPT_fms_hotpatch, options::OPT__SLASH_hotpatch))
    CmdArgs.push_back("-functionpadmin");

  // Pass on /Brepro if it was passed to the compiler.
  // Note that /Brepro maps to -mno-incremental-linker-compatible.
  bool DefaultIncrementalLinkerCompatible =
      C.getDefaultToolChain().getTriple().isWindowsMSVCEnvironment();
  if (!Args.hasFlag(options::OPT_mincremental_linker_compatible,
                    options::OPT_mno_incremental_linker_compatible,
                    DefaultIncrementalLinkerCompatible))
    CmdArgs.push_back("-Brepro");

  bool DLL = Args.hasArg(options::OPT__SLASH_LD, options::OPT__SLASH_LDd,
                         options::OPT_shared);
  if (DLL) {
    CmdArgs.push_back(Args.MakeArgString("-dll"));

    SmallString<128> ImplibName(Output.getFilename());
    llvm::sys::path::replace_extension(ImplibName, "lib");
    CmdArgs.push_back(Args.MakeArgString(std::string("-implib:") + ImplibName));
  }

  if (TC.getSanitizerArgs(Args).needsFuzzer()) {
    if (!Args.hasArg(options::OPT_shared))
      CmdArgs.push_back(
          Args.MakeArgString(std::string("-wholearchive:") +
                             TC.getCompilerRTArgString(Args, "fuzzer")));
    CmdArgs.push_back(Args.MakeArgString("-debug"));
    // Prevent the linker from padding sections we use for instrumentation
    // arrays.
    CmdArgs.push_back(Args.MakeArgString("-incremental:no"));
  }

  if (TC.getSanitizerArgs(Args).needsAsanRt()) {
    CmdArgs.push_back(Args.MakeArgString("-debug"));
    CmdArgs.push_back(Args.MakeArgString("-incremental:no"));
    CmdArgs.push_back(TC.getCompilerRTArgString(Args, "asan_dynamic"));
    auto defines = Args.getAllArgValues(options::OPT_D);
    if (Args.hasArg(options::OPT__SLASH_MD, options::OPT__SLASH_MDd) ||
        llvm::is_contained(defines, "_DLL")) {
      // Make sure the dynamic runtime thunk is not optimized out at link time
      // to ensure proper SEH handling.
      CmdArgs.push_back(Args.MakeArgString(
          TC.getArch() == llvm::Triple::x86
              ? "-include:___asan_seh_interceptor"
              : "-include:__asan_seh_interceptor"));
      // Make sure the linker consider all object files from the dynamic runtime
      // thunk.
      CmdArgs.push_back(Args.MakeArgString(
          std::string("-wholearchive:") +
          TC.getCompilerRT(Args, "asan_dynamic_runtime_thunk")));
    } else {
      // Make sure the linker consider all object files from the static runtime
      // thunk.
      CmdArgs.push_back(Args.MakeArgString(
          std::string("-wholearchive:") +
          TC.getCompilerRT(Args, "asan_static_runtime_thunk")));
    }
  }

  if (C.getDriver().isUsingLTO()) {
    if (Arg *A = tools::getLastProfileSampleUseArg(Args))
      CmdArgs.push_back(Args.MakeArgString(std::string("-lto-sample-profile:") +
                                           A->getValue()));
  }
  Args.AddAllArgValues(CmdArgs, options::OPT__SLASH_link);

  // Control Flow Guard checks
  for (const Arg *A : Args.filtered(options::OPT__SLASH_guard)) {
    StringRef GuardArgs = A->getValue();
    if (GuardArgs.equals_insensitive("cf") ||
        GuardArgs.equals_insensitive("cf,nochecks")) {
      // MSVC doesn't yet support the "nochecks" modifier.
      CmdArgs.push_back("-guard:cf");
    } else if (GuardArgs.equals_insensitive("cf-")) {
      CmdArgs.push_back("-guard:cf-");
    } else if (GuardArgs.equals_insensitive("ehcont")) {
      CmdArgs.push_back("-guard:ehcont");
    } else if (GuardArgs.equals_insensitive("ehcont-")) {
      CmdArgs.push_back("-guard:ehcont-");
    }
  }

  if (Args.hasFlag(options::OPT_fopenmp, options::OPT_fopenmp_EQ,
                   options::OPT_fno_openmp, false)) {
    CmdArgs.push_back("-nodefaultlib:vcomp.lib");
    CmdArgs.push_back("-nodefaultlib:vcompd.lib");
    CmdArgs.push_back(Args.MakeArgString(std::string("-libpath:") +
                                         TC.getDriver().Dir + "/../lib"));
    switch (TC.getDriver().getOpenMPRuntime(Args)) {
    case Driver::OMPRT_OMP:
      CmdArgs.push_back("-defaultlib:libomp.lib");
      break;
    case Driver::OMPRT_IOMP5:
      CmdArgs.push_back("-defaultlib:libiomp5md.lib");
      break;
    case Driver::OMPRT_GOMP:
      break;
    case Driver::OMPRT_Unknown:
      // Already diagnosed.
      break;
    }
  }

  // Add compiler-rt lib in case if it was explicitly
  // specified as an argument for --rtlib option.
  if (!Args.hasArg(options::OPT_nostdlib)) {
    AddRunTimeLibs(TC, TC.getDriver(), CmdArgs, Args);
  }

  StringRef Linker = Args.getLastArgValue(options::OPT_fuse_ld_EQ,
                                          TC.getDriver().getPreferredLinker());
  if (Linker.empty())
    Linker = "link";
  // We need to translate 'lld' into 'lld-link'.
  else if (Linker.equals_insensitive("lld"))
    Linker = "lld-link";

  if (Linker == "lld-link") {
    for (Arg *A : Args.filtered(options::OPT_vfsoverlay))
      CmdArgs.push_back(
          Args.MakeArgString(std::string("/vfsoverlay:") + A->getValue()));

    if (C.getDriver().isUsingLTO() &&
        Args.hasFlag(options::OPT_gsplit_dwarf, options::OPT_gno_split_dwarf,
                     false))
      CmdArgs.push_back(Args.MakeArgString(Twine("/dwodir:") +
                                           Output.getFilename() + "_dwo"));
  }

  // Add filenames, libraries, and other linker inputs.
  for (const auto &Input : Inputs) {
    if (Input.isFilename()) {
      CmdArgs.push_back(Input.getFilename());
      continue;
    }

    const Arg &A = Input.getInputArg();

    // Render -l options differently for the MSVC linker.
    if (A.getOption().matches(options::OPT_l)) {
      StringRef Lib = A.getValue();
      const char *LinkLibArg;
      if (Lib.ends_with(".lib"))
        LinkLibArg = Args.MakeArgString(Lib);
      else
        LinkLibArg = Args.MakeArgString(Lib + ".lib");
      CmdArgs.push_back(LinkLibArg);
      continue;
    }

    // Otherwise, this is some other kind of linker input option like -Wl, -z,
    // or -L. Render it, even if MSVC doesn't understand it.
    A.renderAsInput(Args, CmdArgs);
  }

  addHIPRuntimeLibArgs(TC, C, Args, CmdArgs);

  TC.addProfileRTLibs(Args, CmdArgs);

  std::vector<const char *> Environment;

  // We need to special case some linker paths. In the case of the regular msvc
  // linker, we need to use a special search algorithm.
  llvm::SmallString<128> linkPath;
  if (Linker.equals_insensitive("link")) {
    // If we're using the MSVC linker, it's not sufficient to just use link
    // from the program PATH, because other environments like GnuWin32 install
    // their own link.exe which may come first.
    linkPath = FindVisualStudioExecutable(TC, "link.exe");

    if (!TC.FoundMSVCInstall() && !canExecute(TC.getVFS(), linkPath)) {
      llvm::SmallString<128> ClPath;
      ClPath = TC.GetProgramPath("cl.exe");
      if (canExecute(TC.getVFS(), ClPath)) {
        linkPath = llvm::sys::path::parent_path(ClPath);
        llvm::sys::path::append(linkPath, "link.exe");
        if (!canExecute(TC.getVFS(), linkPath))
          C.getDriver().Diag(clang::diag::warn_drv_msvc_not_found);
      } else {
        C.getDriver().Diag(clang::diag::warn_drv_msvc_not_found);
      }
    }

    // Clang handles passing the proper asan libs to the linker, which goes
    // against link.exe's /INFERASANLIBS which automatically finds asan libs.
    if (TC.getSanitizerArgs(Args).needsAsanRt())
      CmdArgs.push_back("/INFERASANLIBS:NO");

#ifdef _WIN32
    // When cross-compiling with VS2017 or newer, link.exe expects to have
    // its containing bin directory at the top of PATH, followed by the
    // native target bin directory.
    // e.g. when compiling for x86 on an x64 host, PATH should start with:
    // /bin/Hostx64/x86;/bin/Hostx64/x64
    // This doesn't attempt to handle llvm::ToolsetLayout::DevDivInternal.
    if (TC.getIsVS2017OrNewer() &&
        llvm::Triple(llvm::sys::getProcessTriple()).getArch() != TC.getArch()) {
      auto HostArch = llvm::Triple(llvm::sys::getProcessTriple()).getArch();

      auto EnvBlockWide =
          std::unique_ptr<wchar_t[], decltype(&FreeEnvironmentStringsW)>(
              GetEnvironmentStringsW(), FreeEnvironmentStringsW);
      if (!EnvBlockWide)
        goto SkipSettingEnvironment;

      size_t EnvCount = 0;
      size_t EnvBlockLen = 0;
      while (EnvBlockWide[EnvBlockLen] != L'\0') {
        ++EnvCount;
        EnvBlockLen += std::wcslen(&EnvBlockWide[EnvBlockLen]) +
                       1 /*string null-terminator*/;
      }
      ++EnvBlockLen; // add the block null-terminator

      std::string EnvBlock;
      if (!llvm::convertUTF16ToUTF8String(
              llvm::ArrayRef<char>(reinterpret_cast<char *>(EnvBlockWide.get()),
                                   EnvBlockLen * sizeof(EnvBlockWide[0])),
              EnvBlock))
        goto SkipSettingEnvironment;

      Environment.reserve(EnvCount);

      // Now loop over each string in the block and copy them into the
      // environment vector, adjusting the PATH variable as needed when we
      // find it.
      for (const char *Cursor = EnvBlock.data(); *Cursor != '\0';) {
        llvm::StringRef EnvVar(Cursor);
        if (EnvVar.starts_with_insensitive("path=")) {
          constexpr size_t PrefixLen = 5; // strlen("path=")
          Environment.push_back(Args.MakeArgString(
              EnvVar.substr(0, PrefixLen) +
              TC.getSubDirectoryPath(llvm::SubDirectoryType::Bin) +
              llvm::Twine(llvm::sys::EnvPathSeparator) +
              TC.getSubDirectoryPath(llvm::SubDirectoryType::Bin, HostArch) +
              (EnvVar.size() > PrefixLen
                   ? llvm::Twine(llvm::sys::EnvPathSeparator) +
                         EnvVar.substr(PrefixLen)
                   : "")));
        } else {
          Environment.push_back(Args.MakeArgString(EnvVar));
        }
        Cursor += EnvVar.size() + 1 /*null-terminator*/;
      }
    }
  SkipSettingEnvironment:;
#endif
  } else {
    linkPath = TC.GetProgramPath(Linker.str().c_str());
  }

  auto LinkCmd = std::make_unique<Command>(
      JA, *this, ResponseFileSupport::AtFileUTF16(),
      Args.MakeArgString(linkPath), CmdArgs, Inputs, Output);
  if (!Environment.empty())
    LinkCmd->setEnvironment(Environment);
  C.addCommand(std::move(LinkCmd));
}

MSVCToolChain::MSVCToolChain(const Driver &D, const llvm::Triple &Triple,
                             const ArgList &Args)
    : ToolChain(D, Triple, Args), CudaInstallation(D, Triple, Args),
      RocmInstallation(D, Triple, Args), SYCLInstallation(D, Triple, Args) {
  getProgramPaths().push_back(getDriver().Dir);

  std::optional<llvm::StringRef> VCToolsDir, VCToolsVersion;
  if (Arg *A = Args.getLastArg(options::OPT__SLASH_vctoolsdir))
    VCToolsDir = A->getValue();
  if (Arg *A = Args.getLastArg(options::OPT__SLASH_vctoolsversion))
    VCToolsVersion = A->getValue();
  if (Arg *A = Args.getLastArg(options::OPT__SLASH_winsdkdir))
    WinSdkDir = A->getValue();
  if (Arg *A = Args.getLastArg(options::OPT__SLASH_winsdkversion))
    WinSdkVersion = A->getValue();
  if (Arg *A = Args.getLastArg(options::OPT__SLASH_winsysroot))
    WinSysRoot = A->getValue();

  // Check the command line first, that's the user explicitly telling us what to
  // use. Check the environment next, in case we're being invoked from a VS
  // command prompt. Failing that, just try to find the newest Visual Studio
  // version we can and use its default VC toolchain.
  llvm::findVCToolChainViaCommandLine(getVFS(), VCToolsDir, VCToolsVersion,
                                      WinSysRoot, VCToolChainPath, VSLayout) ||
      llvm::findVCToolChainViaEnvironment(getVFS(), VCToolChainPath,
                                          VSLayout) ||
      llvm::findVCToolChainViaSetupConfig(getVFS(), VCToolsVersion,
                                          VCToolChainPath, VSLayout) ||
      llvm::findVCToolChainViaRegistry(VCToolChainPath, VSLayout);
}

Tool *MSVCToolChain::buildLinker() const {
  return new tools::visualstudio::Linker(*this);
}

Tool *MSVCToolChain::buildAssembler() const {
  if (getTriple().isOSBinFormatMachO())
    return new tools::darwin::Assembler(*this);
  getDriver().Diag(clang::diag::err_no_external_assembler);
  return nullptr;
}

ToolChain::UnwindTableLevel
MSVCToolChain::getDefaultUnwindTableLevel(const ArgList &Args) const {
  // Don't emit unwind tables by default for MachO targets.
  if (getTriple().isOSBinFormatMachO())
    return UnwindTableLevel::None;

  // All non-x86_32 Windows targets require unwind tables. However, LLVM
  // doesn't know how to generate them for all targets, so only enable
  // the ones that are actually implemented.
  if (getArch() == llvm::Triple::x86_64 || getArch() == llvm::Triple::arm ||
      getArch() == llvm::Triple::thumb || getArch() == llvm::Triple::aarch64)
    return UnwindTableLevel::Asynchronous;

  return UnwindTableLevel::None;
}

bool MSVCToolChain::isPICDefault() const {
  return getArch() == llvm::Triple::x86_64 ||
         getArch() == llvm::Triple::aarch64;
}

bool MSVCToolChain::isPIEDefault(const llvm::opt::ArgList &Args) const {
  return false;
}

bool MSVCToolChain::isPICDefaultForced() const {
  return getArch() == llvm::Triple::x86_64 ||
         getArch() == llvm::Triple::aarch64;
}

void MSVCToolChain::AddCudaIncludeArgs(const ArgList &DriverArgs,
                                       ArgStringList &CC1Args) const {
  CudaInstallation->AddCudaIncludeArgs(DriverArgs, CC1Args);
}

void MSVCToolChain::AddHIPIncludeArgs(const ArgList &DriverArgs,
                                      ArgStringList &CC1Args) const {
  RocmInstallation->AddHIPIncludeArgs(DriverArgs, CC1Args);
}

void MSVCToolChain::addSYCLIncludeArgs(const ArgList &DriverArgs,
                                       ArgStringList &CC1Args) const {
  SYCLInstallation->addSYCLIncludeArgs(DriverArgs, CC1Args);
}

void MSVCToolChain::AddHIPRuntimeLibArgs(const ArgList &Args,
                                         ArgStringList &CmdArgs) const {
  CmdArgs.append({Args.MakeArgString(StringRef("-libpath:") +
                                     RocmInstallation->getLibPath()),
                  "amdhip64.lib"});
}

void MSVCToolChain::printVerboseInfo(raw_ostream &OS) const {
  CudaInstallation->print(OS);
  RocmInstallation->print(OS);
}

std::string
MSVCToolChain::getSubDirectoryPath(llvm::SubDirectoryType Type,
                                   llvm::StringRef SubdirParent) const {
  return llvm::getSubDirectoryPath(Type, VSLayout, VCToolChainPath, getArch(),
                                   SubdirParent);
}

std::string
MSVCToolChain::getSubDirectoryPath(llvm::SubDirectoryType Type,
                                   llvm::Triple::ArchType TargetArch) const {
  return llvm::getSubDirectoryPath(Type, VSLayout, VCToolChainPath, TargetArch,
                                   "");
}

// Find the most recent version of Universal CRT or Windows 10 SDK.
// vcvarsqueryregistry.bat from Visual Studio 2015 sorts entries in the include
// directory by name and uses the last one of the list.
// So we compare entry names lexicographically to find the greatest one.
// Gets the library path required to link against the Windows SDK.
bool MSVCToolChain::getWindowsSDKLibraryPath(const ArgList &Args,
                                             std::string &path) const {
  std::string sdkPath;
  int sdkMajor = 0;
  std::string windowsSDKIncludeVersion;
  std::string windowsSDKLibVersion;

  path.clear();
  if (!llvm::getWindowsSDKDir(getVFS(), WinSdkDir, WinSdkVersion, WinSysRoot,
                              sdkPath, sdkMajor, windowsSDKIncludeVersion,
                              windowsSDKLibVersion))
    return false;

  llvm::SmallString<128> libPath(sdkPath);
  llvm::sys::path::append(libPath, "Lib");
  if (sdkMajor >= 10)
    if (!(WinSdkDir.has_value() || WinSysRoot.has_value()) &&
        WinSdkVersion.has_value())
      windowsSDKLibVersion = *WinSdkVersion;
  if (sdkMajor >= 8)
    llvm::sys::path::append(libPath, windowsSDKLibVersion, "um");
  return llvm::appendArchToWindowsSDKLibPath(sdkMajor, libPath, getArch(),
                                             path);
}

bool MSVCToolChain::useUniversalCRT() const {
  return llvm::useUniversalCRT(VSLayout, VCToolChainPath, getArch(), getVFS());
}

bool MSVCToolChain::getUniversalCRTLibraryPath(const ArgList &Args,
                                               std::string &Path) const {
  std::string UniversalCRTSdkPath;
  std::string UCRTVersion;

  Path.clear();
  if (!llvm::getUniversalCRTSdkDir(getVFS(), WinSdkDir, WinSdkVersion,
                                   WinSysRoot, UniversalCRTSdkPath,
                                   UCRTVersion))
    return false;

  if (!(WinSdkDir.has_value() || WinSysRoot.has_value()) &&
      WinSdkVersion.has_value())
    UCRTVersion = *WinSdkVersion;

  StringRef ArchName = llvm::archToWindowsSDKArch(getArch());
  if (ArchName.empty())
    return false;

  llvm::SmallString<128> LibPath(UniversalCRTSdkPath);
  llvm::sys::path::append(LibPath, "Lib", UCRTVersion, "ucrt", ArchName);

  Path = std::string(LibPath);
  return true;
}

static VersionTuple getMSVCVersionFromExe(const std::string &BinDir) {
  VersionTuple Version;
#ifdef _WIN32
  SmallString<128> ClExe(BinDir);
  llvm::sys::path::append(ClExe, "cl.exe");

  std::wstring ClExeWide;
  if (!llvm::ConvertUTF8toWide(ClExe.c_str(), ClExeWide))
    return Version;

  const DWORD VersionSize = ::GetFileVersionInfoSizeW(ClExeWide.c_str(),
                                                      nullptr);
  if (VersionSize == 0)
    return Version;

  SmallVector<uint8_t, 4 * 1024> VersionBlock(VersionSize);
  if (!::GetFileVersionInfoW(ClExeWide.c_str(), 0, VersionSize,
                             VersionBlock.data()))
    return Version;

  VS_FIXEDFILEINFO *FileInfo = nullptr;
  UINT FileInfoSize = 0;
  if (!::VerQueryValueW(VersionBlock.data(), L"\\",
                        reinterpret_cast<LPVOID *>(&FileInfo), &FileInfoSize) ||
      FileInfoSize < sizeof(*FileInfo))
    return Version;

  const unsigned Major = (FileInfo->dwFileVersionMS >> 16) & 0xFFFF;
  const unsigned Minor = (FileInfo->dwFileVersionMS      ) & 0xFFFF;
  const unsigned Micro = (FileInfo->dwFileVersionLS >> 16) & 0xFFFF;

  Version = VersionTuple(Major, Minor, Micro);
#endif
  return Version;
}

void MSVCToolChain::AddSystemIncludeWithSubfolder(
    const ArgList &DriverArgs, ArgStringList &CC1Args,
    const std::string &folder, const Twine &subfolder1, const Twine &subfolder2,
    const Twine &subfolder3) const {
  llvm::SmallString<128> path(folder);
  llvm::sys::path::append(path, subfolder1, subfolder2, subfolder3);
  addSystemInclude(DriverArgs, CC1Args, path);
}

void MSVCToolChain::AddClangSystemIncludeArgs(const ArgList &DriverArgs,
                                              ArgStringList &CC1Args) const {
  if (DriverArgs.hasArg(options::OPT_nostdinc))
    return;

  if (!DriverArgs.hasArg(options::OPT_nobuiltininc)) {
    AddSystemIncludeWithSubfolder(DriverArgs, CC1Args, getDriver().ResourceDir,
                                  "include");
  }

  // Add %INCLUDE%-like directories from the -imsvc flag.
  for (const auto &Path : DriverArgs.getAllArgValues(options::OPT__SLASH_imsvc))
    addSystemInclude(DriverArgs, CC1Args, Path);

  auto AddSystemIncludesFromEnv = [&](StringRef Var) -> bool {
    if (auto Val = llvm::sys::Process::GetEnv(Var)) {
      SmallVector<StringRef, 8> Dirs;
      StringRef(*Val).split(Dirs, ";", /*MaxSplit=*/-1, /*KeepEmpty=*/false);
      if (!Dirs.empty()) {
        addSystemIncludes(DriverArgs, CC1Args, Dirs);
        return true;
      }
    }
    return false;
  };

  // Add %INCLUDE%-like dirs via /external:env: flags.
  for (const auto &Var :
       DriverArgs.getAllArgValues(options::OPT__SLASH_external_env)) {
    AddSystemIncludesFromEnv(Var);
  }

  // Add DIA SDK include if requested.
  if (const Arg *A = DriverArgs.getLastArg(options::OPT__SLASH_diasdkdir,
                                           options::OPT__SLASH_winsysroot)) {
    // cl.exe doesn't find the DIA SDK automatically, so this too requires
    // explicit flags and doesn't automatically look in "DIA SDK" relative
    // to the path we found for VCToolChainPath.
    llvm::SmallString<128> DIASDKPath(A->getValue());
    if (A->getOption().getID() == options::OPT__SLASH_winsysroot)
      llvm::sys::path::append(DIASDKPath, "DIA SDK");
    AddSystemIncludeWithSubfolder(DriverArgs, CC1Args, std::string(DIASDKPath),
                                  "include");
  }

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

  // Honor %INCLUDE% and %EXTERNAL_INCLUDE%. It should have essential search
  // paths set by vcvarsall.bat. Skip if the user expressly set any of the
  // Windows SDK or VC Tools options.
  if (!DriverArgs.hasArg(
          options::OPT__SLASH_vctoolsdir, options::OPT__SLASH_vctoolsversion,
          options::OPT__SLASH_winsysroot, options::OPT__SLASH_winsdkdir,
          options::OPT__SLASH_winsdkversion)) {
    bool Found = AddSystemIncludesFromEnv("INCLUDE");
    Found |= AddSystemIncludesFromEnv("EXTERNAL_INCLUDE");
    if (Found)
      return;
  }

  // When built with access to the proper Windows APIs, try to actually find
  // the correct include paths first.
  if (!VCToolChainPath.empty()) {
    addSystemInclude(DriverArgs, CC1Args,
                     getSubDirectoryPath(llvm::SubDirectoryType::Include));
    addSystemInclude(
        DriverArgs, CC1Args,
        getSubDirectoryPath(llvm::SubDirectoryType::Include, "atlmfc"));

    if (useUniversalCRT()) {
      std::string UniversalCRTSdkPath;
      std::string UCRTVersion;
      if (llvm::getUniversalCRTSdkDir(getVFS(), WinSdkDir, WinSdkVersion,
                                      WinSysRoot, UniversalCRTSdkPath,
                                      UCRTVersion)) {
        if (!(WinSdkDir.has_value() || WinSysRoot.has_value()) &&
            WinSdkVersion.has_value())
          UCRTVersion = *WinSdkVersion;
        AddSystemIncludeWithSubfolder(DriverArgs, CC1Args, UniversalCRTSdkPath,
                                      "Include", UCRTVersion, "ucrt");
      }
    }

    std::string WindowsSDKDir;
    int major = 0;
    std::string windowsSDKIncludeVersion;
    std::string windowsSDKLibVersion;
    if (llvm::getWindowsSDKDir(getVFS(), WinSdkDir, WinSdkVersion, WinSysRoot,
                               WindowsSDKDir, major, windowsSDKIncludeVersion,
                               windowsSDKLibVersion)) {
      if (major >= 10)
        if (!(WinSdkDir.has_value() || WinSysRoot.has_value()) &&
            WinSdkVersion.has_value())
          windowsSDKIncludeVersion = windowsSDKLibVersion = *WinSdkVersion;
      if (major >= 8) {
        // Note: windowsSDKIncludeVersion is empty for SDKs prior to v10.
        // Anyway, llvm::sys::path::append is able to manage it.
        AddSystemIncludeWithSubfolder(DriverArgs, CC1Args, WindowsSDKDir,
                                      "Include", windowsSDKIncludeVersion,
                                      "shared");
        AddSystemIncludeWithSubfolder(DriverArgs, CC1Args, WindowsSDKDir,
                                      "Include", windowsSDKIncludeVersion,
                                      "um");
        AddSystemIncludeWithSubfolder(DriverArgs, CC1Args, WindowsSDKDir,
                                      "Include", windowsSDKIncludeVersion,
                                      "winrt");
        if (major >= 10) {
          llvm::VersionTuple Tuple;
          if (!Tuple.tryParse(windowsSDKIncludeVersion) &&
              Tuple.getSubminor().value_or(0) >= 17134) {
            AddSystemIncludeWithSubfolder(DriverArgs, CC1Args, WindowsSDKDir,
                                          "Include", windowsSDKIncludeVersion,
                                          "cppwinrt");
          }
        }
      } else {
        AddSystemIncludeWithSubfolder(DriverArgs, CC1Args, WindowsSDKDir,
                                      "Include");
      }
    }

    return;
  }

#if defined(_WIN32)
  // As a fallback, select default install paths.
  // FIXME: Don't guess drives and paths like this on Windows.
  const StringRef Paths[] = {
    "C:/Program Files/Microsoft Visual Studio 10.0/VC/include",
    "C:/Program Files/Microsoft Visual Studio 9.0/VC/include",
    "C:/Program Files/Microsoft Visual Studio 9.0/VC/PlatformSDK/Include",
    "C:/Program Files/Microsoft Visual Studio 8/VC/include",
    "C:/Program Files/Microsoft Visual Studio 8/VC/PlatformSDK/Include"
  };
  addSystemIncludes(DriverArgs, CC1Args, Paths);
#endif
}

void MSVCToolChain::AddClangCXXStdlibIncludeArgs(const ArgList &DriverArgs,
                                                 ArgStringList &CC1Args) const {
  // FIXME: There should probably be logic here to find libc++ on Windows.
}

VersionTuple MSVCToolChain::computeMSVCVersion(const Driver *D,
                                               const ArgList &Args) const {
  bool IsWindowsMSVC = getTriple().isWindowsMSVCEnvironment();
  VersionTuple MSVT = ToolChain::computeMSVCVersion(D, Args);
  if (MSVT.empty())
    MSVT = getTriple().getEnvironmentVersion();
  if (MSVT.empty() && IsWindowsMSVC)
    MSVT =
        getMSVCVersionFromExe(getSubDirectoryPath(llvm::SubDirectoryType::Bin));
  if (MSVT.empty() &&
      Args.hasFlag(options::OPT_fms_extensions, options::OPT_fno_ms_extensions,
                   IsWindowsMSVC)) {
    // -fms-compatibility-version=19.33 is default, aka 2022, 17.3
    // NOTE: when changing this value, also update
    // clang/docs/CommandGuide/clang.rst and clang/docs/UsersManual.rst
    // accordingly.
    MSVT = VersionTuple(19, 33);
  }
  return MSVT;
}

std::string
MSVCToolChain::ComputeEffectiveClangTriple(const ArgList &Args,
                                           types::ID InputType) const {
  // The MSVC version doesn't care about the architecture, even though it
  // may look at the triple internally.
  VersionTuple MSVT = computeMSVCVersion(/*D=*/nullptr, Args);
  MSVT = VersionTuple(MSVT.getMajor(), MSVT.getMinor().value_or(0),
                      MSVT.getSubminor().value_or(0));

  // For the rest of the triple, however, a computed architecture name may
  // be needed.
  llvm::Triple Triple(ToolChain::ComputeEffectiveClangTriple(Args, InputType));
  if (Triple.getEnvironment() == llvm::Triple::MSVC) {
    StringRef ObjFmt = Triple.getEnvironmentName().split('-').second;
    if (ObjFmt.empty())
      Triple.setEnvironmentName((Twine("msvc") + MSVT.getAsString()).str());
    else
      Triple.setEnvironmentName(
          (Twine("msvc") + MSVT.getAsString() + Twine('-') + ObjFmt).str());
  }
  return Triple.getTriple();
}

SanitizerMask MSVCToolChain::getSupportedSanitizers() const {
  SanitizerMask Res = ToolChain::getSupportedSanitizers();
  Res |= SanitizerKind::Address;
  Res |= SanitizerKind::PointerCompare;
  Res |= SanitizerKind::PointerSubtract;
  Res |= SanitizerKind::Fuzzer;
  Res |= SanitizerKind::FuzzerNoLink;
  Res &= ~SanitizerKind::CFIMFCall;
  return Res;
}

static void TranslateOptArg(Arg *A, llvm::opt::DerivedArgList &DAL,
                            bool SupportsForcingFramePointer,
                            const char *ExpandChar, const OptTable &Opts) {
  assert(A->getOption().matches(options::OPT__SLASH_O));

  StringRef OptStr = A->getValue();
  for (size_t I = 0, E = OptStr.size(); I != E; ++I) {
    const char &OptChar = *(OptStr.data() + I);
    switch (OptChar) {
    default:
      break;
    case '1':
    case '2':
    case 'x':
    case 'd':
      // Ignore /O[12xd] flags that aren't the last one on the command line.
      // Only the last one gets expanded.
      if (&OptChar != ExpandChar) {
        A->claim();
        break;
      }
      if (OptChar == 'd') {
        DAL.AddFlagArg(A, Opts.getOption(options::OPT_O0));
      } else {
        if (OptChar == '1') {
          DAL.AddJoinedArg(A, Opts.getOption(options::OPT_O), "s");
        } else if (OptChar == '2' || OptChar == 'x') {
          DAL.AddFlagArg(A, Opts.getOption(options::OPT_fbuiltin));
          DAL.AddJoinedArg(A, Opts.getOption(options::OPT_O), "3");
        }
        if (SupportsForcingFramePointer &&
            !DAL.hasArgNoClaim(options::OPT_fno_omit_frame_pointer))
          DAL.AddFlagArg(A, Opts.getOption(options::OPT_fomit_frame_pointer));
        if (OptChar == '1' || OptChar == '2')
          DAL.AddFlagArg(A, Opts.getOption(options::OPT_ffunction_sections));
      }
      break;
    case 'b':
      if (I + 1 != E && isdigit(OptStr[I + 1])) {
        switch (OptStr[I + 1]) {
        case '0':
          DAL.AddFlagArg(A, Opts.getOption(options::OPT_fno_inline));
          break;
        case '1':
          DAL.AddFlagArg(A, Opts.getOption(options::OPT_finline_hint_functions));
          break;
        case '2':
        case '3':
          DAL.AddFlagArg(A, Opts.getOption(options::OPT_finline_functions));
          break;
        }
        ++I;
      }
      break;
    case 'g':
      A->claim();
      break;
    case 'i':
      if (I + 1 != E && OptStr[I + 1] == '-') {
        ++I;
        DAL.AddFlagArg(A, Opts.getOption(options::OPT_fno_builtin));
      } else {
        DAL.AddFlagArg(A, Opts.getOption(options::OPT_fbuiltin));
      }
      break;
    case 's':
      DAL.AddJoinedArg(A, Opts.getOption(options::OPT_O), "s");
      break;
    case 't':
      DAL.AddJoinedArg(A, Opts.getOption(options::OPT_O), "3");
      break;
    case 'y': {
      bool OmitFramePointer = true;
      if (I + 1 != E && OptStr[I + 1] == '-') {
        OmitFramePointer = false;
        ++I;
      }
      if (SupportsForcingFramePointer) {
        if (OmitFramePointer)
          DAL.AddFlagArg(A,
                         Opts.getOption(options::OPT_fomit_frame_pointer));
        else
          DAL.AddFlagArg(
              A, Opts.getOption(options::OPT_fno_omit_frame_pointer));
      } else {
        // Don't warn about /Oy- in x86-64 builds (where
        // SupportsForcingFramePointer is false).  The flag having no effect
        // there is a compiler-internal optimization, and people shouldn't have
        // to special-case their build files for x86-64 clang-cl.
        A->claim();
      }
      break;
    }
    }
  }
}

static void TranslateDArg(Arg *A, llvm::opt::DerivedArgList &DAL,
                          const OptTable &Opts) {
  assert(A->getOption().matches(options::OPT_D));

  StringRef Val = A->getValue();
  size_t Hash = Val.find('#');
  if (Hash == StringRef::npos || Hash > Val.find('=')) {
    DAL.append(A);
    return;
  }

  std::string NewVal = std::string(Val);
  NewVal[Hash] = '=';
  DAL.AddJoinedArg(A, Opts.getOption(options::OPT_D), NewVal);
}

static void TranslatePermissive(Arg *A, llvm::opt::DerivedArgList &DAL,
                                const OptTable &Opts) {
  DAL.AddFlagArg(A, Opts.getOption(options::OPT__SLASH_Zc_twoPhase_));
  DAL.AddFlagArg(A, Opts.getOption(options::OPT_fno_operator_names));
}

static void TranslatePermissiveMinus(Arg *A, llvm::opt::DerivedArgList &DAL,
                                     const OptTable &Opts) {
  DAL.AddFlagArg(A, Opts.getOption(options::OPT__SLASH_Zc_twoPhase));
  DAL.AddFlagArg(A, Opts.getOption(options::OPT_foperator_names));
}

llvm::opt::DerivedArgList *
MSVCToolChain::TranslateArgs(const llvm::opt::DerivedArgList &Args,
                             StringRef BoundArch,
                             Action::OffloadKind OFK) const {
  DerivedArgList *DAL = new DerivedArgList(Args.getBaseArgs());
  const OptTable &Opts = getDriver().getOpts();

  // /Oy and /Oy- don't have an effect on X86-64
  bool SupportsForcingFramePointer = getArch() != llvm::Triple::x86_64;

  // The -O[12xd] flag actually expands to several flags.  We must desugar the
  // flags so that options embedded can be negated.  For example, the '-O2' flag
  // enables '-Oy'.  Expanding '-O2' into its constituent flags allows us to
  // correctly handle '-O2 -Oy-' where the trailing '-Oy-' disables a single
  // aspect of '-O2'.
  //
  // Note that this expansion logic only applies to the *last* of '[12xd]'.

  // First step is to search for the character we'd like to expand.
  const char *ExpandChar = nullptr;
  for (Arg *A : Args.filtered(options::OPT__SLASH_O)) {
    StringRef OptStr = A->getValue();
    for (size_t I = 0, E = OptStr.size(); I != E; ++I) {
      char OptChar = OptStr[I];
      char PrevChar = I > 0 ? OptStr[I - 1] : '0';
      if (PrevChar == 'b') {
        // OptChar does not expand; it's an argument to the previous char.
        continue;
      }
      if (OptChar == '1' || OptChar == '2' || OptChar == 'x' || OptChar == 'd')
        ExpandChar = OptStr.data() + I;
    }
  }

  for (Arg *A : Args) {
    if (A->getOption().matches(options::OPT__SLASH_O)) {
      // The -O flag actually takes an amalgam of other options.  For example,
      // '/Ogyb2' is equivalent to '/Og' '/Oy' '/Ob2'.
      TranslateOptArg(A, *DAL, SupportsForcingFramePointer, ExpandChar, Opts);
    } else if (A->getOption().matches(options::OPT_D)) {
      // Translate -Dfoo#bar into -Dfoo=bar.
      TranslateDArg(A, *DAL, Opts);
    } else if (A->getOption().matches(options::OPT__SLASH_permissive)) {
      // Expand /permissive
      TranslatePermissive(A, *DAL, Opts);
    } else if (A->getOption().matches(options::OPT__SLASH_permissive_)) {
      // Expand /permissive-
      TranslatePermissiveMinus(A, *DAL, Opts);
    } else if (OFK != Action::OFK_HIP) {
      // HIP Toolchain translates input args by itself.
      DAL->append(A);
    }
  }

  return DAL;
}

void MSVCToolChain::addClangTargetOptions(
    const ArgList &DriverArgs, ArgStringList &CC1Args,
    Action::OffloadKind DeviceOffloadKind) const {
  // MSVC STL kindly allows removing all usages of typeid by defining
  // _HAS_STATIC_RTTI to 0. Do so, when compiling with -fno-rtti
  if (DriverArgs.hasFlag(options::OPT_fno_rtti, options::OPT_frtti,
                         /*Default=*/false))
    CC1Args.push_back("-D_HAS_STATIC_RTTI=0");

  if (Arg *A = DriverArgs.getLastArgNoClaim(options::OPT_marm64x))
    A->ignoreTargetSpecific();
}
