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
