Merge pull request #14034 from graydon/batch-mode-driver-work

NFC: Batch mode driver work
diff --git a/include/swift/Driver/Driver.h b/include/swift/Driver/Driver.h
index 67bf54a..b6285f5 100644
--- a/include/swift/Driver/Driver.h
+++ b/include/swift/Driver/Driver.h
@@ -21,6 +21,7 @@
 #include "swift/Basic/LLVM.h"
 #include "swift/Basic/OptionSet.h"
 #include "swift/Basic/Sanitizers.h"
+#include "swift/Driver/OutputFileMap.h"
 #include "swift/Driver/Types.h"
 #include "swift/Driver/Util.h"
 #include "llvm/ADT/DenseMap.h"
@@ -45,6 +46,7 @@
   class DiagnosticEngine;
 namespace driver {
   class Action;
+  class CommandOutput;
   class Compilation;
   class Job;
   class JobAction;
@@ -263,6 +265,53 @@
                           const ToolChain &TC, bool AtTopLevel,
                           JobCacheMap &JobCache) const;
 
+private:
+  void computeMainOutput(Compilation &C, const JobAction *JA,
+                         const OutputInfo &OI, const OutputFileMap *OFM,
+                         const ToolChain &TC, bool AtTopLevel,
+                         SmallVectorImpl<const Action *> &InputActions,
+                         SmallVectorImpl<const Job *> &InputJobs,
+                         const TypeToPathMap *OutputMap, StringRef BaseInput,
+                         llvm::SmallString<128> &Buf,
+                         CommandOutput *Output) const;
+
+  void chooseSwiftModuleOutputPath(Compilation &C, const OutputInfo &OI,
+                                   const OutputFileMap *OFM,
+                                   const TypeToPathMap *OutputMap,
+                                   CommandOutput *Output) const;
+
+  void chooseSwiftModuleDocOutputPath(Compilation &C,
+                                      const TypeToPathMap *OutputMap,
+                                      CommandOutput *Output) const;
+  void chooseRemappingOutputPath(Compilation &C, const TypeToPathMap *OutputMap,
+                                 CommandOutput *Output) const;
+
+  void chooseSerializedDiagnosticsPath(Compilation &C, const JobAction *JA,
+                                       const OutputInfo &OI,
+                                       const TypeToPathMap *OutputMap,
+                                       CommandOutput *Output) const;
+
+  void chooseDependenciesOutputPaths(Compilation &C, const OutputInfo &OI,
+                                     const TypeToPathMap *OutputMap,
+                                     llvm::SmallString<128> &Buf,
+                                     CommandOutput *Output) const;
+
+  void chooseSaveOptimizationPath(Compilation &C, const OutputInfo &OI,
+                                  llvm::SmallString<128> &Buf,
+                                  CommandOutput *Output) const;
+
+  void chooseObjectiveCHeaderOutputPath(Compilation &C, const OutputInfo &OI,
+                                        const TypeToPathMap *OutputMap,
+                                        CommandOutput *Output) const;
+
+  void chooseLoadedModuleTracePath(Compilation &C, const OutputInfo &OI,
+                                   llvm::SmallString<128> &Buf,
+                                   CommandOutput *Output) const;
+
+  void chooseTBDPath(Compilation &C, const OutputInfo &OI,
+                     llvm::SmallString<128> &Buf, CommandOutput *Output) const;
+
+public:
   /// Handle any arguments which should be treated before building actions or
   /// binding tools.
   ///
diff --git a/include/swift/Driver/Job.h b/include/swift/Driver/Job.h
index 1839506..a562111 100644
--- a/include/swift/Driver/Job.h
+++ b/include/swift/Driver/Job.h
@@ -115,7 +115,7 @@
   EnvironmentVector ExtraEnvironment;
 
   /// Whether the job wants a list of input or output files created.
-  FilelistInfo FilelistFileInfo;
+  std::vector<FilelistInfo> FilelistFileInfos;
 
   /// The modification time of the main input file, if any.
   llvm::sys::TimePoint<> InputModTime = llvm::sys::TimePoint<>::max();
@@ -127,12 +127,12 @@
       const char *Executable,
       llvm::opt::ArgStringList Arguments,
       EnvironmentVector ExtraEnvironment = {},
-      FilelistInfo Info = {})
+      std::vector<FilelistInfo> Infos = {})
       : SourceAndCondition(&Source, Condition::Always),
         Inputs(std::move(Inputs)), Output(std::move(Output)),
         Executable(Executable), Arguments(std::move(Arguments)),
         ExtraEnvironment(std::move(ExtraEnvironment)),
