|  | /* | 
|  | * Copyright (C) 2017-2021 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 | 
|  |  |