//===- DependencyScannerImpl.cpp - Implements module dependency scanning --===//
//
// 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 "clang/DependencyScanning/DependencyScannerImpl.h"
#include "clang/Basic/DiagnosticFrontend.h"
#include "clang/Basic/DiagnosticSerialization.h"
#include "clang/DependencyScanning/DependencyActionController.h"
#include "clang/DependencyScanning/DependencyConsumer.h"
#include "clang/DependencyScanning/DependencyScanningFilesystem.h"
#include "clang/DependencyScanning/DependencyScanningService.h"
#include "clang/DependencyScanning/DependencyScanningWorker.h"
#include "clang/Frontend/FrontendActions.h"
#include "llvm/ADT/IntrusiveRefCntPtr.h"
#include "llvm/ADT/ScopeExit.h"
#include "llvm/Option/Option.h"
#include "llvm/Support/AdvisoryLock.h"
#include "llvm/Support/CrashRecoveryContext.h"
#include "llvm/Support/VirtualFileSystem.h"
#include "llvm/TargetParser/Host.h"

#include <mutex>
#include <thread>

using namespace clang;
using namespace dependencies;

static bool checkHeaderSearchPaths(const HeaderSearchOptions &HSOpts,
                                   const HeaderSearchOptions &ExistingHSOpts,
                                   DiagnosticsEngine *Diags,
                                   const LangOptions &LangOpts) {
  if (LangOpts.Modules) {
    if (HSOpts.VFSOverlayFiles != ExistingHSOpts.VFSOverlayFiles) {
      if (Diags) {
        Diags->Report(diag::warn_pch_vfsoverlay_mismatch);
        auto VFSNote = [&](int Type, ArrayRef<std::string> VFSOverlays) {
          if (VFSOverlays.empty()) {
            Diags->Report(diag::note_pch_vfsoverlay_empty) << Type;
          } else {
            std::string Files = llvm::join(VFSOverlays, "\n");
            Diags->Report(diag::note_pch_vfsoverlay_files) << Type << Files;
          }
        };
        VFSNote(0, HSOpts.VFSOverlayFiles);
        VFSNote(1, ExistingHSOpts.VFSOverlayFiles);
      }
    }
  }
  return false;
}

namespace {

using PrebuiltModuleFilesT = decltype(HeaderSearchOptions::PrebuiltModuleFiles);

/// A listener that collects the imported modules and the input
/// files. While visiting, collect vfsoverlays and file inputs that determine
/// whether prebuilt modules fully resolve in stable directories.
class PrebuiltModuleListener : public ASTReaderListener {
public:
  PrebuiltModuleListener(PrebuiltModuleFilesT &PrebuiltModuleFiles,
                         llvm::SmallVector<std::string> &NewModuleFiles,
                         PrebuiltModulesAttrsMap &PrebuiltModulesASTMap,
                         const HeaderSearchOptions &HSOpts,
                         const LangOptions &LangOpts, DiagnosticsEngine &Diags,
                         const ArrayRef<StringRef> StableDirs)
      : PrebuiltModuleFiles(PrebuiltModuleFiles),
        NewModuleFiles(NewModuleFiles),
        PrebuiltModulesASTMap(PrebuiltModulesASTMap), ExistingHSOpts(HSOpts),
        ExistingLangOpts(LangOpts), Diags(Diags), StableDirs(StableDirs) {}

  bool needsImportVisitation() const override { return true; }
  bool needsInputFileVisitation() override { return true; }
  bool needsSystemInputFileVisitation() override { return true; }

  /// Accumulate the modules are transitively depended on by the initial
  /// prebuilt module.
  void visitImport(StringRef ModuleName, StringRef Filename) override {
    if (PrebuiltModuleFiles.insert({ModuleName.str(), Filename.str()}).second)
      NewModuleFiles.push_back(Filename.str());

    auto PrebuiltMapEntry = PrebuiltModulesASTMap.try_emplace(Filename);
    PrebuiltModuleASTAttrs &PrebuiltModule = PrebuiltMapEntry.first->second;
    if (PrebuiltMapEntry.second)
      PrebuiltModule.setInStableDir(!StableDirs.empty());

    if (auto It = PrebuiltModulesASTMap.find(CurrentFile);
        It != PrebuiltModulesASTMap.end() && CurrentFile != Filename)
      PrebuiltModule.addDependent(It->getKey());
  }