-        FilelistFileInfo(std::move(Info)) {}
+        FilelistFileInfos(std::move(Infos)) {}
 
   const JobAction &getSource() const {
     return *SourceAndCondition.getPointer();
@@ -140,7 +140,7 @@
 
   const char *getExecutable() const { return Executable; }
   const llvm::opt::ArgStringList &getArguments() const { return Arguments; }
-  FilelistInfo getFilelistInfo() const { return FilelistFileInfo; }
+  ArrayRef<FilelistInfo> getFilelistInfos() const { return FilelistFileInfos; }
 
   ArrayRef<const Job *> getInputs() const { return Inputs; }
   const CommandOutput &getOutput() const { return *Output; }
diff --git a/include/swift/Driver/ToolChain.h b/include/swift/Driver/ToolChain.h
index 1b79835..98f43a7 100644
--- a/include/swift/Driver/ToolChain.h
+++ b/include/swift/Driver/ToolChain.h
@@ -90,7 +90,7 @@
     const char *ExecutableName;
     llvm::opt::ArgStringList Arguments;
     std::vector<std::pair<const char *, const char *>> ExtraEnvironment;
-    FilelistInfo FilelistInfo;
+    std::vector<FilelistInfo> FilelistInfos;
 
     InvocationInfo(const char *name, llvm::opt::ArgStringList args = {},
                    decltype(ExtraEnvironment) extraEnv = {})
diff --git a/include/swift/Driver/Util.h b/include/swift/Driver/Util.h
index b7b27b3..ad9f32f 100644
--- a/include/swift/Driver/Util.h
+++ b/include/swift/Driver/Util.h
@@ -43,8 +43,9 @@
   /// The Compilation is responsible for generating this file before running
   /// the Job this info is attached to.
   struct FilelistInfo {
-    enum WhichFiles : bool {
+    enum class WhichFiles : unsigned {
       Input,
+      PrimaryInputs,
       Output
     };
 
diff --git a/lib/Driver/Compilation.cpp b/lib/Driver/Compilation.cpp
index 50da198..93ce244 100644
--- a/lib/Driver/Compilation.cpp
+++ b/lib/Driver/Compilation.cpp
@@ -787,40 +787,51 @@
 }
 
 static bool writeFilelistIfNecessary(const Job *job, DiagnosticEngine &diags) {
-  FilelistInfo filelistInfo = job->getFilelistInfo();
-  if (filelistInfo.path.empty())
-    return true;
+  bool ok = true;
+  for (const FilelistInfo &filelistInfo : job->getFilelistInfos()) {
+    if (filelistInfo.path.empty())
+      return true;
 
-  std::error_code error;
-  llvm::raw_fd_ostream out(filelistInfo.path, error, llvm::sys::fs::F_None);
-  if (out.has_error()) {
-    out.clear_error();
-    diags.diagnose(SourceLoc(), diag::error_unable_to_make_temporary_file,
-                   error.message());
-    return false;
-  }
-
-  if (filelistInfo.whichFiles == FilelistInfo::Input) {
-    // FIXME: Duplicated from ToolChains.cpp.
-    for (const Job *input : job->getInputs()) {
-      const CommandOutput &outputInfo = input->getOutput();
-      if (outputInfo.getPrimaryOutputType() == filelistInfo.type) {
-        for (auto &output : outputInfo.getPrimaryOutputFilenames())
-          out << output << "\n";
-      } else {
-        auto &output = outputInfo.getAnyOutputForType(filelistInfo.type);
-        if (!output.empty())
-          out << output << "\n";
-      }
+    std::error_code error;
+    llvm::raw_fd_ostream out(filelistInfo.path, error, llvm::sys::fs::F_None);
+    if (out.has_error()) {
+      out.clear_error();
+      diags.diagnose(SourceLoc(), diag::error_unable_to_make_temporary_file,
+                     error.message());
+      ok = false;
+      continue;
     }
-  } else {
-    const CommandOutput &outputInfo = job->getOutput();
-    assert(outputInfo.getPrimaryOutputType() == filelistInfo.type);
-    for (auto &output : outputInfo.getPrimaryOutputFilenames())
-      out << output << "\n";
-  }
 
-  return true;
+    switch (filelistInfo.whichFiles) {
+    case FilelistInfo::WhichFiles::Input:
+      // FIXME: Duplicated from ToolChains.cpp.
+      for (const Job *input : job->getInputs()) {
+        const CommandOutput &outputInfo = input->getOutput();
+        if (outputInfo.getPrimaryOutputType() == filelistInfo.type) {
+          for (auto &output : outputInfo.getPrimaryOutputFilenames())
+            out << output << "\n";
+        } else {
+          auto &output = outputInfo.getAnyOutputForType(filelistInfo.type);
+          if (!output.empty())
+            out << output << "\n";
+        }
+      }
+      break;
+    case FilelistInfo::WhichFiles::PrimaryInputs:
+      for (const Action *A : job->getSource().getInputs()) {
+        const auto *IA = cast<InputAction>(A);
+        out << IA->getInputArg().getValue() << "\n";
+      }
+      break;
+    case FilelistInfo::WhichFiles::Output:
+      const CommandOutput &outputInfo = job->getOutput();
+      assert(outputInfo.getPrimaryOutputType() == filelistInfo.type);
+      for (auto &output : outputInfo.getPrimaryOutputFilenames())
+        out << output << "\n";
+      break;
+    }
+  }
+  return ok;
 }
 
 int Compilation::performJobsImpl(bool &abnormalExit) {
diff --git a/lib/Driver/Driver.cpp b/lib/Driver/Driver.cpp
index 336feda..30132fc 100644
--- a/lib/Driver/Driver.cpp
+++ b/lib/Driver/Driver.cpp
@@ -1736,6 +1736,50 @@
   return None;
 }
 
+static StringRef assignOutputName(Compilation &C, const JobAction *JA,
+                                  DiagnosticEngine &Diags,
+                                  llvm::SmallString<128> &Buffer,
+                                  StringRef BaseName,
+                                  PreserveOnSignal ShouldPreserveOnSignal) {
+  // We should output to a temporary file, since we're not at the top level
+  // (or are generating a bridging PCH, which is currently always a temp).
+  StringRef Stem = llvm::sys::path::stem(BaseName);
+  StringRef Suffix = types::getTypeTempSuffix(JA->getType());
+  std::error_code EC = llvm::sys::fs::createTemporaryFile(Stem, Suffix, Buffer);
+  if (EC) {
+    Diags.diagnose(SourceLoc(), diag::error_unable_to_make_temporary_file,
+                   EC.message());
+    return {};
+  }
+  C.addTemporaryFile(Buffer.str(), ShouldPreserveOnSignal);
+
+  return Buffer.str();
+}
+
+static StringRef baseNameForImage(const JobAction *JA, const OutputInfo &OI,
+                                  const llvm::Triple &Triple,
+                                  llvm::SmallString<128> &Buffer,
+                                  StringRef BaseInput, StringRef BaseName) {
+  if (JA->size() == 1 && OI.ModuleNameIsFallback && BaseInput != "-")
+    return llvm::sys::path::stem(BaseInput);
+  auto link = dyn_cast<LinkJobAction>(JA);
+  if (!link)
+    return BaseName;
+  if (link->getKind() != LinkKind::DynamicLibrary)
+    return BaseName;
+
+  Buffer = Triple.isOSWindows() ? "" : "lib";
+  Buffer.append(BaseName);
+
+  if (Triple.isOSDarwin())
+    Buffer.append(".dylib");
+  else if (Triple.isOSWindows())
+    Buffer.append(".dll");
+  else
+    Buffer.append(".so");
+  return Buffer.str();
+}
+
 static StringRef getOutputFilename(Compilation &C,
                                    const JobAction *JA,
                                    const OutputInfo &OI,
@@ -1805,47 +1849,12 @@
     BaseName = OI.ModuleName;
 
   // We don't yet have a name, assign one.
-  if (!AtTopLevel) {
-    // We should output to a temporary file, since we're not at the top level
-    // (or are generating a bridging PCH, which is currently always a temp).
-    StringRef Stem = llvm::sys::path::stem(BaseName);
-    StringRef Suffix = types::getTypeTempSuffix(JA->getType());
-    std::error_code EC =
-        llvm::sys::fs::createTemporaryFile(Stem, Suffix, Buffer);
-    if (EC) {
-      Diags.diagnose(SourceLoc(),
-                     diag::error_unable_to_make_temporary_file,
-                     EC.message());
-      return {};
-    }
-    C.addTemporaryFile(Buffer.str(), ShouldPreserveOnSignal);
+  if (!AtTopLevel)
+    return assignOutputName(C, JA, Diags, Buffer, BaseName,
+                            ShouldPreserveOnSignal);
 
-    return Buffer.str();
-  }
-
-
-  if (JA->getType() == types::TY_Image) {
-    if (JA->size() == 1 && OI.ModuleNameIsFallback && BaseInput != "-")
-      BaseName = llvm::sys::path::stem(BaseInput);
-    if (auto link = dyn_cast<LinkJobAction>(JA)) {
-      if (link->getKind() == LinkKind::DynamicLibrary) {
-        if (Triple.isOSWindows())
-          Buffer = "";
-        else
-          Buffer = "lib";
-        Buffer.append(BaseName);
-        if (Triple.isOSDarwin())
-          Buffer.append(".dylib");
-        else if (Triple.isOSWindows())
-          Buffer.append(".dll");
-        else
-          Buffer.append(".so");
-        return Buffer.str();
-      }
-    }
-    return BaseName;
-  }
-
+  if (JA->getType() == types::TY_Image)
+    return baseNameForImage(JA, OI, Triple, Buffer, BaseInput, BaseName);
 
   StringRef Suffix = types::getTypeTempSuffix(JA->getType());
   assert(Suffix.data() &&
@@ -2042,250 +2051,38 @@
 
   std::unique_ptr<CommandOutput> Output(new CommandOutput(JA->getType()));
   llvm::SmallString<128> Buf;
-  StringRef OutputFile;
+  computeMainOutput(C, JA, OI, OFM, TC, AtTopLevel, InputActions, InputJobs,
+                    OutputMap, BaseInput, Buf, Output.get());
 
-  if (OI.isMultiThreading() && isa<CompileJobAction>(JA) &&
-      types::isAfterLLVM(JA->getType())) {
-    // Multi-threaded compilation: A single frontend command produces multiple
-    // output file: one for each input files.
-    auto OutputFunc = [&](StringRef Input) {
-      const TypeToPathMap *OMForInput = nullptr;
-      if (OFM)
-        OMForInput = OFM->getOutputMapForInput(Input);
+  if (OI.ShouldGenerateModule && isa<CompileJobAction>(JA))
+    chooseSwiftModuleOutputPath(C, OI, OFM, OutputMap, Output.get());
 
-      OutputFile = getOutputFilename(C, JA, OI, OMForInput, TC.getTriple(),
-                                     C.getArgs(), AtTopLevel, Input, InputJobs,
-                                     Diags, Buf);
-      Output->addPrimaryOutput(OutputFile, Input);
-    };
-    // Add an output file for each input action.
-    for (const Action *A : InputActions) {
-      const InputAction *IA = cast<InputAction>(A);
-      OutputFunc(IA->getInputArg().getValue());
-
-    }
-    // Add an output file for each input job.
-    for (const Job *job : InputJobs) {
-      OutputFunc(job->getOutput().getBaseInput(0));
-    }
-  } else {
-    // The common case: there is a single output file.
-    OutputFile = getOutputFilename(C, JA, OI, OutputMap, TC.getTriple(),
-                                   C.getArgs(), AtTopLevel, BaseInput,
-                                   InputJobs, Diags, Buf);
-    Output->addPrimaryOutput(OutputFile, BaseInput);
-  }
-
-  // Choose the swiftmodule output path.
-  if (OI.ShouldGenerateModule && isa<CompileJobAction>(JA)) {
-    StringRef OFMModuleOutputPath;
-    if (OutputMap) {
-      auto iter = OutputMap->find(types::TY_SwiftModuleFile);
-      if (iter != OutputMap->end())
-        OFMModuleOutputPath = iter->second;
-    }
-
-    const Arg *A = C.getArgs().getLastArg(options::OPT_emit_module_path);
-    if (!OFMModuleOutputPath.empty()) {
-      // Prefer a path from the OutputMap.
-      Output->setAdditionalOutputForType(types::TY_SwiftModuleFile,
-                                         OFMModuleOutputPath);
-    } else if (A && OI.CompilerMode == OutputInfo::Mode::SingleCompile) {
-      // We're performing a single compilation (and thus no merge module step),
-      // so prefer to use -emit-module-path, if present.
-      Output->setAdditionalOutputForType(types::TY_SwiftModuleFile,
-                                         A->getValue());
-    } else if (OI.CompilerMode == OutputInfo::Mode::SingleCompile &&
-               OI.ShouldTreatModuleAsTopLevelOutput) {
-      // We're performing a single compile and don't have -emit-module-path,
-      // but have been told to treat the module as a top-level output.
-      // Determine an appropriate path.
-      if (const Arg *A = C.getArgs().getLastArg(options::OPT_o)) {
-        // Put the module next to the top-level output.
-        llvm::SmallString<128> Path(A->getValue());
-        llvm::sys::path::remove_filename(Path);
-        llvm::sys::path::append(Path, OI.ModuleName);
-        llvm::sys::path::replace_extension(Path, SERIALIZED_MODULE_EXTENSION);
-        Output->setAdditionalOutputForType(types::TY_SwiftModuleFile, Path);
-      } else {
-        // A top-level output wasn't specified, so just output to
-        // <ModuleName>.swiftmodule.
-        llvm::SmallString<128> Path(OI.ModuleName);
-        llvm::sys::path::replace_extension(Path, SERIALIZED_MODULE_EXTENSION);
-        Output->setAdditionalOutputForType(types::TY_SwiftModuleFile, Path);
-      }
-    } else {
-      // We're only generating the module as an intermediate, so put it next
-      // to the primary output of the compile command.
-      llvm::SmallString<128> Path(Output->getPrimaryOutputFilenames()[0]);
-      bool isTempFile = C.isTemporaryFile(Path);
-      llvm::sys::path::replace_extension(Path, SERIALIZED_MODULE_EXTENSION);
-      Output->setAdditionalOutputForType(types::ID::TY_SwiftModuleFile, Path);
-      if (isTempFile)
-        C.addTemporaryFile(Path);
-    }
-  }
-
-  // Choose the swiftdoc output path.
   if (OI.ShouldGenerateModule &&
-      (isa<CompileJobAction>(JA) || isa<MergeModuleJobAction>(JA))) {
-    StringRef OFMModuleDocOutputPath;
-    if (OutputMap) {
-      auto iter = OutputMap->find(types::TY_SwiftModuleDocFile);
-      if (iter != OutputMap->end())
-        OFMModuleDocOutputPath = iter->second;
-    }
-    if (!OFMModuleDocOutputPath.empty()) {
-      // Prefer a path from the OutputMap.
-      Output->setAdditionalOutputForType(types::TY_SwiftModuleDocFile,
-                                         OFMModuleDocOutputPath);
-    } else {
-      // Otherwise, put it next to the swiftmodule file.
-      llvm::SmallString<128> Path(
-          Output->getAnyOutputForType(types::TY_SwiftModuleFile));
-      bool isTempFile = C.isTemporaryFile(Path);
-      llvm::sys::path::replace_extension(Path,
-                                         SERIALIZED_MODULE_DOC_EXTENSION);
-      Output->setAdditionalOutputForType(types::TY_SwiftModuleDocFile, Path);
-      if (isTempFile)
-        C.addTemporaryFile(Path);
-    }
-  }
+      (isa<CompileJobAction>(JA) || isa<MergeModuleJobAction>(JA)))
+    chooseSwiftModuleDocOutputPath(C, OutputMap, Output.get());
 
-  if (C.getArgs().hasArg(options::OPT_update_code) &&
-      isa<CompileJobAction>(JA)) {
-    StringRef OFMFixitsOutputPath;
-    if (OutputMap) {
-      auto iter = OutputMap->find(types::TY_Remapping);
-      if (iter != OutputMap->end())
-        OFMFixitsOutputPath = iter->second;
-    }
-    if (!OFMFixitsOutputPath.empty()) {
-      Output->setAdditionalOutputForType(types::ID::TY_Remapping,
-                                         OFMFixitsOutputPath);
-    } else {
-      llvm::SmallString<128> Path(Output->getPrimaryOutputFilenames()[0]);
-      bool isTempFile = C.isTemporaryFile(Path);
-      llvm::sys::path::replace_extension(Path, "remap");
-      Output->setAdditionalOutputForType(types::ID::TY_Remapping, Path);
-      if (isTempFile)
-        C.addTemporaryFile(Path);
-    }
-  }
+  if (C.getArgs().hasArg(options::OPT_update_code) && isa<CompileJobAction>(JA))
+    chooseRemappingOutputPath(C, OutputMap, Output.get());
 
   if (isa<CompileJobAction>(JA) || isa<GeneratePCHJobAction>(JA)) {
     // Choose the serialized diagnostics output path.
-    if (C.getArgs().hasArg(options::OPT_serialize_diagnostics)) {
-      auto pchJA = dyn_cast<GeneratePCHJobAction>(JA);
-      if (pchJA && pchJA->isPersistentPCH()) {
-        addDiagFileOutputForPersistentPCHAction(C, pchJA, *Output, OI,
-                                                OutputMap, Diags);
-      } else {
-        addAuxiliaryOutput(C, *Output, types::TY_SerializedDiagnostics, OI,
-                           OutputMap);
-      }
-
-      // Remove any existing diagnostics files so that clients can detect their
-      // presence to determine if a command was run.
-      StringRef OutputPath =
-        Output->getAnyOutputForType(types::TY_SerializedDiagnostics);
-      if (llvm::sys::fs::is_regular_file(OutputPath))
-        llvm::sys::fs::remove(OutputPath);
-    }
+    if (C.getArgs().hasArg(options::OPT_serialize_diagnostics))
+      chooseSerializedDiagnosticsPath(C, JA, OI, OutputMap, Output.get());
   }
 
-  if (isa<CompileJobAction>(JA)) {
-    // Choose the dependencies file output path.
-    if (C.getArgs().hasArg(options::OPT_emit_dependencies)) {
-      addAuxiliaryOutput(C, *Output, types::TY_Dependencies, OI, OutputMap);
-    }
-    if (C.getIncrementalBuildEnabled()) {
-      addAuxiliaryOutput(C, *Output, types::TY_SwiftDeps, OI, OutputMap);
-    }
-
-    // The loaded-module-trace is the same for all compile jobs: all `import`
-    // statements are processed, even ones from non-primary files. Thus, only
-    // one of those jobs needs to emit the file, and we can get it to write
-    // straight to the desired final location.
-    auto tracePathEnvVar = getenv("SWIFT_LOADED_MODULE_TRACE_FILE");
-    auto shouldEmitTrace =
-        tracePathEnvVar ||
-        C.getArgs().hasArg(options::OPT_emit_loaded_module_trace,
-                           options::OPT_emit_loaded_module_trace_path);
-
-    if (shouldEmitTrace &&
-        C.requestPermissionForFrontendToEmitLoadedModuleTrace()) {
-      StringRef filename;
-      // Prefer the environment variable.
-      if (tracePathEnvVar)
-        filename = StringRef(tracePathEnvVar);
-      else {
-        // By treating this as a top-level output, the return value always
-        // exists.
-        filename = *getOutputFilenameFromPathArgOrAsTopLevel(
-            OI, C.getArgs(), options::OPT_emit_loaded_module_trace_path,
-            types::TY_ModuleTrace,
-            /*TreatAsTopLevelOutput=*/true, "trace.json", Buf);
-      }
-
-      Output->setAdditionalOutputForType(types::TY_ModuleTrace, filename);
-    }
-
-    if (C.getArgs().hasArg(options::OPT_emit_tbd, options::OPT_emit_tbd_path)) {
-      if (OI.CompilerMode != OutputInfo::Mode::SingleCompile) {
-        llvm::outs() << "TBD emission has been disabled, because it requires a "
-                     << "single compiler invocation: consider enabling the "
-                     << "-whole-module-optimization flag.\n";
-      } else {
-        auto filename = *getOutputFilenameFromPathArgOrAsTopLevel(
-            OI, C.getArgs(), options::OPT_emit_tbd_path, types::TY_TBD,
-            /*TreatAsTopLevelOutput=*/true, "tbd", Buf);
-
-        Output->setAdditionalOutputForType(types::TY_TBD, filename);
-      }
-    }
-  }
+  if (isa<CompileJobAction>(JA))
+    chooseDependenciesOutputPaths(C, OI, OutputMap, Buf, Output.get());
 
   if (C.getArgs().hasArg(options::OPT_save_optimization_record,
-                         options::OPT_save_optimization_record_path)) {
-    if (OI.CompilerMode == OutputInfo::Mode::SingleCompile) {
-      auto filename = *getOutputFilenameFromPathArgOrAsTopLevel(
-          OI, C.getArgs(), options::OPT_save_optimization_record_path,
-          types::TY_OptRecord, /*TreatAsTopLevelOutput=*/true, "opt.yaml", Buf);
+                         options::OPT_save_optimization_record_path))
+    chooseSaveOptimizationPath(C, OI, Buf, Output.get());
 
-      Output->setAdditionalOutputForType(types::TY_OptRecord, filename);
-    } else
-      // FIXME: We should use the OutputMap in this case.
-      Diags.diagnose({}, diag::warn_opt_remark_disabled);
-  }
-
-  // Choose the Objective-C header output path.
   if ((isa<MergeModuleJobAction>(JA) ||
        (isa<CompileJobAction>(JA) &&
         OI.CompilerMode == OutputInfo::Mode::SingleCompile)) &&
       C.getArgs().hasArg(options::OPT_emit_objc_header,
-                         options::OPT_emit_objc_header_path)) {
-    StringRef ObjCHeaderPath;
-    if (OutputMap) {
-      auto iter = OutputMap->find(types::TY_ObjCHeader);
-      if (iter != OutputMap->end())
-        ObjCHeaderPath = iter->second;
-    }
-
-    if (ObjCHeaderPath.empty())
-      if (auto A = C.getArgs().getLastArg(options::OPT_emit_objc_header_path))
-        ObjCHeaderPath = A->getValue();
-
-    if (!ObjCHeaderPath.empty()) {
-      Output->setAdditionalOutputForType(types::TY_ObjCHeader, ObjCHeaderPath);
-    } else {
-      // Put the header next to the primary output file.
-      // FIXME: That's not correct if the user /just/ passed -emit-header
-      // and not -emit-module.
-      addAuxiliaryOutput(C, *Output, types::TY_ObjCHeader, OI,
-                         /*output file map*/nullptr);
-    }
-  }
+                         options::OPT_emit_objc_header_path))
+    chooseObjectiveCHeaderOutputPath(C, OI, OutputMap, Output.get());
 
   // 4. Construct a Job which produces the right CommandOutput.
   std::unique_ptr<Job> ownedJob = TC.constructJob(*JA, C, std::move(InputJobs),
@@ -2371,6 +2168,274 @@
   return J;
 }
 
