[clang] [llvm] [clang] Support multiple in-process cc1 jobs in a ToolSession (PR #222531)

Anutosh Bhat via cfe-commits cfe-commits at lists.llvm.org
Sun Oct 18 01:46:01 PDT 2026


https://github.com/anutosh491 updated https://github.com/llvm/llvm-project/pull/222531

>From fc5ccef97d8afff43751a859f627ba60bc2e13be Mon Sep 17 00:00:00 2001
From: anutosh491 <andersonbhat491 at gmail.com>
Date: Thu, 10 Sep 2026 12:22:00 +0530
Subject: [PATCH] [clang] Support multiple in-process cc1 jobs in a ToolSession

---
 clang/include/clang/Driver/Compilation.h      |  9 ++-
 clang/include/clang/Driver/Driver.h           |  6 +-
 clang/include/clang/Driver/Job.h              |  6 ++
 clang/lib/Driver/Compilation.cpp              |  7 +-
 clang/lib/Driver/Driver.cpp                   | 12 ++--
 clang/lib/Driver/Job.cpp                      | 13 ++--
 clang/lib/Driver/ToolChains/Clang.cpp         |  7 +-
 clang/test/Driver/cc1-spawnprocess.c          | 11 +--
 clang/test/Driver/clang-translation.c         |  4 +-
 .../Driver/in-process-multiple-cc1-crash.c    | 20 ++++++
 clang/test/Driver/in-process-multiple-cc1.c   | 68 +++++++++++++++++++
 clang/tools/driver/cc1_main.cpp               | 30 ++++----
 clang/tools/driver/driver.cpp                 |  4 +-
 llvm/include/llvm/Support/Driver.h            |  3 +
 .../LLVMToolSession/LLVMToolSessionTest.cpp   |  6 ++
 15 files changed, 170 insertions(+), 36 deletions(-)
 create mode 100644 clang/test/Driver/in-process-multiple-cc1-crash.c
 create mode 100644 clang/test/Driver/in-process-multiple-cc1.c

diff --git a/clang/include/clang/Driver/Compilation.h b/clang/include/clang/Driver/Compilation.h
index 825806b6cfe332..79960bb6e80a12 100644
--- a/clang/include/clang/Driver/Compilation.h
+++ b/clang/include/clang/Driver/Compilation.h
@@ -121,6 +121,10 @@ class Compilation {
   /// Whether we're compiling for diagnostic purposes.
   bool ForDiagnostics = false;
 
+  /// Whether the in-process cc1 callback supports repeated invocation.
+  /// Reusable callbacks must free per-invocation state before returning.
+  bool CC1MainIsReusable = false;
+
   /// Whether an error during the parsing of the input args.
   bool ContainsError;
 
@@ -134,7 +138,8 @@ class Compilation {
 public:
   Compilation(const Driver &D, const ToolChain &DefaultToolChain,
               llvm::opt::InputArgList *Args,
-              llvm::opt::DerivedArgList *TranslatedArgs, bool ContainsError);
+              llvm::opt::DerivedArgList *TranslatedArgs, bool ContainsError,
+              bool CC1MainIsReusable);
   ~Compilation();
 
   const Driver &getDriver() const { return TheDriver; }
@@ -337,6 +342,8 @@ class Compilation {
   /// Return true if we're compiling for diagnostics.
   bool isForDiagnostics() const { return ForDiagnostics; }
 
+  bool isCC1MainReusable() const { return CC1MainIsReusable; }
+
   /// Return whether an error during the parsing of the input args.
   bool containsError() const { return ContainsError; }
 
diff --git a/clang/include/clang/Driver/Driver.h b/clang/include/clang/Driver/Driver.h
index 15b6fdcb8a5746..f829474cd776b0 100644
--- a/clang/include/clang/Driver/Driver.h
+++ b/clang/include/clang/Driver/Driver.h
@@ -463,11 +463,15 @@ class Driver {
   /// BuildCompilation - Construct a compilation object for a command
   /// line argument vector.
   ///
+  /// \param CC1MainIsReusable Whether the in-process cc1 callback supports
+  /// repeated invocation and therefore requires normal per-job cleanup.
+  ///
   /// \return A compilation, or 0 if none was built for the given
   /// argument vector. A null return value does not necessarily
   /// indicate an error condition, the diagnostics should be queried
   /// to determine if an error occurred.
-  Compilation *BuildCompilation(ArrayRef<const char *> Args);
+  Compilation *BuildCompilation(ArrayRef<const char *> Args,
+                                bool CC1MainIsReusable = false);
 
   /// ParseArgStrings - Parse the given list of strings into an
   /// ArgList.
diff --git a/clang/include/clang/Driver/Job.h b/clang/include/clang/Driver/Job.h
index 03779be5b5a6a0..5ac3d2bae84bb7 100644
--- a/clang/include/clang/Driver/Job.h
+++ b/clang/include/clang/Driver/Job.h
@@ -259,7 +259,13 @@ class Command {
 
 /// Use the CC1 tool callback when available, to avoid creating a new process
 class CC1Command : public Command {
+  static bool CanRunAgain;
+
 public:
+  /// Whether this process can run cc1 again after crash recovery.
+  /// Embedding hosts must discard it if this returns false.
+  static bool canRunAgain();
+
   CC1Command(const Action &Source, const Tool &Creator,
              ResponseFileSupport ResponseSupport, const char *Executable,
              const llvm::opt::ArgStringList &Arguments,
diff --git a/clang/lib/Driver/Compilation.cpp b/clang/lib/Driver/Compilation.cpp
index c81c4445a29f95..42edb82e0eddd4 100644
--- a/clang/lib/Driver/Compilation.cpp
+++ b/clang/lib/Driver/Compilation.cpp
@@ -37,9 +37,10 @@ using namespace llvm::opt;
 
 Compilation::Compilation(const Driver &D, const ToolChain &_DefaultToolChain,
                          InputArgList *_Args, DerivedArgList *_TranslatedArgs,
-                         bool ContainsError)
+                         bool ContainsError, bool CC1MainIsReusable)
     : TheDriver(D), DefaultToolChain(_DefaultToolChain), Args(_Args),
-      TranslatedArgs(_TranslatedArgs), ContainsError(ContainsError) {
+      TranslatedArgs(_TranslatedArgs), CC1MainIsReusable(CC1MainIsReusable),
+      ContainsError(ContainsError) {
   // The offloading host toolchain is the default toolchain.
   OrderedOffloadingToolchains.insert(
       std::make_pair(Action::OFK_Host, &DefaultToolChain));
@@ -400,6 +401,8 @@ void Compilation::ExecuteJobs(const JobList &Jobs,
       if (TheDriver.IsCLMode())
         return;
     }
+    if (Job.InProcess && !CC1Command::canRunAgain())
+      return;
     ++I;
   }
 }
diff --git a/clang/lib/Driver/Driver.cpp b/clang/lib/Driver/Driver.cpp
index b2dbc32f1b89fd..8026658b10254e 100644
--- a/clang/lib/Driver/Driver.cpp
+++ b/clang/lib/Driver/Driver.cpp
@@ -1506,7 +1506,8 @@ bool Driver::loadDefaultConfigFiles(llvm::cl::ExpansionContext &ExpCtx) {
   return false;
 }
 
-Compilation *Driver::BuildCompilation(ArrayRef<const char *> ArgList) {
+Compilation *Driver::BuildCompilation(ArrayRef<const char *> ArgList,
+                                      bool CC1MainIsReusable) {
   llvm::PrettyStackTraceString CrashInfo("Compilation construction");
 
   // FIXME: Handle environment options which affect driver behavior, somewhere
@@ -1839,7 +1840,7 @@ Compilation *Driver::BuildCompilation(ArrayRef<const char *> ArgList) {
 
   // The compilation takes ownership of Args.
   Compilation *C = new Compilation(*this, TC, UArgs.release(), TranslatedArgs,
-                                   ContainsError);
+                                   ContainsError, CC1MainIsReusable);
 
   if (!HandleImmediateArgs(*C))
     return C;
@@ -4678,9 +4679,10 @@ void Driver::BuildJobs(Compilation &C) const {
                        /*TargetDeviceOffloadKind*/ Action::OFK_None);
   }
 
-  // If we have more than one job, then disable integrated-cc1 for now. Do this
-  // also when we need to report process execution statistics.
-  if (C.getJobs().size() > 1 || CCPrintProcessStats)
+  // Unless the callback explicitly supports repeated invocation, use a
+  // separate process when there is more than one job. Process execution
+  // statistics always require a separate process.
+  if ((C.getJobs().size() > 1 && !C.isCC1MainReusable()) || CCPrintProcessStats)
     for (auto &J : C.getJobs())
       J.InProcess = false;
 
diff --git a/clang/lib/Driver/Job.cpp b/clang/lib/Driver/Job.cpp
index da7a1f2e07e900..7ceaae0860b2fe 100644
--- a/clang/lib/Driver/Job.cpp
+++ b/clang/lib/Driver/Job.cpp
@@ -387,6 +387,10 @@ int Command::Execute(ArrayRef<std::optional<StringRef>> Redirects,
                                    ErrMsg, ExecutionFailed, &ProcStat);
 }
 
+bool CC1Command::CanRunAgain = true;
+
+bool CC1Command::canRunAgain() { return CanRunAgain; }
+
 CC1Command::CC1Command(const Action &Source, const Tool &Creator,
                        ResponseFileSupport ResponseSupport,
                        const char *Executable,
@@ -400,17 +404,15 @@ CC1Command::CC1Command(const Action &Source, const Tool &Creator,
 
 void CC1Command::Print(raw_ostream &OS, const char *Terminator, bool Quote,
                        CrashReportInfo *CrashInfo) const {
-  if (InProcess)
+  if (InProcess && canRunAgain())
     OS << " (in-process)\n";
   Command::Print(OS, Terminator, Quote, CrashInfo);
 }
 
 int CC1Command::Execute(ArrayRef<std::optional<StringRef>> Redirects,
                         std::string *ErrMsg, bool *ExecutionFailed) const {
-  // FIXME: Currently, if there're more than one job, we disable
-  // -fintegrate-cc1. If we're no longer a integrated-cc1 job, fallback to
-  // out-of-process execution. See discussion in https://reviews.llvm.org/D74447
-  if (!InProcess)
+  // Use a separate process for crash diagnostics after an in-process crash.
+  if (!InProcess || !canRunAgain())
     return Command::Execute(Redirects, ErrMsg, ExecutionFailed);
 
   PrintFileNames();
@@ -440,6 +442,7 @@ int CC1Command::Execute(ArrayRef<std::optional<StringRef>> Redirects,
   int R = 0;
   // Enter ExecuteCC1Tool() instead of starting up a new process
   if (!CRC.RunSafely([&]() { R = D.CC1Main(Argv); })) {
+    CanRunAgain = false;
     llvm::RestorePrettyStackState(PrettyState);
     return CRC.RetCode;
   }
diff --git a/clang/lib/Driver/ToolChains/Clang.cpp b/clang/lib/Driver/ToolChains/Clang.cpp
index 45aede75681103..4870cb3c066d09 100644
--- a/clang/lib/Driver/ToolChains/Clang.cpp
+++ b/clang/lib/Driver/ToolChains/Clang.cpp
@@ -5822,9 +5822,10 @@ void Clang::ConstructJob(Compilation &C, const JobAction &JA,
     CmdArgs.push_back("-fembed-bitcode=marker");
 
   // We normally speed up the clang process a bit by skipping destructors at
-  // exit, but when we're generating diagnostics we can rely on some of the
-  // cleanup.
-  if (!C.isForDiagnostics())
+  // exit. Diagnostic compilations and reusable in-process callbacks must run
+  // the normal cleanup instead.
+  if (!C.isForDiagnostics() &&
+      (!C.isCC1MainReusable() || D.CCPrintProcessStats))
     CmdArgs.push_back("-disable-free");
   CmdArgs.push_back("-clear-ast-before-backend");
 
diff --git a/clang/test/Driver/cc1-spawnprocess.c b/clang/test/Driver/cc1-spawnprocess.c
index a6ff7d148604e3..dea0a778ec9cca 100644
--- a/clang/test/Driver/cc1-spawnprocess.c
+++ b/clang/test/Driver/cc1-spawnprocess.c
@@ -31,15 +31,18 @@
 // Only one TU, one job, thus integrated-cc1 is enabled.
 // RUN: %clang -fintegrated-cc1 -fintegrated-as -c %s -### 2>&1 | FileCheck %s --check-prefix=YES
 
-// Only one TU, but we're linking, two jobs, thus integrated-cc1 is disabled.
-// RUN: %clang -fintegrated-cc1 %s -### 2>&1 | FileCheck %s --check-prefix=NO
+// Only one TU, but we're linking, two jobs. A reusable cc1 callback remains
+// in-process; an ordinary callback is disabled.
+// RUN: %clang -fintegrated-cc1 %s -### 2>&1 | FileCheck %s \
+// RUN:   --check-prefix=%if llvm-driver %{YES%} %else %{NO%}
 
 // RUN: echo 'int main() { return f() + g(); }' > %t1.cpp
 // RUN: echo 'int f() { return 1; }' > %t2.cpp
 // RUN: echo 'int g() { return 2; }' > %t3.cpp
 
-// Three jobs, thus integrated-cc1 is disabled.
-// RUN: %clang -fintegrated-cc1 -c %t1.cpp %t2.cpp %t3.cpp -### 2>&1 | FileCheck %s --check-prefix=NO
+// Three jobs likewise use a reusable callback when one is available.
+// RUN: %clang -fintegrated-cc1 -c %t1.cpp %t2.cpp %t3.cpp -### 2>&1 \
+// RUN:   | FileCheck %s --check-prefix=%if llvm-driver %{YES%} %else %{NO%}
 
 // -fintegrated-cc1 works with cc1as.
 // macOS triples have an extra -x assembler-with-cpp job so (in-process) is not triggered.
diff --git a/clang/test/Driver/clang-translation.c b/clang/test/Driver/clang-translation.c
index 5ec052a7aaa112..3f98b0bc8c5f62 100644
--- a/clang/test/Driver/clang-translation.c
+++ b/clang/test/Driver/clang-translation.c
@@ -1,4 +1,4 @@
-// RUN: %clang -target i386-unknown-unknown -### -S -O0 -Os %s -o %t.s -fverbose-asm -fvisibility=hidden 2>&1 | FileCheck -check-prefix=I386 %s
+// RUN: %clang -fno-integrated-cc1 -target i386-unknown-unknown -### -S -O0 -Os %s -o %t.s -fverbose-asm -fvisibility=hidden 2>&1 | FileCheck -check-prefix=I386 %s
 // I386: "-triple" "i386-unknown-unknown"
 // I386: "-Os"
 // I386: "-S"
@@ -10,7 +10,7 @@
 // I386: "-o"
 // I386: clang-translation
 
-// RUN: %clang -target i386-unknown-unknown -### -S %s -o %t.s -Xclang -no-disable-free 2>&1 | FileCheck -check-prefix=FREE %s
+// RUN: %clang -fno-integrated-cc1 -target i386-unknown-unknown -### -S %s -o %t.s -Xclang -no-disable-free 2>&1 | FileCheck -check-prefix=FREE %s
 // FREE: "-disable-free"
 // FREE: "-no-disable-free"
 
diff --git a/clang/test/Driver/in-process-multiple-cc1-crash.c b/clang/test/Driver/in-process-multiple-cc1-crash.c
new file mode 100644
index 00000000000000..3fc4d39a7d06dc
--- /dev/null
+++ b/clang/test/Driver/in-process-multiple-cc1-crash.c
@@ -0,0 +1,20 @@
+// REQUIRES: llvm-driver, webassembly-registered-target, crash-recovery
+
+// A recovered compiler crash must not execute the next independent cc1 job.
+// RUN: rm -rf %t
+// RUN: split-file %s %t
+// RUN: cd %t && not %clang -fintegrated-cc1 -fno-crash-diagnostics \
+// RUN:   --target=wasm32-unknown-unknown -c before.c crash.c after.c 2>&1 \
+// RUN:   | FileCheck %s --implicit-check-not='Error handler already registered'
+// RUN: test -f %t/before.o
+// RUN: not test -f %t/after.o
+// CHECK: fatal error: error in backend: #pragma clang __debug llvm_fatal_error
+
+//--- before.c
+int before(void) { return 1; }
+
+//--- crash.c
+#pragma clang __debug llvm_fatal_error
+
+//--- after.c
+int after(void) { return 2; }
diff --git a/clang/test/Driver/in-process-multiple-cc1.c b/clang/test/Driver/in-process-multiple-cc1.c
new file mode 100644
index 00000000000000..a01f91e1680fb2
--- /dev/null
+++ b/clang/test/Driver/in-process-multiple-cc1.c
@@ -0,0 +1,68 @@
+// REQUIRES: llvm-driver, webassembly-registered-target
+
+// A Clang invocation owned by ToolSession can execute multiple cc1 jobs
+// in-process. Each job must free its CompilerInstance before returning.
+// RUN: rm -rf %t
+// RUN: split-file %s %t
+// RUN: cd %t && %clang -fintegrated-cc1 --target=wasm32-unknown-unknown -c -### \
+// RUN:   first.c second.c 2>&1 \
+// RUN:   | FileCheck %s --check-prefix=COMMANDS \
+// RUN:       --implicit-check-not='"-disable-free"'
+// COMMANDS-COUNT-2: (in-process)
+
+// The same cleanup rule applies to a single cc1 job because the session stays
+// alive after the top-level Clang invocation returns.
+// RUN: cd %t && %clang -fintegrated-cc1 --target=wasm32-unknown-unknown -c -### first.c 2>&1 \
+// RUN:   | FileCheck %s --check-prefix=SINGLE \
+// RUN:       --implicit-check-not='"-disable-free"'
+// SINGLE: (in-process)
+
+// An explicit request for a separate cc1 process remains authoritative.
+// RUN: cd %t && %clang --target=wasm32-unknown-unknown \
+// RUN:   -fno-integrated-cc1 -c -### first.c second.c 2>&1 \
+// RUN:   | FileCheck %s --check-prefix=SPAWN \
+// RUN:       --implicit-check-not='(in-process)'
+// SPAWN-COUNT-2: "-disable-free"
+
+// Exercise the jobs and verify that both objects were emitted successfully.
+// RUN: cd %t && %clang -fintegrated-cc1 --target=wasm32-unknown-unknown \
+// RUN:   -c first.c second.c
+// RUN: llvm-readobj --file-headers %t/first.o %t/second.o \
+// RUN:   | FileCheck %s --check-prefix=OBJECTS
+// OBJECTS-COUNT-2: Format: WASM
+
+// A failed cc1 job does not prevent the following job from running in the
+// same process, regardless of which source comes first.
+// RUN: cd %t && not %clang -fintegrated-cc1 --target=wasm32-unknown-unknown \
+// RUN:   -c good-before-bad.c bad-after-good.c
+// RUN: test -f %t/good-before-bad.o
+// RUN: cd %t && not %clang -fintegrated-cc1 --target=wasm32-unknown-unknown \
+// RUN:   -c bad-before-good.c good-after-bad.c
+// RUN: test -f %t/good-after-bad.o
+
+// Failure to open one time-trace file must still clean up the profiler before
+// the next in-process job initializes it again.
+// RUN: mkdir %t/first.json
+// RUN: cd %t && %clang -fintegrated-cc1 --target=wasm32-unknown-unknown \
+// RUN:   -ftime-trace -c first.c second.c 2>&1 \
+// RUN:   | FileCheck %s --check-prefix=TRACE
+// RUN: test -f %t/second.json
+// TRACE: error: unable to open output file 'first.json'
+
+//--- first.c
+int first(void) { return 1; }
+
+//--- second.c
+int second(void) { return 2; }
+
+//--- good-before-bad.c
+int good_before_bad(void) { return 3; }
+
+//--- bad-after-good.c
+int bad_after_good(void) { return does_not_parse( }
+
+//--- bad-before-good.c
+int bad_before_good(void) { return does_not_parse( }
+
+//--- good-after-bad.c
+int good_after_bad(void) { return 4; }
diff --git a/clang/tools/driver/cc1_main.cpp b/clang/tools/driver/cc1_main.cpp
index 89b0a340e6672e..8fa60c98e8a747 100644
--- a/clang/tools/driver/cc1_main.cpp
+++ b/clang/tools/driver/cc1_main.cpp
@@ -38,6 +38,7 @@
 #include "llvm/Option/OptTable.h"
 #include "llvm/Support/BuryPointer.h"
 #include "llvm/Support/Compiler.h"
+#include "llvm/Support/CrashRecoveryContext.h"
 #include "llvm/Support/ErrorHandling.h"
 #include "llvm/Support/IOSandbox.h"
 #include "llvm/Support/ManagedStatic.h"
@@ -250,11 +251,6 @@ int cc1_main(ArrayRef<const char *> Argv, const char *Argv0, void *MainAddr) {
   auto Clang = std::make_unique<CompilerInstance>(std::move(Invocation),
                                                   std::move(PCHOps));
 
-  if (!Clang->getFrontendOpts().TimeTracePath.empty()) {
-    llvm::timeTraceProfilerInitialize(
-        Clang->getFrontendOpts().TimeTraceGranularity, Argv0,
-        Clang->getFrontendOpts().TimeTraceVerbose);
-  }
   // --print-supported-cpus takes priority over the actual compilation.
   if (Clang->getFrontendOpts().PrintSupportedCPUs)
     return PrintSupportedCPUs(Clang->getTargetOpts().Triple);
@@ -283,15 +279,24 @@ int cc1_main(ArrayRef<const char *> Argv, const char *Argv0, void *MainAddr) {
   // Create the actual diagnostics engine.
   Clang->createDiagnostics();
 
-  // Set an error handler, so that any LLVM backend diagnostics go through our
-  // error handler.
-  llvm::install_fatal_error_handler(LLVMErrorHandler,
-                                  static_cast<void*>(&Clang->getDiagnostics()));
-
   DiagsBuffer->FlushDiagnostics(Clang->getDiagnostics());
   if (!Success)
     return 1;
 
+  // Initialize process-global state only after the early-return paths above.
+  if (!Clang->getFrontendOpts().TimeTracePath.empty()) {
+    llvm::timeTraceProfilerInitialize(
+        Clang->getFrontendOpts().TimeTraceGranularity, Argv0,
+        Clang->getFrontendOpts().TimeTraceVerbose);
+  }
+
+  // Set an error handler, so that any LLVM backend diagnostics go through our
+  // error handler.
+  auto ErrorHandler = std::make_unique<llvm::ScopedFatalErrorHandler>(
+      LLVMErrorHandler, &Clang->getDiagnostics());
+  llvm::CrashRecoveryContextCleanupRegistrar<llvm::ScopedFatalErrorHandler>
+      ErrorHandlerCleanup(ErrorHandler.get());
+
   // Execute the frontend actions.
   Success = ExecuteCompilerInvocation(Clang.get());
 
@@ -318,15 +323,16 @@ int cc1_main(ArrayRef<const char *> Argv, const char *Argv0, void *MainAddr) {
             /*useTemporary=*/false)) {
       llvm::timeTraceProfilerWrite(*profilerOutput);
       profilerOutput.reset();
-      llvm::timeTraceProfilerCleanup();
       Clang->clearOutputFiles(false);
     }
+    llvm::timeTraceProfilerCleanup();
   }
 
   // Our error handler depends on the Diagnostics object, which we're
   // potentially about to delete. Uninstall the handler now so that any
   // later errors use the default handling behavior instead.
-  llvm::remove_fatal_error_handler();
+  ErrorHandlerCleanup.unregister();
+  ErrorHandler.reset();
 
   // When running with -disable-free, don't do any destruction or shutdown.
   if (Clang->getFrontendOpts().DisableFree) {
diff --git a/clang/tools/driver/driver.cpp b/clang/tools/driver/driver.cpp
index af96812e9ebd97..ab41b11c2046ca 100644
--- a/clang/tools/driver/driver.cpp
+++ b/clang/tools/driver/driver.cpp
@@ -385,7 +385,9 @@ int clang_main(int Argc, char **Argv, const llvm::ToolContext &ToolContext) {
         /*NeedsPOSIXUtilitySignalHandling=*/true);
   }
 
-  std::unique_ptr<Compilation> C(TheDriver.BuildCompilation(Args));
+  bool CC1MainIsReusable = ToolContext.hasSession() && !UseNewCC1Process;
+  std::unique_ptr<Compilation> C(
+      TheDriver.BuildCompilation(Args, CC1MainIsReusable));
 
   Driver::ReproLevel ReproLevel = Driver::ReproLevel::OnCrash;
   if (Arg *A = C->getArgs().getLastArg(options::OPT_gen_reproducer_eq)) {
diff --git a/llvm/include/llvm/Support/Driver.h b/llvm/include/llvm/Support/Driver.h
index eef1d21cc8e0a9..e65cc416ba048e 100644
--- a/llvm/include/llvm/Support/Driver.h
+++ b/llvm/include/llvm/Support/Driver.h
@@ -56,6 +56,9 @@ class ToolContext {
 
   /// Invokes another tool registered with the same host session.
   LLVM_ABI ErrorOr<int> callTool(ArrayRef<const char *> Args) const;
+
+  /// Returns true when this invocation is owned by a tool session.
+  bool hasSession() const { return Session != nullptr; }
 };
 
 /// Owns LLVM process initialization and an in-process tool registry.
diff --git a/llvm/unittests/Support/LLVMToolSession/LLVMToolSessionTest.cpp b/llvm/unittests/Support/LLVMToolSession/LLVMToolSessionTest.cpp
index e0252324ed4037..c6eb625f347076 100644
--- a/llvm/unittests/Support/LLVMToolSession/LLVMToolSessionTest.cpp
+++ b/llvm/unittests/Support/LLVMToolSession/LLVMToolSessionTest.cpp
@@ -28,6 +28,7 @@ int linkerMain(int Argc, char **Argv, const ToolContext &Context) {
   ++LinkerCalls;
   EXPECT_EQ(Argc, 3);
   EXPECT_STREQ(Argv[0], "wasm-ld");
+  EXPECT_TRUE(Context.hasSession());
   EXPECT_TRUE(Context.getCallableTool("clang"));
   return 0;
 }
@@ -72,6 +73,11 @@ int fuzzyMain(int Argc, char **Argv, const ToolContext &Context) {
   return 0;
 }
 
+TEST(LLVMToolSessionTest, DistinguishesStandaloneContext) {
+  ToolContext Context("clang", nullptr, false);
+  EXPECT_FALSE(Context.hasSession());
+}
+
 TEST(LLVMToolSessionTest, SupportsSequentialNestedToolCalls) {
   unsigned CompilerCallsBefore = CompilerCalls;
   unsigned LinkerCallsBefore = LinkerCalls;



More information about the cfe-commits mailing list