  /// For each input file discovered, check whether it's external path is in a
  /// stable directory. Traversal is stopped if the current module is not
  /// considered stable.
  bool visitInputFileAsRequested(StringRef FilenameAsRequested,
                                 StringRef Filename, bool isSystem,
                                 bool isOverridden, time_t StoredTime,
                                 bool isExplicitModule) override {
    if (StableDirs.empty())
      return false;
    auto PrebuiltEntryIt = PrebuiltModulesASTMap.find(CurrentFile);
    if ((PrebuiltEntryIt == PrebuiltModulesASTMap.end()) ||
        (!PrebuiltEntryIt->second.isInStableDir()))
      return false;

    PrebuiltEntryIt->second.setInStableDir(
        isPathInStableDir(StableDirs, Filename));
    return PrebuiltEntryIt->second.isInStableDir();
  }

  /// Update which module that is being actively traversed.
  void visitModuleFile(ModuleFileName Filename, serialization::ModuleKind Kind,
                       bool DirectlyImported) override {
    // If the CurrentFile is not
    // considered stable, update any of it's transitive dependents.
    auto PrebuiltEntryIt = PrebuiltModulesASTMap.find(CurrentFile);
    if ((PrebuiltEntryIt != PrebuiltModulesASTMap.end()) &&
        !PrebuiltEntryIt->second.isInStableDir())
      PrebuiltEntryIt->second.updateDependentsNotInStableDirs(
          PrebuiltModulesASTMap);
    CurrentFile = Filename.str();
  }

  /// Check the header search options for a given module when considering
  /// if the module comes from stable directories.
  bool ReadHeaderSearchOptions(const HeaderSearchOptions &HSOpts,
                               StringRef ModuleFilename, StringRef ContextHash,
                               bool Complain) override {

    auto PrebuiltMapEntry = PrebuiltModulesASTMap.try_emplace(CurrentFile);
    PrebuiltModuleASTAttrs &PrebuiltModule = PrebuiltMapEntry.first->second;
    if (PrebuiltMapEntry.second)
      PrebuiltModule.setInStableDir(!StableDirs.empty());

    if (PrebuiltModule.isInStableDir())
      PrebuiltModule.setInStableDir(areOptionsInStableDir(StableDirs, HSOpts));

    return false;
  }

