diff --git a/clang/CMakeLists.txt b/clang/CMakeLists.txt --- a/clang/CMakeLists.txt +++ b/clang/CMakeLists.txt @@ -237,6 +237,9 @@ set(ENABLE_EXPERIMENTAL_NEW_PASS_MANAGER FALSE CACHE BOOL "Enable the experimental new pass manager by default.") +set(CLANG_SPAWN_CC1 OFF CACHE BOOL + "Whether clang should use a new process for the CC1 invocation") + # TODO: verify the values against LangStandards.def? set(CLANG_DEFAULT_STD_C "" CACHE STRING "Default standard to use for C/ObjC code (IDENT from LangStandards.def, empty for platform default)") diff --git a/clang/include/clang/Config/config.h.cmake b/clang/include/clang/Config/config.h.cmake --- a/clang/include/clang/Config/config.h.cmake +++ b/clang/include/clang/Config/config.h.cmake @@ -80,4 +80,7 @@ #cmakedefine01 CLANG_ENABLE_OBJC_REWRITER #cmakedefine01 CLANG_ENABLE_STATIC_ANALYZER +/* Spawn a new process clang.exe for the CC1 tool invocation, when necessary */ +#cmakedefine01 CLANG_SPAWN_CC1 + #endif diff --git a/clang/include/clang/Driver/Driver.h b/clang/include/clang/Driver/Driver.h --- a/clang/include/clang/Driver/Driver.h +++ b/clang/include/clang/Driver/Driver.h @@ -204,6 +204,13 @@ /// Whether the driver is generating diagnostics for debugging purposes. unsigned CCGenDiagnostics : 1; + /// Pointer to the ExecuteCC1Tool function, if available. + /// When the clangDriver lib is used through clang.exe, this provides a + /// shortcut for executing the -cc1 command-line directly, in the same + /// process. + typedef int (*CC1ToolFunc)(ArrayRef argv); + CC1ToolFunc CC1Main = nullptr; + private: /// Raw target triple. std::string TargetTriple; diff --git a/clang/include/clang/Driver/Job.h b/clang/include/clang/Driver/Job.h --- a/clang/include/clang/Driver/Job.h +++ b/clang/include/clang/Driver/Job.h @@ -119,7 +119,7 @@ /// \param NewEnvironment An array of environment variables. /// \remark If the environment remains unset, then the environment /// from the parent process will be used. - void setEnvironment(llvm::ArrayRef NewEnvironment); + virtual void setEnvironment(llvm::ArrayRef NewEnvironment); const char *getExecutable() const { return Executable; } @@ -130,6 +130,24 @@ /// Set whether to print the input filenames when executing. void setPrintInputFilenames(bool P) { PrintInputFilenames = P; } + +protected: + /// Optionally print the filenames to be compiled + void PrintFileNames() const; +}; + +/// Use the CC1 tool callback when available, to avoid creating a new process +class CC1Command : public Command { +public: + using Command::Command; + + void Print(llvm::raw_ostream &OS, const char *Terminator, bool Quote, + CrashReportInfo *CrashInfo = nullptr) const override; + + int Execute(ArrayRef> Redirects, std::string *ErrMsg, + bool *ExecutionFailed) const override; + + void setEnvironment(llvm::ArrayRef NewEnvironment) override; }; /// Like Command, but with a fallback which is executed in case diff --git a/clang/lib/Driver/Job.cpp b/clang/lib/Driver/Job.cpp --- a/clang/lib/Driver/Job.cpp +++ b/clang/lib/Driver/Job.cpp @@ -19,8 +19,10 @@ #include "llvm/ADT/StringRef.h" #include "llvm/ADT/StringSet.h" #include "llvm/ADT/StringSwitch.h" +#include "llvm/Support/CrashRecoveryContext.h" #include "llvm/Support/FileSystem.h" #include "llvm/Support/Path.h" +#include "llvm/Support/PrettyStackTrace.h" #include "llvm/Support/Program.h" #include "llvm/Support/raw_ostream.h" #include @@ -313,15 +315,46 @@ Environment.push_back(nullptr); } -int Command::Execute(ArrayRef> Redirects, - std::string *ErrMsg, bool *ExecutionFailed) const { +void Command::PrintFileNames() const { if (PrintInputFilenames) { for (const char *Arg : InputFilenames) llvm::outs() << llvm::sys::path::filename(Arg) << "\n"; llvm::outs().flush(); } +} - SmallVector Argv; +int Command::Execute(ArrayRef> Redirects, + std::string *ErrMsg, bool *ExecutionFailed) const { + PrintFileNames(); + + SmallVector Argv; + if (ResponseFile == nullptr) { + Argv.push_back(Executable); + Argv.append(Arguments.begin(), Arguments.end()); + Argv.push_back(nullptr); + } else { + // If the command is too large, we need to put arguments in a response file. + std::string RespContents; + llvm::raw_string_ostream SS(RespContents); + + // Write file contents and build the Argv vector + writeResponseFile(SS); + buildArgvForResponseFile(Argv); + Argv.push_back(nullptr); + SS.flush(); + + // Save the response file in the appropriate encoding + if (std::error_code EC = writeFileWithEncoding( + ResponseFile, RespContents, Creator.getResponseFileEncoding())) { + if (ErrMsg) + *ErrMsg = EC.message(); + if (ExecutionFailed) + *ExecutionFailed = true; + // Return -1 by convention (see llvm/include/llvm/Support/Program.h) to + // indicate the requested executable cannot be started. + return -1; + } + } Optional> Env; std::vector ArgvVectorStorage; @@ -332,42 +365,51 @@ Env = makeArrayRef(ArgvVectorStorage); } - if (ResponseFile == nullptr) { - Argv.push_back(Executable); - Argv.append(Arguments.begin(), Arguments.end()); - Argv.push_back(nullptr); + auto Args = llvm::toStringRefArray(Argv.data()); + return llvm::sys::ExecuteAndWait(Executable, Args, Env, Redirects, + /*secondsToWait*/ 0, + /*memoryLimit*/ 0, ErrMsg, ExecutionFailed); +} - auto Args = llvm::toStringRefArray(Argv.data()); - return llvm::sys::ExecuteAndWait( - Executable, Args, Env, Redirects, /*secondsToWait*/ 0, - /*memoryLimit*/ 0, ErrMsg, ExecutionFailed); - } +void CC1Command::Print(raw_ostream &OS, const char *Terminator, bool Quote, + CrashReportInfo *CrashInfo) const { + OS << " (in-process)"; + Command::Print(OS, Terminator, Quote, CrashInfo); +} - // We need to put arguments in a response file (command is too large) - // Open stream to store the response file contents - std::string RespContents; - llvm::raw_string_ostream SS(RespContents); +int CC1Command::Execute(ArrayRef> /*Redirects*/, + std::string *ErrMsg, bool *ExecutionFailed) const { + PrintFileNames(); - // Write file contents and build the Argv vector - writeResponseFile(SS); - buildArgvForResponseFile(Argv); + SmallVector Argv; + Argv.push_back(getExecutable()); + Argv.append(getArguments().begin(), getArguments().end()); Argv.push_back(nullptr); - SS.flush(); - - // Save the response file in the appropriate encoding - if (std::error_code EC = writeFileWithEncoding( - ResponseFile, RespContents, Creator.getResponseFileEncoding())) { - if (ErrMsg) - *ErrMsg = EC.message(); - if (ExecutionFailed) - *ExecutionFailed = true; - return -1; + + // This flag simply indicates that the program couldn't start, which isn't + // applicable here. + if (ExecutionFailed) + *ExecutionFailed = false; + + llvm::CrashRecoveryContext CRC; + CRC.DumpStackAndCleanupOnFailure = true; + + const void *PrettyState = llvm::SavePrettyStackState(); + const Driver &D = getCreator().getToolChain().getDriver(); + + int R = 0; + // Enter ExecuteCC1Tool() instead of starting up a new process + if (!CRC.RunSafely([&]() { R = D.CC1Main(Argv); })) { + llvm::RestorePrettyStackState(PrettyState); + return CRC.RetCode; } + return R; +} - auto Args = llvm::toStringRefArray(Argv.data()); - return llvm::sys::ExecuteAndWait(Executable, Args, Env, Redirects, - /*secondsToWait*/ 0, - /*memoryLimit*/ 0, ErrMsg, ExecutionFailed); +void CC1Command::setEnvironment(llvm::ArrayRef NewEnvironment) { + // We don't support set a new environment when calling into ExecuteCC1Tool() + llvm_unreachable( + "The CC1Command doesn't support changing the environment vars!"); } FallbackCommand::FallbackCommand(const Action &Source_, const Tool &Creator_, diff --git a/clang/lib/Driver/ToolChains/Clang.cpp b/clang/lib/Driver/ToolChains/Clang.cpp --- a/clang/lib/Driver/ToolChains/Clang.cpp +++ b/clang/lib/Driver/ToolChains/Clang.cpp @@ -5979,6 +5979,10 @@ // fails, so that the main compilation's fallback to cl.exe runs. C.addCommand(std::make_unique(JA, *this, Exec, CmdArgs, Inputs)); + } else if (D.CC1Main && !D.CCGenDiagnostics) { + // Invoke the CC1 directly in this process + C.addCommand( + std::make_unique(JA, *this, Exec, CmdArgs, Inputs)); } else { C.addCommand(std::make_unique(JA, *this, Exec, CmdArgs, Inputs)); } diff --git a/clang/test/CMakeLists.txt b/clang/test/CMakeLists.txt --- a/clang/test/CMakeLists.txt +++ b/clang/test/CMakeLists.txt @@ -13,6 +13,7 @@ CLANG_BUILD_EXAMPLES CLANG_ENABLE_ARCMT CLANG_ENABLE_STATIC_ANALYZER + CLANG_SPAWN_CC1 ENABLE_BACKTRACES ENABLE_EXPERIMENTAL_NEW_PASS_MANAGER LLVM_ENABLE_ZLIB diff --git a/clang/test/Driver/cc1-spawnprocess.c b/clang/test/Driver/cc1-spawnprocess.c new file mode 100644 --- /dev/null +++ b/clang/test/Driver/cc1-spawnprocess.c @@ -0,0 +1,4 @@ +// RUN: env -u CLANG_SPAWN_CC1 %clang -c %s -o /dev/null +// RUN: env CLANG_SPAWN_CC1=0 %clang -c %s -o /dev/null +// RUN: env CLANG_SPAWN_CC1=1 %clang -c %s -o /dev/null +// RUN: env CLANG_SPAWN_CC1=test not %clang -c %s -o /dev/null diff --git a/clang/test/Driver/clang_f_opts.c b/clang/test/Driver/clang_f_opts.c --- a/clang/test/Driver/clang_f_opts.c +++ b/clang/test/Driver/clang_f_opts.c @@ -493,7 +493,7 @@ // 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 // Make sure we don't match the -NOT lines with the linker invocation. // Delimiters match the start of the cc1 and the start of the linker lines -// DELIMITERS: {{^ *"}} +// DELIMITERS: {{^ (\(in-process\)|")}} // CHECK-WCHAR1: -fwchar-type=int // CHECK-WCHAR1-NOT: -fwchar-type=short // CHECK-WCHAR2: -fwchar-type=short diff --git a/clang/test/Driver/fsanitize-blacklist.c b/clang/test/Driver/fsanitize-blacklist.c --- a/clang/test/Driver/fsanitize-blacklist.c +++ b/clang/test/Driver/fsanitize-blacklist.c @@ -6,7 +6,7 @@ // Make sure we don't match the -NOT lines with the linker invocation. // Delimiters match the start of the cc1 and the start of the linker lines // for fragile tests. -// DELIMITERS: {{^ *"}} +// DELIMITERS: {{^ (\(in-process\)|")}} // RUN: echo "fun:foo" > %t.good // RUN: echo "fun:bar" > %t.second diff --git a/clang/test/Driver/unknown-arg.c b/clang/test/Driver/unknown-arg.c --- a/clang/test/Driver/unknown-arg.c +++ b/clang/test/Driver/unknown-arg.c @@ -54,7 +54,7 @@ // SILENT-NOT: warning: // CC1AS-DID-YOU-MEAN: error: unknown argument '-hell'; did you mean '-help'? // CC1AS-DID-YOU-MEAN: error: unknown argument '--version'; did you mean '-version'? -// UNKNOWN-INTEGRATED: error: unknown integrated tool 'asphalt'. Valid tools include '-cc1' and '-cc1as'. +// UNKNOWN-INTEGRATED: error: unknown integrated tool '-cc1asphalt'. Valid tools include '-cc1' and '-cc1as'. // RUN: %clang -S %s -o %t.s -Wunknown-to-clang-option 2>&1 | FileCheck --check-prefix=IGNORED %s diff --git a/clang/test/Driver/warning-options_pedantic.cpp b/clang/test/Driver/warning-options_pedantic.cpp --- a/clang/test/Driver/warning-options_pedantic.cpp +++ b/clang/test/Driver/warning-options_pedantic.cpp @@ -1,6 +1,6 @@ // Make sure we don't match the -NOT lines with the linker invocation. // Delimiters match the start of the cc1 and the start of the linker lines -// DELIMITERS: {{^ *"}} +// DELIMITERS: {{^ (\(in-process\)|")}} // RUN: %clang -### -pedantic -no-pedantic %s 2>&1 | FileCheck -check-prefix=NO_PEDANTIC -check-prefix=DELIMITERS %s // RUN: %clang -### -pedantic -Wno-pedantic %s 2>&1 | FileCheck -check-prefix=PEDANTIC -check-prefix=DELIMITERS %s diff --git a/clang/tools/driver/driver.cpp b/clang/tools/driver/driver.cpp --- a/clang/tools/driver/driver.cpp +++ b/clang/tools/driver/driver.cpp @@ -14,6 +14,7 @@ #include "clang/Driver/Driver.h" #include "clang/Basic/DiagnosticOptions.h" #include "clang/Basic/Stack.h" +#include "clang/Config/config.h" #include "clang/Driver/Compilation.h" #include "clang/Driver/DriverDiagnostic.h" #include "clang/Driver/Options.h" @@ -30,6 +31,7 @@ #include "llvm/Option/OptTable.h" #include "llvm/Option/Option.h" #include "llvm/Support/CommandLine.h" +#include "llvm/Support/CrashRecoveryContext.h" #include "llvm/Support/ErrorHandling.h" #include "llvm/Support/FileSystem.h" #include "llvm/Support/Host.h" @@ -239,6 +241,8 @@ *NumberSignPtr = '='; } +static int ExecuteCC1Tool(ArrayRef argv); + static void SetBackdoorDriverOutputsFromEnvVars(Driver &TheDriver) { // Handle CC_PRINT_OPTIONS and CC_PRINT_OPTIONS_FILE. TheDriver.CCPrintOptions = !!::getenv("CC_PRINT_OPTIONS"); @@ -254,6 +258,27 @@ TheDriver.CCLogDiagnostics = !!::getenv("CC_LOG_DIAGNOSTICS"); if (TheDriver.CCLogDiagnostics) TheDriver.CCLogDiagnosticsFilename = ::getenv("CC_LOG_DIAGNOSTICS_FILE"); + + // Whether the cc1 tool should be called inside the current process, or if we + // should spawn a new clang process (old behavior). + // Not having an additional process saves some execution time of Windows, + // and makes debugging easier. + bool UseNewCC1Process = CLANG_SPAWN_CC1; + + StringRef SpawnCC1Str = ::getenv("CLANG_SPAWN_CC1"); + if (!SpawnCC1Str.empty()) { + if (SpawnCC1Str != "0" && SpawnCC1Str != "1") { + llvm::errs() << "error: the value of the environment variable " + "CLANG_SPAWN_CC1 must be either 0 or 1.\n"; + ::exit(1); + } + UseNewCC1Process = SpawnCC1Str[0] - '0'; + } + if (!UseNewCC1Process) { + TheDriver.CC1Main = &ExecuteCC1Tool; + // Ensure the CC1Command actually catches cc1 crashes + llvm::CrashRecoveryContext::Enable(); + } } static void FixupDiagPrefixExeName(TextDiagnosticPrinter *DiagClient, @@ -303,13 +328,19 @@ TheDriver.setInstalledDir(InstalledPathParent); } -static int ExecuteCC1Tool(ArrayRef argv, StringRef Tool) { +static int ExecuteCC1Tool(ArrayRef argv) { + // If we call the cc1 tool from the clangDriver library (through + // Driver::CC1Main), we need to cleanup the options usage count. The options + // are currently global, and they might have been used previously by the + // driver. + llvm::cl::ResetAllOptionOccurrences(); + StringRef Tool = argv[1]; void *GetExecutablePathVP = (void *)(intptr_t) GetExecutablePath; - if (Tool == "") + if (Tool == "-cc1") return cc1_main(argv.slice(2), argv[0], GetExecutablePathVP); - if (Tool == "as") + if (Tool == "-cc1as") return cc1as_main(argv.slice(2), argv[0], GetExecutablePathVP); - if (Tool == "gen-reproducer") + if (Tool == "-cc1gen-reproducer") return cc1gen_reproducer_main(argv.slice(2), argv[0], GetExecutablePathVP); // Reject unknown tools. @@ -379,7 +410,7 @@ auto newEnd = std::remove(argv.begin(), argv.end(), nullptr); argv.resize(newEnd - argv.begin()); } - return ExecuteCC1Tool(argv, argv[1] + 4); + return ExecuteCC1Tool(argv); } bool CanonicalPrefixes = true; @@ -503,7 +534,7 @@ #ifdef _WIN32 // Exit status should not be negative on Win32, unless abnormal termination. - // Once abnormal termiation was caught, negative status should not be + // Once abnormal termination was caught, negative status should not be // propagated. if (Res < 0) Res = 1;