[clang] [llvm] [clang] Support multiple in-process cc1 jobs in a ToolSession (PR #222531)
Anutosh Bhat via llvm-commits
llvm-commits at lists.llvm.org
Tue Sep 22 02:10:23 PDT 2026
https://github.com/anutosh491 updated https://github.com/llvm/llvm-project/pull/222531
>From 71d4a489e3223f8030b83cabae142fa04d2814bf 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/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;
More information about the llvm-commits
mailing list