v8
V8 is Google’s open source high-performance JavaScript and WebAssembly engine, written in C++.
Loading...
Searching...
No Matches
lazy-compile-dispatcher.cc
Go to the documentation of this file.
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
3// found in the LICENSE file.
4
6
7#include <atomic>
8
13#include "src/common/globals.h"
15#include "src/flags/flags.h"
22#include "src/parsing/scanner.h"
25#include "src/zone/zone-list-inl.h" // crbug.com/v8/8816
26
27namespace v8 {
28namespace internal {
29
30// The maximum amount of time we should allow a single function's FinishNow to
31// spend opportunistically finalizing other finalizable jobs.
32static constexpr int kMaxOpportunisticFinalizeTimeMs = 1;
33
35 public:
36 explicit JobTask(LazyCompileDispatcher* lazy_compile_dispatcher)
37 : lazy_compile_dispatcher_(lazy_compile_dispatcher) {}
38
39 void Run(JobDelegate* delegate) final {
41 }
42
43 size_t GetMaxConcurrency(size_t worker_count) const final {
45 std::memory_order_relaxed);
46 if (v8_flags.lazy_compile_dispatcher_max_threads == 0) return n;
47 return std::min(
48 n, static_cast<size_t>(v8_flags.lazy_compile_dispatcher_max_threads));
49 }
50
51 private:
53};
54
55LazyCompileDispatcher::Job::Job(std::unique_ptr<BackgroundCompileTask> task)
56 : task(std::move(task)), state(Job::State::kPending) {}
57
59
61 Platform* platform,
62 size_t max_stack_size)
63 : isolate_(isolate),
65 isolate->counters()->worker_thread_runtime_call_stats()),
67 isolate->counters()->compile_function_on_background()),
68 taskrunner_(platform->GetForegroundTaskRunner(
69 reinterpret_cast<v8::Isolate*>(isolate))),
70 platform_(platform),
71 max_stack_size_(max_stack_size),
72 trace_compiler_dispatcher_(v8_flags.trace_compiler_dispatcher),
77 block_for_testing_(false),
80 std::make_unique<JobTask>(this));
81}
82
84 // AbortAll must be called before LazyCompileDispatcher is destroyed.
85 CHECK(!job_handle_->IsValid());
86}
87
88namespace {
89
90// If the SharedFunctionInfo's UncompiledData has a job slot, then write into
91// it. Otherwise, allocate a new UncompiledData with a job slot, and then write
92// into that. Since we have two optional slots (preparse data and job), this
93// gets a little messy.
94void SetUncompiledDataJobPointer(LocalIsolate* isolate,
96 Address job_address) {
97 Tagged<UncompiledData> uncompiled_data =
98 shared_info->uncompiled_data(isolate);
99 switch (uncompiled_data->map(isolate)->instance_type()) {
100 // The easy cases -- we already have a job slot, so can write into it and
101 // return.
102 case UNCOMPILED_DATA_WITH_PREPARSE_DATA_AND_JOB_TYPE:
104 ->set_job(job_address);
105 break;
106 case UNCOMPILED_DATA_WITHOUT_PREPARSE_DATA_WITH_JOB_TYPE:
108 ->set_job(job_address);
109 break;
110
111 // Otherwise, we'll have to allocate a new UncompiledData (with or without
112 // preparse data as appropriate), set the job pointer on that, and update
113 // the SharedFunctionInfo to use the new UncompiledData
114 case UNCOMPILED_DATA_WITH_PREPARSE_DATA_TYPE: {
115 Handle<String> inferred_name(uncompiled_data->inferred_name(), isolate);
116 Handle<PreparseData> preparse_data(
118 ->preparse_data(),
119 isolate);
121 isolate->factory()->NewUncompiledDataWithPreparseDataAndJob(
122 inferred_name, uncompiled_data->start_position(),
123 uncompiled_data->end_position(), preparse_data);
124
125 new_uncompiled_data->set_job(job_address);
126 shared_info->set_uncompiled_data(*new_uncompiled_data);
127 break;
128 }
129 case UNCOMPILED_DATA_WITHOUT_PREPARSE_DATA_TYPE: {
130 DCHECK(IsUncompiledDataWithoutPreparseData(uncompiled_data));
131 Handle<String> inferred_name(uncompiled_data->inferred_name(), isolate);
132 DirectHandle<UncompiledDataWithoutPreparseDataWithJob>
133 new_uncompiled_data =
134 isolate->factory()->NewUncompiledDataWithoutPreparseDataWithJob(
135 inferred_name, uncompiled_data->start_position(),
136 uncompiled_data->end_position());
137
138 new_uncompiled_data->set_job(job_address);
139 shared_info->set_uncompiled_data(*new_uncompiled_data);
140 break;
141 }
142
143 default:
144 UNREACHABLE();
145 }
146}
147
148} // namespace
149
151 LocalIsolate* isolate, Handle<SharedFunctionInfo> shared_info,
152 std::unique_ptr<Utf16CharacterStream> character_stream) {
154 "V8.LazyCompilerDispatcherEnqueue");
155 RCS_SCOPE(isolate, RuntimeCallCounterId::kCompileEnqueueOnDispatcher);
156
157 Job* job = new Job(std::make_unique<BackgroundCompileTask>(
158 isolate_, shared_info, std::move(character_stream),
160 static_cast<int>(max_stack_size_)));
161
162 SetUncompiledDataJobPointer(isolate, shared_info,
163 reinterpret_cast<Address>(job));
164
165 // Post a background worker task to perform the compilation on the worker
166 // thread.
167 {
170 PrintF("LazyCompileDispatcher: enqueued job for ");
171 ShortPrint(*shared_info);
172 PrintF("\n");
173 }
174
175#ifdef DEBUG
176 all_jobs_.insert(job);
177#endif
178 pending_background_jobs_.push_back(job);
180 }
181 // This is not in NotifyAddedBackgroundJob to avoid being inside the mutex.
182 job_handle_->NotifyConcurrencyIncrease();
183}
184
187 if (!shared->HasUncompiledData()) return false;
188 Job* job = nullptr;
189 Tagged<UncompiledData> data = shared->uncompiled_data(isolate_);
190 if (IsUncompiledDataWithPreparseDataAndJob(data)) {
191 job = reinterpret_cast<Job*>(
193 } else if (IsUncompiledDataWithoutPreparseDataWithJob(data)) {
194 job = reinterpret_cast<Job*>(
196 }
197 return job != nullptr;
198}
199
201 Job* job, const base::MutexGuard& lock) {
203 "V8.LazyCompilerDispatcherWaitForBackgroundJob");
204 RCS_SCOPE(isolate_, RuntimeCallCounterId::kCompileWaitForDispatcher);
205
206 if (!job->is_running_on_background()) {
207 if (job->state == Job::State::kPending) {
208 DCHECK_EQ(std::count(pending_background_jobs_.begin(),
209 pending_background_jobs_.end(), job),
210 1);
211
212 // TODO(leszeks): Remove from pending jobs without walking the whole
213 // vector.
215 std::remove(pending_background_jobs_.begin(),
216 pending_background_jobs_.end(), job),
220 } else {
222 DCHECK_EQ(
223 std::count(finalizable_jobs_.begin(), finalizable_jobs_.end(), job),
224 1);
225
226 // TODO(leszeks): Remove from finalizable jobs without walking the whole
227 // vector.
228 finalizable_jobs_.erase(
229 std::remove(finalizable_jobs_.begin(), finalizable_jobs_.end(), job),
230 finalizable_jobs_.end());
232 }
233 return;
234 }
237 while (main_thread_blocking_on_job_ != nullptr) {
239 }
240
242 DCHECK_EQ(std::count(finalizable_jobs_.begin(), finalizable_jobs_.end(), job),
243 1);
244
245 // TODO(leszeks): Remove from finalizable jobs without walking the whole
246 // vector.
247 finalizable_jobs_.erase(
248 std::remove(finalizable_jobs_.begin(), finalizable_jobs_.end(), job),
249 finalizable_jobs_.end());
251}
252
256 "V8.LazyCompilerDispatcherFinishNow");
257 RCS_SCOPE(isolate_, RuntimeCallCounterId::kCompileFinishNowOnDispatcher);
259 PrintF("LazyCompileDispatcher: finishing ");
260 ShortPrint(*function);
261 PrintF(" now\n");
262 }
263
264 Job* job;
265
266 {
268 job = GetJobFor(function, lock);
270 }
271
273 job->task->RunOnMainThread(isolate_);
275 }
276
277 if (DEBUG_BOOL) {
279 DCHECK_EQ(std::count(pending_background_jobs_.begin(),
280 pending_background_jobs_.end(), job),
281 0);
282 DCHECK_EQ(
283 std::count(finalizable_jobs_.begin(), finalizable_jobs_.end(), job), 0);
285 }
286
290
291 DCHECK_NE(success, isolate_->has_exception());
292 DeleteJob(job);
293
294 // Opportunistically finalize all other jobs for a maximum time of
295 // kMaxOpportunisticFinalizeTimeMs.
296 double deadline_in_seconds = platform_->MonotonicallyIncreasingTime() +
298 while (deadline_in_seconds > platform_->MonotonicallyIncreasingTime()) {
299 if (!FinalizeSingleJob()) break;
300 }
301
302 return success;
303}
304
308 PrintF("LazyCompileDispatcher: aborting job for ");
309 ShortPrint(*shared_info);
310 PrintF("\n");
311 }
313
314 Job* job = GetJobFor(shared_info, lock);
315 if (job->is_running_on_background()) {
316 // Job is currently running on the background thread, wait until it's done
317 // and remove job then.
319 } else {
320 if (job->state == Job::State::kPending) {
321 DCHECK_EQ(std::count(pending_background_jobs_.begin(),
322 pending_background_jobs_.end(), job),
323 1);
324
326 std::remove(pending_background_jobs_.begin(),
327 pending_background_jobs_.end(), job),
331 } else if (job->state == Job::State::kReadyToFinalize) {
332 DCHECK_EQ(
333 std::count(finalizable_jobs_.begin(), finalizable_jobs_.end(), job),
334 1);
335
336 finalizable_jobs_.erase(
337 std::remove(finalizable_jobs_.begin(), finalizable_jobs_.end(), job),
338 finalizable_jobs_.end());
340 } else {
341 UNREACHABLE();
342 }
343 job->task->AbortFunction();
345 DeleteJob(job, lock);
346 }
347}
348
350 idle_task_manager_->TryAbortAll();
351 job_handle_->Cancel();
352
353 {
355 for (Job* job : pending_background_jobs_) {
356 job->task->AbortFunction();
357 job->state = Job::State::kFinalized;
358 DeleteJob(job, lock);
359 }
361 for (Job* job : finalizable_jobs_) {
362 job->task->AbortFunction();
363 job->state = Job::State::kFinalized;
364 DeleteJob(job, lock);
365 }
366 finalizable_jobs_.clear();
367 for (Job* job : jobs_to_dispose_) {
368 delete job;
369 }
370 jobs_to_dispose_.clear();
371
372 DCHECK_EQ(all_jobs_.size(), 0);
375 }
376
377 idle_task_manager_->CancelAndWait();
378}
379
382 if (!shared->HasUncompiledData()) return nullptr;
383 Tagged<UncompiledData> data = shared->uncompiled_data(isolate_);
384 if (IsUncompiledDataWithPreparseDataAndJob(data)) {
385 return reinterpret_cast<Job*>(
387 } else if (IsUncompiledDataWithoutPreparseDataWithJob(data)) {
388 return reinterpret_cast<Job*>(
390 }
391 return nullptr;
392}
393
395 const base::MutexGuard&) {
396 if (!taskrunner_->IdleTasksEnabled()) return;
397 if (idle_task_scheduled_) return;
398
400 // TODO(leszeks): Using a full task manager for a single cancellable task is
401 // overkill, we could probably do the cancelling ourselves.
403 idle_task_manager_.get(),
404 [this](double deadline_in_seconds) { DoIdleWork(deadline_in_seconds); }));
405}
406
409 "V8.LazyCompileDispatcherDoBackgroundWork");
410
412 UnparkedScope unparked_scope(&isolate);
413 LocalHandleScope handle_scope(&isolate);
414
415 ReusableUnoptimizedCompileState reusable_state(&isolate);
416
417 while (true) {
418 // Return immediately on yield, avoiding the second loop.
419 if (delegate->ShouldYield()) return;
420
421 Job* job = nullptr;
422 {
424
425 if (pending_background_jobs_.empty()) break;
426 job = pending_background_jobs_.back();
427 pending_background_jobs_.pop_back();
429
431 }
432
436 }
437
439 PrintF("LazyCompileDispatcher: doing background work\n");
440 }
441
442 job->task->Run(&isolate, &reusable_state);
443
444 {
446 if (job->state == Job::State::kRunning) {
448 // Schedule an idle task to finalize the compilation on the main thread
449 // if the job has a shared function info registered.
450 } else {
453 }
454 finalizable_jobs_.push_back(job);
456
457 if (main_thread_blocking_on_job_ == job) {
460 } else {
462 }
463 }
464 }
465
466 while (!delegate->ShouldYield()) {
467 Job* job = nullptr;
468 {
470 if (jobs_to_dispose_.empty()) break;
471 job = jobs_to_dispose_.back();
472 jobs_to_dispose_.pop_back();
473 if (jobs_to_dispose_.empty()) {
475 }
476 }
477 delete job;
478 }
479
480 // Don't touch |this| anymore after this point, as it might have been
481 // deleted.
482}
483
486
487 if (finalizable_jobs_.empty()) return nullptr;
488
489 Job* job = finalizable_jobs_.back();
490 finalizable_jobs_.pop_back();
495 } else {
498 }
499 return job;
500}
501
503 Job* job = PopSingleFinalizeJob();
504 if (job == nullptr) return false;
505
507 PrintF("LazyCompileDispatcher: idle finalizing job\n");
508 }
509
510 if (job->state == Job::State::kFinalizingNow) {
511 HandleScope scope(isolate_);
514 } else {
516 job->task->AbortFunction();
517 }
519 DeleteJob(job);
520 return true;
521}
522
523void LazyCompileDispatcher::DoIdleWork(double deadline_in_seconds) {
525 "V8.LazyCompilerDispatcherDoIdleWork");
526 {
528 idle_task_scheduled_ = false;
529 }
530
532 PrintF("LazyCompileDispatcher: received %0.1lfms of idle time\n",
533 (deadline_in_seconds - platform_->MonotonicallyIncreasingTime()) *
534 static_cast<double>(base::Time::kMillisecondsPerSecond));
535 }
536 while (deadline_in_seconds > platform_->MonotonicallyIncreasingTime()) {
537 // Find a job which is pending finalization and has a shared function info
538 auto there_was_a_job = FinalizeSingleJob();
539 if (!there_was_a_job) return;
540 }
541
542 // We didn't return above so there still might be jobs to finalize.
543 {
546 }
547}
548
554
557#ifdef DEBUG
558 all_jobs_.erase(job);
559#endif
560 jobs_to_dispose_.push_back(job);
561 if (jobs_to_dispose_.size() == 1) {
563 }
564}
565
566#ifdef DEBUG
568 size_t pending_jobs = 0;
569 size_t running_jobs = 0;
570 size_t finalizable_jobs = 0;
571
572 for (Job* job : all_jobs_) {
573 switch (job->state) {
575 pending_jobs++;
576 break;
579 running_jobs++;
580 break;
583 finalizable_jobs++;
584 break;
589 // Ignore.
590 break;
591 }
592 }
593
594 CHECK_EQ(pending_background_jobs_.size(), pending_jobs);
595 CHECK_EQ(finalizable_jobs_.size(), finalizable_jobs);
597 pending_jobs + running_jobs + (jobs_to_dispose_.empty() ? 0 : 1));
598}
599#endif
600
601} // namespace internal
602} // namespace v8
virtual bool ShouldYield()=0
std::unique_ptr< JobHandle > PostJob(TaskPriority priority, std::unique_ptr< JobTask > job_task, const SourceLocation &location=SourceLocation::Current())
virtual double MonotonicallyIncreasingTime()=0
V8_INLINE void SetValue(T new_value)
V8_INLINE T Value() const
static constexpr int64_t kMillisecondsPerSecond
Definition time.h:45
static bool FinalizeBackgroundCompileTask(BackgroundCompileTask *task, Isolate *isolate, ClearExceptionFlag flag)
Definition compiler.cc:3159
JobTask(LazyCompileDispatcher *lazy_compile_dispatcher)
size_t GetMaxConcurrency(size_t worker_count) const final
void Enqueue(LocalIsolate *isolate, Handle< SharedFunctionInfo > shared_info, std::unique_ptr< Utf16CharacterStream > character_stream)
void DoIdleWork(double deadline_in_seconds)
bool FinishNow(DirectHandle< SharedFunctionInfo > function)
void ScheduleIdleTaskFromAnyThread(const base::MutexGuard &)
bool IsEnqueued(DirectHandle< SharedFunctionInfo > function) const
void NotifyRemovedBackgroundJob(const base::MutexGuard &lock)
LazyCompileDispatcher(Isolate *isolate, Platform *platform, size_t max_stack_size)
void NotifyAddedBackgroundJob(const base::MutexGuard &lock)
WorkerThreadRuntimeCallStats * worker_thread_runtime_call_stats_
base::ConditionVariable main_thread_blocking_signal_
std::unique_ptr< CancelableTaskManager > idle_task_manager_
void AbortJob(DirectHandle< SharedFunctionInfo > function)
Job * GetJobFor(DirectHandle< SharedFunctionInfo > shared, const base::MutexGuard &) const
std::shared_ptr< TaskRunner > taskrunner_
void WaitForJobIfRunningOnBackground(Job *job, const base::MutexGuard &)
void VerifyBackgroundTaskCount(const base::MutexGuard &)
#define DEBUG_BOOL
Definition globals.h:87
Isolate * isolate
int n
Definition mul-fft.cc:296
STL namespace.
void PrintF(const char *format,...)
Definition utils.cc:39
void ShortPrint(Tagged< Object > obj, FILE *out)
Definition objects.cc:1865
std::unique_ptr< CancelableIdleTask > MakeCancelableIdleTask(Isolate *isolate, std::function< void(double)> func)
Definition task-utils.cc:53
static constexpr int kMaxOpportunisticFinalizeTimeMs
V8_EXPORT_PRIVATE FlagValues v8_flags
Tagged< To > Cast(Tagged< From > value, const v8::SourceLocation &loc=INIT_SOURCE_LOCATION_IN_DEBUG)
Definition casting.h:150
#define RCS_SCOPE(...)
#define DCHECK_NULL(val)
Definition logging.h:491
#define CHECK(condition)
Definition logging.h:124
#define DCHECK_NE(v1, v2)
Definition logging.h:486
#define CHECK_EQ(lhs, rhs)
#define DCHECK(condition)
Definition logging.h:482
#define DCHECK_EQ(v1, v2)
Definition logging.h:485
std::unique_ptr< BackgroundCompileTask > task
Job(std::unique_ptr< BackgroundCompileTask > task)
#define TRACE_EVENT0(category_group, name)
#define TRACE_DISABLED_BY_DEFAULT(name)
#define V8_UNLIKELY(condition)
Definition v8config.h:660