https://github.com/cpsughrue created https://github.com/llvm/llvm-project/pull/68498
Work in progress implementation of the module build daemon proposed in https://discourse.llvm.org/t/rfc-modules-build-daemon-build-system-agnostic-support-for-explicitly-built-modules/71524. >From 9fe97509277fbce0333c454bb4e2619fed04b189 Mon Sep 17 00:00:00 2001 From: cpsughrue <cpsugh...@gmail.com> Date: Sun, 9 Jul 2023 23:19:58 -0400 Subject: [PATCH 1/2] [WIP][clang][MBD] module build daemon --- clang/include/clang/Driver/Options.td | 12 + .../include/clang/Frontend/FrontendOptions.h | 7 + .../clang/Tooling/ModuleBuildDaemon/Client.h | 56 ++ .../ModuleBuildDaemon/SocketMsgSupport.h | 166 ++++++ .../Tooling/ModuleBuildDaemon/SocketSupport.h | 31 + .../clang/Tooling/ModuleBuildDaemon/Utils.h | 28 + clang/lib/Driver/ToolChains/Clang.cpp | 14 +- clang/lib/Tooling/CMakeLists.txt | 1 + .../Tooling/ModuleBuildDaemon/CMakeLists.txt | 9 + .../lib/Tooling/ModuleBuildDaemon/Client.cpp | 224 +++++++ .../ModuleBuildDaemon/SocketSupport.cpp | 128 ++++ clang/lib/Tooling/ModuleBuildDaemon/Utils.cpp | 32 + clang/test/Driver/unknown-arg.c | 2 +- clang/test/ModuleBuildDaemon/launch.c | 10 + clang/test/ModuleBuildDaemon/parallel-scan.c | 31 + clang/test/ModuleBuildDaemon/scan.c | 20 + clang/tools/driver/CMakeLists.txt | 3 + clang/tools/driver/cc1_main.cpp | 74 ++- clang/tools/driver/cc1modbuildd_main.cpp | 553 ++++++++++++++++++ clang/tools/driver/driver.cpp | 17 +- 20 files changed, 1408 insertions(+), 10 deletions(-) create mode 100644 clang/include/clang/Tooling/ModuleBuildDaemon/Client.h create mode 100644 clang/include/clang/Tooling/ModuleBuildDaemon/SocketMsgSupport.h create mode 100644 clang/include/clang/Tooling/ModuleBuildDaemon/SocketSupport.h create mode 100644 clang/include/clang/Tooling/ModuleBuildDaemon/Utils.h create mode 100644 clang/lib/Tooling/ModuleBuildDaemon/CMakeLists.txt create mode 100644 clang/lib/Tooling/ModuleBuildDaemon/Client.cpp create mode 100644 clang/lib/Tooling/ModuleBuildDaemon/SocketSupport.cpp create mode 100644 clang/lib/Tooling/ModuleBuildDaemon/Utils.cpp create mode 100644 clang/test/ModuleBuildDaemon/launch.c create mode 100644 clang/test/ModuleBuildDaemon/parallel-scan.c create mode 100644 clang/test/ModuleBuildDaemon/scan.c create mode 100644 clang/tools/driver/cc1modbuildd_main.cpp diff --git a/clang/include/clang/Driver/Options.td b/clang/include/clang/Driver/Options.td index 5415b18d3f406df..33c24e182fccca2 100644 --- a/clang/include/clang/Driver/Options.td +++ b/clang/include/clang/Driver/Options.td @@ -2883,6 +2883,18 @@ defm declspec : BoolOption<"f", "declspec", NegFlag<SetFalse, [], [ClangOption], "Disallow">, BothFlags<[], [ClangOption, CC1Option], " __declspec as a keyword">>, Group<f_clang_Group>; + +def fmodule_build_daemon : Flag<["-"], "fmodule-build-daemon">, Group<f_Group>, + Flags<[NoXarchOption]>, + Visibility<[ClangOption, CC1Option]>, + HelpText<"Enables module build daemon functionality">, + MarshallingInfoFlag<FrontendOpts<"ModuleBuildDaemon">>; +def fmodule_build_daemon_EQ : Joined<["-"], "fmodule-build-daemon=">, Group<f_Group>, + Flags<[NoXarchOption]>, + Visibility<[ClangOption, CC1Option]>, + HelpText<"Enables module build daemon functionality and defines location of output files">, + MarshallingInfoString<FrontendOpts<"ModuleBuildDaemonPath">>; + def fmodules_cache_path : Joined<["-"], "fmodules-cache-path=">, Group<i_Group>, Flags<[NoXarchOption]>, Visibility<[ClangOption, CC1Option]>, MetaVarName<"<directory>">, diff --git a/clang/include/clang/Frontend/FrontendOptions.h b/clang/include/clang/Frontend/FrontendOptions.h index 117e35de6f76c4c..8ce97a57d413c0b 100644 --- a/clang/include/clang/Frontend/FrontendOptions.h +++ b/clang/include/clang/Frontend/FrontendOptions.h @@ -350,6 +350,9 @@ class FrontendOptions { /// Whether to share the FileManager when building modules. unsigned ModulesShareFileManager : 1; + /// Connect to module build daemon + unsigned ModuleBuildDaemon : 1; + CodeCompleteOptions CodeCompleteOpts; /// Specifies the output format of the AST. @@ -435,6 +438,10 @@ class FrontendOptions { /// The output file, if any. std::string OutputFile; + /// If given, the path to the module build daemon's output files and socket + /// address + std::string ModuleBuildDaemonPath; + /// If given, the new suffix for fix-it rewritten files. std::string FixItSuffix; diff --git a/clang/include/clang/Tooling/ModuleBuildDaemon/Client.h b/clang/include/clang/Tooling/ModuleBuildDaemon/Client.h new file mode 100644 index 000000000000000..5d5df74743a6a68 --- /dev/null +++ b/clang/include/clang/Tooling/ModuleBuildDaemon/Client.h @@ -0,0 +1,56 @@ +//===----------------------------- Protocol.h -----------------------------===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// + +#ifndef LLVM_CLANG_TOOLING_MODULEBUILDDAEMON_CLIENT_H +#define LLVM_CLANG_TOOLING_MODULEBUILDDAEMON_CLIENT_H + +#include "clang/Frontend/CompilerInstance.h" +#include "llvm/ADT/ArrayRef.h" +#include "llvm/ADT/StringRef.h" +#include "llvm/Config/llvm-config.h" +#include "llvm/Support/YAMLParser.h" +#include "llvm/Support/YAMLTraits.h" + +#define MAX_BUFFER 4096 +#define SOCKET_FILE_NAME "mbd.sock" +#define STDOUT_FILE_NAME "mbd.out" +#define STDERR_FILE_NAME "mbd.err" + +using namespace clang; +using namespace llvm; + +namespace cc1modbuildd { + +// Returns where to store log files and socket address. Of the format +// /tmp/clang-<BLAKE3HashOfClagnFullVersion>/ +std::string getBasePath(); + +llvm::Error attemptHandshake(int SocketFD); + +llvm::Error spawnModuleBuildDaemon(StringRef BasePath, const char *Argv0); + +Expected<int> getModuleBuildDaemon(const char *Argv0, StringRef BasePath); + +// Sends request to module build daemon +llvm::Error registerTranslationUnit(ArrayRef<const char *> CC1Cmd, + StringRef Argv0, StringRef CWD, + int ServerFD); + +// Processes response from module build daemon +Expected<std::vector<std::string>> getUpdatedCC1(int ServerFD); + +// Work in progress. Eventually function will modify CC1 command line to include +// path to modules already built by the daemon +Expected<std::vector<std::string>> +updateCC1WithModuleBuildDaemon(const CompilerInvocation &Clang, + ArrayRef<const char *> CC1Cmd, const char *Argv0, + StringRef CWD); + +} // namespace cc1modbuildd + +#endif // LLVM_CLANG_TOOLING_MODULEBUILDDAEMON_PROTOCAL_H diff --git a/clang/include/clang/Tooling/ModuleBuildDaemon/SocketMsgSupport.h b/clang/include/clang/Tooling/ModuleBuildDaemon/SocketMsgSupport.h new file mode 100644 index 000000000000000..c02a426054f1170 --- /dev/null +++ b/clang/include/clang/Tooling/ModuleBuildDaemon/SocketMsgSupport.h @@ -0,0 +1,166 @@ +//===------------------------- SocketMsgSupport.h -------------------------===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// + +#ifndef LLVM_CLANG_TOOLING_MODULEBUILDDAEMON_SOCKETMSGSUPPORT_H +#define LLVM_CLANG_TOOLING_MODULEBUILDDAEMON_SOCKETMSGSUPPORT_H + +#include "clang/Tooling/ModuleBuildDaemon/Client.h" +#include "clang/Tooling/ModuleBuildDaemon/SocketSupport.h" + +using namespace clang; +using namespace llvm; + +namespace cc1modbuildd { + +enum class ActionType { REGISTER, HANDSHAKE }; +enum class StatusType { REQUEST, SUCCESS, FAILURE }; + +struct BaseMsg { + ActionType MsgAction; + StatusType MsgStatus; + + BaseMsg() = default; + BaseMsg(ActionType Action, StatusType Status) + : MsgAction(Action), MsgStatus(Status) {} +}; + +struct RegisterMsg : public BaseMsg { + std::optional<std::string> WorkingDirectory; + // The scanner requires the path to the clang executable + std::optional<std::string> ExecutablePath; + // Does not include executable + std::optional<std::vector<std::string>> CC1CommandLine; + + RegisterMsg() = default; + + RegisterMsg(ActionType Action, StatusType Status, + const std::optional<std::string> &CurrentWD, + const std::optional<std::string> &Argv0, + const std::optional<std::vector<std::string>> &Argv) + : BaseMsg(Action, Status), WorkingDirectory(CurrentWD), + ExecutablePath(Argv0), CC1CommandLine(Argv) {} + + RegisterMsg(ActionType Action, StatusType Status) + : BaseMsg(Action, Status), WorkingDirectory(std::nullopt), + ExecutablePath(std::nullopt), CC1CommandLine(std::nullopt) {} +}; + +struct HandshakeMsg : public BaseMsg { + HandshakeMsg() = default; + HandshakeMsg(ActionType Action, StatusType Status) + : BaseMsg(Action, Status) {} +}; + +template <typename T> std::string getBufferFromSocketMsg(T Msg) { + static_assert(std::is_base_of<cc1modbuildd::BaseMsg, T>::value, + "T must inherit from cc1modbuildd::BaseMsg"); + + std::string Buffer; + llvm::raw_string_ostream OS(Buffer); + llvm::yaml::Output YamlOut(OS); + + YamlOut << Msg; + return Buffer; +} + +template <typename T> Expected<T> getSocketMsgFromBuffer(const char *Buffer) { + static_assert(std::is_base_of<cc1modbuildd::BaseMsg, T>::value, + "T must inherit from cc1modbuildd::BaseMsg"); + + T ClientRequest; + llvm::yaml::Input YamlIn(Buffer); + YamlIn >> ClientRequest; + + if (YamlIn.error()) { + std::string Msg = "Syntax or semantic error during YAML parsing"; + return llvm::make_error<StringError>(Msg, inconvertibleErrorCode()); + } + + return ClientRequest; +} + +template <typename T> Expected<T> readSocketMsgFromSocket(int FD) { + static_assert(std::is_base_of<cc1modbuildd::BaseMsg, T>::value, + "T must inherit from cc1modbuildd::BaseMsg"); + + Expected<std::string> MaybeResponseBuffer = readFromSocket(FD); + if (!MaybeResponseBuffer) + return std::move(MaybeResponseBuffer.takeError()); + + // Wait for response from module build daemon + Expected<T> MaybeResponse = + getSocketMsgFromBuffer<T>(std::move(*MaybeResponseBuffer).c_str()); + if (!MaybeResponse) + return std::move(MaybeResponse.takeError()); + return std::move(*MaybeResponse); +} + +template <typename T> llvm::Error writeSocketMsgToSocket(T Msg, int FD) { + static_assert(std::is_base_of<cc1modbuildd::BaseMsg, T>::value, + "T must inherit from cc1modbuildd::BaseMsg"); + + std::string Buffer = getBufferFromSocketMsg(Msg); + if (llvm::Error Err = writeToSocket(Buffer, FD)) + return std::move(Err); + + return llvm::Error::success(); +} + +template <typename T> +Expected<int> connectAndWriteSocketMsgToSocket(T Msg, StringRef SocketPath) { + static_assert(std::is_base_of<cc1modbuildd::BaseMsg, T>::value, + "T must inherit from cc1modbuildd::BaseMsg"); + + Expected<int> MaybeFD = connectToSocket(SocketPath); + if (!MaybeFD) + return std::move(MaybeFD.takeError()); + int FD = std::move(*MaybeFD); + + if (llvm::Error Err = writeSocketMsgToSocket(Msg, FD)) + return std::move(Err); + + return FD; +} + +} // namespace cc1modbuildd + +template <> +struct llvm::yaml::ScalarEnumerationTraits<cc1modbuildd::StatusType> { + static void enumeration(IO &Io, cc1modbuildd::StatusType &Value) { + Io.enumCase(Value, "REQUEST", cc1modbuildd::StatusType::REQUEST); + Io.enumCase(Value, "SUCCESS", cc1modbuildd::StatusType::SUCCESS); + Io.enumCase(Value, "FAILURE", cc1modbuildd::StatusType::FAILURE); + } +}; + +template <> +struct llvm::yaml::ScalarEnumerationTraits<cc1modbuildd::ActionType> { + static void enumeration(IO &Io, cc1modbuildd::ActionType &Value) { + Io.enumCase(Value, "REGISTER", cc1modbuildd::ActionType::REGISTER); + Io.enumCase(Value, "HANDSHAKE", cc1modbuildd::ActionType::HANDSHAKE); + } +}; + +template <> struct llvm::yaml::MappingTraits<cc1modbuildd::RegisterMsg> { + static void mapping(IO &Io, cc1modbuildd::RegisterMsg &Info) { + Io.mapRequired("Action", Info.MsgAction); + Io.mapRequired("Status", Info.MsgStatus); + Io.mapOptional("WorkingDirectory", Info.WorkingDirectory); + Io.mapOptional("ExecutablePath", Info.ExecutablePath); + Io.mapOptional("CC1CommandLine", Info.CC1CommandLine); + } +}; + +template <> struct llvm::yaml::MappingTraits<cc1modbuildd::HandshakeMsg> { + static void mapping(IO &Io, cc1modbuildd::HandshakeMsg &Info) { + Io.mapRequired("Action", Info.MsgAction); + Io.mapRequired("Status", Info.MsgStatus); + } +}; + +#endif // LLVM_CLANG_TOOLING_MODULEBUILDDAEMON_SOCKETMSGSUPPORT_H diff --git a/clang/include/clang/Tooling/ModuleBuildDaemon/SocketSupport.h b/clang/include/clang/Tooling/ModuleBuildDaemon/SocketSupport.h new file mode 100644 index 000000000000000..bc21084faab3966 --- /dev/null +++ b/clang/include/clang/Tooling/ModuleBuildDaemon/SocketSupport.h @@ -0,0 +1,31 @@ +//===-------------------------- SocketSupport.h ---------------------------===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// + +#ifndef LLVM_CLANG_TOOLING_MODULEBUILDDAEMON_SOCKETSUPPORT_H +#define LLVM_CLANG_TOOLING_MODULEBUILDDAEMON_SOCKETSUPPORT_H + +#include "clang/Frontend/CompilerInstance.h" +#include "llvm/ADT/ArrayRef.h" +#include "llvm/ADT/StringRef.h" +#include "llvm/Support/YAMLParser.h" +#include "llvm/Support/YAMLTraits.h" + +using namespace clang; +using namespace llvm; + +namespace cc1modbuildd { + +Expected<int> createSocket(); +Expected<int> connectToSocket(StringRef SocketPath); +Expected<int> connectAndWriteToSocket(std::string Buffer, StringRef SocketPath); +Expected<std::string> readFromSocket(int FD); +llvm::Error writeToSocket(std::string Buffer, int WriteFD); + +} // namespace cc1modbuildd + +#endif // LLVM_CLANG_TOOLING_MODULEBUILDDAEMON_SOCKETSUPPORT_H diff --git a/clang/include/clang/Tooling/ModuleBuildDaemon/Utils.h b/clang/include/clang/Tooling/ModuleBuildDaemon/Utils.h new file mode 100644 index 000000000000000..79a2ffc3c1804d8 --- /dev/null +++ b/clang/include/clang/Tooling/ModuleBuildDaemon/Utils.h @@ -0,0 +1,28 @@ +//===------------------------------ Utils.h -------------------------------===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// +// +// Functions required by both the module build daemon (server) and clang +// invocation (client) +// +//===----------------------------------------------------------------------===// + +#ifndef LLVM_CLANG_TOOLING_MODULEBUILDDAEMON_UTILS_H +#define LLVM_CLANG_TOOLING_MODULEBUILDDAEMON_UTILS_H + +#include "llvm/Support/Error.h" +#include <string> + +namespace cc1modbuildd { + +void writeError(llvm::Error Err, std::string Msg); +std::string getFullErrorMsg(llvm::Error Err, std::string Msg); +llvm::Error makeStringError(llvm::Error Err, std::string Msg); + +} // namespace cc1modbuildd + +#endif // LLVM_CLANG_TOOLING_MODULEBUILDDAEMON_UTILS_H \ No newline at end of file diff --git a/clang/lib/Driver/ToolChains/Clang.cpp b/clang/lib/Driver/ToolChains/Clang.cpp index bfd6c5c2864abf7..62fbcae0f87cf45 100644 --- a/clang/lib/Driver/ToolChains/Clang.cpp +++ b/clang/lib/Driver/ToolChains/Clang.cpp @@ -5,7 +5,6 @@ // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception // //===----------------------------------------------------------------------===// - #include "Clang.h" #include "AMDGPU.h" #include "Arch/AArch64.h" @@ -3740,6 +3739,19 @@ static bool RenderModulesOptions(Compilation &C, const Driver &D, Std->containsValue("c++latest") || Std->containsValue("gnu++latest")); bool HaveModules = HaveStdCXXModules; + // -fmodule-build-daemon enables module build daemon functionality + if (Args.hasArg(options::OPT_fmodule_build_daemon)) + Args.AddLastArg(CmdArgs, options::OPT_fmodule_build_daemon); + + // by default module build daemon socket address and output files are saved + // under /tmp/ but that can be overridden by providing the + // -fmodule-build-daemon=<path> flag + if (Arg *A = Args.getLastArg(options::OPT_fmodule_build_daemon_EQ)) { + CmdArgs.push_back( + Args.MakeArgString(Twine("-fmodule-build-daemon=") + A->getValue())); + CmdArgs.push_back("-fmodule-build-daemon"); + } + // -fmodules enables the use of precompiled modules (off by default). // Users can pass -fno-cxx-modules to turn off modules support for // C++/Objective-C++ programs. diff --git a/clang/lib/Tooling/CMakeLists.txt b/clang/lib/Tooling/CMakeLists.txt index aff39e4de13c0b2..85752e577332650 100644 --- a/clang/lib/Tooling/CMakeLists.txt +++ b/clang/lib/Tooling/CMakeLists.txt @@ -13,6 +13,7 @@ add_subdirectory(DumpTool) add_subdirectory(Syntax) add_subdirectory(DependencyScanning) add_subdirectory(Transformer) +add_subdirectory(ModuleBuildDaemon) # Replace the last lib component of the current binary directory with include string(FIND ${CMAKE_CURRENT_BINARY_DIR} "/lib/" PATH_LIB_START REVERSE) diff --git a/clang/lib/Tooling/ModuleBuildDaemon/CMakeLists.txt b/clang/lib/Tooling/ModuleBuildDaemon/CMakeLists.txt new file mode 100644 index 000000000000000..9c1f5dc1aa2c0c0 --- /dev/null +++ b/clang/lib/Tooling/ModuleBuildDaemon/CMakeLists.txt @@ -0,0 +1,9 @@ +set(LLVM_LINK_COMPONENTS + Support + ) + +add_clang_library(clangModuleBuildDaemon + Client.cpp + SocketSupport.cpp + Utils.cpp + ) diff --git a/clang/lib/Tooling/ModuleBuildDaemon/Client.cpp b/clang/lib/Tooling/ModuleBuildDaemon/Client.cpp new file mode 100644 index 000000000000000..08fe14476c901b0 --- /dev/null +++ b/clang/lib/Tooling/ModuleBuildDaemon/Client.cpp @@ -0,0 +1,224 @@ +//===----------------------------- Client.cpp -----------------------------===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// + +#include "clang/Tooling/ModuleBuildDaemon/Client.h" +#include "clang/Basic/Version.h" +#include "clang/Frontend/CompilerInstance.h" +#include "clang/Tooling/ModuleBuildDaemon/SocketMsgSupport.h" +#include "clang/Tooling/ModuleBuildDaemon/SocketSupport.h" +#include "clang/Tooling/ModuleBuildDaemon/Utils.h" +#include "llvm/ADT/ArrayRef.h" +#include "llvm/ADT/ScopeExit.h" +#include "llvm/ADT/SmallString.h" +#include "llvm/ADT/StringRef.h" +#include "llvm/Config/llvm-config.h" +#include "llvm/Support/BLAKE3.h" + +// TODO: Make portable +#if LLVM_ON_UNIX + +#include <cerrno> +#include <filesystem> +#include <fstream> +#include <signal.h> +#include <spawn.h> +#include <string> +#include <sys/socket.h> +#include <sys/stat.h> +#include <sys/un.h> +#include <unistd.h> + +using namespace clang; +using namespace llvm; +using namespace cc1modbuildd; + +std::string cc1modbuildd::getBasePath() { + llvm::BLAKE3 Hash; + Hash.update(getClangFullVersion()); + auto HashResult = Hash.final<sizeof(uint64_t)>(); + uint64_t HashValue = + llvm::support::endian::read<uint64_t, llvm::support::native>( + HashResult.data()); + std::string Key = toString(llvm::APInt(64, HashValue), 36, /*Signed*/ false); + + // set paths + SmallString<128> BasePath; + llvm::sys::path::system_temp_directory(/*erasedOnReboot*/ true, BasePath); + llvm::sys::path::append(BasePath, "clang-" + Key); + return BasePath.c_str(); +} + +llvm::Error cc1modbuildd::attemptHandshake(int SocketFD) { + + cc1modbuildd::HandshakeMsg Request{ActionType::HANDSHAKE, + StatusType::REQUEST}; + std::string Buffer = cc1modbuildd::getBufferFromSocketMsg(Request); + + if (llvm::Error Err = writeToSocket(Buffer, SocketFD)) + return std::move(Err); + + Expected<RegisterMsg> MaybeServerResponse = + readSocketMsgFromSocket<RegisterMsg>(SocketFD); + if (!MaybeServerResponse) + return std::move(MaybeServerResponse.takeError()); + RegisterMsg ServerResponse = std::move(*MaybeServerResponse); + + assert(ServerResponse.MsgAction == ActionType::HANDSHAKE && + "At this point response ActionType should only ever be HANDSHAKE"); + + if (ServerResponse.MsgStatus == StatusType::SUCCESS) + return llvm::Error::success(); + + return llvm::make_error<StringError>("Handshake failed", + inconvertibleErrorCode()); +} + +llvm::Error cc1modbuildd::spawnModuleBuildDaemon(StringRef BasePath, + const char *Argv0) { + std::string BasePathStr = BasePath.str(); + const char *Args[] = {Argv0, "-cc1modbuildd", BasePathStr.c_str(), nullptr}; + pid_t pid; + int EC = posix_spawn(&pid, Args[0], + /*file_actions*/ nullptr, + /*spawnattr*/ nullptr, const_cast<char **>(Args), + /*envp*/ nullptr); + if (EC) + return createStringError(std::error_code(EC, std::generic_category()), + "failed to spawn module build daemon process"); + + return llvm::Error::success(); +} + +Expected<int> cc1modbuildd::getModuleBuildDaemon(const char *Argv0, + StringRef BasePath) { + + SmallString<128> SocketPath = BasePath; + llvm::sys::path::append(SocketPath, SOCKET_FILE_NAME); + + if (llvm::sys::fs::exists(SocketPath)) { + Expected<int> MaybeFD = connectToSocket(SocketPath); + if (MaybeFD) + return std::move(*MaybeFD); + consumeError(MaybeFD.takeError()); + } + + if (llvm::Error Err = cc1modbuildd::spawnModuleBuildDaemon(BasePath, Argv0)) + return std::move(Err); + + const unsigned int MICROSEC_IN_SEC = 1000000; + constexpr unsigned int MAX_TIME = 30 * MICROSEC_IN_SEC; + const unsigned short INTERVAL = 100; + + unsigned int CumulativeTime = 0; + unsigned int WaitTime = 0; + + while (CumulativeTime <= MAX_TIME) { + // Wait a bit then check to see if the module build daemon has initialized + usleep(WaitTime); + + if (llvm::sys::fs::exists(SocketPath)) { + Expected<int> MaybeFD = connectToSocket(SocketPath); + if (MaybeFD) + return std::move(*MaybeFD); + consumeError(MaybeFD.takeError()); + } + + CumulativeTime += INTERVAL; + } + + // After waiting 30 seconds give up + return llvm::make_error<StringError>( + "Module build daemon did not exist after spawn attempt", + inconvertibleErrorCode()); +} + +llvm::Error +cc1modbuildd::registerTranslationUnit(ArrayRef<const char *> CC1Command, + StringRef Argv0, StringRef CWD, + int ServerFD) { + + std::vector<std::string> StrCC1Command; + for (const char *Arg : CC1Command) + StrCC1Command.emplace_back(Arg); + + cc1modbuildd::RegisterMsg Request{ActionType::REGISTER, StatusType::REQUEST, + CWD.str(), Argv0.str(), StrCC1Command}; + + llvm::Error WriteErr = writeSocketMsgToSocket(Request, ServerFD); + if (WriteErr) + return std::move(WriteErr); + + return llvm::Error::success(); +} + +Expected<std::vector<std::string>> cc1modbuildd::getUpdatedCC1(int ServerFD) { + + // Blocks cc1 invocation until module build daemon is done processing + // translation unit. Currently receives a SUCCESS message and returns + // llvm::Error::success() but will eventually recive updated cc1 command line + Expected<RegisterMsg> MaybeServerResponse = + readSocketMsgFromSocket<RegisterMsg>(ServerFD); + if (!MaybeServerResponse) + return std::move(MaybeServerResponse.takeError()); + RegisterMsg ServerResponse = std::move(*MaybeServerResponse); + + // Confirm response is REGISTER and MsgStatus is SUCCESS + assert(ServerResponse.MsgAction == ActionType::REGISTER && + "At this point response ActionType should only ever be REGISTER"); + + if (ServerResponse.MsgStatus == StatusType::SUCCESS) + return ServerResponse.CC1CommandLine.value(); + + return llvm::make_error<StringError>( + "Daemon failed to processes registered translation unit", + inconvertibleErrorCode()); +} + +Expected<std::vector<std::string>> +cc1modbuildd::updateCC1WithModuleBuildDaemon(const CompilerInvocation &Clang, + ArrayRef<const char *> CC1Cmd, + const char *Argv0, StringRef CWD) { + + // The module build daemon stores all output files and its socket address + // under BasePath. Either set BasePath to a user provided option or create an + // appropriate BasePath based on the hash of the clang version + std::string BasePath; + if (!Clang.getFrontendOpts().ModuleBuildDaemonPath.empty()) + BasePath = Clang.getFrontendOpts().ModuleBuildDaemonPath; + else + BasePath = cc1modbuildd::getBasePath(); + + // If module build daemon does not exist spawn module build daemon + Expected<int> MaybeDaemonFD = + cc1modbuildd::getModuleBuildDaemon(Argv0, BasePath); + if (!MaybeDaemonFD) + return makeStringError(MaybeDaemonFD.takeError(), + "Connect to daemon failed: "); + int DaemonFD = std::move(*MaybeDaemonFD); + + if (llvm::Error HandshakeErr = attemptHandshake(DaemonFD)) + return makeStringError(std::move(HandshakeErr), + "Failed to hadshake with daemon: "); + + // Send translation unit information to module build daemon for processing + if (llvm::Error RegisterErr = + registerTranslationUnit(CC1Cmd, Argv0, CWD, DaemonFD)) + return makeStringError(std::move(RegisterErr), + "Register translation unti failed: "); + + // Wait for response from module build daemon. Response will hopefully be an + // updated cc1 command line with additional -fmodule-file=<file> flags and + // implicit module flags removed + Expected<std::vector<std::string>> MaybeUpdatedCC1 = getUpdatedCC1(DaemonFD); + if (!MaybeUpdatedCC1) + return makeStringError(MaybeUpdatedCC1.takeError(), + "Failed to get updated CC1: "); + return std::move(*MaybeUpdatedCC1); +} + +#endif // LLVM_ON_UNIX diff --git a/clang/lib/Tooling/ModuleBuildDaemon/SocketSupport.cpp b/clang/lib/Tooling/ModuleBuildDaemon/SocketSupport.cpp new file mode 100644 index 000000000000000..58526e4422f457b --- /dev/null +++ b/clang/lib/Tooling/ModuleBuildDaemon/SocketSupport.cpp @@ -0,0 +1,128 @@ +//===------------------------- SocketSupport.cpp --------------------------===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// + +#include "clang/Tooling/ModuleBuildDaemon/SocketSupport.h" +#include "clang/Basic/Version.h" +#include "clang/Frontend/CompilerInstance.h" +#include "clang/Tooling/ModuleBuildDaemon/Client.h" +#include "llvm/ADT/ArrayRef.h" +#include "llvm/ADT/ScopeExit.h" +#include "llvm/ADT/SmallString.h" +#include "llvm/ADT/StringRef.h" +#include "llvm/Config/llvm-config.h" +#include "llvm/Support/BLAKE3.h" + +// TODO: Make portable +#if LLVM_ON_UNIX + +#include <cerrno> +#include <filesystem> +#include <fstream> +#include <signal.h> +#include <spawn.h> +#include <string> +#include <sys/socket.h> +#include <sys/stat.h> +#include <sys/un.h> +#include <unistd.h> + +Expected<int> cc1modbuildd::createSocket() { + int FD; + if ((FD = socket(AF_UNIX, SOCK_STREAM, 0)) == -1) { + std::string Msg = "socket create error: " + std::string(strerror(errno)); + return createStringError(inconvertibleErrorCode(), Msg); + } + return FD; +} + +Expected<int> cc1modbuildd::connectToSocket(StringRef SocketPath) { + + Expected<int> MaybeFD = cc1modbuildd::createSocket(); + if (!MaybeFD) + return std::move(MaybeFD.takeError()); + + int FD = std::move(*MaybeFD); + + struct sockaddr_un Addr; + memset(&Addr, 0, sizeof(Addr)); + Addr.sun_family = AF_UNIX; + strncpy(Addr.sun_path, SocketPath.str().c_str(), sizeof(Addr.sun_path) - 1); + + if (connect(FD, (struct sockaddr *)&Addr, sizeof(Addr)) == -1) { + close(FD); + std::string msg = "socket connect error: " + std::string(strerror(errno)); + return createStringError(inconvertibleErrorCode(), msg); + } + return FD; +} + +Expected<int> cc1modbuildd::connectAndWriteToSocket(std::string Buffer, + StringRef SocketPath) { + + Expected<int> MaybeConnectedFD = connectToSocket(SocketPath); + if (!MaybeConnectedFD) + return std::move(MaybeConnectedFD.takeError()); + + int ConnectedFD = std::move(*MaybeConnectedFD); + llvm::Error Err = writeToSocket(Buffer, ConnectedFD); + if (Err) + return std::move(Err); + + return ConnectedFD; +} + +Expected<std::string> cc1modbuildd::readFromSocket(int FD) { + + const size_t BUFFER_SIZE = 4096; + std::vector<char> Buffer(BUFFER_SIZE); + size_t TotalBytesRead = 0; + + ssize_t n; + while ((n = read(FD, Buffer.data() + TotalBytesRead, + Buffer.size() - TotalBytesRead)) > 0) { + + TotalBytesRead += n; + // Read until ...\n encountered (last line of YAML document) + if (std::string(&Buffer[TotalBytesRead - 4], 4) == "...\n") + break; + if (Buffer.size() - TotalBytesRead < BUFFER_SIZE) + Buffer.resize(Buffer.size() + BUFFER_SIZE); + } + + if (n < 0) { + std::string Msg = "socket read error: " + std::string(strerror(errno)); + return llvm::make_error<StringError>(Msg, inconvertibleErrorCode()); + } + if (n == 0) + return llvm::make_error<StringError>("EOF", inconvertibleErrorCode()); + return std::string(Buffer.begin(), Buffer.end()); +} + +llvm::Error cc1modbuildd::writeToSocket(std::string Buffer, int WriteFD) { + + ssize_t BytesToWrite = static_cast<ssize_t>(Buffer.size()); + const char *Bytes = Buffer.c_str(); + + while (BytesToWrite) { + ssize_t BytesWritten = write(WriteFD, Bytes, BytesToWrite); + if (BytesWritten == -1) { + std::string Msg = "socket write error: " + std::string(strerror(errno)); + return llvm::make_error<StringError>(Msg, inconvertibleErrorCode()); + } + + if (!BytesWritten || BytesWritten > BytesToWrite) + return llvm::errorCodeToError( + std::error_code(EIO, std::generic_category())); + + BytesToWrite -= BytesWritten; + Bytes += BytesWritten; + } + return llvm::Error::success(); +} + +#endif // LLVM_ON_UNIX diff --git a/clang/lib/Tooling/ModuleBuildDaemon/Utils.cpp b/clang/lib/Tooling/ModuleBuildDaemon/Utils.cpp new file mode 100644 index 000000000000000..c03e8e3762114f6 --- /dev/null +++ b/clang/lib/Tooling/ModuleBuildDaemon/Utils.cpp @@ -0,0 +1,32 @@ +//===------------------------------ Utils.cpp -----------------------------===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// + +#include <clang/Tooling/ModuleBuildDaemon/Utils.h> +#include <llvm/Support/Error.h> +#include <string> + +using namespace llvm; + +void cc1modbuildd::writeError(llvm::Error Err, std::string Msg) { + handleAllErrors(std::move(Err), [&](ErrorInfoBase &EIB) { + errs() << Msg << EIB.message() << '\n'; + }); +} + +std::string cc1modbuildd::getFullErrorMsg(llvm::Error Err, std::string Msg) { + std::string ErrMessage; + handleAllErrors(std::move(Err), [&](ErrorInfoBase &EIB) { + ErrMessage = Msg + EIB.message(); + }); + return ErrMessage; +} + +llvm::Error cc1modbuildd::makeStringError(llvm::Error Err, std::string Msg) { + std::string ErrMsg = getFullErrorMsg(std::move(Err), Msg); + return llvm::make_error<StringError>(ErrMsg, inconvertibleErrorCode()); +} \ No newline at end of file diff --git a/clang/test/Driver/unknown-arg.c b/clang/test/Driver/unknown-arg.c index 52ea0f5ff3220fc..3a22b824adc5c74 100644 --- a/clang/test/Driver/unknown-arg.c +++ b/clang/test/Driver/unknown-arg.c @@ -59,7 +59,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 '-cc1asphalt'. Valid tools include '-cc1' and '-cc1as'. +// UNKNOWN-INTEGRATED: error: unknown integrated tool '-cc1asphalt'. Valid tools include '-cc1', '-cc1as', '-cc1gen-reproducer', and '-cc1modbuildd'. // RUN: %clang -S %s -o %t.s -Wunknown-to-clang-option 2>&1 | FileCheck --check-prefix=IGNORED %s diff --git a/clang/test/ModuleBuildDaemon/launch.c b/clang/test/ModuleBuildDaemon/launch.c new file mode 100644 index 000000000000000..575aba0ac3ce603 --- /dev/null +++ b/clang/test/ModuleBuildDaemon/launch.c @@ -0,0 +1,10 @@ +// REQUIRES: !system-windows + +// RUN: if pgrep -f "cc1modbuildd mbd-launch"; then pkill -f "cc1modbuildd mbd-launch"; fi + +// RUN: %clang -cc1modbuildd mbd-launch -v +// RUN: cat mbd-launch/mbd.out | FileCheck %s -DPREFIX=%t + +// CHECK: mbd created and binded to socket address at: mbd-launch/mbd.sock + +// RUN: if pgrep -f "cc1modbuildd mbd-launch"; then pkill -f "cc1modbuildd mbd-launch"; fi \ No newline at end of file diff --git a/clang/test/ModuleBuildDaemon/parallel-scan.c b/clang/test/ModuleBuildDaemon/parallel-scan.c new file mode 100644 index 000000000000000..0dddaa0f161457e --- /dev/null +++ b/clang/test/ModuleBuildDaemon/parallel-scan.c @@ -0,0 +1,31 @@ +// Confirm module build daemon can handle two translation units simultaneously + +// REQUIRES: !system-windows + +// RUN: if pgrep -f "cc1modbuildd parallel-scan"; then pkill -f "cc1modbuildd parallel-scan"; fi +// : rm -rf parallel-scan +// RUN: split-file %s %t + +//--- main.c +#include "app.h" +int main() {return 0;} + +//--- app.c +#include "util.h" + +//--- app.h + +//--- util.h + +// RUN: %clang -fmodule-build-daemon=parallel-scan %t/main.c %t/app.c +// RUN: pwd && ls +// RUN: cat parallel-scan/mbd.out +// RUN: cat parallel-scan/mbd.out | FileCheck %s -DPREFIX=%t + +// CHECK: main.c +// CHECK: app.h +// CHECK: app.c +// CHECK: util.h + +// RUN: if pgrep -f "cc1modbuildd parallel-scan"; then pkill -f "cc1modbuildd parallel-scan"; fi +// : rm -rf parallel-scan diff --git a/clang/test/ModuleBuildDaemon/scan.c b/clang/test/ModuleBuildDaemon/scan.c new file mode 100644 index 000000000000000..f70210362ab6443 --- /dev/null +++ b/clang/test/ModuleBuildDaemon/scan.c @@ -0,0 +1,20 @@ +// REQUIRES: !system-windows + +// RUN: if pgrep -f "cc1modbuildd scan"; then pkill -f "cc1modbuildd scan"; fi +// RUN: rm -rf scan +// RUN: split-file %s %t + +//--- main.c +#include "header.h" +int main() {return 0;} + +//--- header.h + +// RUN: %clang -fmodule-build-daemon=scan %t/main.c +// RUN: cat scan/mbd.out | FileCheck %s -DPREFIX=%t + +// CHECK: main.c +// CHECK: header.h + +// RUN: if pgrep -f "cc1modbuildd scan"; then pkill -f "cc1modbuildd scan"; fi +// RUN: rm -rf scan diff --git a/clang/tools/driver/CMakeLists.txt b/clang/tools/driver/CMakeLists.txt index 2182486f93a5553..9f4c7a01f848938 100644 --- a/clang/tools/driver/CMakeLists.txt +++ b/clang/tools/driver/CMakeLists.txt @@ -28,6 +28,7 @@ add_clang_tool(clang cc1_main.cpp cc1as_main.cpp cc1gen_reproducer_main.cpp + cc1modbuildd_main.cpp DEPENDS intrinsics_gen @@ -39,9 +40,11 @@ clang_target_link_libraries(clang PRIVATE clangBasic clangCodeGen + clangDependencyScanning clangDriver clangFrontend clangFrontendTool + clangModuleBuildDaemon clangSerialization ) diff --git a/clang/tools/driver/cc1_main.cpp b/clang/tools/driver/cc1_main.cpp index e9d2c6aad371dbb..b4b19c7e778226c 100644 --- a/clang/tools/driver/cc1_main.cpp +++ b/clang/tools/driver/cc1_main.cpp @@ -25,6 +25,7 @@ #include "clang/Frontend/TextDiagnosticPrinter.h" #include "clang/Frontend/Utils.h" #include "clang/FrontendTool/Utils.h" +#include "clang/Tooling/ModuleBuildDaemon/Client.h" #include "llvm/ADT/Statistic.h" #include "llvm/Config/llvm-config.h" #include "llvm/LinkAllPasses.h" @@ -63,7 +64,7 @@ using namespace llvm::opt; static void LLVMErrorHandler(void *UserData, const char *Message, bool GenCrashDiag) { - DiagnosticsEngine &Diags = *static_cast<DiagnosticsEngine*>(UserData); + DiagnosticsEngine &Diags = *static_cast<DiagnosticsEngine *>(UserData); Diags.Report(diag::err_fe_error_backend) << Message; @@ -251,8 +252,69 @@ int cc1_main(ArrayRef<const char *> Argv, const char *Argv0, void *MainAddr) { Diags.setSeverity(diag::remark_cc1_round_trip_generated, diag::Severity::Remark, {}); - bool Success = CompilerInvocation::CreateFromArgs(Clang->getInvocation(), - Argv, Diags, Argv0); + std::shared_ptr<CompilerInvocation> Invocation = + std::make_shared<CompilerInvocation>(); + bool Success = + CompilerInvocation::CreateFromArgs(*Invocation, Argv, Diags, Argv0); + + // FIXME: does not actually flush any diagnostics + DiagsBuffer->FlushDiagnostics(Diags); + if (!Success) { + DiagsBuffer->finish(); + return 1; + } + + // The module build daemon may update the cc1 args and needs someplace to + // store a modified command line for the lifetime of the compilation + std::vector<std::string> UpdatedArgv; + std::vector<const char *> CharUpdatedArgv; + +#if LLVM_ON_UNIX + // Handle module build daemon functionality if enabled + if (Invocation->getFrontendOpts().ModuleBuildDaemon) { + + // Scanner needs cc1 invocations working directory + IntrusiveRefCntPtr<vfs::FileSystem> System = + createVFSFromCompilerInvocation(*Invocation, Diags); + ErrorOr<std::string> MaybeCWD = System->getCurrentWorkingDirectory(); + + if (MaybeCWD.getError()) { + errs() << "Could not get working directory: " + << MaybeCWD.getError().message() << "\n"; + return 1; + } + + Expected<std::vector<std::string>> MaybeUpdatedArgv = + cc1modbuildd::updateCC1WithModuleBuildDaemon(*Invocation, Argv, Argv0, + *MaybeCWD); + if (!MaybeUpdatedArgv) { + errs() << toString(std::move(MaybeUpdatedArgv.takeError())) << '\n'; + return 1; + } + + // CompilerInvocation::CreateFromArgs expects an ArrayRef<const char *> + UpdatedArgv = std::move(*MaybeUpdatedArgv); + CharUpdatedArgv.reserve(UpdatedArgv.size()); + for (const auto &Arg : UpdatedArgv) { + CharUpdatedArgv.push_back(Arg.c_str()); + } + } +#endif + + outs() << "translation unit command line" << '\n'; + for (const auto &Arg : Argv) + outs() << Arg << " "; + outs() << "\n"; + + // If Argv was modified by the module build daemon create new Invocation + if (!Argv.equals(ArrayRef<const char *>(CharUpdatedArgv)) && + !CharUpdatedArgv.empty()) { + Argv = ArrayRef<const char *>(CharUpdatedArgv); + Success = CompilerInvocation::CreateFromArgs(Clang->getInvocation(), Argv, + Diags, Argv0); + } else { + Clang->setInvocation(Invocation); + } if (!Clang->getFrontendOpts().TimeTracePath.empty()) { llvm::timeTraceProfilerInitialize( @@ -270,7 +332,7 @@ int cc1_main(ArrayRef<const char *> Argv, const char *Argv0, void *MainAddr) { if (Clang->getHeaderSearchOpts().UseBuiltinIncludes && Clang->getHeaderSearchOpts().ResourceDir.empty()) Clang->getHeaderSearchOpts().ResourceDir = - CompilerInvocation::GetResourcesPath(Argv0, MainAddr); + CompilerInvocation::GetResourcesPath(Argv0, MainAddr); // Create the actual diagnostics engine. Clang->createDiagnostics(); @@ -279,8 +341,8 @@ int cc1_main(ArrayRef<const char *> Argv, const char *Argv0, void *MainAddr) { // 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())); + llvm::install_fatal_error_handler( + LLVMErrorHandler, static_cast<void *>(&Clang->getDiagnostics())); DiagsBuffer->FlushDiagnostics(Clang->getDiagnostics()); if (!Success) { diff --git a/clang/tools/driver/cc1modbuildd_main.cpp b/clang/tools/driver/cc1modbuildd_main.cpp new file mode 100644 index 000000000000000..96b95c1c8509bf1 --- /dev/null +++ b/clang/tools/driver/cc1modbuildd_main.cpp @@ -0,0 +1,553 @@ +//===------- cc1modbuildd_main.cpp - Clang CC1 Module Build Daemon --------===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// + +#include "clang/Basic/DiagnosticCategories.h" +#include "clang/Basic/DiagnosticFrontend.h" +#include "clang/Frontend/TextDiagnosticBuffer.h" +#include "clang/Frontend/TextDiagnosticPrinter.h" +#include "clang/Tooling/DependencyScanning/DependencyScanningService.h" +#include "clang/Tooling/DependencyScanning/DependencyScanningTool.h" +#include "clang/Tooling/DependencyScanning/ModuleDepCollector.h" +#include "clang/Tooling/ModuleBuildDaemon/SocketMsgSupport.h" +#include "clang/Tooling/ModuleBuildDaemon/SocketSupport.h" +#include "clang/Tooling/ModuleBuildDaemon/Utils.h" +#include "llvm/ADT/ArrayRef.h" +#include "llvm/ADT/SmallString.h" +#include "llvm/Config/llvm-config.h" +#include "llvm/Support/Program.h" +#include "llvm/Support/ThreadPool.h" +#include "llvm/Support/Threading.h" +#include "llvm/Support/YAMLParser.h" +#include "llvm/Support/YAMLTraits.h" + +// TODO: Make portable +#if LLVM_ON_UNIX + +#include <errno.h> +#include <fstream> +#include <mutex> +#include <optional> +#include <signal.h> +#include <sstream> +#include <stdbool.h> +#include <string> +#include <sys/socket.h> +#include <sys/stat.h> +#include <sys/un.h> +#include <type_traits> +#include <unistd.h> +#include <unordered_map> + +using namespace llvm; +using namespace clang; +using namespace tooling::dependencies; +using namespace cc1modbuildd; + +// Create unbuffered STDOUT stream so that any logging done by module build +// daemon can be viewed without having to terminate the process +static raw_fd_ostream &unbuff_outs() { + static raw_fd_ostream S(STDOUT_FILENO, false, true); + return S; +} + +namespace { + +enum class BuildStatus { WAITING, BUILDING, BUILT }; + +struct ModuleIDHash { + std::size_t + operator()(const clang::tooling::dependencies::ModuleID &ID) const { + return llvm::hash_value(ID); + } +}; + +struct ModuleBuildInfo { + const ModuleDeps Info; + BuildStatus ModuleBuildStatus; +}; + +// Thread safe hash map that stores dependency and build information +class DependencyBuildData { +public: + void insert(ModuleID Key, ModuleBuildInfo Value) { + std::lock_guard<std::mutex> lock(Mutex); + HashMap.insert({Key, Value}); + } + + std::optional<std::reference_wrapper<ModuleBuildInfo>> get(ModuleID Key) { + std::lock_guard<std::mutex> lock(Mutex); + if (auto search = HashMap.find(Key); search != HashMap.end()) + return std::ref(search->second); + return std::nullopt; + } + + bool updateBuildStatus(ModuleID Key, BuildStatus newStatus) { + std::lock_guard<std::mutex> lock(Mutex); + if (auto search = HashMap.find(Key); search != HashMap.end()) { + search->second.ModuleBuildStatus = newStatus; + return true; + } + return false; + } + + void print() { + unbuff_outs() << "printing hash table keys" << '\n'; + for (const auto &i : HashMap) { + unbuff_outs() << "Module: " << i.first.ModuleName << '\n'; + unbuff_outs() << "Dependencies: "; + for (const auto &Dep : i.second.Info.ClangModuleDeps) + unbuff_outs() << Dep.ModuleName << ", "; + unbuff_outs() << '\n'; + } + } + +private: + std::unordered_map<ModuleID, ModuleBuildInfo, ModuleIDHash> HashMap; + std::mutex Mutex; +}; + +class ModuleBuildDaemonServer { +public: + SmallString<128> BasePath; + SmallString<128> SocketPath; + SmallString<128> PidPath; + + ModuleBuildDaemonServer(SmallString<128> Path, ArrayRef<const char *> Argv) + : BasePath(Path), SocketPath(Path) { + llvm::sys::path::append(SocketPath, SOCKET_FILE_NAME); + } + + ~ModuleBuildDaemonServer() { shutdownDaemon(SIGTERM); } + + int forkDaemon(); + int launchDaemon(); + int listenForClients(); + + static void handleClient(int Client); + static void handleRegister(int Client, RegisterMsg ClientRequest); + + void shutdownDaemon(int signal) { + unlink(SocketPath.c_str()); + shutdown(ListenSocketFD, SHUT_RD); + close(ListenSocketFD); + exit(EXIT_SUCCESS); + } + +private: + // Initializes and returns DiagnosticsEngine + pid_t Pid = -1; + int ListenSocketFD = -1; +}; + +// Required to handle SIGTERM by calling Shutdown +ModuleBuildDaemonServer *DaemonPtr = nullptr; +void handleSignal(int Signal) { + if (DaemonPtr != nullptr) { + DaemonPtr->shutdownDaemon(Signal); + } +} +} // namespace + +static bool verbose = false; +static void verbose_print(const llvm::Twine &message) { + if (verbose) { + unbuff_outs() << message << '\n'; + } +} + +static DependencyBuildData DaemonBuildData; + +static Expected<TranslationUnitDeps> +scanTranslationUnit(cc1modbuildd::RegisterMsg Request) { + + DependencyScanningService Service(ScanningMode::DependencyDirectivesScan, + ScanningOutputFormat::Full, + /*OptimizeArgs*/ false, + /*EagerLoadModules*/ false); + + DependencyScanningTool Tool(Service); + llvm::DenseSet<ModuleID> AlreadySeenModules; + auto LookupOutput = [&](const ModuleID &MID, ModuleOutputKind MOK) { + return "/tmp/" + MID.ContextHash; + }; + + // Add executable path to cc1 command line for dependency scanner + std::vector<std::string> ScannerCommandLine; + ScannerCommandLine.push_back(Request.ExecutablePath.value()); + ScannerCommandLine.insert(ScannerCommandLine.end(), + Request.CC1CommandLine.value().begin(), + Request.CC1CommandLine.value().end()); + + Expected<TranslationUnitDeps> MaybeTUDeps = + Tool.getTranslationUnitDependencies(ScannerCommandLine, + Request.WorkingDirectory.value(), + AlreadySeenModules, LookupOutput); + + if (!MaybeTUDeps) + return std::move(MaybeTUDeps.takeError()); + + return std::move(*MaybeTUDeps); +} + +static void storeScanResults(const TranslationUnitDeps &Results) { + + if (Results.ModuleGraph.empty()) + return; + + // Insert children + for (const ModuleDeps &MD : Results.ModuleGraph) + DaemonBuildData.insert(MD.ID, {MD, BuildStatus::WAITING}); +} + +// Remove -fmodule-build-daemon and add -fno-implicit-modules to command line. +// Return value can either be a std::vector of std::string or StringRef +template <typename T> +static std::vector<T> modifyCC1(const std::vector<std::string> &CommandLine) { + static_assert(std::is_same_v<T, std::string> || std::is_same_v<T, StringRef>); + + std::vector<T> ReturnArgs; + ReturnArgs.reserve(CommandLine.size()); + + for (const auto &Arg : CommandLine) { + if (Arg != "-fmodule-build-daemon") + ReturnArgs.emplace_back(Arg); + } + ReturnArgs.emplace_back("-fno-implicit-modules"); + return ReturnArgs; +} + +// TODO: Return llvm::Error +static void precompileModuleID(const StringRef Executable, const ModuleID ID) { + unbuff_outs() << "module " << ID.ModuleName << " will be built" << '\n'; + + std::optional<std::reference_wrapper<ModuleBuildInfo>> MaybeDeps = + DaemonBuildData.get(ID); + if (!MaybeDeps) + return; + ModuleBuildInfo &Deps = MaybeDeps->get(); + + // TODO: look into making getBuildArguments a const method + ModuleDeps &NonConstDepsInfo = const_cast<ModuleDeps &>(Deps.Info); + const std::vector<std::string> &Args = NonConstDepsInfo.getBuildArguments(); + std::vector<std::string> NonConstArgs = + const_cast<std::vector<std::string> &>(Args); + + unbuff_outs() << "original command line" << '\n'; + for (const auto &Arg : Args) + unbuff_outs() << Arg << " "; + unbuff_outs() << "\n\n"; + + const std::vector<StringRef> ProcessedArgs = + modifyCC1<StringRef>(NonConstArgs); + + std::vector<StringRef> ExecuteCommandLine; + ExecuteCommandLine.push_back(Executable); + ExecuteCommandLine.insert(ExecuteCommandLine.end(), ProcessedArgs.begin(), + ProcessedArgs.end()); + + unbuff_outs() << "new command line" << '\n'; + for (const auto &Arg : NonConstArgs) + unbuff_outs() << Arg << " "; + unbuff_outs() << "\n"; + + // TODO: Handle error code returned from ExecuteAndWait + llvm::sys::ExecuteAndWait(Executable, + ArrayRef<StringRef>(ExecuteCommandLine)); + DaemonBuildData.updateBuildStatus(ID, BuildStatus::BUILT); + + unbuff_outs() << "module " << ID.ModuleName << " finished building" << '\n'; + unbuff_outs() << "\n\n"; + + return; +} + +// TODO: implement concurrent approach +// can only handle one translation unit at a time +static void buildModuleID(const StringRef Executable, const ModuleID ID) { + + std::optional<std::reference_wrapper<ModuleBuildInfo>> MaybeDeps = + DaemonBuildData.get(ID); + if (!MaybeDeps) + return; + ModuleBuildInfo &Deps = MaybeDeps->get(); + + if (Deps.ModuleBuildStatus == BuildStatus::BUILT) + return; + + for (const ModuleID &Dep : Deps.Info.ClangModuleDeps) + buildModuleID(Executable, Dep); + + // Do not build the root ID aka the registered translation unit + if (ID.ModuleName.empty()) + return; + + precompileModuleID(Executable, ID); + return; +} + +// Takes a client request in the form of a cc1modbuildd::SocketMsg and +// returns an updated cc1 command line for the registered cc1 invocation +// after building all modular dependencies +static Expected<std::vector<std::string>> +processRegisterRequest(cc1modbuildd::RegisterMsg Request) { + + Expected<TranslationUnitDeps> MaybeTUDeps = scanTranslationUnit(Request); + if (!MaybeTUDeps) + return std::move(MaybeTUDeps.takeError()); + const TranslationUnitDeps TUDeps = std::move(*MaybeTUDeps); + + // For now write dependencies to log file + for (const auto &Dep : TUDeps.FileDeps) + unbuff_outs() << Dep << '\n'; + + // If TU does not depend on modules then return command line as is + if (TUDeps.ModuleGraph.empty()) + return Request.CC1CommandLine.value(); + + unbuff_outs() << "modules detected" << '\n'; + storeScanResults(TUDeps); + DaemonBuildData.print(); + + // Build all direct and transitive dependencies by iterating over direct + // dependencies + for (const ModuleID &Dep : TUDeps.ClangModuleDeps) + buildModuleID(Request.ExecutablePath.value(), Dep); + + return modifyCC1<std::string>(TUDeps.Commands[0].Arguments); +} + +// Forks and detaches process, creating module build daemon +int ModuleBuildDaemonServer::forkDaemon() { + + pid_t pid = fork(); + + if (pid < 0) { + exit(EXIT_FAILURE); + } + if (pid > 0) { + exit(EXIT_SUCCESS); + } + + Pid = getpid(); + + // close(STDIN_FILENO); + // close(STDOUT_FILENO); + // close(STDERR_FILENO); + + // SmallString<128> STDOUT = BasePath; + // llvm::sys::path::append(STDOUT, STDOUT_FILE_NAME); + // freopen(STDOUT.c_str(), "a", stdout); + + // SmallString<128> STDERR = BasePath; + // llvm::sys::path::append(STDERR, STDERR_FILE_NAME); + // freopen(STDERR.c_str(), "a", stderr); + + if (signal(SIGTERM, handleSignal) == SIG_ERR) { + errs() << "failed to handle SIGTERM" << '\n'; + exit(EXIT_FAILURE); + } + if (signal(SIGHUP, SIG_IGN) == SIG_ERR) { + errs() << "failed to ignore SIGHUP" << '\n'; + exit(EXIT_FAILURE); + } + if (setsid() == -1) { + errs() << "setsid failed" << '\n'; + exit(EXIT_FAILURE); + } + + return EXIT_SUCCESS; +} + +// Creates unix socket for IPC with module build daemon +int ModuleBuildDaemonServer::launchDaemon() { + + // new socket + if ((ListenSocketFD = socket(AF_UNIX, SOCK_STREAM, 0)) == -1) { + std::perror("Socket create error: "); + exit(EXIT_FAILURE); + } + + struct sockaddr_un addr; + memset(&addr, 0, sizeof(struct sockaddr_un)); + addr.sun_family = AF_UNIX; + strncpy(addr.sun_path, SocketPath.c_str(), sizeof(addr.sun_path) - 1); + + // bind to local address + if (bind(ListenSocketFD, (struct sockaddr *)&addr, sizeof(addr)) == -1) { + + // If the socket address is already in use, exit because another module + // build daemon has successfully launched. When translation units are + // compiled in parallel, until the socket file is created, all clang + // invocations will spawn a module build daemon. + if (errno == EADDRINUSE) { + close(ListenSocketFD); + exit(EXIT_SUCCESS); + } + std::perror("Socket bind error: "); + exit(EXIT_FAILURE); + } + verbose_print("mbd created and binded to socket address at: " + SocketPath); + + // set socket to accept incoming connection request + unsigned MaxBacklog = llvm::hardware_concurrency().compute_thread_count(); + if (listen(ListenSocketFD, MaxBacklog) == -1) { + std::perror("Socket listen error: "); + exit(EXIT_FAILURE); + } + + return 0; +} + +void ModuleBuildDaemonServer::handleRegister(int Client, + RegisterMsg ClientRequest) { + + Expected<std::vector<std::string>> MaybeExplicitCC1 = + processRegisterRequest(ClientRequest); + + // if getUpdatedCC1 fails emit error + if (!MaybeExplicitCC1) { + + RegisterMsg Msg(ActionType::REGISTER, StatusType::FAILURE); + llvm::Error RegisterFailureWriteErr = writeSocketMsgToSocket(Msg, Client); + + if (RegisterFailureWriteErr) { + writeError(llvm::joinErrors(std::move(RegisterFailureWriteErr), + std::move(MaybeExplicitCC1.takeError())), + "Failed to process register request and was unable to notify " + "clang infocation: "); + return; + } + writeError(MaybeExplicitCC1.takeError(), + "Failed to process register request: "); + return; + } + + // getUpdateCC1 success + std::vector<std::string> ExplicitCC1 = std::move(*MaybeExplicitCC1); + + unbuff_outs() << "modified command line for translation unit" << '\n'; + for (const auto &Arg : ExplicitCC1) + unbuff_outs() << Arg << " "; + unbuff_outs() << "\n"; + + // Send new CC1 command line to waiting clang invocation + RegisterMsg Msg(ActionType::REGISTER, StatusType::SUCCESS, + ClientRequest.WorkingDirectory, ClientRequest.ExecutablePath, + ExplicitCC1); + + llvm::Error RegisterSuccessWriteErr = writeSocketMsgToSocket(Msg, Client); + + if (RegisterSuccessWriteErr) { + writeError(std::move(RegisterSuccessWriteErr), + "Failed to notify clang invocation that register request was a " + "success: "); + return; + } + return; +} + +// Function submitted to thread pool with each client connection. Not +// responsible for closing client connections +void ModuleBuildDaemonServer::handleClient(int Client) { + + // Read handshake from client + Expected<HandshakeMsg> MaybeHandshake = + readSocketMsgFromSocket<HandshakeMsg>(Client); + + if (!MaybeHandshake) { + writeError(MaybeHandshake.takeError(), + "Failed to read handshake message from socket: "); + return; + } + + // Handle HANDSHAKE + RegisterMsg Msg(ActionType::HANDSHAKE, StatusType::SUCCESS); + llvm::Error WriteErr = writeSocketMsgToSocket(Msg, Client); + + if (WriteErr) { + writeError(std::move(WriteErr), + "Failed to notify client that handshake was received"); + return; + } + + // Read register request from client + Expected<RegisterMsg> MaybeRegister = + readSocketMsgFromSocket<RegisterMsg>(Client); + + if (!MaybeRegister) { + writeError(MaybeRegister.takeError(), + "Failed to read registration message from socket: "); + return; + } + + RegisterMsg Register = std::move(*MaybeRegister); + handleRegister(Client, Register); + return; +} + +int ModuleBuildDaemonServer::listenForClients() { + + llvm::ThreadPool Pool; + int Client; + + while (true) { + + if ((Client = accept(ListenSocketFD, NULL, NULL)) == -1) { + std::perror("Socket accept error: "); + continue; + } + + Pool.async(handleClient, Client); + } + return 0; +} + +// Module build daemon is spawned with the following command line: +// +// clang -cc1modbuildd <path> -v +// +// <path> defines the location of all files created by the module build daemon +// and should follow the format /path/to/dir. For example, `clang +// -cc1modbuildd /tmp/` creates a socket file at `/tmp/mbd.sock`. /tmp is also +// valid. +// +// When module build daemons are spawned by cc1 invocations, <path> follows +// the format /tmp/clang-<BLAKE3HashOfClangFullVersion> +// +// -v is optional and provides debug information +// +int cc1modbuildd_main(ArrayRef<const char *> Argv) { + + if (Argv.size() < 1) { + outs() << "spawning a module build daemon requies a command line format of " + "`clang -cc1modbuildd <path>`. <path> defines where the module " + "build daemon will create files" + << '\n'; + return 1; + } + + // Where to store log files and socket address + // TODO: Add check to confirm BasePath is directory + SmallString<128> BasePath(Argv[0]); + llvm::sys::fs::create_directories(BasePath); + ModuleBuildDaemonServer Daemon(BasePath, Argv); + + // Used to handle signals + DaemonPtr = &Daemon; + + if (find(Argv, StringRef("-v")) != Argv.end()) + verbose = true; + + Daemon.forkDaemon(); + Daemon.launchDaemon(); + Daemon.listenForClients(); + + return 0; +} + +#endif // LLVM_ON_UNIX diff --git a/clang/tools/driver/driver.cpp b/clang/tools/driver/driver.cpp index 531b5b4a61c1804..9697bc6d457776d 100644 --- a/clang/tools/driver/driver.cpp +++ b/clang/tools/driver/driver.cpp @@ -213,6 +213,9 @@ extern int cc1as_main(ArrayRef<const char *> Argv, const char *Argv0, extern int cc1gen_reproducer_main(ArrayRef<const char *> Argv, const char *Argv0, void *MainAddr, const llvm::ToolContext &); +#if LLVM_ON_UNIX +extern int cc1modbuildd_main(ArrayRef<const char *> Argv); +#endif static void insertTargetAndModeArgs(const ParsedClangName &NameParts, SmallVectorImpl<const char *> &ArgVector, @@ -369,9 +372,19 @@ static int ExecuteCC1Tool(SmallVectorImpl<const char *> &ArgV, if (Tool == "-cc1gen-reproducer") return cc1gen_reproducer_main(ArrayRef(ArgV).slice(2), ArgV[0], GetExecutablePathVP, ToolContext); - // Reject unknown tools. + if (Tool == "-cc1modbuildd") { +#if LLVM_ON_UNIX + return cc1modbuildd_main(ArrayRef(ArgV).slice(2)); +#else + llvm::errs() << "-cc1modbuildd not supported by current platform" << '\n'; + return 1; +#endif + } + + // Reject unknown tools llvm::errs() << "error: unknown integrated tool '" << Tool << "'. " - << "Valid tools include '-cc1' and '-cc1as'.\n"; + << "Valid tools include '-cc1', '-cc1as', '-cc1gen-reproducer', " + "and '-cc1modbuildd'.\n"; return 1; } >From 749d0489b403686b225dbaa39af5ba4f2626ff50 Mon Sep 17 00:00:00 2001 From: cpsughrue <cpsugh...@gmail.com> Date: Sat, 7 Oct 2023 16:54:25 -0400 Subject: [PATCH 2/2] temporarily remove tests. if a test fails daemon will never terminate --- clang/test/ModuleBuildDaemon/launch.c | 10 ------- clang/test/ModuleBuildDaemon/parallel-scan.c | 31 -------------------- clang/test/ModuleBuildDaemon/scan.c | 20 ------------- 3 files changed, 61 deletions(-) delete mode 100644 clang/test/ModuleBuildDaemon/launch.c delete mode 100644 clang/test/ModuleBuildDaemon/parallel-scan.c delete mode 100644 clang/test/ModuleBuildDaemon/scan.c diff --git a/clang/test/ModuleBuildDaemon/launch.c b/clang/test/ModuleBuildDaemon/launch.c deleted file mode 100644 index 575aba0ac3ce603..000000000000000 --- a/clang/test/ModuleBuildDaemon/launch.c +++ /dev/null @@ -1,10 +0,0 @@ -// REQUIRES: !system-windows - -// RUN: if pgrep -f "cc1modbuildd mbd-launch"; then pkill -f "cc1modbuildd mbd-launch"; fi - -// RUN: %clang -cc1modbuildd mbd-launch -v -// RUN: cat mbd-launch/mbd.out | FileCheck %s -DPREFIX=%t - -// CHECK: mbd created and binded to socket address at: mbd-launch/mbd.sock - -// RUN: if pgrep -f "cc1modbuildd mbd-launch"; then pkill -f "cc1modbuildd mbd-launch"; fi \ No newline at end of file diff --git a/clang/test/ModuleBuildDaemon/parallel-scan.c b/clang/test/ModuleBuildDaemon/parallel-scan.c deleted file mode 100644 index 0dddaa0f161457e..000000000000000 --- a/clang/test/ModuleBuildDaemon/parallel-scan.c +++ /dev/null @@ -1,31 +0,0 @@ -// Confirm module build daemon can handle two translation units simultaneously - -// REQUIRES: !system-windows - -// RUN: if pgrep -f "cc1modbuildd parallel-scan"; then pkill -f "cc1modbuildd parallel-scan"; fi -// : rm -rf parallel-scan -// RUN: split-file %s %t - -//--- main.c -#include "app.h" -int main() {return 0;} - -//--- app.c -#include "util.h" - -//--- app.h - -//--- util.h - -// RUN: %clang -fmodule-build-daemon=parallel-scan %t/main.c %t/app.c -// RUN: pwd && ls -// RUN: cat parallel-scan/mbd.out -// RUN: cat parallel-scan/mbd.out | FileCheck %s -DPREFIX=%t - -// CHECK: main.c -// CHECK: app.h -// CHECK: app.c -// CHECK: util.h - -// RUN: if pgrep -f "cc1modbuildd parallel-scan"; then pkill -f "cc1modbuildd parallel-scan"; fi -// : rm -rf parallel-scan diff --git a/clang/test/ModuleBuildDaemon/scan.c b/clang/test/ModuleBuildDaemon/scan.c deleted file mode 100644 index f70210362ab6443..000000000000000 --- a/clang/test/ModuleBuildDaemon/scan.c +++ /dev/null @@ -1,20 +0,0 @@ -// REQUIRES: !system-windows - -// RUN: if pgrep -f "cc1modbuildd scan"; then pkill -f "cc1modbuildd scan"; fi -// RUN: rm -rf scan -// RUN: split-file %s %t - -//--- main.c -#include "header.h" -int main() {return 0;} - -//--- header.h - -// RUN: %clang -fmodule-build-daemon=scan %t/main.c -// RUN: cat scan/mbd.out | FileCheck %s -DPREFIX=%t - -// CHECK: main.c -// CHECK: header.h - -// RUN: if pgrep -f "cc1modbuildd scan"; then pkill -f "cc1modbuildd scan"; fi -// RUN: rm -rf scan _______________________________________________ cfe-commits mailing list cfe-commits@lists.llvm.org https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits