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