  /// Accumulate vfsoverlays used to build these prebuilt modules.
  bool ReadHeaderSearchPaths(const HeaderSearchOptions &HSOpts,
                             bool Complain) override {

    auto PrebuiltMapEntry = PrebuiltModulesASTMap.try_emplace(CurrentFile);
    PrebuiltModuleASTAttrs &PrebuiltModule = PrebuiltMapEntry.first->second;
    if (PrebuiltMapEntry.second)
      PrebuiltModule.setInStableDir(!StableDirs.empty());

    PrebuiltModule.setVFS(
        llvm::StringSet<>(llvm::from_range, HSOpts.VFSOverlayFiles));

    return checkHeaderSearchPaths(
        HSOpts, ExistingHSOpts, Complain ? &Diags : nullptr, ExistingLangOpts);
  }

private:
  PrebuiltModuleFilesT &PrebuiltModuleFiles;
  llvm::SmallVector<std::string> &NewModuleFiles;
  PrebuiltModulesAttrsMap &PrebuiltModulesASTMap;
  const HeaderSearchOptions &ExistingHSOpts;
  const LangOptions &ExistingLangOpts;
  DiagnosticsEngine &Diags;
  std::string CurrentFile;
  const ArrayRef<StringRef> StableDirs;
};

/// Visit the given prebuilt module and collect all of the modules it
/// transitively imports and contributing input files.
static bool visitPrebuiltModule(StringRef PrebuiltModuleFilename,
                                CompilerInstance &CI,
                                PrebuiltModuleFilesT &ModuleFiles,
                                PrebuiltModulesAttrsMap &PrebuiltModulesASTMap,
                                DiagnosticsEngine &Diags,
                                const ArrayRef<StringRef> StableDirs) {
  // List of module files to be processed.
  llvm::SmallVector<std::string> Worklist;

  PrebuiltModuleListener Listener(ModuleFiles, Worklist, PrebuiltModulesASTMap,
                                  CI.getHeaderSearchOpts(), CI.getLangOpts(),
                                  Diags, StableDirs);

  Listener.visitModuleFile(ModuleFileName::makeExplicit(PrebuiltModuleFilename),
                           serialization::MK_ExplicitModule,
                           /*DirectlyImported=*/true);
  if (ASTReader::readASTFileControlBlock(
          PrebuiltModuleFilename, CI.getFileManager(), CI.getModuleCache(),
          CI.getPCHContainerReader(),
          /*FindModuleFileExtensions=*/false, Listener,
          /*ValidateDiagnosticOptions=*/false, ASTReader::ARR_OutOfDate))
    return true;

  while (!Worklist.empty()) {
    // FIXME: This is assuming the PCH only refers to explicitly-built modules,
    // which technically is not guaranteed. To remove the assumption, we'd need
    // to also rework how the module files are handled to the scan, specifically
    // change the values of HeaderSearchOptions::PrebuiltModuleFiles from plain
    // paths to ModuleFileName.
    Listener.visitModuleFile(ModuleFileName::makeExplicit(Worklist.back()),
                             serialization::MK_ExplicitModule,
                             /*DirectlyImported=*/false);
    if (ASTReader::readASTFileControlBlock(
            Worklist.pop_back_val(), CI.getFileManager(), CI.getModuleCache(),
            CI.getPCHContainerReader(),
            /*FindModuleFileExtensions=*/false, Listener,
            /*ValidateDiagnosticOptions=*/false))
      return true;
  }
  return false;
}

/// Transform arbitrary file name into an object-like file name.
static std::string makeObjFileName(StringRef FileName) {
  SmallString<128> ObjFileName(FileName);
  llvm::sys::path::replace_extension(ObjFileName, "o");
  return std::string(ObjFileName);
}

/// Deduce the dependency target based on the output file and input files.
static std::string
deduceDepTarget(const std::string &OutputFile,
                const SmallVectorImpl<FrontendInputFile> &InputFiles) {
  if (OutputFile != "-")
    return OutputFile;

  if (InputFiles.empty() || !InputFiles.front().isFile())
    return "clang-scan-deps\\ dependency";

  return makeObjFileName(InputFiles.front().getFile());
}

// Clang implements -D and -U by splatting text into a predefines buffer. This
// allows constructs such as `-DFඞ=3 "-D F\u{0D9E} 4 3 2”` to be accepted and
// define the same macro, or adding C++ style comments before the macro name.
//
// This function checks that the first non-space characters in the macro
// obviously form an identifier that can be uniqued on without lexing. Failing
// to do this could lead to changing the final definition of a macro.
//
// We could set up a preprocessor and actually lex the name, but that's very
// heavyweight for a situation that will almost never happen in practice.
static std::optional<StringRef> getSimpleMacroName(StringRef Macro) {
  StringRef Name = Macro.split("=").first.ltrim(" \t");
  std::size_t I = 0;

  auto FinishName = [&]() -> std::optional<StringRef> {
    StringRef SimpleName = Name.slice(0, I);
    if (SimpleName.empty())
      return std::nullopt;
    return SimpleName;
  };

  for (; I != Name.size(); ++I) {
    switch (Name[I]) {
    case '(': // Start of macro parameter list
    case ' ': // End of macro name
    case '\t':
      return FinishName();
    case '_':
      continue;
    default:
      if (llvm::isAlnum(Name[I]))
        continue;
      return std::nullopt;
    }
  }
  return FinishName();
}
} // namespace

void dependencies::canonicalizeDefines(PreprocessorOptions &PPOpts) {
  using MacroOpt = std::pair<StringRef, std::size_t>;
  std::vector<MacroOpt> SimpleNames;
  SimpleNames.reserve(PPOpts.Macros.size());
  std::size_t Index = 0;
  for (const auto &M : PPOpts.Macros) {
    auto SName = getSimpleMacroName(M.first);
    // Skip optimizing if we can't guarantee we can preserve relative order.
    if (!SName)
      return;
    SimpleNames.emplace_back(*SName, Index);
    ++Index;
  }

  llvm::stable_sort(SimpleNames, llvm::less_first());
  // Keep the last instance of each macro name by going in reverse
  auto NewEnd = std::unique(
      SimpleNames.rbegin(), SimpleNames.rend(),
      [](const MacroOpt &A, const MacroOpt &B) { return A.first == B.first; });
  SimpleNames.erase(SimpleNames.begin(), NewEnd.base());

  // Apply permutation.
  decltype(PPOpts.Macros) NewMacros;
  NewMacros.reserve(SimpleNames.size());
  for (std::size_t I = 0, E = SimpleNames.size(); I != E; ++I) {
    std::size_t OriginalIndex = SimpleNames[I].second;
    // We still emit undefines here as they may be undefining a predefined macro
    NewMacros.push_back(std::move(PPOpts.Macros[OriginalIndex]));
  }
  std::swap(PPOpts.Macros, NewMacros);
}

