diff --git a/lld/Common/ErrorHandler.cpp b/lld/Common/ErrorHandler.cpp --- a/lld/Common/ErrorHandler.cpp +++ b/lld/Common/ErrorHandler.cpp @@ -15,7 +15,7 @@ #include "llvm/IR/DiagnosticInfo.h" #include "llvm/IR/DiagnosticPrinter.h" #include "llvm/Support/CrashRecoveryContext.h" -#include "llvm/Support/ManagedStatic.h" +#include "llvm/Support/FastShutdown.h" #include "llvm/Support/Process.h" #include "llvm/Support/Program.h" #include "llvm/Support/raw_ostream.h" @@ -99,12 +99,11 @@ // safeLldMain(). CrashRecoveryContext::throwIfCrash(val); - // Dealloc/destroy ManagedStatic variables before calling _exit(). - // In an LTO build, allows us to get the output of -time-passes. - // Ensures that the thread pool for the parallel algorithms is stopped to - // avoid intermittent crashes on Windows when exiting. + // We will call _exit() but want to get the output of -time-passes in an LTO + // build. Also ensures that the thread pool for the parallel algorithms is + // stopped to avoid intermittent crashes on Windows when exiting. if (!CrashRecoveryContext::GetCurrent()) - llvm_shutdown(); + llvm_fast_shutdown(); if (hasContext()) lld::errorHandler().flushStreams(); diff --git a/llvm/docs/ProgrammersManual.rst b/llvm/docs/ProgrammersManual.rst --- a/llvm/docs/ProgrammersManual.rst +++ b/llvm/docs/ProgrammersManual.rst @@ -1434,6 +1434,31 @@ reduce file size. This means that you need a Debug+Asserts or Release+Asserts build to use these features. +.. _FastShutdown: + +Fast process shutdown +--------------------- + +LLVM is used in a wide variety of environments, including by loadable components +in long running processes like IDEs and in graphics drivers. To enable these +use cases, care is taken to properly cleanup state and free resources in +destructors. + +When LLVM is used in command-line tools, this cleanup is often unnecessary +since the operating system will free resources anyway. Such tools may want to +exit the process without cleanups to save a little bit of time. The +``sys::Process::Exit`` allows this to be done in a portable manner. + +However, there are a few components in LLVM which perform useful tasks from +global destructors, for example, working around platform-specific issues with +thread pools and printing statistics (the ``-stats`` command-line option). +These tasks can be run manually via the ``llvm_fast_shutdown`` function: + +.. code-block:: c++ + + llvm_fast_shutdown(); + sys::Process::Exit(RetCode, /* NoCleanup */ true); + .. _datastructure: Picking the Right Data Structure for a Task diff --git a/llvm/include/llvm/ADT/Statistic.h b/llvm/include/llvm/ADT/Statistic.h --- a/llvm/include/llvm/ADT/Statistic.h +++ b/llvm/include/llvm/ADT/Statistic.h @@ -9,8 +9,8 @@ /// \file /// This file defines the 'Statistic' class, which is designed to be an easy way /// to expose various metrics from passes. These statistics are printed at the -/// end of a run (from llvm_shutdown), when the -stats command line option is -/// passed on the command line. +/// end of a run (from global destructors or when llvm_fast_shutdown is called), +/// when the -stats command line option is passed on the command line. /// /// This is useful for reporting information like the number of instructions /// simplified, optimized or removed by various transformations, like this: diff --git a/llvm/include/llvm/Support/FastShutdown.h b/llvm/include/llvm/Support/FastShutdown.h new file mode 100644 --- /dev/null +++ b/llvm/include/llvm/Support/FastShutdown.h @@ -0,0 +1,39 @@ +//===-- llvm/Support/FastShutdown.h -----------------------------*- C++ -*-===// +// +// 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 +// +//===----------------------------------------------------------------------===// +// +// Helpers to allow a fast process shutdown without the normal cleanups +// (e.g., using _exit) but with some desirable side-effects that usually happen +// when global static destructors are run. +// +//===----------------------------------------------------------------------===// + +#ifndef LLVM_SUPPORT_FASTSHUTDOWN_H +#define LLVM_SUPPORT_FASTSHUTDOWN_H + +namespace llvm { + +/// Run a small set of operations that usually run from global static +/// destructors, but which have side-effects (e.g., printing statistics) that +/// are desirable for tools that want to do a fast process exit without cleanups +/// (e.g., using _exit). +/// +/// Note that if the process exits (or LLVM is unloaded) normally, calling this +/// function isn't necessary and should be avoided. +/// +/// IMPORTANT: It's only safe to call llvm_fast_shutdown() in a single thread, +/// without any other threads executing LLVM APIs. No LLVM APIs should be used +/// after calling this function (other than sys::Process::Exit). +void llvm_fast_shutdown(); + +void fast_shutdown_debug_counters(); +void fast_shutdown_statistics(); +void fast_shutdown_parallel(); + +} // namespace llvm + +#endif // LLVM_SUPPORT_FASTSHUTDOWN_H diff --git a/llvm/lib/Support/CMakeLists.txt b/llvm/lib/Support/CMakeLists.txt --- a/llvm/lib/Support/CMakeLists.txt +++ b/llvm/lib/Support/CMakeLists.txt @@ -156,6 +156,7 @@ Error.cpp ErrorHandling.cpp ExtensibleRTTI.cpp + FastShutdown.cpp FileCollector.cpp FileUtilities.cpp FileOutputBuffer.cpp diff --git a/llvm/lib/Support/DebugCounter.cpp b/llvm/lib/Support/DebugCounter.cpp --- a/llvm/lib/Support/DebugCounter.cpp +++ b/llvm/lib/Support/DebugCounter.cpp @@ -3,6 +3,7 @@ #include "DebugOptions.h" #include "llvm/Support/CommandLine.h" +#include "llvm/Support/FastShutdown.h" #include "llvm/Support/Format.h" using namespace llvm; @@ -43,6 +44,8 @@ } }; +bool DebugCounterInitialized = false; + // All global objects associated to the DebugCounter, including the DebugCounter // itself, are owned by a single global instance of the DebugCounterOwner // struct. This makes it easier to control the order in which constructors and @@ -61,10 +64,16 @@ // Our destructor uses the debug stream. By referencing it here, we // ensure that its destructor runs after our destructor. (void)dbgs(); + DebugCounterInitialized = true; } // Print information when destroyed, iff command line option is specified. ~DebugCounterOwner() { + printIfEnabled(); + DebugCounterInitialized = false; + } + + void printIfEnabled() { if (DC.isCountingEnabled() && PrintDebugCounter) DC.print(dbgs()); } @@ -77,6 +86,11 @@ } // anonymous namespace +void llvm::fast_shutdown_debug_counters() { + if (DebugCounterInitialized) + DebugCounterOwner::instance().printIfEnabled(); +} + void llvm::initDebugCounterOptions() { (void)DebugCounter::instance(); } DebugCounter &DebugCounter::instance() { diff --git a/llvm/lib/Support/FastShutdown.cpp b/llvm/lib/Support/FastShutdown.cpp new file mode 100644 --- /dev/null +++ b/llvm/lib/Support/FastShutdown.cpp @@ -0,0 +1,20 @@ +//===-- FastShutdown.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 "llvm/Support/FastShutdown.h" +#include "llvm/Support/raw_ostream.h" + +using namespace llvm; + +void llvm::llvm_fast_shutdown() { + fast_shutdown_debug_counters(); + fast_shutdown_statistics(); + fast_shutdown_parallel(); + outs().flush(); + errs().flush(); +} diff --git a/llvm/lib/Support/Parallel.cpp b/llvm/lib/Support/Parallel.cpp --- a/llvm/lib/Support/Parallel.cpp +++ b/llvm/lib/Support/Parallel.cpp @@ -8,7 +8,7 @@ #include "llvm/Support/Parallel.h" #include "llvm/Config/llvm-config.h" -#include "llvm/Support/ManagedStatic.h" +#include "llvm/Support/FastShutdown.h" #include "llvm/Support/Threading.h" #include @@ -27,13 +27,22 @@ namespace { +class ThreadPoolExecutor; + +// Whether the executor has ever been initialized. +// +// This variable is only read via the llvm_fast_shutdown path, whose caller is +// responsible for ensuring that no other threads are running; and it is only +// written to at most once, by the ThreadPoolExecutor constructor. +static bool DefaultExecutorInitialized = false; + /// An abstract class that takes closures and runs them asynchronously. class Executor { public: virtual ~Executor() = default; virtual void add(std::function func) = 0; - static Executor *getDefaultExecutor(); + static ThreadPoolExecutor *getDefaultExecutor(); }; /// An implementation of an Executor that runs closures on a thread pool @@ -41,6 +50,9 @@ class ThreadPoolExecutor : public Executor { public: explicit ThreadPoolExecutor(ThreadPoolStrategy S = hardware_concurrency()) { + assert(!DefaultExecutorInitialized); + DefaultExecutorInitialized = true; + unsigned ThreadCount = S.compute_thread_count(); // Spawn all but one of the threads in another thread as spawning threads // can take a while. @@ -79,13 +91,6 @@ T.join(); } - struct Creator { - static void *call() { return new ThreadPoolExecutor(strategy); } - }; - struct Deleter { - static void call(void *Ptr) { ((ThreadPoolExecutor *)Ptr)->stop(); } - }; - void add(std::function F) override { { std::lock_guard Lock(Mutex); @@ -117,30 +122,9 @@ std::vector Threads; }; -Executor *Executor::getDefaultExecutor() { - // The ManagedStatic enables the ThreadPoolExecutor to be stopped via - // llvm_shutdown() which allows a "clean" fast exit, e.g. via _exit(). This - // stops the thread pool and waits for any worker thread creation to complete - // but does not wait for the threads to finish. The wait for worker thread - // creation to complete is important as it prevents intermittent crashes on - // Windows due to a race condition between thread creation and process exit. - // - // The ThreadPoolExecutor will only be destroyed when the static unique_ptr to - // it is destroyed, i.e. in a normal full exit. The ThreadPoolExecutor - // destructor ensures it has been stopped and waits for worker threads to - // finish. The wait is important as it prevents intermittent crashes on - // Windows when the process is doing a full exit. - // - // The Windows crashes appear to only occur with the MSVC static runtimes and - // are more frequent with the debug static runtime. - // - // This also prevents intermittent deadlocks on exit with the MinGW runtime. - - static ManagedStatic - ManagedExec; - static std::unique_ptr Exec(&(*ManagedExec)); - return Exec.get(); +ThreadPoolExecutor *Executor::getDefaultExecutor() { + static ThreadPoolExecutor Exec; + return &Exec; } } // namespace @@ -175,6 +159,32 @@ } // namespace llvm #endif // LLVM_ENABLE_THREADS +void llvm::fast_shutdown_parallel() { +#if LLVM_ENABLE_THREADS + // Stop the executor, but only if it has been started. + // + // This allows a "clean" fast exit, via llvm_fast_shutdown() and e.g. _exit(). + // This stops the thread pool and waits for any worker thread creation to + // complete but does not wait for the threads to finish. The wait for worker + // thread creation to complete is important as it prevents intermittent + // crashes on Windows due to a race condition between thread creation and + // process exit. + // + // The ThreadPoolExecutor will only be destroyed when the static unique_ptr to + // it is destroyed, i.e. in a normal full exit. The ThreadPoolExecutor + // destructor ensures it has been stopped and waits for worker threads to + // finish. The wait is important as it prevents intermittent crashes on + // Windows when the process is doing a full exit. + // + // The Windows crashes appear to only occur with the MSVC static runtimes and + // are more frequent with the debug static runtime. + // + // This also prevents intermittent deadlocks on exit with the MinGW runtime. + if (parallel::detail::DefaultExecutorInitialized) + parallel::detail::Executor::getDefaultExecutor()->stop(); +#endif +} + void llvm::parallelFor(size_t Begin, size_t End, llvm::function_ref Fn) { // If we have zero or one items, then do not incur the overhead of spinning up diff --git a/llvm/lib/Support/Statistic.cpp b/llvm/lib/Support/Statistic.cpp --- a/llvm/lib/Support/Statistic.cpp +++ b/llvm/lib/Support/Statistic.cpp @@ -28,8 +28,8 @@ #include "llvm/Support/CommandLine.h" #include "llvm/Support/Compiler.h" #include "llvm/Support/Debug.h" +#include "llvm/Support/FastShutdown.h" #include "llvm/Support/Format.h" -#include "llvm/Support/ManagedStatic.h" #include "llvm/Support/Mutex.h" #include "llvm/Support/Timer.h" #include "llvm/Support/YAMLTraits.h" @@ -58,12 +58,17 @@ } namespace { -/// This class is used in a ManagedStatic so that it is created on demand (when -/// the first statistic is bumped) and destroyed only when llvm_shutdown is -/// called. We print statistics from the destructor. +/// This class is instantiated via a function scope static variable so that it +/// is created on demand (when the first statistic is bumped) and destroyed only +/// when global destructors are run. +/// +/// We print statistics from the destructor or when llvm_fast_shutdown() is +/// called. +/// /// This class is also used to look up statistic values from applications that /// use LLVM. class StatisticInfo { + sys::SmartMutex Lock; std::vector Stats; friend void llvm::PrintStatistics(); @@ -78,6 +83,8 @@ StatisticInfo(); ~StatisticInfo(); + sys::SmartMutex &getLock() { return Lock; } + void addStatistic(TrackingStatistic *S) { Stats.push_back(S); } const_iterator begin() const { return Stats.begin(); } @@ -90,24 +97,27 @@ }; } // end anonymous namespace -static ManagedStatic StatInfo; -static ManagedStatic > StatLock; +// Whether the executor has ever been initialized. +// +// This variable is only read via the llvm_fast_shutdown path, whose caller is +// responsible for ensuring that no other threads are running; and it is only +// written to by the StatisticInfo constructor and destructor, which can only +// be run from a single thread. +static bool StatisticInfoInitialized = false; + +static StatisticInfo &getStatInfo() { + static StatisticInfo StatInfo; + return StatInfo; +} /// RegisterStatistic - The first time a statistic is bumped, this method is /// called. void TrackingStatistic::RegisterStatistic() { // If stats are enabled, inform StatInfo that this statistic should be // printed. - // llvm_shutdown calls destructors while holding the ManagedStatic mutex. - // These destructors end up calling PrintStatistics, which takes StatLock. - // Since dereferencing StatInfo and StatLock can require taking the - // ManagedStatic mutex, doing so with StatLock held would lead to a lock - // order inversion. To avoid that, we dereference the ManagedStatics first, - // and only take StatLock afterwards. if (!Initialized.load(std::memory_order_relaxed)) { - sys::SmartMutex &Lock = *StatLock; - StatisticInfo &SI = *StatInfo; - sys::SmartScopedLock Writer(Lock); + StatisticInfo &SI = getStatInfo(); + sys::SmartScopedLock Writer(SI.getLock()); // Check Initialized again after acquiring the lock. if (Initialized.load(std::memory_order_relaxed)) return; @@ -120,6 +130,9 @@ } StatisticInfo::StatisticInfo() { + assert(!StatisticInfoInitialized); + StatisticInfoInitialized = true; + // Ensure that necessary timer global objects are created first so they are // destructed after us. TimerGroup::ConstructForStatistics(); @@ -129,6 +142,7 @@ StatisticInfo::~StatisticInfo() { if (EnableStats || PrintOnExit) llvm::PrintStatistics(); + StatisticInfoInitialized = false; } void llvm::EnableStatistics(bool DoPrintOnExit) { @@ -152,7 +166,7 @@ } void StatisticInfo::reset() { - sys::SmartScopedLock Writer(*StatLock); + sys::SmartScopedLock Writer(getLock()); // Tell each statistic that it isn't registered so it has to register // again. We're holding the lock so it won't be able to do so until we're @@ -174,7 +188,8 @@ } void llvm::PrintStatistics(raw_ostream &OS) { - StatisticInfo &Stats = *StatInfo; + StatisticInfo &Stats = getStatInfo(); + sys::SmartScopedLock Reader(Stats.getLock()); // Figure out how long the biggest Value and Name fields are. unsigned MaxDebugTypeLen = 0, MaxValLen = 0; @@ -201,8 +216,8 @@ } void llvm::PrintStatisticsJSON(raw_ostream &OS) { - sys::SmartScopedLock Reader(*StatLock); - StatisticInfo &Stats = *StatInfo; + StatisticInfo &Stats = getStatInfo(); + sys::SmartScopedLock Reader(Stats.getLock()); Stats.sort(); @@ -228,8 +243,8 @@ void llvm::PrintStatistics() { #if LLVM_ENABLE_STATS - sys::SmartScopedLock Reader(*StatLock); - StatisticInfo &Stats = *StatInfo; + StatisticInfo &Stats = getStatInfo(); + sys::SmartScopedLock Reader(Stats.getLock()); // Statistics not enabled? if (Stats.Stats.empty()) return; @@ -255,14 +270,17 @@ } const std::vector> llvm::GetStatistics() { - sys::SmartScopedLock Reader(*StatLock); + sys::SmartScopedLock Reader(getStatInfo().getLock()); std::vector> ReturnStats; - for (const auto &Stat : StatInfo->statistics()) + for (const auto &Stat : getStatInfo().statistics()) ReturnStats.emplace_back(Stat->getName(), Stat->getValue()); return ReturnStats; } -void llvm::ResetStatistics() { - StatInfo->reset(); +void llvm::ResetStatistics() { getStatInfo().reset(); } + +void llvm::fast_shutdown_statistics() { + if (StatisticInfoInitialized) + llvm::PrintStatistics(); }