+void Driver::computeMainOutput(Compilation &C, const JobAction *JA,
+                               const OutputInfo &OI, const OutputFileMap *OFM,
+                               const ToolChain &TC, bool AtTopLevel,
+                               SmallVectorImpl<const Action *> &InputActions,
+                               SmallVectorImpl<const Job *> &InputJobs,
+                               const TypeToPathMap *OutputMap,
+                               StringRef BaseInput, llvm::SmallString<128> &Buf,
+                               CommandOutput *Output) const {
+  StringRef OutputFile;
+  if (OI.isMultiThreading() && isa<CompileJobAction>(JA) &&
+      types::isAfterLLVM(JA->getType())) {
+    // Multi-threaded compilation: A single frontend command produces multiple
+    // output file: one for each input files.
+    auto OutputFunc = [&](StringRef Input) {
+      const TypeToPathMap *OMForInput = nullptr;
+      if (OFM)
+        OMForInput = OFM->getOutputMapForInput(Input);
+
+      OutputFile = getOutputFilename(C, JA, OI, OMForInput, TC.getTriple(),
+                                     C.getArgs(), AtTopLevel, Input, InputJobs,
+                                     Diags, Buf);
+      Output->addPrimaryOutput(OutputFile, Input);
+    };
+    // Add an output file for each input action.
+    for (const Action *A : InputActions) {
+      const InputAction *IA = cast<InputAction>(A);
+      OutputFunc(IA->getInputArg().getValue());
+    }
+    // Add an output file for each input job.
+    for (const Job *job : InputJobs) {
+      OutputFunc(job->getOutput().getBaseInput(0));
+    }
+  } else {
+    // The common case: there is a single output file.
+    OutputFile =
+        getOutputFilename(C, JA, OI, OutputMap, TC.getTriple(), C.getArgs(),
+                          AtTopLevel, BaseInput, InputJobs, Diags, Buf);
+    Output->addPrimaryOutput(OutputFile, BaseInput);
+  }
+}
+
+void Driver::chooseSwiftModuleOutputPath(Compilation &C, const OutputInfo &OI,
+                                         const OutputFileMap *OFM,
+                                         const TypeToPathMap *OutputMap,
+                                         CommandOutput *Output) const {
+  StringRef OFMModuleOutputPath;
+  if (OutputMap) {
+    auto iter = OutputMap->find(types::TY_SwiftModuleFile);
+    if (iter != OutputMap->end())
+      OFMModuleOutputPath = iter->second;
+  }
+
+  const Arg *A = C.getArgs().getLastArg(options::OPT_emit_module_path);
+  if (!OFMModuleOutputPath.empty()) {
+    // Prefer a path from the OutputMap.
+    Output->setAdditionalOutputForType(types::TY_SwiftModuleFile,
+                                       OFMModuleOutputPath);
+  } else if (A && OI.CompilerMode == OutputInfo::Mode::SingleCompile) {
+    // We're performing a single compilation (and thus no merge module step),
+    // so prefer to use -emit-module-path, if present.
+    Output->setAdditionalOutputForType(types::TY_SwiftModuleFile,
+                                       A->getValue());
+  } else if (OI.CompilerMode == OutputInfo::Mode::SingleCompile &&
+             OI.ShouldTreatModuleAsTopLevelOutput) {
+    // We're performing a single compile and don't have -emit-module-path,
+    // but have been told to treat the module as a top-level output.
+    // Determine an appropriate path.
+    if (const Arg *A = C.getArgs().getLastArg(options::OPT_o)) {
+      // Put the module next to the top-level output.
+      llvm::SmallString<128> Path(A->getValue());
+      llvm::sys::path::remove_filename(Path);
+      llvm::sys::path::append(Path, OI.ModuleName);
+      llvm::sys::path::replace_extension(Path, SERIALIZED_MODULE_EXTENSION);
+      Output->setAdditionalOutputForType(types::TY_SwiftModuleFile, Path);
+    } else {
+      // A top-level output wasn't specified, so just output to
+      // <ModuleName>.swiftmodule.
+      llvm::SmallString<128> Path(OI.ModuleName);
+      llvm::sys::path::replace_extension(Path, SERIALIZED_MODULE_EXTENSION);
+      Output->setAdditionalOutputForType(types::TY_SwiftModuleFile, Path);
+    }
+  } else {
+    // We're only generating the module as an intermediate, so put it next
+    // to the primary output of the compile command.
+    llvm::SmallString<128> Path(Output->getPrimaryOutputFilenames()[0]);
+    bool isTempFile = C.isTemporaryFile(Path);
+    llvm::sys::path::replace_extension(Path, SERIALIZED_MODULE_EXTENSION);
+    Output->setAdditionalOutputForType(types::ID::TY_SwiftModuleFile, Path);
+    if (isTempFile)
+      C.addTemporaryFile(Path);
+  }
+}
+
+void Driver::chooseSwiftModuleDocOutputPath(Compilation &C,
+                                            const TypeToPathMap *OutputMap,
+                                            CommandOutput *Output) const {
+  StringRef OFMModuleDocOutputPath;
+  if (OutputMap) {
+    auto iter = OutputMap->find(types::TY_SwiftModuleDocFile);
+    if (iter != OutputMap->end())
+      OFMModuleDocOutputPath = iter->second;
+  }
+  if (!OFMModuleDocOutputPath.empty()) {
+    // Prefer a path from the OutputMap.
+    Output->setAdditionalOutputForType(types::TY_SwiftModuleDocFile,
+                                       OFMModuleDocOutputPath);
+  } else {
+    // Otherwise, put it next to the swiftmodule file.
+    llvm::SmallString<128> Path(
+        Output->getAnyOutputForType(types::TY_SwiftModuleFile));
+    bool isTempFile = C.isTemporaryFile(Path);
+    llvm::sys::path::replace_extension(Path, SERIALIZED_MODULE_DOC_EXTENSION);
+    Output->setAdditionalOutputForType(types::TY_SwiftModuleDocFile, Path);
+    if (isTempFile)
+      C.addTemporaryFile(Path);
+  }
+}
+
+void Driver::chooseRemappingOutputPath(Compilation &C,
+                                       const TypeToPathMap *OutputMap,
+                                       CommandOutput *Output) const {
+  StringRef OFMFixitsOutputPath;
+  if (OutputMap) {
+    auto iter = OutputMap->find(types::TY_Remapping);
+    if (iter != OutputMap->end())
+      OFMFixitsOutputPath = iter->second;
+  }
+  if (!OFMFixitsOutputPath.empty()) {
+    Output->setAdditionalOutputForType(types::ID::TY_Remapping,
+                                       OFMFixitsOutputPath);
+  } else {
+    llvm::SmallString<128> Path(Output->getPrimaryOutputFilenames()[0]);
+    bool isTempFile = C.isTemporaryFile(Path);
+    llvm::sys::path::replace_extension(Path, "remap");
+    Output->setAdditionalOutputForType(types::ID::TY_Remapping, Path);
+    if (isTempFile)
+      C.addTemporaryFile(Path);
+  }
+}
+
+void Driver::chooseSerializedDiagnosticsPath(Compilation &C,
+                                             const JobAction *JA,
+                                             const OutputInfo &OI,
+                                             const TypeToPathMap *OutputMap,
+                                             CommandOutput *Output) const {
+  if (C.getArgs().hasArg(options::OPT_serialize_diagnostics)) {
+    auto pchJA = dyn_cast<GeneratePCHJobAction>(JA);
+    if (pchJA && pchJA->isPersistentPCH()) {
+      addDiagFileOutputForPersistentPCHAction(C, pchJA, *Output, OI, OutputMap,
+                                              Diags);
+    } else {
+      addAuxiliaryOutput(C, *Output, types::TY_SerializedDiagnostics, OI,
+                         OutputMap);
+    }
+
+    // Remove any existing diagnostics files so that clients can detect their
+    // presence to determine if a command was run.
+    StringRef OutputPath =
+        Output->getAnyOutputForType(types::TY_SerializedDiagnostics);
+    if (llvm::sys::fs::is_regular_file(OutputPath))
+      llvm::sys::fs::remove(OutputPath);
+  }
+}
+
+void Driver::chooseDependenciesOutputPaths(Compilation &C, const OutputInfo &OI,
+                                           const TypeToPathMap *OutputMap,
+                                           llvm::SmallString<128> &Buf,
+                                           CommandOutput *Output) const {
+  if (C.getArgs().hasArg(options::OPT_emit_dependencies)) {
+    addAuxiliaryOutput(C, *Output, types::TY_Dependencies, OI, OutputMap);
+  }
+  if (C.getIncrementalBuildEnabled()) {
+    addAuxiliaryOutput(C, *Output, types::TY_SwiftDeps, OI, OutputMap);
+  }
+  chooseLoadedModuleTracePath(C, OI, Buf, Output);
+  chooseTBDPath(C, OI, Buf, Output);
+}
+
+void Driver::chooseLoadedModuleTracePath(Compilation &C, const OutputInfo &OI,
+                                         llvm::SmallString<128> &Buf,
+                                         CommandOutput *Output) const {
+  // The loaded-module-trace is the same for all compile jobs: all `import`
+  // statements are processed, even ones from non-primary files. Thus, only
+  // one of those jobs needs to emit the file, and we can get it to write
+  // straight to the desired final location.
+  auto tracePathEnvVar = getenv("SWIFT_LOADED_MODULE_TRACE_FILE");
+  auto shouldEmitTrace =
+      tracePathEnvVar ||
+      C.getArgs().hasArg(options::OPT_emit_loaded_module_trace,
+                         options::OPT_emit_loaded_module_trace_path);
+
+  if (shouldEmitTrace &&
+      C.requestPermissionForFrontendToEmitLoadedModuleTrace()) {
+    StringRef filename;
+    // Prefer the environment variable.
+    if (tracePathEnvVar)
+      filename = StringRef(tracePathEnvVar);
+    else {
+      // By treating this as a top-level output, the return value always
+      // exists.
+      filename = *getOutputFilenameFromPathArgOrAsTopLevel(
+          OI, C.getArgs(), options::OPT_emit_loaded_module_trace_path,
+          types::TY_ModuleTrace,
+          /*TreatAsTopLevelOutput=*/true, "trace.json", Buf);
+    }
+
+    Output->setAdditionalOutputForType(types::TY_ModuleTrace, filename);
+  }
+}
+
+void Driver::chooseTBDPath(Compilation &C, const OutputInfo &OI,
+                           llvm::SmallString<128> &Buf,
+                           CommandOutput *Output) const {
+  if (C.getArgs().hasArg(options::OPT_emit_tbd, options::OPT_emit_tbd_path)) {
+    if (OI.CompilerMode != OutputInfo::Mode::SingleCompile) {
+      llvm::outs() << "TBD emission has been disabled, because it requires a "
+                   << "single compiler invocation: consider enabling the "
+                   << "-whole-module-optimization flag.\n";
+    } else {
+      auto filename = *getOutputFilenameFromPathArgOrAsTopLevel(
+          OI, C.getArgs(), options::OPT_emit_tbd_path, types::TY_TBD,
+          /*TreatAsTopLevelOutput=*/true, "tbd", Buf);
+
+      Output->setAdditionalOutputForType(types::TY_TBD, filename);
+    }
+  }
+}
+
+void Driver::chooseSaveOptimizationPath(Compilation &C, const OutputInfo &OI,
+                                        llvm::SmallString<128> &Buf,
+                                        CommandOutput *Output) const {
+  if (OI.CompilerMode == OutputInfo::Mode::SingleCompile) {
+    auto filename = *getOutputFilenameFromPathArgOrAsTopLevel(
+        OI, C.getArgs(), options::OPT_save_optimization_record_path,
+        types::TY_OptRecord, /*TreatAsTopLevelOutput=*/true, "opt.yaml", Buf);
+
+    Output->setAdditionalOutputForType(types::TY_OptRecord, filename);
+  } else
+    // FIXME: We should use the OutputMap in this case.
+    Diags.diagnose({}, diag::warn_opt_remark_disabled);
+}
+
+void Driver::chooseObjectiveCHeaderOutputPath(Compilation &C,
+                                              const OutputInfo &OI,
+                                              const TypeToPathMap *OutputMap,
+                                              CommandOutput *Output) const {
+  StringRef ObjCHeaderPath;
+  if (OutputMap) {
+    auto iter = OutputMap->find(types::TY_ObjCHeader);
+    if (iter != OutputMap->end())
+      ObjCHeaderPath = iter->second;
+  }
+
+  if (ObjCHeaderPath.empty())
+    if (auto A = C.getArgs().getLastArg(options::OPT_emit_objc_header_path))
+      ObjCHeaderPath = A->getValue();
+
+  if (!ObjCHeaderPath.empty()) {
+    Output->setAdditionalOutputForType(types::TY_ObjCHeader, ObjCHeaderPath);
+  } else {
+    // Put the header next to the primary output file.
+    // FIXME: That's not correct if the user /just/ passed -emit-header
+    // and not -emit-module.
+    addAuxiliaryOutput(C, *Output, types::TY_ObjCHeader, OI,
+                       /*output file map*/ nullptr);
+  }
+}
+
 static unsigned printActions(const Action *A,
                              llvm::DenseMap<const Action *, unsigned> &Ids) {
   if (Ids.count(A))
diff --git a/lib/Driver/ToolChain.cpp b/lib/Driver/ToolChain.cpp
index 0ab96f2..0e95833 100644
--- a/lib/Driver/ToolChain.cpp
+++ b/lib/Driver/ToolChain.cpp
@@ -121,7 +121,7 @@
                                 executablePath,
                                 std::move(invocationInfo.Arguments),
                                 std::move(invocationInfo.ExtraEnvironment),
-                                std::move(invocationInfo.FilelistInfo));
+                                std::move(invocationInfo.FilelistInfos));
 }
 
 std::string
