Chromium Code Reviews| OLD | NEW |
|---|---|
| 1 // Copyright 2016 the V8 project authors. All rights reserved. | 1 // Copyright 2016 the V8 project authors. All rights reserved. |
| 2 // Use of this source code is governed by a BSD-style license that can be | 2 // Use of this source code is governed by a BSD-style license that can be |
| 3 // found in the LICENSE file. | 3 // found in the LICENSE file. |
| 4 | 4 |
| 5 #include "src/compiler-dispatcher/compiler-dispatcher.h" | 5 #include "src/compiler-dispatcher/compiler-dispatcher.h" |
| 6 | 6 |
| 7 #include "include/v8-platform.h" | 7 #include "include/v8-platform.h" |
| 8 #include "include/v8.h" | 8 #include "include/v8.h" |
| 9 #include "src/base/platform/time.h" | 9 #include "src/base/platform/time.h" |
| 10 #include "src/cancelable-task.h" | 10 #include "src/cancelable-task.h" |
| (...skipping 48 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 59 try_catch.ReThrow(); | 59 try_catch.ReThrow(); |
| 60 } | 60 } |
| 61 return job->status() != CompileJobStatus::kFailed; | 61 return job->status() != CompileJobStatus::kFailed; |
| 62 } | 62 } |
| 63 | 63 |
| 64 bool IsFinished(CompilerDispatcherJob* job) { | 64 bool IsFinished(CompilerDispatcherJob* job) { |
| 65 return job->status() == CompileJobStatus::kDone || | 65 return job->status() == CompileJobStatus::kDone || |
| 66 job->status() == CompileJobStatus::kFailed; | 66 job->status() == CompileJobStatus::kFailed; |
| 67 } | 67 } |
| 68 | 68 |
| 69 bool CanRunOnAnyThread(CompilerDispatcherJob* job) { | |
| 70 return (job->status() == CompileJobStatus::kReadyToParse && | |
| 71 job->can_parse_on_background_thread()) || | |
| 72 (job->status() == CompileJobStatus::kReadyToCompile && | |
| 73 job->can_compile_on_background_thread()); | |
| 74 } | |
| 75 | |
| 76 void DoNextStepOnBackgroundThread(CompilerDispatcherJob* job) { | |
| 77 DCHECK(CanRunOnAnyThread(job)); | |
| 78 switch (job->status()) { | |
| 79 case CompileJobStatus::kReadyToParse: | |
| 80 job->Parse(); | |
| 81 break; | |
| 82 | |
| 83 case CompileJobStatus::kReadyToCompile: | |
| 84 job->Compile(); | |
| 85 break; | |
| 86 | |
| 87 default: | |
| 88 UNREACHABLE(); | |
| 89 } | |
| 90 } | |
| 91 | |
| 69 // Theoretically we get 50ms of idle time max, however it's unlikely that | 92 // Theoretically we get 50ms of idle time max, however it's unlikely that |
| 70 // we'll get all of it so try to be a conservative. | 93 // we'll get all of it so try to be a conservative. |
| 71 const double kMaxIdleTimeToExpectInMs = 40; | 94 const double kMaxIdleTimeToExpectInMs = 40; |
| 72 | 95 |
| 73 } // namespace | 96 } // namespace |
| 74 | 97 |
| 98 class CompilerDispatcher::BackgroundTask : public CancelableTask { | |
| 99 public: | |
| 100 BackgroundTask(Isolate* isolate, CompilerDispatcher* dispatcher); | |
| 101 ~BackgroundTask() override; | |
| 102 | |
| 103 // CancelableTask implementation. | |
| 104 void RunInternal() override; | |
| 105 | |
| 106 private: | |
| 107 CompilerDispatcher* dispatcher_; | |
| 108 | |
| 109 DISALLOW_COPY_AND_ASSIGN(BackgroundTask); | |
| 110 }; | |
| 111 | |
| 112 CompilerDispatcher::BackgroundTask::BackgroundTask( | |
| 113 Isolate* isolate, CompilerDispatcher* dispatcher) | |
| 114 : CancelableTask(isolate), dispatcher_(dispatcher) {} | |
| 115 | |
| 116 CompilerDispatcher::BackgroundTask::~BackgroundTask() {} | |
| 117 | |
| 118 void CompilerDispatcher::BackgroundTask::RunInternal() { | |
| 119 dispatcher_->DoBackgroundWork(); | |
| 120 } | |
| 121 | |
| 75 class CompilerDispatcher::IdleTask : public CancelableIdleTask { | 122 class CompilerDispatcher::IdleTask : public CancelableIdleTask { |
| 76 public: | 123 public: |
| 77 IdleTask(Isolate* isolate, CompilerDispatcher* dispatcher); | 124 IdleTask(Isolate* isolate, CompilerDispatcher* dispatcher); |
| 78 ~IdleTask() override; | 125 ~IdleTask() override; |
| 79 | 126 |
| 80 // CancelableIdleTask implementation. | 127 // CancelableIdleTask implementation. |
| 81 void RunInternal(double deadline_in_seconds) override; | 128 void RunInternal(double deadline_in_seconds) override; |
| 82 | 129 |
| 83 private: | 130 private: |
| 84 CompilerDispatcher* dispatcher_; | 131 CompilerDispatcher* dispatcher_; |
| (...skipping 10 matching lines...) Expand all Loading... | |
| 95 void CompilerDispatcher::IdleTask::RunInternal(double deadline_in_seconds) { | 142 void CompilerDispatcher::IdleTask::RunInternal(double deadline_in_seconds) { |
| 96 dispatcher_->DoIdleWork(deadline_in_seconds); | 143 dispatcher_->DoIdleWork(deadline_in_seconds); |
| 97 } | 144 } |
| 98 | 145 |
| 99 CompilerDispatcher::CompilerDispatcher(Isolate* isolate, Platform* platform, | 146 CompilerDispatcher::CompilerDispatcher(Isolate* isolate, Platform* platform, |
| 100 size_t max_stack_size) | 147 size_t max_stack_size) |
| 101 : isolate_(isolate), | 148 : isolate_(isolate), |
| 102 platform_(platform), | 149 platform_(platform), |
| 103 max_stack_size_(max_stack_size), | 150 max_stack_size_(max_stack_size), |
| 104 tracer_(new CompilerDispatcherTracer(isolate_)), | 151 tracer_(new CompilerDispatcherTracer(isolate_)), |
| 105 idle_task_scheduled_(false) {} | 152 idle_task_scheduled_(false), |
| 153 num_scheduled_background_tasks_(0), | |
| 154 num_available_background_jobs_(0), | |
| 155 main_thread_blocking_on_job_(nullptr) {} | |
| 106 | 156 |
| 107 CompilerDispatcher::~CompilerDispatcher() { | 157 CompilerDispatcher::~CompilerDispatcher() { |
| 108 // To avoid crashing in unit tests due to unfished jobs. | 158 // To avoid crashing in unit tests due to unfished jobs. |
| 109 AbortAll(BlockingBehavior::kBlock); | 159 AbortAll(BlockingBehavior::kBlock); |
| 110 } | 160 } |
| 111 | 161 |
| 112 bool CompilerDispatcher::Enqueue(Handle<SharedFunctionInfo> function) { | 162 bool CompilerDispatcher::Enqueue(Handle<SharedFunctionInfo> function) { |
| 113 if (!IsEnabled()) return false; | 163 if (!IsEnabled()) return false; |
| 114 | 164 |
| 115 // We only handle functions (no eval / top-level code / wasm) that are | 165 // We only handle functions (no eval / top-level code / wasm) that are |
| 116 // attached to a script. | 166 // attached to a script. |
| 117 if (!function->script()->IsScript() || !function->is_function() || | 167 if (!function->script()->IsScript() || !function->is_function() || |
| 118 function->asm_function() || function->native()) { | 168 function->asm_function() || function->native()) { |
| 119 return false; | 169 return false; |
| 120 } | 170 } |
| 121 | 171 |
| 122 if (IsEnqueued(function)) return true; | 172 if (IsEnqueued(function)) return true; |
| 123 std::unique_ptr<CompilerDispatcherJob> job(new CompilerDispatcherJob( | 173 std::unique_ptr<CompilerDispatcherJob> job(new CompilerDispatcherJob( |
| 124 isolate_, tracer_.get(), function, max_stack_size_)); | 174 isolate_, tracer_.get(), function, max_stack_size_)); |
| 125 std::pair<int, int> key(Script::cast(function->script())->id(), | 175 std::pair<int, int> key(Script::cast(function->script())->id(), |
| 126 function->function_literal_id()); | 176 function->function_literal_id()); |
| 127 jobs_.insert(std::make_pair(key, std::move(job))); | 177 jobs_.insert(std::make_pair(key, std::move(job))); |
| 128 ScheduleIdleTaskIfNeeded(); | 178 ScheduleIdleTaskIfNeeded(); |
| 129 return true; | 179 return true; |
| 130 } | 180 } |
| 131 | 181 |
| 132 bool CompilerDispatcher::IsEnabled() const { | 182 bool CompilerDispatcher::IsEnabled() const { |
| 133 v8::Isolate* v8_isolate = reinterpret_cast<v8::Isolate*>(isolate_); | 183 v8::Isolate* v8_isolate = reinterpret_cast<v8::Isolate*>(isolate_); |
| 134 return FLAG_compiler_dispatcher && platform_->IdleTasksEnabled(v8_isolate); | 184 return FLAG_compiler_dispatcher && platform_->IdleTasksEnabled(v8_isolate) && |
| 185 platform_->NumberOfAvailableBackgroundThreads() > 0; | |
|
vogelheim
2017/01/02 17:16:24
Generally speaking, it would be nice if the dispat
jochen (gone - plz use gerrit)
2017/01/02 19:39:39
fair point. Since we only schedule background task
| |
| 135 } | 186 } |
| 136 | 187 |
| 137 bool CompilerDispatcher::IsEnqueued(Handle<SharedFunctionInfo> function) const { | 188 bool CompilerDispatcher::IsEnqueued(Handle<SharedFunctionInfo> function) const { |
| 138 return GetJobFor(function) != jobs_.end(); | 189 return GetJobFor(function) != jobs_.end(); |
| 139 } | 190 } |
| 140 | 191 |
| 192 void CompilerDispatcher::EnsureNotOnBackground(CompilerDispatcherJob* job) { | |
| 193 base::LockGuard<base::Mutex> lock(&mutex_); | |
| 194 if (running_background_jobs_.find(job) == running_background_jobs_.end()) { | |
| 195 pending_background_jobs_.erase(job); | |
|
vogelheim
2017/01/02 17:16:24
Do you need to update num_available_background_job
jochen (gone - plz use gerrit)
2017/01/02 19:39:39
actually, I don't need that variable anymore, so I
| |
| 196 return; | |
| 197 } | |
| 198 DCHECK_NULL(main_thread_blocking_on_job_); | |
| 199 main_thread_blocking_on_job_ = job; | |
| 200 while (main_thread_blocking_on_job_ != nullptr) { | |
| 201 main_thread_blocking_signal_.Wait(&mutex_); | |
| 202 } | |
| 203 DCHECK(pending_background_jobs_.find(job) == pending_background_jobs_.end()); | |
| 204 } | |
| 205 | |
| 141 bool CompilerDispatcher::FinishNow(Handle<SharedFunctionInfo> function) { | 206 bool CompilerDispatcher::FinishNow(Handle<SharedFunctionInfo> function) { |
| 142 JobMap::const_iterator job = GetJobFor(function); | 207 JobMap::const_iterator job = GetJobFor(function); |
| 143 CHECK(job != jobs_.end()); | 208 CHECK(job != jobs_.end()); |
| 144 | 209 |
| 145 // TODO(jochen): Check if there's an in-flight background task working on this | 210 EnsureNotOnBackground(job->second.get()); |
| 146 // job. | |
| 147 while (!IsFinished(job->second.get())) { | 211 while (!IsFinished(job->second.get())) { |
| 148 DoNextStepOnMainThread(isolate_, job->second.get(), | 212 DoNextStepOnMainThread(isolate_, job->second.get(), |
| 149 ExceptionHandling::kThrow); | 213 ExceptionHandling::kThrow); |
| 150 } | 214 } |
| 151 bool result = job->second->status() != CompileJobStatus::kFailed; | 215 bool result = job->second->status() != CompileJobStatus::kFailed; |
| 152 job->second->ResetOnMainThread(); | 216 job->second->ResetOnMainThread(); |
| 153 jobs_.erase(job); | 217 jobs_.erase(job); |
| 154 return result; | 218 return result; |
| 155 } | 219 } |
| 156 | 220 |
| 157 void CompilerDispatcher::Abort(Handle<SharedFunctionInfo> function, | 221 void CompilerDispatcher::Abort(Handle<SharedFunctionInfo> function, |
| 158 BlockingBehavior blocking) { | 222 BlockingBehavior blocking) { |
| 159 USE(blocking); | 223 USE(blocking); |
| 160 JobMap::const_iterator job = GetJobFor(function); | 224 JobMap::const_iterator job = GetJobFor(function); |
| 161 CHECK(job != jobs_.end()); | 225 CHECK(job != jobs_.end()); |
| 162 | 226 |
| 163 // TODO(jochen): Check if there's an in-flight background task working on this | 227 EnsureNotOnBackground(job->second.get()); |
| 164 // job. | |
| 165 job->second->ResetOnMainThread(); | 228 job->second->ResetOnMainThread(); |
| 166 jobs_.erase(job); | 229 jobs_.erase(job); |
| 167 } | 230 } |
| 168 | 231 |
| 169 void CompilerDispatcher::AbortAll(BlockingBehavior blocking) { | 232 void CompilerDispatcher::AbortAll(BlockingBehavior blocking) { |
| 170 USE(blocking); | 233 USE(blocking); |
| 171 // TODO(jochen): Check if there's an in-flight background task working on this | |
| 172 // job. | |
| 173 for (auto& kv : jobs_) { | 234 for (auto& kv : jobs_) { |
| 235 EnsureNotOnBackground(kv.second.get()); | |
| 174 kv.second->ResetOnMainThread(); | 236 kv.second->ResetOnMainThread(); |
| 175 } | 237 } |
| 176 jobs_.clear(); | 238 jobs_.clear(); |
| 177 } | 239 } |
| 178 | 240 |
| 179 CompilerDispatcher::JobMap::const_iterator CompilerDispatcher::GetJobFor( | 241 CompilerDispatcher::JobMap::const_iterator CompilerDispatcher::GetJobFor( |
| 180 Handle<SharedFunctionInfo> shared) const { | 242 Handle<SharedFunctionInfo> shared) const { |
| 181 if (!shared->script()->IsScript()) return jobs_.end(); | 243 if (!shared->script()->IsScript()) return jobs_.end(); |
| 182 std::pair<int, int> key(Script::cast(shared->script())->id(), | 244 std::pair<int, int> key(Script::cast(shared->script())->id(), |
| 183 shared->function_literal_id()); | 245 shared->function_literal_id()); |
| 184 auto range = jobs_.equal_range(key); | 246 auto range = jobs_.equal_range(key); |
| 185 for (auto job = range.first; job != range.second; ++job) { | 247 for (auto job = range.first; job != range.second; ++job) { |
| 186 if (job->second->IsAssociatedWith(shared)) return job; | 248 if (job->second->IsAssociatedWith(shared)) return job; |
| 187 } | 249 } |
| 188 return jobs_.end(); | 250 return jobs_.end(); |
| 189 } | 251 } |
| 190 | 252 |
| 191 void CompilerDispatcher::ScheduleIdleTaskIfNeeded() { | 253 void CompilerDispatcher::ScheduleIdleTaskIfNeeded() { |
| 192 v8::Isolate* v8_isolate = reinterpret_cast<v8::Isolate*>(isolate_); | 254 v8::Isolate* v8_isolate = reinterpret_cast<v8::Isolate*>(isolate_); |
| 193 DCHECK(platform_->IdleTasksEnabled(v8_isolate)); | 255 DCHECK(platform_->IdleTasksEnabled(v8_isolate)); |
| 194 if (idle_task_scheduled_) return; | 256 if (idle_task_scheduled_) return; |
| 195 if (jobs_.empty()) return; | 257 if (jobs_.empty()) return; |
| 196 idle_task_scheduled_ = true; | 258 idle_task_scheduled_ = true; |
| 197 platform_->CallIdleOnForegroundThread(v8_isolate, | 259 platform_->CallIdleOnForegroundThread(v8_isolate, |
| 198 new IdleTask(isolate_, this)); | 260 new IdleTask(isolate_, this)); |
| 199 } | 261 } |
| 200 | 262 |
| 263 void CompilerDispatcher::ConsiderJobForBackgroundProcessing( | |
| 264 CompilerDispatcherJob* job) { | |
| 265 if (!CanRunOnAnyThread(job)) return; | |
| 266 { | |
| 267 base::LockGuard<base::Mutex> lock(&mutex_); | |
| 268 pending_background_jobs_.insert(job); | |
| 269 ++num_available_background_jobs_; | |
| 270 } | |
| 271 ScheduleMoreBackgroundTasksIfNeeded(); | |
| 272 } | |
| 273 | |
| 274 void CompilerDispatcher::ScheduleMoreBackgroundTasksIfNeeded() { | |
| 275 base::LockGuard<base::Mutex> lock(&mutex_); | |
| 276 if (num_available_background_jobs_ == 0) return; | |
| 277 if (platform_->NumberOfAvailableBackgroundThreads() <= | |
| 278 num_scheduled_background_tasks_) { | |
| 279 return; | |
| 280 } | |
| 281 ++num_scheduled_background_tasks_; | |
| 282 platform_->CallOnBackgroundThread(new BackgroundTask(isolate_, this), | |
| 283 v8::Platform::kShortRunningTask); | |
| 284 } | |
| 285 | |
| 286 void CompilerDispatcher::DoBackgroundWork() { | |
| 287 CompilerDispatcherJob* job = nullptr; | |
| 288 { | |
| 289 base::LockGuard<base::Mutex> lock(&mutex_); | |
| 290 --num_scheduled_background_tasks_; | |
| 291 if (!pending_background_jobs_.empty()) { | |
| 292 auto it = pending_background_jobs_.begin(); | |
| 293 job = *it; | |
| 294 pending_background_jobs_.erase(it); | |
| 295 running_background_jobs_.insert(job); | |
| 296 --num_available_background_jobs_; | |
| 297 } | |
| 298 } | |
| 299 if (job == nullptr) return; | |
| 300 DoNextStepOnBackgroundThread(job); | |
| 301 { | |
| 302 base::LockGuard<base::Mutex> lock(&mutex_); | |
| 303 running_background_jobs_.erase(job); | |
| 304 | |
| 305 if (main_thread_blocking_on_job_ == job) { | |
| 306 main_thread_blocking_on_job_ = nullptr; | |
| 307 main_thread_blocking_signal_.NotifyOne(); | |
| 308 } | |
| 309 } | |
| 310 ScheduleMoreBackgroundTasksIfNeeded(); | |
| 311 } | |
| 312 | |
| 201 void CompilerDispatcher::DoIdleWork(double deadline_in_seconds) { | 313 void CompilerDispatcher::DoIdleWork(double deadline_in_seconds) { |
| 202 idle_task_scheduled_ = false; | 314 idle_task_scheduled_ = false; |
| 203 | 315 |
| 204 // Number of jobs that are unlikely to make progress during any idle callback | 316 // Number of jobs that are unlikely to make progress during any idle callback |
| 205 // due to their estimated duration. | 317 // due to their estimated duration. |
| 206 size_t too_long_jobs = 0; | 318 size_t too_long_jobs = 0; |
| 207 | 319 |
| 208 // Iterate over all available jobs & remaining time. For each job, decide | 320 // Iterate over all available jobs & remaining time. For each job, decide |
| 209 // whether to 1) skip it (if it would take too long), 2) erase it (if it's | 321 // whether to 1) skip it (if it would take too long), 2) erase it (if it's |
| 210 // finished), or 3) make progress on it. | 322 // finished), or 3) make progress on it. |
| 211 double idle_time_in_seconds = | 323 double idle_time_in_seconds = |
| 212 deadline_in_seconds - platform_->MonotonicallyIncreasingTime(); | 324 deadline_in_seconds - platform_->MonotonicallyIncreasingTime(); |
| 213 for (auto job = jobs_.begin(); | 325 for (auto job = jobs_.begin(); |
| 214 job != jobs_.end() && idle_time_in_seconds > 0.0; | 326 job != jobs_.end() && idle_time_in_seconds > 0.0; |
| 215 idle_time_in_seconds = | 327 idle_time_in_seconds = |
| 216 deadline_in_seconds - platform_->MonotonicallyIncreasingTime()) { | 328 deadline_in_seconds - platform_->MonotonicallyIncreasingTime()) { |
| 329 { | |
| 330 // Don't work on jobs that are being worked on by background tasks. | |
| 331 // Similarly, remove jobs we work on from the set of available background | |
| 332 // jobs. | |
| 333 base::LockGuard<base::Mutex> lock(&mutex_); | |
| 334 if (running_background_jobs_.find(job->second.get()) != | |
| 335 running_background_jobs_.end()) { | |
| 336 ++job; | |
| 337 continue; | |
| 338 } | |
| 339 auto it = pending_background_jobs_.find(job->second.get()); | |
| 340 if (it != pending_background_jobs_.end()) { | |
| 341 pending_background_jobs_.erase(it); | |
| 342 --num_available_background_jobs_; | |
| 343 } | |
|
vogelheim
2017/01/02 17:16:24
This logic is a bit weird, because first we take i
| |
| 344 } | |
|
vogelheim
2017/01/02 17:16:24
[opinion ahead; feel free to ignore:]
The can-id
jochen (gone - plz use gerrit)
2017/01/02 19:39:39
the can-idle property can change, when the estimat
vogelheim
2017/01/03 10:32:37
Hmm. Not sure I'm willing to give this up yet. For
jochen (gone - plz use gerrit)
2017/01/03 12:59:35
in d8, we do get predictable idle work calls, howe
| |
| 217 double estimate_in_ms = job->second->EstimateRuntimeOfNextStepInMs(); | 345 double estimate_in_ms = job->second->EstimateRuntimeOfNextStepInMs(); |
| 218 if (idle_time_in_seconds < | 346 if (idle_time_in_seconds < |
| 219 (estimate_in_ms / | 347 (estimate_in_ms / |
| 220 static_cast<double>(base::Time::kMillisecondsPerSecond))) { | 348 static_cast<double>(base::Time::kMillisecondsPerSecond))) { |
| 221 // If there's not enough time left, try to estimate whether we would | 349 // If there's not enough time left, try to estimate whether we would |
| 222 // have managed to finish the job in a large idle task to assess | 350 // have managed to finish the job in a large idle task to assess |
| 223 // whether we should ask for another idle callback. | 351 // whether we should ask for another idle callback. |
| 224 if (estimate_in_ms > kMaxIdleTimeToExpectInMs) ++too_long_jobs; | 352 if (estimate_in_ms > kMaxIdleTimeToExpectInMs) ++too_long_jobs; |
| 353 ConsiderJobForBackgroundProcessing(job->second.get()); | |
| 225 ++job; | 354 ++job; |
| 226 } else if (IsFinished(job->second.get())) { | 355 } else if (IsFinished(job->second.get())) { |
| 227 job->second->ResetOnMainThread(); | 356 job->second->ResetOnMainThread(); |
| 228 job = jobs_.erase(job); | 357 job = jobs_.erase(job); |
| 229 break; | 358 break; |
| 230 } else { | 359 } else { |
| 231 // Do one step, and keep processing the job (as we don't advance the | 360 // Do one step, and keep processing the job (as we don't advance the |
| 232 // iterator). | 361 // iterator). |
| 233 DoNextStepOnMainThread(isolate_, job->second.get(), | 362 DoNextStepOnMainThread(isolate_, job->second.get(), |
| 234 ExceptionHandling::kSwallow); | 363 ExceptionHandling::kSwallow); |
| 235 } | 364 } |
| 236 } | 365 } |
| 237 if (jobs_.size() > too_long_jobs) ScheduleIdleTaskIfNeeded(); | 366 if (jobs_.size() > too_long_jobs) ScheduleIdleTaskIfNeeded(); |
| 238 } | 367 } |
| 239 | 368 |
| 240 } // namespace internal | 369 } // namespace internal |
| 241 } // namespace v8 | 370 } // namespace v8 |
| OLD | NEW |