Index: clang/include/clang/Basic/DiagnosticFrontendKinds.td =================================================================== --- clang/include/clang/Basic/DiagnosticFrontendKinds.td +++ clang/include/clang/Basic/DiagnosticFrontendKinds.td @@ -261,6 +261,11 @@ "test module file extension '%0' has different version (%1.%2) than expected " "(%3.%4)">; +def remark_mbd_socket_addr : Remark<"mbd created socket address at %0">, + InGroup; +def remark_mbd_generic_msg : Remark<"%0">, + InGroup; + def err_missing_vfs_overlay_file : Error< "virtual filesystem overlay file '%0' not found">, DefaultFatal; def err_invalid_vfs_overlay : Error< Index: clang/include/clang/Basic/DiagnosticGroups.td =================================================================== --- clang/include/clang/Basic/DiagnosticGroups.td +++ clang/include/clang/Basic/DiagnosticGroups.td @@ -514,6 +514,7 @@ def ModuleConflict : DiagGroup<"module-conflict">; def ModuleFileExtension : DiagGroup<"module-file-extension">; def ModuleIncludeDirectiveTranslation : DiagGroup<"module-include-translation">; +def ModuleBuildDaemon : DiagGroup<"module-build-daemon">; def RoundTripCC1Args : DiagGroup<"round-trip-cc1-args">; def NewlineEOF : DiagGroup<"newline-eof">; def Nullability : DiagGroup<"nullability">; Index: clang/include/clang/Driver/Options.td =================================================================== --- clang/include/clang/Driver/Options.td +++ clang/include/clang/Driver/Options.td @@ -2823,6 +2823,13 @@ NegFlag, BothFlags<[], [ClangOption, CC1Option], " __declspec as a keyword">>, Group; + +def fmodule_build_daemon : Flag<["-"], "fmodule-build-daemon">, Group, + Flags<[NoXarchOption]>, + Visibility<[ClangOption, CC1Option]>, + HelpText<"Enables module build daemon functionality">, + MarshallingInfoFlag>; + def fmodules_cache_path : Joined<["-"], "fmodules-cache-path=">, Group, Flags<[NoXarchOption]>, Visibility<[ClangOption, CC1Option]>, MetaVarName<"">, Index: clang/include/clang/Frontend/FrontendOptions.h =================================================================== --- clang/include/clang/Frontend/FrontendOptions.h +++ clang/include/clang/Frontend/FrontendOptions.h @@ -347,6 +347,9 @@ /// 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. Index: clang/include/clang/Tooling/ModuleBuildDaemon/Protocol.h =================================================================== --- /dev/null +++ clang/include/clang/Tooling/ModuleBuildDaemon/Protocol.h @@ -0,0 +1,63 @@ +//===----------------------------- 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_PROTOCAL_H +#define LLVM_CLANG_TOOLING_MODULEBUILDDAEMON_PROTOCAL_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 { + +// Create unbuffered STDOUT stream so that any logging done by module build +// daemon can be viewed without having to terminate the process +raw_fd_ostream &unbuff_outs(); + +// Returns where to store log files and socket address. Of the format +// /tmp/clang-/ +std::string getBasePath(); + +bool daemonExists(StringRef BasePath); + +llvm::Error attemptHandshake(int SocketFD); + +Expected connectToSocketAndHandshake(StringRef SocketPath); + +llvm::Error spawnModuleBuildDaemon(StringRef BasePath, const char *Argv0); + +llvm::Error getModuleBuildDaemon(const char *Argv0, StringRef BasePath); + +// Sends request to module build daemon +Expected registerTranslationUnit(ArrayRef CC1Cmd, + StringRef Argv0, StringRef BasePath, + StringRef CWD); + +// Processes response from module build daemon +Expected> getUpdatedCC1(int ServerFD); + +// Work in progress. Eventually function will modify CC1 command line to include +// path to modules already built by the daemon +Expected> +updateCC1WithModuleBuildDaemon(ArrayRef CC1Cmd, const char *Argv0, + StringRef CWD); + +} // namespace cc1modbuildd + +#endif // LLVM_CLANG_TOOLING_MODULEBUILDDAEMON_PROTOCAL_H Index: clang/include/clang/Tooling/ModuleBuildDaemon/SocketMsgSupport.h =================================================================== --- /dev/null +++ clang/include/clang/Tooling/ModuleBuildDaemon/SocketMsgSupport.h @@ -0,0 +1,77 @@ +//===------------------------- 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/Protocol.h" + +using namespace clang; +using namespace llvm; + +namespace cc1modbuildd { + +enum class ActionType { REGISTER, HANDSHAKE }; +enum class StatusType { REQUEST, SUCCESS, FAILURE }; + +struct SocketMsg { + ActionType MsgAction; + StatusType MsgStatus; + std::optional WorkingDirectory; + // First element needs to be path to compiler + std::optional> Argv0PlusCC1CommandLine; + + SocketMsg() = default; + + SocketMsg(ActionType Action, StatusType Status, + const std::optional &CurrentWD, + const std::optional> &CommandLine) + : MsgAction(Action), MsgStatus(Status), WorkingDirectory(CurrentWD), + Argv0PlusCC1CommandLine(CommandLine) {} + + SocketMsg(ActionType Action, StatusType Status) + : MsgAction(Action), MsgStatus(Status), WorkingDirectory(std::nullopt), + Argv0PlusCC1CommandLine(std::nullopt) {} +}; + +std::string getBufferFromSocketMsg(SocketMsg SocketMsg); +Expected getSocketMsgFromBuffer(char *Buffer); +Expected readSocketMsgFromSocket(int FD); +llvm::Error writeSocketMsgToSocket(SocketMsg Msg, int FD); +Expected connectAndWriteSocketMsgToSocket(SocketMsg Msg, + StringRef SocketPath); + +} // namespace cc1modbuildd + +template <> +struct llvm::yaml::ScalarEnumerationTraits { + 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 { + 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 { + static void mapping(IO &io, cc1modbuildd::SocketMsg &info) { + io.mapRequired("Action", info.MsgAction); + io.mapRequired("Status", info.MsgStatus); + io.mapOptional("WorkingDirectory", info.WorkingDirectory); + io.mapOptional("FullCommandLine", info.Argv0PlusCC1CommandLine); + } +}; + +#endif // LLVM_CLANG_TOOLING_MODULEBUILDDAEMON_SOCKETMSGSUPPORT_H \ No newline at end of file Index: clang/include/clang/Tooling/ModuleBuildDaemon/SocketSupport.h =================================================================== --- /dev/null +++ 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 createSocket(); +Expected connectToSocket(StringRef SocketPath); +Expected connectAndWriteToSocket(std::string Buffer, StringRef SocketPath); +Expected> readFromSocket(int FD); +llvm::Error writeToSocket(std::string Buffer, int WriteFD); + +} // namespace cc1modbuildd + +#endif // LLVM_CLANG_TOOLING_MODULEBUILDDAEMON_SOCKETSUPPORT_H \ No newline at end of file Index: clang/lib/Driver/ToolChains/Clang.cpp =================================================================== --- clang/lib/Driver/ToolChains/Clang.cpp +++ 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" @@ -3735,6 +3734,10 @@ Std->containsValue("c++latest") || Std->containsValue("gnu++latest")); bool HaveModules = HaveStdCXXModules; + // -fmodule-build-daemon enables the module build daemon functionality + if (Args.hasArg(options::OPT_fmodule_build_daemon)) + Args.AddLastArg(CmdArgs, options::OPT_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. Index: clang/lib/Tooling/CMakeLists.txt =================================================================== --- clang/lib/Tooling/CMakeLists.txt +++ clang/lib/Tooling/CMakeLists.txt @@ -13,6 +13,7 @@ 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) Index: clang/lib/Tooling/ModuleBuildDaemon/CMakeLists.txt =================================================================== --- /dev/null +++ clang/lib/Tooling/ModuleBuildDaemon/CMakeLists.txt @@ -0,0 +1,9 @@ +set(LLVM_LINK_COMPONENTS + Support + ) + +add_clang_library(clangModuleBuildDaemon + Protocol.cpp + SocketSupport.cpp + SocketMsgSupport.cpp + ) \ No newline at end of file Index: clang/lib/Tooling/ModuleBuildDaemon/Protocol.cpp =================================================================== --- /dev/null +++ clang/lib/Tooling/ModuleBuildDaemon/Protocol.cpp @@ -0,0 +1,255 @@ +//===---------------------------- Protocol.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/Protocol.h" +#include "clang/Basic/Version.h" +#include "clang/Frontend/CompilerInstance.h" +#include "clang/Tooling/ModuleBuildDaemon/SocketMsgSupport.h" +#include "clang/Tooling/ModuleBuildDaemon/SocketSupport.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 +#include +#include +#include +#include +#include +#include +#include +#include +#include + +using namespace clang; +using namespace llvm; + +raw_fd_ostream &cc1modbuildd::unbuff_outs() { + static raw_fd_ostream S(STDOUT_FILENO, false, true); + return S; +} + +std::string cc1modbuildd::getBasePath() { + llvm::BLAKE3 Hash; + Hash.update(getClangFullVersion()); + auto HashResult = Hash.final(); + uint64_t HashValue = + llvm::support::endian::read( + 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(); +} + +bool cc1modbuildd::daemonExists(StringRef BasePath) { + + SmallString<128> SocketPath = BasePath; + llvm::sys::path::append(SocketPath, SOCKET_FILE_NAME); + + if (!llvm::sys::fs::exists(SocketPath)) + return false; + + Expected ConnectedFD = connectToSocketAndHandshake(SocketPath); + if (ConnectedFD) { + close(std::move(*ConnectedFD)); + return true; + } + + consumeError(ConnectedFD.takeError()); + return false; +} + +llvm::Error cc1modbuildd::attemptHandshake(int SocketFD) { + + cc1modbuildd::SocketMsg Request{ActionType::HANDSHAKE, StatusType::REQUEST}; + std::string Buffer = cc1modbuildd::getBufferFromSocketMsg(Request); + + if (llvm::Error Err = writeToSocket(Buffer, SocketFD)) + return std::move(Err); + + Expected MaybeServerResponse = readSocketMsgFromSocket(SocketFD); + if (!MaybeServerResponse) + return std::move(MaybeServerResponse.takeError()); + SocketMsg 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("Handshake failed", + inconvertibleErrorCode()); +} + +Expected cc1modbuildd::connectToSocketAndHandshake(StringRef SocketPath) { + + Expected ConnectedFD = connectToSocket(SocketPath); + if (!ConnectedFD) + return std::move(ConnectedFD.takeError()); + + llvm::Error Err = attemptHandshake(std::move(*ConnectedFD)); + if (Err) + return std::move(Err); + + return ConnectedFD; +} + +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(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(); +} + +llvm::Error cc1modbuildd::getModuleBuildDaemon(const char *Argv0, + StringRef BasePath) { + + // If module build daemon already exist return success + if (cc1modbuildd::daemonExists(BasePath)) { + return llvm::Error::success(); + } + + 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 was created + usleep(WaitTime); + if (cc1modbuildd::daemonExists(BasePath)) + return llvm::Error::success(); + CumulativeTime += INTERVAL; + } + + // After waiting 30 seconds give up + return llvm::make_error( + "Module build daemon did not exist after spawn attempt", + inconvertibleErrorCode()); +} + +Expected +cc1modbuildd::registerTranslationUnit(ArrayRef CC1Cmd, + StringRef Argv0, StringRef BasePath, + StringRef CWD) { + + std::vector Argv0PlusCC1; + Argv0PlusCC1.push_back(Argv0.str()); + Argv0PlusCC1.insert(Argv0PlusCC1.end(), CC1Cmd.begin(), CC1Cmd.end()); + + // FIXME: Should not need to append again here + SmallString<128> SocketPath = BasePath; + llvm::sys::path::append(SocketPath, SOCKET_FILE_NAME); + + cc1modbuildd::SocketMsg Request{ActionType::REGISTER, StatusType::REQUEST, + CWD.str(), Argv0PlusCC1}; + + Expected MaybeServerFD = + connectAndWriteSocketMsgToSocket(Request, SocketPath); + if (!MaybeServerFD) + return std::move(MaybeServerFD.takeError()); + + return std::move(*MaybeServerFD); +} + +Expected> 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 MaybeServerResponse = readSocketMsgFromSocket(ServerFD); + if (!MaybeServerResponse) + return std::move(MaybeServerResponse.takeError()); + SocketMsg 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.Argv0PlusCC1CommandLine.value(); + + return llvm::make_error( + "Daemon failed to processes registered translation unit", + inconvertibleErrorCode()); +} + +Expected> +cc1modbuildd::updateCC1WithModuleBuildDaemon(ArrayRef CC1Cmd, + const char *Argv0, StringRef CWD) { + + std::string BasePath = cc1modbuildd::getBasePath(); + std::string ErrMessage; + + // If module build daemon does not exist spawn module build daemon + llvm::Error DaemonErr = cc1modbuildd::getModuleBuildDaemon(Argv0, BasePath); + if (DaemonErr) { + handleAllErrors(std::move(DaemonErr), [&](ErrorInfoBase &EIB) { + ErrMessage = "Connect to daemon failed: " + EIB.message(); + }); + return llvm::make_error(ErrMessage, inconvertibleErrorCode()); + } + + // Send translation unit information to module build daemon for processing + Expected MaybeServerFD = + cc1modbuildd::registerTranslationUnit(CC1Cmd, Argv0, BasePath, CWD); + if (!MaybeServerFD) { + handleAllErrors( + std::move(MaybeServerFD.takeError()), [&](ErrorInfoBase &EIB) { + ErrMessage = "Register translation unit failed: " + EIB.message(); + }); + return llvm::make_error(ErrMessage, inconvertibleErrorCode()); + } + + // Wait for response from module build daemon. Response will hopefully be an + // updated cc1 command line with additional -fmodule-file= flags and + // implicit module flags removed + Expected> MaybeUpdatedCC1 = + cc1modbuildd::getUpdatedCC1(std::move(*MaybeServerFD)); + if (!MaybeUpdatedCC1) { + handleAllErrors(std::move(MaybeUpdatedCC1.takeError()), + [&](ErrorInfoBase &EIB) { + ErrMessage = "Get updated cc1 failed: " + EIB.message(); + }); + return llvm::make_error(ErrMessage, inconvertibleErrorCode()); + } + + // Remove the Argv0 from SocketMsg.Argv0PlusCC1CommandLine + std::vector UpdatedCC1 = std::move(*MaybeUpdatedCC1); + if (!UpdatedCC1.empty()) { + UpdatedCC1.erase(UpdatedCC1.begin()); + } + return UpdatedCC1; +} + +#endif // LLVM_ON_UNIX \ No newline at end of file Index: clang/lib/Tooling/ModuleBuildDaemon/SocketMsgSupport.cpp =================================================================== --- /dev/null +++ clang/lib/Tooling/ModuleBuildDaemon/SocketMsgSupport.cpp @@ -0,0 +1,74 @@ +//===------------------------ SocketMsgSupport.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/SocketMsgSupport.h" +#include "clang/Tooling/ModuleBuildDaemon/Protocol.h" +#include "clang/Tooling/ModuleBuildDaemon/SocketSupport.h" + +std::string cc1modbuildd::getBufferFromSocketMsg(SocketMsg SocketMsg) { + + std::string Buffer; + llvm::raw_string_ostream OS(Buffer); + llvm::yaml::Output YamlOut(OS); + + YamlOut << SocketMsg; + return Buffer; +} + +Expected +cc1modbuildd::getSocketMsgFromBuffer(char *Buffer) { + + SocketMsg 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(Msg, inconvertibleErrorCode()); + } + + return ClientRequest; +} + +Expected +cc1modbuildd::readSocketMsgFromSocket(int FD) { + Expected> MaybeResponseBuffer = readFromSocket(FD); + if (!MaybeResponseBuffer) + return std::move(MaybeResponseBuffer.takeError()); + + // Wait for response from module build daemon + Expected MaybeResponse = + getSocketMsgFromBuffer(std::move(*MaybeResponseBuffer).get()); + if (!MaybeResponse) + return std::move(MaybeResponse.takeError()); + + return std::move(*MaybeResponse); +} + +llvm::Error cc1modbuildd::writeSocketMsgToSocket(SocketMsg Msg, int FD) { + + std::string Buffer = getBufferFromSocketMsg(Msg); + if (llvm::Error Err = writeToSocket(Buffer, FD)) + return std::move(Err); + + return llvm::Error::success(); +} + +Expected +cc1modbuildd::connectAndWriteSocketMsgToSocket(SocketMsg Msg, + StringRef SocketPath) { + Expected 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; +} \ No newline at end of file Index: clang/lib/Tooling/ModuleBuildDaemon/SocketSupport.cpp =================================================================== --- /dev/null +++ clang/lib/Tooling/ModuleBuildDaemon/SocketSupport.cpp @@ -0,0 +1,106 @@ +//===------------------------- 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/Protocol.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 +#include +#include +#include +#include +#include +#include +#include +#include +#include + +Expected 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 cc1modbuildd::connectToSocket(StringRef SocketPath) { + + Expected 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 cc1modbuildd::connectAndWriteToSocket(std::string Buffer, + StringRef SocketPath) { + + Expected 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> cc1modbuildd::readFromSocket(int FD) { + + std::unique_ptr Buffer(new char[MAX_BUFFER]); + memset(Buffer.get(), 0, MAX_BUFFER); + int n = read(FD, Buffer.get(), MAX_BUFFER); + + if (n < 0) { + std::string Msg = "socket read error: " + std::string(strerror(errno)); + return llvm::make_error(Msg, inconvertibleErrorCode()); + } + if (n == 0) + return llvm::make_error("EOF", inconvertibleErrorCode()); + + return Buffer; +} + +llvm::Error cc1modbuildd::writeToSocket(std::string Buffer, int WriteFD) { + + ssize_t MessageSize = static_cast(Buffer.size()); + + if (write(WriteFD, Buffer.c_str(), Buffer.size()) != MessageSize) { + std::string Msg = "socket write error: " + std::string(strerror(errno)); + return llvm::make_error(Msg, inconvertibleErrorCode()); + } + return llvm::Error::success(); +} + +#endif // LLVM_ON_UNIX \ No newline at end of file Index: clang/test/Driver/unknown-arg.c =================================================================== --- clang/test/Driver/unknown-arg.c +++ 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', and '-cc1gen-reproducer'. // RUN: %clang -S %s -o %t.s -Wunknown-to-clang-option 2>&1 | FileCheck --check-prefix=IGNORED %s Index: clang/tools/driver/CMakeLists.txt =================================================================== --- clang/tools/driver/CMakeLists.txt +++ clang/tools/driver/CMakeLists.txt @@ -28,6 +28,7 @@ cc1_main.cpp cc1as_main.cpp cc1gen_reproducer_main.cpp + cc1modbuildd_main.cpp DEPENDS intrinsics_gen @@ -39,9 +40,11 @@ PRIVATE clangBasic clangCodeGen + clangDependencyScanning clangDriver clangFrontend clangFrontendTool + clangModuleBuildDaemon clangSerialization ) Index: clang/tools/driver/cc1_main.cpp =================================================================== --- clang/tools/driver/cc1_main.cpp +++ 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/Protocol.h" #include "llvm/ADT/Statistic.h" #include "llvm/Config/llvm-config.h" #include "llvm/LinkAllPasses.h" @@ -45,6 +46,7 @@ #include "llvm/Support/raw_ostream.h" #include "llvm/Target/TargetMachine.h" #include +#include #ifdef CLANG_HAVE_RLIMITS #include @@ -181,7 +183,7 @@ std::nullopt)); return 0; } - +#include int cc1_main(ArrayRef Argv, const char *Argv0, void *MainAddr) { ensureSufficientStack(); @@ -205,11 +207,56 @@ TextDiagnosticBuffer *DiagsBuffer = new TextDiagnosticBuffer; DiagnosticsEngine Diags(DiagID, &*DiagOpts, DiagsBuffer); + // Create the actual diagnostics engine. + + Clang->createDiagnostics(); + if (!Clang->hasDiagnostics()) + return 1; + // Setup round-trip remarks for the DiagnosticsEngine used in CreateFromArgs. if (find(Argv, StringRef("-Rround-trip-cc1-args")) != Argv.end()) Diags.setSeverity(diag::remark_cc1_round_trip_generated, diag::Severity::Remark, {}); +#if LLVM_ON_UNIX + std::vector UpdatedArgv; + std::vector CharUpdatedArgv; + + // handle module build daemon functionality if enabled + if (find(Argv, StringRef("-fmodule-build-daemon")) != Argv.end()) { + + // Create FileManager + if (!Clang->hasFileManager()) + Clang->createFileManager(createVFSFromCompilerInvocation( + Clang->getInvocation(), Clang->getDiagnostics())); + + // Get current working directory for when module build daemon scans TU + llvm::ErrorOr MaybeCWD = Clang->getFileManager() + .getVirtualFileSystem() + .getCurrentWorkingDirectory(); + if (MaybeCWD.getError()) { + llvm::errs() << "Could not get working directory: " + << MaybeCWD.getError().message() << "\n"; + return 1; + } + + Expected> MaybeUpdatedArgv = + cc1modbuildd::updateCC1WithModuleBuildDaemon(Argv, Argv0, *MaybeCWD); + if (!MaybeUpdatedArgv) + llvm::errs() << toString(std::move(MaybeUpdatedArgv.takeError())) << '\n'; + UpdatedArgv = std::move(*MaybeUpdatedArgv); + + // Command line in SocketMsg is stored as std::vector to + // accomadate the dependency scanner + CharUpdatedArgv.reserve(UpdatedArgv.size()); + for (const auto &str : UpdatedArgv) { + CharUpdatedArgv.push_back(str.c_str()); + } + + Argv = llvm::ArrayRef(CharUpdatedArgv); + } +#endif + bool Success = CompilerInvocation::CreateFromArgs(Clang->getInvocation(), Argv, Diags, Argv0); @@ -227,11 +274,6 @@ Clang->getHeaderSearchOpts().ResourceDir = CompilerInvocation::GetResourcesPath(Argv0, MainAddr); - // Create the actual diagnostics engine. - Clang->createDiagnostics(); - if (!Clang->hasDiagnostics()) - return 1; - // Set an error handler, so that any LLVM backend diagnostics go through our // error handler. llvm::install_fatal_error_handler(LLVMErrorHandler, Index: clang/tools/driver/cc1modbuildd_main.cpp =================================================================== --- /dev/null +++ clang/tools/driver/cc1modbuildd_main.cpp @@ -0,0 +1,361 @@ +//===------- 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/Protocol.h" +#include "clang/Tooling/ModuleBuildDaemon/SocketMsgSupport.h" +#include "clang/Tooling/ModuleBuildDaemon/SocketSupport.h" +#include "llvm/ADT/ArrayRef.h" +#include "llvm/ADT/SmallString.h" +#include "llvm/Config/llvm-config.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 +#include +#include +#include +#include +#include +#include +#include +#include +#include + +using namespace llvm; +using namespace clang; +using namespace tooling::dependencies; + +namespace { +class ModuleBuildDaemonServer { +public: + SmallString<128> BasePath; + SmallString<128> SocketPath; + SmallString<128> PidPath; + + ModuleBuildDaemonServer(SmallString<128> Path, ArrayRef Argv) + : BasePath(Path), SocketPath(Path), + Diags(constructDiagnosticsEngine(Argv)) { + llvm::sys::path::append(SocketPath, SOCKET_FILE_NAME); + + if (find(Argv, StringRef("-Rmodule-build-daemon")) != Argv.end()) + Diags.setSeverityForGroup(diag::Flavor::Remark, + diag::Group::ModuleBuildDaemon, + diag::Severity::Remark); + } + + ~ModuleBuildDaemonServer() { Shutdown(SIGTERM); } + + int Fork(); + int Launch(); + int Listen(); + static llvm::Error Service(int Client); + + void Shutdown(int signal) { + unlink(SocketPath.c_str()); + shutdown(ListenSocketFD, SHUT_RD); + close(ListenSocketFD); + exit(EXIT_SUCCESS); + } + +private: + // Initializes and returns DiagnosticsEngine + static DiagnosticsEngine + constructDiagnosticsEngine(ArrayRef Argv) { + IntrusiveRefCntPtr DiagID(new DiagnosticIDs()); + IntrusiveRefCntPtr DiagOpts = + CreateAndPopulateDiagOpts(Argv); + TextDiagnosticPrinter *DiagsBuffer = + new TextDiagnosticPrinter(cc1modbuildd::unbuff_outs(), &*DiagOpts); + return DiagnosticsEngine(DiagID, &*DiagOpts, DiagsBuffer); + } + + pid_t Pid = -1; + int ListenSocketFD = -1; + DiagnosticsEngine Diags; +}; + +// Required to handle SIGTERM by calling Shutdown +ModuleBuildDaemonServer *DaemonPtr = nullptr; +void handleSignal(int Signal) { + if (DaemonPtr != nullptr) { + DaemonPtr->Shutdown(Signal); + } +} +} // namespace + +static Expected +scanTranslationUnit(cc1modbuildd::SocketMsg Request) { + + DependencyScanningService Service(ScanningMode::DependencyDirectivesScan, + ScanningOutputFormat::Full, + /*OptimizeArgs*/ false, + /*EagerLoadModules*/ false); + + DependencyScanningTool Tool(Service); + + llvm::DenseSet AlreadySeenModules; + auto LookupOutput = [&](const ModuleID &MID, ModuleOutputKind MOK) { + return MID.ContextHash; + }; + + auto MaybeTUDeps = Tool.getTranslationUnitDependencies( + Request.Argv0PlusCC1CommandLine.value(), Request.WorkingDirectory.value(), + AlreadySeenModules, LookupOutput); + + if (!MaybeTUDeps) + return std::move(MaybeTUDeps.takeError()); + + return std::move(*MaybeTUDeps); +} + +// GOAL: take a client request in the form of a cc1modbuildd::SocketMsg and +// return an updated cc1 command line for the registered cc1 invocation +static Expected> +getUpdatedCC1(cc1modbuildd::SocketMsg Request) { + + Expected MaybeTUDeps = scanTranslationUnit(Request); + if (!MaybeTUDeps) + return std::move(MaybeTUDeps.takeError()); + TranslationUnitDeps TUDeps = std::move(*MaybeTUDeps); + + // For now write dependencies to log file + for (auto const &Dep : TUDeps.FileDeps) { + cc1modbuildd::unbuff_outs() << Dep << '\n'; + } + + if (!TUDeps.ModuleGraph.empty()) + errs() << "Warning: translation unit contained modules. Module build " + "daemon not yet able to build modules" + << '\n'; + + // TODO: implement building modules and returning updated cc1 command line + // Until then just return original command line + return Request.Argv0PlusCC1CommandLine.value(); +} + +// Forks and detaches process, creating module build daemon +int ModuleBuildDaemonServer::Fork() { + + 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::Launch() { + + // 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); + } + Diags.Report(diag::remark_mbd_socket_addr) << 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); + } + + Diags.Report(diag::remark_mbd_generic_msg) + << "module build daemon launch complete!"; + return 0; +} + +// Function submitted to thread pool with each client connection. Not +// responsible for closing client connections +llvm::Error ModuleBuildDaemonServer::Service(int Client) { + + // Read request from client + Expected MaybeClientRequest = + cc1modbuildd::readSocketMsgFromSocket(Client); + if (!MaybeClientRequest) + return std::move(MaybeClientRequest.takeError()); + cc1modbuildd::SocketMsg ClientRequest = std::move(*MaybeClientRequest); + + // Handle HANDSHAKE + if (ClientRequest.MsgAction == cc1modbuildd::ActionType::HANDSHAKE) { + std::string ServerResponse = cc1modbuildd::getBufferFromSocketMsg( + {cc1modbuildd::ActionType::HANDSHAKE, + cc1modbuildd::StatusType::SUCCESS}); + llvm::Error WriteErr = cc1modbuildd::writeToSocket(ServerResponse, Client); + if (WriteErr) + return std::move(WriteErr); + return llvm::Error::success(); + } + + // Handle REGISTER + if (ClientRequest.MsgAction == cc1modbuildd::ActionType::REGISTER) { + + Expected> MaybeUpdatedCC1 = + getUpdatedCC1(ClientRequest); + + if (!MaybeUpdatedCC1) { + // TODO: Add error message to cc1modbuildd::SocketMsg + llvm::Error FailureWriteErr = cc1modbuildd::writeSocketMsgToSocket( + {cc1modbuildd::ActionType::REGISTER, + cc1modbuildd::StatusType::FAILURE}, + Client); + + if (FailureWriteErr) + return llvm::joinErrors(std::move(FailureWriteErr), + std::move(MaybeUpdatedCC1.takeError())); + + return std::move(MaybeUpdatedCC1.takeError()); + } + + llvm::Error SuccessWriteErr = cc1modbuildd::writeSocketMsgToSocket( + {cc1modbuildd::ActionType::REGISTER, cc1modbuildd::StatusType::SUCCESS, + std::nullopt, std::move(*MaybeUpdatedCC1)}, + Client); + + if (SuccessWriteErr) + return std::move(SuccessWriteErr); + + return llvm::Error::success(); + } + + // Should never be reached. Conditional should exist for each ActionType + llvm_unreachable("Unrecognized Action"); +} + +int ModuleBuildDaemonServer::Listen() { + + llvm::ThreadPool Pool; + int Client; + + while (true) { + + if ((Client = accept(ListenSocketFD, NULL, NULL)) == -1) { + std::perror("Socket accept error: "); + continue; + } + + // FIXME: Error message could be overwritten before being handled if two + // threads return their results simultaneously + std::shared_future result = Pool.async(Service, Client); + llvm::Error Err = std::move(const_cast(result.get())); + + if (Err) { + handleAllErrors(std::move(Err), [&](ErrorInfoBase &EIB) { + errs() << "Error while scanning: " << EIB.message() << '\n'; + }); + } + + close(Client); + } + return 0; +} + +// Module build daemon is spawned with the following command line: +// +// clang -cc1modbuildd -Rmodule-build-daemon +// +// 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, follows the +// format /tmp/clang- +// +// -Rmodule-build-daemon is optional and provides some debug information +// +int cc1modbuildd_main(ArrayRef Argv) { + + if (Argv.size() < 1) { + outs() << "spawning a module build daemon requies a command line format of " + "`clang -cc1modbuildd `. 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; + + Daemon.Fork(); + Daemon.Launch(); + Daemon.Listen(); + + return 0; +} + +#endif // LLVM_ON_UNIX \ No newline at end of file Index: clang/tools/driver/driver.cpp =================================================================== --- clang/tools/driver/driver.cpp +++ clang/tools/driver/driver.cpp @@ -213,6 +213,9 @@ extern int cc1gen_reproducer_main(ArrayRef Argv, const char *Argv0, void *MainAddr, const llvm::ToolContext &); +#if LLVM_ON_UNIX +extern int cc1modbuildd_main(ArrayRef Argv); +#endif static void insertTargetAndModeArgs(const ParsedClangName &NameParts, SmallVectorImpl &ArgVector, @@ -369,9 +372,15 @@ if (Tool == "-cc1gen-reproducer") return cc1gen_reproducer_main(ArrayRef(ArgV).slice(2), ArgV[0], GetExecutablePathVP, ToolContext); - // Reject unknown tools. - llvm::errs() << "error: unknown integrated tool '" << Tool << "'. " - << "Valid tools include '-cc1' and '-cc1as'.\n"; +#if LLVM_ON_UNIX + if (Tool == "-cc1modbuildd") + return cc1modbuildd_main(ArrayRef(ArgV).slice(2)); +#endif + + // Reject unknown tools + llvm::errs() + << "error: unknown integrated tool '" << Tool << "'. " + << "Valid tools include '-cc1', '-cc1as', and '-cc1gen-reproducer'.\n"; return 1; }