diff --git a/lib/Driver/ToolChains.cpp b/lib/Driver/ToolChains.cpp
index 8071960..65152ec 100644
--- a/lib/Driver/ToolChains.cpp
+++ b/lib/Driver/ToolChains.cpp
@@ -485,9 +485,9 @@
         context.Output.getPrimaryOutputFilenames().size() > TOO_MANY_FILES) {
       Arguments.push_back("-output-filelist");
       Arguments.push_back(context.getTemporaryFilePath("outputs", ""));
-      II.FilelistInfo = {Arguments.back(),
-                         context.Output.getPrimaryOutputType(),
-                         FilelistInfo::Output};
+      II.FilelistInfos.push_back({Arguments.back(),
+                                  context.Output.getPrimaryOutputType(),
+                                  FilelistInfo::WhichFiles::Output});
     } else {
       for (auto &FileName : context.Output.getPrimaryOutputFilenames()) {
         Arguments.push_back("-o");
@@ -693,8 +693,8 @@
       context.Inputs.size() > TOO_MANY_FILES) {
     Arguments.push_back("-filelist");
     Arguments.push_back(context.getTemporaryFilePath("inputs", ""));
-    II.FilelistInfo = {Arguments.back(), types::TY_SwiftModuleFile,
-                       FilelistInfo::Input};
+    II.FilelistInfos.push_back({Arguments.back(), types::TY_SwiftModuleFile,
+                                FilelistInfo::WhichFiles::Input});
 
     addInputsOfType(Arguments, context.InputActions, types::TY_SwiftModuleFile);
   } else {
@@ -1249,7 +1249,8 @@
       context.Inputs.size() > TOO_MANY_FILES) {
     Arguments.push_back("-filelist");
     Arguments.push_back(context.getTemporaryFilePath("inputs", "LinkFileList"));
-    II.FilelistInfo = {Arguments.back(), types::TY_Object, FilelistInfo::Input};
+    II.FilelistInfos.push_back(
+        {Arguments.back(), types::TY_Object, FilelistInfo::WhichFiles::Input});
   } else {
     addPrimaryInputsOfType(Arguments, context.Inputs, types::TY_Object);
   }