Skip to content

Commit b4a99a0

Browse files
committed
[Clang][Driver] Re-use the calling process instead of creating a new process for the cc1 invocation
With this patch, the clang tool will now call the -cc1 invocation directly inside the same process. Previously, the -cc1 invocation was creating, and waiting for, a new process. This patch therefore reduces the number of created processes during a build, thus it reduces build times on platforms where process creation can be costly (Windows) and/or impacted by a antivirus. It also makes debugging a bit easier, as there's no need to attach to the secondary -cc1 process anymore, breakpoints will be hit inside the same process. Crashes or signaling inside the -cc1 invocation will have the same side-effect as before, and will be reported through the same means. This behavior can be controlled at compile-time through the CLANG_SPAWN_CC1 cmake flag, which defaults to OFF. Setting it to ON will revert to the previous behavior, where any -cc1 invocation will create/fork a secondary process. At run-time, it is also possible to tweak the CLANG_SPAWN_CC1 environment variable. Setting it and will override the compile-time setting. A value of 0 calls -cc1 inside the calling process; a value of 1 will create a secondary process, as before. Differential Revision: https://reviews.llvm.org/D69825
1 parent 043c5ea commit b4a99a0

File tree

13 files changed

+157
-44
lines changed

13 files changed

+157
-44
lines changed

