Index: clang/include/clang/Driver/Options.td =================================================================== --- clang/include/clang/Driver/Options.td +++ clang/include/clang/Driver/Options.td @@ -2507,6 +2507,12 @@ PosFlag, NegFlag, BothFlags<[CC1Option], " __declspec as a keyword">>, Group; + +def fmodule_build_daemon : Flag<["-"], "fmodule-build-daemon">, Group, + Flags<[NoXarchOption, CC1Option]>, + HelpText<"Enables module build daemon functionality">, + MarshallingInfoFlag>; + def fmodules_cache_path : Joined<["-"], "fmodules-cache-path=">, Group, Flags<[NoXarchOption, CC1Option]>, MetaVarName<"">, HelpText<"Specify the module cache path">; 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/Protocall.h =================================================================== --- /dev/null +++ clang/include/clang/Tooling/ModuleBuildDaemon/Protocall.h @@ -0,0 +1,65 @@ +//===---------------------------- Protocall.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/Support/YAMLParser.h" +#include "llvm/Support/YAMLTraits.h" + +#define MAX_BUFFER 2048 + +using namespace clang; +using namespace llvm; + +namespace cc1modbuildd { + +enum Action { SCAN }; + +struct Command { + Action CMD; + std::string WorkingDirectory; + std::vector FullCommandLine; +}; + +Expected CreateSocket(); + +Expected ConnectToSocket(StringRef SocketPath, int FD); + +bool ModuleBuildDaemonExists(StringRef BasePath); + +Expected SpawnModuleBuildDaemon(StringRef BasePath, const char *Argv0); + +SmallString<128> GetBasePath(); + +llvm::Error ReadMsg(int fd, std::function func); + +int SendMessage(CompilerInstance &Clang, StringRef Argv0, + ArrayRef Argv, std::string WD, + StringRef BasePath); + +} // namespace cc1modbuildd + +template <> struct llvm::yaml::ScalarEnumerationTraits { + static void enumeration(IO &io, cc1modbuildd::Action &value) { + io.enumCase(value, "SCAN", cc1modbuildd::SCAN); + } +}; + +template <> struct llvm::yaml::MappingTraits { + static void mapping(IO &io, cc1modbuildd::Command &info) { + io.mapRequired("CMD", info.CMD); + io.mapRequired("WorkingDirectory", info.WorkingDirectory); + io.mapRequired("FullCommandLine", info.FullCommandLine); + } +}; + +#endif // LLVM_CLANG_TOOLING_MODULEBUILDDAEMON_PROTOCAL_H 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,7 @@ +set(LLVM_LINK_COMPONENTS + Support + ) + +add_clang_library(clangModuleBuildDaemon + Protocall.cpp + ) \ No newline at end of file Index: clang/lib/Tooling/ModuleBuildDaemon/Protocall.cpp =================================================================== --- /dev/null +++ clang/lib/Tooling/ModuleBuildDaemon/Protocall.cpp @@ -0,0 +1,189 @@ +//===--------------------------- Protocall.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/Protocall.h" +#include "clang/Basic/Version.h" +#include "clang/Frontend/CompilerInstance.h" +#include "llvm/ADT/ArrayRef.h" +#include "llvm/ADT/ScopeExit.h" +#include "llvm/ADT/SmallString.h" +#include "llvm/ADT/StringRef.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; + +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, int FD) { + + 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; +} + +bool cc1modbuildd::ModuleBuildDaemonExists(StringRef BasePath) { + SmallString<128> SocketPath = BasePath; + SocketPath.append(".sock"); + + Expected TestFD = cc1modbuildd::CreateSocket(); + if (!TestFD) { + consumeError(TestFD.takeError()); + return false; + } + + Expected ConnectedFD = ConnectToSocket(SocketPath, std::move(*TestFD)); + if (llvm::sys::fs::exists(SocketPath) && ConnectedFD) { + close(std::move(*ConnectedFD)); + return true; + } + + consumeError(ConnectedFD.takeError()); + return false; +} + +Expected 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 true; +} + +SmallString<128> 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, "mbd"); + return BasePath; +} + +llvm::Error cc1modbuildd::ReadMsg(int fd, std::function func) +{ + char buf[MAX_BUFFER]; + memset(buf, 0, MAX_BUFFER); + int n = read(fd, buf, MAX_BUFFER); + + if (n < 0) { + std::string msg = "socket read error: " + std::string(strerror(errno)); + return llvm::make_error(msg, inconvertibleErrorCode()); + } else if (n == 0) { + return llvm::make_error("EOF", inconvertibleErrorCode()); + } else { + func(buf); + } + return llvm::Error::success(); +} + +// Temporary test component used to send messages to cc1modbuildd +int cc1modbuildd::SendMessage(CompilerInstance &Clang, StringRef Argv0, + ArrayRef Argv, std::string WD, + StringRef BasePath) { + + std::vector CC1Cmd = Clang.getInvocation().getCC1CommandLine(); + CC1Cmd.insert(CC1Cmd.begin(), Argv0.str()); + cc1modbuildd::Command Command{SCAN, WD, CC1Cmd}; + + std::string buffer; + llvm::raw_string_ostream OS(buffer); + llvm::yaml::Output yout(OS); + + yout << Command; + + // FIXME: Should not need to append again here + SmallString<128> SocketPath = BasePath; + SocketPath.append(".sock"); + + Expected MaybeFD = cc1modbuildd::CreateSocket(); + if (!MaybeFD) { + std::cout << toString(MaybeFD.takeError()) << std::endl; + return -1; + } + consumeError(MaybeFD.takeError()); + int RealFD = std::move(*MaybeFD); + + Expected MaybeWriteFD = cc1modbuildd::ConnectToSocket(SocketPath, RealFD); + if (!MaybeWriteFD) { + std::cout << toString(MaybeWriteFD.takeError()) << std::endl; + return -1; + } + int WriteFD = std::move(*MaybeWriteFD); + + // write + ssize_t message_size = static_cast(buffer.size()); + if (write(WriteFD, buffer.c_str(), buffer.size()) != message_size) { + std::perror("write error"); + close(WriteFD); + return -1; + } + + char buf[2048]; + memset(buf, 0, 2048); + + // read MAX_BUFFER bytes from client to buf + int n = read(WriteFD, buf, 2048); + + if (n < 0) { + perror("read error"); + return -1; + } else if (n == 0) { + std::cerr << "EOF" << std::endl; + return -1; + } else { + std::cout << "message: " << buf << std::endl; + } + + close(WriteFD); + return 0; +} +#endif // LLVM_ON_UNIX \ No newline at end of file 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/Protocall.h" #include "llvm/ADT/Statistic.h" #include "llvm/Config/llvm-config.h" #include "llvm/LinkAllPasses.h" @@ -45,6 +46,14 @@ #include "llvm/Support/raw_ostream.h" #include "llvm/Target/TargetMachine.h" #include +#include +#include + +// to communicate with module build daemon +#include +#include +#include +#include #ifdef CLANG_HAVE_RLIMITS #include @@ -243,6 +252,24 @@ return 1; } +#if LLVM_ON_UNIX + // handle module build daemon functionality if enabled + if (Clang->getInvocation().getFrontendOpts().ModuleBuildDaemon) { + + SmallString<128> BasePath = cc1modbuildd::GetBasePath(); + + // start module build daemon if not already running + if (!cc1modbuildd::ModuleBuildDaemonExists(BasePath)) { + Expected SpawnedDaemon = + cc1modbuildd::SpawnModuleBuildDaemon(BasePath, Argv0); + std::cout << toString(std::move(SpawnedDaemon.takeError())) << std::endl; + sleep(10); + } + std::string CWD = std::filesystem::current_path().string(); + cc1modbuildd::SendMessage(*Clang, Argv0, Argv, CWD, BasePath); + } +#endif + // Execute the frontend actions. { llvm::TimeTraceScope TimeScope("ExecuteCompiler"); Index: clang/tools/driver/cc1modbuildd_main.cpp =================================================================== --- /dev/null +++ clang/tools/driver/cc1modbuildd_main.cpp @@ -0,0 +1,252 @@ +//===------- 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/Tooling/DependencyScanning/DependencyScanningService.h" +#include "clang/Tooling/DependencyScanning/DependencyScanningTool.h" +#include "clang/Tooling/DependencyScanning/ModuleDepCollector.h" +#include "clang/Tooling/ModuleBuildDaemon/Protocall.h" +#include "llvm/ADT/ArrayRef.h" +#include "llvm/ADT/SmallString.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 + +#define MAX_BUFFER 2048 +#define BASE_FILE "clang-mbd" + +static constexpr const char *SocketExtension = ".sock"; +static constexpr const char *PidExtension = ".pid"; + +using namespace llvm; +using namespace clang; + +static void Scan(cc1modbuildd::Command Command) { + + tooling::dependencies::DependencyScanningService Service( + tooling::dependencies::ScanningMode::DependencyDirectivesScan, + tooling::dependencies::ScanningOutputFormat::Full, + /*OptimizeArgs*/ false, + /*EagerLoadModules*/ false); + + tooling::dependencies::DependencyScanningTool Tool(Service); + + llvm::DenseSet AlreadySeenModules; + auto LookupOutput = [&](const tooling::dependencies::ModuleID &MID, + tooling::dependencies::ModuleOutputKind MOK) { + return MID.ContextHash; + }; + + auto MaybeFile = Tool.getTranslationUnitDependencies( + Command.FullCommandLine, Command.WorkingDirectory, AlreadySeenModules, + LookupOutput); + + std::cout << toString(MaybeFile.takeError()) << std::endl; + + clang::tooling::dependencies::TranslationUnitDeps TUDeps = + std::move(*MaybeFile); + + for (auto const &Dep : TUDeps.FileDeps) { + std::cout << Dep << std::endl; + } +} + +namespace { +class ModuleBuildDaemonServer { +public: + SmallString<128> BasePath; + SmallString<128> SocketPath; + SmallString<128> PidPath; + + ModuleBuildDaemonServer(SmallString<128> Path) + : BasePath(Path), SocketPath(Path), PidPath(Path) { + SocketPath.append(SocketExtension); + PidPath.append(PidExtension); + } + + ~ModuleBuildDaemonServer() { Shutdown(SIGTERM); } + + int Fork(); + int Launch(); + int Listen(); + + // FIXME: Shutdown is not called when computer is powered off + void Shutdown(int signal) { + ::unlink(SocketPath.c_str()); + ::shutdown(ListenSocketFD, SHUT_RD); + ::close(ListenSocketFD); + exit(EXIT_SUCCESS); + } + +private: + 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->Shutdown(signal); + } +} +} // namespace + +// 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); + + freopen("daemon.out", "a", stdout); + freopen("daemon.err", "a", stderr); + + if (::signal(SIGTERM, HandleSignal) == SIG_ERR) + // FIXME: should be replaced by error handler (reportError) + std::cout << "failed to handle SIGTERM" << std::endl; + if (::signal(SIGHUP, SIG_IGN) == SIG_ERR) + // FIXME: should be replaced by error handler (reportError) + std::cout << "failed to ignore SIGHUP" << std::endl; + if (::setsid() == -1) + // FIXME: should be replaced by error handler (reportError) + std::cout << "setsid failed" << std::endl; + + std::cout << "daemon initialization complete!" << std::endl; + return EXIT_SUCCESS; +} + +// Creates unix socket for IPC with module build daemon +int ModuleBuildDaemonServer::Launch() { + + struct sockaddr_un addr; + + // new socket + if ((ListenSocketFD = socket(AF_UNIX, SOCK_STREAM, 0)) == -1) { + std::perror("socket error"); + return -1; + } + + // set addr to all 0s + 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) { + std::perror("bind error"); + return -1; + } + + // set socket to accept incoming connection request + unsigned MaxBacklog = llvm::hardware_concurrency().compute_thread_count(); + if (::listen(ListenSocketFD, MaxBacklog) == -1) { + std::perror("listen error"); + return -1; + } + + return 0; +} + +int ModuleBuildDaemonServer::Listen() { + + auto Service = [](int client) { + + auto HandleMsg = [](char* buf) { + cc1modbuildd::Command Command; + llvm::yaml::Input yin(buf); + + yin >> Command; + if (yin.error()) + return -1; + + Scan(Command); + return 0; + }; + + llvm::Error Err = cc1modbuildd::ReadMsg(client, HandleMsg); + + // When a cc1 invokation checks wheather a daemon exists the act of + // attempting to make a socket connection on SocketPath triggers the + // daemon to accept a client connection. Only send response if ReadMsg + // reads a command sent from the cc1 invocation + if(Err) + return std::move(Err); + + std::string msg = "command complete"; + ssize_t message_size = static_cast(msg.size()); + if (write(client, msg.c_str(), msg.size()) != message_size) { + close(client); + std::string msg = "socket write error: " + std::string(strerror(errno)); + return llvm::make_error(msg, inconvertibleErrorCode()); + } + + close(client); + }; + + llvm::ThreadPool Pool; + int client; + + while (true) { + + if ((client = accept(ListenSocketFD, NULL, NULL)) == -1) { + std::perror("accept error"); + continue; + } + + std::shared_future result = Pool.async(Service, client); + } + return 0; +} + +int cc1modbuildd_main(ArrayRef Argv) { + + SmallString<128> BasePath(Argv[0]); + + StringRef BaseDir = llvm::sys::path::parent_path(BasePath); + llvm::sys::fs::create_directories(BaseDir); + + ModuleBuildDaemonServer Daemon(BasePath); + std::cout << Daemon.SocketPath.c_str() << std::endl; + + // 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,20 @@ if (Tool == "-cc1gen-reproducer") return cc1gen_reproducer_main(ArrayRef(ArgV).slice(2), ArgV[0], GetExecutablePathVP, ToolContext); - // Reject unknown tools. +#if LLVM_ON_UNIX + if (Tool == "-cc1modbuildd") + return cc1modbuildd_main(ArrayRef(ArgV).slice(2)); + + // FIXME: Once cc1modbuildd becomes portable unify llvm::errs messages llvm::errs() << "error: unknown integrated tool '" << Tool << "'. " - << "Valid tools include '-cc1' and '-cc1as'.\n"; + << "Valid tools include '-cc1', '-cc1as', '-cc1gen-reproducer', " + << "and '-cc1modbuildd'.\n"; +#else + // Reject unknown tools + llvm::errs() + << "error: unknown integrated tool '" << Tool << "'. " + << "Valid tools include '-cc1', '-cc1as', and '-cc1gen-reproducer'.\n"; +#endif return 1; }