mirror of
https://github.com/darlinghq/darling-JavaScriptCore.git
synced 2024-11-23 04:09:40 +00:00
265 lines
9.8 KiB
C++
265 lines
9.8 KiB
C++
/*
|
|
* 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. AND ITS CONTRIBUTORS ``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 ITS 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 "MarkingConstraintSolver.h"
|
|
|
|
#include "JSCInlines.h"
|
|
#include "MarkingConstraintSet.h"
|
|
|
|
namespace JSC {
|
|
|
|
MarkingConstraintSolver::MarkingConstraintSolver(MarkingConstraintSet& set)
|
|
: m_heap(set.m_heap)
|
|
, m_mainVisitor(m_heap.collectorSlotVisitor())
|
|
, m_set(set)
|
|
{
|
|
m_heap.forEachSlotVisitor(
|
|
[&] (SlotVisitor& visitor) {
|
|
m_visitCounters.append(VisitCounter(visitor));
|
|
});
|
|
}
|
|
|
|
MarkingConstraintSolver::~MarkingConstraintSolver()
|
|
{
|
|
}
|
|
|
|
bool MarkingConstraintSolver::didVisitSomething() const
|
|
{
|
|
for (const VisitCounter& visitCounter : m_visitCounters) {
|
|
if (visitCounter.visitCount())
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
void MarkingConstraintSolver::execute(SchedulerPreference preference, ScopedLambda<Optional<unsigned>()> pickNext)
|
|
{
|
|
m_pickNextIsStillActive = true;
|
|
RELEASE_ASSERT(!m_numThreadsThatMayProduceWork);
|
|
|
|
if (Options::useParallelMarkingConstraintSolver()) {
|
|
dataLogIf(Options::logGC(), preference == ParallelWorkFirst ? "P" : "N", "<");
|
|
|
|
m_heap.runFunctionInParallel(
|
|
[&] (SlotVisitor& visitor) { runExecutionThread(visitor, preference, pickNext); });
|
|
|
|
dataLogIf(Options::logGC(), ">");
|
|
} else
|
|
runExecutionThread(m_mainVisitor, preference, pickNext);
|
|
|
|
RELEASE_ASSERT(!m_pickNextIsStillActive);
|
|
RELEASE_ASSERT(!m_numThreadsThatMayProduceWork);
|
|
|
|
if (!m_toExecuteSequentially.isEmpty()) {
|
|
for (unsigned indexToRun : m_toExecuteSequentially)
|
|
execute(*m_set.m_set[indexToRun]);
|
|
m_toExecuteSequentially.clear();
|
|
}
|
|
|
|
RELEASE_ASSERT(m_toExecuteInParallel.isEmpty());
|
|
}
|
|
|
|
void MarkingConstraintSolver::drain(BitVector& unexecuted)
|
|
{
|
|
auto iter = unexecuted.begin();
|
|
auto end = unexecuted.end();
|
|
if (iter == end)
|
|
return;
|
|
auto pickNext = scopedLambda<Optional<unsigned>()>(
|
|
[&] () -> Optional<unsigned> {
|
|
if (iter == end)
|
|
return WTF::nullopt;
|
|
return *iter++;
|
|
});
|
|
execute(NextConstraintFirst, pickNext);
|
|
unexecuted.clearAll();
|
|
}
|
|
|
|
void MarkingConstraintSolver::converge(const Vector<MarkingConstraint*>& order)
|
|
{
|
|
if (didVisitSomething())
|
|
return;
|
|
|
|
if (order.isEmpty())
|
|
return;
|
|
|
|
size_t index = 0;
|
|
|
|
// We want to execute the first constraint sequentially if we think it will quickly give us a
|
|
// result. If we ran it in parallel to other constraints, then we might end up having to wait for
|
|
// those other constraints to finish, which would be a waste of time since during convergence it's
|
|
// empirically most optimal to return to draining as soon as a constraint generates work. Most
|
|
// constraints don't generate any work most of the time, and when they do generate work, they tend
|
|
// to generate enough of it to feed a decent draining cycle. Therefore, pause times are lowest if
|
|
// we get the heck out of here as soon as a constraint generates work. I think that part of what
|
|
// makes this optimal is that we also never abort running a constraint early, so when we do run
|
|
// one, it has an opportunity to generate as much work as it possibly can.
|
|
if (order[index]->quickWorkEstimate(m_mainVisitor) > 0.) {
|
|
execute(*order[index++]);
|
|
|
|
if (m_toExecuteInParallel.isEmpty()
|
|
&& (order.isEmpty() || didVisitSomething()))
|
|
return;
|
|
}
|
|
|
|
auto pickNext = scopedLambda<Optional<unsigned>()>(
|
|
[&] () -> Optional<unsigned> {
|
|
if (didVisitSomething())
|
|
return WTF::nullopt;
|
|
|
|
if (index >= order.size())
|
|
return WTF::nullopt;
|
|
|
|
MarkingConstraint& constraint = *order[index++];
|
|
return constraint.index();
|
|
});
|
|
|
|
execute(ParallelWorkFirst, pickNext);
|
|
}
|
|
|
|
void MarkingConstraintSolver::execute(MarkingConstraint& constraint)
|
|
{
|
|
if (m_executed.get(constraint.index()))
|
|
return;
|
|
|
|
constraint.prepareToExecute(NoLockingNecessary, m_mainVisitor);
|
|
constraint.execute(m_mainVisitor);
|
|
m_executed.set(constraint.index());
|
|
}
|
|
|
|
void MarkingConstraintSolver::addParallelTask(RefPtr<SharedTask<void(SlotVisitor&)>> task, MarkingConstraint& constraint)
|
|
{
|
|
auto locker = holdLock(m_lock);
|
|
m_toExecuteInParallel.append(TaskWithConstraint(WTFMove(task), &constraint));
|
|
}
|
|
|
|
void MarkingConstraintSolver::runExecutionThread(SlotVisitor& visitor, SchedulerPreference preference, ScopedLambda<Optional<unsigned>()> pickNext)
|
|
{
|
|
for (;;) {
|
|
bool doParallelWorkMode;
|
|
MarkingConstraint* constraint = nullptr;
|
|
unsigned indexToRun = UINT_MAX;
|
|
TaskWithConstraint task;
|
|
{
|
|
auto locker = holdLock(m_lock);
|
|
|
|
for (;;) {
|
|
auto tryParallelWork = [&] () -> bool {
|
|
if (m_toExecuteInParallel.isEmpty())
|
|
return false;
|
|
|
|
task = m_toExecuteInParallel.first();
|
|
constraint = task.constraint;
|
|
doParallelWorkMode = true;
|
|
return true;
|
|
};
|
|
|
|
auto tryNextConstraint = [&] () -> bool {
|
|
if (!m_pickNextIsStillActive)
|
|
return false;
|
|
|
|
for (;;) {
|
|
Optional<unsigned> pickResult = pickNext();
|
|
if (!pickResult) {
|
|
m_pickNextIsStillActive = false;
|
|
return false;
|
|
}
|
|
|
|
if (m_executed.get(*pickResult))
|
|
continue;
|
|
|
|
MarkingConstraint& candidateConstraint = *m_set.m_set[*pickResult];
|
|
if (candidateConstraint.concurrency() == ConstraintConcurrency::Sequential) {
|
|
m_toExecuteSequentially.append(*pickResult);
|
|
continue;
|
|
}
|
|
if (candidateConstraint.parallelism() == ConstraintParallelism::Parallel)
|
|
m_numThreadsThatMayProduceWork++;
|
|
indexToRun = *pickResult;
|
|
constraint = &candidateConstraint;
|
|
doParallelWorkMode = false;
|
|
constraint->prepareToExecute(locker, visitor);
|
|
return true;
|
|
}
|
|
};
|
|
|
|
if (preference == ParallelWorkFirst) {
|
|
if (tryParallelWork() || tryNextConstraint())
|
|
break;
|
|
} else {
|
|
if (tryNextConstraint() || tryParallelWork())
|
|
break;
|
|
}
|
|
|
|
// This means that we have nothing left to run. The only way for us to have more work is
|
|
// if someone is running a constraint that may produce parallel work.
|
|
|
|
if (!m_numThreadsThatMayProduceWork)
|
|
return;
|
|
|
|
// FIXME: Any waiting could be replaced with just running the SlotVisitor.
|
|
// I wonder if that would be profitable.
|
|
m_condition.wait(m_lock);
|
|
}
|
|
}
|
|
|
|
if (doParallelWorkMode)
|
|
constraint->doParallelWork(visitor, *task.task);
|
|
else {
|
|
if (constraint->parallelism() == ConstraintParallelism::Parallel) {
|
|
visitor.m_currentConstraint = constraint;
|
|
visitor.m_currentSolver = this;
|
|
}
|
|
|
|
constraint->execute(visitor);
|
|
|
|
visitor.m_currentConstraint = nullptr;
|
|
visitor.m_currentSolver = nullptr;
|
|
}
|
|
|
|
{
|
|
auto locker = holdLock(m_lock);
|
|
|
|
if (doParallelWorkMode) {
|
|
if (!m_toExecuteInParallel.isEmpty()
|
|
&& task == m_toExecuteInParallel.first())
|
|
m_toExecuteInParallel.takeFirst();
|
|
else
|
|
ASSERT(!m_toExecuteInParallel.contains(task));
|
|
} else {
|
|
if (constraint->parallelism() == ConstraintParallelism::Parallel)
|
|
m_numThreadsThatMayProduceWork--;
|
|
m_executed.set(indexToRun);
|
|
}
|
|
|
|
m_condition.notifyAll();
|
|
}
|
|
}
|
|
}
|
|
|
|
} // namespace JSC
|
|
|