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