namespace {
class ScanningDependencyDirectivesGetter : public DependencyDirectivesGetter {
  DependencyScanningWorkerFilesystem *DepFS;

public:
  ScanningDependencyDirectivesGetter(FileManager &FileMgr) : DepFS(nullptr) {
    FileMgr.getVirtualFileSystem().visit([&](llvm::vfs::FileSystem &FS) {
      auto *DFS = llvm::dyn_cast<DependencyScanningWorkerFilesystem>(&FS);
      if (DFS) {
        assert(!DepFS && "Found multiple scanning VFSs");
        DepFS = DFS;
      }
    });
    assert(DepFS && "Did not find scanning VFS");
  }

  std::unique_ptr<DependencyDirectivesGetter>
  cloneFor(FileManager &FileMgr) override {
    return std::make_unique<ScanningDependencyDirectivesGetter>(FileMgr);
  }

  std::optional<ArrayRef<dependency_directives_scan::Directive>>
  operator()(FileEntryRef File) override {
    return DepFS->getDirectiveTokens(File.getName());
  }
};

/// Sanitize diagnostic options for dependency scan.
void sanitizeDiagOpts(DiagnosticOptions &DiagOpts) {
  // Don't print 'X warnings and Y errors generated'.
  DiagOpts.ShowCarets = false;
  // Don't write out diagnostic file.
  DiagOpts.DiagnosticSerializationFile.clear();
  // Don't emit warnings except for scanning specific warnings.
  // TODO: It would be useful to add a more principled way to ignore all
  //       warnings that come from source code. The issue is that we need to
  //       ignore warnings that could be surpressed by
  //       `#pragma clang diagnostic`, while still allowing some scanning
  //       warnings for things we're not ready to turn into errors yet.
  //       See `test/ClangScanDeps/diagnostic-pragmas.c` for an example.
  llvm::erase_if(DiagOpts.Warnings, [](StringRef Warning) {
    return llvm::StringSwitch<bool>(Warning)
        .Cases({"pch-vfs-diff", "error=pch-vfs-diff"}, false)
        .StartsWith("no-error=", false)
        .Default(true);
  });
}
} // namespace

std::unique_ptr<DiagnosticOptions>
dependencies::createDiagOptions(ArrayRef<std::string> CommandLine) {
  std::vector<const char *> CLI;
  for (const std::string &Arg : CommandLine)
    CLI.push_back(Arg.c_str());
  auto DiagOpts = CreateAndPopulateDiagOpts(CLI);
  sanitizeDiagOpts(*DiagOpts);
  return DiagOpts;
}

DiagnosticsEngineWithDiagOpts::DiagnosticsEngineWithDiagOpts(
    ArrayRef<std::string> CommandLine,
    IntrusiveRefCntPtr<llvm::vfs::FileSystem> FS, DiagnosticConsumer &DC) {
  std::vector<const char *> CCommandLine(CommandLine.size(), nullptr);
  llvm::transform(CommandLine, CCommandLine.begin(),
                  [](const std::string &Str) { return Str.c_str(); });
  DiagOpts = CreateAndPopulateDiagOpts(CCommandLine);
  sanitizeDiagOpts(*DiagOpts);
  DiagEngine = CompilerInstance::createDiagnostics(*FS, *DiagOpts, &DC,
                                                   /*ShouldOwnClient=*/false);
}

std::unique_ptr<CompilerInvocation>
dependencies::createCompilerInvocation(ArrayRef<std::string> CommandLine,
                                       DiagnosticsEngine &Diags) {
  llvm::opt::ArgStringList Argv;
  for (const std::string &Str : ArrayRef(CommandLine).drop_front())
    Argv.push_back(Str.c_str());

  auto Invocation = std::make_unique<CompilerInvocation>();
  if (!CompilerInvocation::CreateFromArgs(*Invocation, Argv, Diags)) {
    // FIXME: Should we just go on like cc1_main does?
    return nullptr;
  }
  return Invocation;
}

void dependencies::initializeScanCompilerInstance(
    CompilerInstance &ScanInstance,
    IntrusiveRefCntPtr<llvm::vfs::FileSystem> FS,
    DiagnosticConsumer *DiagConsumer, DependencyScanningService &Service,
    IntrusiveRefCntPtr<DependencyScanningWorkerFilesystem> DepFS) {
  ScanInstance.setBuildingModule(false);
  ScanInstance.createVirtualFileSystem(FS, DiagConsumer);
  ScanInstance.createDiagnostics(DiagConsumer, /*ShouldOwnClient=*/false);
  if (!Service.getOpts().EmitWarnings)
    ScanInstance.getDiagnostics().setIgnoreAllWarnings(true);
  ScanInstance.createFileManager();
  ScanInstance.createSourceManager();

  // Use DepFS for getting the dependency directives if requested to do so.
  if (Service.getOpts().Mode == ScanningMode::DependencyDirectivesScan)
    ScanInstance.setDependencyDirectivesGetter(
        std::make_unique<ScanningDependencyDirectivesGetter>(
            ScanInstance.getFileManager()));
}

