Index: clang/lib/Tooling/ModuleBuildDaemon/Client.cpp =================================================================== --- clang/lib/Tooling/ModuleBuildDaemon/Client.cpp +++ clang/lib/Tooling/ModuleBuildDaemon/Client.cpp @@ -214,7 +214,7 @@ // 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 a hash of the clang version + // appropriate BasePath based on the hash of the clang version std::string BasePath; if (!Clang.getFrontendOpts().ModuleBuildDaemonPath.empty()) BasePath = Clang.getFrontendOpts().ModuleBuildDaemonPath; Index: clang/tools/driver/cc1_main.cpp =================================================================== --- clang/tools/driver/cc1_main.cpp +++ clang/tools/driver/cc1_main.cpp @@ -61,7 +61,7 @@ static void LLVMErrorHandler(void *UserData, const char *Message, bool GenCrashDiag) { - DiagnosticsEngine &Diags = *static_cast(UserData); + DiagnosticsEngine &Diags = *static_cast(UserData); Diags.Report(diag::err_fe_error_backend) << Message; @@ -212,30 +212,32 @@ Diags.setSeverity(diag::remark_cc1_round_trip_generated, diag::Severity::Remark, {}); - CompilerInvocation Invocation; - bool InvocationSuccess = - CompilerInvocation::CreateFromArgs(Invocation, Argv, Diags, Argv0); + std::shared_ptr Invocation = + std::make_shared(); + bool Success = + CompilerInvocation::CreateFromArgs(*Invocation, Argv, Diags, Argv0); // FIXME: does not actually flush any diagnostics DiagsBuffer->FlushDiagnostics(Diags); - if (!InvocationSuccess) { + if (!Success) { DiagsBuffer->finish(); return 1; } -#if LLVM_ON_UNIX - // module build daemon may update cc1 args and needs someplace to store - // modified command line for lifetime of compilation + // 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 UpdatedArgv; std::vector CharUpdatedArgv; - // handle module build daemon functionality if enabled - if (Invocation.getFrontendOpts().ModuleBuildDaemon) { +#if LLVM_ON_UNIX + // Handle module build daemon functionality if enabled + if (Invocation->getFrontendOpts().ModuleBuildDaemon) { - // get current working directory for when module build daemon scans TU + // Scanner needs cc1 invocations working directory IntrusiveRefCntPtr System = - createVFSFromCompilerInvocation(Invocation, Diags); + createVFSFromCompilerInvocation(*Invocation, Diags); ErrorOr MaybeCWD = System->getCurrentWorkingDirectory(); + if (MaybeCWD.getError()) { errs() << "Could not get working directory: " << MaybeCWD.getError().message() << "\n"; @@ -243,28 +245,36 @@ } Expected> MaybeUpdatedArgv = - cc1modbuildd::updateCC1WithModuleBuildDaemon(Invocation, Argv, Argv0, + cc1modbuildd::updateCC1WithModuleBuildDaemon(*Invocation, Argv, Argv0, *MaybeCWD); if (!MaybeUpdatedArgv) { errs() << toString(std::move(MaybeUpdatedArgv.takeError())) << '\n'; return 1; } + // CompilerInvocation::CreateFromArgs expects an ArrayRef UpdatedArgv = std::move(*MaybeUpdatedArgv); - // command line in cc1modbuidd::SocketMsg has to be stored as - // std::vector to accomadate the dependency scanner CharUpdatedArgv.reserve(UpdatedArgv.size()); - for (const auto &str : UpdatedArgv) { - CharUpdatedArgv.push_back(str.c_str()); + for (const auto &Arg : UpdatedArgv) { + CharUpdatedArgv.push_back(Arg.c_str()); } - - if (!Argv.equals(ArrayRef(CharUpdatedArgv))) - Argv = ArrayRef(CharUpdatedArgv); } #endif - bool Success = CompilerInvocation::CreateFromArgs(Clang->getInvocation(), - Argv, Diags, Argv0); + cc1modbuildd::unbuff_outs() << "translation unit command line" << '\n'; + for (const auto &Arg : Argv) + cc1modbuildd::unbuff_outs() << Arg << " "; + cc1modbuildd::unbuff_outs() << "\n"; + + // If Argv was modified by the module build daemon create new Invocation + if (!Argv.equals(ArrayRef(CharUpdatedArgv)) && + !CharUpdatedArgv.empty()) { + Argv = ArrayRef(CharUpdatedArgv); + Success = CompilerInvocation::CreateFromArgs(Clang->getInvocation(), Argv, + Diags, Argv0); + } else { + Clang->setInvocation(Invocation); + } if (!Clang->getFrontendOpts().TimeTracePath.empty()) { llvm::timeTraceProfilerInitialize( @@ -282,7 +292,7 @@ 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(); @@ -291,8 +301,8 @@ // Set an error handler, so that any LLVM backend diagnostics go through our // error handler. - llvm::install_fatal_error_handler(LLVMErrorHandler, - static_cast(&Clang->getDiagnostics())); + llvm::install_fatal_error_handler( + LLVMErrorHandler, static_cast(&Clang->getDiagnostics())); DiagsBuffer->FlushDiagnostics(Clang->getDiagnostics()); if (!Success) { Index: clang/tools/driver/cc1modbuildd_main.cpp =================================================================== --- clang/tools/driver/cc1modbuildd_main.cpp +++ clang/tools/driver/cc1modbuildd_main.cpp @@ -18,6 +18,7 @@ #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" @@ -28,6 +29,8 @@ #include #include +#include +#include #include #include #include @@ -36,20 +39,80 @@ #include #include #include +#include using namespace llvm; using namespace clang; using namespace tooling::dependencies; -static bool verbose = false; +namespace { -static void verbose_print(const llvm::Twine &message) { - if (verbose) { - cc1modbuildd::unbuff_outs() << message << '\n'; +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 lock(Mutex); + HashTable.insert({Key, Value}); + } + + bool find(ModuleID Key) { + std::lock_guard lock(Mutex); + if (auto search = HashTable.find(Key); search != HashTable.end()) + return true; + return false; + } + + std::optional> get(ModuleID Key) { + std::lock_guard lock(Mutex); + if (auto search = HashTable.find(Key); search != HashTable.end()) + return std::ref(search->second); + return std::nullopt; + } + + bool erase(const ModuleID &Key) { + std::lock_guard lock(Mutex); + return HashTable.erase(Key) > 0; + } + + bool updateBuildStatus(ModuleID Key, BuildStatus newStatus) { + std::lock_guard lock(Mutex); + if (auto search = HashTable.find(Key); search != HashTable.end()) { + search->second.ModuleBuildStatus = newStatus; + return true; + } + return false; + } + + void print() { + cc1modbuildd::unbuff_outs() << "printing hash table keys" << '\n'; + for (const auto &i : HashTable) { + cc1modbuildd::unbuff_outs() << "Module: " << i.first.ModuleName << '\n'; + cc1modbuildd::unbuff_outs() << "Dependencies: "; + for (const auto &Dep : i.second.Info.ClangModuleDeps) + cc1modbuildd::unbuff_outs() << Dep.ModuleName << ", "; + cc1modbuildd::unbuff_outs() << '\n'; + } + } + +private: + std::unordered_map HashTable; + std::mutex Mutex; +}; -namespace { class ModuleBuildDaemonServer { public: SmallString<128> BasePath; @@ -66,7 +129,7 @@ int forkDaemon(); int launchDaemon(); int listenForClients(); - static llvm::Error handleClient(int Client); + static void handleClient(int Client); void shutdownDaemon(int signal) { unlink(SocketPath.c_str()); @@ -90,6 +153,15 @@ } } // namespace +static bool verbose = false; +static void verbose_print(const llvm::Twine &message) { + if (verbose) { + cc1modbuildd::unbuff_outs() << message << '\n'; + } +} + +static DependencyBuildData DaemonBuildData; + static Expected scanTranslationUnit(cc1modbuildd::SocketMsg Request) { @@ -121,29 +193,192 @@ 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 void storeScanResults(const TranslationUnitDeps &Results) { + + if (Results.ModuleGraph.empty()) + return; + + // Add root node to dependency graph + ModuleDeps Deps; + Deps.ClangModuleDeps = Results.ClangModuleDeps; + DaemonBuildData.insert(Results.ID, {Deps, BuildStatus::WAITING}); + + // Insert children + for (const ModuleDeps &MD : Results.ModuleGraph) + DaemonBuildData.insert(MD.ID, {MD, BuildStatus::WAITING}); +} + +// Slightly modify a modules command line for module build daemon +static std::vector modifyModuleCC1(const StringRef Executable, + std::vector &Args, + SmallString<128> TmpPath) { + + std::vector RefArgs; + RefArgs.reserve(Args.size()); + std::string ModuleFilePrefix = "-fmodule-file="; + + RefArgs.emplace_back(Executable); + for (size_t i = 0; i < Args.size(); i++) { + // Update output to place pre compiled module in /tmp/ directory + if (Args[i] == "-o") { + RefArgs.emplace_back(Args[i++]); + llvm::sys::path::append(TmpPath, Args[i++]); + RefArgs.emplace_back(TmpPath); + } + // Modify arguments to look for pre compiled modules in /tmp/ directory + // For example, -fmodule-file=C=7FSSKSAEKH8XVRF70BTSOZWPB becomes + // -fmodule-file=C=/tmp/7FSSKSAEKH8XVRF70BTSOZWPB + else if (Args[i].substr(0, ModuleFilePrefix.size()) == ModuleFilePrefix) { + // Find the position of the second '=' + size_t Pos = Args[i].find('='); + if (Pos != std::string::npos) { + Pos = Args[i].find('=', Pos + 1); + } + // Insert '/tmp/' after the second '=' + if (Pos != std::string::npos) { + Args[i].insert(Pos + 1, "/tmp/"); + } + RefArgs.emplace_back(Args[i]); + } + // Remove -fmodule-build-daemon + else if (Args[i] != "-fmodule-build-daemon") { + RefArgs.emplace_back(Args[i]); + } + } + // Add to guarantee no implicit tmodules + RefArgs.emplace_back("-fno-implicit-modules"); + + return RefArgs; +} + +static void precompileModuleID(const StringRef Executable, const ModuleID ID) { + cc1modbuildd::unbuff_outs() + << "module " << ID.ModuleName << " will be built" << '\n'; + + std::optional> MaybeDeps = + DaemonBuildData.get(ID); + if (!MaybeDeps) + return; + ModuleBuildInfo &Deps = MaybeDeps->get(); + + // TODO: look into making getBuildArguments a const method + ModuleDeps &NonConstDepsInfo = const_cast(Deps.Info); + const std::vector &Args = NonConstDepsInfo.getBuildArguments(); + std::vector NonConstArgs = + const_cast &>(Args); + + cc1modbuildd::unbuff_outs() << "original command line" << '\n'; + for (const auto &Arg : Args) + cc1modbuildd::unbuff_outs() << Arg << " "; + cc1modbuildd::unbuff_outs() << "\n\n"; + + SmallString<128> Path; + llvm::sys::path::system_temp_directory(/*erasedOnReboot*/ true, Path); + + const std::vector ProcessedArgs = + modifyModuleCC1(Executable, NonConstArgs, Path); + + cc1modbuildd::unbuff_outs() << "new command line" << '\n'; + for (const auto &Arg : ProcessedArgs) + cc1modbuildd::unbuff_outs() << Arg << " "; + cc1modbuildd::unbuff_outs() << "\n"; + + // TODO: Handle error code returned from ExecuteAndWait + llvm::sys::ExecuteAndWait(Executable, ArrayRef(ProcessedArgs)); + DaemonBuildData.updateBuildStatus(ID, BuildStatus::BUILT); + + cc1modbuildd::unbuff_outs() + << "module " << ID.ModuleName << " finished building" << '\n'; + cc1modbuildd::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> 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; +} + +static std::vector +makeTranslationUnitCC1Explicit(std::vector &Args, + const TranslationUnitDeps &TUDeps) { + + std::vector ReturnArgs; + ReturnArgs.reserve(Args.size()); + std::string ModCachePathPrefix = "-fmodules-cache-path"; + + cc1modbuildd::unbuff_outs() + << "origional command line for translation unit" << '\n'; + for (const auto &Arg : Args) + cc1modbuildd::unbuff_outs() << Arg << " "; + cc1modbuildd::unbuff_outs() << "\n\n"; + + for (size_t i = 0; i < Args.size(); ++i) { + if (Args[i] != "-fmodule-build-daemon" && + Args[i] != "-fimplicit-module-maps" && + Args[i].substr(0, ModCachePathPrefix.size()) != ModCachePathPrefix) { + ReturnArgs.emplace_back(Args[i]); + } + } + + for (const auto &Dep : TUDeps.ClangModuleDeps) { + SmallString<128> BasePath; + llvm::sys::path::system_temp_directory(/*erasedOnReboot*/ true, BasePath); + llvm::sys::path::append(BasePath, Dep.ContextHash); + + std::string MF = "-fmodule-file=" + Dep.ModuleName + "=" + BasePath.c_str(); + ReturnArgs.emplace_back(MF); + } + ReturnArgs.emplace_back("-fno-implicit-modules"); + + return ReturnArgs; +} + +// 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> -getUpdatedCC1(cc1modbuildd::SocketMsg Request) { +processRegisterRequest(cc1modbuildd::SocketMsg Request) { Expected MaybeTUDeps = scanTranslationUnit(Request); if (!MaybeTUDeps) return std::move(MaybeTUDeps.takeError()); - TranslationUnitDeps TUDeps = std::move(*MaybeTUDeps); + const 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'; + // If TU does not depend on modules then return command line as is + if (TUDeps.ModuleGraph.empty()) + return Request.CC1CommandLine.value(); + + cc1modbuildd::unbuff_outs() << "modules detected" << '\n'; + storeScanResults(TUDeps); + DaemonBuildData.print(); + buildModuleID(Request.ExecutablePath.value(), TUDeps.ID); - // TODO: implement building modules and returning updated cc1 command line - // Until then just return original command line - return Request.CC1CommandLine.value(); + return makeTranslationUnitCC1Explicit(Request.CC1CommandLine.value(), TUDeps); } // Forks and detaches process, creating module build daemon @@ -160,17 +395,17 @@ Pid = getpid(); - close(STDIN_FILENO); - close(STDOUT_FILENO); - close(STDERR_FILENO); + // 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> 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); + // 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'; @@ -230,13 +465,18 @@ // Function submitted to thread pool with each client connection. Not // responsible for closing client connections -llvm::Error ModuleBuildDaemonServer::handleClient(int Client) { +void ModuleBuildDaemonServer::handleClient(int Client) { // Read request from client Expected MaybeClientRequest = cc1modbuildd::readSocketMsgFromSocket(Client); - if (!MaybeClientRequest) - return std::move(MaybeClientRequest.takeError()); + + if (!MaybeClientRequest) { + handleAllErrors(std::move(MaybeClientRequest.takeError()), [&](ErrorInfoBase &EIB) { + errs() << "Failed to read client request: " << EIB.message() << '\n'; + }); + return; // Failure + } cc1modbuildd::SocketMsg ClientRequest = std::move(*MaybeClientRequest); // Handle HANDSHAKE @@ -244,45 +484,69 @@ 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(); + if (WriteErr) { + handleAllErrors(std::move(WriteErr), [&](ErrorInfoBase &EIB) { + errs() << "Failed to write to socket: " << EIB.message() << '\n'; + }); + return; // Failure + } + return; // Success } // Handle REGISTER if (ClientRequest.MsgAction == cc1modbuildd::ActionType::REGISTER) { - Expected> MaybeNewCC1 = - getUpdatedCC1(ClientRequest); + Expected> MaybeExplicitCC1 = processRegisterRequest(ClientRequest); // if getUpdatedCC1 fails emit error - if (!MaybeNewCC1) { + if (!MaybeExplicitCC1) { llvm::Error FailureWriteErr = cc1modbuildd::writeSocketMsgToSocket( {cc1modbuildd::ActionType::REGISTER, cc1modbuildd::StatusType::FAILURE}, Client); - if (FailureWriteErr) - return llvm::joinErrors(std::move(FailureWriteErr), - std::move(MaybeNewCC1.takeError())); + if (FailureWriteErr) { + llvm::Error Err = llvm::joinErrors(std::move(FailureWriteErr), std::move(MaybeExplicitCC1.takeError())); + handleAllErrors(std::move(Err), [&](ErrorInfoBase &EIB) { + errs() << "Failed to write to socket: " << EIB.message() << '\n'; + }); + return; // Failure + } - return std::move(MaybeNewCC1.takeError()); + handleAllErrors(std::move(MaybeExplicitCC1.takeError()), [&](ErrorInfoBase &EIB) { + errs() << "Failed to write to socket: " << EIB.message() << '\n'; + }); + return; //Failure } + std::vector ExplicitCC1 = std::move(*MaybeExplicitCC1); + cc1modbuildd::unbuff_outs() + << "modified command line for translation unit" << '\n'; + for (const auto &Arg : ExplicitCC1) + cc1modbuildd::unbuff_outs() << Arg << " "; + cc1modbuildd::unbuff_outs() << "\n"; + // Send new CC1 command line to waiting clang invocation llvm::Error SuccessWriteErr = cc1modbuildd::writeSocketMsgToSocket( {cc1modbuildd::ActionType::REGISTER, cc1modbuildd::StatusType::SUCCESS, ClientRequest.WorkingDirectory, ClientRequest.ExecutablePath, - std::move(*MaybeNewCC1)}, + ExplicitCC1}, Client); - if (SuccessWriteErr) - return std::move(SuccessWriteErr); + if (SuccessWriteErr) { + + handleAllErrors(std::move(SuccessWriteErr), [&](ErrorInfoBase &EIB) { + errs() << "Failed to write to socket: " << EIB.message() << '\n'; + }); + return; // Failure + } - return llvm::Error::success(); + return; // Success } + close(Client); // Should never be reached. Conditional should exist for each ActionType llvm_unreachable("Unrecognized Action"); } @@ -299,18 +563,8 @@ continue; } - // FIXME: Error message could be overwritten before being handled if two - // threads return their results simultaneously - std::shared_future result = Pool.async(handleClient, 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); + Pool.async(handleClient, Client); + } return 0; }