clang/CMakeLists.txt

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -237,6 +237,9 @@ set(ENABLE_X86_RELAX_RELOCATIONS OFF CACHE BOOL
237237
set(ENABLE_EXPERIMENTAL_NEW_PASS_MANAGER FALSE CACHE BOOL
238238
"Enable the experimental new pass manager by default.")
239239

240+
set(CLANG_SPAWN_CC1 OFF CACHE BOOL
241+
"Whether clang should use a new process for the CC1 invocation")
242+
240243
# TODO: verify the values against LangStandards.def?
241244
set(CLANG_DEFAULT_STD_C "" CACHE STRING
242245
"Default standard to use for C/ObjC code (IDENT from LangStandards.def, empty for platform default)")

clang/include/clang/Config/config.h.cmake

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -80,4 +80,7 @@
8080
#cmakedefine01 CLANG_ENABLE_OBJC_REWRITER
8181
#cmakedefine01 CLANG_ENABLE_STATIC_ANALYZER
8282

83+
/* Spawn a new process clang.exe for the CC1 tool invocation, when necessary */
84+
#cmakedefine01 CLANG_SPAWN_CC1
85+
8386
#endif

clang/include/clang/Driver/Driver.h

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -204,6 +204,13 @@ class Driver {
204204
/// Whether the driver is generating diagnostics for debugging purposes.
205205
unsigned CCGenDiagnostics : 1;
206206

207+
/// Pointer to the ExecuteCC1Tool function, if available.
208+
/// When the clangDriver lib is used through clang.exe, this provides a
209+
/// shortcut for executing the -cc1 command-line directly, in the same
210+
/// process.
211+
typedef int (*CC1ToolFunc)(ArrayRef<const char *> argv);
212+
CC1ToolFunc CC1Main = nullptr;
213+
207214
private:
208215
/// Raw target triple.
209216
std::string TargetTriple;

clang/include/clang/Driver/Job.h

Lines changed: 19 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -119,7 +119,7 @@ class Command {
119119
/// \param NewEnvironment An array of environment variables.
120120
/// \remark If the environment remains unset, then the environment
121121
/// from the parent process will be used.
122-
void setEnvironment(llvm::ArrayRef<const char *> NewEnvironment);
122+
virtual void setEnvironment(llvm::ArrayRef<const char *> NewEnvironment);
123123

124124
const char *getExecutable() const { return Executable; }
125125

@@ -130,6 +130,24 @@ class Command {
130130

131131
/// Set whether to print the input filenames when executing.
132132
void setPrintInputFilenames(bool P) { PrintInputFilenames = P; }
133+
134+
protected:
135+
/// Optionally print the filenames to be compiled
136+
void PrintFileNames() const;
137+
};
138+
139+
/// Use the CC1 tool callback when available, to avoid creating a new process
140+
class CC1Command : public Command {
141+
public:
142+
using Command::Command;
143+
144+
void Print(llvm::raw_ostream &OS, const char *Terminator, bool Quote,
145+
CrashReportInfo *CrashInfo = nullptr) const override;
146+
147+
int Execute(ArrayRef<Optional<StringRef>> Redirects, std::string *ErrMsg,
148+
bool *ExecutionFailed) const override;
149+
150+
void setEnvironment(llvm::ArrayRef<const char *> NewEnvironment) override;
133151
};
134152

135153
/// Like Command, but with a fallback which is executed in case

clang/lib/Driver/Job.cpp

Lines changed: 75 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -19,8 +19,10 @@
1919
#include "llvm/ADT/StringRef.h"
2020
#include "llvm/ADT/StringSet.h"
2121
#include "llvm/ADT/StringSwitch.h"
22+
#include "llvm/Support/CrashRecoveryContext.h"
2223
#include "llvm/Support/FileSystem.h"
2324
#include "llvm/Support/Path.h"
25+
#include "llvm/Support/PrettyStackTrace.h"
2426
#include "llvm/Support/Program.h"
2527
#include "llvm/Support/raw_ostream.h"
2628
#include <algorithm>
@@ -313,15 +315,46 @@ void Command::setEnvironment(llvm::ArrayRef<const char *> NewEnvironment) {
313315
Environment.push_back(nullptr);
314316
}
315317

316-
int Command::Execute(ArrayRef<llvm::Optional<StringRef>> Redirects,
317-
std::string *ErrMsg, bool *ExecutionFailed) const {
318+
void Command::PrintFileNames() const {
318319
if (PrintInputFilenames) {
319320
for (const char *Arg : InputFilenames)
320321
llvm::outs() << llvm::sys::path::filename(Arg) << "\n";
321322
llvm::outs().flush();
322323
}
324+
}
323325

324-
SmallVector<const char*, 128> Argv;
326+
int Command::Execute(ArrayRef<llvm::Optional<StringRef>> Redirects,
327+
std::string *ErrMsg, bool *ExecutionFailed) const {
328+
PrintFileNames();
329+
330+
SmallVector<const char *, 128> Argv;
331+
if (ResponseFile == nullptr) {
332+
Argv.push_back(Executable);
333+
Argv.append(Arguments.begin(), Arguments.end());
334+
Argv.push_back(nullptr);
335+
} else {
336+
// If the command is too large, we need to put arguments in a response file.
337+
std::string RespContents;
338+
llvm::raw_string_ostream SS(RespContents);
339+
340+
// Write file contents and build the Argv vector
341+
writeResponseFile(SS);
342+
buildArgvForResponseFile(Argv);
343+
Argv.push_back(nullptr);
344+
SS.flush();
345+
346+
// Save the response file in the appropriate encoding
347+
if (std::error_code EC = writeFileWithEncoding(
348+
ResponseFile, RespContents, Creator.getResponseFileEncoding())) {
349+
if (ErrMsg)
350+
*ErrMsg = EC.message();
351+
if (ExecutionFailed)
352+
*ExecutionFailed = true;
353+
// Return -1 by convention (see llvm/include/llvm/Support/Program.h) to
354+
// indicate the requested executable cannot be started.
355+
return -1;
356+
}
357+
}
325358

326359
Optional<ArrayRef<StringRef>> Env;
327360
std::vector<StringRef> ArgvVectorStorage;
@@ -332,42 +365,51 @@ int Command::Execute(ArrayRef<llvm::Optional<StringRef>> Redirects,
332365
Env = makeArrayRef(ArgvVectorStorage);
333366
}
334367

335-
if (ResponseFile == nullptr) {
336-
Argv.push_back(Executable);
337-
Argv.append(Arguments.begin(), Arguments.end());
338-
Argv.push_back(nullptr);
368+
auto Args = llvm::toStringRefArray(Argv.data());
369+
return llvm::sys::ExecuteAndWait(Executable, Args, Env, Redirects,
370+
/*secondsToWait*/ 0,
371+
/*memoryLimit*/ 0, ErrMsg, ExecutionFailed);
372+
}
339373

340-
auto Args = llvm::toStringRefArray(Argv.data());
341-
return llvm::sys::ExecuteAndWait(
342-
Executable, Args, Env, Redirects, /*secondsToWait*/ 0,
343-
/*memoryLimit*/ 0, ErrMsg, ExecutionFailed);
344-
}
374+
void CC1Command::Print(raw_ostream &OS, const char *Terminator, bool Quote,
375+
CrashReportInfo *CrashInfo) const {
376+
OS << " (in-process)";
377+
Command::Print(OS, Terminator, Quote, CrashInfo);
378+
}
345379

346-
// We need to put arguments in a response file (command is too large)
347-
// Open stream to store the response file contents
348-
std::string RespContents;
349-
llvm::raw_string_ostream SS(RespContents);
380+
int CC1Command::Execute(ArrayRef<llvm::Optional<StringRef>> /*Redirects*/,
381+
std::string *ErrMsg, bool *ExecutionFailed) const {
382+
PrintFileNames();
350383

351-
// Write file contents and build the Argv vector
352-
writeResponseFile(SS);
353-
buildArgvForResponseFile(Argv);
384+
SmallVector<const char *, 128> Argv;
385+
Argv.push_back(getExecutable());
386+
Argv.append(getArguments().begin(), getArguments().end());
354387
Argv.push_back(nullptr);
355-
SS.flush();
356-
357-
// Save the response file in the appropriate encoding
358-
if (std::error_code EC = writeFileWithEncoding(
359-
ResponseFile, RespContents, Creator.getResponseFileEncoding())) {
360-
if (ErrMsg)
361-
*ErrMsg = EC.message();
362-
if (ExecutionFailed)
363-
*ExecutionFailed = true;
364-
return -1;
388+
389+
// This flag simply indicates that the program couldn't start, which isn't
390+
// applicable here.
391+
if (ExecutionFailed)
392+
*ExecutionFailed = false;
393+
394+
llvm::CrashRecoveryContext CRC;
395+
CRC.DumpStackAndCleanupOnFailure = true;
396+
397+
const void *PrettyState = llvm::SavePrettyStackState();
398+
const Driver &D = getCreator().getToolChain().getDriver();
399+
400+
int R = 0;
401+
// Enter ExecuteCC1Tool() instead of starting up a new process
402+
if (!CRC.RunSafely([&]() { R = D.CC1Main(Argv); })) {
403+
llvm::RestorePrettyStackState(PrettyState);
404+
return CRC.RetCode;
365405
}
406+
return R;
407+
}
366408

367-
auto Args = llvm::toStringRefArray(Argv.data());
368-
return llvm::sys::ExecuteAndWait(Executable, Args, Env, Redirects,
369-
/*secondsToWait*/ 0,
370-
/*memoryLimit*/ 0, ErrMsg, ExecutionFailed);
409+
void CC1Command::setEnvironment(llvm::ArrayRef<const char *> NewEnvironment) {
410+
// We don't support set a new environment when calling into ExecuteCC1Tool()
411+
llvm_unreachable(
412+
"The CC1Command doesn't support changing the environment vars!");
371413
}
372414

373415
FallbackCommand::FallbackCommand(const Action &Source_, const Tool &Creator_,

clang/lib/Driver/ToolChains/Clang.cpp

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5979,6 +5979,10 @@ void Clang::ConstructJob(Compilation &C, const JobAction &JA,
59795979
// fails, so that the main compilation's fallback to cl.exe runs.
59805980
C.addCommand(std::make_unique<ForceSuccessCommand>(JA, *this, Exec,
59815981
CmdArgs, Inputs));
5982+
} else if (D.CC1Main && !D.CCGenDiagnostics) {
5983+
// Invoke the CC1 directly in this process
5984+
C.addCommand(
5985+
std::make_unique<CC1Command>(JA, *this, Exec, CmdArgs, Inputs));
59825986
} else {
59835987
C.addCommand(std::make_unique<Command>(JA, *this, Exec, CmdArgs, Inputs));
59845988
}

clang/test/CMakeLists.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ llvm_canonicalize_cmake_booleans(
1313
CLANG_BUILD_EXAMPLES
1414
CLANG_ENABLE_ARCMT
1515
CLANG_ENABLE_STATIC_ANALYZER
16+
CLANG_SPAWN_CC1
1617
ENABLE_BACKTRACES
1718
ENABLE_EXPERIMENTAL_NEW_PASS_MANAGER
1819
LLVM_ENABLE_ZLIB
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
// RUN: env -u CLANG_SPAWN_CC1 %clang -c %s -o /dev/null
2+
// RUN: env CLANG_SPAWN_CC1=0 %clang -c %s -o /dev/null
3+
// RUN: env CLANG_SPAWN_CC1=1 %clang -c %s -o /dev/null
4+
// RUN: env CLANG_SPAWN_CC1=test not %clang -c %s -o /dev/null

clang/test/Driver/clang_f_opts.c

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -493,7 +493,7 @@
493493
// RUN: %clang -target x86_64-unknown-none-none -### -fno-short-wchar -fshort-wchar %s 2>&1 | FileCheck -check-prefix=CHECK-WCHAR2 -check-prefix=DELIMITERS %s
494494
// Make sure we don't match the -NOT lines with the linker invocation.
495495
// Delimiters match the start of the cc1 and the start of the linker lines
496-
// DELIMITERS: {{^ *"}}
496+
// DELIMITERS: {{^ (\(in-process\)|")}}
497497
// CHECK-WCHAR1: -fwchar-type=int
498498
// CHECK-WCHAR1-NOT: -fwchar-type=short
499499
// CHECK-WCHAR2: -fwchar-type=short

clang/test/Driver/fsanitize-blacklist.c

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66
// Make sure we don't match the -NOT lines with the linker invocation.
77
// Delimiters match the start of the cc1 and the start of the linker lines
88
// for fragile tests.
9-
// DELIMITERS: {{^ *"}}
9+
// DELIMITERS: {{^ (\(in-process\)|")}}
1010

1111
// RUN: echo "fun:foo" > %t.good
1212
// RUN: echo "fun:bar" > %t.second

0 commit comments

Comments
 (0)