V8 API Reference, 7.2.502.16 (for Deno 0.2.4)
compiler-dispatcher.cc
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 
5 #include "src/compiler-dispatcher/compiler-dispatcher.h"
6 
7 #include "src/ast/ast.h"
8 #include "src/base/platform/time.h"
9 #include "src/base/template-utils.h"
10 #include "src/cancelable-task.h"
11 #include "src/compiler.h"
12 #include "src/flags.h"
13 #include "src/global-handles.h"
14 #include "src/objects-inl.h"
15 #include "src/parsing/parse-info.h"
16 #include "src/parsing/parser.h"
17 #include "src/task-utils.h"
18 
19 namespace v8 {
20 namespace internal {
21 
22 CompilerDispatcher::Job::Job(BackgroundCompileTask* task_arg)
23  : task(task_arg), has_run(false), aborted(false) {}
24 
25 CompilerDispatcher::Job::~Job() = default;
26 
27 CompilerDispatcher::CompilerDispatcher(Isolate* isolate, Platform* platform,
28  size_t max_stack_size)
29  : isolate_(isolate),
30  allocator_(isolate->allocator()),
31  worker_thread_runtime_call_stats_(
32  isolate->counters()->worker_thread_runtime_call_stats()),
33  background_compile_timer_(
34  isolate->counters()->compile_function_on_background()),
35  taskrunner_(platform->GetForegroundTaskRunner(
36  reinterpret_cast<v8::Isolate*>(isolate))),
37  platform_(platform),
38  max_stack_size_(max_stack_size),
39  trace_compiler_dispatcher_(FLAG_trace_compiler_dispatcher),
40  task_manager_(new CancelableTaskManager()),
41  next_job_id_(0),
42  shared_to_unoptimized_job_id_(isolate->heap()),
43  idle_task_scheduled_(false),
44  num_worker_tasks_(0),
45  main_thread_blocking_on_job_(nullptr),
46  block_for_testing_(false),
47  semaphore_for_testing_(0) {
48  if (trace_compiler_dispatcher_ && !IsEnabled()) {
49  PrintF("CompilerDispatcher: dispatcher is disabled\n");
50  }
51 }
52 
53 CompilerDispatcher::~CompilerDispatcher() {
54  // AbortAll must be called before CompilerDispatcher is destroyed.
55  CHECK(task_manager_->canceled());
56 }
57 
58 base::Optional<CompilerDispatcher::JobId> CompilerDispatcher::Enqueue(
59  const ParseInfo* outer_parse_info, const AstRawString* function_name,
60  const FunctionLiteral* function_literal) {
61  TRACE_EVENT0(TRACE_DISABLED_BY_DEFAULT("v8.compile"),
62  "V8.CompilerDispatcherEnqueue");
63  RuntimeCallTimerScope runtimeTimer(
64  isolate_, RuntimeCallCounterId::kCompileEnqueueOnDispatcher);
65 
66  if (!IsEnabled()) return base::nullopt;
67 
68  std::unique_ptr<Job> job = base::make_unique<Job>(new BackgroundCompileTask(
69  allocator_, outer_parse_info, function_name, function_literal,
70  worker_thread_runtime_call_stats_, background_compile_timer_,
71  static_cast<int>(max_stack_size_)));
72  JobMap::const_iterator it = InsertJob(std::move(job));
73  JobId id = it->first;
74  if (trace_compiler_dispatcher_) {
75  PrintF("CompilerDispatcher: enqueued job %zu for function literal id %d\n",
76  id, function_literal->function_literal_id());
77  }
78 
79  // Post a a background worker task to perform the compilation on the worker
80  // thread.
81  {
82  base::MutexGuard lock(&mutex_);
83  pending_background_jobs_.insert(it->second.get());
84  }
85  ScheduleMoreWorkerTasksIfNeeded();
86  return base::make_optional(id);
87 }
88 
89 bool CompilerDispatcher::IsEnabled() const { return FLAG_compiler_dispatcher; }
90 
91 bool CompilerDispatcher::IsEnqueued(Handle<SharedFunctionInfo> function) const {
92  if (jobs_.empty()) return false;
93  return GetJobFor(function) != jobs_.end();
94 }
95 
96 bool CompilerDispatcher::IsEnqueued(JobId job_id) const {
97  return jobs_.find(job_id) != jobs_.end();
98 }
99 
100 void CompilerDispatcher::RegisterSharedFunctionInfo(
101  JobId job_id, SharedFunctionInfo* function) {
102  DCHECK_NE(jobs_.find(job_id), jobs_.end());
103 
104  if (trace_compiler_dispatcher_) {
105  PrintF("CompilerDispatcher: registering ");
106  function->ShortPrint();
107  PrintF(" with job id %zu\n", job_id);
108  }
109 
110  // Make a global handle to the function.
111  Handle<SharedFunctionInfo> function_handle =
112  isolate_->global_handles()->Create(function);
113 
114  // Register mapping.
115  auto job_it = jobs_.find(job_id);
116  DCHECK_NE(job_it, jobs_.end());
117  Job* job = job_it->second.get();
118  shared_to_unoptimized_job_id_.Set(function_handle, job_id);
119 
120  {
121  base::MutexGuard lock(&mutex_);
122  job->function = function_handle;
123  if (job->IsReadyToFinalize(lock)) {
124  // Schedule an idle task to finalize job if it is ready.
125  ScheduleIdleTaskFromAnyThread(lock);
126  }
127  }
128 }
129 
130 void CompilerDispatcher::WaitForJobIfRunningOnBackground(Job* job) {
131  TRACE_EVENT0(TRACE_DISABLED_BY_DEFAULT("v8.compile"),
132  "V8.CompilerDispatcherWaitForBackgroundJob");
133  RuntimeCallTimerScope runtimeTimer(
134  isolate_, RuntimeCallCounterId::kCompileWaitForDispatcher);
135 
136  base::MutexGuard lock(&mutex_);
137  if (running_background_jobs_.find(job) == running_background_jobs_.end()) {
138  pending_background_jobs_.erase(job);
139  return;
140  }
141  DCHECK_NULL(main_thread_blocking_on_job_);
142  main_thread_blocking_on_job_ = job;
143  while (main_thread_blocking_on_job_ != nullptr) {
144  main_thread_blocking_signal_.Wait(&mutex_);
145  }
146  DCHECK(pending_background_jobs_.find(job) == pending_background_jobs_.end());
147  DCHECK(running_background_jobs_.find(job) == running_background_jobs_.end());
148 }
149 
150 bool CompilerDispatcher::FinishNow(Handle<SharedFunctionInfo> function) {
151  TRACE_EVENT0(TRACE_DISABLED_BY_DEFAULT("v8.compile"),
152  "V8.CompilerDispatcherFinishNow");
153  RuntimeCallTimerScope runtimeTimer(
154  isolate_, RuntimeCallCounterId::kCompileFinishNowOnDispatcher);
155  if (trace_compiler_dispatcher_) {
156  PrintF("CompilerDispatcher: finishing ");
157  function->ShortPrint();
158  PrintF(" now\n");
159  }
160 
161  JobMap::const_iterator it = GetJobFor(function);
162  CHECK(it != jobs_.end());
163  Job* job = it->second.get();
164  WaitForJobIfRunningOnBackground(job);
165 
166  if (!job->has_run) {
167  job->task->Run();
168  job->has_run = true;
169  }
170 
171  DCHECK(job->IsReadyToFinalize(&mutex_));
172  DCHECK(!job->aborted);
173  bool success = Compiler::FinalizeBackgroundCompileTask(
174  job->task.get(), function, isolate_, Compiler::KEEP_EXCEPTION);
175 
176  DCHECK_NE(success, isolate_->has_pending_exception());
177  RemoveJob(it);
178  return success;
179 }
180 
181 void CompilerDispatcher::AbortJob(JobId job_id) {
182  if (trace_compiler_dispatcher_) {
183  PrintF("CompilerDispatcher: aborted job %zu\n", job_id);
184  }
185  JobMap::const_iterator job_it = jobs_.find(job_id);
186  Job* job = job_it->second.get();
187 
188  base::LockGuard<base::Mutex> lock(&mutex_);
189  pending_background_jobs_.erase(job);
190  if (running_background_jobs_.find(job) == running_background_jobs_.end()) {
191  RemoveJob(job_it);
192  } else {
193  // Job is currently running on the background thread, wait until it's done
194  // and remove job then.
195  job->aborted = true;
196  }
197 }
198 
199 void CompilerDispatcher::AbortAll() {
200  task_manager_->TryAbortAll();
201 
202  for (auto& it : jobs_) {
203  WaitForJobIfRunningOnBackground(it.second.get());
204  if (trace_compiler_dispatcher_) {
205  PrintF("CompilerDispatcher: aborted job %zu\n", it.first);
206  }
207  }
208  jobs_.clear();
209  shared_to_unoptimized_job_id_.Clear();
210  {
211  base::MutexGuard lock(&mutex_);
212  DCHECK(pending_background_jobs_.empty());
213  DCHECK(running_background_jobs_.empty());
214  }
215 
216  task_manager_->CancelAndWait();
217 }
218 
219 CompilerDispatcher::JobMap::const_iterator CompilerDispatcher::GetJobFor(
220  Handle<SharedFunctionInfo> shared) const {
221  JobId* job_id_ptr = shared_to_unoptimized_job_id_.Find(shared);
222  JobMap::const_iterator job = jobs_.end();
223  if (job_id_ptr) {
224  job = jobs_.find(*job_id_ptr);
225  }
226  return job;
227 }
228 
229 void CompilerDispatcher::ScheduleIdleTaskFromAnyThread(
230  const base::MutexGuard&) {
231  if (!taskrunner_->IdleTasksEnabled()) return;
232  if (idle_task_scheduled_) return;
233 
234  idle_task_scheduled_ = true;
235  taskrunner_->PostIdleTask(MakeCancelableIdleTask(
236  task_manager_.get(),
237  [this](double deadline_in_seconds) { DoIdleWork(deadline_in_seconds); }));
238 }
239 
240 void CompilerDispatcher::ScheduleMoreWorkerTasksIfNeeded() {
241  TRACE_EVENT0(TRACE_DISABLED_BY_DEFAULT("v8.compile"),
242  "V8.CompilerDispatcherScheduleMoreWorkerTasksIfNeeded");
243  {
244  base::MutexGuard lock(&mutex_);
245  if (pending_background_jobs_.empty()) return;
246  if (platform_->NumberOfWorkerThreads() <= num_worker_tasks_) {
247  return;
248  }
249  ++num_worker_tasks_;
250  }
251  platform_->CallOnWorkerThread(
252  MakeCancelableTask(task_manager_.get(), [this] { DoBackgroundWork(); }));
253 }
254 
255 void CompilerDispatcher::DoBackgroundWork() {
256  TRACE_EVENT0(TRACE_DISABLED_BY_DEFAULT("v8.compile"),
257  "V8.CompilerDispatcherDoBackgroundWork");
258  for (;;) {
259  Job* job = nullptr;
260  {
261  base::MutexGuard lock(&mutex_);
262  if (!pending_background_jobs_.empty()) {
263  auto it = pending_background_jobs_.begin();
264  job = *it;
265  pending_background_jobs_.erase(it);
266  running_background_jobs_.insert(job);
267  }
268  }
269  if (job == nullptr) break;
270 
271  if (V8_UNLIKELY(block_for_testing_.Value())) {
272  block_for_testing_.SetValue(false);
273  semaphore_for_testing_.Wait();
274  }
275 
276  if (trace_compiler_dispatcher_) {
277  PrintF("CompilerDispatcher: doing background work\n");
278  }
279 
280  job->task->Run();
281 
282  {
283  base::MutexGuard lock(&mutex_);
284  running_background_jobs_.erase(job);
285 
286  job->has_run = true;
287  if (job->IsReadyToFinalize(lock)) {
288  // Schedule an idle task to finalize the compilation on the main thread
289  // if the job has a shared function info registered.
290  ScheduleIdleTaskFromAnyThread(lock);
291  }
292 
293  if (main_thread_blocking_on_job_ == job) {
294  main_thread_blocking_on_job_ = nullptr;
295  main_thread_blocking_signal_.NotifyOne();
296  }
297  }
298  }
299 
300  {
301  base::MutexGuard lock(&mutex_);
302  --num_worker_tasks_;
303  }
304  // Don't touch |this| anymore after this point, as it might have been
305  // deleted.
306 }
307 
308 void CompilerDispatcher::DoIdleWork(double deadline_in_seconds) {
309  TRACE_EVENT0(TRACE_DISABLED_BY_DEFAULT("v8.compile"),
310  "V8.CompilerDispatcherDoIdleWork");
311  {
312  base::MutexGuard lock(&mutex_);
313  idle_task_scheduled_ = false;
314  }
315 
316  if (trace_compiler_dispatcher_) {
317  PrintF("CompilerDispatcher: received %0.1lfms of idle time\n",
318  (deadline_in_seconds - platform_->MonotonicallyIncreasingTime()) *
319  static_cast<double>(base::Time::kMillisecondsPerSecond));
320  }
321  while (deadline_in_seconds > platform_->MonotonicallyIncreasingTime()) {
322  // Find a job which is pending finalization and has a shared function info
323  CompilerDispatcher::JobMap::const_iterator it;
324  {
325  base::MutexGuard lock(&mutex_);
326  for (it = jobs_.cbegin(); it != jobs_.cend(); ++it) {
327  if (it->second->IsReadyToFinalize(lock)) break;
328  }
329  // Since we hold the lock here, we can be sure no jobs have become ready
330  // for finalization while we looped through the list.
331  if (it == jobs_.cend()) return;
332 
333  DCHECK(it->second->IsReadyToFinalize(lock));
334  DCHECK_EQ(running_background_jobs_.find(it->second.get()),
335  running_background_jobs_.end());
336  DCHECK_EQ(pending_background_jobs_.find(it->second.get()),
337  pending_background_jobs_.end());
338  }
339 
340  Job* job = it->second.get();
341  if (!job->aborted) {
342  Compiler::FinalizeBackgroundCompileTask(
343  job->task.get(), job->function.ToHandleChecked(), isolate_,
344  Compiler::CLEAR_EXCEPTION);
345  }
346  RemoveJob(it);
347  }
348 
349  // We didn't return above so there still might be jobs to finalize.
350  {
351  base::MutexGuard lock(&mutex_);
352  ScheduleIdleTaskFromAnyThread(lock);
353  }
354 }
355 
356 CompilerDispatcher::JobMap::const_iterator CompilerDispatcher::InsertJob(
357  std::unique_ptr<Job> job) {
358  bool added;
359  JobMap::const_iterator it;
360  std::tie(it, added) =
361  jobs_.insert(std::make_pair(next_job_id_++, std::move(job)));
362  DCHECK(added);
363  return it;
364 }
365 
366 CompilerDispatcher::JobMap::const_iterator CompilerDispatcher::RemoveJob(
367  CompilerDispatcher::JobMap::const_iterator it) {
368  Job* job = it->second.get();
369 
370  DCHECK_EQ(running_background_jobs_.find(job), running_background_jobs_.end());
371  DCHECK_EQ(pending_background_jobs_.find(job), pending_background_jobs_.end());
372 
373  // Delete SFI associated with job if its been registered.
374  Handle<SharedFunctionInfo> function;
375  if (job->function.ToHandle(&function)) {
376  GlobalHandles::Destroy(function.location());
377  }
378 
379  // Delete job.
380  return jobs_.erase(it);
381 }
382 
383 } // namespace internal
384 } // namespace v8
Definition: libplatform.h:13