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

>From 71d4a489e3223f8030b83cabae142fa04d2814bf Mon Sep 17 00:00:00 2001
From: anutosh491 <[email protected]>
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/lib/Driver/Compilation.cpp              |  5 +-
 clang/lib/Driver/Driver.cpp                   | 12 ++--
 clang/lib/Driver/ToolChains/Clang.cpp         |  7 ++-
 clang/test/Driver/cc1-spawnprocess.c          | 11 ++--
 clang/test/Driver/clang-translation.c         |  4 +-
 clang/test/Driver/in-process-multiple-cc1.c   | 58 +++++++++++++++++++
 clang/tools/driver/cc1_main.cpp               | 21 ++++---
 clang/tools/driver/driver.cpp                 |  4 +-
 llvm/include/llvm/Support/Driver.h            |  3 +
 .../LLVMToolSession/LLVMToolSessionTest.cpp   |  6 ++
 12 files changed, 118 insertions(+), 28 deletions(-)
 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 825806b6cfe33..79960bb6e80a1 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 e653d8e3a2dbe..d1b399f4d735f 100644
--- a/clang/include/clang/Driver/Driver.h
+++ b/clang/include/clang/Driver/Driver.h
@@ -464,11 +464,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/lib/Driver/Compilation.cpp b/clang/lib/Driver/Compilation.cpp
index c81c4445a29f9..f580b1cf285a6 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));
diff --git a/clang/lib/Driver/Driver.cpp b/clang/lib/Driver/Driver.cpp
index 7a742e404bf5c..1d2004b3165a4 100644
--- a/clang/lib/Driver/Driver.cpp
+++ b/clang/lib/Driver/Driver.cpp
@@ -1532,7 +1532,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
@@ -1865,7 +1866,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;
@@ -4697,9 +4698,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/ToolChains/Clang.cpp 
b/clang/lib/Driver/ToolChains/Clang.cpp
index 6636a5fd6e655..1d0a79db8b525 100644
--- a/clang/lib/Driver/ToolChains/Clang.cpp
+++ b/clang/lib/Driver/ToolChains/Clang.cpp
@@ -5758,9 +5758,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 a6ff7d148604e..dea0a778ec9cc 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 5ec052a7aaa11..3f98b0bc8c5f6 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.c 
b/clang/test/Driver/in-process-multiple-cc1.c
new file mode 100644
index 0000000000000..28466f9bcfcef
--- /dev/null
+++ b/clang/test/Driver/in-process-multiple-cc1.c
@@ -0,0 +1,58 @@
+// 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: 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
+
+//--- 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 89b0a340e6672..c2720b275217c 100644
--- a/clang/tools/driver/cc1_main.cpp
+++ b/clang/tools/driver/cc1_main.cpp
@@ -250,11 +250,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 +278,23 @@ int cc1_main(ArrayRef<const char *> Argv, const char 
*Argv0, void *MainAddr) {
   // Create the actual diagnostics engine.
   Clang->createDiagnostics();
 
+  DiagsBuffer->FlushDiagnostics(Clang->getDiagnostics());
+  if (!Success)
+    return 1;
+
+  // Initialize process-global state only after the early-return paths above.
+  // Everything below reaches the corresponding cleanup before returning.
+  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.
   llvm::install_fatal_error_handler(LLVMErrorHandler,
                                   
static_cast<void*>(&Clang->getDiagnostics()));
 
-  DiagsBuffer->FlushDiagnostics(Clang->getDiagnostics());
-  if (!Success)
-    return 1;
-
   // Execute the frontend actions.
   Success = ExecuteCompilerInvocation(Clang.get());
 
diff --git a/clang/tools/driver/driver.cpp b/clang/tools/driver/driver.cpp
index af96812e9ebd9..ab41b11c2046c 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 eef1d21cc8e0a..e65cc416ba048 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 d1ef91444c2f7..e34080f920126 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;
 }
@@ -70,6 +71,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;

_______________________________________________
cfe-commits mailing list
[email protected]
https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits

Reply via email to