blob: 36d74c14b18c707b812a0487f91e29c385391de2 [file] [log] [blame]
/*
* Copyright (C) 2017 Apple Inc. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY APPLE INC. ``AS IS'' AND ANY
* EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
* PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR
* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
* EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
* PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
* OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
#include "config.h"
#include "WasmWorklist.h"
#if ENABLE(WEBASSEMBLY)
#include "CPU.h"
#include "WasmPlan.h"
namespace JSC { namespace Wasm {
namespace WasmWorklistInternal {
static const bool verbose = false;
}
const char* Worklist::priorityString(Priority priority)
{
switch (priority) {
case Priority::Preparation: return "Preparation";
case Priority::Shutdown: return "Shutdown";
case Priority::Compilation: return "Compilation";
case Priority::Synchronous: return "Synchronous";
}
RELEASE_ASSERT_NOT_REACHED();
}
// The Thread class is designed to prevent threads from blocking when there is still work
// in the queue. Wasm's Plans have some phases, Validiation, Preparation, and Completion,
// that can only be done by one thread, and another phase, Compilation, that can be done
// many threads. In order to stop a thread from wasting time we remove any plan that is
// is currently in a single threaded state from the work queue so other plans can run.
class Worklist::Thread final : public AutomaticThread {
public:
using Base = AutomaticThread;
Thread(const AbstractLocker& locker, Worklist& work)
: Base(locker, work.m_lock, work.m_planEnqueued.copyRef())
, worklist(work)
{
}
protected:
PollResult poll(const AbstractLocker&) override
{
auto& queue = worklist.m_queue;
synchronize.notifyAll();
while (!queue.isEmpty()) {
Priority priority = queue.peek().priority;
if (priority == Worklist::Priority::Shutdown)
return PollResult::Stop;
element = queue.peek();
// Only one thread should validate/prepare.
if (!queue.peek().plan->multiThreaded())
queue.dequeue();
if (element.plan->hasWork())
return PollResult::Work;
// There must be a another thread linking this plan so we can deque and see if there is other work.
queue.dequeue();
element = QueueElement();
}
return PollResult::Wait;
}
WorkResult work() override
{
auto complete = [&] (const AbstractLocker&) {
// We need to hold the lock to release our plan otherwise the main thread, while canceling plans
// might use after free the plan we are looking at
element = QueueElement();
return WorkResult::Continue;
};
Plan* plan = element.plan.get();
ASSERT(plan);
bool wasMultiThreaded = plan->multiThreaded();
plan->work(Plan::Partial);
ASSERT(!plan->hasWork() || plan->multiThreaded());
if (plan->hasWork() && !wasMultiThreaded && plan->multiThreaded()) {
LockHolder locker(*worklist.m_lock);
element.setToNextPriority();
worklist.m_queue.enqueue(WTFMove(element));
worklist.m_planEnqueued->notifyAll(locker);
return complete(locker);
}
return complete(holdLock(*worklist.m_lock));
}
const char* name() const override
{
return "Wasm Worklist Helper Thread";
}
public:
Condition synchronize;
Worklist& worklist;
// We can only modify element when holding the lock. A synchronous compile might look at each thread's tasks in order to boost the priority.
QueueElement element;
};
void Worklist::QueueElement::setToNextPriority()
{
switch (priority) {
case Priority::Preparation:
priority = Priority::Compilation;
return;
case Priority::Synchronous:
return;
default:
break;
}
RELEASE_ASSERT_NOT_REACHED();
}
void Worklist::enqueue(Ref<Plan> plan)
{
LockHolder locker(*m_lock);
if (!ASSERT_DISABLED) {
for (const auto& element : m_queue)
ASSERT_UNUSED(element, element.plan.get() != &plan.get());
}
dataLogLnIf(WasmWorklistInternal::verbose, "Enqueuing plan");
m_queue.enqueue({ Priority::Preparation, nextTicket(), WTFMove(plan) });
m_planEnqueued->notifyOne(locker);
}
void Worklist::completePlanSynchronously(Plan& plan)
{
{
LockHolder locker(*m_lock);
m_queue.decreaseKey([&] (QueueElement& element) {
if (element.plan == &plan) {
element.priority = Priority::Synchronous;
return true;
}
return false;
});
for (auto& thread : m_threads) {
if (thread->element.plan == &plan)
thread->element.priority = Priority::Synchronous;
}
}
plan.waitForCompletion();
}
void Worklist::stopAllPlansForContext(Context& context)
{
LockHolder locker(*m_lock);
Vector<QueueElement> elements;
while (!m_queue.isEmpty()) {
QueueElement element = m_queue.dequeue();
bool didCancel = element.plan->tryRemoveContextAndCancelIfLast(context);
if (!didCancel)
elements.append(WTFMove(element));
}
for (auto& element : elements)
m_queue.enqueue(WTFMove(element));
for (auto& thread : m_threads) {
if (thread->element.plan) {
bool didCancel = thread->element.plan->tryRemoveContextAndCancelIfLast(context);
if (didCancel) {
// We don't have to worry about the deadlocking since the thread can't block without checking for a new plan and must hold the lock to do so.
thread->synchronize.wait(*m_lock);
}
}
}
}
Worklist::Worklist()
: m_lock(Box<Lock>::create())
, m_planEnqueued(AutomaticThreadCondition::create())
{
unsigned numberOfCompilationThreads = Options::useConcurrentJIT() ? kernTCSMAwareNumberOfProcessorCores() : 1;
m_threads.reserveCapacity(numberOfCompilationThreads);
LockHolder locker(*m_lock);
for (unsigned i = 0; i < numberOfCompilationThreads; i++)
m_threads.uncheckedAppend(std::make_unique<Worklist::Thread>(locker, *this));
}
Worklist::~Worklist()
{
{
LockHolder locker(*m_lock);
m_queue.enqueue({ Priority::Shutdown, nextTicket(), nullptr });
m_planEnqueued->notifyAll(locker);
}
for (unsigned i = 0; i < m_threads.size(); ++i)
m_threads[i]->join();
}
static Worklist* globalWorklist;
Worklist* existingWorklistOrNull() { return globalWorklist; }
Worklist& ensureWorklist()
{
static std::once_flag initializeWorklist;
std::call_once(initializeWorklist, [] {
Worklist* worklist = new Worklist();
WTF::storeStoreFence();
globalWorklist = worklist;
});
return *globalWorklist;
}
} } // namespace JSC::Wasm
#endif // ENABLE(WEBASSEMBLY)