std::shared_ptr<CompilerInvocation> dependencies::createScanCompilerInvocation(
    const CompilerInvocation &Invocation,
    const DependencyScanningService &Service,
    DependencyActionController &Controller) {
  auto ScanInvocation = std::make_shared<CompilerInvocation>(Invocation);

  sanitizeDiagOpts(ScanInvocation->getDiagnosticOpts());

  ScanInvocation->getPreprocessorOpts().AllowPCHWithDifferentModulesCachePath =
      true;

  if (ScanInvocation->getHeaderSearchOpts().ModulesValidateOncePerBuildSession)
    ScanInvocation->getHeaderSearchOpts().BuildSessionTimestamp =
        Service.getOpts().BuildSessionTimestamp;

  ScanInvocation->getFrontendOpts().DisableFree = false;
  ScanInvocation->getFrontendOpts().GenerateGlobalModuleIndex = false;
  ScanInvocation->getFrontendOpts().UseGlobalModuleIndex = false;
  ScanInvocation->getFrontendOpts().GenReducedBMI = false;
  ScanInvocation->getFrontendOpts().ModuleOutputPath.clear();
  // This will prevent us compiling individual modules asynchronously since
  // FileManager is not thread-safe, but it does improve performance for now.
  ScanInvocation->getFrontendOpts().ModulesShareFileManager = true;
  ScanInvocation->getHeaderSearchOpts().ModuleFormat = "raw";
  ScanInvocation->getHeaderSearchOpts().ModulesIncludeVFSUsage =
      any(Service.getOpts().OptimizeArgs & ScanningOptimizations::VFS);

  // Consider different header search and diagnostic options to create
  // different modules. This avoids the unsound aliasing of module PCMs.
  //
  // TODO: Implement diagnostic bucketing to reduce the impact of strict
  // context hashing.
  ScanInvocation->getHeaderSearchOpts().ModulesStrictContextHash = true;
  ScanInvocation->getHeaderSearchOpts().ModulesSerializeOnlyPreprocessor = true;
  ScanInvocation->getHeaderSearchOpts().ModulesSkipDiagnosticOptions = true;
  ScanInvocation->getHeaderSearchOpts().ModulesSkipHeaderSearchPaths = true;
  ScanInvocation->getHeaderSearchOpts().ModulesSkipPragmaDiagnosticMappings =
      true;
  ScanInvocation->getHeaderSearchOpts().ModulesForceValidateUserHeaders = false;

  // FIXME: Do this even with PCHs by marking the option as something like
  // "preprocessor benign" in LangOptions.def so that it passes the
  // compatibility checks in ASTReader.
  if (ScanInvocation->getPreprocessorOpts().ImplicitPCHInclude.empty()) {
    // Application extension only affects the handling of availability
    // attributes, which cannot change the dependencies.
    ScanInvocation->getLangOpts().AppExt = false;
  }

  // Ensure that the scanner does not create new dependency collectors,
  // and thus won't write out the extra '.d' files to disk.
  ScanInvocation->getDependencyOutputOpts() = {};

  Controller.initializeScanInvocation(*ScanInvocation);

  return ScanInvocation;
}

llvm::SmallVector<StringRef>
dependencies::getInitialStableDirs(const CompilerInstance &ScanInstance) {
  // Create a collection of stable directories derived from the ScanInstance
  // for determining whether module dependencies would fully resolve from
  // those directories.
  llvm::SmallVector<StringRef> StableDirs;
  const StringRef Sysroot = ScanInstance.getHeaderSearchOpts().Sysroot;
  if (!Sysroot.empty() && (llvm::sys::path::root_directory(Sysroot) != Sysroot))
    StableDirs = {Sysroot, ScanInstance.getHeaderSearchOpts().ResourceDir};
  return StableDirs;
}

std::optional<PrebuiltModulesAttrsMap>
dependencies::computePrebuiltModulesASTMap(
    CompilerInstance &ScanInstance, llvm::SmallVector<StringRef> &StableDirs) {
  // Store a mapping of prebuilt module files and their properties like header
  // search options. This will prevent the implicit build to create duplicate
  // modules and will force reuse of the existing prebuilt module files
  // instead.
  PrebuiltModulesAttrsMap PrebuiltModulesASTMap;

  if (!ScanInstance.getPreprocessorOpts().ImplicitPCHInclude.empty())
    if (visitPrebuiltModule(
            ScanInstance.getPreprocessorOpts().ImplicitPCHInclude, ScanInstance,
            ScanInstance.getHeaderSearchOpts().PrebuiltModuleFiles,
            PrebuiltModulesASTMap, ScanInstance.getDiagnostics(), StableDirs))
      return {};

  return PrebuiltModulesASTMap;
}

