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