// -*- mode: C++; c-file-style: "cc-mode" -*- //************************************************************************* // DESCRIPTION: Verilator: Thread pool for Verilator itself // // Code available from: https://verilator.org // //************************************************************************* // // Copyright 2005-2023 by Wilson Snyder. This program is free software; you can // redistribute it and/or modify it under the terms of either the GNU // Lesser General Public License Version 3 or the Perl Artistic License // Version 2.0. // SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0 // //************************************************************************* #ifndef _V3THREADPOOL_H_ #define _V3THREADPOOL_H_ 1 #include "verilated_threads.h" #include #include #include #include #include #include #include //============================================================================ class V3ThreadPool final { // MEMBERS static constexpr unsigned int FUTUREWAITFOR_MS = 100; using job_t = std::function; mutable VerilatedMutex m_mutex; // Mutex for use by m_queue std::queue m_queue VL_GUARDED_BY(m_mutex); // Queue of jobs // We don't need to guard this condition_variable as // both `notify_one` and `notify_all` functions are atomic, // `wait` function is not atomic, but we are guarding `m_queue` that is // used by this condition_variable, so clang checks that we have mutex locked std::condition_variable_any m_cv; // Conditions to wake up workers std::list m_workers; // Worker threads VerilatedMutex m_stoppedJobsMutex; // Used to signal stopped jobs // Conditions to wake up stopped jobs std::condition_variable_any m_stoppedJobsCV VL_GUARDED_BY(m_stoppedJobsMutex); std::atomic_uint m_stoppedJobs{0}; // Currently stopped jobs waiting for wake up std::atomic_bool m_stopRequested{false}; // Signals to resume stopped jobs std::atomic_bool m_exclusiveAccess{false}; // Signals that all other threads are stopped std::atomic_bool m_shutdown{false}; // Termination pending // CONSTRUCTORS V3ThreadPool() = default; ~V3ThreadPool() { VerilatedLockGuard lock{m_mutex}; m_queue = {}; // make sure queue is empty lock.unlock(); resize(0); } public: // METHODS // Singleton static V3ThreadPool& s() VL_MT_SAFE { static V3ThreadPool s_s; return s_s; } // Resize thread pool to n workers (queue must be empty) void resize(unsigned n) VL_MT_UNSAFE; // Enqueue a job for asynchronous execution template std::future enqueue(std::function&& f) VL_MT_SAFE; // Request exclusive access to processing. // It sends request to stop all other threads and waits for them to stop. // Other threads needs to manually call 'check_stop_requested' in places where // they can be stopped. // When all other threads are stopped, this function executes the job // and resumes execution of other jobs. void requestExclusiveAccess(const job_t&& exclusiveAccessJob) VL_MT_SAFE; // Check if other thread requested exclusive access to processing, // if so, it waits for it to complete. Afterwards it is resumed. // Returns true if request was send and we waited, otherwise false bool waitIfStopRequested() VL_MT_SAFE; template T waitForFuture(std::future& future) VL_MT_SAFE_EXCLUDES(m_mutex); static void selfTest(); private: bool willExecuteSynchronously() const VL_MT_SAFE { return m_workers.empty() || m_exclusiveAccess; } // True when any thread requested exclusive access bool stopRequested() const VL_REQUIRES(m_stoppedJobsMutex) { // don't wait if shutdown already requested if (m_shutdown) return false; return m_stopRequested; } bool stopRequestedStandalone() VL_MT_SAFE_EXCLUDES(m_stoppedJobsMutex) { const VerilatedLockGuard lock{m_stoppedJobsMutex}; return stopRequested(); } // Waits until exclusive access job completes its job void waitStopRequested(VerilatedLockGuard& stoppedJobLock) VL_REQUIRES(m_stoppedJobsMutex); // Waits until all other jobs are stopped void waitOtherThreads(VerilatedLockGuard& stoppedJobLock) VL_MT_SAFE_EXCLUDES(m_mutex) VL_REQUIRES(m_stoppedJobsMutex); template void pushJob(std::shared_ptr>& prom, std::function&& f) VL_MT_SAFE; void workerJobLoop(int id) VL_MT_SAFE; static void startWorker(V3ThreadPool* selfThreadp, int id) VL_MT_SAFE; }; template T V3ThreadPool::waitForFuture(std::future& future) VL_MT_SAFE_EXCLUDES(m_mutex) { while (true) { waitIfStopRequested(); { std::future_status status = future.wait_for(std::chrono::milliseconds(V3ThreadPool::FUTUREWAITFOR_MS)); switch (status) { case std::future_status::deferred: continue; case std::future_status::timeout: continue; case std::future_status::ready: return future.get(); } } } } template std::future V3ThreadPool::enqueue(std::function&& f) VL_MT_SAFE { std::shared_ptr> prom = std::make_shared>(); std::future result = prom->get_future(); pushJob(prom, std::move(f)); const VerilatedLockGuard guard{m_mutex}; m_cv.notify_one(); return result; } template void V3ThreadPool::pushJob(std::shared_ptr>& prom, std::function&& f) VL_MT_SAFE { if (willExecuteSynchronously()) { prom->set_value(f()); } else { const VerilatedLockGuard guard{m_mutex}; m_queue.push([prom, f] { prom->set_value(f()); }); } } template <> void V3ThreadPool::pushJob(std::shared_ptr>& prom, std::function&& f) VL_MT_SAFE; #endif // Guard