std::unique_ptr<DependencyOutputOptions>
dependencies::createDependencyOutputOptions(
    const CompilerInvocation &Invocation) {
  auto Opts = std::make_unique<DependencyOutputOptions>(
      Invocation.getDependencyOutputOpts());
  // We need at least one -MT equivalent for the generator of make dependency
  // files to work.
  if (Opts->Targets.empty())
    Opts->Targets = {deduceDepTarget(Invocation.getFrontendOpts().OutputFile,
                                     Invocation.getFrontendOpts().Inputs)};
  Opts->IncludeSystemHeaders = true;

  return Opts;
}

std::shared_ptr<ModuleDepCollector>
dependencies::initializeScanInstanceDependencyCollector(
    CompilerInstance &ScanInstance,
    std::unique_ptr<DependencyOutputOptions> DepOutputOpts,
    DependencyScanningService &Service, CompilerInvocation &Inv,
    DependencyActionController &Controller,
    PrebuiltModulesAttrsMap PrebuiltModulesASTMap,
    SmallVector<StringRef> &StableDirs) {
  auto MDC = std::make_shared<ModuleDepCollector>(
      Service, std::move(DepOutputOpts), ScanInstance, Controller, Inv,
      std::move(PrebuiltModulesASTMap), StableDirs);
  ScanInstance.addDependencyCollector(MDC);
  return MDC;
}

/// Manages (and terminates) the asynchronous compilation of modules.
class AsyncModuleCompiles {
  std::mutex Mutex;
  bool Stop = false;
  // FIXME: Have the service own a thread pool and use that instead.
  std::vector<std::thread> Compiles;

public:
  /// Registers the module compilation, unless this instance is about to be
  /// destroyed.
  void add(llvm::unique_function<void()> Compile) {
    std::lock_guard<std::mutex> Lock(Mutex);
    if (!Stop)
      Compiles.emplace_back(std::move(Compile));
  }

  ~AsyncModuleCompiles() {
    {
      // Prevent registration of further module compiles.
      std::lock_guard<std::mutex> Lock(Mutex);
      Stop = true;
    }

    // Wait for outstanding module compiles to finish.
    for (std::thread &Compile : Compiles)
      Compile.join();
  }
};

struct SingleModuleWithAsyncModuleCompiles : PreprocessOnlyAction {
  DependencyScanningService &Service;
  DependencyActionController &Controller;
  AsyncModuleCompiles &Compiles;

  SingleModuleWithAsyncModuleCompiles(DependencyScanningService &Service,
                                      DependencyActionController &Controller,
                                      AsyncModuleCompiles &Compiles)
      : Service(Service), Controller(Controller), Compiles(Compiles) {}

  bool BeginSourceFileAction(CompilerInstance &CI) override;
};

/// The preprocessor callback that takes care of initiating an asynchronous
/// module compilation if needed.
struct AsyncModuleCompile : PPCallbacks {
  CompilerInstance &CI;
  DependencyScanningService &Service;
  DependencyActionController &Controller;
  AsyncModuleCompiles &Compiles;

  AsyncModuleCompile(CompilerInstance &CI, DependencyScanningService &Service,
                     DependencyActionController &Controller,
                     AsyncModuleCompiles &Compiles)
      : CI(CI), Service(Service), Controller(Controller), Compiles(Compiles) {}

