GCParallelTask.cpp (7640B)
1 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- 2 * vim: set ts=8 sts=2 et sw=2 tw=80: 3 * This Source Code Form is subject to the terms of the Mozilla Public 4 * License, v. 2.0. If a copy of the MPL was not distributed with this 5 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 6 7 #include "gc/GCParallelTask.h" 8 9 #include "mozilla/Maybe.h" 10 #include "mozilla/TimeStamp.h" 11 12 #include "gc/GCContext.h" 13 #include "gc/GCInternals.h" 14 #include "gc/ParallelWork.h" 15 #include "vm/HelperThreadState.h" 16 #include "vm/Runtime.h" 17 #include "vm/Time.h" 18 19 using namespace js; 20 using namespace js::gc; 21 22 using mozilla::Maybe; 23 using mozilla::TimeDuration; 24 using mozilla::TimeStamp; 25 26 js::GCParallelTask::~GCParallelTask() { 27 // The LinkedListElement destructor will remove us from any list we are part 28 // of without synchronization, so ensure that doesn't happen. 29 MOZ_DIAGNOSTIC_ASSERT(!isInList()); 30 31 // Only most-derived classes' destructors may do the join: base class 32 // destructors run after those for derived classes' members, so a join in a 33 // base class can't ensure that the task is done using the members. All we 34 // can do now is check that someone has previously stopped the task. 35 assertIdle(); 36 } 37 38 static bool ShouldMeasureTaskStartDelay() { 39 // We use many tasks during GC so randomly sample a small fraction for the 40 // purposes of recording telemetry. 41 return (rand() % 100) == 0; 42 } 43 44 void js::GCParallelTask::startWithLockHeld(AutoLockHelperThreadState& lock) { 45 MOZ_ASSERT(CanUseExtraThreads()); 46 MOZ_ASSERT(HelperThreadState().isInitialized(lock)); 47 assertIdle(); 48 49 maybeQueueTime_ = TimeStamp(); 50 if (ShouldMeasureTaskStartDelay()) { 51 maybeQueueTime_ = TimeStamp::Now(); 52 } 53 54 dispatchedToThreadPool = false; 55 56 gc->dispatchOrQueueParallelTask(this, lock); 57 } 58 59 void js::GCParallelTask::start() { 60 if (!CanUseExtraThreads()) { 61 runFromMainThread(); 62 return; 63 } 64 65 AutoLockHelperThreadState lock; 66 startWithLockHeld(lock); 67 } 68 69 void js::GCParallelTask::startOrRunIfIdle(AutoLockHelperThreadState& lock) { 70 if (wasStarted(lock)) { 71 return; 72 } 73 74 // Join the previous invocation of the task. This will return immediately 75 // if the thread has never been started. 76 joinWithLockHeld(lock); 77 78 if (!CanUseExtraThreads()) { 79 runFromMainThread(lock); 80 return; 81 } 82 83 startWithLockHeld(lock); 84 } 85 86 void js::GCParallelTask::cancelAndWait() { 87 MOZ_ASSERT(!isCancelled()); 88 cancel_ = true; 89 join(); 90 cancel_ = false; 91 } 92 93 void js::GCParallelTask::join(Maybe<TimeStamp> deadline) { 94 AutoLockHelperThreadState lock; 95 joinWithLockHeld(lock, deadline); 96 } 97 98 void js::GCParallelTask::joinWithLockHeld(AutoLockHelperThreadState& lock, 99 Maybe<TimeStamp> deadline) { 100 // Task has not been started; there's nothing to do. 101 if (isIdle(lock)) { 102 return; 103 } 104 105 if (lock.hasQueuedTasks()) { 106 // Unlock to allow task dispatch without lock held, otherwise we could wait 107 // forever. 108 AutoUnlockHelperThreadState unlock(lock); 109 } 110 111 if (isNotYetRunning(lock) && !dispatchedToThreadPool && 112 deadline.isNothing()) { 113 // If the task was dispatched but has not yet started then cancel the task 114 // and run it from the main thread. This stops us from blocking here when 115 // the helper threads are busy with other tasks. 116 MOZ_ASSERT(isInList()); 117 MOZ_ASSERT_IF(isDispatched(lock), gc->dispatchedParallelTasks != 0); 118 119 remove(); 120 runFromMainThread(lock); 121 } else { 122 // Otherwise wait for the task to complete. 123 joinNonIdleTask(deadline, lock); 124 } 125 126 if (isIdle(lock)) { 127 recordDuration(); 128 } 129 } 130 131 void GCParallelTask::recordDuration() { 132 if (phaseKind != gcstats::PhaseKind::NONE) { 133 gc->stats().recordParallelPhase(phaseKind, duration_); 134 } 135 } 136 137 void js::GCParallelTask::joinNonIdleTask(Maybe<TimeStamp> deadline, 138 AutoLockHelperThreadState& lock) { 139 MOZ_ASSERT(!isIdle(lock)); 140 141 while (!isFinished(lock)) { 142 TimeDuration timeout = TimeDuration::Forever(); 143 if (deadline) { 144 TimeStamp now = TimeStamp::Now(); 145 if (*deadline <= now) { 146 break; 147 } 148 timeout = *deadline - now; 149 } 150 151 HelperThreadState().wait(lock, timeout); 152 } 153 154 if (isFinished(lock)) { 155 setIdle(lock); 156 } 157 } 158 159 void js::GCParallelTask::runFromMainThread() { 160 AutoLockHelperThreadState lock; 161 runFromMainThread(lock); 162 } 163 164 void js::GCParallelTask::runFromMainThread(AutoLockHelperThreadState& lock) { 165 MOZ_ASSERT(isNotYetRunning(lock)); 166 MOZ_ASSERT(js::CurrentThreadCanAccessRuntime(gc->rt)); 167 168 if (lock.hasQueuedTasks()) { 169 // Unlock to allow task dispatch without lock held, otherwise we can wait 170 // forever. 171 AutoUnlockHelperThreadState unlock(lock); 172 } 173 174 runTask(gc->rt->gcContext(), lock); 175 setIdle(lock); 176 } 177 178 class MOZ_RAII AutoGCContext { 179 JS::GCContext context; 180 181 public: 182 explicit AutoGCContext(JSRuntime* runtime) : context(runtime) { 183 MOZ_RELEASE_ASSERT(TlsGCContext.init(), 184 "Failed to initialize TLS for GC context"); 185 186 MOZ_ASSERT(!TlsGCContext.get()); 187 TlsGCContext.set(&context); 188 } 189 190 ~AutoGCContext() { 191 MOZ_ASSERT(TlsGCContext.get() == &context); 192 TlsGCContext.set(nullptr); 193 } 194 195 JS::GCContext* get() { return &context; } 196 }; 197 198 void js::GCParallelTask::runHelperThreadTask(AutoLockHelperThreadState& lock) { 199 AutoGCContext gcContext(gc->rt); 200 runTask(gcContext.get(), lock); 201 MOZ_ASSERT(isFinished(lock)); 202 } 203 204 void GCParallelTask::runTask(JS::GCContext* gcx, 205 AutoLockHelperThreadState& lock) { 206 // Run the task from either the main thread or a helper thread. 207 208 bool wasDispatched = isDispatched(lock); 209 setRunning(lock); 210 211 AutoSetThreadGCUse setUse(gcx, use); 212 213 // The hazard analysis can't tell what the call to func_ will do but it's not 214 // allowed to GC. 215 JS::AutoSuppressGCAnalysis nogc; 216 217 TimeStamp timeStart = TimeStamp::Now(); 218 run(lock); 219 duration_ = TimeSince(timeStart); 220 221 if (maybeQueueTime_) { 222 TimeDuration delay = timeStart - maybeQueueTime_; 223 gc->rt->metrics().GC_TASK_START_DELAY_US(delay); 224 } 225 226 setFinished(lock); 227 gc->onParallelTaskEnd(wasDispatched, lock); 228 } 229 230 void GCParallelTask::onThreadPoolDispatch() { 231 MOZ_ASSERT(!dispatchedToThreadPool); 232 dispatchedToThreadPool = true; 233 } 234 235 void GCRuntime::dispatchOrQueueParallelTask( 236 GCParallelTask* task, const AutoLockHelperThreadState& lock) { 237 task->setQueued(lock); 238 queuedParallelTasks.ref().insertBack(task, lock); 239 maybeDispatchParallelTasks(lock); 240 } 241 242 void GCRuntime::maybeDispatchParallelTasks( 243 const AutoLockHelperThreadState& lock) { 244 MOZ_ASSERT(maxParallelThreads != 0); 245 MOZ_ASSERT(dispatchedParallelTasks <= maxParallelThreads); 246 247 while (dispatchedParallelTasks < maxParallelThreads && 248 !queuedParallelTasks.ref().isEmpty(lock)) { 249 GCParallelTask* task = queuedParallelTasks.ref().popFirst(lock); 250 task->setDispatched(lock); 251 HelperThreadState().submitTask(task, lock); 252 dispatchedParallelTasks++; 253 } 254 } 255 256 void GCRuntime::onParallelTaskEnd(bool wasDispatched, 257 const AutoLockHelperThreadState& lock) { 258 if (wasDispatched) { 259 MOZ_ASSERT(dispatchedParallelTasks != 0); 260 dispatchedParallelTasks--; 261 } 262 maybeDispatchParallelTasks(lock); 263 } 264 265 bool js::GCParallelTask::isIdle() const { 266 AutoLockHelperThreadState lock; 267 return isIdle(lock); 268 } 269 270 bool js::GCParallelTask::wasStarted() const { 271 AutoLockHelperThreadState lock; 272 return wasStarted(lock); 273 } 274 275 /* static */ 276 size_t js::gc::GCRuntime::parallelWorkerCount() const { 277 return std::min(helperThreadCount.ref(), MaxParallelWorkers); 278 }