  void moduleLoadSkipped(Module *M) override {
    M = M->getTopLevelModule();

    HeaderSearch &HS = CI.getPreprocessor().getHeaderSearchInfo();
    ModuleCache &ModCache = CI.getModuleCache();
    ModuleFileName ModuleFileName = HS.getCachedModuleFileName(M);

    uint64_t Timestamp = ModCache.getModuleTimestamp(ModuleFileName);
    // Someone else already built/validated the PCM.
    if (Timestamp > CI.getHeaderSearchOpts().BuildSessionTimestamp)
      return;

    if (!CI.getASTReader())
      CI.createASTReader();
    SmallVector<ASTReader::ImportedModule, 0> Imported;
    // Only calling ReadASTCore() to avoid the expensive eager deserialization
    // of the clang::Module objects in ReadAST().
    // FIXME: Consider doing this in the new thread depending on how expensive
    // the read turns out to be.
    switch (CI.getASTReader()->ReadASTCore(
        ModuleFileName, serialization::MK_ImplicitModule, SourceLocation(),
        nullptr, Imported, {}, {}, {},
        ASTReader::ARR_OutOfDate | ASTReader::ARR_Missing |
            ASTReader::ARR_TreatModuleWithErrorsAsOutOfDate)) {
    case ASTReader::Success:
      // We successfully read a valid, up-to-date PCM.
      // FIXME: This could update the timestamp. Regular calls to
      // ASTReader::ReadAST() would do so unless they encountered corrupted
      // AST block, corrupted extension block, or did not read the expected
      // top-level module.
      return;
    case ASTReader::OutOfDate:
    case ASTReader::Missing:
      // The most interesting case.
      break;
    default:
      // Let the regular scan diagnose this.
      return;
    }

    auto Lock = ModCache.getLock(ModuleFileName);
    bool Owned;
    llvm::Error LockErr = Lock->tryLock().moveInto(Owned);
    // Someone else is building the PCM right now.
    if (!LockErr && !Owned)
      return;
    // We should build the PCM.
    IntrusiveRefCntPtr<llvm::vfs::FileSystem> VFS =
        llvm::makeIntrusiveRefCnt<DependencyScanningWorkerFilesystem>(
            Service, Service.getOpts().MakeVFS());
    VFS = createVFSFromCompilerInvocation(CI.getInvocation(),
                                          CI.getDiagnostics(), std::move(VFS));
    auto DC = std::make_unique<DiagnosticConsumer>();
    auto MC = makeInProcessModuleCache(Service.getModuleCacheEntries());
    CompilerInstance::ThreadSafeCloneConfig CloneConfig(std::move(VFS), *DC,
                                                        std::move(MC));
    auto ModCI1 = CI.cloneForModuleCompile(SourceLocation(), M, ModuleFileName,
                                           CloneConfig);
    auto ModCI2 = CI.cloneForModuleCompile(SourceLocation(), M, ModuleFileName,
                                           CloneConfig);

    auto ModController = Controller.clone();

    // Note: This lock belongs to a module cache that might not outlive the
    // thread. This works, because the in-process lock only refers to an object
    // managed by the service, which does outlive the thread.
    Compiles.add([Lock = std::move(Lock), ModCI1 = std::move(ModCI1),
                  ModCI2 = std::move(ModCI2), DC = std::move(DC),
                  ModController = std::move(ModController), Service = &Service,
                  Compiles = &Compiles] {
      llvm::CrashRecoveryContext CRC;
      (void)CRC.RunSafely([&] {
        // Quickly discovers and compiles modules for the real scan below.
        SingleModuleWithAsyncModuleCompiles Action1(*Service, *ModController,
                                                    *Compiles);
        (void)ModCI1->ExecuteAction(Action1);
        // The real scan below.
        ModCI2->getPreprocessorOpts().SingleModuleParseMode = false;
        GenerateModuleFromModuleMapAction Action2;
        (void)ModCI2->ExecuteAction(Action2);
      });
    });
  }
};

/// Runs the preprocessor on a TU with single-module-parse-mode and compiles
/// modules asynchronously without blocking or importing them.
struct SingleTUWithAsyncModuleCompiles : PreprocessOnlyAction {
  DependencyScanningService &Service;
  DependencyActionController &Controller;
  AsyncModuleCompiles &Compiles;

  SingleTUWithAsyncModuleCompiles(DependencyScanningService &Service,
                                  DependencyActionController &Controller,
                                  AsyncModuleCompiles &Compiles)
      : Service(Service), Controller(Controller), Compiles(Compiles) {}

  bool BeginSourceFileAction(CompilerInstance &CI) override {
    CI.getInvocation().getPreprocessorOpts().SingleModuleParseMode = true;
    CI.getPreprocessor().addPPCallbacks(std::make_unique<AsyncModuleCompile>(
        CI, Service, Controller, Compiles));
    return true;
  }
};

bool SingleModuleWithAsyncModuleCompiles::BeginSourceFileAction(
    CompilerInstance &CI) {
  CI.getInvocation().getPreprocessorOpts().SingleModuleParseMode = true;
  CI.getPreprocessor().addPPCallbacks(
      std::make_unique<AsyncModuleCompile>(CI, Service, Controller, Compiles));
  return true;
}

bool DependencyScanningAction::runInvocation(
    std::string Executable,
    std::unique_ptr<CompilerInvocation> OriginalInvocation,
    IntrusiveRefCntPtr<llvm::vfs::FileSystem> FS,
    std::shared_ptr<PCHContainerOperations> PCHContainerOps,
    DiagnosticConsumer *DiagConsumer) {
  // Making sure that we canonicalize the defines early to avoid unnecessary
  // variants in both the scanner and in the resulting  explicit command lines.
  if (any(Service.getOpts().OptimizeArgs & ScanningOptimizations::Macros))
    canonicalizeDefines(OriginalInvocation->getPreprocessorOpts());

  if (Scanned) {
    CompilerInstance &ScanInstance = *ScanInstanceStorage;

    // Scanning runs once for the first -cc1 invocation in a chain of driver
    // jobs. For any dependent jobs, reuse the scanning result and just
    // update the new invocation.
    // FIXME: to support multi-arch builds, each arch requires a separate scan
    if (MDC)
      MDC->applyDiscoveredDependencies(*OriginalInvocation);

    bool Success = OriginalInvocation->withCowRef<bool>(
        [&](CowCompilerInvocation &CowOriginalInvocation) {
          return Controller.finalize(ScanInstance, CowOriginalInvocation);
        });
    if (!Success)
      return false;

    Consumer.handleBuildCommand(
        {Executable, OriginalInvocation->getCC1CommandLine()});
    return true;
  }

  Scanned = true;

  // Create a compiler instance to handle the actual work.
  auto ScanInvocation =
      createScanCompilerInvocation(*OriginalInvocation, Service, Controller);

  // Quickly discovers and compiles modules for the real scan below.
  std::optional<AsyncModuleCompiles> AsyncCompiles;
  if (Service.getOpts().AsyncScanModules) {
    auto ModCache = makeInProcessModuleCache(Service.getModuleCacheEntries());
    auto ScanInstanceStorage = std::make_unique<CompilerInstance>(
        std::make_shared<CompilerInvocation>(*ScanInvocation), PCHContainerOps,
        std::move(ModCache));
    CompilerInstance &ScanInstance = *ScanInstanceStorage;

    DiagnosticConsumer DiagConsumer;
    initializeScanCompilerInstance(ScanInstance, FS, &DiagConsumer, Service,
                                   DepFS);

    // FIXME: Do this only once.
    SmallVector<StringRef> StableDirs = getInitialStableDirs(ScanInstance);
    auto MaybePrebuiltModulesASTMap =
        computePrebuiltModulesASTMap(ScanInstance, StableDirs);
    if (!MaybePrebuiltModulesASTMap)
      return false;

    // Normally this would be handled by GeneratePCHAction
    if (ScanInstance.getFrontendOpts().ProgramAction == frontend::GeneratePCH)
      ScanInstance.getLangOpts().CompilingPCH = true;

    AsyncCompiles.emplace();
    SingleTUWithAsyncModuleCompiles Action(Service, Controller, *AsyncCompiles);
    (void)ScanInstance.ExecuteAction(Action);
  }

  auto ModCache = makeInProcessModuleCache(Service.getModuleCacheEntries());
  ScanInstanceStorage.emplace(std::move(ScanInvocation),
                              std::move(PCHContainerOps), std::move(ModCache));
  CompilerInstance &ScanInstance = *ScanInstanceStorage;

  initializeScanCompilerInstance(ScanInstance, FS, DiagConsumer, Service,
                                 DepFS);

  llvm::SmallVector<StringRef> StableDirs = getInitialStableDirs(ScanInstance);
  auto MaybePrebuiltModulesASTMap =
      computePrebuiltModulesASTMap(ScanInstance, StableDirs);
  if (!MaybePrebuiltModulesASTMap)
    return false;

  auto DepOutputOpts = createDependencyOutputOptions(*OriginalInvocation);

  MDC = initializeScanInstanceDependencyCollector(
      ScanInstance, std::move(DepOutputOpts), Service, *OriginalInvocation,
      Controller, *MaybePrebuiltModulesASTMap, StableDirs);

  if (ScanInstance.getDiagnostics().hasErrorOccurred())
    return false;

  if (!Controller.initialize(ScanInstance, *OriginalInvocation))
    return false;

  ReadPCHAndPreprocessAction Action;
  const bool Result = ScanInstance.ExecuteAction(Action);

  if (Result) {
    if (MDC) {
      MDC->run(Consumer);
      MDC->applyDiscoveredDependencies(*OriginalInvocation);
    }

    bool Success = OriginalInvocation->withCowRef<bool>(
        [&](CowCompilerInvocation &CowOriginalInvocation) {
          return Controller.finalize(ScanInstance, CowOriginalInvocation);
        });
    if (!Success)
      return false;

    Consumer.handleBuildCommand(
        {Executable, OriginalInvocation->getCC1CommandLine()});
  }

  return Result;
}
